Skip to content

Commit 8a2fed2

Browse files
committed
build/bake: allow runner mappings for platform-specific builds
1 parent 5840c33 commit 8a2fed2

5 files changed

Lines changed: 202 additions & 28 deletions

File tree

.github/workflows/.test-bake.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ jobs:
9090
context: test
9191
output: image
9292
push: ${{ github.event_name != 'pull_request' }}
93+
runner: |
94+
default=ubuntu-24.04
95+
linux/arm=ubuntu-24.04-arm
96+
linux/arm64=ubuntu-24.04-arm
9397
sbom: true
9498
set: |
9599
*.args.VERSION={{meta.version}}
@@ -465,7 +469,7 @@ jobs:
465469
contents: read
466470
id-token: write
467471
with:
468-
runner: amd64
472+
runner: ubuntu-24.04
469473
context: test
470474
output: image
471475
push: false

.github/workflows/.test-build.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ jobs:
9393
output: image
9494
platforms: linux/amd64,linux/arm64
9595
push: ${{ github.event_name != 'pull_request' }}
96+
runner: |
97+
default=ubuntu-24.04
98+
linux/arm=ubuntu-24.04-arm
99+
linux/arm64=ubuntu-24.04-arm
96100
sbom: true
97101
meta-images: |
98102
public.ecr.aws/q3b5f1u4/test-docker-action
@@ -514,7 +518,7 @@ jobs:
514518
contents: read
515519
id-token: write
516520
with:
517-
runner: amd64
521+
runner: ubuntu-24.04
518522
build-args: |
519523
VERSION={{meta.version}}
520524
file: test/hello.Dockerfile

