From baebf6547f3947caedd6400eafcde2f385107233 Mon Sep 17 00:00:00 2001 From: Adam Hall Date: Thu, 26 Mar 2026 11:40:54 +1030 Subject: [PATCH] DO-1953: Update the pwa deployment workflow to be in-line with our cdk and nx deployment workflows by using the environment for AWS credentials instead of inputs --- .github/workflows/pwa-deployment.yml | 280 +++++++++++++++++++-------- docs/pwa-deployment.md | 80 ++++---- 2 files changed, 247 insertions(+), 113 deletions(-) diff --git a/.github/workflows/pwa-deployment.yml b/.github/workflows/pwa-deployment.yml index b2df29c..49a9619 100644 --- a/.github/workflows/pwa-deployment.yml +++ b/.github/workflows/pwa-deployment.yml @@ -9,21 +9,17 @@ on: type: string required: false default: "ap-southeast-2" - s3-bucket: - description: "S3 bucket name for deployment" + role-session-name: + description: "AWS role session name for OIDC authentication (default: {repo}-{short-sha}-{run-number})" type: string - required: true - cloudfront-distribution-id: - description: "CloudFront distribution ID for cache invalidation" - type: string - required: true + required: false + default: "" # Environment Configuration - environment: - description: "Deployment environment (GitHub environment name for protection rules)" + github-environment: + description: "GitHub Environment name for secrets/variables (e.g. Staging, Production)" type: string - required: false - default: "staging" + required: true # Build Configuration package-manager: @@ -92,14 +88,6 @@ on: required: false default: false - secrets: - aws-access-key-id: - description: "AWS access key ID" - required: true - aws-secret-access-key: - description: "AWS secret access key" - required: true - outputs: deployment-url: description: "URL of the deployed application" @@ -113,6 +101,7 @@ jobs: prepare: name: 🔍 Prepare Deployment runs-on: ubuntu-latest + environment: ${{ inputs.github-environment }} outputs: cache-control-static: ${{ steps.cache-config.outputs.cache-control-static }} cache-control-html: ${{ steps.cache-config.outputs.cache-control-html }} @@ -120,11 +109,79 @@ jobs: deployment-url: ${{ steps.deployment-config.outputs.deployment-url }} brand-matrix: ${{ steps.brand-config.outputs.matrix }} invalidation-paths: ${{ steps.cache-config.outputs.invalidation-paths }} + auth-mode: ${{ steps.validate-inputs.outputs.auth-mode }} + role-session-name: ${{ steps.resolve-session-name.outputs.role-session-name }} steps: + - name: Validate required inputs + id: validate-inputs + env: + ENVIRONMENT: ${{ inputs.github-environment }} + S3_BUCKET: ${{ vars.S3_BUCKET }} + CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} + VAR_AWS_ACCESS_KEY_ID: ${{ vars.AWS_ACCESS_KEY_ID }} + SECRET_AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + VAR_AWS_ROLE_ARN: ${{ vars.AWS_ROLE_ARN }} + run: | + echo "🔍 Validating deployment configuration..." + echo "â„šī¸ Using variables from $ENVIRONMENT environment" + + if [ -z "$S3_BUCKET" ]; then + echo "❌ Error: S3_BUCKET must be set as a variable in your $ENVIRONMENT environment" + exit 1 + fi + echo "✅ S3_BUCKET: $S3_BUCKET" + + if [ -z "$CLOUDFRONT_DISTRIBUTION_ID" ]; then + echo "❌ Error: CLOUDFRONT_DISTRIBUTION_ID must be set as a variable in your $ENVIRONMENT environment" + exit 1 + fi + echo "✅ CLOUDFRONT_DISTRIBUTION_ID: $CLOUDFRONT_DISTRIBUTION_ID" + + if [ -n "$VAR_AWS_ACCESS_KEY_ID" ]; then + echo "â„šī¸ AWS_ACCESS_KEY_ID set from variable: $VAR_AWS_ACCESS_KEY_ID" + if [ -z "$SECRET_AWS_SECRET_ACCESS_KEY" ]; then + echo "❌ Error: AWS_SECRET_ACCESS_KEY is not defined as a secret in your $ENVIRONMENT environment" + exit 1 + fi + echo "✅ AWS_SECRET_ACCESS_KEY secret is configured" + echo "✅ Using static credential authentication" + echo "auth-mode=static" >> $GITHUB_OUTPUT + elif [ -n "$VAR_AWS_ROLE_ARN" ]; then + echo "â„šī¸ AWS_ROLE_ARN set from variable: $VAR_AWS_ROLE_ARN" + echo "✅ Using OIDC authentication" + echo "auth-mode=oidc" >> $GITHUB_OUTPUT + else + echo "❌ Error: No AWS credentials configured in your $ENVIRONMENT environment." + echo " Configure one of the following:" + echo " - Static credentials: AWS_ACCESS_KEY_ID (variable) + AWS_SECRET_ACCESS_KEY (secret)" + echo " - OIDC: AWS_ROLE_ARN (variable)" + exit 1 + fi + + echo "✅ All inputs validated successfully" + + - name: Resolve role session name + id: resolve-session-name + if: steps.validate-inputs.outputs.auth-mode == 'oidc' + env: + ROLE_SESSION_NAME: ${{ inputs.role-session-name }} + COMMIT_SHA: ${{ github.sha }} + REPOSITORY_NAME: ${{ github.event.repository.name }} + RUN_NUMBER: ${{ github.run_number }} + run: | + SESSION_NAME="$ROLE_SESSION_NAME" + if [ -z "$SESSION_NAME" ]; then + SHORT_SHA=$(echo "$COMMIT_SHA" | cut -c1-7) + SESSION_NAME="${REPOSITORY_NAME}-${SHORT_SHA}-${RUN_NUMBER}" + fi + # AWS session names: max 64 chars, allowed [a-zA-Z0-9=,.@-] + SESSION_NAME=$(echo "$SESSION_NAME" | tr -c '[:alnum:]=,.@-' '-' | cut -c1-64) + echo "role-session-name=$SESSION_NAME" >> $GITHUB_OUTPUT + - name: Configure cache strategy id: cache-config run: | - case "${{ inputs.cache-strategy }}" in + case "${INPUTS_CACHE_STRATEGY}" in "immutable") # Static assets with content hashing - cache for 1 year echo "cache-control-static=public, max-age=31536000, immutable" >> $GITHUB_OUTPUT @@ -137,7 +194,7 @@ jobs: echo "cache-control-html=no-cache, no-store, must-revalidate" >> $GITHUB_OUTPUT ;; *) - echo "❌ Invalid cache-strategy: '${{ inputs.cache-strategy }}'. Must be 'immutable' or 'no-cache'." + echo "❌ Invalid cache-strategy: '${INPUTS_CACHE_STRATEGY}'. Must be 'immutable' or 'no-cache'." exit 1 ;; esac @@ -145,16 +202,19 @@ jobs: # Prepare invalidation paths (use heredoc to preserve JSON special characters) { echo 'invalidation-paths<> $GITHUB_OUTPUT if [ "${{ inputs.debug }}" = "true" ]; then echo "🔍 Cache configuration:" - echo " Strategy: ${{ inputs.cache-strategy }}" + echo " Strategy: ${INPUTS_CACHE_STRATEGY}" echo " Static assets: $(cat $GITHUB_OUTPUT | grep cache-control-static | cut -d'=' -f2-)" echo " HTML files: $(cat $GITHUB_OUTPUT | grep cache-control-html | cut -d'=' -f2-)" fi + env: + INPUTS_CACHE_STRATEGY: ${{ inputs.cache-strategy }} + INPUTS_CLOUDFRONT_INVALIDATION_PATHS: ${{ inputs.cloudfront-invalidation-paths }} - name: Configure deployment paths id: deployment-config @@ -164,24 +224,25 @@ jobs: if [ -n "${{ github.event.pull_request.number }}" ]; then PREFIX="pr-${{ github.event.pull_request.number }}" else - PREFIX="branch-$(echo '${{ github.ref_name }}' | sed 's/[^a-zA-Z0-9-]/-/g')" + PREFIX="branch-$(echo '${GITHUB_REF_NAME}' | sed 's/[^a-zA-Z0-9-]/-/g')" fi echo "s3-prefix=${PREFIX}/" >> $GITHUB_OUTPUT - if [ -n "${{ inputs.preview-base-url }}" ]; then - echo "deployment-url=${{ inputs.preview-base-url }}/${PREFIX}/" >> $GITHUB_OUTPUT + if [ -n "${INPUTS_PREVIEW_BASE_URL}" ]; then + echo "deployment-url=${INPUTS_PREVIEW_BASE_URL}/${PREFIX}/" >> $GITHUB_OUTPUT else - echo "deployment-url=https://${{ inputs.s3-bucket }}.s3.amazonaws.com/${PREFIX}/index.html" >> $GITHUB_OUTPUT + DEPLOY_URL="https://${INPUTS_S3_BUCKET}.s3.amazonaws.com/${PREFIX}/index.html" + echo "deployment-url=$DEPLOY_URL" >> $GITHUB_OUTPUT fi else # Production/staging deployment to root echo "s3-prefix=" >> $GITHUB_OUTPUT - if [ -n "${{ inputs.preview-base-url }}" ]; then - echo "deployment-url=${{ inputs.preview-base-url }}/" >> $GITHUB_OUTPUT + if [ -n "${INPUTS_PREVIEW_BASE_URL}" ]; then + echo "deployment-url=${INPUTS_PREVIEW_BASE_URL}/" >> $GITHUB_OUTPUT else - echo "deployment-url=https://${{ inputs.s3-bucket }}.s3.amazonaws.com/index.html" >> $GITHUB_OUTPUT + echo "deployment-url=https://${INPUTS_S3_BUCKET}.s3.amazonaws.com/index.html" >> $GITHUB_OUTPUT fi fi @@ -190,25 +251,34 @@ jobs: echo " S3 Prefix: $(cat $GITHUB_OUTPUT | grep s3-prefix | cut -d'=' -f2-)" echo " Deployment URL: $(cat $GITHUB_OUTPUT | grep deployment-url | cut -d'=' -f2-)" fi + env: + INPUTS_PREVIEW_BASE_URL: ${{ inputs.preview-base-url }} + INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} - name: Configure multi-brand matrix id: brand-config run: | - if [ -n "${{ inputs.brand-config }}" ]; then - echo "matrix=${{ inputs.brand-config }}" >> $GITHUB_OUTPUT + if [ -n "${INPUTS_BRAND_CONFIG}" ]; then + echo "matrix=${INPUTS_BRAND_CONFIG}" >> $GITHUB_OUTPUT echo "📱 Multi-brand deployment configured" else echo 'matrix={"brand":["default"]}' >> $GITHUB_OUTPUT echo "📱 Single brand deployment" fi - # Build and test the application + env: + INPUTS_BRAND_CONFIG: ${{ inputs.brand-config }} + + # Build and deploy the application build-and-deploy: name: 🚀 Build Application and Deploy to AWS runs-on: ubuntu-latest needs: [prepare] + permissions: + id-token: write + contents: read environment: - name: ${{ inputs.environment }} + name: ${{ inputs.github-environment }} url: ${{ needs.prepare.outputs.deployment-url }} outputs: deployment-url: ${{ needs.prepare.outputs.deployment-url }} @@ -217,7 +287,9 @@ jobs: matrix: ${{ fromJSON(needs.prepare.outputs.brand-matrix) }} steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + persist-credentials: false - name: Setup Node.js uses: actions/setup-node@v6 @@ -232,7 +304,7 @@ jobs: debug="--verbose" fi - case "${{ inputs.package-manager }}" in + case "${INPUTS_PACKAGE_MANAGER}" in "yarn") if [ "${{ inputs.is-yarn-classic }}" = "true" ]; then yarn install --frozen-lockfile $debug @@ -244,10 +316,12 @@ jobs: npm ci $debug ;; *) - echo "❌ Unsupported package manager: ${{ inputs.package-manager }}" + echo "❌ Unsupported package manager: ${INPUTS_PACKAGE_MANAGER}" exit 1 ;; esac + env: + INPUTS_PACKAGE_MANAGER: ${{ inputs.package-manager }} - name: Build application run: | @@ -257,18 +331,22 @@ jobs: fi # Set brand-specific environment if multi-brand - if [ "${{ matrix.brand }}" != "default" ]; then - echo "đŸˇī¸ Building for brand: ${{ matrix.brand }}" - export BRAND=${{ matrix.brand }} + if [ "${MATRIX_BRAND}" != "default" ]; then + echo "đŸˇī¸ Building for brand: ${MATRIX_BRAND}" + export BRAND=${MATRIX_BRAND} fi echo "đŸ—ī¸ Building application..." - ${{ inputs.package-manager }} run ${{ inputs.build-command }} $debug + ${INPUTS_PACKAGE_MANAGER} run ${INPUTS_BUILD_COMMAND} $debug + env: + MATRIX_BRAND: ${{ matrix.brand }} + INPUTS_PACKAGE_MANAGER: ${{ inputs.package-manager }} + INPUTS_BUILD_COMMAND: ${{ inputs.build-command }} - name: Verify build output run: | - if [ ! -d "${{ inputs.build-directory }}" ]; then - echo "❌ Build directory '${{ inputs.build-directory }}' not found" + if [ ! -d "${INPUTS_BUILD_DIRECTORY}" ]; then + echo "❌ Build directory '${INPUTS_BUILD_DIRECTORY}' not found" echo "Available directories:" ls -la exit 1 @@ -276,66 +354,97 @@ jobs: echo "✅ Build completed successfully" echo "📁 Build directory contents:" - find "${{ inputs.build-directory }}" -type f | head -10 + find "${INPUTS_BUILD_DIRECTORY}" -type f | head -10 + env: + INPUTS_BUILD_DIRECTORY: ${{ inputs.build-directory }} + + - name: Configure AWS credentials (Static) + if: needs.prepare.outputs.auth-mode == 'static' + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 + with: + aws-access-key-id: ${{ vars.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ vars.AWS_REGION || inputs.aws-region }} - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v6 + - name: Configure AWS credentials (OIDC) + if: needs.prepare.outputs.auth-mode == 'oidc' + uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 with: - aws-access-key-id: ${{ secrets.aws-access-key-id }} - aws-secret-access-key: ${{ secrets.aws-secret-access-key }} - aws-region: ${{ inputs.aws-region }} + role-to-assume: ${{ vars.AWS_ROLE_ARN }} + role-session-name: ${{ needs.prepare.outputs.role-session-name }} + aws-region: ${{ vars.AWS_REGION || inputs.aws-region }} - name: Configure S3 deployment paths id: s3-config run: | - S3_PATH="${{ needs.prepare.outputs.s3-prefix }}" + S3_PATH="${NEEDS_PREPARE_OUTPUTS_S3_PREFIX}" - if [ "${{ matrix.brand }}" != "default" ]; then - S3_PATH="${S3_PATH}${{ matrix.brand }}/" + if [ "${MATRIX_BRAND}" != "default" ]; then + S3_PATH="${S3_PATH}${MATRIX_BRAND}/" fi echo "s3-path=${S3_PATH}" >> $GITHUB_OUTPUT - echo "đŸŽ¯ Deploying to: s3://${{ inputs.s3-bucket }}/${S3_PATH}" + echo "đŸŽ¯ Deploying to: s3://${INPUTS_S3_BUCKET}/${S3_PATH}" + env: + NEEDS_PREPARE_OUTPUTS_S3_PREFIX: ${{ needs.prepare.outputs.s3-prefix }} + MATRIX_BRAND: ${{ matrix.brand }} + INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} - name: Deploy static assets to S3 run: | echo "🚀 Deploying static assets..." # Deploy static assets with immutable cache headers (exclude HTML and service-worker.js) - aws s3 sync "${{ inputs.build-directory }}" "s3://${{ inputs.s3-bucket }}/${{ steps.s3-config.outputs.s3-path }}" \ + aws s3 sync \ + "${INPUTS_BUILD_DIRECTORY}" \ + "s3://${INPUTS_S3_BUCKET}/${STEPS_S3_CONFIG_OUTPUTS_S3_PATH}" \ --exclude "*.html" \ --exclude "service-worker.js" \ - --cache-control "${{ needs.prepare.outputs.cache-control-static }}" \ + --cache-control "${NEEDS_PREPARE_OUTPUTS_CACHE_CONTROL_STATIC}" \ --delete \ - ${{ inputs.extra-sync-args }} + ${INPUTS_EXTRA_SYNC_ARGS} + env: + INPUTS_BUILD_DIRECTORY: ${{ inputs.build-directory }} + INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} + STEPS_S3_CONFIG_OUTPUTS_S3_PATH: ${{ steps.s3-config.outputs.s3-path }} + NEEDS_PREPARE_OUTPUTS_CACHE_CONTROL_STATIC: ${{ needs.prepare.outputs.cache-control-static }} + INPUTS_EXTRA_SYNC_ARGS: ${{ inputs.extra-sync-args }} - name: Deploy HTML and service worker to S3 run: | echo "📄 Deploying HTML files and service worker..." # Deploy HTML and service-worker.js with revalidation cache headers - aws s3 sync "${{ inputs.build-directory }}" "s3://${{ inputs.s3-bucket }}/${{ steps.s3-config.outputs.s3-path }}" \ + aws s3 sync \ + "${INPUTS_BUILD_DIRECTORY}" \ + "s3://${INPUTS_S3_BUCKET}/${STEPS_S3_CONFIG_OUTPUTS_S3_PATH}" \ --exclude "*" \ --include "*.html" \ --include "service-worker.js" \ - --cache-control "${{ needs.prepare.outputs.cache-control-html }}" \ - ${{ inputs.extra-sync-args }} + --cache-control "${NEEDS_PREPARE_OUTPUTS_CACHE_CONTROL_HTML}" \ + ${INPUTS_EXTRA_SYNC_ARGS} + env: + INPUTS_BUILD_DIRECTORY: ${{ inputs.build-directory }} + INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} + STEPS_S3_CONFIG_OUTPUTS_S3_PATH: ${{ steps.s3-config.outputs.s3-path }} + NEEDS_PREPARE_OUTPUTS_CACHE_CONTROL_HTML: ${{ needs.prepare.outputs.cache-control-html }} + INPUTS_EXTRA_SYNC_ARGS: ${{ inputs.extra-sync-args }} - name: Invalidate CloudFront cache run: | echo "🔄 Invalidating CloudFront cache..." # Parse invalidation paths - PATHS=$(echo '${{ needs.prepare.outputs.invalidation-paths }}' | jq -r '.[]') + PATHS=$(echo '${NEEDS_PREPARE_OUTPUTS_INVALIDATION_PATHS}' | jq -r '.[]') # Add brand prefix if multi-brand deployment - if [ "${{ matrix.brand }}" != "default" ]; then + if [ "${MATRIX_BRAND}" != "default" ]; then PREFIXED_PATHS="" for path in $PATHS; do if [ "$path" = "/*" ]; then - PREFIXED_PATHS="$PREFIXED_PATHS /${{ steps.s3-config.outputs.s3-path }}*" + PREFIXED_PATHS="$PREFIXED_PATHS /${STEPS_S3_CONFIG_OUTPUTS_S3_PATH}*" else - PREFIXED_PATHS="$PREFIXED_PATHS /${{ steps.s3-config.outputs.s3-path }}${path#/}" + PREFIXED_PATHS="$PREFIXED_PATHS /${STEPS_S3_CONFIG_OUTPUTS_S3_PATH}${path#/}" fi done PATHS="$PREFIXED_PATHS" @@ -344,7 +453,7 @@ jobs: echo "Invalidating paths: $PATHS" INVALIDATION_ID=$(aws cloudfront create-invalidation \ - --distribution-id "${{ inputs.cloudfront-distribution-id }}" \ + --distribution-id "${INPUTS_CLOUDFRONT_DISTRIBUTION_ID}" \ --paths $PATHS \ --query 'Invalidation.Id' \ --output text) @@ -354,10 +463,15 @@ jobs: if [ "${{ inputs.debug }}" = "true" ]; then echo "🔍 Waiting for invalidation to complete..." aws cloudfront wait invalidation-completed \ - --distribution-id "${{ inputs.cloudfront-distribution-id }}" \ + --distribution-id "${INPUTS_CLOUDFRONT_DISTRIBUTION_ID}" \ --id "$INVALIDATION_ID" echo "✅ CloudFront invalidation completed" fi + env: + NEEDS_PREPARE_OUTPUTS_INVALIDATION_PATHS: ${{ needs.prepare.outputs.invalidation-paths }} + MATRIX_BRAND: ${{ matrix.brand }} + STEPS_S3_CONFIG_OUTPUTS_S3_PATH: ${{ steps.s3-config.outputs.s3-path }} + INPUTS_CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} - name: Generate deployment summary run: | @@ -365,25 +479,37 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| **Environment** | ${{ inputs.environment }} |" >> $GITHUB_STEP_SUMMARY - echo "| **Brand** | ${{ matrix.brand }} |" >> $GITHUB_STEP_SUMMARY - echo "| **S3 Bucket** | ${{ inputs.s3-bucket }} |" >> $GITHUB_STEP_SUMMARY - echo "| **S3 Path** | ${{ steps.s3-config.outputs.s3-path }} |" >> $GITHUB_STEP_SUMMARY - echo "| **Cache Strategy** | ${{ inputs.cache-strategy }} |" >> $GITHUB_STEP_SUMMARY - echo "| **Deployment URL** | ${{ needs.prepare.outputs.deployment-url }} |" >> $GITHUB_STEP_SUMMARY + echo "| **Environment** | ${INPUTS_GITHUB_ENVIRONMENT} |" >> $GITHUB_STEP_SUMMARY + echo "| **Brand** | ${MATRIX_BRAND} |" >> $GITHUB_STEP_SUMMARY + echo "| **S3 Bucket** | ${INPUTS_S3_BUCKET} |" >> $GITHUB_STEP_SUMMARY + echo "| **S3 Path** | ${STEPS_S3_CONFIG_OUTPUTS_S3_PATH} |" >> $GITHUB_STEP_SUMMARY + echo "| **Cache Strategy** | ${INPUTS_CACHE_STRATEGY} |" >> $GITHUB_STEP_SUMMARY + echo "| **Deployment URL** | ${NEEDS_PREPARE_OUTPUTS_DEPLOYMENT_URL} |" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "### 📊 Build Information" >> $GITHUB_STEP_SUMMARY - echo "- **Package Manager**: ${{ inputs.package-manager }}" >> $GITHUB_STEP_SUMMARY - echo "- **Build Command**: ${{ inputs.build-command }}" >> $GITHUB_STEP_SUMMARY - echo "- **Build Directory**: ${{ inputs.build-directory }}" >> $GITHUB_STEP_SUMMARY + echo "- **Package Manager**: ${INPUTS_PACKAGE_MANAGER}" >> $GITHUB_STEP_SUMMARY + echo "- **Build Command**: ${INPUTS_BUILD_COMMAND}" >> $GITHUB_STEP_SUMMARY + echo "- **Build Directory**: ${INPUTS_BUILD_DIRECTORY}" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY + DEPLOY_URL="${NEEDS_PREPARE_OUTPUTS_DEPLOYMENT_URL}" if [ "${{ inputs.preview-mode }}" = "true" ]; then echo "### 🔍 Preview Environment" >> $GITHUB_STEP_SUMMARY echo "This is a preview deployment. The application is available at:" >> $GITHUB_STEP_SUMMARY - echo "**[${{ needs.prepare.outputs.deployment-url }}](${{ needs.prepare.outputs.deployment-url }})**" >> $GITHUB_STEP_SUMMARY + echo "**[$DEPLOY_URL]($DEPLOY_URL)**" >> $GITHUB_STEP_SUMMARY else echo "### 🌍 Deployment" >> $GITHUB_STEP_SUMMARY - echo "Application deployed to ${{ inputs.environment }} environment:" >> $GITHUB_STEP_SUMMARY - echo "**[${{ needs.prepare.outputs.deployment-url }}](${{ needs.prepare.outputs.deployment-url }})**" >> $GITHUB_STEP_SUMMARY + echo "Application deployed to ${INPUTS_GITHUB_ENVIRONMENT} environment:" >> $GITHUB_STEP_SUMMARY + echo "**[$DEPLOY_URL]($DEPLOY_URL)**" >> $GITHUB_STEP_SUMMARY fi + + env: + INPUTS_GITHUB_ENVIRONMENT: ${{ inputs.github-environment }} + MATRIX_BRAND: ${{ matrix.brand }} + INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} + STEPS_S3_CONFIG_OUTPUTS_S3_PATH: ${{ steps.s3-config.outputs.s3-path }} + INPUTS_CACHE_STRATEGY: ${{ inputs.cache-strategy }} + NEEDS_PREPARE_OUTPUTS_DEPLOYMENT_URL: ${{ needs.prepare.outputs.deployment-url }} + INPUTS_PACKAGE_MANAGER: ${{ inputs.package-manager }} + INPUTS_BUILD_COMMAND: ${{ inputs.build-command }} + INPUTS_BUILD_DIRECTORY: ${{ inputs.build-directory }} diff --git a/docs/pwa-deployment.md b/docs/pwa-deployment.md index c048308..3726e3f 100644 --- a/docs/pwa-deployment.md +++ b/docs/pwa-deployment.md @@ -12,15 +12,31 @@ A comprehensive Progressive Web Application deployment workflow supporting S3 st - **Manual production gates**: Environment-based deployment protection - **Comprehensive caching**: Build artifact optimisation and cleanup +#### **GitHub Environment Variables and Secrets** + +Environment-specific values are read directly from the GitHub Environment (set via `github-environment`), rather than being passed as workflow inputs. Configure the following on each environment: + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| `S3_BUCKET` | variable | :white_check_mark: | S3 bucket name for deployment | +| `CLOUDFRONT_DISTRIBUTION_ID` | variable | :white_check_mark: | CloudFront distribution ID for cache invalidation | +| `AWS_REGION` | variable | :x: | AWS region (falls back to `aws-region` input) | +| **Static credentials** | | | | +| `AWS_ACCESS_KEY_ID` | variable | :white_check_mark: | AWS access key ID (required if not using OIDC) | +| `AWS_SECRET_ACCESS_KEY` | secret | :white_check_mark: | AWS secret access key (required if not using OIDC) | +| **OIDC** | | | | +| `AWS_ROLE_ARN` | variable | :white_check_mark: | IAM role ARN to assume via OIDC (alternative to static credentials) | + +Either `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` **or** `AWS_ROLE_ARN` must be configured. The workflow detects which to use automatically. + #### **Inputs** | Name | Required | Type | Default | Description | |------|----------|------|---------|-------------| -| **AWS Configuration** | -| aws-region | :x: | string | ap-southeast-2 | AWS region for deployment | -| s3-bucket | :white_check_mark: | string | | S3 bucket name for deployment | -| cloudfront-distribution-id | :white_check_mark: | string | | CloudFront distribution ID for cache invalidation | | **Environment Configuration** | -| environment | :x: | string | staging | Deployment environment (GitHub environment name for protection rules) | +| github-environment | :white_check_mark: | string | | GitHub Environment name for secrets/variables (e.g. Staging, Production) | +| **AWS Configuration** | +| aws-region | :x: | string | ap-southeast-2 | AWS region fallback (overridden by `AWS_REGION` environment variable if set) | +| role-session-name | :x: | string | | AWS role session name for OIDC (default: `{repo}-{short-sha}-{run-number}`) | | **Build Configuration** | | package-manager | :x: | string | yarn | Node package manager (yarn/npm) | | is-yarn-classic | :x: | boolean | false | Use Yarn Classic (pre-Berry) instead of modern Yarn | @@ -38,14 +54,6 @@ A comprehensive Progressive Web Application deployment workflow supporting S3 st | extra-sync-args | :x: | string | | Additional AWS S3 sync arguments | | **Debug and Control** | | debug | :x: | boolean | false | Enable verbose logging and debug output | -| skip-build | :x: | boolean | false | Skip the build step (use pre-built assets) | -| skip-tests | :x: | boolean | false | Skip test execution | - -#### **Secrets** -| Name | Required | Description | -|------|----------|-------------| -| aws-access-key-id | :white_check_mark: | AWS access key ID | -| aws-secret-access-key | :white_check_mark: | AWS secret access key | #### **Outputs** | Name | Description | @@ -55,21 +63,30 @@ A comprehensive Progressive Web Application deployment workflow supporting S3 st #### **Example Usage** -**Basic Production Deployment:** +**Basic Deployment (Static Credentials):** +```yaml +jobs: + deploy-staging: + uses: aligent/workflows/.github/workflows/pwa-deployment.yml@main + with: + github-environment: Staging + secrets: inherit +``` + +The `Staging` GitHub Environment must have `S3_BUCKET`, `CLOUDFRONT_DISTRIBUTION_ID`, `AWS_ACCESS_KEY_ID`, and `AWS_SECRET_ACCESS_KEY` configured. + +**Basic Deployment (OIDC):** ```yaml jobs: deploy-production: uses: aligent/workflows/.github/workflows/pwa-deployment.yml@main with: - s3-bucket: my-production-bucket - cloudfront-distribution-id: E1234567890ABC - environment: production - cache-strategy: immutable - secrets: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + github-environment: Production + secrets: inherit ``` +The `Production` GitHub Environment must have `S3_BUCKET`, `CLOUDFRONT_DISTRIBUTION_ID`, and `AWS_ROLE_ARN` configured. + **Preview Environment for Pull Requests:** ```yaml jobs: @@ -77,15 +94,11 @@ jobs: if: github.event_name == 'pull_request' uses: aligent/workflows/.github/workflows/pwa-deployment.yml@main with: - s3-bucket: my-preview-bucket - cloudfront-distribution-id: E1234567890ABC - environment: preview + github-environment: Preview preview-mode: true preview-base-url: https://preview.example.com cache-strategy: no-cache - secrets: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + secrets: inherit ``` **Multi-brand Deployment:** @@ -94,14 +107,10 @@ jobs: deploy-multi-brand: uses: aligent/workflows/.github/workflows/pwa-deployment.yml@main with: - s3-bucket: my-multi-brand-bucket - cloudfront-distribution-id: E1234567890ABC - environment: production + github-environment: Production brand-config: '{"brand":["brand-a","brand-b","brand-c"]}' build-command: build:brands - secrets: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + secrets: inherit ``` **Custom Build Configuration:** @@ -110,13 +119,12 @@ jobs: deploy-custom: uses: aligent/workflows/.github/workflows/pwa-deployment.yml@main with: - s3-bucket: my-custom-bucket - cloudfront-distribution-id: E1234567890ABC - environment: staging + github-environment: Staging package-manager: npm build-command: build:staging build-directory: build cloudfront-invalidation-paths: '["/*", "/api/*"]' extra-sync-args: --exclude "*.map" debug: true + secrets: inherit ```