diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index a95d4ac..b1dee4c 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -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 @@ -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 }} @@ -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 @@ -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 @@ -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 }} @@ -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