action.yml 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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. echo "🔍 DEBUG: cache-save action started"
  45. echo "🔍 DEBUG: inputs.cache_path=${{ inputs.cache_path }}"
  46. echo "🔍 DEBUG: inputs.cache_key=${{ inputs.cache_key }}"
  47. echo "🔍 DEBUG: inputs.cache_bucket=${{ inputs.cache_bucket }}"
  48. echo "🔍 DEBUG: inputs.compression_level=${{ inputs.compression_level }}"
  49. echo "🔍 DEBUG: inputs.working_dir=${{ inputs.working_dir }}"
  50. echo "🔍 DEBUG: inputs.debug=${{ inputs.debug }}"
  51. echo "🔍 DEBUG: GITHUB_WORKSPACE=$GITHUB_WORKSPACE"
  52. echo "🔍 DEBUG: TARGET_REPO=$TARGET_REPO"
  53. if [ "${{ inputs.working_dir }}" != "" ]; then
  54. echo "🔍 DEBUG: Changing to working_dir: ${{ inputs.working_dir }}"
  55. cd "${{ inputs.working_dir }}"
  56. echo "🔍 DEBUG: Current directory after change: $(pwd)"
  57. else
  58. echo "🔍 DEBUG: No working_dir specified, using GITHUB_WORKSPACE"
  59. fi
  60. git config user.name "github-actions[bot]"
  61. git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
  62. echo "🔍 DEBUG: Git config set for github-actions[bot]"
  63. TAG_NAME="${{ inputs.cache_bucket }}"
  64. BASE_FILENAME="cache-${{ inputs.cache_key }}"
  65. if [ "${{ inputs.compression_level }}" -eq 0 ]; then
  66. FILENAME="$BASE_FILENAME.tar"
  67. else
  68. FILENAME="$BASE_FILENAME.tzst"
  69. fi
  70. echo "🔍 DEBUG: TAG_NAME=$TAG_NAME"
  71. echo "🔍 DEBUG: BASE_FILENAME=$BASE_FILENAME"
  72. echo "🔍 DEBUG: FILENAME=$FILENAME"
  73. echo "🔍 DEBUG: compression_level=${{ inputs.compression_level }}"
  74. echo "::group:: Checking Release State"
  75. echo "🔍 DEBUG: Checking if git tag '$TAG_NAME' exists"
  76. git fetch --tags --force
  77. echo "🔍 DEBUG: Git tags fetched successfully"
  78. if ! git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
  79. echo "🔍 DEBUG: Tag '$TAG_NAME' not found, creating new release"
  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 "General Cache Storage"
  82. echo "🔍 DEBUG: Git tag created, attempting push"
  83. if git push origin "$TAG_NAME" --force; then
  84. echo "🔍 DEBUG: Push successful, creating GitHub release"
  85. gh release create "$TAG_NAME" --title "General Cache" --notes "Automated storage for build assets" --repo "$TARGET_REPO" || true
  86. else
  87. echo "🔍 DEBUG: Push failed (may be parallel job conflict)"
  88. echo "Push failed, tag might have been created by a parallel job. Continuing..."
  89. fi
  90. else
  91. echo "🔍 DEBUG: Tag '$TAG_NAME' already exists"
  92. echo "Release '$TAG_NAME' already exists. Ready for upload."
  93. fi
  94. echo "::endgroup::"
  95. echo "🔍 DEBUG: Release state check complete"
  96. echo "::group:: Archiving Path"
  97. echo "🔍 DEBUG: Archiving process started"
  98. echo "Target: ${{ inputs.cache_path }}"
  99. echo "🔍 DEBUG: Checking if target path exists"
  100. if [ ! -d "${{ inputs.cache_path }}" ] && [ ! -f "${{ inputs.cache_path }}" ]; then
  101. echo "🔍 DEBUG: Target path does not exist"
  102. echo "⚠️ Target path not found. Skipping cache save."
  103. echo "::endgroup::"
  104. else
  105. echo "🔍 DEBUG: Target path exists, proceeding with archive"
  106. DIR_NAME=$(dirname "${{ inputs.cache_path }}")
  107. BASE_NAME=$(basename "${{ inputs.cache_path }}")
  108. echo "🔍 DEBUG: DIR_NAME=$DIR_NAME"
  109. echo "🔍 DEBUG: BASE_NAME=$BASE_NAME"
  110. if [ "${{ inputs.debug }}" = "true" ]; then
  111. if [ "${{ inputs.compression_level }}" -eq 0 ]; then
  112. tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME"
  113. else
  114. tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME"
  115. fi
  116. else
  117. if [ "${{ inputs.compression_level }}" -eq 0 ]; then
  118. tar --posix -h -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
  119. else
  120. tar --posix -h -I "zstd -T0 -${{ inputs.compression_level }}" -cvf "$FILENAME" -C "$DIR_NAME" "$BASE_NAME" > /dev/null 2>&1
  121. fi
  122. fi
  123. FILE_SIZE=$(stat -c%s "$FILENAME")
  124. echo "Archive created: $FILENAME ($FILE_SIZE bytes)"
  125. echo "::endgroup::"
  126. echo "::group:: Uploading Assets"
  127. echo "🔍 DEBUG: Upload process started"
  128. MAX_SIZE=2000000000 # ~1.86GB
  129. echo "🔍 DEBUG: MAX_SIZE=$MAX_SIZE bytes ($(( MAX_SIZE / 1024 / 1024 )) MB)"
  130. upload_with_retry() {
  131. local file=$1
  132. local max_attempts=3
  133. local timeout_duration="10m"
  134. echo "🔍 DEBUG: upload_with_retry() called for file=$file"
  135. for ((i=1; i<=max_attempts; i++)); do
  136. echo " Attempt $i for $file..."
  137. echo "🔍 DEBUG: Upload attempt $i for $file"
  138. if [ "${{ inputs.debug }}" = "true" ]; then
  139. echo "🔍 DEBUG: Running gh release upload in debug mode"
  140. if timeout "$timeout_duration" gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO"; then
  141. echo " Successfully uploaded $file"
  142. echo "🔍 DEBUG: Upload successful for $file"
  143. return 0
  144. fi
  145. else
  146. echo "🔍 DEBUG: Running gh release upload in silent mode"
  147. if timeout "$timeout_duration" gh release upload "$TAG_NAME" "$file" --clobber --repo "$TARGET_REPO" > /dev/null 2>&1; then
  148. echo " Successfully uploaded $file"
  149. echo "🔍 DEBUG: Upload successful for $file"
  150. return 0
  151. fi
  152. fi
  153. echo " ⚠ Attempt $i failed or timed out after $timeout_duration. Retrying..."
  154. echo "🔍 DEBUG: Upload attempt $i failed, timeout=$timeout_duration"
  155. if [ "$i" -lt "$max_attempts" ]; then
  156. echo "🔍 DEBUG: Sleeping 5 seconds before retry"
  157. sleep 5
  158. fi
  159. done
  160. echo "🔍 DEBUG: All $max_attempts attempts exhausted for $file"
  161. return 1
  162. }
  163. # Fetch existing assets to clean up stale entries
  164. echo "🔍 DEBUG: Fetching existing assets for cleanup"
  165. ASSETS_URL="https://github.com/${TARGET_REPO}/releases/expanded_assets/$TAG_NAME"
  166. echo "🔍 DEBUG: ASSETS_URL=$ASSETS_URL"
  167. HTTP_RESPONSE=$(curl -sL -A "Mozilla/5.0" -w "%{http_code}" "$ASSETS_URL" -o assets.html || echo "404")
  168. echo "🔍 DEBUG: HTTP_RESPONSE=$HTTP_RESPONSE"
  169. if [ "$HTTP_RESPONSE" -eq 200 ]; then
  170. ALL_ASSETS=$(grep -oP "download/$TAG_NAME/\K[^\"' ]+" assets.html | sort || echo "")
  171. echo "🔍 DEBUG: Parsed ALL_ASSETS, count=$(echo "$ALL_ASSETS" | wc -l)"
  172. else
  173. ALL_ASSETS=""
  174. echo "🔍 DEBUG: Failed to fetch assets, ALL_ASSETS is empty"
  175. fi
  176. rm -f assets.html
  177. echo "🔍 DEBUG: assets.html removed"
  178. chunk_size="1500M"
  179. split_required=false
  180. echo "🔍 DEBUG: Evaluating split requirement"
  181. echo "🔍 DEBUG: FILE_SIZE=$FILE_SIZE, MAX_SIZE=$MAX_SIZE"
  182. if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then
  183. split_required=true
  184. echo "🔍 DEBUG: Split required (FILE_SIZE > MAX_SIZE)"
  185. else
  186. echo "🔍 DEBUG: No split required (FILE_SIZE <= MAX_SIZE)"
  187. fi
  188. if [ "$split_required" = "true" ]; then
  189. echo "⚠ Splitting into ${chunk_size} chunks..."
  190. echo "🔍 DEBUG: Executing split command with chunk_size=1500M"
  191. split -b "$chunk_size" -a 2 "$FILENAME" "${FILENAME}.part"
  192. NEW_PARTS=(${FILENAME}.part*)
  193. NEW_PARTS_COUNT=${#NEW_PARTS[@]}
  194. echo "🔍 DEBUG: Split complete, NEW_PARTS_COUNT=$NEW_PARTS_COUNT"
  195. # Remove old single-file asset if switching to split parts
  196. if echo "$ALL_ASSETS" | grep -xF "${FILENAME}" >/dev/null 2>&1; then
  197. echo "🔍 DEBUG: Found old single-file asset, removing it"
  198. echo "Removing old single asset to make way for split parts: ${FILENAME}"
  199. gh release delete-asset "$TAG_NAME" "${FILENAME}" --repo "$TARGET_REPO" -y || true
  200. else
  201. echo "🔍 DEBUG: No old single-file asset to remove"
  202. fi
  203. # Clean up excess trailing parts from previous builds
  204. OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" || echo "")
  205. echo "🔍 DEBUG: OLD_PARTS count=$(echo "$OLD_PARTS" | grep -c . || echo 0)"
  206. if [ -n "$OLD_PARTS" ]; then
  207. OLD_PARTS_COUNT=$(echo "$OLD_PARTS" | wc -l)
  208. echo "🔍 DEBUG: OLD_PARTS_COUNT=$OLD_PARTS_COUNT, NEW_PARTS_COUNT=$NEW_PARTS_COUNT"
  209. if [ "$OLD_PARTS_COUNT" -gt "$NEW_PARTS_COUNT" ]; then
  210. echo "Old build had $OLD_PARTS_COUNT parts, new has $NEW_PARTS_COUNT. Cleaning up excess parts..."
  211. EXT_PARTS=$(echo "$OLD_PARTS" | awk -v skip="$NEW_PARTS_COUNT" 'NR>skip')
  212. for asset in $EXT_PARTS; do
  213. echo "🔍 DEBUG: Removing old part: $asset"
  214. echo "Removing leftover trailing part: $asset"
  215. gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true
  216. done
  217. else
  218. echo "🔍 DEBUG: No excess parts to clean up"
  219. fi
  220. fi
  221. for part in "${FILENAME}".part*; do
  222. echo "🔍 DEBUG: Uploading part: $part"
  223. echo "Uploading $part..."
  224. if ! upload_with_retry "$part"; then
  225. echo "::error::Failed to upload part $part after multiple attempts."
  226. echo "🔍 DEBUG: CRITICAL - Upload failed for $part"
  227. exit 1
  228. fi
  229. echo "🔍 DEBUG: Successfully uploaded $part, removing local copy"
  230. rm -f "$part"
  231. done
  232. echo "🔍 DEBUG: All parts uploaded, removing original archive"
  233. rm -rf "$FILENAME"
  234. else
  235. # Remove old split parts if new build fits in a single file
  236. echo "🔍 DEBUG: Checking for old split parts to clean up"
  237. OLD_PARTS=$(echo "$ALL_ASSETS" | grep -F "${FILENAME}.part" || echo "")
  238. if [ -n "$OLD_PARTS" ]; then
  239. echo "New build shrunk to single file. Cleaning up old split parts for ${FILENAME}..."
  240. echo "🔍 DEBUG: Found old split parts, removing them"
  241. while read -r asset; do
  242. [ -z "$asset" ] && continue
  243. echo "🔍 DEBUG: Removing ghost split part: $asset"
  244. echo "Removing ghost split part: $asset"
  245. gh release delete-asset "$TAG_NAME" "$asset" --repo "$TARGET_REPO" -y || true
  246. done <<< "$OLD_PARTS"
  247. else
  248. echo "🔍 DEBUG: No old split parts to clean up"
  249. fi
  250. echo "🔍 DEBUG: Uploading single file: $FILENAME"
  251. echo "Uploading $FILENAME..."
  252. if ! upload_with_retry "$FILENAME"; then
  253. echo "::error::Failed to upload $FILENAME after multiple attempts."
  254. echo "🔍 DEBUG: CRITICAL - Upload failed for $FILENAME"
  255. exit 1
  256. fi
  257. echo "🔍 DEBUG: Successfully uploaded $FILENAME, removing local copy"
  258. rm -f "$FILENAME"
  259. fi
  260. # Cleanup any leftover archive artifacts (single-file or split parts)
  261. echo "🔍 DEBUG: Final cleanup phase"
  262. if [ "${{ inputs.debug }}" = "true" ]; then
  263. echo "Cleaning archive artifacts: ${BASE_FILENAME}*"
  264. echo "🔍 DEBUG: Listing files matching ${BASE_FILENAME}*"
  265. ls -la "${BASE_FILENAME}"* 2>/dev/null || true
  266. fi
  267. rm -f "${BASE_FILENAME}"* || true
  268. echo "🔍 DEBUG: Cleanup complete"
  269. echo "::endgroup::"
  270. echo "🔍 DEBUG: cache-save action finished successfully"
  271. fi