Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/actions/test-rock/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Test rock
description: Set up Rockcraft and LXD, then run spread tests.

inputs:
rock-name:
description: Rock name used for log artifact naming.
required: true
rock-path:
description: Path to the rock directory.
required: true
runner:
description: Runner label used for log artifact naming.
required: true

runs:
using: composite
steps:
- name: Install Rockcraft
shell: bash
run: sudo snap install rockcraft --classic

- name: Set up LXD
uses: canonical/setup-lxd@v0.1.2

- name: Allow runner to use LXD
shell: bash
run: sudo usermod --append --groups lxd "$USER"

# `rockcraft test` packs the rock and runs the spread suites against it.
# Fail if no spread.yaml is found.
- name: Run spread tests
shell: bash
working-directory: ${{ inputs.rock-path }}
run: |
if [ ! -f spread.yaml ]; then
echo "No spread.yaml in ${{ inputs.rock-path }}; failing."
exit 1
fi
sg lxd -c "rockcraft test"

- name: Rockcraft logs
if: ${{ success() || failure() }}
shell: bash
run: cat ~/.local/state/rockcraft/log/* || true

- name: Upload Rockcraft logs
if: ${{ success() || failure() }}
uses: actions/upload-artifact@v4
with:
name: rockcraft-logs-${{ inputs.rock-name }}-${{ inputs.runner }}
path: ~/.local/state/rockcraft/log/*
if-no-files-found: ignore
retention-days: 14

- name: Upload spread diagnostics
if: ${{ success() || failure() }}
uses: actions/upload-artifact@v4
with:
name: spread-diagnostics-${{ inputs.rock-name }}-${{ inputs.runner }}
path: ${{ inputs.rock-path }}/spread-logs/**
if-no-files-found: ignore
retention-days: 14
23 changes: 23 additions & 0 deletions .github/scripts/retarget-rock-stage-snaps.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail

: "${ROCK_NAME:?ROCK_NAME must be set}"
: "${SNAP_CHANNEL:?SNAP_CHANNEL must be set}"

# Check that rockcraft.yaml has stage snaps for this rock pointing at the
# expected channel, e.g. "mongodb-server-sharded/6/edge".
if ! yq \
'.parts[] | select(has("stage-snaps")) | .["stage-snaps"][] | select(test("^" + env.ROCK_NAME + "/6/edge$"))' \
"${ROCKCRAFT_FILE}" | grep -q .; then
echo "No ${ROCK_NAME}/6/edge stage snap channel found in ${ROCKCRAFT_FILE}"
exit 1
fi

# Rewrite rockcraft.yaml to point stage snaps at the PR snap channel. For
# example, "mongodb-server-sharded/6/edge" becomes
# "mongodb-server-sharded/6/edge/pr-123".
yq -Yi \
'(.parts[] | select(has("stage-snaps")) | .["stage-snaps"][]) |= sub("/6/edge$"; "/" + env.SNAP_CHANNEL)' \
"${ROCKCRAFT_FILE}"

git diff -- "${ROCKCRAFT_FILE}"
32 changes: 32 additions & 0 deletions .github/scripts/snapcraft-revisions-lib.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash

get_live_snap_revisions_by_arch() {
local snap="$1"

# `snapcraft revisions` prints a table, so read it as raw text and turn the
# rows for the current live channel into {"amd64":2,"arm64":1}.
snapcraft revisions "$snap" \
| jq -Rsc --arg channel "$CHANNEL" '
# Split the raw table output into lines.
split("\n")
# Drop the header row: Rev. Uploaded Arches Version Channels.
| .[1:]
# Ignore empty lines, split rows on spaces, and remove empty columns
# caused by table alignment.
| map(select(length > 0) | split(" ") | map(select(length > 0)))
# Keep only rows with the expected table columns.
| map(select(length >= 5))
# Convert each row into a clearer object:
# column 1 is revision, column 3 is architecture, column 5 is channels.
| map({
revision: (.[0] | tonumber),
arch: .[2],
channels: (.[4] | split(","))
})
# Keep only rows where the target channel is marked live with "*".
| map(select(.channels | index($channel + "*")))
# Convert matching rows to architecture keyed objects.
| map({(.arch): .revision})
# Merge architecture objects; return {} if no revisions matched.
| add // {}'
}
28 changes: 28 additions & 0 deletions .github/scripts/snapshot-current-snap-revisions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail

# This script captures the current Snapcraft revisions for snaps that back rocks.
# It is intended for GitHub Actions workflows where we need to know the current
# channel revisions before triggering a promotion or release.

source .github/scripts/snapcraft-revisions-lib.sh

# Identify snaps that back rocks by matching names.
names=$(jq -nr --argjson rocks "$ROCKS" --argjson snaps "$SNAPS" '
[$rocks[].name] as $rock_names
| [$snaps[].name | select(. as $snap_name | $rock_names | index($snap_name))]
| .[]')

result='{}'
for snap in $names; do
# Query Snapcraft for the current revisions on the target channel.
revisions=$(get_live_snap_revisions_by_arch "$snap")

# `revisions` looks like {"amd64":2,"arm64":1}
echo "Current ${snap} on ${CHANNEL}: ${revisions}"
# Build a JSON object mapping snap name to architecture-specific revision values.
result=$(jq -c --arg s "$snap" --argjson r "$revisions" '. + {($s): $r}' <<<"$result")
done

# Export the collected revisions as a GitHub Actions output variable.
echo "current-revisions=${result}" >> "$GITHUB_OUTPUT"
63 changes: 63 additions & 0 deletions .github/scripts/wait-for-snap-revisions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
set -euo pipefail

# This script waits until the snap revisions for each architecture have advanced
# on the target channel, compared with the revisions previously recorded in
# CURRENT_REVISIONS.

source .github/scripts/snapcraft-revisions-lib.sh

snaps=$(jq -r 'keys[]' <<<"$CURRENT_REVISIONS")
if [ -z "$snaps" ]; then
echo "No snap-backed rocks to wait for; nothing to do."
exit 0
fi

# We track both amd64 and arm64 revisions.
archs="amd64 arm64"
pending=$(
for snap in $snaps; do
for arch in $archs; do
echo "${snap} ${arch}"
done
done
)

# Poll up to 60 times with a 30-second delay between attempts.
for i in $(seq 1 60); do
next_pending=""
status=""
pending_snaps=$(awk '{print $1}' <<<"$pending" | sort -u)
for snap in $pending_snaps; do
# Fetch the current live revision state for this snap/channel.
current=$(get_live_snap_revisions_by_arch "$snap")

snap_status=""
pending_archs=$(awk -v snap="$snap" '$1 == snap {print $2}' <<<"$pending")
for arch in $pending_archs; do
previous=$(jq -r --arg s "$snap" --arg arch "$arch" '.[$s][$arch] // 0' <<<"$CURRENT_REVISIONS")
now=$(jq -r --arg arch "$arch" '.[$arch] // 0' <<<"$current")
if [ "$now" -le "$previous" ]; then
# Still waiting for a new revision on this architecture.
next_pending="${next_pending}${snap} ${arch}"$'\n'
snap_status="${snap_status} ${arch}:${now}<=${previous}"
else
# Revision has advanced beyond the previously recorded value.
snap_status="${snap_status} ${arch}:${now}>${previous}"
fi
done
status="${status} ${snap}={${snap_status# }}"
done
if [ -z "$next_pending" ]; then
echo "All new revisions live on ${CHANNEL}:${status}"
exit 0
fi

pending="$next_pending"
echo "Attempt ${i}: still waiting on ${CHANNEL}:${status}; retrying in 30s"
sleep 30
done

# Timeout expired before all snaps updated.
echo "Timed out waiting for new snap revisions on ${CHANNEL}"
exit 1
10 changes: 10 additions & 0 deletions .github/workflows/cla.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: cla-check
on: [pull_request]

jobs:
cla-check:
runs-on: ubuntu-latest
steps:
- name: Check if CLA signed
uses: canonical/has-signed-canonical-cla@v2
permissions: {}
83 changes: 83 additions & 0 deletions .github/workflows/pr-rocks-from-snaps.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Artifacts PR tests - Rocks from Snaps

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
pull_request:

env:
CHANNEL: 6/edge/pr-${{ github.event.pull_request.number }}

jobs:
discover-rocks:
uses: ./.github/workflows/rocks-discover.yaml

discover-snaps:
uses: ./.github/workflows/snaps-discover.yaml

# 1. Record the revision currently live on the store for the given branch
snapshot-current-snap-revisions:
name: Snapshot current PR snap revisions
needs:
- discover-rocks
- discover-snaps
runs-on: ubuntu-latest
outputs:
current-revisions: ${{ steps.current-revisions.outputs.current-revisions }}
env:
ROCKS: ${{ needs.discover-rocks.outputs.rocks }}
SNAPS: ${{ needs.discover-snaps.outputs.snaps }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_TOKEN_EDGE }}
steps:
- uses: actions/checkout@v6

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

- id: current-revisions
run: .github/scripts/snapshot-current-snap-revisions.sh

# 2. Build and publish the snaps to a branch
publish-pr-snaps:
name: Publish snaps to PR branch
needs: snapshot-current-snap-revisions
uses: ./.github/workflows/snaps-pr-publish.yaml
secrets:
SNAP_STORE_TOKEN_EDGE: ${{ secrets.SNAP_STORE_TOKEN_EDGE }}
permissions:
actions: read
contents: read

# 3. Wait until the new revisions are available
wait-for-snap-revision:
name: Wait for PR snap revisions
needs:
- snapshot-current-snap-revisions
- publish-pr-snaps
runs-on: ubuntu-latest
timeout-minutes: 30
env:
CURRENT_REVISIONS: ${{ needs.snapshot-current-snap-revisions.outputs.current-revisions }}
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_TOKEN_EDGE }}
steps:
- uses: actions/checkout@v6

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

- name: Poll store until newer revisions appear
run: .github/scripts/wait-for-snap-revisions.sh

# 4. Build the rock using the snap in the branch and test it
test-pr-rocks:
name: Test rocks using PR snaps
needs: wait-for-snap-revision
uses: ./.github/workflows/rocks-pr-tests.yaml
with:
snap-channel: 6/edge/pr-${{ github.event.pull_request.number }}
permissions:
actions: read
contents: read
security-events: write
Loading
Loading