From 762278a787ae7e051a73832f1cae1bcb623f9e7b Mon Sep 17 00:00:00 2001 From: Julien Carsique Date: Wed, 18 Mar 2026 18:08:30 +0100 Subject: [PATCH] BUILD-10724: Import GitHub cache to S3 when no S3 cache exists (migration mode) When the S3 backend is used and the S3 cache misses, automatically attempt to restore the cache from GitHub using the original unprefixed key. The S3 post-job step will then save the restored content to S3, pre-provisioning it for subsequent runs. The feature is enabled by default. Resolution order to disable it: 1. Action input `import-github-cache: 'false'` 2. Environment variable `CACHE_IMPORT_GITHUB=false` 3. Default: true `fail-on-cache-miss` and `lookup-only` are propagated to the GitHub fallback step. When `fail-on-cache-miss` is set and import mode is active, failure is deferred until both S3 and GitHub have been tried. Also adds `.github/workflows/check-cache-migration.yml`: a manually-triggered workflow that compares GitHub cache entries to S3 objects across target branches (main, master, branch-*, dogfood-on-*, feature/long/*), ignoring transient keys (build-number-*, mise-*). When 100% of entries are found in S3, it automatically sets the CACHE_IMPORT_GITHUB=false repository variable to disable the import fallback (this requires the `CACHE_IMPORT_GITHUB` environment variable to be set from the repository variable via `${{ vars.CACHE_IMPORT_GITHUB }}`). Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/check-cache-migration.yml | 178 ++++++++++++++++++ .../workflows/test-cache-migration-gh2s3.yml | 147 +++++++++++++++ README.md | 115 ++++++++--- action.yml | 59 +++++- 4 files changed, 473 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/check-cache-migration.yml create mode 100644 .github/workflows/test-cache-migration-gh2s3.yml diff --git a/.github/workflows/check-cache-migration.yml b/.github/workflows/check-cache-migration.yml new file mode 100644 index 0000000..edef6f4 --- /dev/null +++ b/.github/workflows/check-cache-migration.yml @@ -0,0 +1,178 @@ +--- +name: Check cache migration (GitHub → S3) + +on: + workflow_dispatch: + inputs: + environment: + description: Cache environment to check + required: true + default: prod + type: choice + options: + - prod + - dev + +jobs: + check-migration: + runs-on: ubuntu-latest + name: Compare GitHub cache vs S3 + permissions: + id-token: write + contents: read + actions: write # required to list GitHub cache entries and set the opt-out repository variable + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Setup S3 cache credentials + id: aws-auth + uses: SonarSource/gh-action_cache/credential-setup@v1 + with: + environment: ${{ inputs.environment }} + + - name: List GitHub cache entries + id: gh-caches + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + # Fetch all GitHub cache entries (paginated, up to 10 000) + PAGE=1 + PER_PAGE=100 + ALL_ENTRIES="[]" + while true; do + RESPONSE=$(curl -s -f \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/caches?per_page=${PER_PAGE}&page=${PAGE}") + ENTRIES=$(echo "$RESPONSE" | jq '.actions_caches') + COUNT=$(echo "$ENTRIES" | jq 'length') + ALL_ENTRIES=$(echo "$ALL_ENTRIES $ENTRIES" | jq -s 'add') + if [[ "$COUNT" -lt "$PER_PAGE" ]]; then + break + fi + PAGE=$((PAGE + 1)) + done + + # Filter: include only target branches, exclude unwanted key patterns + FILTERED=$(echo "$ALL_ENTRIES" | jq '[ + .[] | + select( + (.ref | test("^refs/heads/(main|master|branch-.+|dogfood-on-.+|feature/long/.+)$")) and + (.key | test("^(build-number-|mise-)") | not) + ) | + { ref: .ref, key: .key } + ]') + + TOTAL=$(echo "$ALL_ENTRIES" | jq 'length') + INCLUDED=$(echo "$FILTERED" | jq 'length') + echo "Total GitHub cache entries: $TOTAL" + echo "Included for migration check: $INCLUDED" + + # Write to file for next step + echo "$FILTERED" > /tmp/gh_caches.json + echo "included-count=$INCLUDED" >> "$GITHUB_OUTPUT" + + - name: List S3 cache objects + id: s3-objects + shell: bash + env: + 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_DEFAULT_REGION: eu-central-1 + S3_BUCKET: sonarsource-s3-cache-${{ inputs.environment }}-bucket + run: | + # List all S3 objects with pagination handled internally by the CLI + aws s3 ls "s3://$S3_BUCKET/" --recursive \ + | awk '{print $4}' \ + | sort > /tmp/s3_keys.txt + + S3_COUNT=$(wc -l < /tmp/s3_keys.txt) + echo "Total S3 cache objects: $S3_COUNT" + echo "s3-count=$S3_COUNT" >> "$GITHUB_OUTPUT" + + - name: Compare and report + id: compare + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + INCLUDED=$(jq 'length' /tmp/gh_caches.json) + MIGRATED=0 + MISSING=() + + while IFS= read -r ENTRY; do + REF=$(echo "$ENTRY" | jq -r '.ref') + KEY=$(echo "$ENTRY" | jq -r '.key') + # S3 keys use full ref for push events (refs/heads/main/{key}) and short branch name for PR events (feat/someBranch/{key}). + # Check both forms since we cannot know which was used at save time. + REF_SHORT="${REF#refs/heads/}" + S3_KEY_FULL="${REF}/${KEY}" + S3_KEY_SHORT="${REF_SHORT}/${KEY}" + if grep -qxF "$S3_KEY_FULL" /tmp/s3_keys.txt || grep -qxF "$S3_KEY_SHORT" /tmp/s3_keys.txt; then + MIGRATED=$((MIGRATED + 1)) + else + MISSING+=("$S3_KEY_SHORT") + fi + done < <(jq -c '.[]' /tmp/gh_caches.json) + + echo "" + echo "=========================================" + echo " Migration status: $MIGRATED / $INCLUDED" + echo "=========================================" + + if [[ "${#MISSING[@]}" -gt 0 ]]; then + echo "" + echo "Missing in S3 (${#MISSING[@]} entries):" + printf ' %s\n' "${MISSING[@]}" + fi + + if [[ "$MIGRATED" -eq "$INCLUDED" && "$INCLUDED" -gt 0 ]]; then + echo "" + echo "All included GitHub cache entries are present in S3." + echo "migration-complete=true" >> "$GITHUB_OUTPUT" + else + echo "migration-complete=false" >> "$GITHUB_OUTPUT" + fi + + - name: Set CACHE_IMPORT_GITHUB=false (migration complete) + if: steps.compare.outputs.migration-complete == 'true' + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + VARIABLE_NAME="CACHE_IMPORT_GITHUB" + + # Check if variable already exists + STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/variables/${VARIABLE_NAME}") + + if [[ "$STATUS" == "200" ]]; then + # Update existing variable + curl -s -f -X PATCH \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/variables/${VARIABLE_NAME}" \ + -d '{"name":"'"$VARIABLE_NAME"'","value":"false"}' + echo "Updated repository variable $VARIABLE_NAME=false" + else + # Create new variable + curl -s -f -X POST \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/actions/variables" \ + -d '{"name":"'"$VARIABLE_NAME"'","value":"false"}' + echo "Created repository variable $VARIABLE_NAME=false" + fi + + echo "" + echo "Migration complete — CACHE_IMPORT_GITHUB set to false" + echo "Import fallback will be disabled on next workflow runs." + echo "To re-enable migration mode, delete or set CACHE_IMPORT_GITHUB=true in repository variables." diff --git a/.github/workflows/test-cache-migration-gh2s3.yml b/.github/workflows/test-cache-migration-gh2s3.yml new file mode 100644 index 0000000..297bfaa --- /dev/null +++ b/.github/workflows/test-cache-migration-gh2s3.yml @@ -0,0 +1,147 @@ +--- +name: Test cache migration (GitHub → S3) + +on: + push: + branches: [ master ] + pull_request: + workflow_dispatch: + +jobs: + # Prerequisite: provision GitHub cache entries used by the test scenarios + provision-github-cache: + runs-on: github-ubuntu-latest-s + name: "Provision GitHub cache entries" + permissions: + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Save GitHub cache entry (for import scenario) + run: mkdir -p ~/.cache/test-migration && echo "github-content" > ~/.cache/test-migration/test-file.txt + - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~/.cache/test-migration + key: test-migration-gh + - name: Save GitHub cache entry (for S3-hit scenario, to confirm it is NOT restored) + run: echo "github-content" > ~/.cache/test-migration/test-file.txt + - uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: ~/.cache/test-migration + key: test-migration-s3hit + + # Prerequisite: provision an S3 cache entry for the S3-hit scenario + provision-s3-cache: + runs-on: github-ubuntu-latest-s + name: "Provision S3 cache entry" + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Create S3 cache content + run: mkdir -p ~/.cache/test-migration && echo "s3-content" > ~/.cache/test-migration/test-file.txt + - name: Save to S3 cache + uses: ./ + with: + path: ~/.cache/test-migration + key: test-migration-s3hit + environment: dev + backend: s3 + + # Scenario 1: S3 backend with import mode active (default behavior after migration begins) + test-s3-import-enabled: + needs: provision-github-cache + runs-on: github-ubuntu-latest-s + name: "S3 + import enabled (migration fallback)" + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Cache with migration fallback + id: cache + uses: ./ + with: + path: ~/.cache/test-migration + key: test-migration-gh + environment: dev + backend: s3 + # import-github-cache defaults to true — no need to set it explicitly + - name: Verify import succeeded + run: | + [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]] || { echo "ERROR: cache-hit is not true"; exit 1; } + [[ "$(cat ~/.cache/test-migration/test-file.txt)" == "github-content" ]] || { echo "ERROR: unexpected content, not restored from GitHub"; exit 1; } + rm -rf ~/.cache/test-migration # prevent saving to S3 so other scenarios don't find it + + # Scenario 2: S3 backend with import mode explicitly disabled + test-s3-import-disabled: + needs: provision-github-cache + runs-on: github-ubuntu-latest-s + name: "S3 + import disabled (migration complete)" + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Cache without migration fallback + id: cache + uses: ./ + with: + path: ~/.cache/test-migration + key: test-migration-gh + environment: dev + backend: s3 + import-github-cache: 'false' + - name: Verify no import from GitHub + run: | + [[ "${{ steps.cache.outputs.cache-hit }}" != "true" ]] || { echo "ERROR: cache-hit is true but import should be disabled"; exit 1; } + test ! -f ~/.cache/test-migration/test-file.txt || { echo "ERROR: cache content was restored but import should be disabled"; exit 1; } + + # Scenario 3: S3 backend with import disabled via env var (repo variable pattern) + test-s3-import-disabled-via-env: + needs: provision-github-cache + runs-on: github-ubuntu-latest-s + name: "S3 + import disabled via env var" + permissions: + id-token: write + contents: read + env: + CACHE_IMPORT_GITHUB: 'false' + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Cache without migration fallback (via env) + id: cache + uses: ./ + with: + path: ~/.cache/test-migration + key: test-migration-gh + environment: dev + backend: s3 + - name: Verify no import from GitHub + run: | + [[ "${{ steps.cache.outputs.cache-hit }}" != "true" ]] || { echo "ERROR: cache-hit is true but import should be disabled"; exit 1; } + test ! -f ~/.cache/test-migration/test-file.txt || { echo "ERROR: cache content was restored but import should be disabled"; exit 1; } + + # Scenario 4: S3 hit — GitHub import step must be skipped entirely + test-s3-hit-skips-github-import: + needs: [ provision-github-cache, provision-s3-cache ] + runs-on: github-ubuntu-latest-s + name: "S3 hit → no GitHub import" + permissions: + id-token: write + contents: read + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Cache (S3 hit expected, import enabled) + id: cache + uses: ./ + with: + path: ~/.cache/test-migration + key: test-migration-s3hit + environment: dev + backend: s3 + # import-github-cache defaults to true — S3 hit should prevent GitHub from being tried + - name: Verify S3 content restored, not GitHub + run: | + [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]] || { echo "ERROR: expected S3 hit"; exit 1; } + [[ "$(cat ~/.cache/test-migration/test-file.txt)" == "s3-content" ]] || { echo "ERROR: unexpected content — GitHub import may have overridden S3"; exit 1; } diff --git a/README.md b/README.md index 9e53316..db10dd1 100644 --- a/README.md +++ b/README.md @@ -60,21 +60,29 @@ These must be committed since GitHub Actions runs them directly. restore-keys: cache-${{ runner.os }}- ``` +### Input Environment Variables + +| Environment Variable | Description | +|-----------------------|-----------------------------------------------------------------------------------| +| `CACHE_BACKEND` | Force specific backend: `github` or `s3` (overrides auto-detection) | +| `CACHE_IMPORT_GITHUB` | Disable GitHub cache fallback for S3 backend (migration mode) when set to `false` | + ## Inputs -| Input | Description | Required | Default | -|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------| -| `path` | Files, directories, and wildcard patterns to cache | Yes | | -| `key` | Explicit key for restoring and saving cache | Yes | | -| `restore-keys` | Ordered list of prefix-matched keys for fallback | No | | -| `fallback-to-default-branch` | Automatically add a fallback restore key pointing to the default branch cache (S3 backend only). Disable if you want strict branch isolation. | No | `false` | -| `fallback-branch` | Optional maintenance branch for fallback restore keys (pattern: `branch-*`, S3 backend only). If not set, the repository default branch is used. | No | | -| `environment` | Environment to use (dev or prod, S3 backend only) | No | `prod` | -| `upload-chunk-size` | Chunk size for large file uploads (bytes) | No | | -| `enableCrossOsArchive` | Enable cross-OS cache compatibility | No | `false` | -| `fail-on-cache-miss` | Fail workflow if cache entry not found | No | `false` | -| `lookup-only` | Only check cache existence without downloading | No | `false` | -| `backend` | Force specific backend: `github` or `s3`. Takes priority over `CACHE_BACKEND` env var and auto-detection. | No | | +| Input | Description | Required | Default | +|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------| +| `path` | Files, directories, and wildcard patterns to cache | Yes | | +| `key` | Explicit key for restoring and saving cache | Yes | | +| `restore-keys` | Ordered list of prefix-matched keys for fallback | No | | +| `fallback-to-default-branch` | Automatically add a fallback restore key pointing to the default branch cache (S3 backend only). Disable if you want strict branch isolation. | No | `false` | +| `fallback-branch` | Optional maintenance branch for fallback restore keys (pattern: `branch-*`, S3 backend only). If not set, the repository default branch is used. | No | | +| `environment` | Environment to use (dev or prod, S3 backend only) | No | `prod` | +| `upload-chunk-size` | Chunk size for large file uploads (bytes) | No | | +| `enableCrossOsArchive` | Enable cross-OS cache compatibility | No | `false` | +| `fail-on-cache-miss` | Fail workflow if cache entry not found | No | `false` | +| `lookup-only` | Only check cache existence without downloading | No | `false` | +| `backend` | Force specific backend: `github` or `s3`. Takes priority over `CACHE_BACKEND` env var and auto-detection. | No | | +| `import-github-cache` | Import GitHub cache to S3 when no S3 cache exists (migration mode, S3 backend only). Takes priority over `CACHE_IMPORT_GITHUB` env var. | No | `true` | ## Backend Selection @@ -84,16 +92,75 @@ The cache backend is determined in the following priority order: 2. **`CACHE_BACKEND` environment variable** — set at the job or workflow level (`github` or `s3`) 3. **Repository visibility** — `github` for public repos, `s3` for private/internal repos -The `CACHE_BACKEND` env var is useful when the cache action is called indirectly through a composite action and you cannot set the `backend` -input directly: +The `CACHE_BACKEND` env var is useful when the cache action is called indirectly through a composite action, and you cannot set the +`backend` input directly, or when you want to enforce the same backend for all cache steps in a workflow without modifying each step: + +```yaml +env: + CACHE_BACKEND: s3 # forces S3 for all cache steps, including those in reusable actions +``` + +## Migration Mode (GitHub cache → S3) + +When switching from GitHub Actions cache to S3, existing cache entries live only in GitHub and would need to be rebuilt from scratch. +Migration mode bridges this gap: when using the S3 backend and no S3 cache exists, the action automatically falls back to restore +from GitHub Actions cache using the original key. The S3 post-job step then saves the restored content to S3, pre-provisioning it +for subsequent runs. + +Migration mode is **enabled by default** for S3 backend. Once all relevant entries have been migrated to S3, disable it to avoid +the overhead of the GitHub fallback attempt on every cache miss. + +**Disable resolution order** (first match wins): + +1. **`import-github-cache: 'false'`** — action input in the step +2. **`CACHE_IMPORT_GITHUB=false`** — environment variable at job or workflow level (can be sourced from a repository variable) +3. Default: `true` + +**Disabling via repository variable** (recommended for gradual rollout): ```yaml jobs: build: env: - CACHE_BACKEND: s3 # forces S3 for all cache steps, including those in reusable actions + CACHE_IMPORT_GITHUB: ${{ vars.CACHE_IMPORT_GITHUB }} steps: - - uses: SonarSource/some-other-action@v1 # internally calls gh-action_cache + - uses: SonarSource/gh-action_cache@v1 +``` + +Set the `CACHE_IMPORT_GITHUB` repository variable to `false` once migration is complete — no workflow changes needed. + +**Behavior with `fail-on-cache-miss`:** when migration mode is active, the S3 miss does not immediately fail the job. +The action tries the GitHub fallback first and only fails if both S3 and GitHub report a cache miss. + +### Checking migration progress + +`gh-action_cache` ships a sample workflow at `.github/workflows/check-cache-migration.yml` that you must **copy into +each repository** using the S3 backend. It compares GitHub cache entries against S3 objects for that repository and +automatically disables the import fallback once migration is complete. + +**What it checks:** branches matching `main`, `master`, `branch-*`, `dogfood-on-*`, `feature/long/*`, excluding +transient keys (`build-number-*`, `mise-*`). + +**When all included entries are present in S3**, the workflow automatically sets `CACHE_IMPORT_GITHUB=false` as a +repository variable, disabling the import fallback for all subsequent runs if the environment variable is set with +`${{ vars.CACHE_IMPORT_GITHUB }}` in the workflow. + +**Trigger manually via GitHub CLI:** + +```bash +# Check prod environment (default) +gh workflow run check-cache-migration.yml + +# Check dev environment +gh workflow run check-cache-migration.yml -f environment=dev +``` + +To re-enable migration mode after it has been automatically disabled, delete or reset the repository variable: + +```bash +gh variable delete CACHE_IMPORT_GITHUB +# or +gh variable set CACHE_IMPORT_GITHUB --body "true" ``` ## Outputs @@ -288,13 +355,13 @@ jobs: ### Modes of Operation -| Scenario | Branch | Key | Dry-run | -|----------|--------|-----|---------| -| List all cache entries | _(empty)_ | _(empty)_ | n/a | -| Preview what would be deleted | `feature/my-branch` | _(optional)_ | `true` | -| Delete cache for a branch | `feature/my-branch` | _(optional)_ | `false` | -| Delete key for given branch | `feature/my-branch` | `sccache-Linux-` | `false` | -| Delete key across all branches | _(empty)_ | `sccache-Linux-` | `false` | +| Scenario | Branch | Key | Dry-run | +|--------------------------------|---------------------|------------------|---------| +| List all cache entries | _(empty)_ | _(empty)_ | n/a | +| Preview what would be deleted | `feature/my-branch` | _(optional)_ | `true` | +| Delete cache for a branch | `feature/my-branch` | _(optional)_ | `false` | +| Delete key for given branch | `feature/my-branch` | `sccache-Linux-` | `false` | +| Delete key across all branches | _(empty)_ | `sccache-Linux-` | `false` | ### Running via GitHub CLI diff --git a/action.yml b/action.yml index 0520a53..a75ac5c 100644 --- a/action.yml +++ b/action.yml @@ -39,11 +39,16 @@ inputs: 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.github-cache.outputs.cache-hit || steps.s3-cache.outputs.cache-hit }} + value: ${{ steps.github-cache.outputs.cache-hit || steps.s3-cache.outputs.cache-hit || steps.github-import.outputs.cache-hit }} runs: using: composite @@ -121,6 +126,26 @@ runs: GITHUB_REPOSITORY: ${{ github.repository }} run: $ACTION_PATH_CACHE/scripts/prepare-keys.sh + - name: Determine GitHub cache import mode + if: steps.cache-backend.outputs.cache-backend == 's3' + id: import-mode + shell: bash + env: + INPUT_IMPORT_GITHUB_CACHE: ${{ inputs.import-github-cache }} + run: | + # Resolution order: input → CACHE_IMPORT_GITHUB env var → default true + 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" + else + IMPORT_GITHUB="true" + echo "Using default import mode: $IMPORT_GITHUB" + fi + echo "import-github=$IMPORT_GITHUB" >> "$GITHUB_OUTPUT" + - name: Cache on S3 if: steps.cache-backend.outputs.cache-backend == 's3' uses: runs-on/cache@50350ad4242587b6c8c2baa2e740b1bc11285ff4 # v4.3.0 @@ -142,9 +167,39 @@ runs: restore-keys: ${{ steps.prepare-keys.outputs.branch-restore-keys }} upload-chunk-size: ${{ inputs.upload-chunk-size }} enableCrossOsArchive: ${{ inputs.enableCrossOsArchive }} - fail-on-cache-miss: ${{ inputs.fail-on-cache-miss }} + # 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.import-mode.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-cache.outputs.cache-hit != 'true' && + steps.import-mode.outputs.import-github == 'true' + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + 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.import-mode.outputs.import-github == 'true' && + inputs.fail-on-cache-miss == '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 + - name: Credential guard for S3 cache save if: steps.cache-backend.outputs.cache-backend == 's3' uses: SonarSource/gh-action_cache/credential-guard@v1