action.yml 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. name: 'Save Cache'
  2. description: ''
  3. inputs:
  4. cache_path:
  5. description: 'Path where cache is saved'
  6. required: true
  7. type: string
  8. cache_key:
  9. description: 'Unique key for the cache'
  10. required: true
  11. type: string
  12. cache_bucket:
  13. description: 'The tag name for the release (e.g., general-cache)'
  14. required: true
  15. default: 'general-cache'
  16. github_token:
  17. description: 'GitHub Token'
  18. required: true
  19. compression_level:
  20. description: 'Compression level (0-9). 0 = No compression, 1 = Fast, 19 = Best.'
  21. required: false
  22. type: number
  23. default: 6
  24. working_dir:
  25. description: 'Path where .git folder exists (use only if you clone multiple repos)'
  26. required: false
  27. type: string
  28. debug:
  29. description: 'Enable Logs'
  30. required: false
  31. type: boolean
  32. default: false
  33. runs:
  34. using: 'composite'
  35. steps:
  36. - name: Archive, Split, and Upload Cache
  37. shell: bash
  38. env:
  39. GH_TOKEN: ${{ inputs.github_token }}
  40. TARGET_REPO: "${{ github.repository }}"
  41. run: |
  42. set -euo pipefail
  43. # Archive, Split, and Upload Cache
  44. if [ "${{ inputs.debug }}" = "true" ]; then set -x; fi
  45. if [ "${{ inputs.working_dir }}" != "" ]; then
  46. cd "${{ inputs.working_dir }}"
  47. fi
  48. git config user.name "github-actions[bot]"
  49. git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
  50. TAG_NAME="${{ inputs.cache_bucket }}"
  51. BASE_FILENAME="cache-${{ inputs.cache_key }}"
  52. if [ "${{ inputs.compression_level }}" -eq 0 ]; then
  53. FILENAME="$BASE_FILENAME.tar"
  54. else
  55. FILENAME="$BASE_FILENAME.tzst"
  56. fi
  57. echo "::group:: Checking Release State"
  58. git fetch --tags --force
  59. if ! git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
  60. echo "Tag '$TAG_NAME' not found. Creating backdated release..."
  61. GIT_COMMITTER_DATE="2015-01-01T12:00:00" git tag -a "$TAG_NAME" -m "General Cache Storage"
  62. if git push origin "$TAG_NAME" --force; then
  63. gh release create "$TAG_NAME" --title "General Cache" --notes "Automated storage for build assets" --repo "$TARGET_REPO" || true
  64. else
  65. echo "Push failed, tag might have been created by a parallel job. Continuing..."
  66. fi
  67. else
  68. echo "Release '$TAG_NAME' already exists. Ready for upload."
  69. fi
  70. echo "::endgroup::"
  71. echo "::group:: Archiving Path"
  72. echo "Target: ${{ inputs.cache_path }}"
  73. if [ ! -d "${{ inputs.cache_path }}" ] && [ ! -f "${{ inputs.cache_path }}" ]; then
  74. echo "⚠️ Target path not found. Skipping cache save."
  75. echo "::endgroup::"
  76. else
  77. DIR_NAME=$(dirname "${{ inputs.cache_path }}")
  78. BASE_NAME=$(basename "${{ inputs.cache_path }}")
  79. if [ "${{ inputs.debug }}" = "true" ]; then
  80. if [ "${{ inputs.compression_level }}" -eq 0 ]; then
  81. tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME"
  82. else
  83. tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME"
  84. fi
  85. else
  86. if [ "${{ inputs.compression_level }}" -eq 0 ]; then
  87. tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
  88. else
  89. tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
  90. fi
  91. fi
  92. FILE_SIZE=$(stat -c%s "$FILENAME")
  93. echo "Archive created: $FILENAME ($FILE_SIZE bytes)"
  94. echo "::endgroup::"
  95. echo "::group:: Uploading Assets"
  96. MAX_SIZE=2000000000 # ~1.86GB
  97. upload_with_retry() {
  98. local file=$1
  99. local max_attempts=3
  100. local timeout_duration="10m"
  101. for ((i=1; i<=max_attempts; i++)); do
  102. echo " Attempt $i for $file..."
  103. if [ "${{ inputs.debug }}" = "true" ]; then
  104. if timeout "$timeout_duration" gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO"; then
  105. echo " Successfully uploaded $file"
  106. return 0
  107. fi
  108. else
  109. if timeout "$timeout_duration" gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO" > /dev/null 2>&1; then
  110. echo " Successfully uploaded $file"
  111. return 0
  112. fi
  113. fi
  114. echo " ⚠ Attempt $i failed or timed out after $timeout_duration. Retrying..."
  115. if [ "$i" -lt "$max_attempts" ]; then
  116. sleep 5
  117. fi
  118. done
  119. return 1
  120. }
  121. # Fetch existing assets to clean up stale entries
  122. ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME"
  123. HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404")
  124. if [ "$HTTP_RESPONSE" -eq 200 ]; then
  125. ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort || echo "")
  126. else
  127. ALL_ASSETS=""
  128. fi
  129. rm -f assets.html
  130. chunk_size="1500M"
  131. split_required=false
  132. if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
  133. split_required=true
  134. fi
  135. if [ "$split_required" = "true" ]; then
  136. echo "⚠ Splitting into ${chunk_size} chunks..."
  137. split -b "$chunk_size" -a 2 "$FILENAME" "${FILENAME}.part"
  138. NEW_PARTS=(${FILENAME}.part*)
  139. NEW_PARTS_COUNT=${#NEW_PARTS[@]}
  140. # Remove old single-file asset if switching to split parts
  141. if echo "$ALL_ASSETS" | grep -xF "${FILENAME}" >/dev/null 2>&1; then
  142. echo "Removing old single asset to make way for split parts: ${FILENAME}"
  143. gh release delete-asset "$TAG_NAME" "${FILENAME}" --repo "$TARGET_REPO" -y || true
  144. fi
  145. # Clean up excess trailing parts from previous builds
  146. OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" || echo "")
  147. if [ -n "$OLD_PARTS" ]; then
  148. OLD_PARTS_COUNT=$(echo "$OLD_PARTS" | wc -l)
  149. if [ "$OLD_PARTS_COUNT" -gt "$NEW_PARTS_COUNT" ]; then
  150. echo "Old build had $OLD_PARTS_COUNT parts, new has $NEW_PARTS_COUNT. Cleaning up excess parts..."
  151. EXT_PARTS=$(echo "$OLD_PARTS" | awk -v skip="$NEW_PARTS_COUNT" 'NR>skip')
  152. for asset in $EXT_PARTS; do
  153. echo "Removing leftover trailing part: $asset"
  154. gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true
  155. done
  156. fi
  157. fi
  158. for part in "${FILENAME}".part*; do
  159. echo "Uploading $part..."
  160. if ! upload_with_retry "$part"; then
  161. echo "::error::Failed to upload part $part after multiple attempts."
  162. exit 1
  163. fi
  164. rm -f "$part"
  165. done
  166. rm -rf "$FILENAME"
  167. else
  168. # Remove old split parts if new build fits in a single file
  169. OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" || echo "")
  170. if [ -n "$OLD_PARTS" ]; then
  171. echo "New build shrunk to single file. Cleaning up old split parts for ${FILENAME}..."
  172. while read -r asset; do
  173. [ -z "$asset" ] && continue
  174. echo "Removing ghost split part: $asset"
  175. gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true
  176. done <<< "$OLD_PARTS"
  177. fi
  178. echo "Uploading $FILENAME..."
  179. if ! upload_with_retry "$FILENAME"; then
  180. echo "::error::Failed to upload $FILENAME after multiple attempts."
  181. exit 1
  182. fi
  183. rm -f "$FILENAME"
  184. fi
  185. # Cleanup any leftover archive artifacts (single-file or split parts)
  186. if [ "${{ inputs.debug }}" = "true" ]; then
  187. echo "Cleaning archive artifacts: ${BASE_FILENAME}*"
  188. fi
  189. rm -f "${BASE_FILENAME}"* || true
  190. echo "::endgroup::"
  191. fi