Deploy #273
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy | |
| on: | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| id-token: write | |
| env: | |
| AWS_REGION : "us-east-1" | |
| AWS_REGION_ZONE : "us-east-1" | |
| S3_BUCKET_NAME: "ciacompliancemanager-frontend-us-east-1-172017021075" | |
| CLOUDFRONT_STACK_NAME: "ciacompliancemanager-frontend" | |
| jobs: | |
| deploy: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9ca718d3bf646d6534007c269a635b3e54cadf99 # v2.19.2 | |
| with: | |
| egress-policy: block | |
| allowed-endpoints: > | |
| accounts.google.com:443 | |
| amazon-cloudfront-secure-static-site-s3bucketroot-14oliw5cmta06.s3.us-east-1.amazonaws.com:443 | |
| api.github.com:443 | |
| api.securityscorecards.dev:443 | |
| app.fossa.io:443 | |
| auth.docker.io:443 | |
| bestpractices.coreinfrastructure.org:443 | |
| cfu.zaproxy.org:443 | |
| cla-assistant.io:443 | |
| cla-assistant.io:80 | |
| clients2.google.com:80 | |
| cloudformation.us-east-1.amazonaws.com:443 | |
| cloudfront.amazonaws.com:443 | |
| content-signature-2.cdn.mozilla.net:443 | |
| deb.debian.org:80 | |
| firefox-settings-attachments.cdn.mozilla.net:443 | |
| firefox.settings.services.mozilla.com:443 | |
| fonts.googleapis.com:443 | |
| fonts.gstatic.com:443 | |
| ghcr.io:443 | |
| github.com:443 | |
| hack23.com:443 | |
| hack23.com:80 | |
| hack23.comnull:443 | |
| img.shields.io:443 | |
| isitmaintained.com:443 | |
| isitmaintained.com:80 | |
| location.services.mozilla.com:443 | |
| news.zaproxy.org:443 | |
| objects.githubusercontent.com:443 | |
| pkg-containers.githubusercontent.com:443 | |
| production.cloudflare.docker.com:443 | |
| r10.o.lencr.org:443 | |
| r11.o.lencr.org:80 | |
| raw.githubusercontent.com:443 | |
| registry-1.docker.io:443 | |
| registry.npmjs.org:443 | |
| safebrowsingohttpgateway.googleapis.com:443 | |
| shavar.services.mozilla.com:443 | |
| slsa.dev:443 | |
| sonarcloud.io:443 | |
| storage.googleapis.com:443 | |
| sts.us-east-1.amazonaws.com:443 | |
| tel.zaproxy.org:443 | |
| tracking-protection.cdn.mozilla.net:443 | |
| us-central1-lighthouse-infrastructure.cloudfunctions.net:443 | |
| www.google.com:443 | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: configure aws credentials | |
| uses: aws-actions/configure-aws-credentials@d979d5b3a71173a29b74b5b88418bfda9437d885 # v6.1.1 | |
| with: | |
| role-to-assume: arn:aws:iam::172017021075:role/GithubWorkFlowRole | |
| role-session-name: githubworkflowrolesessiont2 | |
| aws-region: ${{ env.AWS_REGION }} | |
| - name: Deploy to S3 with optimized sync | |
| run: | | |
| # Cost-optimized deployment: minimizes S3 API calls and CloudTrail events | |
| # - Uses --size-only for immutable content-hashed assets only | |
| # - Uses default sync (size+mtime) for mutable files (HTML, metadata) | |
| # - Consolidates multiple syncs into minimal commands per cache tier | |
| # - Deploys to both / and /docs/ paths to support all current and legacy URLs | |
| echo "🚀 Starting cost-optimized deployment (minimal API calls)" | |
| # --- Deploy to root (/) --- | |
| # 1. Immutable assets: JS, CSS, fonts, images (all content-hashed) | |
| echo "⚡ [root] Syncing immutable assets (JS, CSS, fonts, images)..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/ \ | |
| --size-only \ | |
| --exclude "*" \ | |
| --include "*.js" \ | |
| --include "*.css" \ | |
| --include "*.woff" \ | |
| --include "*.woff2" \ | |
| --include "*.ttf" \ | |
| --include "*.eot" \ | |
| --include "*.otf" \ | |
| --include "*.webp" \ | |
| --include "*.png" \ | |
| --include "*.jpg" \ | |
| --include "*.jpeg" \ | |
| --include "*.gif" \ | |
| --include "*.svg" \ | |
| --include "*.ico" \ | |
| --cache-control "public, max-age=31536000, immutable" \ | |
| --exclude ".git/*" \ | |
| --exclude "screenshots/*" | |
| # 2. Source map files with explicit content-type (immutable, content-hashed) | |
| echo "🗺️ [root] Syncing source maps with correct content-type..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/ \ | |
| --size-only \ | |
| --exclude "*" \ | |
| --include "*.js.map" \ | |
| --include "*.css.map" \ | |
| --cache-control "public, max-age=31536000, immutable" \ | |
| --content-type "application/json" \ | |
| --exclude ".git/*" | |
| # 3. HTML files (not content-hashed, short cache, use size+mtime for correctness) | |
| echo "📄 [root] Syncing HTML files..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/ \ | |
| --exclude "*" \ | |
| --include "*.html" \ | |
| --cache-control "public, max-age=3600, must-revalidate" \ | |
| --content-type "text/html; charset=utf-8" \ | |
| --exclude ".git/*" | |
| # 4. Metadata and text files (medium cache, use size+mtime for correctness) | |
| echo "📋 [root] Syncing metadata files..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/ \ | |
| --exclude "*" \ | |
| --include "*.xml" \ | |
| --include "*.json" \ | |
| --include "*.txt" \ | |
| --include "*.md" \ | |
| --cache-control "public, max-age=86400" \ | |
| --exclude ".git/*" | |
| # 5. Extensionless files (CNAME, .nojekyll, etc.) | |
| echo "📎 [root] Syncing extensionless/dotfiles..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/ \ | |
| --exclude "*.*" \ | |
| --exclude ".git/*" \ | |
| --cache-control "public, max-age=86400" | |
| # --- Deploy to /docs/ (legacy URL support) --- | |
| # 6. Immutable assets to /docs/ | |
| echo "⚡ [/docs/] Syncing immutable assets..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/docs/ \ | |
| --size-only \ | |
| --exclude "*" \ | |
| --include "*.js" \ | |
| --include "*.css" \ | |
| --include "*.woff" \ | |
| --include "*.woff2" \ | |
| --include "*.ttf" \ | |
| --include "*.eot" \ | |
| --include "*.otf" \ | |
| --include "*.webp" \ | |
| --include "*.png" \ | |
| --include "*.jpg" \ | |
| --include "*.jpeg" \ | |
| --include "*.gif" \ | |
| --include "*.svg" \ | |
| --include "*.ico" \ | |
| --cache-control "public, max-age=31536000, immutable" \ | |
| --exclude ".git/*" \ | |
| --exclude "screenshots/*" | |
| # 7. Source map files to /docs/ with explicit content-type | |
| echo "🗺️ [/docs/] Syncing source maps with correct content-type..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/docs/ \ | |
| --size-only \ | |
| --exclude "*" \ | |
| --include "*.js.map" \ | |
| --include "*.css.map" \ | |
| --cache-control "public, max-age=31536000, immutable" \ | |
| --content-type "application/json" \ | |
| --exclude ".git/*" | |
| # 8. HTML files to /docs/ | |
| echo "📄 [/docs/] Syncing HTML files..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/docs/ \ | |
| --exclude "*" \ | |
| --include "*.html" \ | |
| --cache-control "public, max-age=3600, must-revalidate" \ | |
| --content-type "text/html; charset=utf-8" \ | |
| --exclude ".git/*" | |
| # 9. Metadata and text files to /docs/ | |
| echo "📋 [/docs/] Syncing metadata files..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/docs/ \ | |
| --exclude "*" \ | |
| --include "*.xml" \ | |
| --include "*.json" \ | |
| --include "*.txt" \ | |
| --include "*.md" \ | |
| --cache-control "public, max-age=86400" \ | |
| --exclude ".git/*" | |
| # 10. Extensionless files to /docs/ | |
| echo "📎 [/docs/] Syncing extensionless/dotfiles..." | |
| aws s3 sync docs/. s3://${{ env.S3_BUCKET_NAME }}/docs/ \ | |
| --exclude "*.*" \ | |
| --exclude ".git/*" \ | |
| --cache-control "public, max-age=86400" | |
| # --- Screenshots (if directory exists) --- | |
| if [ -d "docs/screenshots" ]; then | |
| echo "📸 Syncing screenshots to both paths..." | |
| aws s3 sync docs/screenshots/. s3://${{ env.S3_BUCKET_NAME }}/screenshots/ \ | |
| --size-only | |
| aws s3 sync docs/screenshots/. s3://${{ env.S3_BUCKET_NAME }}/docs/screenshots/ \ | |
| --size-only | |
| fi | |
| echo "✅ Deployment completed (10 sync commands vs 16 previously)" | |
| # Invalidate CloudFront cache for HTML and metadata only (immutable assets don't need invalidation) | |
| - name: Invalidate CloudFront | |
| run: | | |
| echo "🔍 Discovering CloudFront distribution ID from stack: ${{ env.CLOUDFRONT_STACK_NAME }}" | |
| CloudFrontDistId=$(aws cloudformation describe-stacks \ | |
| --stack-name ${{ env.CLOUDFRONT_STACK_NAME }} \ | |
| --query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue" \ | |
| --output text 2>/dev/null || echo "") | |
| if [ -z "$CloudFrontDistId" ]; then | |
| echo "⚠️ Warning: CloudFront distribution ID not found in stack outputs" | |
| echo "Attempting to find distribution by S3 origin domain..." | |
| CloudFrontDistId=$(aws cloudfront list-distributions \ | |
| --output json 2>/dev/null | \ | |
| jq -r ".DistributionList.Items[] | select(.Origins.Items[].DomainName | contains(\"${{ env.S3_BUCKET_NAME }}\")) | .Id" | \ | |
| head -n 1 || echo "") | |
| fi | |
| if [ -z "$CloudFrontDistId" ] || [ "$CloudFrontDistId" = "None" ]; then | |
| echo "❌ Error: Could not discover CloudFront distribution ID" | |
| exit 1 | |
| fi | |
| echo "✅ Found CloudFront distribution: $CloudFrontDistId" | |
| echo "🔄 Invalidating HTML and metadata paths (immutable assets use cache-busting hashes)..." | |
| aws cloudfront create-invalidation \ | |
| --distribution-id $CloudFrontDistId \ | |
| --paths "/index.html" "/docs/index.html" "/*.html" "/docs/*.html" "/*/index.html" "/docs/*/index.html" "/sitemap.xml" "/docs/sitemap.xml" "/manifest.json" "/docs/manifest.json" "/robots.txt" "/docs/robots.txt" "/version.txt" "/docs/version.txt" | |
| echo "✅ CloudFront invalidation completed (targeted paths only, reduces cost vs /*)" | |