diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2fb79a1..2bea55c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -733,3 +733,21 @@ jobs: ./test/config.hcl allow: network.host targets: app-entitlements + + distribute: + uses: ./.github/workflows/reusable-distribute-mp.yml + with: + target: app-plus + push: false + meta-image: user/app + bake-files: ./test/config.hcl + + distribute-cache: + uses: ./.github/workflows/reusable-distribute-mp.yml + with: + target: image-all + push: false + cache: true + cache-scope: go-app + meta-image: user/goapp + bake-source: "{{defaultContext}}:test/go" diff --git a/.github/workflows/reusable-distribute-mp.yml b/.github/workflows/reusable-distribute-mp.yml new file mode 100644 index 00000000..24e5b022 --- /dev/null +++ b/.github/workflows/reusable-distribute-mp.yml @@ -0,0 +1,643 @@ +# 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-mp + +on: + workflow_call: + inputs: + # inputs specific to this reusable worklow + runner: + type: string + description: "Runner instance" + required: false + default: 'auto' + target: + type: string + description: "Multi-platform target to build" + required: true + push: + type: boolean + description: "Push image to registry" + required: false + default: false + cache: + type: boolean + description: "Enable cache to GitHub Actions cache backend" + required: false + default: false + cache-scope: + type: string + description: "Which scope cache object belongs to if cache enabled (default is target name)" + required: false + cache-mode: + type: string + description: "Cache layers to export if cache enabled (min or max)" + required: false + default: 'min' + 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: + type: boolean + description: "Install QEMU static binaries" + required: false + default: true + # same as docker/metadata-action inputs (minus sep-tags, sep-labels, sep-annotations) + meta-image: + type: string + description: "Image to use as base name for tags" + required: true + meta-tags: + type: string + description: "List of tags as key-value pair attributes" + required: false + meta-flavor: + type: string + description: "Flavors to apply" + required: false + meta-labels: + type: string + description: "List of custom labels" + required: false + meta-annotations: + type: string + description: "List of custom annotations" + required: false + meta-bake-target: + type: string + description: "Bake target name (default docker-metadata-action)" + required: false + # same as docker/login-action inputs (minus logout) + login-registry: + type: string + description: "Server address of Docker registry. If not set then will default to Docker Hub" + required: false + login-username: + type: string + description: "Username used to log against the Docker registry" + required: false + login-ecr: + type: string + description: "Specifies whether the given registry is ECR (auto, true or false)" + default: 'auto' + required: false + # same as docker/setup-buildx-action inputs (minus driver, install, use, endpoint, append, cleanup) + buildx-version: + type: string + description: "Buildx version. (eg. v0.3.0)" + required: false + buildx-cache-binary: + type: boolean + description: "Cache buildx binary to GitHub Actions cache backend" + default: true + required: false + buildx-driver-opts: + type: string + description: "List of additional docker-container options. (eg. image=moby/buildkit:master)" + required: false + buildkitd-flags: + type: string + description: "BuildKit daemon flags" + required: false + buildkitd-config: + type: string + description: "BuildKit daemon config file" + required: false + buildkitd-config-inline: + type: string + description: "Inline BuildKit daemon config" + required: false + # same as docker/setup-qemu-action inputs (minus platforms, cache-image) + qemu-image: + type: string + description: "QEMU static binaries Docker image (e.g. tonistiigi/binfmt:latest)" + required: false + # same as docker/bake-action inputs (minus workdir, builder, targets, load, push) + bake-source: + type: string + description: "Git context to build from for a remote bake definition" + required: false + bake-allow: + type: string + description: "Allow build to access specified resources (e.g., network.host)" + required: false + bake-files: + type: string + description: "List of bake definition files" + required: false + bake-no-cache: + type: boolean + description: "Do not use cache when building the image" + required: false + default: false + bake-pull: + type: boolean + description: "Always attempt to pull a newer version of the image" + required: false + default: false + bake-provenance: + type: string + description: "Provenance is a shorthand for --set=*.attest=type=provenance" + required: false + bake-sbom: + type: string + description: "SBOM is a shorthand for --set=*.attest=type=sbom" + required: false + bake-set: + type: string + description: "List of targets values to override (eg. targetpattern.key=value)" + 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: "API token used to authenticate to a Git repository for remote definitions" + required: false + +env: + ACTIONS_TOOLKIT_VERSION: "0.54.0" + HANDLEBARS_VERSION: "4.7.8" + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + includes: ${{ steps.set.outputs.includes }} + steps: + - + name: Install npm dependencies + uses: actions/github-script@v7 + with: + script: | + await exec.exec('npm', ['install', + '@docker/actions-toolkit@${{ env.ACTIONS_TOOLKIT_VERSION }}', + 'handlebars@${{ env.HANDLEBARS_VERSION }}' + ]); + - + name: Set includes + id: set + uses: actions/github-script@v7 + env: + INPUT_RUNNER: ${{ inputs.runner }} + INPUT_TARGET: ${{ inputs.target }} + INPUT_META-IMAGE: ${{ inputs.meta-image }} + INPUT_BAKE-ALLOW: ${{ inputs.bake-allow }} + INPUT_BAKE-FILES: ${{ inputs.bake-files }} + INPUT_BAKE-NO-CACHE: ${{ inputs.bake-no-cache }} + INPUT_BAKE-PROVENANCE: ${{ inputs.bake-provenance }} + INPUT_BAKE-SBOM: ${{ inputs.bake-sbom }} + INPUT_BAKE-SET: ${{ inputs.bake-set }} + INPUT_BAKE-SOURCE: ${{ inputs.bake-source }} + GITHUB_TOKEN: ${{ secrets.github-token || github.token }} + with: + script: | + const handlebars = require('handlebars'); + const { Bake } = require('@docker/actions-toolkit/lib/buildx/bake'); + const { Build } = require('@docker/actions-toolkit/lib/buildx/build'); + const { Context } = require('@docker/actions-toolkit/lib/context'); + const { Util } = require('@docker/actions-toolkit/lib/util'); + + if (Util.getInputList('meta-image').length > 1) { + throw new Error('Only one meta-image is allowed'); + } + if (Util.getInputList('target').length > 1) { + throw new Error('Only one target is allowed'); + } + + const inpRunner = core.getInput('runner'); + const inpTarget = core.getInput('target'); + const inpBakeAllow = Util.getInputList('bake-allow'); + const inpBakeFiles = Util.getInputList('bake-files'); + const inpBakeNoCache = core.getBooleanInput('bake-no-cache'); + const inpBakeProvenance = Build.getProvenanceInput('bake-provenance'); + const inpBakeSbom = core.getInput('bake-sbom'); + const inpBakeSet = Util.getInputList('bake-set', {ignoreComma: true, quote: false}); + let inpBakeSource = handlebars.compile(core.getInput('bake-source'))({ + defaultContext: Context.gitContext() + }); + if (!inpBakeSource) { + inpBakeSource = Context.gitContext(); + } + if (inpBakeSource === '.') { + inpBakeSource = ''; + } + + let def; + await core.group(`Validating definition`, async () => { + const bake = new Bake(); + def = await bake.getDefinition({ + allow: inpBakeAllow, + files: inpBakeFiles, + noCache: inpBakeNoCache, + overrides: inpBakeSet, + provenance: inpBakeProvenance, + sbom: inpBakeSbom, + source: inpBakeSource, + targets: [inpTarget], + githubToken: process.env.GITHUB_TOKEN + }); + if (!def) { + throw new Error('Bake definition not set'); + } + }); + + await core.group(`Set includes`, async () => { + const platforms = def.target[inpTarget].platforms; + if (platforms.length > 100) { + throw new Error('Too many platforms'); + } else if (platforms.length <= 1) { + throw new Error('At least 2 platforms are required'); + } + let includes = []; + platforms.forEach((platform, index) => { + let runner = inpRunner; + if (runner === 'auto') { + runner = platform.startsWith('linux/arm') ? 'ubuntu-24.04-arm' : 'ubuntu-latest'; + } + includes.push({ + index: index, + platform: platform, + runner: runner + }); + }); + core.info(JSON.stringify(includes, null, 2)); + core.setOutput('includes', JSON.stringify(includes)); + }); + + warmup: + runs-on: ${{ inputs.runner == 'auto' && 'ubuntu-latest' || inputs.runner }} + steps: + - + name: Set up QEMU + uses: docker/setup-qemu-action@v3 + if: ${{ inputs.setup-qemu }} + with: + image: ${{ inputs.qemu-image }} + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: ${{ inputs.buildx-version }} + driver-opts: ${{ inputs.buildx-driver-opts }} + buildkitd-flags: ${{ inputs.buildkitd-flags }} + buildkitd-config: ${{ inputs.buildkitd-config }} + buildkitd-config-inline: ${{ inputs.buildkitd-config-inline }} + cache-binary: ${{ inputs.buildx-cache-binary }} + - + name: Warm up cache + uses: docker/bake-action@v6 + if: ${{ inputs.cache }} + with: + source: ${{ inputs.bake-source }} + files: ${{ inputs.bake-files }} + targets: ${{ inputs.target }} + provenance: false + sbom: false + set: | + ${{ inputs.bake-set }} + *.platform= + *.output=type=cacheonly + *.cache-from=type=gha,scope=${{ inputs.cache-scope || inputs.target }}-warmup + *.cache-to=type=gha,scope=${{ inputs.cache-scope || inputs.target }}-warmup,mode=${{ inputs.cache-mode }} + github-token: ${{ secrets.github-token || github.token }} + + build: + runs-on: ${{ matrix.runner }} + needs: + - prepare + - warmup + outputs: + # needs predefined outputs as we can't use dynamic ones atm: https://github.com/actions/runner/pull/2477 + # 100 is the maximum number of platforms supported by the matrix strategy + digest_0: ${{ steps.digest.outputs.digest_0 }} + digest_1: ${{ steps.digest.outputs.digest_1 }} + digest_2: ${{ steps.digest.outputs.digest_2 }} + digest_3: ${{ steps.digest.outputs.digest_3 }} + digest_4: ${{ steps.digest.outputs.digest_4 }} + digest_5: ${{ steps.digest.outputs.digest_5 }} + digest_6: ${{ steps.digest.outputs.digest_6 }} + digest_7: ${{ steps.digest.outputs.digest_7 }} + digest_8: ${{ steps.digest.outputs.digest_8 }} + digest_9: ${{ steps.digest.outputs.digest_9 }} + digest_10: ${{ steps.digest.outputs.digest_10 }} + digest_11: ${{ steps.digest.outputs.digest_11 }} + digest_12: ${{ steps.digest.outputs.digest_12 }} + digest_13: ${{ steps.digest.outputs.digest_13 }} + digest_14: ${{ steps.digest.outputs.digest_14 }} + digest_15: ${{ steps.digest.outputs.digest_15 }} + digest_16: ${{ steps.digest.outputs.digest_16 }} + digest_17: ${{ steps.digest.outputs.digest_17 }} + digest_18: ${{ steps.digest.outputs.digest_18 }} + digest_19: ${{ steps.digest.outputs.digest_19 }} + digest_20: ${{ steps.digest.outputs.digest_20 }} + digest_21: ${{ steps.digest.outputs.digest_21 }} + digest_22: ${{ steps.digest.outputs.digest_22 }} + digest_23: ${{ steps.digest.outputs.digest_23 }} + digest_24: ${{ steps.digest.outputs.digest_24 }} + digest_25: ${{ steps.digest.outputs.digest_25 }} + digest_26: ${{ steps.digest.outputs.digest_26 }} + digest_27: ${{ steps.digest.outputs.digest_27 }} + digest_28: ${{ steps.digest.outputs.digest_28 }} + digest_29: ${{ steps.digest.outputs.digest_29 }} + digest_30: ${{ steps.digest.outputs.digest_30 }} + digest_31: ${{ steps.digest.outputs.digest_31 }} + digest_32: ${{ steps.digest.outputs.digest_32 }} + digest_33: ${{ steps.digest.outputs.digest_33 }} + digest_34: ${{ steps.digest.outputs.digest_34 }} + digest_35: ${{ steps.digest.outputs.digest_35 }} + digest_36: ${{ steps.digest.outputs.digest_36 }} + digest_37: ${{ steps.digest.outputs.digest_37 }} + digest_38: ${{ steps.digest.outputs.digest_38 }} + digest_39: ${{ steps.digest.outputs.digest_39 }} + digest_40: ${{ steps.digest.outputs.digest_40 }} + digest_41: ${{ steps.digest.outputs.digest_41 }} + digest_42: ${{ steps.digest.outputs.digest_42 }} + digest_43: ${{ steps.digest.outputs.digest_43 }} + digest_44: ${{ steps.digest.outputs.digest_44 }} + digest_45: ${{ steps.digest.outputs.digest_45 }} + digest_46: ${{ steps.digest.outputs.digest_46 }} + digest_47: ${{ steps.digest.outputs.digest_47 }} + digest_48: ${{ steps.digest.outputs.digest_48 }} + digest_49: ${{ steps.digest.outputs.digest_49 }} + digest_50: ${{ steps.digest.outputs.digest_50 }} + digest_51: ${{ steps.digest.outputs.digest_51 }} + digest_52: ${{ steps.digest.outputs.digest_52 }} + digest_53: ${{ steps.digest.outputs.digest_53 }} + digest_54: ${{ steps.digest.outputs.digest_54 }} + digest_55: ${{ steps.digest.outputs.digest_55 }} + digest_56: ${{ steps.digest.outputs.digest_56 }} + digest_57: ${{ steps.digest.outputs.digest_57 }} + digest_58: ${{ steps.digest.outputs.digest_58 }} + digest_59: ${{ steps.digest.outputs.digest_59 }} + digest_60: ${{ steps.digest.outputs.digest_60 }} + digest_61: ${{ steps.digest.outputs.digest_61 }} + digest_62: ${{ steps.digest.outputs.digest_62 }} + digest_63: ${{ steps.digest.outputs.digest_63 }} + digest_64: ${{ steps.digest.outputs.digest_64 }} + digest_65: ${{ steps.digest.outputs.digest_65 }} + digest_66: ${{ steps.digest.outputs.digest_66 }} + digest_67: ${{ steps.digest.outputs.digest_67 }} + digest_68: ${{ steps.digest.outputs.digest_68 }} + digest_69: ${{ steps.digest.outputs.digest_69 }} + digest_70: ${{ steps.digest.outputs.digest_70 }} + digest_71: ${{ steps.digest.outputs.digest_71 }} + digest_72: ${{ steps.digest.outputs.digest_72 }} + digest_73: ${{ steps.digest.outputs.digest_73 }} + digest_74: ${{ steps.digest.outputs.digest_74 }} + digest_75: ${{ steps.digest.outputs.digest_75 }} + digest_76: ${{ steps.digest.outputs.digest_76 }} + digest_77: ${{ steps.digest.outputs.digest_77 }} + digest_78: ${{ steps.digest.outputs.digest_78 }} + digest_79: ${{ steps.digest.outputs.digest_79 }} + digest_80: ${{ steps.digest.outputs.digest_80 }} + digest_81: ${{ steps.digest.outputs.digest_81 }} + digest_82: ${{ steps.digest.outputs.digest_82 }} + digest_83: ${{ steps.digest.outputs.digest_83 }} + digest_84: ${{ steps.digest.outputs.digest_84 }} + digest_85: ${{ steps.digest.outputs.digest_85 }} + digest_86: ${{ steps.digest.outputs.digest_86 }} + digest_87: ${{ steps.digest.outputs.digest_87 }} + digest_88: ${{ steps.digest.outputs.digest_88 }} + digest_89: ${{ steps.digest.outputs.digest_89 }} + digest_90: ${{ steps.digest.outputs.digest_90 }} + digest_91: ${{ steps.digest.outputs.digest_91 }} + digest_92: ${{ steps.digest.outputs.digest_92 }} + digest_93: ${{ steps.digest.outputs.digest_93 }} + digest_94: ${{ steps.digest.outputs.digest_94 }} + digest_95: ${{ steps.digest.outputs.digest_95 }} + digest_96: ${{ steps.digest.outputs.digest_96 }} + digest_97: ${{ steps.digest.outputs.digest_97 }} + digest_98: ${{ steps.digest.outputs.digest_98 }} + digest_99: ${{ steps.digest.outputs.digest_99 }} + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.prepare.outputs.includes) }} + steps: + - + name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.meta-image }} + tags: ${{ inputs.meta-tags }} + flavor: ${{ inputs.meta-flavor }} + labels: ${{ inputs.meta-labels }} + annotations: ${{ inputs.meta-annotations }} + bake-target: ${{ inputs.meta-bake-target }} + - + name: Login to registry + uses: docker/login-action@v3 + if: ${{ inputs.push }} + with: + registry: ${{ inputs.login-registry }} + username: ${{ inputs.login-username || secrets.login-username }} + password: ${{ secrets.login-password }} + - + name: Set up QEMU + uses: docker/setup-qemu-action@v3 + if: ${{ inputs.setup-qemu }} + with: + image: ${{ inputs.qemu-image }} + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: ${{ inputs.buildx-version }} + driver-opts: ${{ inputs.buildx-driver-opts }} + buildkitd-flags: ${{ inputs.buildkitd-flags }} + buildkitd-config: ${{ inputs.buildkitd-config }} + buildkitd-config-inline: ${{ inputs.buildkitd-config-inline }} + cache-binary: ${{ inputs.buildx-cache-binary }} + - + name: Set bake files and overrides + id: bake-opts + uses: actions/github-script@v7 + env: + PLATFORM: ${{ matrix.platform }} + INPUT_TARGET: ${{ inputs.target }} + INPUT_CACHE: ${{ inputs.cache }} + INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} + INPUT_CACHE-MODE: ${{ inputs.cache-mode }} + INPUT_SET-META-ANNOTATIONS: ${{ inputs.set-meta-annotations }} + INPUT_SET-META-LABELS: ${{ inputs.set-meta-labels }} + INPUT_BAKE-FILES: ${{ inputs.bake-files }} + with: + script: | + await core.group(`Installing npm dependencies`, async () => { + await exec.exec('npm', ['install', + '@docker/actions-toolkit@${{ env.ACTIONS_TOOLKIT_VERSION }}' + ]); + }); + + const os = require('os'); + const { Util } = require('@docker/actions-toolkit/lib/util'); + + const platformPair = process.env.PLATFORM.replace(/\//g, '-'); + const inpTarget = core.getInput('target'); + const inpCache = core.getBooleanInput('cache'); + const inpCacheScope = core.getInput('cache-scope'); + const inpCacheMode = core.getInput('cache-mode'); + let bakeFiles = Util.getInputList('bake-files'); + const inpSetMetaAnnotations = core.getBooleanInput('set-meta-annotations'); + const inpSetMetaLabels = core.getBooleanInput('set-meta-labels'); + + await core.group(`Set bake files`, async () => { + if (bakeFiles.length === 0) { + bakeFiles = ['docker-bake.hcl']; + } + bakeFiles.push(`cwd://${{ steps.meta.outputs.bake-file-tags }}`); + if (inpSetMetaAnnotations) { + bakeFiles.push(`cwd://${{ steps.meta.outputs.bake-file-annotations }}`); + } + if (inpSetMetaLabels) { + bakeFiles.push(`cwd://${{ steps.meta.outputs.bake-file-labels }}`); + } + core.info(JSON.stringify(bakeFiles, null, 2)); + core.setOutput('files', bakeFiles.join(os.EOL)); + }); + + await core.group(`Set bake overrides`, async () => { + let bakeOverrides = []; + if (inpCache) { + bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || inpTarget}-warmup`); + bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || inpTarget}-${platformPair}`); + bakeOverrides.push(`*.cache-to=type=gha,scope=${inpCacheScope || inpTarget}-${platformPair},mode=${inpCacheMode}`); + } + core.info(JSON.stringify(bakeOverrides, null, 2)); + core.setOutput('overrides', bakeOverrides.join(os.EOL)); + }); + - + name: Build + id: bake + uses: docker/bake-action@v6 + with: + source: ${{ inputs.bake-source }} + files: ${{ steps.bake-opts.outputs.files }} + targets: ${{ inputs.target }} + allow: ${{ inputs.bake-allow }} + no-cache: ${{ inputs.bake-no-cache }} + pull: ${{ inputs.bake-pull }} + provenance: ${{ inputs.bake-provenance }} + sbom: ${{ inputs.bake-sbom }} + set: | + ${{ inputs.bake-set }} + ${{ steps.bake-opts.outputs.overrides }} + *.tags= + *.platform=${{ matrix.platform }} + *.output=type=image,"name=${{ inputs.meta-image }}",push-by-digest=true,name-canonical=true,push=${{ inputs.push }} + github-token: ${{ secrets.github-token || github.token }} + - + name: Set digest output + id: digest + uses: actions/github-script@v7 + env: + INPUT_TARGET: ${{ inputs.target }} + with: + script: | + const metadata = JSON.parse(`${{ steps.bake.outputs.metadata }}`); + const digest = metadata[core.getInput('target')]['containerimage.digest']; + const outputKey = `digest_${{ matrix.index }}`; + core.info(`Setting digest output: ${outputKey}=${digest}`); + core.setOutput(outputKey, digest); + + merge: + runs-on: ubuntu-latest + needs: + - build + steps: + - + name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.meta-image }} + tags: ${{ inputs.meta-tags }} + flavor: ${{ inputs.meta-flavor }} + bake-target: ${{ inputs.meta-bake-target }} + - + name: Login to registry + uses: docker/login-action@v3 + if: ${{ inputs.push }} + with: + registry: ${{ inputs.login-registry }} + username: ${{ inputs.login-username || secrets.login-username }} + password: ${{ secrets.login-password }} + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + version: ${{ inputs.buildx-version }} + driver-opts: ${{ inputs.buildx-driver-opts }} + buildkitd-flags: ${{ inputs.buildkitd-flags }} + buildkitd-config: ${{ inputs.buildkitd-config }} + buildkitd-config-inline: ${{ inputs.buildkitd-config-inline }} + cache-binary: ${{ inputs.buildx-cache-binary }} + - + name: Create manifest list + uses: actions/github-script@v7 + env: + INPUT_PUSH: ${{ inputs.push }} + INPUT_META-IMAGE: ${{ inputs.meta-image }} + with: + script: | + const inpPush = core.getBooleanInput('push'); + const inpMetaImage = core.getInput('meta-image'); + + let digests = []; + await core.group(`Digests`, async () => { + digests = Object.values(JSON.parse(`${{ toJSON(needs.build.outputs) }}`)); + core.info(JSON.stringify(digests, null, 2)); + }); + + let tags = []; + await core.group(`Tags`, async () => { + tags = `${{ steps.meta.outputs.tags }}`.split('\n').filter(Boolean); + core.info(JSON.stringify(tags, null, 2)); + }); + + let createArgs = ['buildx', 'imagetools', 'create']; + for (const tag of tags) { + createArgs.push(`-t`, tag); + } + for (const digest of digests) { + createArgs.push(`${inpMetaImage}@${digest}`); + } + + if (inpPush) { + if (tags.length === 0) { + throw new Error('No tags to create manifest list'); + } + await exec.getExecOutput('docker', createArgs, { + ignoreReturnCode: true + }).then(res => { + if (res.stderr.length > 0 && res.exitCode != 0) { + throw new Error(res.stderr); + } + }); + await core.group(`Inspect image`, async () => { + await exec.getExecOutput('docker', ['buildx', 'imagetools', 'inspect', `${inpMetaImage}:${tags[0]}`], { + ignoreReturnCode: true + }).then(res => { + if (res.stderr.length > 0 && res.exitCode != 0) { + throw new Error(res.stderr); + } + }); + }); + } else { + await core.group(`Generated imagetools create command`, async () => { + core.info(`docker ${createArgs.join(' ')}`); + }); + core.info(`Push is disabled, skipping manifest list creation`); + } diff --git a/README.md b/README.md index 67a27440..e512e6a5 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,14 @@ jobs: *.tags=user/app:latest ``` +## Examples + +* [Distribute multi-platform build across runners](https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-multi-platform-build-across-runners) +* [Annotations](https://docs.docker.com/build/ci/github-actions/annotations/) +* [Secrets](https://docs.docker.com/build/ci/github-actions/secrets/) +* [Build checks](https://docs.docker.com/build/ci/github-actions/checks/#run-checks-with-dockerbake-action) +* [Reproducible builds](https://docs.docker.com/build/ci/github-actions/reproducible-builds/) + ## Summaries This action generates a [job summary](https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/) diff --git a/test/config.hcl b/test/config.hcl index 6e48771b..47303bf6 100644 --- a/test/config.hcl +++ b/test/config.hcl @@ -1,3 +1,5 @@ +target "docker-metadata-action" {} + group "default" { targets = ["db", "app"] } @@ -12,6 +14,7 @@ target "db" { } target "app" { + inherits = ["docker-metadata-action"] context = "./test" dockerfile = "Dockerfile" args = { diff --git a/test/go/docker-bake.hcl b/test/go/docker-bake.hcl index 639f5c47..52008482 100644 --- a/test/go/docker-bake.hcl +++ b/test/go/docker-bake.hcl @@ -1,3 +1,5 @@ +target "docker-metadata-action" {} + variable "DESTDIR" { default = "/tmp/bake-build" } @@ -15,3 +17,13 @@ target "image" { target = "image" tags = ["localhost:5000/name/app:latest"] } + +target "image-all" { + inherits = ["binary"] + platforms = [ + "linux/amd64", + "linux/arm64", + "linux/arm/v7", + "linux/arm/v6" + ] +}