Skip to content

Commit

Permalink
ci: automate pinning charms in regression tests (#1215)
Browse files Browse the repository at this point in the history
Switch to using pinned versions of the charm (latest main) for our charm
tests in the GitHub Actions.

- [x] charmcraft-pack.yaml
- [x] db-charm-tests.yaml
- [x] ~framework-tests.yaml~ no change needed
- [x] hello-charm-tests.yaml
- [x] observability-charm-tests.yaml
- [x] ~publish.yml~ aggregates other workflows
- [x] ~test-publish.yml~ aggregates other workflows
- [x] automation to update pins
   - [x] detect changes
   - [x] create PR to update pins
   - [x] setup, token 

Also I've re-enabled mysql-k8s charm test, as that apparently got fixed
upstream.

Here's the configuration for the personal access token I'm using to
develop the external charm "dependabot":
- read/write access to the repo
- `workflow` scope to push branches with changes to workflows

![Screenshot 2024-05-23 at 17 07
50](https://github.com/canonical/operator/assets/662249/21f2e825-00f9-4f6e-afc2-c37d0ca21c35)

Co-authored-by: Tony Meyer <tony.meyer@gmail.com>
  • Loading branch information
dimaqq and tonyandrewmeyer authored May 29, 2024
1 parent 16240bf commit e40f78d
Show file tree
Hide file tree
Showing 11 changed files with 233 additions and 43 deletions.
29 changes: 29 additions & 0 deletions .github/actions/update-charm-pins/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
name: Update Charm Pins
description: Updates pinned versions of external charms we use to test our changes against to prevent regressions
author: Dima Tisnek <dimaqq@gmail.com>
branding:
icon: activity
color: orange

inputs:
workflows:
description: Whitespace-separated paths to the local workflow file, relative to repository root
required: true
gh-pat:
description: Personal access token to check out external repos from github
required: true

runs:
using: composite
steps:
- uses: actions/setup-python@v5
with:
python-version: "3.12"

- run: python -m pip install -r .github/actions/update-charm-pins/requirements.txt
shell: bash
- run: python .github/actions/update-charm-pins/main.py '${{ inputs.workflows }}'
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.gh-pat }}
55 changes: 55 additions & 0 deletions .github/actions/update-charm-pins/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2024 Canonical Ltd.

"""Updates pinned versions of charms in tests."""

import logging
import os
import sys

from httpx import Client
from ruamel.yaml import YAML

yaml = YAML(typ="rt")
yaml.indent(mapping=2, sequence=4, offset=2)

github = Client(
base_url="https://api.github.com/repos",
headers={
"Authorization": f'token {os.getenv("GITHUB_TOKEN")}',
"Accept": "application/vnd.github.v3+json",
},
)


def update_charm_pins(workflow):
"""Update pinned versions of charms in the given GitHub Actions workflow."""
with open(workflow) as file:
doc = yaml.load(file)

# Assume the workflow has a single job or the first job is parameterized with charm repos
job_name = next(iter(doc["jobs"]))

for idx, item in enumerate(doc["jobs"][job_name]["strategy"]["matrix"]["include"]):
charm_repo = item["charm-repo"]
commit = github.get(f"{charm_repo}/commits").raise_for_status().json()[0]
data = github.get(f"{charm_repo}/tags").raise_for_status().json()
comment = " ".join(
[tag["name"] for tag in data if tag["commit"]["sha"] == commit["sha"]]
+ [commit["commit"]["committer"]["date"]]
)

# A YAML node, as opposed to a plain value, can be updated in place to tweak comments
node = doc.mlget(
["jobs", job_name, "strategy", "matrix", "include", idx], list_ok=True
)
node["commit"] = commit["sha"]
node.yaml_add_eol_comment(comment, key="commit")

with open(workflow, "w") as file:
yaml.dump(doc, file)


if __name__ == "__main__":
logging.basicConfig(level="INFO")
for workflow in " ".join(sys.argv[1:]).split():
update_charm_pins(workflow)
27 changes: 27 additions & 0 deletions .github/actions/update-charm-pins/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Update Charm Pins

## GitHub Actions Usage

Inputs:

- `workflows`: space or newline-separated list of workflow YAML files relative to this repository root
- `gh-pat`: personal access token to query external repositories hosted at GitHub

This action will update the `workflows` in the current checkout. It is the responsibility of the caller
to do something with these changes.

## Local Usage

