Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 101 additions & 26 deletions .github/workflows/pwa-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,22 @@ on:
s3-bucket:
description: "S3 bucket name for deployment"
type: string
required: true
required: false
cloudfront-distribution-id:
description: "CloudFront distribution ID for cache invalidation"
type: string
required: true
required: false
role-session-name:
description: "AWS role session name for OIDC authentication (default: {repo}-{short-sha}-{run-number})"
type: string
required: false
default: ""

# Environment Configuration
environment:
description: "Deployment environment (GitHub environment name for protection rules)"
description: "GitHub Environment name for secrets/variables (e.g. Staging, Production)"
type: string
required: false
default: "staging"
required: true

# Build Configuration
package-manager:
Expand Down Expand Up @@ -92,14 +96,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"
Expand All @@ -113,14 +109,83 @@ jobs:
prepare:
name: 🔍 Prepare Deployment
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
outputs:
cache-control-static: ${{ steps.cache-config.outputs.cache-control-static }}
cache-control-html: ${{ steps.cache-config.outputs.cache-control-html }}
s3-prefix: ${{ steps.deployment-config.outputs.s3-prefix }}
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.environment }}
S3_BUCKET: ${{ vars.S3_BUCKET }} || ${{ inputs.s3-bucket }}
CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} || ${{ inputs.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: |
Expand Down Expand Up @@ -196,7 +261,7 @@ jobs:
fi
env:
INPUTS_PREVIEW_BASE_URL: ${{ inputs.preview-base-url }}
INPUTS_S3_BUCKET: ${{ inputs.s3-bucket }}
INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} || ${{ inputs.s3-bucket }}

- name: Configure multi-brand matrix
id: brand-config
Expand All @@ -212,11 +277,14 @@ jobs:
env:
INPUTS_BRAND_CONFIG: ${{ inputs.brand-config }}

# Build and test the application
# 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 }}
url: ${{ needs.prepare.outputs.deployment-url }}
Expand Down Expand Up @@ -301,9 +369,17 @@ jobs:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 #v6.0.0
with:
aws-access-key-id: ${{ secrets.aws-access-key-id }}
aws-secret-access-key: ${{ secrets.aws-secret-access-key }}
aws-region: ${{ inputs.aws-region }}
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 (OIDC)
if: needs.prepare.outputs.auth-mode == 'oidc'
uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 #v6.0.0
with:
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
Expand All @@ -319,7 +395,7 @@ jobs:
env:
NEEDS_PREPARE_OUTPUTS_S3_PREFIX: ${{ needs.prepare.outputs.s3-prefix }}
MATRIX_BRAND: ${{ matrix.brand }}
INPUTS_S3_BUCKET: ${{ inputs.s3-bucket }}
INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} || ${{ inputs.s3-bucket }}

- name: Deploy static assets to S3
run: |
Expand All @@ -336,7 +412,7 @@ jobs:
${INPUTS_EXTRA_SYNC_ARGS}
env:
INPUTS_BUILD_DIRECTORY: ${{ inputs.build-directory }}
INPUTS_S3_BUCKET: ${{ inputs.s3-bucket }}
INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} || ${{ inputs.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 }}
Expand All @@ -356,7 +432,7 @@ jobs:
${INPUTS_EXTRA_SYNC_ARGS}
env:
INPUTS_BUILD_DIRECTORY: ${{ inputs.build-directory }}
INPUTS_S3_BUCKET: ${{ inputs.s3-bucket }}
INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} || ${{ inputs.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 }}
Expand Down Expand Up @@ -384,7 +460,7 @@ jobs:
echo "Invalidating paths: $PATHS"

INVALIDATION_ID=$(aws cloudfront create-invalidation \
--distribution-id "${INPUTS_CLOUDFRONT_DISTRIBUTION_ID}" \
--distribution-id "${CLOUDFRONT_DISTRIBUTION_ID}" \
--paths $PATHS \
--query 'Invalidation.Id' \
--output text)
Expand All @@ -394,16 +470,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 "${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: ${{ inputs.cloudfront-distribution-id }}

CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }} || ${{ inputs.cloudfront-distribution-id }}
- name: Generate deployment summary
run: |
echo "## 🚀 Deployment Summary" >> $GITHUB_STEP_SUMMARY
Expand Down Expand Up @@ -437,7 +512,7 @@ jobs:
env:
INPUTS_ENVIRONMENT: ${{ inputs.environment }}
MATRIX_BRAND: ${{ matrix.brand }}
INPUTS_S3_BUCKET: ${{ inputs.s3-bucket }}
INPUTS_S3_BUCKET: ${{ vars.S3_BUCKET }} || ${{ inputs.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 }}
Expand Down
80 changes: 44 additions & 36 deletions docs/pwa-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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 |
Expand All @@ -55,37 +63,42 @@ 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:
deploy-preview:
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:**
Expand All @@ -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:**
Expand All @@ -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
```