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
157 changes: 157 additions & 0 deletions .github/workflows/workflow-deploy-to-s3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
name: Deploy Static Site to S3

on:
workflow_call:
inputs:
bucket:
description: "Destination S3 bucket name (without the s3:// prefix)."
required: true
type: string
source:
description: "Directory to sync to the S3 bucket."
required: false
default: public
type: string
aws-region:
description: "AWS region for S3/SES calls."
required: false
default: us-west-2
type: string
delete-extra-files:
description: "When true, remove objects from the bucket that are not present locally."
required: false
default: true
type: boolean
cloudflare-zone-id:
description: "Optional Cloudflare Zone ID for cache purge."
required: false
default: ""
type: string
purge-cloudflare:
description: "Purge the entire Cloudflare cache when a zone ID and API token are provided."
required: false
default: true
type: boolean
cloudflare-api-token:
description: "Optional Cloudflare API token with purge_cache permission."
required: false
default: ""
type: string
email-subject:
description: "Subject for the SES notification email (defaults to bucket name)."
required: false
default: ""
type: string
email-body:
description: "Body for the SES notification email."
required: false
default: ""
type: string
email-from:
description: "Sender address for SES notifications."
required: false
default: ""
type: string
email-to:
description: "Recipient address for SES notifications."
required: false
default: ""
type: string
secrets:
aws_access_key_id:
description: "AWS access key for S3/SES."
required: true
aws_secret_access_key:
description: "AWS secret key for S3/SES."
required: true
aws_session_token:
description: "Optional session token for temporary credentials."
required: false
outputs:
deployed:
description: "True when the sync step completed."
value: ${{ jobs.deploy.outputs.deployed }}

permissions:
contents: read

jobs:
deploy:
name: Sync static assets
runs-on: ubuntu-latest
outputs:
deployed: ${{ steps.sync.outputs.deployed }}
env:
AWS_REGION: ${{ inputs.aws-region }}
BUCKET: ${{ inputs.bucket }}
SOURCE_DIR: ${{ inputs.source }}
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.aws_access_key_id }}
aws-secret-access-key: ${{ secrets.aws_secret_access_key }}
aws-session-token: ${{ secrets.aws_session_token }}
aws-region: ${{ inputs.aws-region }}

- name: Sync directory to S3
id: sync
env:
DELETE_FLAG: ${{ inputs.delete-extra-files }}
run: |
set -euo pipefail
if [ ! -d "$SOURCE_DIR" ]; then
echo "Source directory '$SOURCE_DIR' does not exist." >&2
exit 1
fi

delete_arg=""
if [ "${DELETE_FLAG,,}" = "true" ]; then
delete_arg="--delete"
fi

aws s3 sync "$SOURCE_DIR" "s3://${BUCKET}" $delete_arg
echo "deployed=true" >> "$GITHUB_OUTPUT"

- name: Purge Cloudflare cache
if: ${{ inputs.purge-cloudflare && inputs.cloudflare-zone-id != '' && inputs.cloudflare-api-token != '' }}
env:
CLOUDFLARE_ZONE_ID: ${{ inputs.cloudflare-zone-id }}
CLOUDFLARE_API_TOKEN: ${{ inputs.cloudflare-api-token }}
run: |
set -euo pipefail
curl -X POST "https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/purge_cache" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'

- name: Send SES notification
if: ${{ inputs.email-from != '' && inputs.email-to != '' }}
env:
EMAIL_FROM: ${{ inputs.email-from }}
EMAIL_TO: ${{ inputs.email-to }}
CUSTOM_SUBJECT: ${{ inputs.email-subject }}
CUSTOM_BODY: ${{ inputs.email-body }}
run: |
set -euo pipefail

subject="${CUSTOM_SUBJECT}"
if [ -z "$subject" ]; then
subject="Deployment to ${BUCKET}"
fi

body="${CUSTOM_BODY}"
if [ -z "$body" ]; then
body="Static site synced to s3://${BUCKET} by ${GITHUB_ACTOR} (run ${GITHUB_RUN_ID}) in ${GITHUB_REPOSITORY}."
fi

aws ses send-email \
--from "$EMAIL_FROM" \
--destination "ToAddresses=$EMAIL_TO" \
--message "{
\"Subject\": {\"Data\": \"${subject}\", \"Charset\": \"utf8\"},
\"Body\": {\"Text\": {\"Data\": \"${body}\", \"Charset\": \"utf8\"}}
}"
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,51 @@ jobs:
gh_token: ${{ secrets.GITHUB_TOKEN }}
```

### `workflow-deploy-to-s3.yml`
Syncs a directory to an S3 bucket with optional Cloudflare cache purge and SES notification.

**Inputs**
- `bucket` (required): destination S3 bucket (without `s3://`).
- `source` (default `public`): local directory to sync.
- `aws-region` (default `us-west-2`): region for S3/SES calls.
- `delete-extra-files` (default `true`): remove objects not present locally.
- `cloudflare-zone-id` (optional): zone to purge after deploy.
- `purge-cloudflare` (default `true`): whether to purge the zone when credentials are provided.
- `cloudflare-api-token` (optional): Cloudflare token (pass a secret from the caller).
- `email-subject` (optional): SES email subject (defaults to the bucket name).
- `email-body` (optional): SES email body (defaults to an auto-generated message).
- `email-from` (optional): sender address for SES notifications (pass a secret from the caller).
- `email-to` (optional): recipient address for SES notifications (pass a secret from the caller).

**Secrets**
- `aws_access_key_id` (required)
- `aws_secret_access_key` (required)
- `aws_session_token` (optional)

**Outputs**
- `deployed`: `true` when the S3 sync completes.

**Example**
```yaml
jobs:
deploy-static:
needs: tests
uses: vinitu-net/github-workflows/.github/workflows/workflow-deploy-to-s3.yml@vX.Y.Z
with:
bucket: www.example.com
source: public
aws-region: us-west-2
delete-extra-files: true
cloudflare-zone-id: ${{ secrets.CLOUDFLARE_ZONE_ID }}
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
email-subject: "Site deployed"
email-from: ${{ secrets.EMAIL_FROM }}
email-to: ${{ secrets.EMAIL_TO }}
secrets:
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
```

### End-to-end usage in a caller repo
```yaml
jobs:
Expand Down