-
Notifications
You must be signed in to change notification settings - Fork 17
263 lines (237 loc) · 14.7 KB
/
Copy pathcreate-release-branch.yml
File metadata and controls
263 lines (237 loc) · 14.7 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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
name: Create Release Branch and PR
on:
workflow_dispatch:
jobs:
create-release-branch:
runs-on: ubuntu-latest
steps:
- name: Check out the code
uses: actions/checkout@v6
with:
ref: dev
- name: Get release date and branch name
run: |
RELEASE_DATE=$(date +'%Y-%m-%d')
echo "RELEASE_DATE=$RELEASE_DATE" >> $GITHUB_ENV
echo "BRANCH_NAME=release/$RELEASE_DATE" >> $GITHUB_ENV
- name: Create or update branch
run: |
git checkout -B $BRANCH_NAME
git push --force --set-upstream origin $BRANCH_NAME
- name: Create pull request using GitHub CLI
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Create PR if it doesn't already exist
if ! gh pr view "$BRANCH_NAME" --json number &>/dev/null; then
gh pr create \
--base main \
--head "$BRANCH_NAME" \
--title "release: $RELEASE_DATE" \
--body ":rocket: Automated release PR"
fi
# Always ensure labels are applied (handles 502 / retry scenarios)
gh pr edit "$BRANCH_NAME" --add-label "release,auto-pr"
- name: Comment build links on the PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PUBLIC_URL_PREFIX: ${{ vars.EXPLORER_TEAM_S3_BUCKET_PUBLIC_URL }}
run: |
set -euo pipefail
# Bot-created PRs trigger no workflows, so no build runs and no badge
# comment gets posted. The release branch is cut from dev tip, whose
# SHA was already built by the push to dev — reuse that run's
# artifacts instead of rebuilding.
PR_NUMBER=$(gh pr view "$BRANCH_NAME" --json number --jq '.number')
EXISTING=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments?per_page=100" \
--jq '[.[] | select(.user.login == "github-actions[bot]") | select(.body | contains("img.shields.io/badge/Build"))] | .[0] // empty')
# A "Build Not Found" comment is upgraded in place on re-run once the
# build exists; only a success comment short-circuits.
if [ -n "$EXISTING" ] && jq -e '.body | contains("Build-Success")' <<< "$EXISTING" > /dev/null; then
echo "Build links comment already present, skipping."
exit 0
fi
COMMENT_ID=$([ -n "$EXISTING" ] && jq -r '.id' <<< "$EXISTING" || echo "")
post_comment() {
if [ -n "$COMMENT_ID" ]; then
gh api -X PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" -F body=@comment.md > /dev/null
else
gh pr comment "$BRANCH_NAME" --body-file comment.md
fi
}
HEAD_SHA=$(git rev-parse HEAD)
SHORT_SHA="${HEAD_SHA:0:7}"
REPO_NAME="${GITHUB_REPOSITORY#*/}"
# Scoped to the build workflow file — a repo-wide head_sha listing
# gets crowded out by bot runs sharing the SHA and the build falls
# past the first page.
RUN_JSON=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/build-unitycloud.yml/runs?head_sha=${HEAD_SHA}&status=success&per_page=20" \
--jq '[.workflow_runs[] | select(.event == "pull_request" or .event == "push")] | .[0]')
if [ -z "$RUN_JSON" ] || [ "$RUN_JSON" = "null" ]; then
{
echo '![badge] <img src="https://ui.decentraland.org/decentraland_256x256.png" width="30">'
echo ""
echo "No completed Unity Cloud Build found for \`${SHORT_SHA}\` — check the [build runs](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/workflows/build-unitycloud.yml) and re-run this workflow once one finishes."
echo ""
echo "[badge]: https://img.shields.io/badge/Build-Not%20Found-yellow?logo=github&style=for-the-badge"
} > comment.md
post_comment
exit 0
fi
RUN_ID=$(jq -r '.id' <<< "$RUN_JSON")
RUN_NUMBER=$(jq -r '.run_number' <<< "$RUN_JSON")
SUITE_ID=$(jq -r '.check_suite_id' <<< "$RUN_JSON")
RUN_EVENT=$(jq -r '.event' <<< "$RUN_JSON")
RUN_BRANCH=$(jq -r '.head_branch' <<< "$RUN_JSON")
BUILD_DATE=$(jq -r '.updated_at' <<< "$RUN_JSON")
# Same event -> S3 prefix mapping as build-unitycloud.yml
case "$RUN_EVENT" in
pull_request) BUILD_PREFIX="pr" ;;
push) BUILD_PREFIX="pu" ;;
merge_group) BUILD_PREFIX="mg" ;;
workflow_dispatch) BUILD_PREFIX="wd" ;;
workflow_call) BUILD_PREFIX="wc" ;;
schedule) BUILD_PREFIX="sc" ;;
*) BUILD_PREFIX="gn" ;;
esac
S3_PATH="@dcl/${REPO_NAME}/branch/${RUN_BRANCH}/${BUILD_PREFIX}-${RUN_NUMBER}-${SHORT_SHA}"
WINDOWS_ARTIFACT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}/artifacts" \
--jq '.artifacts[] | select(.name == "Decentraland_windows64") | .id' || true)
MAC_ARTIFACT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}/artifacts" \
--jq '.artifacts[] | select(.name == "Decentraland_macos") | .id' || true)
{
echo '![badge] <img src="https://ui.decentraland.org/decentraland_256x256.png" width="30">'
echo ""
echo "Windows and Mac build successful in Unity Cloud! Links reused from the existing \`${RUN_BRANCH}\` build of this commit."
echo ""
echo "| Name | Link |"
echo "| -------- | ----------------------- |"
echo "| Commit | ${HEAD_SHA} |"
echo "| Logs | ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID} |"
echo "| Download Windows | ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/suites/${SUITE_ID}/artifacts/${WINDOWS_ARTIFACT_ID} |"
echo "| Download Windows S3 | ${PUBLIC_URL_PREFIX}/${S3_PATH}/Decentraland_windows64.zip |"
echo "| Download Mac | ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/suites/${SUITE_ID}/artifacts/${MAC_ARTIFACT_ID} |"
echo "| Download Mac S3 | ${PUBLIC_URL_PREFIX}/${S3_PATH}/Decentraland_macos.zip |"
echo "| Built on | ${BUILD_DATE} |"
echo ""
echo "[badge]: https://img.shields.io/badge/Build-Success!-3fb950?logo=github&style=for-the-badge"
} > comment.md
post_comment
# The PR above was opened with GITHUB_TOKEN, whose events start no workflow
# runs — the same suppression the build-links step exists to work around.
# So the InWorld gate has to be started explicitly, by a token that can.
#
# Dispatching against the release branch is what puts the gate on the PR:
# a workflow_dispatch run attaches its checks to the dispatched ref's tip,
# and that tip is the PR head. Without this the required check on main
# never reports and the release cannot merge.
- name: Start the InWorld gate on the release branch
env:
GH_TOKEN: ${{ secrets.ORG_ACCESS_TOKEN }}
run: |
set -euo pipefail
# On the dispatch path the gate resolves this commit's build with
# wait-minutes 0, so it fails within seconds unless both zips are
# already up. Ask it the same question — did both build legs conclude
# success — rather than whether a run went green: build-unitycloud.yml
# also concludes green having built nothing, when the commit touches
# no Explorer/** file and the matrix never expands, and green having
# built one platform, when a windows-only/macos-only label narrows it.
# Either would dispatch a gate that immediately reds a brand-new
# release PR with nothing wrong in it.
#
# Runs come back newest first, and only the newest few are worth
# asking about — the same candidate cap resolve-explorer-build uses.
HEAD_SHA=$(git rev-parse HEAD)
# The helper resolve-explorer-build defines, duplicated because a
# composite action cannot export a shell function to its caller. Same
# justification, more sharply: this step decides on one answer and
# then the workflow ends, so a single 5xx must not settle a release
# cut. Three attempts five seconds apart cover a transport failure —
# a 5xx, a connection reset — and only that; a spent rate limit
# refills on an hour boundary, which is why the flags below record
# whether the API answered rather than trusting the retry to make it.
# stderr is kept on the last attempt so a permanent failure (this
# workflow file renamed, say) says what it was instead of vanishing
# into "the API did not answer".
gh_api_retry() {
local out attempt
for attempt in 1 2 3; do
if [ "$attempt" -lt 3 ]; then
if out=$(gh api "$@" 2>/dev/null); then printf '%s' "$out"; return 0; fi
sleep 5
else
if out=$(gh api "$@"); then printf '%s' "$out"; return 0; fi
fi
done
return 1
}
# Absorbed rather than left to set -e, here and on the jobs call
# below: an API blip must not red a cut whose branch, PR, labels and
# build-links comment have all landed. But it must not be reported as
# "no build" either — that is the conflation LISTED/LISTED_NOW exist
# to stop in resolve-explorer-build, and it costs more here, because
# this message is the operator's only instruction and the workflow
# ends after it. LISTED is "the run listing answered at all";
# UNEXAMINED is "a candidate's jobs call did not", which leaves that
# run neither green nor known to be anything else.
LISTED=0
UNEXAMINED=0
if RUN_IDS=$(gh_api_retry "repos/${GITHUB_REPOSITORY}/actions/workflows/build-unitycloud.yml/runs?head_sha=${HEAD_SHA}&per_page=50" \
--jq '[.workflow_runs[] | select(.event == "pull_request" or .event == "push") | .id]'); then
LISTED=1
else
RUN_IDS=''
fi
if [ -z "$RUN_IDS" ]; then RUN_IDS='[]'; fi
LIMIT=$(jq 'length' <<< "$RUN_IDS")
if [ "$LIMIT" -gt 5 ]; then LIMIT=5; fi
LEGS_GREEN=false
for (( i = 0; i < LIMIT; i++ )); do
RUN_ID=$(jq -r ".[$i]" <<< "$RUN_IDS")
if LEGS_GREEN=$(gh_api_retry "repos/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}/jobs?per_page=50" \
--jq '[.jobs[] | select(.conclusion == "success") | .name] | any(startswith("Build (macos")) and any(startswith("Build (windows64"))'); then
if [ "$LEGS_GREEN" = "true" ]; then break; fi
else
LEGS_GREEN=false
UNEXAMINED=1
fi
done
# Three ways to end up not dispatching, and only the last is a build
# problem — so they get three messages. All three are `::error::`:
# each one ends with the gate unreported and the release unmergeable,
# which is not a line in a green log, and the dispatch failure below
# already annotates for exactly that outcome.
#
# None of them says "re-run this workflow". Re-running re-enters the
# `Create or update branch` step above, which force-pushes
# ${BRANCH_NAME} from dev's current tip — quietly re-cutting the
# release from newer commits than the one that was reviewed. Starting
# `In-World Tests` by hand is the remedy in every case, and it is the
# same instruction the dispatch failure below gives.
if [ "$LISTED" = "0" ]; then
echo "::error::Could not list build-unitycloud.yml runs for ${HEAD_SHA:0:7} — the GitHub Actions API did not answer, so no build was ever looked at and the InWorld gate was not started. This says nothing about whether a build exists. The release branch and its PR are fine: start \`In-World Tests\` by hand from the Actions tab against ${BRANCH_NAME}. Do not re-run this workflow — it force-pushes ${BRANCH_NAME} and would re-cut the release from a newer dev tip."
exit 0
fi
if [ "$LEGS_GREEN" != "true" ] && [ "$UNEXAMINED" = "1" ]; then
echo "::error::The GitHub Actions API did not answer for at least one build run of ${HEAD_SHA:0:7}, so whether its legs are green is unknown and the InWorld gate was not started. This is not evidence that no build exists. The release branch and its PR are fine: start \`In-World Tests\` by hand from the Actions tab against ${BRANCH_NAME}. Do not re-run this workflow — it force-pushes ${BRANCH_NAME} and would re-cut the release from a newer dev tip."
exit 0
fi
if [ "$LEGS_GREEN" != "true" ]; then
echo "::error::No build of ${HEAD_SHA:0:7} has both Build (macos) and Build (windows64) green, so the InWorld gate was not started — it would fail on its first poll. Build the commit: label the release PR \`force-build\` if build-unitycloud.yml built nothing, or remove a \`windows-only\`/\`macos-only\` label that narrowed its targets. Then start \`In-World Tests\` by hand against ${BRANCH_NAME}. Do not re-run this workflow — it force-pushes ${BRANCH_NAME} and would re-cut the release from a newer dev tip."
exit 0
fi
# Loud but deliberately not fatal. `gh workflow run` needs
# `actions: write`, which ORG_ACCESS_TOKEN's other uses in this repo
# (org membership, PR reviews) do not require, so it may not have it —
# and a secret's scopes cannot be inspected from here. Failing the job
# would invite a re-run, and re-running this workflow force-pushes the
# release branch, quietly re-cutting the release from a newer dev tip.
# The branch, PR, labels and build-links comment have all landed by
# now; the annotation is what makes the missing gate visible, and the
# required check staying unreported is what keeps the release blocked.
if ! gh workflow run in-world-tests.yml --ref "$BRANCH_NAME"; then
echo "::error::Could not dispatch the InWorld gate against ${BRANCH_NAME}. The likeliest cause is that ORG_ACCESS_TOKEN lacks \`actions: write\` on this repository, which \`gh workflow run\` requires. The release branch and its PR are fine — start \`In-World Tests\` by hand from the Actions tab against ${BRANCH_NAME}, or its required check will sit unreported and the release will not be mergeable."
exit 0
fi
echo "::notice::InWorld gate dispatched against ${BRANCH_NAME}."