action.yml 9.7 KB

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