Skip to content

Commit 9fa6266

Browse files
fix: address the review findings on the subgraph deploy and promote jobs
Three ways the promotion could move or clear the wrong thing: - a 'Synced:' line the CLI prints in any form this does not recognise was read as fully indexed, which moves the 'latest' tag onto a version that is still indexing and deletes the one serving traffic. Only '100%' or an explicit check mark counts now, and anything else means "not synced yet". - clearing the flag deleted whatever the variable held. A deployment that landed while the promotion was running now keeps its flag instead of being dropped silently, never promoted and never reported. - a tag listing that failed passed for 'no previous version', which reported the promotion as done while leaving the old version undeleted. It throws, and the flag survives for the next tick. Nothing bounded a deployment that neither indexed nor failed - a paused indexer, or a missing GOLDSKY_API_KEY_<ENV> failing the script before it can report an outcome - so the schedule picked it up every 30 minutes forever. A deployment now has PROMOTION_DEADLINE_HOURS (24h) to promote, after which the watch is given up on with an error and the flag is cleared. Also: - the production workflow takes the setup action from the ref it is running from: its workspace holds the release being deployed, whose '.github' is whatever that release shipped - the action may not be in it at all - the testing workflow falls back to BSNORG_ACTIONS_SECRET when no GH_TOKEN is passed, and checks it up front rather than deploying a version it then cannot flag, and which nothing would ever promote - the deployed version reaches 'gh variable set' through the environment, and is validated the way the schedule validates it before use - a variable that does not hold a version annotates and skips that subgraph rather than stopping every other network's promotion - the schedule derives the env key and the variable name from the environment and the network instead of carrying them as literal columns - the promotion installs the subgraph workspace and the root, not all nine - the '--env' choices move to 'deploy-envs.ts', shared with the Ormi script - the setup action's 'build' input no longer explains itself with the ./build directory, which is not what it produces Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 69ff8a3 commit 9fa6266

10 files changed

Lines changed: 217 additions & 97 deletions

File tree

.github/actions/setup-subgraph/action.yml

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@ description: "Install the monorepo dependencies and, when the built subgraph is
33

44
inputs:
55
build:
6-
description: "Build the monorepo. Only the deployment needs it, as 'goldsky subgraph deploy' uploads the ./build directory - tagging an already deployed version does not."
6+
description: "Run the monorepo build. The deployment needs the workspace packages the subgraph manifest and mappings pull in; the './build' directory 'goldsky subgraph deploy' uploads is not produced here, but by the 'graph build' that the 'deploy:<env>:<network>' npm script runs itself. Tagging an already deployed version needs neither."
77
required: false
88
default: "true"
9+
workspace:
10+
description: "Install this workspace and the repository root only, rather than every workspace of the monorepo. A job that just runs a script out of one package has no use for the others - and cannot build, as what a build would need is not installed."
11+
required: false
12+
default: ""
913

1014
runs:
1115
using: "composite"
@@ -28,7 +32,15 @@ runs:
2832
turbo-${{ runner.os }}
2933
- name: Install dependencies
3034
shell: bash
31-
run: npm ci
35+
env:
36+
WORKSPACE: ${{ inputs.workspace }}
37+
run: |
38+
set -euo pipefail
39+
if [ -n "$WORKSPACE" ]; then
40+
npm ci --workspace "$WORKSPACE" --include-workspace-root
41+
else
42+
npm ci
43+
fi
3244
- name: Build
3345
if: inputs.build == 'true'
3446
shell: bash

.github/workflows/deploy-prod-subgraph.yaml

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
workflow_dispatch:
55
inputs:
66
version:
7-
description: "The subgraph version to deploy. For example, 1.34.0. The given number should correspond to an existing tag @bosonprotocol/subgraph@<VERSION>"
7+
description: "The subgraph version to deploy. For example, 1.34.0. The given number should correspond to an existing tag @bosonprotocol/subgraph@<VERSION>. Only a release that already deploys on Goldsky can be deployed from here."
88
required: true
99

