Skip to content

chore(release): publisher-collection-v0.1.0 #2

chore(release): publisher-collection-v0.1.0

chore(release): publisher-collection-v0.1.0 #2

---
name: Release collection
# A node-collection-vX.Y.Z tag releases the `decdn.node` Ansible collection, a
# publisher-collection-vX.Y.Z tag the `decdn.publisher` one, each on its own version;
# the `decdn-node` Helm chart has its own workflow (release-chart.yml). See
# RELEASING.md. Each step derives the collection (node|publisher) and the version from
# the tag, which the gate step has checked.
#
# `build` always runs: it gates the tag (galaxy.yml and the collection changelog must
# agree on X.Y.Z), re-runs the collection checks, builds the collection, and uploads it
# with SHA256SUMS as a workflow artifact. `publish` makes it public (Galaxy, and a
# GitHub Release) ONLY when the repository variable PUBLISH_ENABLED is 'true'. Until
# then a tag push is a dry run. A manual dispatch is always a dry run.
on:
push:
tags: ['node-collection-v[0-9]+.[0-9]+.[0-9]+', 'publisher-collection-v[0-9]+.[0-9]+.[0-9]+']
workflow_dispatch:
inputs:
tag:
description: Version to dry-run, e.g. node-collection-v0.1.0 (nothing is published)
required: true
permissions:
contents: read
concurrency:
# Per workflow: a manual dry run of each, both from main, must not queue behind
# (or cancel) the other.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false # never cut a half-published release short
env:
TAG: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }}
# `bash -eo pipefail`, not the default `bash -e`, so a failure anywhere in a pipe
# fails the step.
defaults:
run:
shell: bash
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Gate the tag against the collection and its changelog
# The gate accepts every artifact's tag; a manual dispatch could pass the chart's,
# so refuse it here, before anything is built.
run: |
[[ "$TAG" == node-collection-v* || "$TAG" == publisher-collection-v* ]] \
|| { echo "::error::$TAG is not a collection tag (node-collection-vX.Y.Z or publisher-collection-vX.Y.Z)"; exit 1; }
scripts/check-release-version.sh "$TAG" --notes release-notes.md
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Install collection build + import tooling
run: python -m pip install --upgrade ansible-core ansible-lint galaxy-importer
- name: Build and validate the collection
working-directory: ansible
env:
ANSIBLE_COLLECTIONS_PATH: collections
run: |
make deps
make "galaxy-check-${TAG%-collection-v*}"
- name: Collect the collection and write checksums
run: |
mkdir -p dist
cp ansible/build/decdn-"${TAG%-collection-v*}"-"${TAG##*-collection-v}".tar.gz dist/
(cd dist && sha256sum -- * > SHA256SUMS)
# Not a release asset, so outside SHA256SUMS: the Release body.
cp release-notes.md dist/
cat dist/SHA256SUMS
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: release-${{ env.TAG }}
path: dist/
if-no-files-found: error
publish:
needs: build
if: vars.PUBLISH_ENABLED == 'true' && github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 20
# One collection publish at a time, so the Latest decision below cannot race another
# version's. Job-level, so a dry run (which never reaches this job) cannot queue
# behind or displace a real publish.
concurrency:
group: ${{ github.workflow }}-publish
cancel-in-progress: false
# Protect this environment with required reviewers in the repository settings.
environment: release
permissions:
contents: write # the GitHub Release
steps:
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: release-${{ env.TAG }}
path: dist
- name: Verify the artifacts are the ones build checksummed
working-directory: dist
run: sha256sum --check SHA256SUMS
- name: Require the Galaxy API key
env:
GALAXY_API_KEY: ${{ secrets.GALAXY_API_KEY }}
run: |
[ -n "$GALAXY_API_KEY" ] || { echo "::error::secret GALAXY_API_KEY is not set"; exit 1; }
# The repository's Latest follows decdn.node, the node operator's collection: a
# decdn.publisher release is never Latest, and a node release is Latest only when
# no higher node release exists (a patch to an older line must not take Latest
# from a newer one). Explicit, because gh's default leaves it to GitHub's
# date-and-version guess across every release, the chart's included. Before
# Galaxy, so a failing API call stops the job while it can still re-run.
- name: Decide whether this release becomes Latest
id: latest
env:
GH_TOKEN: ${{ github.token }}
run: |
latest=false newest=""
if [[ "$TAG" == node-collection-v* ]]; then
releases="$(gh release list --repo "$GITHUB_REPOSITORY" --limit 1000 --json tagName --jq '.[].tagName')"
newest="$(printf '%s\n' "$releases" "$TAG" \
| sed -nE 's/^node-collection-v([0-9]+\.[0-9]+\.[0-9]+)$/\1/p' | sort -V | tail -n1)"
if [[ "$newest" == "${TAG##*-collection-v}" ]]; then latest=true; fi
fi
echo "latest=$latest" >> "$GITHUB_OUTPUT"
echo "Latest: $latest (newest node collection release: ${newest:-n/a})"
# The runner's system Python is externally managed (PEP 668): pip into it fails.
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Install ansible-core
run: python3 -m pip install --upgrade ansible-core
# decdn.publisher installs only if Galaxy can resolve its decdn.node dependency.
# scripts/release.sh checks origin's node-collection tags, but a tag is not an
# upload (one pushed while PUBLISH_ENABLED was off, or whose publish failed), so
# ask Galaxy itself, before anything is published: re-run this job once decdn.node
# is up.
- name: Require decdn.publisher's decdn.node dependency on Galaxy
run: |
[[ "$TAG" == publisher-collection-v* ]] || exit 0
tarball="dist/decdn-${TAG%-collection-v*}-${TAG##*-collection-v}.tar.gz"
need="$(tar -xzOf "$tarball" MANIFEST.json \
| python3 -c 'import json, sys; print(json.load(sys.stdin)["collection_info"]["dependencies"]["decdn.node"])')"
echo "decdn.publisher requires decdn.node $need"
ansible-galaxy collection download "decdn.node:$need" --no-deps -p "$(mktemp -d)" \
|| { echo "::error::could not resolve decdn.node $need on Galaxy (see the error above): if it is not published, publish a node-collection release first; then re-run this job"; exit 1; }
# Not idempotent: Galaxy refuses a version that already exists, so a re-run
# after this step succeeded fails here. RELEASING.md covers recovery.
- name: Publish the collection to Galaxy
env:
GALAXY_API_KEY: ${{ secrets.GALAXY_API_KEY }}
run: ansible-galaxy collection publish dist/decdn-"${TAG%-collection-v*}"-"${TAG##*-collection-v}".tar.gz --api-key "$GALAXY_API_KEY"
# Last, so the Release page only appears once the collection is live.
- name: Create the GitHub Release
env:
GH_TOKEN: ${{ github.token }}
LATEST: ${{ steps.latest.outputs.latest }}
run: |
[[ "$LATEST" == true || "$LATEST" == false ]] || { echo "::error::no Latest decision ('$LATEST')"; exit 1; }
gh release create "$TAG" --repo "$GITHUB_REPOSITORY" \
--title "decdn.${TAG%-collection-v*} collection ${TAG##*-collection-v}" --latest="$LATEST" \
--notes-file dist/release-notes.md \
dist/decdn-"${TAG%-collection-v*}"-*.tar.gz dist/SHA256SUMS