Skip to content

Commit 28c84e5

Browse files
authored
ci: set cache headers and remove deploy downtime for storybook publish (#5206)
* ci: set cache headers and remove deploy downtime for storybook publish Storybook prod served every file with Front Door's default cache-control (public, max-age=172800), including index.html and the un-hashed sb-manager/sb-addons bundles. After each deploy, browsers with a warm cache mixed bundles from two deploys and crashed the Manager UI (api.getNavAvailability is not a function). - Upload content-hashed assets/ with max-age=31536000, immutable - Upload everything else with no-cache so browsers revalidate (cheap 304s via ETag/Last-Modified) - Replace delete-all-before-upload with overwrite-then-delete-stale, removing the downtime window on every deploy; new hashed assets are uploaded before index.html so it never references missing chunks * ci: delete stale storybook blobs by name set instead of timestamp Comparing blob lastModified (Azure server clock) against a runner-clock timestamp could delete freshly uploaded blobs under clock skew. Capture the deployed file set before upload and delete container blobs not in that set — deterministic, no timestamps, no locale-sensitive string comparison. * ci: guard empty deploy set and parallelise stale blob deletion An empty deployed-blobs list would classify every blob in the container as stale and wipe the live site — refuse to run cleanup on an empty set. Content-hashed assets are renamed on every deploy, so the whole previous assets/* set is deleted each run; parallelise with xargs -P 8 instead of one sequential az call per blob. * ci: make stale blob cleanup best-effort A single transient az delete failure made xargs exit 123 and failed the job under set -e, even though the new content is already live. Capture the exit code and warn instead — surviving orphans are re-detected as stale on the next deploy. * ci: document container ownership assumption in storybook cleanup
1 parent d16014f commit 28c84e5

1 file changed

Lines changed: 80 additions & 33 deletions

File tree

.github/workflows/publish_storybook.yaml

Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -101,38 +101,6 @@ jobs:
101101
tenant-id: 3aa4a235-b6e2-48d5-9195-7fcf05b459b0
102102
allow-no-subscriptions: true
103103

104-
- name: Delete all existing blobs to ensure clean deployment 🗑️
105-
env:
106-
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZ_STORYBOOK_CONNECTION_STRING }}
107-
run: |
108-
set -euo pipefail
109-
echo "Checking existing blobs in container '\$web'..."
110-
BLOB_COUNT=$(az storage blob list \
111-
--container-name '$web' \
112-
--connection-string "$AZURE_STORAGE_CONNECTION_STRING" \
113-
--query 'length(@)' \
114-
--output tsv)
115-
116-
if [ "$BLOB_COUNT" -eq 0 ]; then
117-
echo "Container already empty."
118-
else
119-
echo "Deleting $BLOB_COUNT blobs..."
120-
az storage blob delete-batch \
121-
--source '$web' \
122-
--pattern "*" \
123-
--connection-string "$AZURE_STORAGE_CONNECTION_STRING" \
124-
--output none
125-
echo "Deletion request submitted."
126-
fi
127-
128-
REMAINING=$(az storage blob list \
129-
--container-name '$web' \
130-
--connection-string "$AZURE_STORAGE_CONNECTION_STRING" \
131-
--query 'length(@)' \
132-
--output tsv)
133-
134-
echo "✅ Blob deletion completed. Remaining blobs: $REMAINING"
135-
136104
- name: Deploy to Azure Blob Storage 🚀
137105
id: deploy-website
138106
env:
@@ -150,20 +118,99 @@ jobs:
150118
FILE_COUNT=$(find packages/eds-core-react/storybook-build -type f | wc -l)
151119
echo "Uploading $FILE_COUNT files..."
152120
121+
# Capture the full set of blob names this deploy produces. Blobs not
122+
# in this set are orphans from a previous deploy and are cleaned up
123+
# after the upload (zero-downtime deploy: upload with --overwrite
124+
# first, delete stale blobs afterwards). Deleting by name set is
125+
# deterministic — timestamps would race the Azure server clock.
126+
# Must run before the mv of assets/ below, or assets/* drops out of
127+
# the set and the cleanup deletes all live assets.
128+
(cd packages/eds-core-react/storybook-build && find . -type f | sed 's|^\./||') \
129+
| LC_ALL=C sort > /tmp/deployed-blobs.txt
130+
131+
# 1) Content-hashed assets: cache forever. Uploaded first so the new
132+
# index.html never references chunks that don't exist yet.
133+
az storage blob upload-batch \
134+
--destination '$web' \
135+
--destination-path assets \
136+
--source packages/eds-core-react/storybook-build/assets \
137+
--content-cache-control 'public, max-age=31536000, immutable' \
138+
--overwrite \
139+
--connection-string "$AZURE_STORAGE_CONNECTION_STRING"
140+
141+
# 2) Un-hashed files (index.html, iframe.html, sb-manager/**,
142+
# sb-addons/** etc.): browsers must revalidate on every load, or a
143+
# stale bundle mix breaks the Manager UI after each deploy (#5205).
144+
# Revalidation is cheap 304s via ETag/Last-Modified. Move assets/
145+
# aside so it is not re-uploaded with the wrong header.
146+
mv packages/eds-core-react/storybook-build/assets /tmp/assets-uploaded
153147
az storage blob upload-batch \
154148
--destination '$web' \
155149
--source packages/eds-core-react/storybook-build \
150+
--content-cache-control 'no-cache' \
156151
--overwrite \
157152
--connection-string "$AZURE_STORAGE_CONNECTION_STRING"
158153
159154
# Verify upload
160155
UPLOADED=$(az storage blob list \
161156
--container-name '$web' \
157+
--num-results '*' \
162158
--connection-string "$AZURE_STORAGE_CONNECTION_STRING" \
163159
--query 'length(@)' \
164160
--output tsv)
165161
166-
echo "✅ Deployment completed successfully. Total blobs in container: $UPLOADED"
162+
echo "✅ Deployment completed successfully. Total blobs in container (before stale cleanup): $UPLOADED"
163+
164+
- name: Delete stale blobs from previous deploy 🗑️
165+
env:
166+
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.AZ_STORYBOOK_CONNECTION_STRING }}
167+
run: |
168+
set -euo pipefail
169+
echo "Deleting blobs not part of this deploy..."
170+
171+
# An empty deploy set would classify every blob in the container as
172+
# stale and wipe the live site — refuse to continue.
173+
if [ ! -s /tmp/deployed-blobs.txt ]; then
174+
echo "❌ Deploy set is empty — refusing to run stale cleanup"
175+
exit 1
176+
fi
177+
178+
az storage blob list \
179+
--container-name '$web' \
180+
--num-results '*' \
181+
--connection-string "$AZURE_STORAGE_CONNECTION_STRING" \
182+
--query '[].name' \
183+
--output tsv \
184+
| LC_ALL=C sort > /tmp/existing-blobs.txt
185+
186+
# Blobs present in the container but not in this deploy's file set.
187+
# Assumes this workflow is the sole writer to the $web container —
188+
# anything uploaded out-of-band (e.g. a manual 404.html) is treated
189+
# as stale and deleted on the next deploy.
190+
LC_ALL=C comm -13 /tmp/deployed-blobs.txt /tmp/existing-blobs.txt > /tmp/stale-blobs.txt
191+
192+
COUNT=$(wc -l < /tmp/stale-blobs.txt | tr -d ' ')
193+
echo "Deleting $COUNT stale blobs..."
194+
195+
# Every deploy renames all content-hashed assets, so the whole
196+
# previous assets/* set is stale each run — parallelise the deletes.
197+
# Cleanup is best-effort: the new content is already live at this
198+
# point, and blobs that survive a transient delete failure are
199+
# re-detected as stale on the next deploy. Don't fail the job.
200+
set +e
201+
xargs -r -P 8 -I{} az storage blob delete \
202+
--container-name '$web' \
203+
--name '{}' \
204+
--connection-string "$AZURE_STORAGE_CONNECTION_STRING" \
205+
--output none < /tmp/stale-blobs.txt
206+
rc=$?
207+
set -e
208+
209+
if [ "$rc" -eq 0 ]; then
210+
echo "✅ Stale blob cleanup completed. Deleted $COUNT stale blobs."
211+
else
212+
echo "⚠️ Some stale deletes failed (rc=$rc); orphans will be retried on the next deploy"
213+
fi
167214
168215
- name: logout 🔓
169216
run: az logout

0 commit comments

Comments
 (0)