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
96 changes: 96 additions & 0 deletions .github/workflows/netlify-ops.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Netlify Operations

on:
workflow_dispatch:
inputs:
operation:
description: "Operation to perform"
required: true
type: choice
options:
- restore-devsy-sh
- find-dl-site

jobs:
restore-devsy-sh:
name: Restore devsy.sh Production Deploy
if: inputs.operation == 'restore-devsy-sh'
runs-on: ubuntu-latest
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
steps:
- name: Find last ready deploy and restore
run: |
echo "Fetching last 100 deploys to find a ready one..."
DEPLOYS=$(curl -s -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites/$NETLIFY_SITE_ID/deploys?per_page=100")

DEPLOY_ID=$(echo "$DEPLOYS" | jq -r '[.[] | select(.state == "ready")] | .[0].id')
DEPLOY_INFO=$(echo "$DEPLOYS" | jq -r '[.[] | select(.state == "ready")] | .[0] | {id, created_at, deploy_source, title}')

if [ "$DEPLOY_ID" = "null" ] || [ -z "$DEPLOY_ID" ]; then
echo "ERROR: No ready deploy found in last 100. States seen:"
echo "$DEPLOYS" | jq '[.[] | .state] | group_by(.) | map({state: .[0], count: length})'
exit 1
fi

echo "Restoring deploy: $DEPLOY_INFO"

RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites/$NETLIFY_SITE_ID/deploys/$DEPLOY_ID/restore")

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
echo "ERROR: Restore failed (HTTP $HTTP_CODE)"
echo "$BODY"
exit 1
fi
echo "Restore succeeded (HTTP $HTTP_CODE)"
sleep 15

STATUS=$(curl -s -o /dev/null -w "%{http_code}" "https://devsy.sh")
echo "devsy.sh HTTP status: $STATUS"
[ "$STATUS" = "200" ] && echo "SUCCESS: devsy.sh is back online" || echo "WARNING: Got $STATUS"

find-dl-site:
name: Find or Create dl.devsy.sh Netlify Site
if: inputs.operation == 'find-dl-site'
runs-on: ubuntu-latest
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
steps:
- name: Find or create dl.devsy.sh site and output ID
run: |
echo "Listing Netlify sites..."
SITES=$(curl -s -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites?per_page=100")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

What is the maximum per_page limit for the Netlify API /sites endpoint and does it support pagination?

💡 Result:

The Netlify API /sites endpoint supports pagination, and the maximum limit for the per_page parameter is 100 items per request [1][2]. Requests that return more than 100 items are paginated by default [2]. You can control the number of results returned per page using the per_page query parameter, up to a maximum of 100 [1][2]. If you specify a value higher than 100, the API will typically cap the response at 100 items [1]. You can navigate through the results using the page query parameter (which is 1-based) [2], and pagination information is provided in the Link header of the API response [2].

Citations:


🏁 Script executed:

# Read the netlify-ops.yml file to see the actual implementation
cat -n .github/workflows/netlify-ops.yml | sed -n '60,100p'

Repository: devsy-org/devsy

Length of output: 2078


Implement pagination to fetch all sites beyond the first 100.

The current code fetches only the first 100 Netlify sites. If the organization has more than 100 sites, the dl.devsy.sh site could be missed, resulting in a duplicate site being created. The Netlify API supports pagination via the page query parameter—implement it to handle organizations with more than 100 sites.

🔄 Example pagination approach
-SITES=$(curl -s -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
-  "https://api.netlify.com/api/v1/sites?per_page=100")
+# Fetch all sites with pagination
+PAGE=1
+ALL_SITES="[]"
+while true; do
+  SITES=$(curl -s -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
+    "https://api.netlify.com/api/v1/sites?per_page=100&page=$PAGE")
+  if [ "$(echo "$SITES" | jq 'length')" -eq 0 ]; then
+    break
+  fi
+  ALL_SITES=$(echo "$ALL_SITES" "$SITES" | jq -s 'add')
+  PAGE=$((PAGE + 1))
+done
+SITES="$ALL_SITES"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/netlify-ops.yml at line 70, The current Netlify fetch uses
a single-page call to "https://api.netlify.com/api/v1/sites?per_page=100" and
must be changed to iterate pages until no more results; modify the workflow step
that calls that URL to loop (or paginate) using the `page` query parameter
(e.g., `?per_page=100&page=<n>`), accumulate/concat results from each page, and
stop when a page returns an empty array or fewer than `per_page` items; ensure
subsequent logic that looks for the dl.devsy.sh site operates over the combined
list of sites instead of just the first page.


DL_SITE_ID=$(echo "$SITES" | \
jq -r '.[] | select(.custom_domain == "dl.devsy.sh" or .name == "dl-devsy-sh") | .id' | head -1)

if [ -n "$DL_SITE_ID" ] && [ "$DL_SITE_ID" != "null" ]; then
DL_SITE_NAME=$(echo "$SITES" | jq -r ".[] | select(.id == \"$DL_SITE_ID\") | .name")
echo "Found existing site: $DL_SITE_NAME"
else
echo "Site not found. Listing all sites for reference:"
echo "$SITES" | jq '.[] | {id, name, custom_domain, url}' | head -100
echo ""
echo "Creating dl-devsy-sh..."
CREATE_RESP=$(curl -s -X POST \
-H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "dl-devsy-sh"}' \
"https://api.netlify.com/api/v1/sites")
DL_SITE_ID=$(echo "$CREATE_RESP" | jq -r '.id')
echo "Created: $(echo "$CREATE_RESP" | jq '{id, name, url}')"
Comment on lines +83 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add error handling for site creation API call.

The site creation API call has no error handling. If the API returns an error or the response doesn't contain a valid id, DL_SITE_ID will be null or empty, causing lines 94-96 to output invalid instructions. The workflow would succeed but provide incorrect information.

🛡️ Proposed fix to validate creation response
 CREATE_RESP=$(curl -s -X POST \
   -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
   -H "Content-Type: application/json" \
   -d '{"name": "dl-devsy-sh"}' \
   "https://api.netlify.com/api/v1/sites")
 DL_SITE_ID=$(echo "$CREATE_RESP" | jq -r '.id')
+
+if [ "$DL_SITE_ID" = "null" ] || [ -z "$DL_SITE_ID" ]; then
+  echo "ERROR: Failed to create site"
+  echo "$CREATE_RESP" | jq '.'
+  exit 1
+fi
 echo "Created: $(echo "$CREATE_RESP" | jq '{id, name, url}')"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/netlify-ops.yml around lines 83 - 89, The CREATE_RESP from
the curl POST to Netlify and the derived DL_SITE_ID are not validated; update
the workflow to check the HTTP result and that DL_SITE_ID (extracted via jq -r
'.id') is a non-empty, non-null value after the curl call, and if it is missing
or the API returned an error, echo a clear error message including CREATE_RESP
and exit with a non-zero status so subsequent steps (that use DL_SITE_ID) do not
run; reference the CREATE_RESP variable, the curl POST block, and DL_SITE_ID
extraction/jq call when implementing the validation and early exit.

fi

echo ""
echo "========================================"
echo "NETLIFY_DL_SITE_ID = $DL_SITE_ID"
echo "========================================"
echo "Run: gh secret set NETLIFY_DL_SITE_ID --org devsy-org --visibility all --body \"$DL_SITE_ID\""
Loading