Skip to content

Commit f8b7c3e

Browse files
committed
Add ability for parallel Jenkins multi-arch builds
Trigger all jenkins builds (adding them to jenkins queue) and then parallel wait on them. We control the concurrency by the number of executors per arch (or if we add multiple machines that match the build label).
1 parent d7eadbd commit f8b7c3e

File tree

2 files changed

+74
-42
lines changed

2 files changed

+74
-42
lines changed

Jenkinsfile.build

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
// one job per arch (for now) that just builds "the top thing" (triggered by the meta-update job)
22
properties([
3-
disableConcurrentBuilds(),
43
disableResume(),
54
durabilityHint('PERFORMANCE_OPTIMIZED'),
65
parameters([
6+
string(name: 'identifier', trim: true),
77
string(name: 'buildId', trim: true),
88
]),
99
])
1010

1111
env.BASHBREW_ARCH = env.JOB_NAME.minus('/build').split('/')[-1] // "windows-amd64", "arm64v8", etc
1212
env.BUILD_ID = params.buildId
13+
if (params.identifier) {
14+
currentBuild.displayName = params.identifier + ' (#' + currentBuild.number + ')'
15+
}
1316

1417
node('multiarch-' + env.BASHBREW_ARCH) { ansiColor('xterm') {
1518
stage('Checkout') {

Jenkinsfile.trigger

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -102,69 +102,98 @@ if (breakEarly) { return } // thanks Jenkins...
102102

103103
// new data to be added to the past-jobs.json
104104
// { lastTime: unixTimestamp, url: "" }
105-
def buildCompletionData = [:]
105+
buildCompletionData = [:]
106106

107-
for (buildObj in queue) {
108-
stage(buildObj.identifier) {
109-
//def json = writeJSON(json: buildObj, returnText: true)
110-
//echo(json) // for debugging/data purposes
107+
// list of closures that we can use to wait for the jobs on.
108+
def waitQueue = [:]
109+
def addToWait(identifier, buildId, externalizableId) {
110+
return {
111+
// "catchError" to set "stageResult" :(
112+
catchError(message: 'Build of "' + identifier + '" failed', buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
113+
stage(identifier) {
114+
def res = waitForBuild(
115+
runId: externalizableId,
116+
propagateAbort: true, // allow cancelling this job to cancel all the triggered jobs
117+
)
118+
buildCompletionData[buildId] = [
119+
lastTime: (res.startTimeInMillis + res.duration) / 1000, // convert to seconds
120+
url: res.absoluteUrl,
121+
]
122+
if (res.result != 'SUCCESS') {
123+
// set stage result via catchError
124+
error(res.result)
125+
}
126+
}
127+
}
128+
}
129+
}
130+
131+
// stage to wrap up all the build job triggers that get waited on later
132+
stage('trigger') {
133+
for (int i = 0; i < queue.size(); i++) {
134+
def buildObj = queue[i]
111135

112136
// "catchError" to set "stageResult" :(
113137
catchError(message: 'Build of "' + buildObj.identifier + '" failed', buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
114138
if (buildObj.gha_payload) {
115-
node {
116-
withEnv([
117-
'payload=' + buildObj.gha_payload,
118-
]) {
119-
withCredentials([
120-
string(
121-
variable: 'GH_TOKEN',
122-
credentialsId: 'github-access-token-docker-library-bot-meta',
123-
),
139+
stage(buildObj.identifier) {
140+
node {
141+
withEnv([
142+
'payload=' + buildObj.gha_payload,
124143
]) {
125-
sh '''
126-
set -u +x
127-
128-
# https://docs.github.com/en/free-pro-team@latest/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event
129-
curl -fL \
130-
-X POST \
131-
-H 'Accept: application/vnd.github+json' \
132-
-H "Authorization: Bearer $GH_TOKEN" \
133-
-H 'X-GitHub-Api-Version: 2022-11-28' \
134-
https://api.github.com/repos/docker-library/meta/actions/workflows/build.yml/dispatches \
135-
-d "$payload"
136-
'''
144+
withCredentials([
145+
string(
146+
variable: 'GH_TOKEN',
147+
credentialsId: 'github-access-token-docker-library-bot-meta',
148+
),
149+
]) {
150+
sh '''
151+
set -u +x
152+
153+
# https://docs.github.com/en/free-pro-team@latest/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event
154+
curl -fL \
155+
-X POST \
156+
-H 'Accept: application/vnd.github+json' \
157+
-H "Authorization: Bearer $GH_TOKEN" \
158+
-H 'X-GitHub-Api-Version: 2022-11-28' \
159+
https://api.github.com/repos/docker-library/meta/actions/workflows/build.yml/dispatches \
160+
-d "$payload"
161+
'''
162+
}
137163
}
164+
// record that GHA was triggered (for tracking continued triggers that fail to push an image)
165+
buildCompletionData[buildObj.buildId] = [
166+
lastTime: System.currentTimeMillis() / 1000, // convert to seconds
167+
url: currentBuild.absoluteUrl,
168+
]
138169
}
139-
// record that GHA was triggered (for tracking continued triggers that fail to push an image)
140-
buildCompletionData[buildObj.buildId] = [
141-
lastTime: System.currentTimeMillis() / 1000, // convert to seconds
142-
url: currentBuild.absoluteUrl,
143-
]
144170
}
145171
} else {
172+
// why not parallel these build() invocations?
173+
// jenkins parallel closures get started in a randomish order, ruining our sorted queue
146174
def res = build(
147175
job: 'build',
148176
parameters: [
177+
string(name: 'identifier', value: buildObj.identifier),
149178
string(name: 'buildId', value: buildObj.buildId),
150179
],
151180
propagate: false,
152-
quietPeriod: 5, // seconds
181+
// trigger these quickly so they all get added to Jenkins queue in "queue" order
182+
quietPeriod: 0, // seconds
183+
// we'll wait on them after they are all queued
184+
waitForStart: true,
153185
)
154-
// record the job failure
155-
buildCompletionData[buildObj.buildId] = [
156-
lastTime: (res.startTimeInMillis + res.duration) / 1000, // convert to seconds
157-
url: res.absoluteUrl,
158-
]
159-
if (res.result != 'SUCCESS') {
160-
// set stage result via catchError
161-
error(res.result)
162-
}
186+
waitQueue[buildObj.identifier] = addToWait(buildObj.identifier, buildObj.buildId, res.externalizableId)
163187
}
164188
}
165189
}
166190
}
167191

192+
// wait on all the 'build' jobs that were queued
193+
if (waitQueue.size() > 0) {
194+
parallel waitQueue
195+
}
196+
168197
// save currentJobs so we can use it next run as pastJobs
169198
node {
170199
def buildCompletionDataJson = writeJSON(json: buildCompletionData, returnText: true)

0 commit comments

Comments
 (0)