Skip to content

Commit

Permalink
Rework freezing CI
Browse files Browse the repository at this point in the history
  • Loading branch information
sed-i committed Mar 28, 2024
1 parent c002603 commit 9b8b5de
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 127 deletions.
44 changes: 0 additions & 44 deletions .github/workflows/freeze.yaml

This file was deleted.

85 changes: 85 additions & 0 deletions .github/workflows/matrix-edge.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Periodic integration matrix tests

on:
schedule:
# every Wednesday at 02:00
- cron: '0 02 * * WED'
workflow_dispatch:


jobs:
render-freeze-bundle:
name: Render and freeze bundle
# We render and freeze at the start to avoid possible races, in case a new charm was release
# while these tests are still running.
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Render and freeze bundle
env:
CHARMHUB_TOKEN: "${{ secrets.CHARMHUB_TOKEN }}"
run: |
tox -e render-edge
python3 freeze_bundle.py bundle.yaml > bundle.yaml
- name: Upload bundle as artifact to be used by the next job
uses: actions/upload-artifact@v3
with:
name: bundle-edge
path: bundle.yaml
integration-matrix:
name: Matrix tests for edge charms
needs: [ render-freeze-bundle ]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
charm-channel: [ "edge", "beta", "candidate", "stable" ]
juju-track: [ "3.1", "3.4" ]
microk8s-channel: [ "1.27-strict/stable", "1.28-strict/stable" ]
include:
- juju-track: "3.1"
juju-channel: "3.1/stable"
juju-agent-version: "3.1.7"
- juju-track: "3.4"
juju-channel: "3.4/stable"
juju-agent-version: "3.4.0"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get prefsrc
run: |
echo "IPADDR=$(ip -4 -j route get 2.2.2.2 | jq -r '.[] | .prefsrc')" >> $GITHUB_ENV
- name: Setup operator environment
uses: charmed-kubernetes/actions-operator@main
with:
juju-channel: ${{ matrix.juju-channel }}
provider: microk8s
channel: ${{ matrix.microk8s-channel }}
microk8s-addons: "hostpath-storage dns metallb:${{ env.IPADDR }}-${{ env.IPADDR }}"
bootstrap-options: "--agent-version ${{ matrix.juju-agent-version }}"
- name: Update python-libjuju dependency to match juju version
# Assuming the dep is given on a separate tox.ini line
run: sed -E -i 's/^\s*juju\s*~=.+/ juju~=${{ matrix.juju-track }}.0/g' tox.ini
- uses: actions/download-artifact@v3
with:
name: bundle-edge
- name: Run tests (juju ${{ matrix.juju-channel }}, microk8s ${{ matrix.microk8s-channel }})
run: tox -e integration -- --channel=edge
- name: Dump logs
if: failure()
uses: canonical/observability/.github/workflows/_dump-logs.yaml@feature/dump_logs_helper
release-pinned-bundle:
name: Release pinned bundle
needs: [ integration-matrix ]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v3
with:
name: bundle-edge
- name: Upload bundle to edge
uses: canonical/charming-actions/[email protected]
with:
channel: "pinned/edge"
credentials: "${{ secrets.CHARMHUB_TOKEN }}"
github-token: "${{ secrets.GITHUB_TOKEN }}"
83 changes: 0 additions & 83 deletions freeze.sh

This file was deleted.

125 changes: 125 additions & 0 deletions freeze_bundle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python3

"""
This script updates a bundle.yaml file with revisions from charmhub.
"""

import sys
import yaml
import json
from pathlib import Path
import os
import base64
from urllib.request import urlopen, Request


def obtain_charm_releases(charm_name: str) -> dict:
"""Obtain charm releases from charmhub as a dict.
Args:
charm_name: e.g. "grafana-k8s".
"""
if token := os.environ.get("CHARMHUB_TOKEN"):
macaroon = json.loads(base64.b64decode(token))['v']
elif file := os.environ.get("CREDS_FILE"):
macaroon = json.loads(base64.b64decode(Path(file).read_text()))['v']
else:
raise RuntimeError("Must set one of CHARMHUB_TOKEN, CREDS_FILE envvars.")
headers = {"Authorization": f"Macaroon {macaroon}"}

url = f"https://api.charmhub.io/v1/charm/{charm_name}/releases"
with urlopen(Request(url, headers=headers), timeout=10) as response:
body = response.read()

# Output looks like this:
# {
# "channel-map": [
# {
# "base": {
# "architecture": "amd64",
# "channel": "20.04",
# "name": "ubuntu"
# },
# "channel": "1.0/beta",
# "expiration-date": null,
# "progressive": {
# "paused": null,
# "percentage": null
# },
# "resources": [
# {
# "name": "grafana-image",
# "revision": 62,
# "type": "oci-image"
# },
# {
# "name": "litestream-image",
# "revision": 43,
# "type": "oci-image"
# }
# ],
# "revision": 93,
# "when": "2023-11-22T09:12:26Z"
# },
return json.loads(body)


def obtain_revisions_from_charmhub(charm_name: str, channel: str, base_arch: str, base_channel: str) -> dict:
"""Obtain revisions for a given channel and arch.
Args:
charm_name: e.g. "grafana-k8s".
channel: e.g. "latest/edge".
base_arch: base architecture, e.g. "amd64".
base_channel: e.g. "22.04". TODO: remove arg and auto pick the latest
Returns: Dict of resources. Looks like this:
{
"grafana-k8s": {
"revision": 106,
"resources": {
"grafana-image": 68,
"litestream-image": 43
}
}
}
"""
releases = obtain_charm_releases(charm_name)
for channel_dict in releases["channel-map"]:
print(charm_name, channel_dict["channel"], channel_dict["base"]["architecture"], channel_dict["base"]["channel"])
if not(channel_dict["channel"] == channel and channel_dict["base"]["architecture"] == base_arch and channel_dict["base"]["channel"] == base_channel):
continue

return {
charm_name: {
"revision": channel_dict["revision"],
"resources": {res["name"]: res["revision"] for res in channel_dict["resources"]}
}
}


def freeze_bundle(bundle: dict, cleanup: bool = True):
bundle = bundle.copy()
for app_name in bundle["applications"]:
app = bundle["applications"][app_name]
charm_name = app["charm"]
app_channel = app["channel"] if "/" in app["channel"] else f"latest/{app['channel']}"
# TODO externalize "base_arch" as an input to the script.
frozen_app = obtain_revisions_from_charmhub(charm_name, app_channel, "amd64", "20.04")
app["revision"] = frozen_app[charm_name]["revision"]
app["resources"].update(frozen_app[charm_name]["resources"])

if cleanup:
app.pop("constraints", None)
app.pop("storage", None)

return bundle


if __name__ == "__main__":
if len(sys.argv) != 2:
raise RuntimeError("Expecting one arg: path to bundle yaml")

bundle_path = sys.argv[1]
frozen = freeze_bundle(yaml.safe_load(Path(bundle_path).read_text()))
print(yaml.safe_dump(frozen))

0 comments on commit 9b8b5de

Please sign in to comment.