Compare commits

..

2 Commits

Author SHA1 Message Date
CrazyMax
af563b8251
Merge 1f530c4b33bad46319ebd036f33c1e16e180e011 into ca877d9245402d1537745e0e356eab47c3520991 2025-02-17 15:18:42 +00:00
CrazyMax
1f530c4b33
reusable workflow to distribute multi-platform builds
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-02-17 16:18:33 +01:00
2 changed files with 91 additions and 32 deletions

View File

@ -1541,7 +1541,7 @@ jobs:
fi fi
distribute: distribute:
uses: ./.github/workflows/distribute.yml uses: ./.github/workflows/reusable-distribute.yml
with: with:
push: false push: false
meta-image: user/app meta-image: user/app

View File

@ -1,4 +1,6 @@
name: distribute # Reusable workflow to distribute multi-platform builds efficiently
# https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
name: reusable-distribute
on: on:
workflow_call: workflow_call:
@ -14,6 +16,16 @@ on:
description: "Push image to registry" description: "Push image to registry"
required: false required: false
default: false default: false
set-meta-annotations:
type: boolean
description: "Set metadata-action annotations"
required: false
default: false
set-meta-labels:
type: boolean
description: "Set metadata-action labels"
required: false
default: false
setup-qemu: setup-qemu:
type: boolean type: boolean
description: "Install QEMU static binaries" description: "Install QEMU static binaries"
@ -59,7 +71,7 @@ on:
type: string type: string
description: 'QEMU static binaries Docker image (e.g. tonistiigi/binfmt:latest)' description: 'QEMU static binaries Docker image (e.g. tonistiigi/binfmt:latest)'
required: false required: false
# same as docker/build-push-action inputs (minus builder, call, context, load, outputs, platforms, push, tags) # same as docker/build-push-action inputs (minus builder, call, load, outputs, platforms, push, tags)
build-add-hosts: build-add-hosts:
type: string type: string
description: "List of a customs host-to-IP mapping (e.g., docker:10.180.0.1)" description: "List of a customs host-to-IP mapping (e.g., docker:10.180.0.1)"
@ -166,6 +178,19 @@ on:
type: string type: string
description: "Ulimit options (e.g., nofile=1024:1024)" description: "Ulimit options (e.g., nofile=1024:1024)"
required: false required: false
secrets:
login-username:
description: 'Username used to log against the Docker registry'
required: false
login-password:
description: "Password or personal access token used to log against the Docker registry"
required: false
github-token:
description: "GitHub Token used to authenticate against a repository for Git context"
required: false
env:
ACTIONS_TOOLKIT_VERSION: "0.54.0"
jobs: jobs:
prepare: prepare:
@ -174,33 +199,42 @@ jobs:
includes: ${{ steps.set.outputs.includes }} includes: ${{ steps.set.outputs.includes }}
steps: steps:
- -
name: Validate name: Install npm dependencies
uses: actions/github-script@v7 uses: actions/github-script@v7
with: with:
script: | script: |
const metaImage = `${{ inputs.meta-image }}` ? `${{ inputs.meta-image }}`.split(/[\r?\n,]+/).filter(Boolean) : []; await exec.exec('npm', ['install',
if (metaImage.length > 1) { '@docker/actions-toolkit@${{ env.ACTIONS_TOOLKIT_VERSION }}'
throw new Error('Only one meta-image is allowed'); ]);
}
const platforms = `${{ inputs.build-platforms }}` ? `${{ inputs.build-platforms }}`.split(/[\r?\n,]+/).filter(Boolean) : [];
if (platforms.length > 100) {
throw new Error('Too many platforms');
} else if (platforms.length <= 1) {
throw new Error('At least 2 platforms are required');
}
core.exportVariable('BUILD_PLATFORMS', platforms.join(','));
- -
name: Set includes name: Set includes
id: set id: set
uses: actions/github-script@v7 uses: actions/github-script@v7
env:
INPUT_RUNNER: ${{ inputs.runner }}
INPUT_META-IMAGE: ${{ inputs.meta-image }}
INPUT_BUILD-PLATFORMS: ${{ inputs.build-platforms }}
GITHUB_TOKEN: ${{ secrets.github-token || github.token }}
with: with:
script: | script: |
const { Util } = require('@docker/actions-toolkit/lib/util');
if (Util.getInputList('meta-image').length > 1) {
throw new Error('Only one meta-image is allowed');
}
const inpRunner = core.getInput('runner');
const inpBuildPlatforms = Util.getInputList('build-platforms');
if (inpBuildPlatforms.length > 100) {
throw new Error('Too many platforms');
} else if (inpBuildPlatforms.length <= 1) {
throw new Error('At least 2 platforms are required');
}
await core.group(`Set includes`, async () => { await core.group(`Set includes`, async () => {
const platforms = `${{ env.BUILD_PLATFORMS }}`.split(',');
let includes = []; let includes = [];
platforms.forEach((platform, index) => { inpBuildPlatforms.forEach((platform, index) => {
let runner = `${{ inputs.runner }}`; let runner = inpRunner;
if (runner === 'auto') { if (runner === 'auto') {
runner = platform.startsWith('linux/arm') ? 'ubuntu-24.04-arm' : 'ubuntu-latest'; runner = platform.startsWith('linux/arm') ? 'ubuntu-24.04-arm' : 'ubuntu-latest';
} }
@ -342,7 +376,7 @@ jobs:
if: ${{ inputs.push }} if: ${{ inputs.push }}
with: with:
registry: ${{ inputs.login-registry }} registry: ${{ inputs.login-registry }}
username: ${{ inputs.login-username }} username: ${{ inputs.login-username || secrets.login-username }}
password: ${{ secrets.login-password }} password: ${{ secrets.login-password }}
- -
name: Set up QEMU name: Set up QEMU
@ -353,6 +387,31 @@ jobs:
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
-
name: Set build inputs
id: build-inputs
uses: actions/github-script@v7
env:
INPUT_SET-META-ANNOTATIONS: ${{ inputs.set-meta-annotations }}
INPUT_SET-META-LABELS: ${{ inputs.set-meta-labels }}
INPUT_BUILD-ANNOTATIONS: ${{ inputs.build-annotations }}
INPUT_BUILD-LABELS: ${{ inputs.build-labels }}
with:
script: |
const inpSetMetaAnnotations = core.getBooleanInput('set-meta-annotations');
const inpSetMetaLabels = core.getBooleanInput('set-meta-labels');
let inpBuildAnnotations = core.getInput('build-annotations');
if (inpSetMetaAnnotations) {
inpBuildAnnotations += `\n${{ steps.meta.outputs.annotations }}`;
}
let inpBuildLabels = core.getInput('build-labels');
if (inpSetMetaLabels) {
inpBuildLabels += `\n${{ steps.meta.outputs.labels }}`;
}
core.setOutput('annotations', inpBuildAnnotations);
core.setOutput('labels', inpBuildLabels);
- -
name: Build name: Build
id: build id: build
@ -360,9 +419,7 @@ jobs:
with: with:
add-hosts: ${{ inputs.build-add-hosts }} add-hosts: ${{ inputs.build-add-hosts }}
allow: ${{ inputs.build-allow }} allow: ${{ inputs.build-allow }}
annotations: | annotations: ${{ steps.build-inputs.outputs.annotations }}
${{ steps.meta.outputs.annotations }}
${{ inputs.build-annotations }}
attests: ${{ inputs.build-attests }} attests: ${{ inputs.build-attests }}
build-args: ${{ inputs.build-build-args }} build-args: ${{ inputs.build-build-args }}
build-contexts: ${{ inputs.build-contexts }} build-contexts: ${{ inputs.build-contexts }}
@ -371,9 +428,7 @@ jobs:
cgroup-parent: ${{ inputs.build-cgroup-parent }} cgroup-parent: ${{ inputs.build-cgroup-parent }}
context: ${{ inputs.build-context }} context: ${{ inputs.build-context }}
file: ${{ inputs.build-file }} file: ${{ inputs.build-file }}
labels: | labels: ${{ steps.build-inputs.outputs.labels }}
${{ steps.meta.outputs.labels }}
${{ inputs.build-labels }}
network: ${{ inputs.build-network }} network: ${{ inputs.build-network }}
no-cache: ${{ inputs.build-no-cache }} no-cache: ${{ inputs.build-no-cache }}
no-cache-filters: ${{ inputs.build-no-cache-filters }} no-cache-filters: ${{ inputs.build-no-cache-filters }}
@ -415,15 +470,13 @@ jobs:
images: ${{ inputs.meta-image }} images: ${{ inputs.meta-image }}
tags: ${{ inputs.meta-tags }} tags: ${{ inputs.meta-tags }}
flavor: ${{ inputs.meta-flavor }} flavor: ${{ inputs.meta-flavor }}
labels: ${{ inputs.meta-labels }}
annotations: ${{ inputs.meta-annotations }}
- -
name: Login to registry name: Login to registry
uses: docker/login-action@v3 uses: docker/login-action@v3
if: ${{ inputs.push }} if: ${{ inputs.push }}
with: with:
registry: ${{ inputs.login-registry }} registry: ${{ inputs.login-registry }}
username: ${{ inputs.login-username }} username: ${{ inputs.login-username || secrets.login-username }}
password: ${{ secrets.login-password }} password: ${{ secrets.login-password }}
- -
name: Set up Docker Buildx name: Set up Docker Buildx
@ -432,8 +485,14 @@ jobs:
- -
name: Create manifest list name: Create manifest list
uses: actions/github-script@v7 uses: actions/github-script@v7
env:
INPUT_PUSH: ${{ inputs.push }}
INPUT_META-IMAGE: ${{ inputs.meta-image }}
with: with:
script: | script: |
const inpPush = core.getBooleanInput('push');
const inpMetaImage = core.getInput('meta-image');
let digests = []; let digests = [];
await core.group(`Digests`, async () => { await core.group(`Digests`, async () => {
digests = Object.values(JSON.parse(`${{ toJSON(needs.build.outputs) }}`)); digests = Object.values(JSON.parse(`${{ toJSON(needs.build.outputs) }}`));
@ -451,10 +510,10 @@ jobs:
createArgs.push(`-t`, tag); createArgs.push(`-t`, tag);
} }
for (const digest of digests) { for (const digest of digests) {
createArgs.push(`${{ inputs.meta-image }}@${digest}`); createArgs.push(`${inpMetaImage}@${digest}`);
} }
if (${{ inputs.push }}) { if (inpPush) {
if (tags.length === 0) { if (tags.length === 0) {
throw new Error('No tags to create manifest list'); throw new Error('No tags to create manifest list');
} }
@ -466,7 +525,7 @@ jobs:
} }
}); });
await core.group(`Inspect image`, async () => { await core.group(`Inspect image`, async () => {
await exec.getExecOutput('docker', ['buildx', 'imagetools', 'inspect', `${{ inputs.meta-image }}:${tags[0]}`], { await exec.getExecOutput('docker', ['buildx', 'imagetools', 'inspect', `${inpMetaImage}:${tags[0]}`], {
ignoreReturnCode: true ignoreReturnCode: true
}).then(res => { }).then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) { if (res.stderr.length > 0 && res.exitCode != 0) {