Skip to content
Merged
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
83 changes: 61 additions & 22 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
lfs: true
# On PR events, check out the PR head (not the merge commit) so the
Expand All @@ -55,7 +55,7 @@ jobs:
echo "BUCKET=${BUCKET}" >> $GITHUB_OUTPUT

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Expand Down Expand Up @@ -108,33 +108,55 @@ jobs:
FQDN="${{ steps.names.outputs.BRANCH }}.${{ steps.zone.outputs.NAME }}"
CONTENT="${{ steps.names.outputs.BUCKET }}.s3-website.${{ secrets.AWS_REGION }}.amazonaws.com"

EXISTING=$(curl --fail-with-body -sS -X GET \
EXISTING_RESP=$(curl --fail-with-body -sS -X GET \
"https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records?type=CNAME&name=${FQDN}" \
-H "X-Auth-Email: ${{ secrets.EMAIL }}" \
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_DNS_SECRET_API_TOKEN }}" \
-H "Content-Type: application/json" | jq -r '.result[0].id // empty')
-H "Content-Type: application/json")
EXISTING=$(echo "$EXISTING_RESP" | jq -r '.result[0].id // empty')
echo "GET response (id=${EXISTING:-none}):"
echo "$EXISTING_RESP" | jq -c '{success, errors, messages, count: (.result | length)}'

PAYLOAD=$(jq -n \
--arg name "${{ steps.names.outputs.BRANCH }}" \
--arg content "$CONTENT" \
'{type:"CNAME", name:$name, content:$content, ttl:1, proxied:true}')
echo "Payload: $PAYLOAD"

if [ -n "$EXISTING" ]; then
echo "Updating existing CNAME (id=$EXISTING) for ${FQDN}"
curl --fail-with-body -sS -X PUT \
"https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records/$EXISTING" \
-H "X-Auth-Email: ${{ secrets.EMAIL }}" \
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_DNS_SECRET_API_TOKEN }}" \
-H "Content-Type: application/json" \
--data "$PAYLOAD" > /dev/null
else
if [ -z "$EXISTING" ]; then
echo "Creating new CNAME for ${FQDN}"
curl --fail-with-body -sS -X POST \
POST_RESP=$(curl --fail-with-body -sS -X POST \
"https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records" \
-H "X-Auth-Email: ${{ secrets.EMAIL }}" \
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_DNS_SECRET_API_TOKEN }}" \
-H "Content-Type: application/json" \
--data "$PAYLOAD" > /dev/null
--data "$PAYLOAD")
if ! echo "$POST_RESP" | jq -e '.success == true' > /dev/null; then
echo "::error::Cloudflare DNS POST returned success=false"
echo "$POST_RESP" | jq .
exit 1
fi
EXISTING=$(echo "$POST_RESP" | jq -r '.result.id')
echo "Created record id=$EXISTING"
fi

# Always finish with a PUT against the record id. For a pre-existing
# record this is just the normal update. For a just-POSTed record
# this works around a Cloudflare quirk where proxied CNAMEs created
# by POST can linger in the control plane without being published to
# the public DNS layer until a subsequent modification nudges them.
echo "Updating CNAME (id=$EXISTING) for ${FQDN}"
RESP=$(curl --fail-with-body -sS -X PUT \
"https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records/$EXISTING" \
-H "X-Auth-Email: ${{ secrets.EMAIL }}" \
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_DNS_SECRET_API_TOKEN }}" \
-H "Content-Type: application/json" \
--data "$PAYLOAD")
echo "PUT response:"
echo "$RESP" | jq -c '{success, errors, messages}'
if ! echo "$RESP" | jq -e '.success == true' > /dev/null; then
echo "::error::Cloudflare DNS PUT returned success=false"
exit 1
fi

# Scope the purge to just the hosts this deploy actually touched so we
Expand All @@ -151,12 +173,17 @@ jobs:
'[$fqdn]')
fi
echo "Purging hosts: $HOSTS"
curl --fail-with-body -sS -X POST \
RESP=$(curl --fail-with-body -sS -X POST \
"https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/purge_cache" \
-H "X-Auth-Email: ${{ secrets.EMAIL }}" \
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_DNS_SECRET_API_TOKEN }}" \
-H "Content-Type: application/json" \
--data "{\"hosts\": $HOSTS}" > /dev/null
--data "{\"hosts\": $HOSTS}")
echo "$RESP" | jq -c '{success, errors, messages}'
if ! echo "$RESP" | jq -e '.success == true' > /dev/null; then
echo "::error::Cloudflare purge returned success=false"
exit 1
fi

cleanup:
# Runs when a PR is closed (merged or not). Tears down the preview bucket
Expand All @@ -169,7 +196,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Expand Down Expand Up @@ -213,19 +240,31 @@ jobs:
- name: Delete preview Cloudflare DNS record
run: |
FQDN="${{ steps.names.outputs.BRANCH }}.${{ steps.zone.outputs.NAME }}"
RECORD_ID=$(curl --fail-with-body -sS -X GET \

LOOKUP_RESP=$(curl --fail-with-body -sS -X GET \
"https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records?type=CNAME&name=${FQDN}" \
-H "X-Auth-Email: ${{ secrets.EMAIL }}" \
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_DNS_SECRET_API_TOKEN }}" \
-H "Content-Type: application/json" | jq -r '.result[0].id // empty')
-H "Content-Type: application/json")
echo "$LOOKUP_RESP" | jq -c '{success, errors, messages}'
if ! echo "$LOOKUP_RESP" | jq -e '.success == true' > /dev/null; then
echo "::error::Cloudflare DNS lookup returned success=false"
exit 1
fi
RECORD_ID=$(echo "$LOOKUP_RESP" | jq -r '.result[0].id // empty')

if [ -n "$RECORD_ID" ]; then
echo "Deleting CNAME ${FQDN} (id=$RECORD_ID)"
curl --fail-with-body -sS -X DELETE \
DEL_RESP=$(curl --fail-with-body -sS -X DELETE \
"https://api.cloudflare.com/client/v4/zones/${{ secrets.CLOUDFLARE_ZONE_ID }}/dns_records/$RECORD_ID" \
-H "X-Auth-Email: ${{ secrets.EMAIL }}" \
-H "Authorization: Bearer ${{ secrets.CLOUDFLARE_DNS_SECRET_API_TOKEN }}" \
-H "Content-Type: application/json" > /dev/null
-H "Content-Type: application/json")
echo "$DEL_RESP" | jq -c '{success, errors, messages}'
if ! echo "$DEL_RESP" | jq -e '.success == true' > /dev/null; then
echo "::error::Cloudflare DNS DELETE returned success=false"
exit 1
fi
else
echo "No CNAME for ${FQDN}, skipping."
fi