action.yml 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. name: 'Save Cache'
  2. description: 'Archives and uploads cache to GitHub Releases. Supports both single and multiple cache paths/keys/buckets.'
  3. inputs:
  4. cache_path:
  5. description: 'Single path where cache is saved (use cache_paths for multiple)'
  6. required: false
  7. type: string
  8. cache_paths:
  9. description: 'Multiline list of paths where caches are saved (one per line)'
  10. required: false
  11. type: string
  12. default: ""
  13. cache_key:
  14. description: 'Unique key for the cache (use cache_keys for multiple)'
  15. required: false
  16. type: string
  17. cache_keys:
  18. description: 'Multiline list of unique keys for caches (one per line, must match cache_paths)'
  19. required: false
  20. type: string
  21. default: ""
  22. cache_bucket:
  23. description: 'Single tag name for the release (use cache_buckets for multiple)'
  24. required: false
  25. default: 'general-cache'
  26. cache_buckets:
  27. description: 'Multiline list of tag names for releases (one per line, must match cache_paths)'
  28. required: false
  29. type: string
  30. default: ""
  31. compression_level:
  32. description: 'Compression level (0-9). 0 = No compression, 1 = Fast, 19 = Best.'
  33. required: false
  34. type: number
  35. default: 6
  36. github_token:
  37. description: 'GitHub token for release upload authentication'
  38. required: true
  39. type: string
  40. runs:
  41. using: 'composite'
  42. steps:
  43. - name: Archive, Split, and Upload Cache
  44. shell: bash
  45. env:
  46. GITHUB_TOKEN: ${{ inputs.github_token }}
  47. TARGET_REPO: "${{ github.repository }}"
  48. run: |
  49. # Validate and parse inputs
  50. PATHS_INPUT="${{ inputs.cache_paths }}"
  51. KEYS_INPUT="${{ inputs.cache_keys }}"
  52. BUCKETS_INPUT="${{ inputs.cache_buckets }}"
  53. # If multiple inputs are empty, fall back to single inputs
  54. if [ -z "$PATHS_INPUT" ]; then
  55. PATHS_INPUT="${{ inputs.cache_path }}"
  56. fi
  57. if [ -z "$KEYS_INPUT" ]; then
  58. KEYS_INPUT="${{ inputs.cache_key }}"
  59. fi
  60. if [ -z "$BUCKETS_INPUT" ]; then
  61. BUCKETS_INPUT="${{ inputs.cache_bucket }}"
  62. fi
  63. # Split inputs into arrays
  64. IFS=$'\n' read -rd '' -a PATHS_ARRAY <<<"$PATHS_INPUT" || true
  65. IFS=$'\n' read -rd '' -a KEYS_ARRAY <<<"$KEYS_INPUT" || true
  66. IFS=$'\n' read -rd '' -a BUCKETS_ARRAY <<<"$BUCKETS_INPUT" || true
  67. # Validate array lengths match
  68. if [[ ${#PATHS_ARRAY[@]} -ne ${#KEYS_ARRAY[@]} ]] || [[ ${#PATHS_ARRAY[@]} -ne ${#BUCKETS_ARRAY[@]} ]]; then
  69. echo "::error::Mismatch in array lengths: paths=${#PATHS_ARRAY[@]}, keys=${#KEYS_ARRAY[@]}, buckets=${#BUCKETS_ARRAY[@]}"
  70. exit 1
  71. fi
  72. # Git configuration for releases
  73. git config user.name "github-actions[bot]"
  74. git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
  75. # Helper function to handle release creation
  76. ensure_release() {
  77. local tag_name="$1"
  78. git fetch --tags --force
  79. if ! git rev-parse "$tag_name" >/dev/null 2>&1; then
  80. echo "Tag '$tag_name' not found. Creating backdated release..."
  81. GIT_COMMITTER_DATE="2015-01-01T12:00:00" git tag -a "$tag_name" -m "Build Cache Storage"
  82. if git push origin "$tag_name" --force; then
  83. gh release create "$tag_name" --title "Build Cache" --notes "Automated storage for build assets" --repo "$TARGET_REPO" || true
  84. else
  85. echo "Push failed, tag might have been created by a parallel job. Continuing..."
  86. fi
  87. else
  88. echo "Release '$tag_name' already exists. Ready for upload."
  89. fi
  90. }
  91. upload_with_retry() {
  92. local file=$1
  93. local tag_name=$2
  94. local max_attempts=3
  95. for ((i=1; i<=max_attempts; i++)); do
  96. echo " Attempt $i for $file..."
  97. if timeout 10m gh release upload "$tag_name" "$file" --clobber --repo "$TARGET_REPO" > /dev/null 2>&1; then
  98. echo " Successfully uploaded $file"
  99. return 0
  100. fi
  101. echo " ⚠ Attempt $i failed or timed out. Retrying..."
  102. sleep 5
  103. done
  104. return 1
  105. }
  106. # Process each cache entry
  107. for ((idx=0; idx<${#PATHS_ARRAY[@]}; idx++)); do
  108. CACHE_PATH="${PATHS_ARRAY[$idx]}"
  109. CACHE_KEY="${KEYS_ARRAY[$idx]}"
  110. CACHE_BUCKET="${BUCKETS_ARRAY[$idx]}"
  111. [ -z "$CACHE_PATH" ] && continue
  112. TAG_NAME="$CACHE_BUCKET"
  113. BASE_FILENAME="cache-$CACHE_KEY"
  114. if [ "${{ inputs.compression_level }}" -eq 0 ]; then
  115. FILENAME="$BASE_FILENAME.tar"
  116. else
  117. FILENAME="$BASE_FILENAME.tzst"
  118. fi
  119. echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Checking Release State ($CACHE_BUCKET)"
  120. ensure_release "$TAG_NAME"
  121. echo "::endgroup::"
  122. echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Archiving Path: $CACHE_PATH"
  123. if [ ! -d "$CACHE_PATH" ] && [ ! -f "$CACHE_PATH" ]; then
  124. echo "⚠️ Target path not found. Skipping cache save."
  125. echo "::endgroup::"
  126. continue
  127. fi
  128. DIR_NAME=$(dirname "$CACHE_PATH")
  129. BASE_NAME=$(basename "$CACHE_PATH")
  130. if [ "${{ inputs.compression_level }}" -eq 0 ]; then
  131. tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
  132. else
  133. tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
  134. fi
  135. FILE_SIZE=$(stat -c%s "$FILENAME")
  136. echo "Archive created: $FILENAME ($FILE_SIZE bytes)"
  137. echo "::endgroup::"
  138. echo "::group:: [$((idx+1))/${#PATHS_ARRAY[@]}] Uploading Assets ($CACHE_BUCKET)"
  139. MAX_SIZE=2000000000 # ~1.86GB
  140. if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
  141. echo "⚠ File > 2GB. Splitting..."
  142. split -b 1500M -a 2 "$FILENAME" "${FILENAME}.part"
  143. for part in "${FILENAME}".part*; do
  144. echo "Uploading $part..."
  145. if ! upload_with_retry "$part" "$TAG_NAME"; then
  146. echo "::error::Failed to upload part $part after multiple attempts."
  147. exit 1
  148. fi
  149. rm -f "$part"
  150. done
  151. rm -rf "$FILENAME"
  152. else
  153. echo "Uploading $FILENAME..."
  154. if ! upload_with_retry "$FILENAME" "$TAG_NAME"; then
  155. echo "::error::Failed to upload $FILENAME after multiple attempts."
  156. exit 1
  157. fi
  158. rm -f "$FILENAME"
  159. fi
  160. echo "::endgroup::"
  161. done