Repository navigation
Expand file tree
/
Copy pathaction.yml
More file actions
411 lines (390 loc) · 19.4 KB
/
Copy pathaction.yml
File metadata and controls
411 lines (390 loc) · 19.4 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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
name: S3 Cache action
description: Cache files on S3 with branch-specific paths for granular permissions
author: SonarSource
inputs:
path:
description: A list of files, directories, and wildcard patterns to cache and restore
required: true
key:
description: An explicit key for restoring and saving the cache
required: true
restore-keys:
description: An ordered list of prefix-matched keys to use for restoring stale cache if no cache hit occurred for key
upload-chunk-size:
description: The chunk size used to split up large files during upload, in bytes
enableCrossOsArchive:
description: When enabled, allows to save or restore caches that can be restored or saved respectively on other platforms
default: false
fail-on-cache-miss:
description: Fail the workflow if cache entry is not found
default: false
lookup-only:
description: Check if a cache entry exists for the given input(s) (key, restore-keys) without downloading the cache
default: false
environment:
description: Environment to use ('dev' or 'prod', 's3' backend only).
default: prod
fallback-branch:
description: >
Explicit fallback branch for restore keys (pattern 'branch-*', 's3' backend only).
Always honoured when set, regardless of 'fallback-to-default-branch'.
If not set, the repository default branch is used when 'fallback-to-default-branch' is true.
fallback-to-default-branch:
description: >
When enabled, automatically adds a fallback restore key pointing to the default branch cache.
Only applies to the S3 backend.
default: 'true'
backend:
description: >
Force cache backend ('github' or 's3'). If not set, falls back to the CACHE_BACKEND environment variable if defined,
then automatically determined based on repository visibility.
import-github-cache:
description: >
Import GitHub cache to S3 when no S3 cache exists (migration mode). Enabled by default when using S3 backend.
Set to 'false' to disable, or control via the CACHE_IMPORT_GITHUB environment variable.
default: ''
outputs:
cache-hit:
description: A boolean value to indicate an exact match was found for the primary key
value: ${{ steps.cache-outputs.outputs.cache-hit }}
cache-matched-key:
description: >
The cache key for which a match was found. Equals the primary key on an exact hit, one of the user-provided restore keys on a partial
hit, or empty when no match was found.
value: ${{ steps.cache-outputs.outputs.matched-key }}
restore-key-hit:
description: >
The restore key that was prefix-matched. Populated only when `cache-hit` is `false` AND a restore key matched (partial hit). Empty on
exact hits and on full misses — i.e. set if this was a partial restore.
value: ${{ steps.cache-outputs.outputs.cache-hit != 'true' && steps.cache-outputs.outputs.matched-key || '' }}
backend:
description: The cache backend that was actually used for this invocation (`github` or `s3`).
value: ${{ steps.cache-backend.outputs.cache-backend }}
cache-size-bytes:
description: >
Total size in bytes of the cached path(s) at restore-time. Requires GNU du (Linux); empty on other platforms.
The action also writes a per-invocation JSON record to /tmp/ci-metrics/cache-${step}.json for the pipeline-runtime-metrics
job-completed hook to ingest. The JSON record is written on all platforms; only the size fields are null on non-Linux.
value: ${{ steps.cache-metrics.outputs.cache-size-bytes }}
runs:
using: composite
steps:
- name: Determine cache backend and import mode
id: cache-backend
shell: bash
env:
GITHUB_TOKEN: ${{ github.token }}
REPO_VISIBILITY: ${{ github.event.repository.visibility }}
FORCED_BACKEND: ${{ inputs.backend }}
INPUT_IMPORT_GITHUB_CACHE: ${{ inputs.import-github-cache }}
run: |
echo "::group::Resolve backend"
BACKEND_SOURCE="auto"
WAS_GITHUB_DEFAULT="false"
if [[ "$FORCED_BACKEND" == "github" || "$FORCED_BACKEND" == "s3" ]]; then
CACHE_BACKEND="$FORCED_BACKEND"
BACKEND_SOURCE="forced"
echo "Using forced backend from input: $CACHE_BACKEND"
elif [[ "$CACHE_BACKEND" == "github" || "$CACHE_BACKEND" == "s3" ]]; then
BACKEND_SOURCE="forced"
echo "Using backend from CACHE_BACKEND environment variable: $CACHE_BACKEND"
else
# If visibility is not available in the event, try to get it from the API
if [[ -z "$REPO_VISIBILITY" || "$REPO_VISIBILITY" = "null" ]]; then
REPO_VISIBILITY=$(curl -s -H "Authorization: token ${{ github.token }}" \
"https://api.github.com/repos/${{ github.repository }}" | \
jq -r '.visibility // "private"')
fi
echo "Repository visibility: $REPO_VISIBILITY"
CACHE_BACKEND="s3"
if [[ "$REPO_VISIBILITY" == "public" ]]; then
WAS_GITHUB_DEFAULT="true"
echo "Using S3 cache for public repository (previously GitHub default — migration eligible)"
else
echo "Using S3 cache for private/internal repository"
fi
fi
echo "::endgroup::"
echo "::group::Resolve GitHub cache import mode"
# Resolution order: input → CACHE_IMPORT_GITHUB env var → true if backend was explicitly forced (migration scenario)
# → true if public repo switching from GitHub default → false (private/internal, already on S3)
if [[ -n "$INPUT_IMPORT_GITHUB_CACHE" ]]; then
IMPORT_GITHUB="$INPUT_IMPORT_GITHUB_CACHE"
echo "Using import mode from input: $IMPORT_GITHUB"
elif [[ -n "${CACHE_IMPORT_GITHUB:-}" ]]; then
IMPORT_GITHUB="$CACHE_IMPORT_GITHUB"
echo "Using import mode from CACHE_IMPORT_GITHUB environment variable: $IMPORT_GITHUB"
elif [[ "$BACKEND_SOURCE" == "forced" ]]; then
IMPORT_GITHUB="true"
echo "Using default import mode (backend explicitly forced to S3 — migration scenario): $IMPORT_GITHUB"
elif [[ "$WAS_GITHUB_DEFAULT" == "true" ]]; then
IMPORT_GITHUB="true"
echo "Using default import mode (public repository — previously used GitHub cache by default): $IMPORT_GITHUB"
else
IMPORT_GITHUB="false"
echo "Using default import mode (private/internal repository — already on S3): $IMPORT_GITHUB"
fi
echo "::endgroup::"
{
echo "cache-backend=$CACHE_BACKEND"
echo "backend-source=$BACKEND_SOURCE"
echo "was-github-default=$WAS_GITHUB_DEFAULT"
echo "import-github=$IMPORT_GITHUB"
} >> "$GITHUB_OUTPUT"
echo "ACTION_PATH_CACHE=${{ github.action_path }}" >> "$GITHUB_ENV"
- name: Cache with GitHub Actions (public repos)
if: steps.cache-backend.outputs.cache-backend == 'github'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: github-cache
with:
path: ${{ inputs.path }}
key: ${{ inputs.key }}
restore-keys: ${{ inputs.restore-keys }}
upload-chunk-size: ${{ inputs.upload-chunk-size }}
enableCrossOsArchive: ${{ inputs.enableCrossOsArchive }}
fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }}
lookup-only: ${{ inputs.lookup-only }}
- name: Setup S3 cache credentials
if: steps.cache-backend.outputs.cache-backend == 's3'
id: aws-auth
uses: SonarSource/gh-action_cache/credential-setup@v1
with:
environment: ${{ inputs.environment }}
- name: Prepare cache keys
if: steps.cache-backend.outputs.cache-backend == 's3'
shell: bash
id: prepare-keys
env:
INPUT_KEY: ${{ inputs.key }}
INPUT_RESTORE_KEYS: ${{ inputs.restore-keys }}
INPUT_FALLBACK_BRANCH: ${{ inputs.fallback-branch }}
INPUT_FALLBACK_TO_DEFAULT_BRANCH: ${{ inputs.fallback-to-default-branch }}
GITHUB_TOKEN: ${{ github.token }}
GITHUB_REPOSITORY: ${{ github.repository }}
run: $ACTION_PATH_CACHE/scripts/prepare-keys.sh
- name: Probe S3 cache (lookup-only)
if: steps.cache-backend.outputs.cache-backend == 's3'
uses: step-security/runs-on-cache/restore@e61ed9b0206c630e9a6bb5f12ca4cd78ae7bbe93 # v5.0.8
id: s3-probe
env:
RUNS_ON_S3_BUCKET_CACHE: sonarsource-s3-cache-${{ inputs.environment }}-bucket
AWS_DEFAULT_REGION: eu-central-1
AWS_REGION: eu-central-1
AWS_ACCESS_KEY_ID: ${{ steps.aws-auth.outputs.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ steps.aws-auth.outputs.AWS_SECRET_ACCESS_KEY }}
AWS_SESSION_TOKEN: ${{ steps.aws-auth.outputs.AWS_SESSION_TOKEN }}
AWS_PROFILE: ''
AWS_DEFAULT_PROFILE: ''
with:
path: ${{ inputs.path }}
key: ${{ steps.prepare-keys.outputs.branch-key }}
restore-keys: ${{ steps.prepare-keys.outputs.branch-restore-keys }}
enableCrossOsArchive: ${{ inputs.enableCrossOsArchive }}
fail-on-cache-miss: false
lookup-only: true
- name: Decide S3 cache mode
if: steps.cache-backend.outputs.cache-backend == 's3'
id: s3-decide
shell: bash
env:
LOOKUP_ONLY: ${{ inputs.lookup-only }}
PROBE_HIT: ${{ steps.s3-probe.outputs.cache-hit }}
PROBE_MATCHED_KEY: ${{ steps.s3-probe.outputs.cache-matched-key }}
FALLBACK_EXACT_KEY: ${{ steps.prepare-keys.outputs.fallback-exact-key }}
run: |
echo "::group::Decide S3 cache mode"
if [[ "$LOOKUP_ONLY" == "true" ]]; then
MODE="lookup"
REASON="lookup-only input set — no restore, no save"
elif [[ "$PROBE_HIT" == "true" ]]; then
MODE="restore-only"
REASON="branch-scoped key already holds exact content — restore without redundant save"
elif [[ -n "$PROBE_MATCHED_KEY" && -n "$FALLBACK_EXACT_KEY" && "$PROBE_MATCHED_KEY" == "$FALLBACK_EXACT_KEY" ]]; then
MODE="restore-only"
REASON="content matched the default-branch fallback-exact key — identical to default branch, skip duplicate save"
else
MODE="combined"
REASON="no usable S3 match (or a non-fallback partial match) — restore and save back under the branch-scoped key"
fi
echo "mode=$MODE (matched-key='$PROBE_MATCHED_KEY', fallback-exact-key='$FALLBACK_EXACT_KEY'): $REASON"
echo "mode=$MODE" >> "$GITHUB_OUTPUT"
echo "::endgroup::"
- name: Restore from S3 (no save)
if: steps.cache-backend.outputs.cache-backend == 's3' && steps.s3-decide.outputs.mode == 'restore-only'
uses: step-security/runs-on-cache/restore@e61ed9b0206c630e9a6bb5f12ca4cd78ae7bbe93 # v5.0.8
id: s3-restore
env:
RUNS_ON_S3_BUCKET_CACHE: sonarsource-s3-cache-${{ inputs.environment }}-bucket
AWS_DEFAULT_REGION: eu-central-1
AWS_REGION: eu-central-1
AWS_ACCESS_KEY_ID: ${{ steps.aws-auth.outputs.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ steps.aws-auth.outputs.AWS_SECRET_ACCESS_KEY }}
AWS_SESSION_TOKEN: ${{ steps.aws-auth.outputs.AWS_SESSION_TOKEN }}
AWS_PROFILE: ''
AWS_DEFAULT_PROFILE: ''
with:
path: ${{ inputs.path }}
key: ${{ steps.prepare-keys.outputs.branch-key }}
restore-keys: ${{ steps.prepare-keys.outputs.branch-restore-keys }}
enableCrossOsArchive: ${{ inputs.enableCrossOsArchive }}
fail-on-cache-miss: ${{ steps.cache-backend.outputs.import-github == 'true' && 'false' || inputs.fail-on-cache-miss }}
- name: Cache on S3
if: steps.cache-backend.outputs.cache-backend == 's3' && steps.s3-decide.outputs.mode == 'combined'
uses: step-security/runs-on-cache@e61ed9b0206c630e9a6bb5f12ca4cd78ae7bbe93 # v5.0.8
id: s3-cache
env:
RUNS_ON_S3_BUCKET_CACHE: sonarsource-s3-cache-${{ inputs.environment }}-bucket
AWS_DEFAULT_REGION: eu-central-1
AWS_REGION: eu-central-1
# Step-level env: overrides GITHUB_ENV values for this step
AWS_ACCESS_KEY_ID: ${{ steps.aws-auth.outputs.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ steps.aws-auth.outputs.AWS_SECRET_ACCESS_KEY }}
AWS_SESSION_TOKEN: ${{ steps.aws-auth.outputs.AWS_SESSION_TOKEN }}
# Clear profile so SDK uses env credentials, not ~/.aws/credentials
AWS_PROFILE: ''
AWS_DEFAULT_PROFILE: ''
with:
path: ${{ inputs.path }}
key: ${{ steps.prepare-keys.outputs.branch-key }}
restore-keys: ${{ steps.prepare-keys.outputs.branch-restore-keys }}
upload-chunk-size: ${{ inputs.upload-chunk-size }}
enableCrossOsArchive: ${{ inputs.enableCrossOsArchive }}
# When import mode is active, suppress fail-on-cache-miss here: a subsequent step handles it after also attempting the GitHub cache
# fallback.
fail-on-cache-miss: ${{ steps.cache-backend.outputs.import-github == 'true' && 'false' || inputs.fail-on-cache-miss }}
lookup-only: ${{ inputs.lookup-only }}
- name: Import GitHub cache to S3 (migration fallback)
if: >-
steps.cache-backend.outputs.cache-backend == 's3' &&
(steps.s3-decide.outputs.mode == 'combined' || steps.s3-decide.outputs.mode == 'lookup') &&
steps.s3-probe.outputs.cache-hit != 'true' &&
steps.s3-cache.outputs.cache-hit != 'true' &&
steps.cache-backend.outputs.import-github == 'true'
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: github-import
with:
path: ${{ inputs.path }}
key: ${{ inputs.key }}
restore-keys: ${{ inputs.restore-keys }}
lookup-only: ${{ inputs.lookup-only }}
fail-on-cache-miss: false
- name: Enforce fail-on-cache-miss after GitHub import fallback
if: >-
steps.cache-backend.outputs.cache-backend == 's3' &&
(steps.s3-decide.outputs.mode == 'combined' || steps.s3-decide.outputs.mode == 'lookup') &&
steps.cache-backend.outputs.import-github == 'true' &&
inputs.fail-on-cache-miss == 'true' &&
steps.s3-probe.outputs.cache-hit != 'true' &&
steps.s3-cache.outputs.cache-hit != 'true' &&
steps.github-import.outputs.cache-matched-key == ''
shell: bash
env:
CACHE_KEY: ${{ inputs.key }}
run: |
echo "::error::Cache miss: no cache found in S3 or GitHub for key '${CACHE_KEY}'"
exit 1
# Aggregate `cache-hit` and `cache-matched-key` across the three possible restore steps once, so every consumer (the top-level outputs
# and the cache-metrics sub-action) reads from a single source instead of repeating the triple-OR. Adding a fourth backend would only
# touch this step.
- name: Aggregate cache outputs across backends
id: cache-outputs
shell: bash
env:
HIT_GITHUB: ${{ steps.github-cache.outputs.cache-hit }}
HIT_S3: ${{ steps.s3-cache.outputs.cache-hit }}
HIT_S3_RESTORE: ${{ steps.s3-restore.outputs.cache-hit }}
HIT_S3_PROBE: ${{ steps.s3-probe.outputs.cache-hit }}
HIT_IMPORT: ${{ steps.github-import.outputs.cache-hit }}
MATCHED_GITHUB: ${{ steps.github-cache.outputs.cache-matched-key }}
MATCHED_S3: ${{ steps.s3-cache.outputs.cache-matched-key }}
MATCHED_S3_RESTORE: ${{ steps.s3-restore.outputs.cache-matched-key }}
MATCHED_S3_PROBE: ${{ steps.s3-probe.outputs.cache-matched-key }}
MATCHED_IMPORT: ${{ steps.github-import.outputs.cache-matched-key }}
run: |
CACHE_HIT="${HIT_GITHUB:-${HIT_S3:-${HIT_S3_RESTORE:-${HIT_S3_PROBE:-${HIT_IMPORT:-}}}}}"
MATCHED_KEY="${MATCHED_GITHUB:-${MATCHED_S3:-${MATCHED_S3_RESTORE:-${MATCHED_S3_PROBE:-${MATCHED_IMPORT:-}}}}}"
echo "cache-hit=${CACHE_HIT}" >> "$GITHUB_OUTPUT"
echo "matched-key=${MATCHED_KEY}" >> "$GITHUB_OUTPUT"
- name: Resolve CI metrics gate
id: ci-metrics-gate
if: runner.os == 'Linux' || env.CI_METRICS_ENABLED == 'true'
shell: bash
run: |
echo "::group::CI metrics gate"
CI_METRICS_DIR="${CI_METRICS_DIR:-/tmp/ci-metrics}"
case "${CI_METRICS_ENABLED:-}" in
true)
mkdir -p "$CI_METRICS_DIR"
: > "$CI_METRICS_DIR/enabled"
decision=true
reason="Metrics enabled: workflow-env-true (touched $CI_METRICS_DIR/enabled for post-job hook)"
;;
false)
rm -f "$CI_METRICS_DIR/enabled"
decision=false
reason="Metrics disabled: workflow-env-false (removed $CI_METRICS_DIR/enabled)"
;;
*)
if [[ -e "$CI_METRICS_DIR/enabled" ]]; then
decision=true
reason="Metrics enabled: runner-side decision file present at $CI_METRICS_DIR/enabled"
else
decision=false
reason="Metrics disabled: no signal (CI_METRICS_ENABLED unset and $CI_METRICS_DIR/enabled absent)"
fi
;;
esac
echo "$reason"
echo "decision=$decision" >> "$GITHUB_OUTPUT"
echo "::endgroup::"
- name: Prepare local cache-metrics sub-action
id: cache-metrics-prep
if: steps.ci-metrics-gate.outputs.decision == 'true'
shell: bash
# Fail-open: the metrics flow must never break the cache flow. The next step's `if:` will skip gracefully if preparation fails.
run: |
echo "::group::Prepare .actions/cache-metrics symlink"
set +e
ACTION_PATH_CACHE="${{ github.action_path }}"
echo "github.action_path=$ACTION_PATH_CACHE"
mkdir -p .actions
ln -sf "$ACTION_PATH_CACHE/cache-metrics" .actions/cache-metrics
ls -la .actions/*
if [ ! -e .actions/cache-metrics/action.yml ]; then
echo "::warning::cache-metrics: could not prepare .actions/cache-metrics symlink — skipping metrics."
echo "prepared=false" >> "$GITHUB_OUTPUT"
echo "::endgroup::"
exit 0
fi
echo "prepared=true" >> "$GITHUB_OUTPUT"
echo "::endgroup::"
exit 0
- name: Emit cache metrics (size + saved)
if: steps.cache-metrics-prep.outputs.prepared == 'true'
id: cache-metrics
uses: ./.actions/cache-metrics
with:
path: ${{ inputs.path }}
key: ${{ inputs.key }}
cache-hit: ${{ steps.cache-outputs.outputs.cache-hit }}
matched-key: ${{ steps.cache-outputs.outputs.matched-key }}
backend: ${{ steps.cache-backend.outputs.cache-backend }}
lookup-only: ${{ inputs.lookup-only }}
step-id: ${{ github.action }}
mode: ${{ steps.s3-decide.outputs.mode }}
- name: Credential guard for S3 cache save
if: steps.cache-backend.outputs.cache-backend == 's3'
uses: SonarSource/gh-action_cache/credential-guard@v1
with:
credentials-file: ${{ steps.aws-auth.outputs.credentials-file }}
# Must be the LAST main step so its post fires FIRST in the LIFO post phase: it recreates `.actions/cache-metrics` before
# cache-metrics-post resolves `uses: ./.actions/cache-metrics`, surviving destruction by an external action between cache-metrics-main
# and cache-metrics-post.
- name: Symlink keeper (preserve .actions/cache-metrics across nested checkouts)
if: steps.cache-metrics-prep.outputs.prepared == 'true'
uses: SonarSource/gh-action_cache/symlink-keeper@symlink-keeper-1.0.0
with:
cache-metrics-action-path: ${{ format('{0}/cache-metrics', github.action_path) }}
branding:
icon: upload-cloud
color: blue