1010
permissions:
@@ -36,12 +36,22 @@ jobs:
3636
group: subgraph-deploy-production-${{ matrix.network }}
3737
cancel-in-progress: false
3838
steps:
39-
- uses: actions/checkout@v3
39+
- name: Check out the released version to deploy
40+
uses: actions/checkout@v3
4041
with:
4142
token: ${{ secrets.BSNORG_ACTIONS_SECRET }}
4243
ref: "@bosonprotocol/subgraph@${{ inputs.version }}"
4344
fetch-depth: "0"
44-
- uses: ./.github/actions/setup-subgraph
45+
# The workspace now holds a release, whose './.github' is whatever that release
46+
# shipped - a release older than the setup action does not carry it at all. So take
47+
# the action from the ref this workflow itself is running from, where it is
48+
# maintained, rather than from the workspace. v4 is what supports sparse-checkout.
49+
- name: Check out this workflow's own actions
50+
uses: actions/checkout@v4
51+
with:
52+
path: .workflow
53+
sparse-checkout: .github/actions
54+
- uses: ./.workflow/.github/actions/setup-subgraph
4555
# Deploys the new version and watches it for a few minutes, long enough to catch a
4656
# deployment that cannot index at all. The 'latest' tag is left on the version
4757
# currently served: moving it is the promotion schedule's business.
@@ -54,4 +64,14 @@ jobs:
5464
- name: Flag the deployment for promotion
5565
env:
5666
GH_TOKEN: ${{ secrets.BSNORG_ACTIONS_SECRET }}
57-
run: gh variable set ${{ matrix.variable }} --repo "$GITHUB_REPOSITORY" --body "${{ steps.deploy.outputs.version }}"
67+
VARIABLE: ${{ matrix.variable }}
68+
VERSION: ${{ steps.deploy.outputs.version }}
69+
run: |
70+
set -euo pipefail
71+
# The flag is read back into a job matrix and onto a command line, so never
72+
# write one that does not look like the version that was just deployed
73+
if [[ ! "$VERSION" =~ ^[A-Za-z0-9.+-]+$ ]]; then
74+
echo "::error::'$VERSION' does not look like a deployed version"
75+
exit 1
76+
fi
77+
gh variable set "$VARIABLE" --repo "$GITHUB_REPOSITORY" --body "$VERSION"

.github/workflows/deploy-staging-subgraph.yaml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ jobs:
3535
group: subgraph-deploy-staging-${{ matrix.network }}
3636
cancel-in-progress: false
3737
steps:
38-
- uses: actions/checkout@v3
38+
# A release runs the workflow, and so this action, off the released commit itself:
39+
# both come from the tag, so they are always the pair that was released together
40+
- name: Check out the released version to deploy
41+
uses: actions/checkout@v3
3942
with:
4043
token: ${{ secrets.BSNORG_ACTIONS_SECRET }}
4144
- uses: ./.github/actions/setup-subgraph
@@ -51,4 +54,14 @@ jobs:
5154
- name: Flag the deployment for promotion
5255
env:
5356
GH_TOKEN: ${{ secrets.BSNORG_ACTIONS_SECRET }}
54-
run: gh variable set ${{ matrix.variable }} --repo "$GITHUB_REPOSITORY" --body "${{ steps.deploy.outputs.version }}"
57+
VARIABLE: ${{ matrix.variable }}
58+
VERSION: ${{ steps.deploy.outputs.version }}
59+
run: |
60+
set -euo pipefail
61+
# The flag is read back into a job matrix and onto a command line, so never
62+
# write one that does not look like the version that was just deployed
63+
if [[ ! "$VERSION" =~ ^[A-Za-z0-9.+-]+$ ]]; then
64+
echo "::error::'$VERSION' does not look like a deployed version"
65+
exit 1
66+
fi
67+
gh variable set "$VARIABLE" --repo "$GITHUB_REPOSITORY" --body "$VERSION"

