Repository navigation
136 lines (130 loc) · 6.74 KB
/
Copy pathmolecule.yml
File metadata and controls
136 lines (130 loc) · 6.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
---
name: Molecule
# Containerised converge + idempotence + verify of the roles, one runner per scenario.
# Heavy (privileged systemd Docker containers) — scoped to ansible/ changes and
# blocking. Mark the `molecule` check (the aggregate job below) required in branch
# protection once proven; the per-scenario check names follow the scenario list.
# This file is in `paths` alongside ansible/ so a change to the jobs themselves (their
# cache wiring, the matrix, the timeouts) is exercised by the PR that makes it.
# cloud-init/ is in `paths` because the `cloud-init` scenarios boot
# cloud-init/user-data*.yaml and run cloud-init/bootstrap.sh, and scripts/molecule.sh
# because every scenario runs through it (`make molecule`).
on:
pull_request:
paths: ['ansible/**', 'cloud-init/**', 'scripts/molecule.sh', '.github/workflows/molecule.yml']
push:
branches: [main]
paths: ['ansible/**', 'cloud-init/**', 'scripts/molecule.sh', '.github/workflows/molecule.yml']
permissions:
contents: read
concurrency:
group: molecule-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
# The matrix comes from the same discovery `make molecule` uses, so a new scenario
# directory is picked up with no edit here, and a directory without a molecule.yml
# fails the run instead of silently dropping out of it (scripts/molecule.sh).
discover:
runs-on: ubuntu-latest
timeout-minutes: 5 # seconds in practice
outputs:
scenarios: ${{ steps.list.outputs.scenarios }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: List the scenarios
id: list
working-directory: ansible
run: |
scenarios="$(make -s molecule-list)"
echo "$scenarios"
echo "scenarios=$scenarios" >>"$GITHUB_OUTPUT"
# One runner per scenario. They were one job at JOBS=3 on one 4-vCPU runner: ~31m
# for 14 scenarios, CPU-bound (per-task overhead roughly doubled under contention),
# with the slowest scenarios queued last. Now the wall clock is the slowest single
# scenario plus setup: ~10m for 18 scenarios (first run, PR #96), set by
# cloud-init-sponsord (~9m45s; now cloud-init-publisher, which also boots an
# origin node), then cloud-init (~8m) and sponsord-onramp (~7m30s);
# most others take 2-5m. By October 2026 iroh-relay and source-build had reached
# ~11m on their side effects, which are now split over sibling scenarios (#106),
# so no scenario should go much past ~6.5m. Re-measure against the timeout when a
# scenario grows, and split one that approaches it along its side effects (see
# CONTRIBUTING.md).
scenario:
name: molecule (${{ matrix.scenario }})
needs: discover
runs-on: ubuntu-latest # Docker is preinstalled
# A bound, not a target: without one a wedged privileged systemd container burns
# the 360-minute default, and with cancel-in-progress above, some branch-protection
# setups read the resulting cancelled check as "not failed" rather than as a failure.
timeout-minutes: 25
strategy:
# Every scenario always reports: one failure must not hide another's.
fail-fast: false
matrix:
scenario: ${{ fromJSON(needs.discover.outputs.scenarios) }}
defaults:
run:
working-directory: ansible
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Keyed on this workflow file: no pip manifest exists, and the workflow is
# where the package list lives. ci.yml's ansible-lint job has the full note.
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
cache: pip
cache-dependency-path: .github/workflows/molecule.yml
- name: Install molecule + Ansible
run: |
python -m pip install --upgrade \
molecule "molecule-plugins[docker]" ansible ansible-lint docker
# Same key and same rationale as ci.yml's jobs (see ansible-lint there): a warm
# tree makes the install a no-op that never contacts galaxy.ansible.com, and the
# save is gated so a part-way Galaxy failure cannot poison the cache. The key is
# derived wholly from ansible/requirements.yml, so it cannot drift from ci.yml's
# copy the way a duplicated env var would. It wraps
# `make molecule` because the deps install is owned by the Make target here, not
# by a step of its own.
- uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: galaxy-cache
with:
path: ansible/collections
key: galaxy-${{ runner.os }}-${{ hashFiles('ansible/requirements.yml') }}
- name: molecule test
# There is deliberately no separate `make deps` step: `make molecule` runs deps
# itself, so a standalone one would resolve and install the Galaxy requirements
# a second time every run — a second chance to trip over a flaky
# galaxy.ansible.com, for no added coverage. The cache above wraps that
# Make-owned invocation instead. The name goes through env, not into the
# script text: it comes from a directory name in the PR.
env:
SCENARIO: ${{ matrix.scenario }}
run: make molecule SCENARIOS="$SCENARIO"
# A step `if:` without a status-check function implies success(), so this is
# skipped when anything above failed — including a genuinely failing scenario,
# which leaves the cache cold for that leg. Conservative on purpose: it is the
# same guard that keeps a part-way Galaxy install out of the cache. On a cold
# key every passing leg races to save; the losers log a harmless "Cache already
# exists", as ci.yml's jobs that share this key do.
- uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
if: steps.galaxy-cache.outputs.cache-hit != 'true'
with:
path: ansible/collections
key: galaxy-${{ runner.os }}-${{ hashFiles('ansible/requirements.yml') }}
# The one stable check name for branch protection: a matrix leg's name changes with
# the scenario list. `always()` so it reports even when discovery or a leg failed
# (skipped dependants otherwise leave the check skipped, which reads as passing),
# and it passes only when every leg succeeded.
molecule:
needs: [discover, scenario]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5 # seconds in practice
steps:
- name: Require every scenario to pass
env:
DISCOVER: ${{ needs.discover.result }}
SCENARIOS: ${{ needs.scenario.result }}
run: |
echo "discover: $DISCOVER, scenarios: $SCENARIOS"
test "$DISCOVER" = success && test "$SCENARIOS" = success