diff --git a/README.md b/README.md index 48fd4ef..42909e9 100644 --- a/README.md +++ b/README.md @@ -65,14 +65,15 @@ Bundled output goes to `credential-setup/dist/` and `credential-guard/dist/`. Th |------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------| | `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-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 | | +| `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 | `true` | +| `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 | | ## Backend Selection @@ -114,20 +115,17 @@ A GitHub Action that provides branch-specific caching on AWS S3 with intelligent ### How Restore Keys Work -**Important**: This action's restore key behavior differs from the standard GitHub cache action. -To enable fallback to default branch caches, you **must** use the `restore-keys` property. - #### Cache Key Resolution Order -When you provide `restore-keys`, the action searches for cache entries in this order: +The action searches for cache entries in this order: 1. **Primary key**: `${BRANCH_NAME}/${key}` -2. **Branch-specific restore keys**: `${BRANCH_NAME}/${restore-key}` (for each restore key) -3. **Default branch fallbacks**: - - `refs/heads/${DEFAULT_BRANCH}/${restore-key}` (for each restore key, where `DEFAULT_BRANCH` is dynamically obtained from the - repository) +2. **Branch-specific restore keys**: `${BRANCH_NAME}/${restore-key}` (for each restore key provided) +3. **Default branch fallbacks** (when `fallback-to-default-branch: true`, the default): + - If `restore-keys` are provided: `refs/heads/${DEFAULT_BRANCH}/${restore-key}` for each restore key + - If no `restore-keys` are provided: `refs/heads/${DEFAULT_BRANCH}/${key}` (exact-match fallback) -#### Example +#### Example — with restore-keys ```yaml - uses: SonarSource/gh-action_cache@v1 @@ -140,12 +138,36 @@ When you provide `restore-keys`, the action searches for cache entries in this o For a feature branch `feature/new-ui`, this will search for: 1. `feature/new-ui/node-linux-abc123...` (exact match) -2. `feature/new-ui/node-linux` (branch-specific partial match) -3. `refs/heads/main/node-linux` (default branch fallback, assuming `main` is the repository's default branch) +2. `feature/new-ui/node-linux-` (branch-specific partial match) +3. `refs/heads/main/node-linux-` (default branch fallback, assuming `main` is the repository's default branch) + +#### Example — without restore-keys + +```yaml +- uses: SonarSource/gh-action_cache@v1 + with: + path: ~/.npm + key: node-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} +``` + +For a feature branch `feature/new-ui`, this will search for: + +1. `feature/new-ui/node-linux-abc123...` (exact match) +2. `refs/heads/main/node-linux-abc123...` (exact-match fallback on default branch) + +To disable the automatic default branch fallback: + +```yaml +- uses: SonarSource/gh-action_cache@v1 + with: + path: ~/.npm + key: node-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} + fallback-to-default-branch: false +``` #### Key Differences from Standard Cache Action -- **Fallback requires restore-keys**: Without `restore-keys`, the action only looks for branch-specific cache entries +- **Automatic default branch fallback**: By default, feature branches fall back to the default branch cache when no branch-specific entry exists - **Dynamic default branch detection**: The action detects your default branch using the GitHub API and uses it for fallback - **Branch isolation**: Each branch maintains its own cache namespace, preventing cross-branch cache pollution diff --git a/action.yml b/action.yml index f64195e..1463d9b 100644 --- a/action.yml +++ b/action.yml @@ -26,8 +26,15 @@ inputs: description: Environment to use ('dev' or 'prod', 's3' backend only). default: prod fallback-branch: - description: Optional maintenance branch for fallback restore keys (pattern 'branch-*', 's3' backend only). If not set, the repository - default branch is used. + 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, @@ -109,6 +116,7 @@ runs: 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 diff --git a/scripts/prepare-keys.sh b/scripts/prepare-keys.sh index f876cde..4f4029c 100755 --- a/scripts/prepare-keys.sh +++ b/scripts/prepare-keys.sh @@ -11,20 +11,39 @@ set -euo pipefail # Optional environment variables: # - INPUT_RESTORE_KEYS: Multi-line list of restore key prefixes # - INPUT_FALLBACK_BRANCH: Maintenance branch for fallback restore keys (pattern: branch-*) +# - INPUT_FALLBACK_TO_DEFAULT_BRANCH: When 'true', add fallback restore key on the default branch (default: true) # - GITHUB_HEAD_REF: Branch name for PR events # - GITHUB_REF: Branch ref for push events # - GITHUB_TOKEN: GitHub token for API authentication # - GITHUB_REPOSITORY: Repository in owner/repo format +# This script prepares cache keys with branch-specific paths and fallback logic (for the S3 backend only). +# +# Required inputs (must be explicitly provided): +# - INPUT_KEY: The primary cache key +# +# GitHub Actions auto-provided: +# - GITHUB_OUTPUT: File path for GitHub Actions output +# - GITHUB_HEAD_REF: Branch name for PR events +# - GITHUB_REF: Branch ref for push events +# - GITHUB_TOKEN: GitHub token for API authentication +# +# Optional user customization: +# - INPUT_RESTORE_KEYS: Multi-line list of restore key prefixes +# - INPUT_FALLBACK_BRANCH: Maintenance branch for fallback restore keys (pattern: branch-*) +# - INPUT_FALLBACK_TO_DEFAULT_BRANCH: Whether to add fallback restore key on the default branch (default: true) + +: "${INPUT_KEY:?}" "${GITHUB_OUTPUT:?}" "${GITHUB_REF:?}" "${GITHUB_TOKEN:?}" "${GITHUB_REPOSITORY:?}" +: "${INPUT_RESTORE_KEYS:=}" "${INPUT_FALLBACK_BRANCH:=}" "${INPUT_FALLBACK_TO_DEFAULT_BRANCH:=true}" # Use GITHUB_HEAD_REF for PR events, GITHUB_REF for push events BRANCH_NAME="${GITHUB_HEAD_REF:-$GITHUB_REF}" BRANCH_KEY="${BRANCH_NAME}/${INPUT_KEY}" echo "branch-key=${BRANCH_KEY}" >> "$GITHUB_OUTPUT" -# Process restore keys: keep branch-specific keys and add fallback to default branch -if [[ -n "${INPUT_RESTORE_KEYS:-}" ]]; then - RESTORE_KEYS="" - # First, add branch-specific restore keys +RESTORE_KEYS="" + +# Process restore keys: add branch-specific keys +if [[ -n $INPUT_RESTORE_KEYS ]]; then while IFS= read -r line; do if [ -n "$line" ]; then if [ -n "$RESTORE_KEYS" ]; then @@ -34,36 +53,47 @@ if [[ -n "${INPUT_RESTORE_KEYS:-}" ]]; then fi fi done <<< "$INPUT_RESTORE_KEYS" +fi - FALLBACK_BRANCH_INPUT="${INPUT_FALLBACK_BRANCH:-}" - - if [[ -n "$FALLBACK_BRANCH_INPUT" ]]; then - FALLBACK_BRANCH="${FALLBACK_BRANCH_INPUT#refs/heads/}" - else - # Query GitHub API to get the default branch - FALLBACK_BRANCH=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}" | \ - jq -r '.default_branch') - fi +# Determine the fallback branch +if [[ -n "$INPUT_FALLBACK_BRANCH" ]]; then + # Explicit fallback-branch is always honoured, regardless of fallback-to-default-branch + FALLBACK_BRANCH="${INPUT_FALLBACK_BRANCH#refs/heads/}" +elif [[ $INPUT_FALLBACK_TO_DEFAULT_BRANCH == "true" ]]; then + # Query GitHub API to get the default branch + FALLBACK_BRANCH=$(curl -s -H "Authorization: token ${GITHUB_TOKEN}" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}" | \ + jq -r '.default_branch') +fi - if [[ -n "$FALLBACK_BRANCH" && "$FALLBACK_BRANCH" != "null" ]]; then +if [[ -n "${FALLBACK_BRANCH:-}" && "$FALLBACK_BRANCH" != "null" ]]; then + # Skip fallback if we're already on the fallback branch + CURRENT_BRANCH="${BRANCH_NAME#refs/heads/}" + if [[ "$CURRENT_BRANCH" != "$FALLBACK_BRANCH" ]]; then case "$FALLBACK_BRANCH" in main|master|branch-*) - # Add fallback branch restore keys - while IFS= read -r line; do - if [[ -n "$line" ]]; then - RESTORE_KEYS="${RESTORE_KEYS}"$'\n'"refs/heads/${FALLBACK_BRANCH}/${line}" - fi - done <<< "$INPUT_RESTORE_KEYS" + if [[ -n $INPUT_RESTORE_KEYS ]]; then + # Add fallback branch restore keys for each user-provided restore key + while IFS= read -r line; do + if [[ -n "$line" ]]; then + RESTORE_KEYS="${RESTORE_KEYS}"$'\n'"refs/heads/${FALLBACK_BRANCH}/${line}" + fi + done <<< "$INPUT_RESTORE_KEYS" + else + # No restore keys provided: add exact-match fallback using the primary key + RESTORE_KEYS="refs/heads/${FALLBACK_BRANCH}/${INPUT_KEY}" + fi ;; *) echo "::warning::Fallback branch '$FALLBACK_BRANCH' is not supported for cache fallback. Supported branches: main, master, branch-*" ;; esac - else - echo "::warning::Unable to determine fallback branch; skipping fallback restore keys." fi +elif [[ -n "$INPUT_FALLBACK_BRANCH" || $INPUT_FALLBACK_TO_DEFAULT_BRANCH == "true" ]]; then + echo "::warning::Unable to determine fallback branch; skipping fallback restore keys." +fi +if [[ -n "$RESTORE_KEYS" ]]; then { echo "branch-restore-keys<