```command
# set up a venv and install the deps
pip install -r requirements.txt

# set the GITHUB_TOKEN env var with a personal access token
export GITHUB_TOKEN=ghp_0123456789

# run the script
python main.py path-to/.github/workflows/one.yaml path-to/.github/workflows/another.yaml

# check the modifications in the current branch
git diff
```
2 changes: 2 additions & 0 deletions .github/actions/update-charm-pins/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ruamel.yaml==0.18.6
httpx==0.27.0
56 changes: 31 additions & 25 deletions .github/workflows/charmcraft-pack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,39 @@ jobs:
charmcraft-pack:
runs-on: ubuntu-22.04

strategy:
matrix:
include:
- charm-repo: jnsgruk/hello-kubecon
commit: dbd133466dde59ee64f20a732a8f3d2e560ec3b8 # 2023-07-03T14:09:38Z
steps:
- name: Checkout test charm repository
uses: actions/checkout@v4
with:
repository: jnsgruk/hello-kubecon
- name: Checkout test charm repository
uses: actions/checkout@v4
with:
repository: ${{ matrix.charm-repo }}
ref: ${{ matrix.commit }}

- name: Update 'ops' dependency in test charm to latest
run: |
sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements.txt
if [ -z "${{ github.event.pull_request.head.sha }}" ]
then
echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements.txt
else
# If on a PR, we need to reference the PR branch's repo and commit (not the GITHUB_SHA
# temporary merge commit), because charmcraft pack does a git checkout which
# can't see the temporary merge commit.
echo -e "\ngit+${{ github.event.pull_request.head.repo.clone_url }}@${{ github.event.pull_request.head.sha }}#egg=ops" >> requirements.txt
fi
cat requirements.txt
- name: Update 'ops' dependency in test charm to latest
run: |
sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements.txt
if [ -z "${{ github.event.pull_request.head.sha }}" ]
then
echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements.txt
else
# If on a PR, we need to reference the PR branch's repo and commit (not the GITHUB_SHA
# temporary merge commit), because charmcraft pack does a git checkout which
# can't see the temporary merge commit.
echo -e "\ngit+${{ github.event.pull_request.head.repo.clone_url }}@${{ github.event.pull_request.head.sha }}#egg=ops" >> requirements.txt
fi
cat requirements.txt
- name: Set up LXD
uses: canonical/setup-lxd@7be523c4c2724a31218a627809044c6a2f0870ad
with:
channel: 5.0/stable
- name: Set up LXD
uses: canonical/setup-lxd@7be523c4c2724a31218a627809044c6a2f0870ad
with:
channel: 5.0/stable

- name: Install charmcraft
run: sudo snap install charmcraft --classic
- name: Install charmcraft
run: sudo snap install charmcraft --classic

- name: Pack the charm
run: sudo charmcraft pack --verbose
- name: Pack the charm
run: sudo charmcraft pack --verbose
18 changes: 10 additions & 8 deletions .github/workflows/db-charm-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ jobs:
strategy:
fail-fast: false
matrix:
charm-repo:
- "canonical/postgresql-operator"
- "canonical/postgresql-k8s-operator"
- "canonical/mysql-operator"
# TODO: uncomment once secrets issues are fixed in this charm:
# https://github.com/canonical/mysql-k8s-operator/pull/371
# - "canonical/mysql-k8s-operator"

include:
- charm-repo: canonical/postgresql-operator
commit: 4feeaeee102cbf5e3dada3c05d44e0495ca68f9a # rev409 2024-05-21T12:52:24Z
- charm-repo: canonical/postgresql-k8s-operator
commit: 1a25c3929747beea7e78467b169b2b345b29d470 # 2024-05-21T12:40:19Z
- charm-repo: canonical/mysql-operator
commit: 19633f3e904d1c3296477b3df191d1ca265fc0d5 # rev234 2024-05-06T12:13:54Z
- charm-repo: canonical/mysql-k8s-operator
commit: 6c09910bc3bd88eb632793d08fa17340c6903cb2 # rev138 2024-05-01T18:08:13Z
steps:
- name: Checkout the ${{ matrix.charm-repo }} repository
uses: actions/checkout@v4
with:
repository: ${{ matrix.charm-repo }}
ref: ${{ matrix.commit }}

- name: Checkout the operator repository
uses: actions/checkout@v4
Expand Down
12 changes: 7 additions & 5 deletions .github/workflows/hello-charm-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ jobs:

strategy:
matrix:
charm-repo:
- "jnsgruk/hello-kubecon"
- "juju/hello-juju-charm"