.github/workflows/deploy-testing-subgraph.yaml

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,21 @@ jobs:
4242
group: subgraph-deploy-testing-${{ matrix.network }}
4343
cancel-in-progress: false
4444
steps:
45+
# GH_TOKEN is only ever passed by the workflow that calls this one, so a manual run
46+
# falls back to the repository secret. Checked up front: a run that cannot flag its
47+
# deployment must not deploy one, as nothing would ever promote it.
48+
- name: Check the token the deployment is flagged with
49+
env:
50+
TOKEN: ${{ secrets.GH_TOKEN || secrets.BSNORG_ACTIONS_SECRET }}
51+
run: |
52+
set -euo pipefail
53+
if [ -z "$TOKEN" ]; then
54+
echo "::error::Neither the GH_TOKEN passed by the calling workflow nor the BSNORG_ACTIONS_SECRET repository secret is set"
55+
exit 1
56+
fi
4557
- uses: actions/checkout@v3
4658
with:
47-
token: ${{ secrets.GH_TOKEN }}
59+
token: ${{ secrets.GH_TOKEN || secrets.BSNORG_ACTIONS_SECRET }}
4860
- uses: ./.github/actions/setup-subgraph
4961
# Deploys the new version and watches it for a few minutes, long enough to catch a
5062
# deployment that cannot index at all. The 'latest' tag is left on the version
@@ -57,5 +69,15 @@ jobs:
5769
GOLDSKY_FATAL_ERROR_WATCH_MINUTES: "5"
5870
- name: Flag the deployment for promotion
5971
env:
60-
GH_TOKEN: ${{ secrets.GH_TOKEN }}
61-
run: gh variable set ${{ matrix.variable }} --repo "$GITHUB_REPOSITORY" --body "${{ steps.deploy.outputs.version }}"
72+
GH_TOKEN: ${{ secrets.GH_TOKEN || secrets.BSNORG_ACTIONS_SECRET }}
73+
VARIABLE: ${{ matrix.variable }}
74+
VERSION: ${{ steps.deploy.outputs.version }}
75+
run: |
76+
set -euo pipefail
77+
# The flag is read back into a job matrix and onto a command line, so never
78+
# write one that does not look like the version that was just deployed
79+
if [[ ! "$VERSION" =~ ^[A-Za-z0-9.+-]+$ ]]; then
80+
echo "::error::'$VERSION' does not look like a deployed version"
81+
exit 1
82+
fi
83+
gh variable set "$VARIABLE" --repo "$GITHUB_REPOSITORY" --body "$VERSION"

.github/workflows/promote-subgraph.yaml

Lines changed: 84 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ name: Promote deployed subgraphs
99
# is then cleared - as it is when the indexing failed, which fails the job so the failure
1010
# is reported. Nothing flagged means nothing to do, and the run costs a few seconds.
1111
#
12+
# A deployment that neither indexes nor fails is watched for PROMOTION_DEADLINE_HOURS and
13+
# given up on after that: a version nobody is ever going to promote gets reported once,
14+
# rather than checked over and over forever.
15+
#
1216
# To watch a deployment again - typically after a failed indexing has been fixed and
1317
# restarted - set the variable back to the deployed version (Settings > Secrets and
1418
# variables > Actions > Variables) and run this workflow.
@@ -22,6 +26,12 @@ on:
2226
permissions:
2327
contents: read
2428

