name: React - Deploy to S3
on: workflow_call: inputs: runner: description: 'Runner type' required: false type: string default: 'ubuntu-latest' environment: description: 'GitHub environment (develop, staging, production)' required: false type: string default: 'develop' build_output_dir: description: 'Build output directory name (must match build artifact)' required: false type: string default: 'build'
aws_role_to_assume: description: >- IAM role ARN to assume via GitHub OIDC. When set, no static access key is sent and AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY are not read. Empty keeps the static-key path, which is the default so that existing consumers keep working. required: false type: string default: ''
secrets: AWS_ACCESS_KEY_ID: required: true AWS_SECRET_ACCESS_KEY: required: true AWS_REGION: required: true AWS_S3_BUCKET: required: true AWS_CLOUDFRONT_DISTRIBUTION_ID: required: false
outputs: deploy_status: description: 'Deployment status' value: ${{ jobs.deploy.outputs.status }} s3_bucket: description: 'S3 bucket deployed to' value: ${{ jobs.deploy.outputs.bucket }}
# Least privilege for what this workflow actually does. Without it the# job inherits whatever the calling repository's default happens to be,# which on older repositories is write-all.permissions: contents: read # Inert unless aws_role_to_assume is set. A reusable workflow cannot # exceed its own declaration, so the OIDC path needs it declared here. id-token: write
jobs: deploy: name: Deploy to S3 runs-on: ${{ inputs.runner }} environment: ${{ inputs.environment }} timeout-minutes: 15
outputs: status: ${{ steps.deploy.outputs.status }} bucket: ${{ steps.deploy.outputs.bucket }}
steps: - name: Download build artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: build-output-${{ github.run_id }} path: ${{ inputs.build_output_dir }}
- name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0 with: # One step, two paths. A long-lived access key in a repository secret # is a credential nobody rotates and everybody with write access can # exfiltrate; OIDC mints a token scoped to this repository that # expires with the job. The keys are only sent when no role is given, # because passing both makes the action assume the role *with* the # static key and the key stays in play. role-to-assume: ${{ inputs.aws_role_to_assume }} aws-access-key-id: ${{ inputs.aws_role_to_assume == '' && secrets.AWS_ACCESS_KEY_ID || '' }} aws-secret-access-key: ${{ inputs.aws_role_to_assume == '' && secrets.AWS_SECRET_ACCESS_KEY || '' }} aws-region: ${{ secrets.AWS_REGION }}
- name: Deploy to S3 id: deploy env: S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }} CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.AWS_CLOUDFRONT_DISTRIBUTION_ID }} run: | echo "Deploying to S3 bucket: $S3_BUCKET"
# Sync build output to S3 (delete stale files) aws s3 sync ${{ inputs.build_output_dir }} "s3://${S3_BUCKET}" --delete
echo "bucket=$S3_BUCKET" >> $GITHUB_OUTPUT
# CloudFront cache invalidation (optional) if [ -n "$CLOUDFRONT_DISTRIBUTION_ID" ]; then echo "Invalidating CloudFront distribution: $CLOUDFRONT_DISTRIBUTION_ID" INVALIDATION_ID=$(aws cloudfront create-invalidation \ --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" \ --paths "/*" \ --query 'Invalidation.Id' \ --output text) echo "Invalidation created: $INVALIDATION_ID" echo "invalidation_id=$INVALIDATION_ID" >> $GITHUB_OUTPUT else echo "No CloudFront distribution ID provided, skipping invalidation" fi
echo "status=success" >> $GITHUB_OUTPUT
# Summary echo "### Deployment Completed" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY echo "| **S3 Bucket** | $S3_BUCKET |" >> $GITHUB_STEP_SUMMARY echo "| **Environment** | ${{ inputs.environment }} |" >> $GITHUB_STEP_SUMMARY if [ -n "$CLOUDFRONT_DISTRIBUTION_ID" ]; then echo "| **CloudFront** | $CLOUDFRONT_DISTRIBUTION_ID |" >> $GITHUB_STEP_SUMMARY echo "| **Invalidation** | $INVALIDATION_ID |" >> $GITHUB_STEP_SUMMARY fi echo "| **Source** | ${{ inputs.build_output_dir }} |" >> $GITHUB_STEP_SUMMARYReact·Reusable workflow·on: workflow_call
React Deploy S3
React - Deploy to S3
.github/workflows/react-deploy-s3.yml