Repository navigation
248 lines (228 loc) · 11.2 KB
/
Copy pathregenerate-lockfiles.yaml
File metadata and controls
248 lines (228 loc) · 11.2 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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
name: regenerate-lockfiles
# Event-driven regeneration of the dependency lockfiles and base-image digests.
# NO schedule: calendar-cron auto-bumps were rejected in review (uncontrolled
# churn). Trigger manually (workflow_dispatch) or from a dependency/CVE advisory
# (repository_dispatch, type "regenerate-lockfiles"). A regenerate installs the
# UNLOCKED (latest) dependency set and freezes it against the supplied snapshot
# date; the previous lockfiles stay valid if a matrix cell fails, so urgency is
# low (R-VP-03).
on:
workflow_dispatch:
inputs:
humble_snapshot_date:
description: snapshots.ros.org date to freeze humble against (YYYY-MM-DD)
required: true
type: string
jazzy_snapshot_date:
description: snapshots.ros.org date to freeze jazzy against (YYYY-MM-DD)
required: true
type: string
reason:
description: Why the lockfiles are being regenerated (shown in the PR body)
required: false
type: string
default: manual dispatch
repository_dispatch:
types:
- regenerate-lockfiles
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
regenerate:
strategy:
fail-fast: false
matrix:
include:
- { distro: humble, arch: amd64, image: ubuntu:22.04, runner: ubuntu-24.04 }
- { distro: jazzy, arch: amd64, image: ubuntu:24.04, runner: ubuntu-24.04 }
- { distro: humble, arch: arm64, image: ubuntu:22.04, runner: ubuntu-24.04-arm }
- { distro: jazzy, arch: arm64, image: ubuntu:24.04, runner: ubuntu-24.04-arm }
runs-on: ${{ matrix.runner }}
container:
image: ${{ matrix.image }}
steps:
- name: 🗓️ Resolve snapshot date for ${{ matrix.distro }}
id: date
env:
DISPATCH_DATE: ${{ github.event.inputs[format('{0}_snapshot_date', matrix.distro)] || github.event.client_payload[format('{0}_snapshot_date', matrix.distro)] }}
run: |
set -eu
if [ -z "${DISPATCH_DATE:-}" ]; then
echo "::error::no snapshot date supplied for ${{ matrix.distro }}"
exit 1
fi
case "$DISPATCH_DATE" in
[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]) ;;
*) echo "::error::snapshot date '$DISPATCH_DATE' is not YYYY-MM-DD"; exit 1 ;;
esac
echo "value=$DISPATCH_DATE" >> "$GITHUB_OUTPUT"
- name: 🛒 Check out repository
uses: actions/checkout@v6
- name: 🌐 Configure apt and debconf
run: |
echo 'Acquire::Retries "5";' > /etc/apt/apt.conf.d/99-retries
echo 'Acquire::http::Timeout "30";' >> /etc/apt/apt.conf.d/99-retries
echo 'Acquire::https::Timeout "30";' >> /etc/apt/apt.conf.d/99-retries
echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
- name: 🔧 Install ansible
run: bash ansible/scripts/install-ansible.sh
- name: 📚 Install Galaxy collections
run: |
export PATH="$HOME/.local/bin:$PATH"
ansible-galaxy collection install -f -r ansible-galaxy-requirements.yaml
- name: 🚀 Install dev environment (unlocked, latest versions)
run: |
export PATH="$HOME/.local/bin:$PATH"
ansible-playbook autoware.dev_env.install_dev_env \
-e prompt_install_nvidia=n --skip-tags artifacts -v
- name: 🔁 Regenerate ${{ matrix.distro }}-${{ matrix.arch }} lockfile
env:
ROS_DISTRO: ${{ matrix.distro }}
ROS_SNAPSHOT_DATE: ${{ steps.date.outputs.value }}
run: |
export PATH="$HOME/.local/bin:$PATH"
./ansible/scripts/generate_ansible_lockfile.sh
echo "::group::locked-versions-${{ matrix.distro }}-${{ matrix.arch }}.yaml"
cat "ansible/vars/locked-versions-${{ matrix.distro }}-${{ matrix.arch }}.yaml"
echo "::endgroup::"
- name: ⬆️ Upload regenerated lockfile
uses: actions/upload-artifact@v4
with:
name: locked-versions-${{ matrix.distro }}-${{ matrix.arch }}
path: ansible/vars/locked-versions-${{ matrix.distro }}-${{ matrix.arch }}.yaml
if-no-files-found: error
open-pr:
needs: regenerate
# Proceed even if some matrix cells failed (R-VP-03): apply whatever lockfiles
# were regenerated; cells that failed keep their previous, still-valid lockfile.
if: ${{ !cancelled() }}
runs-on: ubuntu-24.04
permissions:
contents: write
pull-requests: write
steps:
- name: 🛒 Check out repository
uses: actions/checkout@v6
with:
# The push must run as the App, which bypasses the branch rulesets, not as GITHUB_TOKEN.
persist-credentials: false
- name: 🐍 Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: 📦 Install PyYAML
run: pip install pyyaml
- name: ⬇️ Download regenerated lockfiles
uses: actions/download-artifact@v4
with:
pattern: locked-versions-*
path: /tmp/lockfiles
- name: 📝 Apply regenerated lockfiles (per distro, atomically)
run: |
set -eu
applied=false
for distro in humble jazzy; do
amd64="/tmp/lockfiles/locked-versions-${distro}-amd64/locked-versions-${distro}-amd64.yaml"
arm64="/tmp/lockfiles/locked-versions-${distro}-arm64/locked-versions-${distro}-arm64.yaml"
# A distro's shared multi-arch digest and both arch lockfiles must move
# together, so update a distro only when BOTH arches regenerated this run.
# A partial run keeps the whole distro's previous, still-valid closure (R-VP-03).
if [ -f "$amd64" ] && [ -f "$arm64" ]; then
cp -v "$amd64" "$arm64" ansible/vars/
applied=true
else
echo "::warning::skipping ${distro}: incomplete regeneration (need both amd64 and arm64); keeping its previous lockfiles and digest"
fi
done
if [ "$applied" = false ]; then
echo "::warning::no distro was fully regenerated; nothing to update"
fi
- name: 🔐 Resolve base image digests and update docker-bake.hcl
run: |
set -eu
for distro in humble jazzy; do
amd64="/tmp/lockfiles/locked-versions-${distro}-amd64/locked-versions-${distro}-amd64.yaml"
arm64="/tmp/lockfiles/locked-versions-${distro}-arm64/locked-versions-${distro}-arm64.yaml"
# Refresh a distro's shared multi-arch digest only when BOTH arches were
# regenerated this run (matches the atomic apply above), so the digest and
# every arch's ros_snapshot_date for the distro always move together.
if [ ! -f "$amd64" ] || [ ! -f "$arm64" ]; then
echo "Skipping ${distro} digest (incomplete regeneration this run)."
continue
fi
# ros:<distro>-ros-base is a floating tag with no date-pinned variant, so
# the digest is whatever the tag resolves to at regeneration time (here,
# after the matrix cells ran) rather than at the snapshot date. Retry to
# ride out transient registry errors / Docker Hub rate limits; if the tag
# is republished mid-run, re-run so the committed digest matches the
# closure that is validated below.
digest=""
for attempt in 1 2 3; do
if digest=$(docker buildx imagetools inspect "ros:${distro}-ros-base" \
--format '{{.Manifest.Digest}}'); then
break
fi
echo "::warning::imagetools inspect for ros:${distro}-ros-base failed (attempt ${attempt}/3); retrying"
sleep 10
done
if [ -z "$digest" ]; then
echo "::error::could not resolve digest for ros:${distro}-ros-base after 3 attempts"
exit 1
fi
echo "ros:${distro}-ros-base -> ${digest}"
python3 ansible/scripts/update_base_image_digest.py \
docker/docker-bake.hcl --distro "$distro" --digest "$digest"
done
- name: ✅ Validate lockfiles (format + digest/lockfile coverage)
run: ./ansible/scripts/validate_lockfiles.sh
- name: 🔎 Detect changes
id: diff
run: |
if git diff --quiet -- ansible/vars/ docker/docker-bake.hcl; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No lockfile or digest changes; nothing to open a PR for."
else
echo "changed=true" >> "$GITHUB_OUTPUT"
git --no-pager diff --stat -- ansible/vars/ docker/docker-bake.hcl
fi
- name: 🎫 Generate GitHub App token
id: app-token
if: ${{ steps.diff.outputs.changed == 'true' }}
uses: tibdex/github-app-token@v2
continue-on-error: true
with:
app_id: ${{ secrets.APP_ID }}
private_key: ${{ secrets.PRIVATE_KEY }}
- name: 🚀 Open or update the regenerate PR
if: ${{ steps.diff.outputs.changed == 'true' }}
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REASON: ${{ github.event.inputs.reason || github.event.client_payload.reason || 'dependency advisory / manual dispatch' }}
BRANCH: ci/regenerate-lockfiles
run: |
set -eu
# Do NOT fall back to GITHUB_TOKEN: a PR opened with it does not trigger the
# validate-lockfiles gate (GitHub suppresses workflow events on token-created
# PRs), so the regenerated closure would be mergeable unvalidated. Fail loudly
# instead and let a human configure the App or open the PR from $BRANCH.
if [ -z "${GH_TOKEN:-}" ]; then
echo "::error::GitHub App token unavailable (APP_ID/PRIVATE_KEY unset or the token step failed); refusing to open the PR. The branch ${BRANCH} was not pushed; configure the App or open the PR manually."
exit 1
fi
gh auth setup-git
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -C "$BRANCH"
git add ansible/vars/locked-versions-*.yaml docker/docker-bake.hcl
git commit -s \
-m "chore(ansible): regenerate dependency lockfiles and base image digests" \
-m "Regenerated by the regenerate-lockfiles workflow. Reason: ${REASON}."
git push -f origin "$BRANCH"
gh pr create \
--base "${{ github.event.repository.default_branch }}" \
--head "$BRANCH" \
--title "chore(ansible): regenerate dependency lockfiles and base image digests" \
--body "Automated regeneration of the per-distro/arch lockfiles (\`ansible/vars/locked-versions-*.yaml\`) and the base image digests (\`docker/docker-bake.hcl\` \`BASE_IMAGE_DIGESTS\`), refreshed together so the reproducible-build closure stays consistent. Reason: ${REASON}." \
|| echo "PR for ${BRANCH} already exists; the force-push above updated it."
gh pr edit "$BRANCH" --add-label "run:health-check" 2>/dev/null || true