11---
2- name : CloudFront Invalidation
3- description : Creates CloudFront invalidation to clear cache for updated content
2+ name : ' CloudFront Invalidation'
3+ description : ' Create a CloudFront invalidation to clear cache'
44
55inputs :
66 aws_access_key :
7- description : AWS access key ID (optional if using OIDC)
7+ description : ' AWS access key ID'
88 required : false
99 aws_secret_key :
10- description : AWS secret access key (optional if using OIDC)
10+ description : ' AWS secret access key'
1111 required : false
1212 aws_region :
13- description : AWS region
13+ description : ' AWS region'
1414 required : true
1515 role_to_assume :
16- description : AWS IAM role ARN to assume (for OIDC authentication)
16+ description : ' AWS IAM role ARN to assume'
1717 required : false
1818
1919 distribution_id :
20- description : CloudFront distribution ID
20+ description : ' CloudFront distribution ID'
2121 required : true
2222 paths :
2323 description : >
24- Paths to invalidate (space-separated,
25- e.g. "/* /index.html /css/*")
24+ Space-separated paths to invalidate (e.g. "/* /index.html /css/*")
2625 required : false
2726 default : " /*"
2827 caller_reference :
2928 description : >
30- Unique reference for this invalidation
31- (auto-generated if not provided)
29+ Unique reference for this invalidation (auto-generated if not provided)
3230 required : false
31+ wait_for_completion :
32+ description : ' Wait until invalidation status becomes Completed'
33+ required : false
34+ default : ' false'
35+
36+ show_summary :
37+ description : ' Print summary in the job summary'
38+ required : false
39+ default : ' true'
40+ summary_limit :
41+ description : ' Max number of lines (paths) to show in summary'
42+ required : false
43+ default : ' 250'
3344
3445outputs :
3546 invalidation_id :
36- description : The ID of the created invalidation
47+ description : ' ID of the created invalidation'
3748 value : ${{ steps.invalidate.outputs.invalidation_id }}
3849 status :
39- description : The status of the invalidation
50+ description : ' Status of the invalidation (InProgress/Completed) '
4051 value : ${{ steps.invalidate.outputs.status }}
52+ caller_reference :
53+ description : ' CallerReference used for this invalidation'
54+ value : ${{ steps.invalidate.outputs.caller_reference }}
4155
4256runs :
4357 using : composite
4458 steps :
4559 - name : Validate inputs
4660 shell : bash
4761 run : |
48- set -e
62+ set -euo pipefail
4963
50- if ! command -v jq &> /dev/null; then
51- echo "❌ jq is required but not found. Please install jq or use ubuntu-latest runner "
64+ if ! command -v aws >/dev/null 2>&1 || ! command -v jq > /dev/null 2>&1 ; then
65+ echo "❌ AWS CLI and jq are required on the runner ( use ubuntu-latest) "
5266 exit 1
5367 fi
5468
5569 if [[ ! "${{ inputs.distribution_id }}" =~ ^E[A-Z0-9]{13}$ ]]; then
56- echo "❌ Invalid CloudFront distribution ID format : ${{ inputs.distribution_id }}"
57- echo "Expected format: E + 13 alphanumeric characters (e.g., E1234567890ABC)"
70+ echo "❌ Invalid CloudFront distribution ID: ${{ inputs.distribution_id }}"
71+ echo "Expected pattern: ^E[A-Z0-9]{13}$ (e.g., E1234567890ABC)"
5872 exit 1
5973 fi
6074
6175 PATHS="${{ inputs.paths }}"
62- if [ -z "$PATHS" ]; then
63- echo "❌ Paths cannot be empty"
76+ if [[ -z "$PATHS" ] ]; then
77+ echo "❌ 'paths' cannot be empty"
6478 exit 1
6579 fi
6680
67- IFS=' ' read -ra PATHS_ARRAY <<< "$PATHS"
81+ read -r -a PATHS_ARRAY <<< "$PATHS"
6882 PATHS_COUNT=${#PATHS_ARRAY[@]}
69- if [ $PATHS_COUNT -gt 1000 ]; then
70- echo "❌ Too many paths ($PATHS_COUNT). CloudFront allows maximum 1000 paths per invalidation"
71- echo "Consider using fewer paths or /* wildcard "
83+
84+ if (( PATHS_COUNT == 0 )); then
85+ echo "❌ No paths provided "
7286 exit 1
7387 fi
7488
75- for path in "${PATHS_ARRAY[@]}"; do
76- if [[ ! "$path" =~ ^/.* ]]; then
77- echo "❌ Invalid path: $path (must start with /)"
89+ if (( PATHS_COUNT > 1000 )); then
90+ echo "❌ Too many paths ($PATHS_COUNT) — CloudFront allows up to 1000 per invalidation"
91+ echo " Consider using fewer paths or a wildcard like /*"
92+ exit 1
93+ fi
94+
95+ for p in "${PATHS_ARRAY[@]}"; do
96+ if [[ ! "$p" =~ ^/ ]]; then
97+ echo "❌ Invalid path: $p (must start with /)"
7898 exit 1
7999 fi
80100 done
81101
82- echo "✅ Input validation passed ($PATHS_COUNT paths )"
102+ echo "✅ Input validation passed ($PATHS_COUNT path(s) )"
83103
84104 - name : Configure AWS authentication
85- uses : Mad-Pixels/github-workflows/internal/aws-auth@main
105+ uses : Mad-Pixels/github-workflows/internal/aws-auth@v1
86106 with :
87107 aws_access_key : ${{ inputs.aws_access_key }}
88108 aws_secret_key : ${{ inputs.aws_secret_key }}
@@ -93,61 +113,136 @@ runs:
93113 id : invalidate
94114 shell : bash
95115 run : |
96- set -e
116+ set -euo pipefail
97117
98- if [ -z "${{ inputs.caller_reference }}" ]; then
99- TIMESTAMP=$(date +%s)
100- SHORT_SHA="${{ github.sha }}"
101- SHORT_SHA=${SHORT_SHA:0:8}
102- CALLER_REF="gh-${TIMESTAMP}-${{ github.run_id }}-${SHORT_SHA}"
103- else
118+ if [[ -n "${{ inputs.caller_reference }}" ]]; then
104119 CALLER_REF="${{ inputs.caller_reference }}"
120+ else
121+ TS=$(date +%s)
122+ SHA="${{ github.sha }}"; SHORT_SHA="${SHA:0:8}"
123+ CALLER_REF="gh-${TS}-${{ github.run_id }}-${SHORT_SHA}"
105124 fi
106125
107- echo "🚀 Creating CloudFront invalidation..."
108- echo "Distribution ID: ${{ inputs.distribution_id }}"
109- echo "Caller Reference: ${CALLER_REF}"
110- echo "Paths: ${{ inputs.paths }}"
111-
112- IFS=' ' read -ra PATHS_ARRAY <<< "${{ inputs.paths }}"
126+ read -r -a PATHS_ARRAY <<< "${{ inputs.paths }}"
113127 PATHS_COUNT=${#PATHS_ARRAY[@]}
114-
115128 PATHS_JSON=$(printf '%s\n' "${PATHS_ARRAY[@]}" | jq -R . | jq -s .)
116- echo "📝 Invalidating $PATHS_COUNT path(s)..."
117129
118130 INVALIDATION_BATCH=$(jq -n \
119131 --argjson paths "$PATHS_JSON" \
120- --arg caller_ref "$CALLER_REF" \
121- --argjson quantity "$PATHS_COUNT" \
122- '{
123- "Paths": {
124- "Quantity": $quantity,
125- "Items": $paths
126- },
127- "CallerReference": $caller_ref
128- }')
129-
130- INVALIDATION_RESPONSE=$(aws cloudfront create-invalidation \
132+ --arg caller "$CALLER_REF" \
133+ --argjson qty "$PATHS_COUNT" \
134+ '{Paths:{Quantity:$qty,Items:$paths},CallerReference:$caller}')
135+
136+ echo "🚀 Creating CloudFront invalidation"
137+ echo "• Distribution: ${{ inputs.distribution_id }}"
138+ echo "• CallerReference: $CALLER_REF"
139+ echo "• Paths ($PATHS_COUNT): ${{ inputs.paths }}"
140+
141+ RESP=$(aws cloudfront create-invalidation \
131142 --distribution-id "${{ inputs.distribution_id }}" \
132143 --invalidation-batch "$INVALIDATION_BATCH" \
133144 --output json \
134145 --no-cli-pager)
135146
136- INVALIDATION_ID =$(echo "$INVALIDATION_RESPONSE " | jq -r '.Invalidation.Id')
137- STATUS=$(echo "$INVALIDATION_RESPONSE " | jq -r '.Invalidation.Status')
147+ ID =$(echo "$RESP " | jq -r '.Invalidation.Id')
148+ STATUS=$(echo "$RESP " | jq -r '.Invalidation.Status')
138149
139- echo "✅ CloudFront invalidation created successfully!"
140- echo "Invalidation ID: $INVALIDATION_ID"
141- echo "Status: $STATUS"
142- echo "invalidation_id=$INVALIDATION_ID" >> $GITHUB_OUTPUT
143- echo "status=$STATUS" >> $GITHUB_OUTPUT
150+ echo "invalidation_id=$ID" >> "$GITHUB_OUTPUT"
151+ echo "status=$STATUS" >> "$GITHUB_OUTPUT"
152+ echo "caller_reference=$CALLER_REF" >> "$GITHUB_OUTPUT"
153+
154+ echo "✅ Invalidation created: $ID (status: $STATUS)"
155+
156+ - name : Wait for completion
157+ if : inputs.wait_for_completion == 'true'
158+ shell : bash
159+ run : |
160+ set -euo pipefail
161+
162+ DIST="${{ inputs.distribution_id }}"
163+ ID="${{ steps.invalidate.outputs.invalidation_id }}"
164+
165+ echo "⏳ Waiting for invalidation $ID to become Completed..."
166+
167+ ATTEMPTS=0
168+ MAX_ATTEMPTS=90
169+ while (( ATTEMPTS < MAX_ATTEMPTS )); do
170+ STATUS=$(aws cloudfront get-invalidation \
171+ --distribution-id "$DIST" \
172+ --id "$ID" \
173+ --output json \
174+ --no-cli-pager | jq -r '.Invalidation.Status')
175+
176+ echo " Attempt $((ATTEMPTS+1))/$MAX_ATTEMPTS — status: $STATUS"
177+ if [[ "$STATUS" == "Completed" ]]; then
178+ echo "✅ Invalidation completed"
179+ break
180+ fi
181+
182+ ATTEMPTS=$((ATTEMPTS+1))
183+ sleep 10
184+ done
185+
186+ if (( ATTEMPTS == MAX_ATTEMPTS )); then
187+ echo "⚠️ Timed out waiting for completion — current status: $STATUS"
188+ fi
144189
145190 - name : Summary
191+ if : always() && inputs.show_summary == 'true'
146192 shell : bash
147193 run : |
148- echo "## 📊 CloudFront Invalidation Summary" >> $GITHUB_STEP_SUMMARY
149- echo "- **Invalidation ID:** ${{ steps.invalidate.outputs.invalidation_id }}" >> $GITHUB_STEP_SUMMARY
150- echo "- **Status:** ${{ steps.invalidate.outputs.status }}" >> $GITHUB_STEP_SUMMARY
151- echo "- **Paths invalidated:** ${{ inputs.paths }}" >> $GITHUB_STEP_SUMMARY
152- echo "" >> $GITHUB_STEP_SUMMARY
153- echo "ℹ️ Invalidation started. It may take 10-15 minutes to complete." >> $GITHUB_STEP_SUMMARY
194+ set -euo pipefail
195+
196+ STATUS_ICON="❌"
197+ [[ -n "${{ steps.invalidate.outputs.invalidation_id }}" ]] && STATUS_ICON="✅"
198+
199+ DIST="${{ inputs.distribution_id }}"
200+ ID="${{ steps.invalidate.outputs.invalidation_id }}"
201+ CF_LINK=""
202+ if [[ -n "$DIST" && -n "$ID" ]]; then
203+ CF_LINK="https://console.aws.amazon.com/cloudfront/v4/home#/distributions/${DIST}/invalidations/${ID}"
204+ fi
205+
206+ LIMIT="${{ inputs.summary_limit }}"
207+ [[ "$LIMIT" =~ ^[0-9]+$ ]] || LIMIT="250"
208+
209+ PATHS_RAW='${{ inputs.paths }}'
210+
211+ set -f
212+ IFS=' ' read -r -a P_ARR <<< "$PATHS_RAW"
213+ set +f
214+
215+ TOTAL="${#P_ARR[@]}"
216+ SHOW="$LIMIT"; (( TOTAL < LIMIT )) && SHOW="$TOTAL"
217+
218+ {
219+ echo "## 📊 CloudFront Invalidation ${STATUS_ICON}"
220+ echo "- **Invalidation ID:** \`${ID:-N/A}\`"
221+ echo "- **Status:** \`${{ steps.invalidate.outputs.status || 'N/A' }}\`"
222+ echo "- **CallerReference:** \`${{ steps.invalidate.outputs.caller_reference || 'auto' }}\`"
223+ echo "- **Distribution:** \`${DIST}\`"
224+ if [[ -n "$CF_LINK" ]]; then
225+ echo "- **Console:** ${CF_LINK}"
226+ fi
227+
228+ echo ""
229+ if (( TOTAL > 0 )); then
230+ if (( TOTAL <= LIMIT )); then
231+ echo "### Paths"
232+ else
233+ echo "### Paths (first ${LIMIT} of ${TOTAL})"
234+ fi
235+ echo '```'
236+ for ((i=0;i<SHOW;i++)); do
237+ printf '%s\n' "${P_ARR[i]}"
238+ done
239+ echo '```'
240+ fi
241+
242+ echo ""
243+ if [[ "${{ inputs.wait_for_completion }}" == "true" ]]; then
244+ echo "⏱️ Waited for completion: **true**"
245+ else
246+ echo "⏱️ Waited for completion: **false** (status may change to *Completed* in ~10–15 minutes)"
247+ fi
248+ } >> "$GITHUB_STEP_SUMMARY"
0 commit comments