ci/toolchain installs the pinned tools, collections and rsync from cache for both GitHub and Gitea; GitLab caches pip downloads. shellcheck now finds scripts rather than globbing, which broke when a glob matched nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
133 lines
5.2 KiB
YAML
133 lines
5.2 KiB
YAML
---
|
|
# The pinned toolchain, cached. Shared by the deploy, lint and connectivity
|
|
# workflows of both GitHub Actions and Gitea Actions, so that the six cannot
|
|
# drift and so that the cache logic exists once. It lives outside .github/ and
|
|
# .gitea/ for that reason: deleting the platform you do not use must not delete
|
|
# it. Call it after actions/checkout, as `uses: ./ci/toolchain` — it reads the
|
|
# pins from scripts/check.sh and is itself read from the checkout.
|
|
#
|
|
# A fresh install can take a couple of minutes against a deploy of seconds. On a
|
|
# cache hit nothing here touches the network.
|
|
#
|
|
# The toolchain key covers which pins were asked for, the pins themselves
|
|
# (check.sh, requirements.yml), the exact interpreter the venv is bound to, and
|
|
# the month: the pins are ranges so that patch releases arrive, and a cache that
|
|
# never expired would stop them. A new month, a raised pin or a new Python patch
|
|
# builds it afresh. The hash is taken in shell, not with hashFiles(), because
|
|
# Gitea does not implement it — see "Expressions are barely supported" in
|
|
# docs/ci-gitea.md.
|
|
#
|
|
# A cache server the job cannot reach is a miss, not a failure: the steps fall
|
|
# back to installing, exactly as they would with no cache at all.
|
|
name: Pinned toolchain
|
|
description: Python, the pinned Ansible tools and optionally rsync, from cache when possible
|
|
|
|
inputs:
|
|
specs:
|
|
description: >-
|
|
core installs ansible-core only; all adds ansible-lint and yamllint
|
|
default: core
|
|
collections:
|
|
description: Install the Galaxy collections in ansible/requirements.yml
|
|
default: "false"
|
|
rsync:
|
|
description: Install rsync, which ansible.posix.synchronize needs locally
|
|
default: "false"
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
# Kept even though runner images ship a Python: Ubuntu 24.04 images are PEP
|
|
# 668 "externally managed", where a plain `pip install` refuses to touch the
|
|
# system interpreter. See "actions/setup-python stays" in docs/ci-gitea.md.
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Compute the cache keys
|
|
id: keys
|
|
shell: bash
|
|
env:
|
|
SPECS: ${{ inputs.specs }}
|
|
run: |
|
|
set -eu
|
|
case "$SPECS" in
|
|
core|all) ;;
|
|
*) echo "toolchain: specs must be core or all, not '$SPECS'"; exit 1 ;;
|
|
esac
|
|
. /etc/os-release
|
|
py="$(python -c 'import platform; print(platform.python_version())')"
|
|
pins="$(cat scripts/check.sh ansible/requirements.yml | sha256sum | cut -c1-16)"
|
|
{
|
|
echo "os=${ID}-${VERSION_ID}"
|
|
echo "toolchain=toolchain-${SPECS}-${ID}-${VERSION_ID}-py${py}-$(date -u +%Y-%m)-${pins}"
|
|
echo "venv=$HOME/.venvs/toolchain"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
# ansible.posix.synchronize shells out to rsync on the machine Ansible runs
|
|
# on, not only on the target. GitHub's hosted images ship it; Gitea's
|
|
# act_runner images do not. The .debs are cached rather than the apt lists,
|
|
# which are what the slow `apt-get update` fetches. Jobs run as root in a
|
|
# container runner, where sudo is often not installed at all, so it is
|
|
# asked for only off uid 0.
|
|
- name: Restore the rsync packages
|
|
if: inputs.rsync == 'true'
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.cache/toolchain-debs
|
|
key: rsync-debs-${{ steps.keys.outputs.os }}
|
|
|
|
- name: Install rsync
|
|
if: inputs.rsync == 'true'
|
|
shell: bash
|
|
run: |
|
|
set -eu
|
|
if ! command -v rsync >/dev/null 2>&1; then
|
|
sudo=""
|
|
[ "$(id -u)" -eq 0 ] || sudo=sudo
|
|
debs="$HOME/.cache/toolchain-debs"
|
|
if ! ls "$debs"/rsync_*.deb >/dev/null 2>&1; then
|
|
mkdir -p "$debs/partial"
|
|
$sudo apt-get update -qq
|
|
$sudo apt-get install -y --no-install-recommends --download-only \
|
|
-o Dir::Cache::archives="$debs" rsync
|
|
fi
|
|
$sudo dpkg -i "$debs"/*.deb
|
|
fi
|
|
|
|
- name: Restore the pinned toolchain
|
|
id: toolchain
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
${{ steps.keys.outputs.venv }}
|
|
~/.ansible/collections
|
|
key: ${{ steps.keys.outputs.toolchain }}-collections-${{ inputs.collections }}
|
|
|
|
# A venv rather than the setup-python interpreter itself, so that the cache
|
|
# holds exactly what this step installed and nothing else. check.sh prints
|
|
# the core pin first, which is what `specs: core` takes.
|
|
- name: Install the pinned toolchain
|
|
if: steps.toolchain.outputs.cache-hit != 'true'
|
|
shell: bash
|
|
env:
|
|
VENV: ${{ steps.keys.outputs.venv }}
|
|
SPECS: ${{ inputs.specs }}
|
|
COLLECTIONS: ${{ inputs.collections }}
|
|
run: |
|
|
set -eu
|
|
python -m venv "$VENV"
|
|
filter=(cat)
|
|
[ "$SPECS" = "all" ] || filter=(head -n 1)
|
|
scripts/check.sh --print-specs | "${filter[@]}" | tee /dev/stderr \
|
|
| xargs -d '\n' "$VENV/bin/pip" install --quiet
|
|
if [ "$COLLECTIONS" = "true" ]; then
|
|
"$VENV/bin/ansible-galaxy" install -r ansible/requirements.yml
|
|
fi
|
|
|
|
- name: Put the toolchain on PATH
|
|
shell: bash
|
|
env:
|
|
VENV: ${{ steps.keys.outputs.venv }}
|
|
run: echo "$VENV/bin" >> "$GITHUB_PATH"
|