| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185 |
- name: 'Save Cache'
- description: 'Archives and uploads cache to GitHub Releases. Supports both single and multiple cache paths/keys/buckets.'
- inputs:
- cache_path:
- description: 'Single path where cache is saved (use cache_paths for multiple)'
- required: false
- type: string
- cache_paths:
- description: 'Multiline list of paths where caches are saved (one per line)'
- required: false
- type: string
- default: ""
- cache_key:
- description: 'Unique key for the cache (use cache_keys for multiple)'
- required: false
- type: string
- cache_keys:
- description: 'Multiline list of unique keys for caches (one per line, must match cache_paths)'
- required: false
- type: string
- default: ""
- cache_bucket:
- description: 'Single tag name for the release (use cache_buckets for multiple)'
- required: false
- default: 'general-cache'
- cache_buckets:
- description: 'Multiline list of tag names for releases (one per line, must match cache_paths)'
- required: false
- type: string
- default: ""
- compression_level:
- description: 'Compression level (0-9). 0 = No compression, 1 = Fast, 19 = Best.'
- required: false
- type: number
- default: 6
- github_token:
- description: 'GitHub token for release upload authentication'
- required: true
- type: string
- runs:
- using: 'composite'
- steps:
- - name: Archive, Split, and Upload Cache
- shell: bash
- env:
- GITHUB_TOKEN: ${{ inputs.github_token }}
- TARGET_REPO: "${{ github.repository }}"
- run: |
- # Validate and parse inputs
- PATHS_INPUT="${{ inputs.cache_paths }}"
- KEYS_INPUT="${{ inputs.cache_keys }}"
- BUCKETS_INPUT="${{ inputs.cache_buckets }}"
-
- # If multiple inputs are empty, fall back to single inputs
- if [ -z "$PATHS_INPUT" ]; then
- PATHS_INPUT="${{ inputs.cache_path }}"
- fi
- if [ -z "$KEYS_INPUT" ]; then
- KEYS_INPUT="${{ inputs.cache_key }}"
- fi
- if [ -z "$BUCKETS_INPUT" ]; then
- BUCKETS_INPUT="${{ inputs.cache_bucket }}"
- fi
-
- # Split inputs into arrays
- IFS=$'\n' read -rd '' -a PATHS_ARRAY <<<"$PATHS_INPUT" || true
- IFS=$'\n' read -rd '' -a KEYS_ARRAY <<<"$KEYS_INPUT" || true
- IFS=$'\n' read -rd '' -a BUCKETS_ARRAY <<<"$BUCKETS_INPUT" || true
-
- # Validate array lengths match
- if [[ ${#PATHS_ARRAY[@]} -ne ${#KEYS_ARRAY[@]} ]] || [[ ${#PATHS_ARRAY[@]} -ne ${#BUCKETS_ARRAY[@]} ]]; then
- echo "::error::Mismatch in array lengths: paths=${#PATHS_ARRAY[@]}, keys=${#KEYS_ARRAY[@]}, buckets=${#BUCKETS_ARRAY[@]}"
- exit 1
- fi
-
- # Git configuration for releases
- git config user.name "github-actions[bot]"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
-
- # Helper function to handle release creation
- ensure_release() {
- local tag_name="$1"
-
- git fetch --tags --force
-
- if ! git rev-parse "$tag_name" >/dev/null 2>&1; then
- echo "Tag '$tag_name' not found. Creating backdated release..."
- GIT_COMMITTER_DATE="2015-01-01T12:00:00" git tag -a "$tag_name" -m "Build Cache Storage"
- if git push origin "$tag_name" --force; then
- gh release create "$tag_name" --title "Build Cache" --notes "Automated storage for build assets" --repo "$TARGET_REPO" || true
- else
- echo "Push failed, tag might have been created by a parallel job. Continuing..."
- fi
- else
- echo "Release '$tag_name' already exists. Ready for upload."
- fi
- }
-
- upload_with_retry() {
- local file=$1
- local tag_name=$2
- local max_attempts=3
- for ((i=1; i<=max_attempts; i++)); do
- echo " Attempt $i for $file..."
-
- if timeout 10m gh release upload "$tag_name" "$file" --clobber --repo "$TARGET_REPO" > /dev/null 2>&1; then
- echo " Successfully uploaded $file"
- return 0
- fi
-
- echo " ⚠ Attempt $i failed or timed out. Retrying..."
- sleep 5
- done
- return 1
- }
-
- # Process each cache entry
- for ((idx=0; idx<${#PATHS_ARRAY[@]}; idx++)); do
- CACHE_PATH="${PATHS_ARRAY[$idx]}"
- CACHE_KEY="${KEYS_ARRAY[$idx]}"
- CACHE_BUCKET="${BUCKETS_ARRAY[$idx]}"
-
- [ -z "$CACHE_PATH" ] && continue
-
- TAG_NAME="$CACHE_BUCKET"
- BASE_FILENAME="cache-$CACHE_KEY"
-
- if [ "${{ inputs.compression_level }}" -eq 0 ]; then
- FILENAME="$BASE_FILENAME.tar"
- else
- FILENAME="$BASE_FILENAME.tzst"
- fi
-
- echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Checking Release State ($CACHE_BUCKET)"
- ensure_release "$TAG_NAME"
- echo "::endgroup::"
-
- echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Archiving Path: $CACHE_PATH"
-
- if [ ! -d "$CACHE_PATH" ] && [ ! -f "$CACHE_PATH" ]; then
- echo "⚠️ Target path not found. Skipping cache save."
- echo "::endgroup::"
- continue
- fi
-
- DIR_NAME=$(dirname "$CACHE_PATH")
- BASE_NAME=$(basename "$CACHE_PATH")
-
- if [ "${{ inputs.compression_level }}" -eq 0 ]; then
- tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
- else
- tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
- fi
-
- FILE_SIZE=$(stat -c%s "$FILENAME")
- echo "Archive created: $FILENAME ($FILE_SIZE bytes)"
- echo "::endgroup::"
-
- echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Uploading Assets ($CACHE_BUCKET)"
- MAX_SIZE=2000000000 # ~1.86GB
-
- if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
- echo "⚠ File > 2GB. Splitting..."
- split -b 1500M -a 2 "$FILENAME" "${FILENAME}.part"
- for part in "${FILENAME}".part*; do
- echo "Uploading $part..."
- if ! upload_with_retry "$part" "$TAG_NAME"; then
- echo "::error::Failed to upload part $part after multiple attempts."
- exit 1
- fi
- rm -f "$part"
- done
- rm -rf "$FILENAME"
- else
- echo "Uploading $FILENAME..."
- if ! upload_with_retry "$FILENAME" "$TAG_NAME"; then
- echo "::error::Failed to upload $FILENAME after multiple attempts."
- exit 1
- fi
- rm -f "$FILENAME"
- fi
- echo "::endgroup::"
- done
|