29+
env:
30+
# Indexing a new version takes hours, and indexing a busy chain from scratch can take
31+
# most of a day, so this is deliberately wide: it is the point where a deployment is
32+
# declared never going to make it, not an indexing budget.
33+
PROMOTION_DEADLINE_HOURS: "24"
34+
2535
jobs:
2636
list-pending:
2737
name: List the deployments waiting for promotion
@@ -43,38 +53,51 @@ jobs:
4353
SUBGRAPH_PENDING_PRODUCTION_BASE: ${{ vars.SUBGRAPH_PENDING_PRODUCTION_BASE }}
4454
run: |
4555
set -euo pipefail
46-
# "<boson env>|<network>|<env key>|<label>", one per Goldsky hosted subgraph
56+
# "<boson env>|<network>|<label>", one per Goldsky hosted subgraph. Doubles as
57+
# the allow list a promotion job is built out of: the env key and the variable
58+
# name are derived from the first two fields, never spelled out a second time.
4759
subgraphs=(
48-
"testing|sepolia|TESTING|Sepolia (testing)"
49-
"testing|base|TESTING|Base Sepolia (testing)"
50-
"staging|sepolia|STAGING|Sepolia (staging)"
51-
"staging|base|STAGING|Base Sepolia (staging)"
52-
"production|ethereum|PRODUCTION|Ethereum (production)"
53-
"production|base|PRODUCTION|Base (production)"
60+
"testing|sepolia|Sepolia (testing)"
61+
"testing|base|Base Sepolia (testing)"
62+
"staging|sepolia|Sepolia (staging)"
63+
"staging|base|Base Sepolia (staging)"
64+
"production|ethereum|Ethereum (production)"
65+
"production|base|Base (production)"
5466
)
5567
entries=""
68+
malformed=""
5669
for subgraph in "${subgraphs[@]}"; do
57-
IFS="|" read -r boson_env network key label <<< "$subgraph"
70+
IFS="|" read -r boson_env network label <<< "$subgraph"
71+
key="${boson_env^^}"
5872
variable="SUBGRAPH_PENDING_${key}_${network^^}"
5973
version="${!variable:-}"
6074
if [ -z "$version" ]; then
6175
continue
6276
fi
6377
# The version goes into a JSON matrix and onto a command line, so only ever
64-
# trust one that still looks like the version the deployment flagged
78+
# trust one that still looks like the version the deployment flagged. Skip the
79+
# subgraph rather than stop here: the other networks have a deployment of
80+
# their own waiting, and this run is failed at the end anyway.
6581
if [[ ! "$version" =~ ^[A-Za-z0-9.+-]+$ ]]; then
6682
echo "::error::Repository variable $variable does not hold a version: $version"
67-
exit 1
83+
malformed="$malformed $variable"
84+
continue
6885
fi
6986
entry='{"boson_env":"'"$boson_env"'","network":"'"$network"'","key":"'"$key"'","label":"'"$label"'","variable":"'"$variable"'","version":"'"$version"'"}'
7087
entries="$entries${entries:+,}$entry"
7188
done
7289
echo "pending=[$entries]" | tee -a "$GITHUB_OUTPUT"
90+
if [ -n "$malformed" ]; then
91+
echo "Not holding a version:$malformed"
92+
exit 1
93+
fi
7394
7495
promote:
7596
name: Promote ${{ matrix.label }} subgraph
7697
needs: list-pending
77-
if: needs.list-pending.outputs.pending != '[]'
98+
# A malformed variable fails 'list-pending' only once it has listed the sound ones,
99+
# which are promoted all the same
100+
if: ${{ !cancelled() && needs.list-pending.outputs.pending != '' && needs.list-pending.outputs.pending != '[]' }}
78101
runs-on: ubuntu-latest
79102
timeout-minutes: 20
80103
strategy:
@@ -94,8 +117,10 @@ jobs:
94117
token: ${{ secrets.BSNORG_ACTIONS_SECRET }}
95118
- uses: ./.github/actions/setup-subgraph
96119
with:
97-
# Tagging an already deployed version does not need the built subgraph
120+
# Tagging an already deployed version runs a single script out of the subgraph
121+
# workspace: it needs neither the other workspaces nor the built subgraph
98122
build: "false"
123+
workspace: packages/subgraph
99124
# Reports 'promoted', 'pending' or 'failed' on its 'outcome' output. Anything else
100125
# going wrong - Goldsky being unreachable, say - leaves the output unset, so the
101126
# deployment stays flagged and is checked again on the next run.
@@ -107,7 +132,52 @@ jobs:
107132
- name: Clear the pending deployment flag
108133
# Either the 'latest' tag has been moved, or the version will never index: both
109134
# end the watch, and a failed indexing has already failed the step above
110-
if: always() && contains(fromJSON('["promoted", "failed"]'), steps.promote.outputs.outcome)
135+
if: ${{ !cancelled() && contains(fromJSON('["promoted", "failed"]'), steps.promote.outputs.outcome) }}
111136
env:
112137
GH_TOKEN: ${{ secrets.BSNORG_ACTIONS_SECRET }}
113-
run: gh variable delete "${{ matrix.variable }}" --repo "$GITHUB_REPOSITORY"
138+
VARIABLE: ${{ matrix.variable }}
139+
VERSION: ${{ matrix.version }}
140+
run: |
141+
set -euo pipefail
142+
# A deployment that landed while this job was running has flagged the version it
143+
# deployed: clearing the flag now would drop that one silently
144+
flagged=$(gh api "repos/$GITHUB_REPOSITORY/actions/variables/$VARIABLE" --jq ".value" 2>/dev/null || true)
145+
if [ "$flagged" != "$VERSION" ]; then
146+
echo "::notice::$VARIABLE holds '$flagged' and no longer '$VERSION' - leaving it to the next run"
147+
exit 0
148+
fi
149+
gh variable delete "$VARIABLE" --repo "$GITHUB_REPOSITORY"
150+
- name: Give up on a deployment that never promotes
151+
# Whatever left the flag in place: a version still indexing, or a step that failed
152+
# before reporting an outcome at all - a missing API key, a version deleted by
153+
# hand on Goldsky. Without this, that flag is picked up again every 30 minutes,
154+
# forever, and nothing ever says so.
155+
if: ${{ !cancelled() && !contains(fromJSON('["promoted", "failed"]'), steps.promote.outputs.outcome) }}
156+
env:
157+
GH_TOKEN: ${{ secrets.BSNORG_ACTIONS_SECRET }}
158+
VARIABLE: ${{ matrix.variable }}
159+
VERSION: ${{ matrix.version }}
160+
LABEL: ${{ matrix.label }}
161+
run: |
162+
set -euo pipefail
163+
flag=$(gh api "repos/$GITHUB_REPOSITORY/actions/variables/$VARIABLE" 2>/dev/null || true)
164+
if [ -z "$flag" ]; then
165+
echo "::notice::$VARIABLE is gone - nothing left to watch"
166+
exit 0
167+
fi
168+
flagged=$(jq -r ".value" <<< "$flag")
169+
if [ "$flagged" != "$VERSION" ]; then
170+
echo "::notice::$VARIABLE holds '$flagged' and no longer '$VERSION' - leaving it to the next run"
171+
exit 0
172+
fi
173+
# The flag is written once, by the deployment that raised it, so how long it has
174+
# been waiting is how long ago it was last written
175+
flagged_at=$(jq -r ".updated_at" <<< "$flag")
176+
waited_hours=$(( ( $(date -u +%s) - $(date -u -d "$flagged_at" +%s) ) / 3600 ))
177+
if [ "$waited_hours" -lt "$PROMOTION_DEADLINE_HOURS" ]; then
178+
echo "::notice::$LABEL $VERSION has been waiting for promotion for ${waited_hours}h - checked again on the next run"
179+
exit 0
180+
fi
181+
echo "::error::$LABEL $VERSION has been waiting for promotion for ${waited_hours}h, past the ${PROMOTION_DEADLINE_HOURS}h deadline - giving up on it. Once the deployment is fixed, set $VARIABLE back to a deployed version to watch it again."
182+
gh variable delete "$VARIABLE" --repo "$GITHUB_REPOSITORY"
183+
exit 1
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Option } from "commander";
2+
3+
/** Deployed environments (Boson env + chain) the deployment scripts accept */
4+
const deployEnvs = [
5+
"testing_amoy",
6+
"testing_sepolia",
7+
"testing_base",
8+
"testing_optimism",
9+
"testing_arbitrum",
10+
"staging_amoy",
11+
"staging_sepolia",
12+
"staging_base",
13+
"staging_optimism",
14+
"staging_arbitrum",
15+
"production_polygon",
16+
"production_ethereum",
17+
"production_base",
18+
"production_optimism",
19+
"production_arbitrum"
20+
];
21+
22+
/**
23+
* The `--env` option, shared by every script that acts on a deployed environment - the
24+
* Goldsky deployment and promotion, and the Ormi post-deployment. A fresh `Option` is
25+
* returned on every call, as commander binds one to the program it is added to.
26+
*/
27+
export function envOption(): Option {
28+
return new Option(
29+
"--env <ENV>",
30+
`Deployed environment (Boson env + chain): "testing_amoy", "testing_sepolia", ...`
31+
)
32+
.makeOptionMandatory(true)
33+
.choices(deployEnvs);
34+
}

packages/subgraph/scripts/deploy-on-goldsky.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { program } from "commander";
2+
import { envOption } from "./deploy-envs";
23
import {
34
DeploymentStatus,
4-
envOption,
55
getDeploymentStatus,
66
goldsky,
77
hasFailed,

0 commit comments

Comments
 (0)