[ci] Add polling to download-build-artifacts
Several CI workflows depend on the runtime_build_and_test.yml workflow to complete before it can successfully download the build artifact. However it is possible to encounter a race condition where the build hasn't completed when the new workflow is started. This PR adds a simple polling mechanism that waits up to 10 minutes for the build for that revision to complete. ghstack-source-id: 6a954638a800fbea8081e6fba35ee4b4437731c5 Pull Request resolved: https://github.com/facebook/react/pull/30515
Lauren Tan committed
Jul 29, 2024 at 18:51 UTC
3043e1ae21c3847fb4a762dd2128596d55a7c4cb
1 file changed
+86
-37
scripts/release/shared-commands/download-build-artifacts.js
+86
-37
@@ -21,6 +21,10 @@ const GITHUB_HEADERS = `
21
-H "Authorization: Bearer ${process.env.GH_TOKEN}" \
22
-H "X-GitHub-Api-Version: 2022-11-28"`.trim();
23
24
+function sleep(ms) {
25
+ return new Promise(resolve => setTimeout(resolve, ms));
26
+}
27
+
28
function getWorkflowId() {
29
if (
30
existsSync(join(__dirname, `../../../.github/workflows/${WORKFLOW_ID}`))
@@ -33,7 +37,7 @@ function getWorkflowId() {
37
}
38
}
39
36
-async function getWorkflowRunId(commit) {
40
+async function getWorkflowRun(commit) {
41
const res = await exec(
42
`curl -L ${GITHUB_HEADERS} https://api.github.com/repos/${OWNER}/${REPO}/actions/workflows/${getWorkflowId()}/runs?head_sha=${commit}&branch=main&exclude_pull_requests=true`
43
);
@@ -55,7 +59,7 @@ async function getWorkflowRunId(commit) {
59
process.exit(1);
60
}
61
58
- return workflowRun.id;
62
+ return workflowRun;
63
}
64
65
async function getArtifact(workflowRunId, artifactName) {
@@ -84,43 +88,88 @@ async function getArtifact(workflowRunId, artifactName) {
88
}
89
90
async function downloadArtifactsFromGitHub(commit, releaseChannel) {
87
- const workflowRunId = await getWorkflowRunId(commit);
88
- const artifact = await getArtifact(workflowRunId, 'artifacts_combined');
89
-
90
- // Download and extract artifact
91
- const cwd = join(__dirname, '..', '..', '..');
92
- await exec(`rm -rf ./build`, {cwd});
93
- await exec(
94
- `curl -L ${GITHUB_HEADERS} ${artifact.archive_download_url} \
95
- > a.zip && unzip a.zip -d . && rm a.zip build2.tgz && tar -xvzf build.tgz && rm build.tgz`,
96
- {
97
- cwd,
91
+ const workflowRun = await getWorkflowRun(commit);
92
+ let retries = 0;
93
+ // wait up to 10 mins for build to finish: 10 * 60 * 1_000) / 30_000 = 20
94
+ while (retries < 20) {
95
+ if (typeof workflowRun.status === 'string') {
96
+ switch (workflowRun.status) {
97
+ case 'queued':
98
+ case 'in_progress':
99
+ case 'waiting': {
100
+ retries++;
101
+ console.log(theme`Build still in progress, waiting 30s...`);
102
+ await sleep(30_000);
103
+ break;
104
+ }
105
+ case 'completed': {
106
+ if (workflowRun.conclusion === 'success') {
107
+ const artifact = await getArtifact(
108
+ workflowRun.id,
109
+ 'artifacts_combined'
110
+ );
111
+
112
+ // Download and extract artifact
113
+ const cwd = join(__dirname, '..', '..', '..');
114
+ await exec(`rm -rf ./build`, {cwd});
115
+ await exec(
116
+ `curl -L ${GITHUB_HEADERS} ${artifact.archive_download_url} \
117
+ > a.zip && unzip a.zip -d . && rm a.zip build2.tgz && tar -xvzf build.tgz && rm build.tgz`,
118
+ {
119
+ cwd,
120
+ }
121
+ );
122
+
123
+ // Copy to staging directory
124
+ // TODO: Consider staging the release in a different directory from the CI
125
+ // build artifacts: `./build/node_modules` -> `./staged-releases`
126
+ if (!existsSync(join(cwd, 'build'))) {
127
+ await exec(`mkdir ./build`, {cwd});
128
+ } else {
129
+ await exec(`rm -rf ./build/node_modules`, {cwd});
130
+ }
131
+ let sourceDir;
132
+ // TODO: Rename release channel to `next`
133
+ if (releaseChannel === 'stable') {
134
+ sourceDir = 'oss-stable';
135
+ } else if (releaseChannel === 'experimental') {
136
+ sourceDir = 'oss-experimental';
137
+ } else if (releaseChannel === 'rc') {
138
+ sourceDir = 'oss-stable-rc';
139
+ } else if (releaseChannel === 'latest') {
140
+ sourceDir = 'oss-stable-semver';
141
+ } else {
142
+ console.error(
143
+ 'Internal error: Invalid release channel: ' + releaseChannel
144
+ );
145
+ process.exit(releaseChannel);
146
+ }
147
+ await exec(`cp -r ./build/${sourceDir} ./build/node_modules`, {
148
+ cwd,
149
+ });
150
+ return;
151
+ } else {
152
+ console.log(
153
+ theme`{error Could not download build for ${commit} from GitHub as its conclusion was: ${workflowRun.conclusion}}`
154
+ );
155
+ process.exit(1);
156
+ }
157
+ break;
158
+ }
159
+ default: {
160
+ console.log(
161
+ theme`{error Unhandled workflow run status: ${workflowRun.status}}`
162
+ );
163
+ process.exit(1);
164
+ }
165
+ }
166
}
99
- );
100
-
101
- // Copy to staging directory
102
- // TODO: Consider staging the release in a different directory from the CI
103
- // build artifacts: `./build/node_modules` -> `./staged-releases`
104
- if (!existsSync(join(cwd, 'build'))) {
105
- await exec(`mkdir ./build`, {cwd});
106
- } else {
107
- await exec(`rm -rf ./build/node_modules`, {cwd});
167
}
109
- let sourceDir;
110
- // TODO: Rename release channel to `next`
111
- if (releaseChannel === 'stable') {
112
- sourceDir = 'oss-stable';
113
- } else if (releaseChannel === 'experimental') {
114
- sourceDir = 'oss-experimental';
115
- } else if (releaseChannel === 'rc') {
116
- sourceDir = 'oss-stable-rc';
117
- } else if (releaseChannel === 'latest') {
118
- sourceDir = 'oss-stable-semver';
119
- } else {
120
- console.error('Internal error: Invalid release channel: ' + releaseChannel);
121
- process.exit(releaseChannel);
122
- }
123
- await exec(`cp -r ./build/${sourceDir} ./build/node_modules`, {cwd});
168
+
169
+ console.log(
170
+ theme`{error Could not download build for ${commit} from GitHub as it timed out}`
171
+ );
172
+ process.exit(1);
173
}
174
175
async function downloadBuildArtifacts(commit, releaseChannel) {