Repository navigation
152 lines (138 loc) · 7.46 KB
/
Copy pathdeploy-gate.yml
File metadata and controls
152 lines (138 loc) · 7.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
name: Replicated deploy status
# Merge gate on every PR. Asserts the invariant behind the README badges:
# whatever should have triggered a deploy actually got a run, and that run
# succeeded. The "no run" case is what a badge cannot show -- GitHub renders the
# last real conclusion forever, so a workflow that stopped firing stays green.
#
# Required via the check context `last-deploy` in the `main` ruleset.
# No paths: filter -- a required check that never runs blocks every merge.
on:
pull_request:
# labeled/unlabeled: without them the override label never re-runs this.
types: [opened, synchronize, reopened, labeled, unlabeled]
env:
OVERRIDE_LABEL: deploy-gate-override
jobs:
last-deploy:
name: last-deploy
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
issues: write # repo-level label definitions are an Issues resource
steps:
# Upserted every run so it survives deletion; a label API blip must
# never redden the gate.
- name: Ensure override label exists
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
run: |
gh label create "$OVERRIDE_LABEL" --repo "$REPO" --color FBCA04 \
--description "Override label for fixing or bypassing the Replicated Deploy requirement" --force
# Not stripped on synchronize: this gate tracks main's lanes, not the
# PR's diff, so re-arming would force a re-label after every push.
- name: Break-glass override
if: contains(github.event.pull_request.labels.*.name, env.OVERRIDE_LABEL)
run: |
echo "::warning::deploy gate overridden by the ${OVERRIDE_LABEL} label"
{
echo "### Replicated deploy status: overridden"
echo ""
echo "Bypassed by the \`${OVERRIDE_LABEL}\` label"
} >> "$GITHUB_STEP_SUMMARY"
# Repo idiom: release-replicated-unstable.yml installs yq the same way.
- name: Install yq
if: ${{ !contains(github.event.pull_request.labels.*.name, env.OVERRIDE_LABEL) }}
uses: mikefarah/yq@v4
- name: Assert both lanes deployed their newest trigger
if: ${{ !contains(github.event.pull_request.labels.*.name, env.OVERRIDE_LABEL) }}
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR: ${{ github.event.pull_request.number }}
run: |
FAILED=0
{
echo "### Replicated deploy status"
echo ""
echo "| Lane | Trigger | State | Run |"
echo "| --- | --- | --- | --- |"
} >> "$GITHUB_STEP_SUMMARY"
check() { # <lane> <workflow file> <runs query> <trigger description>
local lane=$1 wf=$2 q=$3 trigger=$4 runs count run status conclusion url
runs=$(gh api "repos/${REPO}/actions/workflows/${wf}/runs?${q}")
count=$(jq -r .total_count <<<"$runs")
run=$(jq -c '.workflow_runs[0] // {}' <<<"$runs")
status=$(jq -r '.status // ""' <<<"$run")
conclusion=$(jq -r '.conclusion // ""' <<<"$run")
url=$(jq -r '.html_url // ""' <<<"$run")
if [ "$count" = 0 ]; then
echo "| ${lane} | ${trigger} | **no run** | — |" >> "$GITHUB_STEP_SUMMARY"
echo "::error::${lane}: no run of ${wf} for ${trigger} — the workflow never fired"
FAILED=1; return
fi
echo "| ${lane} | ${trigger} | ${status}/${conclusion:-n/a} | ${url} |" >> "$GITHUB_STEP_SUMMARY"
[ "$status" != completed ] && { echo "${lane}: still ${status}"; return; }
[ "$conclusion" = success ] && { echo "${lane}: ok"; return; }
gh api "repos/${REPO}/actions/runs/$(jq -r .id <<<"$run")/jobs" \
--jq '.jobs[] | select(.conclusion != "success" and .conclusion != null) | " failed job: \(.name) (\(.conclusion))"'
echo "::error::${lane}: ${conclusion} — ${url}"
FAILED=1
}
# Read the trigger paths from release-replicated-unstable.yml on main -- that
# workflow is the single definition. Read via yq, not python/PyYAML:
# YAML 1.1 parses a bare `on:` key as the boolean True.
gh api "repos/${REPO}/contents/.github/workflows/release-replicated-unstable.yml?ref=main" \
-H "Accept: application/vnd.github.raw" > /tmp/release-replicated-unstable.yml
# `replicated/**` -> `replicated`: the commits API takes a path prefix,
# not a glob. A surviving `*` would silently match nothing.
PATHS=$(yq -r '.on.push.paths[]' /tmp/release-replicated-unstable.yml | sed -E 's#/\*+$##; s#^\./##')
[ -n "$PATHS" ] || { echo "::error::could not read on.push.paths from release-replicated-unstable.yml"; exit 1; }
case "$PATHS" in
*'*'*) echo "::error::unsupported glob in on.push.paths: $PATHS"; exit 1 ;;
esac
echo "trigger paths: ${PATHS//$'\n'/ }"
NEWEST=$(for P in $PATHS; do
gh api "repos/${REPO}/commits?sha=main&path=${P}&per_page=1" \
--jq '.[0] | "\(.commit.committer.date)\t\(.sha)"' 2>/dev/null || true
done | sort -r | head -1)
U_SHA=${NEWEST##*$'\t'}
if [ -n "$U_SHA" ]; then
check unstable release-replicated-unstable.yml "branch=main&head_sha=${U_SHA}" "commit \`${U_SHA:0:8}\`"
else
echo "| unstable | no commit under ${PATHS//$'\n'/ } | skipped | — |" >> "$GITHUB_STEP_SUMMARY"
fi
# Beta fires on the openhands/* tag release-please creates. Tag refs sort
# alphabetically (0.9.0 > 0.49.0), so anchor on the newest *release*
# instead, and match runs by branch -- tag runs carry the tag as
# head_branch.
# per_page=100: three sibling release lines (crd-check, image-loader,
# openhands-secrets) interleave here. `openhands/` includes the slash,
# so it cannot match openhands-secrets/.
B_TAG=$(gh api "repos/${REPO}/releases?per_page=100" \
--jq 'map(select(.tag_name | startswith("openhands/"))) | .[0].tag_name // empty')
if [ -n "$B_TAG" ]; then
check beta release-replicated-beta.yml "branch=${B_TAG}" "tag \`${B_TAG}\`"
else
echo "| beta | no openhands/* release | skipped | — |" >> "$GITHUB_STEP_SUMMARY"
fi
if [ "$FAILED" = 1 ]; then
{
echo ""
echo "#### This check is about \`main\`, not your PR"
echo ""
echo "A replicated deploy is failing (beta or unstable). This gate blocks **every** open PR until it is healthy"
echo ""
echo "**Fix the deployment** — open the run linked in the table above, or ask the most recent person to merge a PR."
echo ""
echo "**Or merge anyway** — if this PR *is* the fix, or cannot wait, apply the override label:"
echo ""
echo " gh pr edit ${PR} --add-label ${OVERRIDE_LABEL}"
echo ""
echo "It re-runs this check green, is recorded on the PR, and stays until removed. Drop it if the lane recovers first."
} >> "$GITHUB_STEP_SUMMARY"
echo "::error::A Replicated deploy on main is unhealthy — this blocks all PRs and is not caused by your changes. Fix the deploy, or run: gh pr edit ${PR} --add-label ${OVERRIDE_LABEL}"
fi
exit "$FAILED"