.github/workflows/bake.yml

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ on:
55
inputs:
66
runner:
77
type: string
8-
description: "Ubuntu GitHub Hosted Runner to build on (one of auto, amd64, arm64). The auto runner selects the best-matching runner based on target platforms. You can set it to amd64 if your build doesn't require emulation (e.g. cross-compilation)"
8+
description: "GitHub-hosted runner label or mapping to build on. Set a single label to use it for every platform, or provide newline-delimited default and platform-specific mappings such as default=ubuntu-24.04 and linux/arm64=ubuntu-24.04-arm"
99
required: false
10-
default: 'auto'
10+
default: |
11+
default=ubuntu-24.04
12+
linux/arm=ubuntu-24.04-arm
13+
linux/arm64=ubuntu-24.04-arm
1114
distribute:
1215
type: boolean
1316
description: "Whether to distribute the build across multiple runners (one platform per runner)"
@@ -278,7 +281,7 @@ jobs:
278281
const inpActionsIdTokenSet = core.getBooleanInput('actions-id-token-set');
279282
const inpMetaImages = core.getMultilineInput('meta-images');
280283
281-
const inpRunner = core.getInput('runner');
284+
const inpRunner = core.getMultilineInput('runner');
282285
const inpDistribute = core.getBooleanInput('distribute');
283286
const inpArtifactUpload = core.getBooleanInput('artifact-upload');
284287
const inpContext = core.getInput('context');
@@ -292,15 +295,84 @@ jobs:
292295
const inpTarget = core.getInput('target');
293296
const inpGitHubToken = core.getInput('github-token');
294297
295-
let runner = inpRunner;
296-
if (inpRunner === 'amd64') {
297-
runner = 'ubuntu-24.04';
298-
} else if (inpRunner === 'arm64') {
299-
runner = 'ubuntu-24.04-arm';
300-
} else if (inpRunner !== 'auto') {
301-
core.setFailed(`Invalid runner input: ${inpRunner}`);
298+
const parseRunnerConfig = value => {
299+
const lines = value.map(line => line.trim()).filter(line => line.length > 0);
300+
if (lines.length === 0) {
301+
throw new Error('runner input cannot be empty');
302+
}
303+
if (lines.length === 1 && !lines[0].includes('=')) {
304+
return {
305+
defaultRunner: lines[0],
306+
rules: []
307+
};
308+
}
309+
const rules = [];
310+
let defaultRunner;
311+
for (const line of lines) {
312+
const idx = line.indexOf('=');
313+
if (idx === -1) {
314+
throw new Error(`Invalid runner mapping: ${line}`);
315+
}
316+
const pattern = line.substring(0, idx).trim();
317+
const runner = line.substring(idx + 1).trim();
318+
if (!pattern) {
319+
throw new Error('Runner mapping pattern cannot be empty');
320+
}
321+
if (!runner) {
322+
throw new Error(`Runner mapping value cannot be empty for ${pattern}`);
323+
}
324+
if (pattern === 'default') {
325+
defaultRunner = runner;
326+
continue;
327+
}
328+
if (pattern.split('/').some(part => part.length === 0)) {
329+
throw new Error(`Runner mapping pattern is not a valid platform prefix: ${pattern}`);
330+
}
331+
rules.push({pattern, runner});
332+
}
333+
if (!defaultRunner) {
334+
throw new Error('Runner mapping must define a default runner');
335+
}
336+
return {
337+
defaultRunner,
338+
rules
339+
};
340+
};
341+
342+
const matchesPlatformPrefix = (pattern, platform) => {
343+
const patternParts = pattern.split('/');
344+
const platformParts = platform.split('/');
345+
return patternParts.length <= platformParts.length &&
346+
patternParts.every((part, index) => part === platformParts[index]);
347+
};
348+
349+
const resolveRunner = (runnerConfig, platform) => {
350+
if (!platform) {
351+
return runnerConfig.defaultRunner;
352+
}
353+
let match;
354+
for (const rule of runnerConfig.rules) {
355+
if (!matchesPlatformPrefix(rule.pattern, platform)) {
356+
continue;
357+
}
358+
const specificity = rule.pattern.split('/').length;
359+
if (!match || specificity >= match.specificity) {
360+
match = {runner: rule.runner, specificity};
361+
}
362+
}
363+
return match ? match.runner : runnerConfig.defaultRunner;
364+
};
365+
366+
let runnerConfig;
367+
try {
368+
runnerConfig = parseRunnerConfig(inpRunner);
369+
} catch (error) {
370+
core.setFailed(error.message);
302371
return;
303372
}
373+
await core.group(`Set runner config`, async () => {
374+
core.info(JSON.stringify(runnerConfig, null, 2));
375+
});
304376
305377
const sign =
306378
inpSign === 'auto'
@@ -425,14 +497,14 @@ jobs:
425497
if (!inpDistribute || platforms.length === 0) {
426498
includes.push({
427499
index: 0,
428-
runner: runner === 'auto' ? 'ubuntu-24.04' : runner
500+
runner: resolveRunner(runnerConfig)
429501
});
430502
} else {
431503
platforms.forEach((platform, index) => {
432504
includes.push({
433505
index: index,
434506
platform: platform,
435-
runner: runner === 'auto' ? (platform.startsWith('linux/arm') ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner
507+
runner: resolveRunner(runnerConfig, platform)
436508
});
437509
});
438510
}
@@ -481,6 +553,17 @@ jobs:
481553
result_18: ${{ steps.result.outputs.result_18 }}
482554
result_19: ${{ steps.result.outputs.result_19 }}
483555
steps:
556+
-
557+
name: Require GitHub-hosted runner
558+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
559+
env:
560+
INPUT_RUNNER-ENVIRONMENT: ${{ runner.environment }}
561+
with:
562+
script: |
563+
const runnerEnvironment = core.getInput('runner-environment');
564+
if (runnerEnvironment !== 'github-hosted') {
565+
core.setFailed(`This workflow requires a GitHub-hosted runner, got: ${runnerEnvironment || 'unknown'}`);
566+
}
484567
-
485568
name: Install dependencies
486569
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0

.github/workflows/build.yml

Lines changed: 95 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ on:
55
inputs:
66
runner:
77
type: string
8-
description: "Ubuntu GitHub Hosted Runner to build on (one of auto, amd64, arm64). The auto runner selects the best-matching runner based on target platforms. You can set it to amd64 if your build doesn't require emulation (e.g. cross-compilation)"
8+
description: "GitHub-hosted runner label or mapping to build on. Set a single label to use it for every platform, or provide newline-delimited default and platform-specific mappings such as default=ubuntu-24.04 and linux/arm64=ubuntu-24.04-arm"
99
required: false
10-
default: 'auto'
10+
default: |
11+
default=ubuntu-24.04
12+
linux/arm=ubuntu-24.04-arm
13+
linux/arm64=ubuntu-24.04-arm
1114
distribute:
1215
type: boolean
1316
description: "Whether to distribute the build across multiple runners (one platform per runner)"
@@ -263,23 +266,92 @@ jobs:
263266
const inpActionsIdTokenSet = core.getBooleanInput('actions-id-token-set');
264267
const inpMetaImages = core.getMultilineInput('meta-images');
265268
266-
const inpRunner = core.getInput('runner');
269+
const inpRunner = core.getMultilineInput('runner');
267270
const inpDistribute = core.getBooleanInput('distribute');
268271
const inpArtifactUpload = core.getBooleanInput('artifact-upload');
269272
const inpPlatforms = Util.getInputList('platforms');
270273
const inpOutput = core.getInput('output');
271274
const inpPush = core.getBooleanInput('push');
272275
const inpSign = core.getInput('sign');
273276
274-
let runner = inpRunner;
275-
if (inpRunner === 'amd64') {
276-
runner = 'ubuntu-24.04';
277-
} else if (inpRunner === 'arm64') {
278-
runner = 'ubuntu-24.04-arm';
279-
} else if (inpRunner !== 'auto') {
280-
core.setFailed(`Invalid runner input: ${inpRunner}`);
277+
const parseRunnerConfig = value => {
278+
const lines = value.map(line => line.trim()).filter(line => line.length > 0);
279+
if (lines.length === 0) {
280+
throw new Error('runner input cannot be empty');
281+
}
282+
if (lines.length === 1 && !lines[0].includes('=')) {
283+
return {
284+
defaultRunner: lines[0],
285+
rules: []
286+
};
287+
}
288+
const rules = [];
289+
let defaultRunner;
290+
for (const line of lines) {
291+
const idx = line.indexOf('=');
292+
if (idx === -1) {
293+
throw new Error(`Invalid runner mapping: ${line}`);
294+
}
295+
const pattern = line.substring(0, idx).trim();
296+
const runner = line.substring(idx + 1).trim();
297+
if (!pattern) {
298+
throw new Error('Runner mapping pattern cannot be empty');
299+
}
300+
if (!runner) {
301+
throw new Error(`Runner mapping value cannot be empty for ${pattern}`);
302+
}
303+
if (pattern === 'default') {
304+
defaultRunner = runner;
305+
continue;
306+
}
307+
if (pattern.split('/').some(part => part.length === 0)) {
308+
throw new Error(`Runner mapping pattern is not a valid platform prefix: ${pattern}`);
309+
}
310+
rules.push({pattern, runner});
311+
}
312+
if (!defaultRunner) {
313+
throw new Error('Runner mapping must define a default runner');
314+
}
315+
return {
316+
defaultRunner,
317+
rules
318+
};
319+
};
320+
321+
const matchesPlatformPrefix = (pattern, platform) => {
322+
const patternParts = pattern.split('/');
323+
const platformParts = platform.split('/');
324+
return patternParts.length <= platformParts.length &&
325+
patternParts.every((part, index) => part === platformParts[index]);
326+
};
327+
328+
const resolveRunner = (runnerConfig, platform) => {
329+
if (!platform) {
330+
return runnerConfig.defaultRunner;
331+
}
332+
let match;
333+
for (const rule of runnerConfig.rules) {
334+
if (!matchesPlatformPrefix(rule.pattern, platform)) {
335+
continue;
336+
}
337+
const specificity = rule.pattern.split('/').length;
338+
if (!match || specificity >= match.specificity) {
339+
match = {runner: rule.runner, specificity};
340+
}
341+
}
342+
return match ? match.runner : runnerConfig.defaultRunner;
343+
};
344+
345+
let runnerConfig;
346+
try {
347+
runnerConfig = parseRunnerConfig(inpRunner);
348+
} catch (error) {
349+
core.setFailed(error.message);
281350
return;
282351
}
352+
await core.group(`Set runner config`, async () => {
353+
core.info(JSON.stringify(runnerConfig, null, 2));
354+
});
283355
284356
const sign =
285357
inpSign === 'auto'
@@ -318,14 +390,14 @@ jobs:
318390
if (!inpDistribute || inpPlatforms.length === 0) {
319391
includes.push({
320392
index: 0,
321-
runner: runner === 'auto' ? 'ubuntu-24.04' : runner
393+
runner: resolveRunner(runnerConfig)
322394
});
323395
} else {
324396
inpPlatforms.forEach((platform, index) => {
325397
includes.push({
326398
index: index,
327399
platform: platform,
328-
runner: runner === 'auto' ? (platform.startsWith('linux/arm') ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner
400+
runner: resolveRunner(runnerConfig, platform)
329401
});
330402
});
331403
}
@@ -374,6 +446,17 @@ jobs:
374446
result_18: ${{ steps.result.outputs.result_18 }}
375447
result_19: ${{ steps.result.outputs.result_19 }}
376448
steps:
449+
-
450+
name: Require GitHub-hosted runner
451+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
452+
env:
453+
INPUT_RUNNER-ENVIRONMENT: ${{ runner.environment }}
454+
with:
455+
script: |
456+
const runnerEnvironment = core.getInput('runner-environment');
457+
if (runnerEnvironment !== 'github-hosted') {
458+
core.setFailed(`This workflow requires a GitHub-hosted runner, got: ${runnerEnvironment || 'unknown'}`);
459+
}
377460
-
378461
name: Install dependencies
379462
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0

0 commit comments

Comments
 (0)