From dc9288f1a37b546ed3c871e8c4e4197efc3d8501 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 20 Aug 2024 15:56:43 +1200 Subject: [PATCH 01/24] Allow manual execution. --- .github/workflows/tiobe.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tiobe.yaml b/.github/workflows/tiobe.yaml index 350906522..92e0d4921 100644 --- a/.github/workflows/tiobe.yaml +++ b/.github/workflows/tiobe.yaml @@ -3,6 +3,7 @@ name: TIOBE Quality Checks on: schedule: - cron: '0 7 1 * *' + workflow_dispatch: jobs: TICS: From 068e978304c8da43c53c87c9fa87350f488f8422 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Mon, 26 Aug 2024 17:36:56 +1200 Subject: [PATCH 02/24] WiP --- .github/generate-published-workflow.py | 72 ++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 .github/generate-published-workflow.py diff --git a/.github/generate-published-workflow.py b/.github/generate-published-workflow.py new file mode 100755 index 000000000..3a9fd7d0d --- /dev/null +++ b/.github/generate-published-workflow.py @@ -0,0 +1,72 @@ +#! /usr/bin/env python + +# /// script +# dependencies = [ +# "requests", +# ] +# /// + +"""Generate a GitHub workload that runs `tox` on all published charms.""" + +import base64 +import binascii +import json +import os +import subprocess + +import requests + + +def _charmcraft_auth_to_macaroon(charmcraft_auth: str): + """Decode charmcraft auth into the macaroon.""" + try: + bytes = base64.b64decode(charmcraft_auth.strip().encode()) + return json.loads(bytes).get('v') + except (binascii.Error, json.JSONDecodeError): + return None + + +def macaroon() -> str: + """Get the charmhub macaroon.""" + macaroon = os.environ.get('CHARM_MACAROON') + charmcraft_auth = os.environ.get('CHARMCRAFT_AUTH') + if not macaroon and charmcraft_auth: + macaroon = _charmcraft_auth_to_macaroon(charmcraft_auth) + if not macaroon: + # Export to stderr because stdout gets a "Login successful" message. + out = subprocess.run( + ['charmcraft', 'login', '--export', '/dev/fd/2'], + text=True, + check=True, + stderr=subprocess.PIPE, + ) + macaroon = _charmcraft_auth_to_macaroon(out.stderr.splitlines()[-1]) + if not macaroon: + raise ValueError('No charmhub macaroon found') + return macaroon.strip() + + +def get_session(): + session = requests.Session() + session.headers['Authorization'] = f'Macaroon {macaroon()}' + session.headers['Content-Type'] = 'application/json' + return session + + +def packages(session: requests.Session): + # This works without being logged in, but we might as well re-use the session. + resp = session.get('https://charmhub.io/packages.json') + return resp.json()['packages'] + + +def info(session: requests.Session, charm: str): + """Get charm info.""" + resp = session.get(f'https://api.charmhub.io/v1/charm/{charm}').json() + print(resp) + + +if __name__ == '__main__': + session = get_session() + for package in packages(session): + info(session, package['name']) + break From dcf47b71a70e853d3fcd27d7ececfd242b0f6ed4 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 5 Sep 2024 21:58:57 +1200 Subject: [PATCH 03/24] Initial workflow for testing viability. --- .github/workflows/published-charms-tests.yaml | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 .github/workflows/published-charms-tests.yaml diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml new file mode 100644 index 000000000..3ecf757d4 --- /dev/null +++ b/.github/workflows/published-charms-tests.yaml @@ -0,0 +1,136 @@ +name: Broad Charm Compatibility Tests + +on: + schedule: + - cron: '0 1 25 * *' + workflow_dispatch: + +jobs: + charm-tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - charm-repo: canonical/data-platform-libs + - charm-repo: canonical/kafka-operator + - charm-repo: canonical/mongodb-operator + - charm-repo: canonical/mysql-operator + - charm-repo: canonical/traefik-k8s-operator + - charm-repo: canonical/mysql-k8s-operator + - charm-repo: canonical/mysql-router-k8s-operator + - charm-repo: canonical/postgresql-operator + - charm-repo: canonical/prometheus-k8s-operator + - charm-repo: canonical/pgbouncer-k8s-operator + - charm-repo: canonical/grafana-k8s-operator + - charm-repo: canonical/postgresql-k8s-operator + - charm-repo: canonical/discourse-k8s-operator + - charm-repo: canonical/s3-integrator + - charm-repo: canonical/zookeeper-operator + - charm-repo: canonical/loki-k8s-operator + - charm-repo: canonical/alertmanager-k8s-operator + - charm-repo: canonical/openfga-operator + - charm-repo: canonical/indico-operator + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/trino-k8s-operator + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/charm-layer-ghost + - charm-repo: canonical/juju-charm-dokuwiki.rb + - charm-repo: canonical/charm-openstack-service-checks + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/layer-arangodb + - charm-repo: canonical/layer-ubuntu-devenv + - charm-repo: canonical/charm-cilium + - charm-repo: canonical/route53-lego-k8s-operator + - charm-repo: canonical/wordpress-k8s-operator + - charm-repo: canonical/temporal-k8s-operator + - charm-repo: canonical/layer-filebeat + - charm-repo: canonical/jenkins-agent-operator + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/layer-kapacitor + - charm-repo: canonical/nfs-charm + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/layer-ssl-termination-fqdn + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/jenkins-charm + - charm-repo: canonical/juju-dashboard + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/seldon-core-operator + - charm-repo: canonical/layer-topbeat + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/nginx-ingress-integrator-operator + - charm-repo: canonical/layer-openjdk + - charm-repo: canonical/oathkeeper-operator + - charm-repo: canonical/jenkins-agent-k8s-operator + - charm-repo: canonical/grafana-agent-k8s-operator + - charm-repo: canonical/metallb-operator + - charm-repo: canonical/kubernetes + - charm-repo: canonical/juju-dashboard + - charm-repo: canonical/namecheap-lego-k8s-operator + - charm-repo: canonical/superset-k8s-operator + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/identity-platform-login-ui-operator + - charm-repo: canonical/temporal-admin-k8s-operator + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/dex-auth-operator + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/juju-layer-fresh-rss + - charm-repo: canonical/ks-charmed + - charm-repo: canonical/charm-lldpd + - charm-repo: canonical/charm-kube-ovn + - charm-repo: canonical/temporal-ui-k8s-operator + - charm-repo: canonical/juju-charm-znc + - charm-repo: canonical/notebook-operators + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/temporal-worker-k8s-operator + - charm-repo: canonical/bigtop + - charm-repo: canonical/charm-flannel + - charm-repo: canonical/charm-prometheus-libvirt-exporter + - charm-repo: canonical/content-cache-k8s-operator + - charm-repo: canonical/ranger-k8s-operator + - charm-repo: canonical/oauth2-proxy-k8s-operator + - charm-repo: canonical/livepatch-k8s-operator + - charm-repo: canonical/self-signed-certificates-operator + - charm-repo: canonical/saml-integrator-operator + - charm-repo: canonical/smtp-integrator-operator + - charm-repo: canonical/manual-tls-certificates-operator + - charm-repo: canonical/jimm + - charm-repo: canonical/hardware-observer-operator + - charm-repo: canonical/charmed-linstor + - charm-repo: canonical/bundle + - charm-repo: canonical/bundle + - charm-repo: canonical/bundle-kubeflow + - charm-repo: canonical/ecosystem-prep + - charm-repo: canonical/bundle-jupyter + - charm-repo: canonical/hello-juju-charm + steps: + - name: Checkout the ${{ matrix.charm-repo }} repository + uses: actions/checkout@v4 + with: + repository: ${{ matrix.charm-repo }} + + - name: Checkout the operator repository + uses: actions/checkout@v4 + with: + path: myops + + - name: Install patch dependencies + run: pip install poetry~=1.6 + + - name: Update 'ops' dependency in test charm to latest + run: | + if [ -e "requirements.txt" ]; then + sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements.txt + echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements.txt + else + sed -i -e "s/^ops[ ><=].*/ops = {path = \"myops\"}/" pyproject.toml + poetry lock --no-update + fi + + - name: Install dependencies + run: pip install tox~=4.2 + + - name: Run the charm's unit tests + run: tox -vve unit From 8296d9c71722989cf6c531c04a4963072e2d09d4 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 5 Sep 2024 22:14:33 +1200 Subject: [PATCH 04/24] Use api/v2 to avoid all the login hassle. Alter the existing file. --- .github/generate-published-workflow.py | 155 +++++++++++++++++-------- 1 file changed, 107 insertions(+), 48 deletions(-) diff --git a/.github/generate-published-workflow.py b/.github/generate-published-workflow.py index 3a9fd7d0d..8392e9749 100755 --- a/.github/generate-published-workflow.py +++ b/.github/generate-published-workflow.py @@ -2,71 +2,130 @@ # /// script # dependencies = [ +# "PyYAML", # "requests", +# "rich", # ] # /// -"""Generate a GitHub workload that runs `tox` on all published charms.""" - -import base64 -import binascii -import json -import os -import subprocess +# Copyright 2024 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Update a GitHub workload that runs `tox -e unit` on all published charms. + +Charms that are not hosted on GitHub are skipped, as well as any charms where +the source URL could not be found. +""" + +import pathlib +import urllib.parse import requests +import rich.console +import yaml +console = rich.console.Console() -def _charmcraft_auth_to_macaroon(charmcraft_auth: str): - """Decode charmcraft auth into the macaroon.""" - try: - bytes = base64.b64decode(charmcraft_auth.strip().encode()) - return json.loads(bytes).get('v') - except (binascii.Error, json.JSONDecodeError): - return None +URL_BASE = 'https://api.charmhub.io/v2/charms/info' +WORKFLOW = pathlib.Path(__file__).parent / 'workflows' / 'published-charms-tests.yaml' -def macaroon() -> str: - """Get the charmhub macaroon.""" - macaroon = os.environ.get('CHARM_MACAROON') - charmcraft_auth = os.environ.get('CHARMCRAFT_AUTH') - if not macaroon and charmcraft_auth: - macaroon = _charmcraft_auth_to_macaroon(charmcraft_auth) - if not macaroon: - # Export to stderr because stdout gets a "Login successful" message. - out = subprocess.run( - ['charmcraft', 'login', '--export', '/dev/fd/2'], - text=True, - check=True, - stderr=subprocess.PIPE, - ) - macaroon = _charmcraft_auth_to_macaroon(out.stderr.splitlines()[-1]) - if not macaroon: - raise ValueError('No charmhub macaroon found') - return macaroon.strip() - - -def get_session(): - session = requests.Session() - session.headers['Authorization'] = f'Macaroon {macaroon()}' - session.headers['Content-Type'] = 'application/json' - return session +SKIP = { + # Handled by db-charm-tests.yaml + 'postgresql-operator', + 'postgresql-k8s-operator', + 'mysql-operator', + 'mysql-k8s-operator', + # Handled by hello-charm-tests.yaml + 'hello-kubecon', # Also not in the canonical org, but jnsgruk. + 'hello-juju-charm', # Also not in the canonical org, but juju. + # Handler by observability-charms-tests.yaml + 'alertmanager-k8s-operator', + 'prometheus-k8s-operator', + 'grafana-k8s-operator', +} def packages(session: requests.Session): - # This works without being logged in, but we might as well re-use the session. + """Get the list of published charms from Charmhub.""" + console.log('Fetching the list of published charms') resp = session.get('https://charmhub.io/packages.json') return resp.json()['packages'] -def info(session: requests.Session, charm: str): - """Get charm info.""" - resp = session.get(f'https://api.charmhub.io/v1/charm/{charm}').json() - print(resp) +def get_source_url(charm: str, session: requests.Session): + """Get the source URL for a charm.""" + console.log(f"Looking for a 'source' URL for {charm}") + try: + source = session.get(f'{URL_BASE}/{charm}?fields=result.links') + source.raise_for_status() + return source.json()['result']['links']['source'][0] + except (requests.HTTPError, KeyError): + pass + console.log(f"Looking for a 'bugs-url' URL for {charm}") + try: + source = session.get(f'{URL_BASE}/{charm}?fields=result.bugs-url') + source.raise_for_status() + return source.json()['result']['bugs-url'] + except (requests.HTTPError, KeyError): + pass + # TODO: Can we try anything else? + console.log(f'Could not find a source URL for {charm}') + return None + + +def url_to_charm_name(url: str): + """Get the charm name from a URL.""" + if not url: + return None + parsed = urllib.parse.urlparse(url) + if parsed.netloc != 'github.com': + console.log(f'URL {url} is not a GitHub URL') + return None + if not parsed.path.startswith('/canonical'): + # TODO: Maybe we can include some of these anyway? + # 'juju-solutions' and 'charmed-kubernetes' seem viable, for example. + console.log(f'URL {url} is not a Canonical charm') + try: + return urllib.parse.urlparse(url).path.split('/')[2] + except IndexError: + console.log(f'Could not get charm name from URL {url}') + return None + + +def main(): + """Update the workflow file.""" + session = requests.Session() + charms = ( + url_to_charm_name(get_source_url(package['name'], session)) + for package in packages(session) + ) + with WORKFLOW.open('r') as f: + workflow = yaml.safe_load(f) + workflow['jobs']['charm-tests']['strategy']['matrix']['include'] = [ + {'charm-repo': f'canonical/{charm}'} for charm in charms if charm and charm not in SKIP + ] + with WORKFLOW.open('w') as f: + yaml.dump(workflow, f) + # yaml.safe_load/yaml.dump transforms "on" to "true". I'm not sure how to avoid that. + with WORKFLOW.open('r') as f: + content = f.read().replace('true:', 'on:') + with WORKFLOW.open('w') as f: + f.write(content) + # TODO: the "Update 'ops' dependency in test charm to latest" run command also gets messed up + # and has to get fixed. if __name__ == '__main__': - session = get_session() - for package in packages(session): - info(session, package['name']) - break + main() From c0956969f5d35909c9240c1ed6a32c5c8c476255 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 6 Sep 2024 11:12:59 +1200 Subject: [PATCH 05/24] Remove the non-Canonical charms for now. --- .github/workflows/published-charms-tests.yaml | 51 +------------------ 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index 3ecf757d4..fe8689377 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -15,78 +15,36 @@ jobs: - charm-repo: canonical/data-platform-libs - charm-repo: canonical/kafka-operator - charm-repo: canonical/mongodb-operator - - charm-repo: canonical/mysql-operator - charm-repo: canonical/traefik-k8s-operator - - charm-repo: canonical/mysql-k8s-operator - charm-repo: canonical/mysql-router-k8s-operator - - charm-repo: canonical/postgresql-operator - - charm-repo: canonical/prometheus-k8s-operator - charm-repo: canonical/pgbouncer-k8s-operator - - charm-repo: canonical/grafana-k8s-operator - - charm-repo: canonical/postgresql-k8s-operator - charm-repo: canonical/discourse-k8s-operator - charm-repo: canonical/s3-integrator - charm-repo: canonical/zookeeper-operator - charm-repo: canonical/loki-k8s-operator - - charm-repo: canonical/alertmanager-k8s-operator - charm-repo: canonical/openfga-operator - charm-repo: canonical/indico-operator - - charm-repo: canonical/bundle-kubeflow - charm-repo: canonical/trino-k8s-operator - - charm-repo: canonical/bundle-kubeflow - - charm-repo: canonical/bundle-kubeflow - - charm-repo: canonical/charm-layer-ghost - - charm-repo: canonical/juju-charm-dokuwiki.rb - charm-repo: canonical/charm-openstack-service-checks - - charm-repo: canonical/bundle-kubeflow - - charm-repo: canonical/layer-arangodb - - charm-repo: canonical/layer-ubuntu-devenv - - charm-repo: canonical/charm-cilium - charm-repo: canonical/route53-lego-k8s-operator - charm-repo: canonical/wordpress-k8s-operator - charm-repo: canonical/temporal-k8s-operator - - charm-repo: canonical/layer-filebeat - charm-repo: canonical/jenkins-agent-operator - - charm-repo: canonical/bundle-kubeflow - - charm-repo: canonical/bundle-kubeflow - - charm-repo: canonical/layer-kapacitor - - charm-repo: canonical/nfs-charm - - charm-repo: canonical/bundle-kubeflow - - charm-repo: canonical/layer-ssl-termination-fqdn - - charm-repo: canonical/bundle-kubeflow - - charm-repo: canonical/jenkins-charm - charm-repo: canonical/juju-dashboard - - charm-repo: canonical/bundle-kubeflow - charm-repo: canonical/seldon-core-operator - - charm-repo: canonical/layer-topbeat - - charm-repo: canonical/bundle-kubeflow - charm-repo: canonical/nginx-ingress-integrator-operator - - charm-repo: canonical/layer-openjdk - charm-repo: canonical/oathkeeper-operator - charm-repo: canonical/jenkins-agent-k8s-operator - charm-repo: canonical/grafana-agent-k8s-operator - - charm-repo: canonical/metallb-operator - - charm-repo: canonical/kubernetes - charm-repo: canonical/juju-dashboard - charm-repo: canonical/namecheap-lego-k8s-operator - charm-repo: canonical/superset-k8s-operator - - charm-repo: canonical/bundle-kubeflow - charm-repo: canonical/identity-platform-login-ui-operator - charm-repo: canonical/temporal-admin-k8s-operator - - charm-repo: canonical/bundle-kubeflow - charm-repo: canonical/dex-auth-operator - - charm-repo: canonical/bundle-kubeflow - - charm-repo: canonical/juju-layer-fresh-rss - - charm-repo: canonical/ks-charmed - - charm-repo: canonical/charm-lldpd - - charm-repo: canonical/charm-kube-ovn - charm-repo: canonical/temporal-ui-k8s-operator - - charm-repo: canonical/juju-charm-znc - charm-repo: canonical/notebook-operators - - charm-repo: canonical/bundle-kubeflow - charm-repo: canonical/temporal-worker-k8s-operator - - charm-repo: canonical/bigtop - - charm-repo: canonical/charm-flannel - charm-repo: canonical/charm-prometheus-libvirt-exporter - charm-repo: canonical/content-cache-k8s-operator - charm-repo: canonical/ranger-k8s-operator @@ -98,14 +56,7 @@ jobs: - charm-repo: canonical/manual-tls-certificates-operator - charm-repo: canonical/jimm - charm-repo: canonical/hardware-observer-operator - - charm-repo: canonical/charmed-linstor - - charm-repo: canonical/bundle - - charm-repo: canonical/bundle - - charm-repo: canonical/bundle-kubeflow - - charm-repo: canonical/ecosystem-prep - - charm-repo: canonical/bundle-jupyter - - charm-repo: canonical/hello-juju-charm - steps: + steps: - name: Checkout the ${{ matrix.charm-repo }} repository uses: actions/checkout@v4 with: From 91df5c79ef9306e422c9c4336d7c51bbb18abba1 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 6 Sep 2024 11:13:52 +1200 Subject: [PATCH 06/24] Typo --- .github/workflows/published-charms-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index fe8689377..2f9f13ea7 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -56,7 +56,7 @@ jobs: - charm-repo: canonical/manual-tls-certificates-operator - charm-repo: canonical/jimm - charm-repo: canonical/hardware-observer-operator - steps: + steps: - name: Checkout the ${{ matrix.charm-repo }} repository uses: actions/checkout@v4 with: From ef8ccb826dfb1c552b89caa3d0a6e1a18e7f2a29 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 6 Sep 2024 11:46:19 +1200 Subject: [PATCH 07/24] Remove non-ops charms. Don't run the ops tests. Handle special requirements files. --- .github/workflows/published-charms-tests.yaml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index 2f9f13ea7..bfb167f9d 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -12,7 +12,6 @@ jobs: fail-fast: false matrix: include: - - charm-repo: canonical/data-platform-libs - charm-repo: canonical/kafka-operator - charm-repo: canonical/mongodb-operator - charm-repo: canonical/traefik-k8s-operator @@ -25,18 +24,15 @@ jobs: - charm-repo: canonical/openfga-operator - charm-repo: canonical/indico-operator - charm-repo: canonical/trino-k8s-operator - - charm-repo: canonical/charm-openstack-service-checks - charm-repo: canonical/route53-lego-k8s-operator - charm-repo: canonical/wordpress-k8s-operator - charm-repo: canonical/temporal-k8s-operator - charm-repo: canonical/jenkins-agent-operator - - charm-repo: canonical/juju-dashboard - charm-repo: canonical/seldon-core-operator - charm-repo: canonical/nginx-ingress-integrator-operator - charm-repo: canonical/oathkeeper-operator - charm-repo: canonical/jenkins-agent-k8s-operator - charm-repo: canonical/grafana-agent-k8s-operator - - charm-repo: canonical/juju-dashboard - charm-repo: canonical/namecheap-lego-k8s-operator - charm-repo: canonical/superset-k8s-operator - charm-repo: canonical/identity-platform-login-ui-operator @@ -45,7 +41,6 @@ jobs: - charm-repo: canonical/temporal-ui-k8s-operator - charm-repo: canonical/notebook-operators - charm-repo: canonical/temporal-worker-k8s-operator - - charm-repo: canonical/charm-prometheus-libvirt-exporter - charm-repo: canonical/content-cache-k8s-operator - charm-repo: canonical/ranger-k8s-operator - charm-repo: canonical/oauth2-proxy-k8s-operator @@ -54,7 +49,6 @@ jobs: - charm-repo: canonical/saml-integrator-operator - charm-repo: canonical/smtp-integrator-operator - charm-repo: canonical/manual-tls-certificates-operator - - charm-repo: canonical/jimm - charm-repo: canonical/hardware-observer-operator steps: - name: Checkout the ${{ matrix.charm-repo }} repository @@ -72,6 +66,15 @@ jobs: - name: Update 'ops' dependency in test charm to latest run: | + rm -rf myops/test + if [ -e "test-requirements.txt" ]; then + sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" test-requirements.txt + echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> test-requirements.txt + fi + if [ -e "requirements-charmcraft.txt" ]; then + sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements-charmcraft.txt + echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements-charmcraft.txt + fi if [ -e "requirements.txt" ]; then sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements.txt echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements.txt From 67c2051e4f6489248ded382ba0563f168319a380 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 6 Sep 2024 11:54:51 +1200 Subject: [PATCH 08/24] Add tests for the charmcraft profiles as well. --- .github/workflows/published-charms-tests.yaml | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index bfb167f9d..cfd05c7e4 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -39,7 +39,6 @@ jobs: - charm-repo: canonical/temporal-admin-k8s-operator - charm-repo: canonical/dex-auth-operator - charm-repo: canonical/temporal-ui-k8s-operator - - charm-repo: canonical/notebook-operators - charm-repo: canonical/temporal-worker-k8s-operator - charm-repo: canonical/content-cache-k8s-operator - charm-repo: canonical/ranger-k8s-operator @@ -88,3 +87,35 @@ jobs: - name: Run the charm's unit tests run: tox -vve unit + + charmcraft-profile-tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - profile: machine + - profile: kubernetes + - profile: simple + steps: + - name: Checkout the operator repository + uses: actions/checkout@v4 + with: + path: myops + + - name: Charmcraft init + run: charmcraft init --profile=${{ matrix.profile }} + + - name: Update 'ops' dependency in test charm to latest + run: | + rm -rf myops/test + if [ -e "requirements.txt" ]; then + sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements.txt + echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements.txt + fi + + - name: Install dependencies + run: pip install tox~=4.2 + + - name: Run the charm's unit tests + run: tox -vve unit From 4406f31a3a7bc9f3ee988c3a6b69303d85873987 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 6 Sep 2024 12:00:51 +1200 Subject: [PATCH 09/24] Install charmcraft. --- .github/workflows/published-charms-tests.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index cfd05c7e4..44c81d675 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -103,6 +103,9 @@ jobs: with: path: myops + - name: Install charmcraft + run: sudo snap install charmcraft --classic + - name: Charmcraft init run: charmcraft init --profile=${{ matrix.profile }} From c239ae0acc9f95fc05d8d2a39157098f3c237eff Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 6 Sep 2024 12:03:38 +1200 Subject: [PATCH 10/24] Reorder to avoid --force. --- .github/workflows/published-charms-tests.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index 44c81d675..cdc087a6e 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -98,17 +98,17 @@ jobs: - profile: kubernetes - profile: simple steps: - - name: Checkout the operator repository - uses: actions/checkout@v4 - with: - path: myops - - name: Install charmcraft run: sudo snap install charmcraft --classic - name: Charmcraft init run: charmcraft init --profile=${{ matrix.profile }} + - name: Checkout the operator repository + uses: actions/checkout@v4 + with: + path: myops + - name: Update 'ops' dependency in test charm to latest run: | rm -rf myops/test From 0d1744a8e3b2f274cccd4560121fcb76d433c2e6 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 6 Sep 2024 12:06:18 +1200 Subject: [PATCH 11/24] Expand the skip list. --- .github/generate-published-workflow.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.github/generate-published-workflow.py b/.github/generate-published-workflow.py index 8392e9749..8850962cd 100755 --- a/.github/generate-published-workflow.py +++ b/.github/generate-published-workflow.py @@ -37,7 +37,6 @@ console = rich.console.Console() - URL_BASE = 'https://api.charmhub.io/v2/charms/info' WORKFLOW = pathlib.Path(__file__).parent / 'workflows' / 'published-charms-tests.yaml' @@ -48,12 +47,21 @@ 'mysql-operator', 'mysql-k8s-operator', # Handled by hello-charm-tests.yaml - 'hello-kubecon', # Also not in the canonical org, but jnsgruk. - 'hello-juju-charm', # Also not in the canonical org, but juju. - # Handler by observability-charms-tests.yaml + 'hello-kubecon', # Not in the canonical org anyway (jnsgruk). + 'hello-juju-charm', # Not in the canonical org anyway (juju). + # Handled by observability-charms-tests.yaml 'alertmanager-k8s-operator', 'prometheus-k8s-operator', 'grafana-k8s-operator', + # This has a redirect, which is too complicated to handle for now. + 'bundle-jupyter', + # The charms are in a subfolder, which this can't handle yet. + 'jimm', + 'notebook-operators', + # Not ops. + 'charm-prometheus-libvirt-exporter', + 'juju-dashboard', + 'charm-openstack-service-checks', } @@ -80,8 +88,7 @@ def get_source_url(charm: str, session: requests.Session): return source.json()['result']['bugs-url'] except (requests.HTTPError, KeyError): pass - # TODO: Can we try anything else? - console.log(f'Could not find a source URL for {charm}') + console.log(f'Could not find a source URL for {charm}', style='bold red') return None @@ -97,10 +104,11 @@ def url_to_charm_name(url: str): # TODO: Maybe we can include some of these anyway? # 'juju-solutions' and 'charmed-kubernetes' seem viable, for example. console.log(f'URL {url} is not a Canonical charm') + return None try: return urllib.parse.urlparse(url).path.split('/')[2] except IndexError: - console.log(f'Could not get charm name from URL {url}') + console.log(f'Could not get charm name from URL {url}', style='bold red') return None From 0c017455d0767817f5880d3a3ce4cad3f9abf516 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 6 Sep 2024 12:07:03 +1200 Subject: [PATCH 12/24] Adjust the script name. --- ...shed-workflow.py => update-published-charms-tests-workflow.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{generate-published-workflow.py => update-published-charms-tests-workflow.py} (100%) diff --git a/.github/generate-published-workflow.py b/.github/update-published-charms-tests-workflow.py similarity index 100% rename from .github/generate-published-workflow.py rename to .github/update-published-charms-tests-workflow.py From 0009082de905129483b91ef5c1963a0bf310d783 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Fri, 6 Sep 2024 12:07:39 +1200 Subject: [PATCH 13/24] Pass an author explicitly. --- .github/workflows/published-charms-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index cdc087a6e..8cc4e9a48 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -102,7 +102,7 @@ jobs: run: sudo snap install charmcraft --classic - name: Charmcraft init - run: charmcraft init --profile=${{ matrix.profile }} + run: charmcraft init --profile=${{ matrix.profile }} --author=charm-tech - name: Checkout the operator repository uses: actions/checkout@v4 From 9012e7cc08ce53fa435aced21781dd0fced0e28f Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 10 Sep 2024 12:40:37 +1200 Subject: [PATCH 14/24] Only use the standard lib and existing ops dependencies. --- .../update-published-charms-tests-workflow.py | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/update-published-charms-tests-workflow.py b/.github/update-published-charms-tests-workflow.py index 8850962cd..ab2376efd 100755 --- a/.github/update-published-charms-tests-workflow.py +++ b/.github/update-published-charms-tests-workflow.py @@ -3,8 +3,6 @@ # /// script # dependencies = [ # "PyYAML", -# "requests", -# "rich", # ] # /// @@ -28,14 +26,17 @@ the source URL could not be found. """ +import json +import logging import pathlib +import urllib.error import urllib.parse +import urllib.request + +logger = logging.getLogger(__name__) -import requests -import rich.console import yaml -console = rich.console.Console() URL_BASE = 'https://api.charmhub.io/v2/charms/info' WORKFLOW = pathlib.Path(__file__).parent / 'workflows' / 'published-charms-tests.yaml' @@ -65,30 +66,33 @@ } -def packages(session: requests.Session): +def packages(): """Get the list of published charms from Charmhub.""" - console.log('Fetching the list of published charms') - resp = session.get('https://charmhub.io/packages.json') - return resp.json()['packages'] + logger.info('Fetching the list of published charms') + url = 'https://charmhub.io/packages.json' + with urllib.request.urlopen(url) as response: + data = response.read().decode() + packages = json.loads(data)['packages'] + return packages -def get_source_url(charm: str, session: requests.Session): +def get_source_url(charm: str): """Get the source URL for a charm.""" - console.log(f"Looking for a 'source' URL for {charm}") + logger.info("Looking for a 'source' URL for %s", charm) try: - source = session.get(f'{URL_BASE}/{charm}?fields=result.links') - source.raise_for_status() - return source.json()['result']['links']['source'][0] - except (requests.HTTPError, KeyError): + with urllib.request.urlopen(f'{URL_BASE}/{charm}?fields=result.links') as response: + data = json.loads(response.read().decode()) + return data['result']['links']['source'][0] + except (urllib.error.HTTPError, KeyError): pass - console.log(f"Looking for a 'bugs-url' URL for {charm}") + logger.info("Looking for a 'bugs-url' URL for %s", charm) try: - source = session.get(f'{URL_BASE}/{charm}?fields=result.bugs-url') - source.raise_for_status() - return source.json()['result']['bugs-url'] - except (requests.HTTPError, KeyError): + with urllib.request.urlopen(f'{URL_BASE}/{charm}?fields=result.bugs-url') as response: + data = json.loads(response.read().decode()) + return data['result']['bugs-url'] + except (urllib.error.HTTPError, KeyError): pass - console.log(f'Could not find a source URL for {charm}', style='bold red') + logger.warning('Could not find a source URL for %s', charm) return None @@ -98,27 +102,23 @@ def url_to_charm_name(url: str): return None parsed = urllib.parse.urlparse(url) if parsed.netloc != 'github.com': - console.log(f'URL {url} is not a GitHub URL') + logger.info('URL %s is not a GitHub URL', url) return None if not parsed.path.startswith('/canonical'): # TODO: Maybe we can include some of these anyway? # 'juju-solutions' and 'charmed-kubernetes' seem viable, for example. - console.log(f'URL {url} is not a Canonical charm') + logger.info('URL %s is not a Canonical charm', url) return None try: return urllib.parse.urlparse(url).path.split('/')[2] except IndexError: - console.log(f'Could not get charm name from URL {url}', style='bold red') + logger.warning('Could not get charm name from URL %s', url) return None def main(): """Update the workflow file.""" - session = requests.Session() - charms = ( - url_to_charm_name(get_source_url(package['name'], session)) - for package in packages(session) - ) + charms = (url_to_charm_name(get_source_url(package['name'])) for package in packages()) with WORKFLOW.open('r') as f: workflow = yaml.safe_load(f) workflow['jobs']['charm-tests']['strategy']['matrix']['include'] = [ From c931b7c1458e1c8ce928d8d2b4cc2b307d8c5ddf Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 10 Sep 2024 13:16:44 +1200 Subject: [PATCH 15/24] Try the generated version to see if it works. --- .github/workflows/published-charms-tests.yaml | 196 ++++++++---------- 1 file changed, 88 insertions(+), 108 deletions(-) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index 8cc4e9a48..87bb45b0e 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -1,124 +1,104 @@ name: Broad Charm Compatibility Tests - on: schedule: - - cron: '0 1 25 * *' - workflow_dispatch: - + - cron: 0 1 25 * * + workflow_dispatch: null jobs: charm-tests: runs-on: ubuntu-latest + steps: + - name: Checkout the ${{ matrix.charm-repo }} repository + uses: actions/checkout@v4 + with: + repository: ${{ matrix.charm-repo }} + - name: Checkout the operator repository + uses: actions/checkout@v4 + with: + path: myops + - name: Install patch dependencies + run: pip install poetry~=1.6 + - name: Update 'ops' dependency in test charm to latest + run: "rm -rf myops/test\nif [ -e \"test-requirements.txt\" ]; then\n sed -i\ + \ -e \"/^ops[ ><=]/d\" -e \"/canonical\\/operator/d\" -e \"/#egg=ops/d\" test-requirements.txt\n\ + \ echo -e \"\\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops\"\ + \ >> test-requirements.txt\nfi\nif [ -e \"requirements-charmcraft.txt\" ];\ + \ then\n sed -i -e \"/^ops[ ><=]/d\" -e \"/canonical\\/operator/d\" -e \"\ + /#egg=ops/d\" requirements-charmcraft.txt\n echo -e \"\\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops\"\ + \ >> requirements-charmcraft.txt\nfi\nif [ -e \"requirements.txt\" ]; then\n\ + \ sed -i -e \"/^ops[ ><=]/d\" -e \"/canonical\\/operator/d\" -e \"/#egg=ops/d\"\ + \ requirements.txt\n echo -e \"\\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops\"\ + \ >> requirements.txt\nelse\n sed -i -e \"s/^ops[ ><=].*/ops = {path = \\\ + \"myops\\\"}/\" pyproject.toml\n poetry lock --no-update\nfi\n" + - name: Install dependencies + run: pip install tox~=4.2 + - name: Run the charm's unit tests + run: tox -vve unit strategy: fail-fast: false matrix: include: - - charm-repo: canonical/kafka-operator - - charm-repo: canonical/mongodb-operator - - charm-repo: canonical/traefik-k8s-operator - - charm-repo: canonical/mysql-router-k8s-operator - - charm-repo: canonical/pgbouncer-k8s-operator - - charm-repo: canonical/discourse-k8s-operator - - charm-repo: canonical/s3-integrator - - charm-repo: canonical/zookeeper-operator - - charm-repo: canonical/loki-k8s-operator - - charm-repo: canonical/openfga-operator - - charm-repo: canonical/indico-operator - - charm-repo: canonical/trino-k8s-operator - - charm-repo: canonical/route53-lego-k8s-operator - - charm-repo: canonical/wordpress-k8s-operator - - charm-repo: canonical/temporal-k8s-operator - - charm-repo: canonical/jenkins-agent-operator - - charm-repo: canonical/seldon-core-operator - - charm-repo: canonical/nginx-ingress-integrator-operator - - charm-repo: canonical/oathkeeper-operator - - charm-repo: canonical/jenkins-agent-k8s-operator - - charm-repo: canonical/grafana-agent-k8s-operator - - charm-repo: canonical/namecheap-lego-k8s-operator - - charm-repo: canonical/superset-k8s-operator - - charm-repo: canonical/identity-platform-login-ui-operator - - charm-repo: canonical/temporal-admin-k8s-operator - - charm-repo: canonical/dex-auth-operator - - charm-repo: canonical/temporal-ui-k8s-operator - - charm-repo: canonical/temporal-worker-k8s-operator - - charm-repo: canonical/content-cache-k8s-operator - - charm-repo: canonical/ranger-k8s-operator - - charm-repo: canonical/oauth2-proxy-k8s-operator - - charm-repo: canonical/livepatch-k8s-operator - - charm-repo: canonical/self-signed-certificates-operator - - charm-repo: canonical/saml-integrator-operator - - charm-repo: canonical/smtp-integrator-operator - - charm-repo: canonical/manual-tls-certificates-operator - - charm-repo: canonical/hardware-observer-operator - steps: - - name: Checkout the ${{ matrix.charm-repo }} repository - uses: actions/checkout@v4 - with: - repository: ${{ matrix.charm-repo }} - - - name: Checkout the operator repository - uses: actions/checkout@v4 - with: - path: myops - - - name: Install patch dependencies - run: pip install poetry~=1.6 - - - name: Update 'ops' dependency in test charm to latest - run: | - rm -rf myops/test - if [ -e "test-requirements.txt" ]; then - sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" test-requirements.txt - echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> test-requirements.txt - fi - if [ -e "requirements-charmcraft.txt" ]; then - sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements-charmcraft.txt - echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements-charmcraft.txt - fi - if [ -e "requirements.txt" ]; then - sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements.txt - echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements.txt - else - sed -i -e "s/^ops[ ><=].*/ops = {path = \"myops\"}/" pyproject.toml - poetry lock --no-update - fi - - - name: Install dependencies - run: pip install tox~=4.2 - - - name: Run the charm's unit tests - run: tox -vve unit - + - charm-repo: canonical/data-platform-libs + - charm-repo: canonical/kafka-operator + - charm-repo: canonical/mongodb-operator + - charm-repo: canonical/traefik-k8s-operator + - charm-repo: canonical/mysql-router-k8s-operator + - charm-repo: canonical/pgbouncer-k8s-operator + - charm-repo: canonical/discourse-k8s-operator + - charm-repo: canonical/s3-integrator + - charm-repo: canonical/zookeeper-operator + - charm-repo: canonical/loki-k8s-operator + - charm-repo: canonical/openfga-operator + - charm-repo: canonical/indico-operator + - charm-repo: canonical/trino-k8s-operator + - charm-repo: canonical/route53-lego-k8s-operator + - charm-repo: canonical/wordpress-k8s-operator + - charm-repo: canonical/temporal-k8s-operator + - charm-repo: canonical/jenkins-agent-operator + - charm-repo: canonical/seldon-core-operator + - charm-repo: canonical/nginx-ingress-integrator-operator + - charm-repo: canonical/oathkeeper-operator + - charm-repo: canonical/jenkins-agent-k8s-operator + - charm-repo: canonical/grafana-agent-k8s-operator + - charm-repo: canonical/namecheap-lego-k8s-operator + - charm-repo: canonical/superset-k8s-operator + - charm-repo: canonical/identity-platform-login-ui-operator + - charm-repo: canonical/temporal-admin-k8s-operator + - charm-repo: canonical/dex-auth-operator + - charm-repo: canonical/temporal-ui-k8s-operator + - charm-repo: canonical/temporal-worker-k8s-operator + - charm-repo: canonical/content-cache-k8s-operator + - charm-repo: canonical/ranger-k8s-operator + - charm-repo: canonical/oauth2-proxy-k8s-operator + - charm-repo: canonical/livepatch-k8s-operator + - charm-repo: canonical/self-signed-certificates-operator + - charm-repo: canonical/saml-integrator-operator + - charm-repo: canonical/smtp-integrator-operator + - charm-repo: canonical/manual-tls-certificates-operator + - charm-repo: canonical/hardware-observer-operator charmcraft-profile-tests: runs-on: ubuntu-latest + steps: + - name: Install charmcraft + run: sudo snap install charmcraft --classic + - name: Charmcraft init + run: charmcraft init --profile=${{ matrix.profile }} --author=charm-tech + - name: Checkout the operator repository + uses: actions/checkout@v4 + with: + path: myops + - name: Update 'ops' dependency in test charm to latest + run: "rm -rf myops/test\nif [ -e \"requirements.txt\" ]; then\n sed -i -e \"\ + /^ops[ ><=]/d\" -e \"/canonical\\/operator/d\" -e \"/#egg=ops/d\" requirements.txt\n\ + \ echo -e \"\\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops\"\ + \ >> requirements.txt\nfi\n" + - name: Install dependencies + run: pip install tox~=4.2 + - name: Run the charm's unit tests + run: tox -vve unit strategy: fail-fast: false matrix: include: - - profile: machine - - profile: kubernetes - - profile: simple - steps: - - name: Install charmcraft - run: sudo snap install charmcraft --classic - - - name: Charmcraft init - run: charmcraft init --profile=${{ matrix.profile }} --author=charm-tech - - - name: Checkout the operator repository - uses: actions/checkout@v4 - with: - path: myops - - - name: Update 'ops' dependency in test charm to latest - run: | - rm -rf myops/test - if [ -e "requirements.txt" ]; then - sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements.txt - echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements.txt - fi - - - name: Install dependencies - run: pip install tox~=4.2 - - - name: Run the charm's unit tests - run: tox -vve unit + - profile: machine + - profile: kubernetes + - profile: simple From 68b9883f5eec811f8a27a15eff17346328275517 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 10 Sep 2024 13:24:54 +1200 Subject: [PATCH 16/24] Add timeouts for the downloads. --- .../update-published-charms-tests-workflow.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/update-published-charms-tests-workflow.py b/.github/update-published-charms-tests-workflow.py index ab2376efd..438f95351 100755 --- a/.github/update-published-charms-tests-workflow.py +++ b/.github/update-published-charms-tests-workflow.py @@ -70,7 +70,7 @@ def packages(): """Get the list of published charms from Charmhub.""" logger.info('Fetching the list of published charms') url = 'https://charmhub.io/packages.json' - with urllib.request.urlopen(url) as response: + with urllib.request.urlopen(url, timeout=120) as response: data = response.read().decode() packages = json.loads(data)['packages'] return packages @@ -80,14 +80,18 @@ def get_source_url(charm: str): """Get the source URL for a charm.""" logger.info("Looking for a 'source' URL for %s", charm) try: - with urllib.request.urlopen(f'{URL_BASE}/{charm}?fields=result.links') as response: + with urllib.request.urlopen( + f'{URL_BASE}/{charm}?fields=result.links', timeout=30 + ) as response: data = json.loads(response.read().decode()) return data['result']['links']['source'][0] except (urllib.error.HTTPError, KeyError): pass logger.info("Looking for a 'bugs-url' URL for %s", charm) try: - with urllib.request.urlopen(f'{URL_BASE}/{charm}?fields=result.bugs-url') as response: + with urllib.request.urlopen( + f'{URL_BASE}/{charm}?fields=result.bugs-url', timeout=30 + ) as response: data = json.loads(response.read().decode()) return data['result']['bugs-url'] except (urllib.error.HTTPError, KeyError): @@ -118,6 +122,7 @@ def url_to_charm_name(url: str): def main(): """Update the workflow file.""" + logging.basicConfig(level=logging.INFO) charms = (url_to_charm_name(get_source_url(package['name'])) for package in packages()) with WORKFLOW.open('r') as f: workflow = yaml.safe_load(f) @@ -126,13 +131,13 @@ def main(): ] with WORKFLOW.open('w') as f: yaml.dump(workflow, f) - # yaml.safe_load/yaml.dump transforms "on" to "true". I'm not sure how to avoid that. + # yaml.safe_load transforms "on" to "true". See https://github.com/yaml/pyyaml/issues/376 + # The 'run' step that patches the requirements files also ends up looking + # rather unfriendly, but it's still functionally the same, so works. with WORKFLOW.open('r') as f: content = f.read().replace('true:', 'on:') with WORKFLOW.open('w') as f: f.write(content) - # TODO: the "Update 'ops' dependency in test charm to latest" run command also gets messed up - # and has to get fixed. if __name__ == '__main__': From 6241bf97a4c679d1bb71ab70c60d65ff84ecab37 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 10 Sep 2024 13:38:09 +1200 Subject: [PATCH 17/24] Style and linting fixes. --- .github/update-published-charms-tests-workflow.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/update-published-charms-tests-workflow.py b/.github/update-published-charms-tests-workflow.py index 438f95351..7845dda80 100755 --- a/.github/update-published-charms-tests-workflow.py +++ b/.github/update-published-charms-tests-workflow.py @@ -33,10 +33,10 @@ import urllib.parse import urllib.request -logger = logging.getLogger(__name__) - import yaml +logger = logging.getLogger(__name__) + URL_BASE = 'https://api.charmhub.io/v2/charms/info' WORKFLOW = pathlib.Path(__file__).parent / 'workflows' / 'published-charms-tests.yaml' @@ -70,7 +70,7 @@ def packages(): """Get the list of published charms from Charmhub.""" logger.info('Fetching the list of published charms') url = 'https://charmhub.io/packages.json' - with urllib.request.urlopen(url, timeout=120) as response: + with urllib.request.urlopen(url, timeout=120) as response: # noqa: S310 (unsafe URL) data = response.read().decode() packages = json.loads(data)['packages'] return packages @@ -80,7 +80,7 @@ def get_source_url(charm: str): """Get the source URL for a charm.""" logger.info("Looking for a 'source' URL for %s", charm) try: - with urllib.request.urlopen( + with urllib.request.urlopen( # noqa: S310 (unsafe URL) f'{URL_BASE}/{charm}?fields=result.links', timeout=30 ) as response: data = json.loads(response.read().decode()) @@ -89,7 +89,7 @@ def get_source_url(charm: str): pass logger.info("Looking for a 'bugs-url' URL for %s", charm) try: - with urllib.request.urlopen( + with urllib.request.urlopen( # noqa: S310 (unsafe URL) f'{URL_BASE}/{charm}?fields=result.bugs-url', timeout=30 ) as response: data = json.loads(response.read().decode()) From a4b8ab92dce114aad1b3c20ae3fab6249603cf3e Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 10 Sep 2024 14:59:16 +1200 Subject: [PATCH 18/24] Update .github/update-published-charms-tests-workflow.py Co-authored-by: Ben Hoyt --- .github/update-published-charms-tests-workflow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/update-published-charms-tests-workflow.py b/.github/update-published-charms-tests-workflow.py index 7845dda80..9d83a3cea 100755 --- a/.github/update-published-charms-tests-workflow.py +++ b/.github/update-published-charms-tests-workflow.py @@ -135,7 +135,7 @@ def main(): # The 'run' step that patches the requirements files also ends up looking # rather unfriendly, but it's still functionally the same, so works. with WORKFLOW.open('r') as f: - content = f.read().replace('true:', 'on:') + content = f.read().replace('\ntrue:', '\non:') with WORKFLOW.open('w') as f: f.write(content) From 99d2f9022c8cf3136be96cf130cfde2afc68d4af Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 10 Sep 2024 15:19:16 +1200 Subject: [PATCH 19/24] Move the copyright comment above the PEP 723 comment. --- .github/update-published-charms-tests-workflow.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/update-published-charms-tests-workflow.py b/.github/update-published-charms-tests-workflow.py index 9d83a3cea..9e431a609 100755 --- a/.github/update-published-charms-tests-workflow.py +++ b/.github/update-published-charms-tests-workflow.py @@ -1,11 +1,5 @@ #! /usr/bin/env python -# /// script -# dependencies = [ -# "PyYAML", -# ] -# /// - # Copyright 2024 Canonical Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +14,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +# /// script +# dependencies = [ +# "PyYAML", +# ] +# /// + """Update a GitHub workload that runs `tox -e unit` on all published charms. Charms that are not hosted on GitHub are skipped, as well as any charms where From 45aaa8c74e5e70ad1f12f0eb8cf1e88ec1abb5d2 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 10 Sep 2024 16:29:43 +1200 Subject: [PATCH 20/24] Edit the raw text, rather than parsing the YAML and then adjusting it afterwards. --- .../update-published-charms-tests-workflow.py | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/.github/update-published-charms-tests-workflow.py b/.github/update-published-charms-tests-workflow.py index 9e431a609..117127fad 100755 --- a/.github/update-published-charms-tests-workflow.py +++ b/.github/update-published-charms-tests-workflow.py @@ -14,12 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -# /// script -# dependencies = [ -# "PyYAML", -# ] -# /// - """Update a GitHub workload that runs `tox -e unit` on all published charms. Charms that are not hosted on GitHub are skipped, as well as any charms where @@ -29,12 +23,12 @@ import json import logging import pathlib +import re +import typing import urllib.error import urllib.parse import urllib.request -import yaml - logger = logging.getLogger(__name__) @@ -100,7 +94,7 @@ def get_source_url(charm: str): return None -def url_to_charm_name(url: str): +def url_to_charm_name(url: typing.Optional[str]): """Get the charm name from a URL.""" if not url: return None @@ -123,21 +117,18 @@ def url_to_charm_name(url: str): def main(): """Update the workflow file.""" logging.basicConfig(level=logging.INFO) - charms = (url_to_charm_name(get_source_url(package['name'])) for package in packages()) - with WORKFLOW.open('r') as f: - workflow = yaml.safe_load(f) - workflow['jobs']['charm-tests']['strategy']['matrix']['include'] = [ - {'charm-repo': f'canonical/{charm}'} for charm in charms if charm and charm not in SKIP - ] - with WORKFLOW.open('w') as f: - yaml.dump(workflow, f) - # yaml.safe_load transforms "on" to "true". See https://github.com/yaml/pyyaml/issues/376 - # The 'run' step that patches the requirements files also ends up looking - # rather unfriendly, but it's still functionally the same, so works. + charms = [url_to_charm_name(get_source_url(package['name'])) for package in packages()] + charms = [charm for charm in charms if charm and charm not in SKIP] + charms.sort() with WORKFLOW.open('r') as f: - content = f.read().replace('\ntrue:', '\non:') + workflow = f.read() + repos = '\n'.join( + f' - charm-repo: canonical/{charm}' + for charm in charms + ) + workflow = re.sub(r'(\s{10}- charm-repo: \S+\n)+', repos + '\n', workflow, count=1) with WORKFLOW.open('w') as f: - f.write(content) + f.write(workflow) if __name__ == '__main__': From fcc2f4938fe843c2832fbb04dff75964879f7581 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 10 Sep 2024 18:24:18 +1200 Subject: [PATCH 21/24] Revert to the manually crafted file. --- .github/workflows/published-charms-tests.yaml | 200 ++++++++++-------- 1 file changed, 112 insertions(+), 88 deletions(-) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index 87bb45b0e..248f86dd6 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -1,104 +1,128 @@ name: Broad Charm Compatibility Tests + on: schedule: - - cron: 0 1 25 * * - workflow_dispatch: null + - cron: '0 1 25 * *' + workflow_dispatch: + jobs: charm-tests: runs-on: ubuntu-latest - steps: - - name: Checkout the ${{ matrix.charm-repo }} repository - uses: actions/checkout@v4 - with: - repository: ${{ matrix.charm-repo }} - - name: Checkout the operator repository - uses: actions/checkout@v4 - with: - path: myops - - name: Install patch dependencies - run: pip install poetry~=1.6 - - name: Update 'ops' dependency in test charm to latest - run: "rm -rf myops/test\nif [ -e \"test-requirements.txt\" ]; then\n sed -i\ - \ -e \"/^ops[ ><=]/d\" -e \"/canonical\\/operator/d\" -e \"/#egg=ops/d\" test-requirements.txt\n\ - \ echo -e \"\\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops\"\ - \ >> test-requirements.txt\nfi\nif [ -e \"requirements-charmcraft.txt\" ];\ - \ then\n sed -i -e \"/^ops[ ><=]/d\" -e \"/canonical\\/operator/d\" -e \"\ - /#egg=ops/d\" requirements-charmcraft.txt\n echo -e \"\\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops\"\ - \ >> requirements-charmcraft.txt\nfi\nif [ -e \"requirements.txt\" ]; then\n\ - \ sed -i -e \"/^ops[ ><=]/d\" -e \"/canonical\\/operator/d\" -e \"/#egg=ops/d\"\ - \ requirements.txt\n echo -e \"\\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops\"\ - \ >> requirements.txt\nelse\n sed -i -e \"s/^ops[ ><=].*/ops = {path = \\\ - \"myops\\\"}/\" pyproject.toml\n poetry lock --no-update\nfi\n" - - name: Install dependencies - run: pip install tox~=4.2 - - name: Run the charm's unit tests - run: tox -vve unit strategy: fail-fast: false matrix: include: - - charm-repo: canonical/data-platform-libs - - charm-repo: canonical/kafka-operator - - charm-repo: canonical/mongodb-operator - - charm-repo: canonical/traefik-k8s-operator - - charm-repo: canonical/mysql-router-k8s-operator - - charm-repo: canonical/pgbouncer-k8s-operator - - charm-repo: canonical/discourse-k8s-operator - - charm-repo: canonical/s3-integrator - - charm-repo: canonical/zookeeper-operator - - charm-repo: canonical/loki-k8s-operator - - charm-repo: canonical/openfga-operator - - charm-repo: canonical/indico-operator - - charm-repo: canonical/trino-k8s-operator - - charm-repo: canonical/route53-lego-k8s-operator - - charm-repo: canonical/wordpress-k8s-operator - - charm-repo: canonical/temporal-k8s-operator - - charm-repo: canonical/jenkins-agent-operator - - charm-repo: canonical/seldon-core-operator - - charm-repo: canonical/nginx-ingress-integrator-operator - - charm-repo: canonical/oathkeeper-operator - - charm-repo: canonical/jenkins-agent-k8s-operator - - charm-repo: canonical/grafana-agent-k8s-operator - - charm-repo: canonical/namecheap-lego-k8s-operator - - charm-repo: canonical/superset-k8s-operator - - charm-repo: canonical/identity-platform-login-ui-operator - - charm-repo: canonical/temporal-admin-k8s-operator - - charm-repo: canonical/dex-auth-operator - - charm-repo: canonical/temporal-ui-k8s-operator - - charm-repo: canonical/temporal-worker-k8s-operator - - charm-repo: canonical/content-cache-k8s-operator - - charm-repo: canonical/ranger-k8s-operator - - charm-repo: canonical/oauth2-proxy-k8s-operator - - charm-repo: canonical/livepatch-k8s-operator - - charm-repo: canonical/self-signed-certificates-operator - - charm-repo: canonical/saml-integrator-operator - - charm-repo: canonical/smtp-integrator-operator - - charm-repo: canonical/manual-tls-certificates-operator - - charm-repo: canonical/hardware-observer-operator + - charm-repo: canonical/content-cache-k8s-operator + - charm-repo: canonical/data-platform-libs + - charm-repo: canonical/dex-auth-operator + - charm-repo: canonical/discourse-k8s-operator + - charm-repo: canonical/grafana-agent-k8s-operator + - charm-repo: canonical/hardware-observer-operator + - charm-repo: canonical/identity-platform-login-ui-operator + - charm-repo: canonical/indico-operator + - charm-repo: canonical/jenkins-agent-k8s-operator + - charm-repo: canonical/jenkins-agent-operator + - charm-repo: canonical/kafka-operator + - charm-repo: canonical/livepatch-k8s-operator + - charm-repo: canonical/loki-k8s-operator + - charm-repo: canonical/manual-tls-certificates-operator + - charm-repo: canonical/mongodb-operator + - charm-repo: canonical/mysql-router-k8s-operator + - charm-repo: canonical/namecheap-lego-k8s-operator + - charm-repo: canonical/nginx-ingress-integrator-operator + - charm-repo: canonical/oathkeeper-operator + - charm-repo: canonical/oauth2-proxy-k8s-operator + - charm-repo: canonical/openfga-operator + - charm-repo: canonical/pgbouncer-k8s-operator + - charm-repo: canonical/ranger-k8s-operator + - charm-repo: canonical/route53-lego-k8s-operator + - charm-repo: canonical/s3-integrator + - charm-repo: canonical/saml-integrator-operator + - charm-repo: canonical/seldon-core-operator + - charm-repo: canonical/self-signed-certificates-operator + - charm-repo: canonical/smtp-integrator-operator + - charm-repo: canonical/superset-k8s-operator + - charm-repo: canonical/temporal-admin-k8s-operator + - charm-repo: canonical/temporal-k8s-operator + - charm-repo: canonical/temporal-ui-k8s-operator + - charm-repo: canonical/temporal-worker-k8s-operator + - charm-repo: canonical/traefik-k8s-operator + - charm-repo: canonical/trino-k8s-operator + - charm-repo: canonical/wordpress-k8s-operator + - charm-repo: canonical/zookeeper-operator + steps: + - name: Checkout the ${{ matrix.charm-repo }} repository + uses: actions/checkout@v4 + with: + repository: ${{ matrix.charm-repo }} + + - name: Checkout the operator repository + uses: actions/checkout@v4 + with: + path: myops + + - name: Install patch dependencies + run: pip install poetry~=1.6 + + - name: Update 'ops' dependency in test charm to latest + run: | + rm -rf myops/test + if [ -e "test-requirements.txt" ]; then + sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" test-requirements.txt + echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> test-requirements.txt + fi + if [ -e "requirements-charmcraft.txt" ]; then + sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements-charmcraft.txt + echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements-charmcraft.txt + fi + if [ -e "requirements.txt" ]; then + sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements.txt + echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements.txt + elif [ -e "poetry.lock" ]; then + sed -i -e "s/^ops[ ><=].*/ops = {path = \"myops\"}/" pyproject.toml + poetry lock --no-update + else + echo "Error: No requirements.txt or poetry.lock file found" + exit 1 + fi + + - name: Install dependencies + run: pip install tox~=4.2 + + - name: Run the charm's unit tests + run: tox -vve unit + charmcraft-profile-tests: runs-on: ubuntu-latest - steps: - - name: Install charmcraft - run: sudo snap install charmcraft --classic - - name: Charmcraft init - run: charmcraft init --profile=${{ matrix.profile }} --author=charm-tech - - name: Checkout the operator repository - uses: actions/checkout@v4 - with: - path: myops - - name: Update 'ops' dependency in test charm to latest - run: "rm -rf myops/test\nif [ -e \"requirements.txt\" ]; then\n sed -i -e \"\ - /^ops[ ><=]/d\" -e \"/canonical\\/operator/d\" -e \"/#egg=ops/d\" requirements.txt\n\ - \ echo -e \"\\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops\"\ - \ >> requirements.txt\nfi\n" - - name: Install dependencies - run: pip install tox~=4.2 - - name: Run the charm's unit tests - run: tox -vve unit strategy: fail-fast: false matrix: include: - - profile: machine - - profile: kubernetes - - profile: simple + - profile: machine + - profile: kubernetes + - profile: simple + steps: + - name: Install charmcraft + run: sudo snap install charmcraft --classic + + - name: Charmcraft init + run: charmcraft init --profile=${{ matrix.profile }} --author=charm-tech + + - name: Checkout the operator repository + uses: actions/checkout@v4 + with: + path: myops + + - name: Update 'ops' dependency in test charm to latest + run: | + rm -rf myops/test + if [ -e "requirements.txt" ]; then + sed -i -e "/^ops[ ><=]/d" -e "/canonical\/operator/d" -e "/#egg=ops/d" requirements.txt + echo -e "\ngit+$GITHUB_SERVER_URL/$GITHUB_REPOSITORY@$GITHUB_SHA#egg=ops" >> requirements.txt + fi + + - name: Install dependencies + run: pip install tox~=4.2 + + - name: Run the charm's unit tests + run: tox -vve unit From 9c985d726fb16cf2df9e96d93b440a9e825cf7cf Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Tue, 10 Sep 2024 18:26:59 +1200 Subject: [PATCH 22/24] Lint. --- .github/update-published-charms-tests-workflow.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/update-published-charms-tests-workflow.py b/.github/update-published-charms-tests-workflow.py index 117127fad..d604d6a61 100755 --- a/.github/update-published-charms-tests-workflow.py +++ b/.github/update-published-charms-tests-workflow.py @@ -122,10 +122,7 @@ def main(): charms.sort() with WORKFLOW.open('r') as f: workflow = f.read() - repos = '\n'.join( - f' - charm-repo: canonical/{charm}' - for charm in charms - ) + repos = '\n'.join(f' - charm-repo: canonical/{charm}' for charm in charms) workflow = re.sub(r'(\s{10}- charm-repo: \S+\n)+', repos + '\n', workflow, count=1) with WORKFLOW.open('w') as f: f.write(workflow) From 815f78fa0b6d12a24dabe2f48d786404d68cdabe Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Thu, 12 Sep 2024 17:28:39 +1200 Subject: [PATCH 23/24] Align with main@HEAD. --- .github/workflows/tiobe.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tiobe.yaml b/.github/workflows/tiobe.yaml index 92e0d4921..6456792a8 100644 --- a/.github/workflows/tiobe.yaml +++ b/.github/workflows/tiobe.yaml @@ -1,9 +1,9 @@ name: TIOBE Quality Checks on: + workflow_dispatch: schedule: - cron: '0 7 1 * *' - workflow_dispatch: jobs: TICS: From 6007933b1f5ecb896de331665cd4c9938c72ce88 Mon Sep 17 00:00:00 2001 From: Tony Meyer Date: Wed, 18 Sep 2024 09:56:49 +1200 Subject: [PATCH 24/24] Add comment explaining how to update the file. --- .github/workflows/published-charms-tests.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/published-charms-tests.yaml b/.github/workflows/published-charms-tests.yaml index 248f86dd6..d7430de06 100644 --- a/.github/workflows/published-charms-tests.yaml +++ b/.github/workflows/published-charms-tests.yaml @@ -1,3 +1,6 @@ +# To update the list of charms included here, run: +# python .github/update-published-charms-tests-workflow.py + name: Broad Charm Compatibility Tests on: