Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 42 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand All @@ -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

Expand Down
12 changes: 10 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
76 changes: 53 additions & 23 deletions scripts/prepare-keys.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<<EOF"
echo "$RESTORE_KEYS"
Expand Down
Loading