ci: deploy the verified local image
All checks were successful
Test and deploy / Verify production build (push) Successful in 10m25s
All checks were successful
Test and deploy / Verify production build (push) Successful in 10m25s
Build and test the Next.js application once with persistent BuildKit caches, smoke-test the immutable runtime image, and activate that same image through the shared production Docker daemon with serialized rollback-safe deployment.
This commit is contained in:
65
scripts/smoke-container.sh
Normal file
65
scripts/smoke-container.sh
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
image="${1:?runtime image is required}"
|
||||
container_name="portfolio-smoke-${GITEA_RUN_ID:-$$}"
|
||||
|
||||
cleanup() {
|
||||
docker rm --force "$container_name" >/dev/null 2>&1 || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
cleanup
|
||||
docker run --detach --name "$container_name" "$image" >/dev/null
|
||||
|
||||
for _ in {1..30}; do
|
||||
status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_name")"
|
||||
case "$status" in
|
||||
healthy)
|
||||
break
|
||||
;;
|
||||
unhealthy|exited|dead)
|
||||
docker logs "$container_name" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [[ "$(docker inspect --format '{{.State.Health.Status}}' "$container_name")" != "healthy" ]]; then
|
||||
docker logs "$container_name" >&2
|
||||
echo "Runtime image did not become healthy within 60 seconds." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker exec -i "$container_name" node <<'NODE'
|
||||
const origin = 'http://127.0.0.1:3000';
|
||||
|
||||
async function expectOk(path, options) {
|
||||
const response = await fetch(`${origin}${path}`, options);
|
||||
if (!response.ok) throw new Error(`${path} returned ${response.status}`);
|
||||
return response;
|
||||
}
|
||||
|
||||
const english = await expectOk('/');
|
||||
await expectOk('/fa');
|
||||
await expectOk('/projects/interanet-solutions');
|
||||
await expectOk('/fa/projects/interanet-solutions');
|
||||
|
||||
const html = await english.text();
|
||||
const staticAsset = html.match(/(?:src|href)="(\/_next\/static\/[^"?#]+)/)?.[1];
|
||||
if (!staticAsset) throw new Error('No built static asset was found in the home page.');
|
||||
await expectOk(staticAsset);
|
||||
|
||||
const optimized = await expectOk(
|
||||
'/_next/image?url=%2Fimages%2Favatar.png&w=256&q=75',
|
||||
{ headers: { Accept: 'image/webp' } },
|
||||
);
|
||||
const contentType = optimized.headers.get('content-type') ?? '';
|
||||
if (!/^image\/(?:webp|png)$/.test(contentType)) {
|
||||
throw new Error(`Unexpected optimized image type: ${contentType}`);
|
||||
}
|
||||
if ((await optimized.arrayBuffer()).byteLength === 0) {
|
||||
throw new Error('Optimized image response was empty.');
|
||||
}
|
||||
NODE
|
||||
Reference in New Issue
Block a user