include:
- charm-repo: jnsgruk/hello-kubecon
commit: dbd133466dde59ee64f20a732a8f3d2e560ec3b8 # 2023-07-03T14:09:38Z
- charm-repo: juju/hello-juju-charm
commit: 046b8ce758660d5aa9cf05207e2370fcbab688d0 # 2021-12-16T10:10:24Z
steps:
- name: Set up Python 3.8
uses: actions/setup-python@v5
with:
python-version: "3.8"
python-version: '3.8'

- name: Checkout the ${{ matrix.charm-repo }} repository
uses: actions/checkout@v4
with:
repository: ${{ matrix.charm-repo }}
ref: ${{ matrix.commit }}

- name: Remove 'ops' from charm requirements.txt
run: |
Expand Down
13 changes: 8 additions & 5 deletions .github/workflows/observability-charm-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ jobs:
strategy:
fail-fast: false
matrix:
charm-repo:
- "canonical/alertmanager-k8s-operator"
- "canonical/prometheus-k8s-operator"
- "canonical/grafana-k8s-operator"

include:
- charm-repo: canonical/alertmanager-k8s-operator
commit: 90b85c79dfdeeeedcc538c92dcbc26fb2b931088 # rev114 2024-05-23T12:09:22Z
- charm-repo: canonical/prometheus-k8s-operator
commit: 41b10003b2e7aba34e26fa387af4297d97ecb535 # rev189 2024-05-21T21:25:20Z
- charm-repo: canonical/grafana-k8s-operator
commit: ec74910fc60848594ce44da3b549ad18aa6528aa # rev113 2024-05-21T14:31:49Z
steps:
- name: Checkout the ${{ matrix.charm-repo }} repository
uses: actions/checkout@v4
with:
repository: ${{ matrix.charm-repo }}
ref: ${{ matrix.commit }}

- name: Update 'ops' dependency in test charm to latest
run: |
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/update-charm-tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
name: Update Charm Pins

on:
# NOTE: to avoid infinite loop, exclude the branch created by this workflow if triggering on push or pull_request
workflow_dispatch:
schedule:
- cron: '0 18 * * SUN' # Sunday 6pm UTC, before international day starts

jobs:
update-pins:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.UPDATE_CHARM_PINS_ACCESS_TOKEN }}

- uses: ./.github/actions/update-charm-pins
with:
# Whitespace (null) separated string, as workflow inputs are always plain values
workflows: |-
.github/workflows/db-charm-tests.yaml
.github/workflows/hello-charm-tests.yaml
.github/workflows/charmcraft-pack.yaml
.github/workflows/observability-charm-tests.yaml
gh-pat: ${{ secrets.UPDATE_CHARM_PINS_ACCESS_TOKEN }}

- run: |
# Force-push pin changes to the branch
echo "New changes in charm pins"
git --no-pager diff
git config --global user.name "github-actions"
git config --global user.email "github-actions@github.com"
git switch -C auto-update-external-charm-pins
git commit --allow-empty -am "chore: update charm pins"
echo "Total changes in charm pins"
git --no-pager diff main HEAD
git push -f --set-upstream origin auto-update-external-charm-pins
- run: |
# Ensure a PR if there are changes, no PR otherwise
PR=$(gh pr list --state open --head auto-update-external-charm-pins --json number -q '.[0].number')
CHANGES=$(git --no-pager diff --stat main HEAD)
echo "Existing PR? $PR"
echo "Changes? $CHANGES"
if [[ -n "$PR" && -z "$CHANGES" ]]; then
echo "Closing #$PR as stale"
gh pr close -c stale "$PR";
elif [[ -z "$PR" && -n "$CHANGES" ]]; then
echo "Opening new PR"
gh pr create --base main --head auto-update-external-charm-pins --title "chore: update charm pins" --body "This is an automated PR to update pins of the external repositories that the operator framework is tested against";
fi
env:
GITHUB_TOKEN: ${{ secrets.UPDATE_CHARM_PINS_ACCESS_TOKEN }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ venv
.vscode
.coverage
/.tox
.*.swp

# Tokens and settings for `act` to run GHA locally
.env
.envrc
.secrets

# Build artifacts
/dist
Expand Down
5 changes: 5 additions & 0 deletions HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ your charm to a controller using that version of Juju. For example, with microk8
3. Run `GOBIN=/path/to/your/juju/_build/linux_amd64/bin:$GOBIN /path/to/your/juju bootstrap`
4. Add a model and deploy your charm as normal

### Regression testing against existing charms

We rely on automation to [update charm pins](.github/actions/update-charm-pins/) of
a bunch of charms that use the operator framework. The script can be run locally too.

# Documentation

In general, new functionality
Expand Down

0 comments on commit e40f78d

Please sign in to comment.