diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index 88ae13fe..2ae83e0a 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -39,6 +39,8 @@ jobs: output: image push: ${{ github.event_name != 'pull_request' }} sbom: true + set: | + *.args.VERSION={{meta.version}} target: hello meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action @@ -90,6 +92,8 @@ jobs: output: image push: ${{ github.event_name != 'pull_request' }} sbom: true + set: | + *.args.VERSION={{meta.version}} target: hello-cross meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action @@ -160,6 +164,8 @@ jobs: output: image push: ${{ github.event_name != 'pull_request' }} sbom: true + set: | + *.args.VERSION={{meta.version}} sign: false target: hello-cross meta-images: | @@ -210,6 +216,8 @@ jobs: output: image push: ${{ github.event_name != 'pull_request' }} sbom: true + set: | + *.args.VERSION={{meta.version}} target: hello-cross meta-images: | registry-1-stage.docker.io/docker/github-builder-test @@ -260,6 +268,8 @@ jobs: output: image push: ${{ github.event_name != 'pull_request' }} sbom: true + set: | + *.args.VERSION={{meta.version}} target: hello-cross meta-images: | ghcr.io/docker/github-builder-test @@ -460,6 +470,8 @@ jobs: context: test output: image push: false + set: | + *.args.VERSION={{meta.version}} target: hello-cross meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action @@ -492,6 +504,8 @@ jobs: output: image push: ${{ github.event_name != 'pull_request' }} sbom: true + set: | + *.args.VERSION={{meta.version}} target: hello-cross meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action @@ -542,6 +556,8 @@ jobs: output: image push: ${{ github.event_name != 'pull_request' }} sbom: true + set: | + *.args.VERSION={{meta.version}} target: hello-cross meta-images: | registry-1-stage.docker.io/docker/github-builder-test diff --git a/.github/workflows/.test-build.yml b/.github/workflows/.test-build.yml index e589b522..7139fdd8 100644 --- a/.github/workflows/.test-build.yml +++ b/.github/workflows/.test-build.yml @@ -33,6 +33,8 @@ jobs: contents: read id-token: write with: + build-args: | + VERSION={{meta.version}} cache: true cache-scope: build-aws-single file: test/hello.Dockerfile @@ -84,6 +86,8 @@ jobs: contents: read id-token: write with: + build-args: | + VERSION={{meta.version}} cache: true cache-scope: build-aws file: test/hello.Dockerfile @@ -154,6 +158,8 @@ jobs: contents: read id-token: write with: + build-args: | + VERSION={{meta.version}} cache: true cache-scope: build-aws-nosign file: test/hello.Dockerfile @@ -207,6 +213,8 @@ jobs: id-token: write packages: write with: + build-args: | + VERSION={{meta.version}} file: test/hello.Dockerfile output: image platforms: linux/amd64,linux/arm64 @@ -255,6 +263,8 @@ jobs: contents: read id-token: write with: + build-args: | + VERSION={{meta.version}} file: test/hello.Dockerfile output: image platforms: linux/amd64,linux/arm64 @@ -304,6 +314,8 @@ jobs: id-token: write packages: write with: + build-args: | + VERSION={{meta.version}} file: test/hello.Dockerfile output: image platforms: linux/amd64,linux/arm64 @@ -504,6 +516,8 @@ jobs: id-token: write with: runner: amd64 + build-args: | + VERSION={{meta.version}} file: test/hello.Dockerfile output: image platforms: linux/amd64,linux/arm64 @@ -532,6 +546,8 @@ jobs: id-token: write with: distribute: false + build-args: | + VERSION={{meta.version}} cache: true cache-scope: build-aws-nodistrib file: test/hello.Dockerfile @@ -570,6 +586,8 @@ jobs: contents: read id-token: write with: + build-args: | + VERSION={{meta.version}} file: test/hello.Dockerfile output: image platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index 3c18054a..e97f2744 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -154,6 +154,7 @@ env: SBOM_IMAGE: "docker/buildkit-syft-scanner:1.10.0" BINFMT_IMAGE: "tonistiigi/binfmt:qemu-v10.2.1-65" DOCKER_ACTIONS_TOOLKIT_MODULE: "@docker/actions-toolkit@0.81.0" + HANDLEBARS_MODULE: "handlebars@4.7.8" COSIGN_VERSION: "v3.0.2" LOCAL_EXPORT_DIR: "/tmp/buildx-output" MATRIX_SIZE_LIMIT: "20" @@ -167,7 +168,7 @@ jobs: ghaCacheSign: ${{ steps.set.outputs.ghaCacheSign }} steps: - - name: Install @docker/actions-toolkit + name: Install dependencies uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_DAT-MODULE: ${{ env.DOCKER_ACTIONS_TOOLKIT_MODULE }} @@ -458,13 +459,20 @@ jobs: result_19: ${{ steps.result.outputs.result_19 }} steps: - - name: Install @docker/actions-toolkit + name: Install dependencies uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_DAT-MODULE: ${{ env.DOCKER_ACTIONS_TOOLKIT_MODULE }} + INPUT_HANDLEBARS-MODULE: ${{ env.HANDLEBARS_MODULE }} with: script: | - await exec.exec('npm', ['install', '--prefer-offline', '--ignore-scripts', core.getInput('dat-module')]); + await exec.exec('npm', [ + 'install', + '--prefer-offline', + '--ignore-scripts', + core.getInput('dat-module'), + core.getInput('handlebars-module') + ]); - name: Docker meta id: meta @@ -610,6 +618,8 @@ jobs: INPUT_TARGET: ${{ inputs.target }} INPUT_VARS: ${{ inputs.vars }} INPUT_META-IMAGES: ${{ inputs.meta-images }} + INPUT_META-VERSION: ${{ steps.meta.outputs.version }} + INPUT_META-TAGS: ${{ steps.meta.outputs.tags }} INPUT_SET-META-ANNOTATIONS: ${{ inputs.set-meta-annotations }} INPUT_SET-META-LABELS: ${{ inputs.set-meta-labels }} INPUT_BAKE-FILE-TAGS: ${{ steps.meta.outputs.bake-file-tags }} @@ -619,6 +629,7 @@ jobs: with: script: | const os = require('os'); + const Handlebars = require('handlebars'); const { Build } = require('@docker/actions-toolkit/lib/buildx/build'); const { GitHub } = require('@docker/actions-toolkit/lib/github/github'); const { Util } = require('@docker/actions-toolkit/lib/util'); @@ -642,12 +653,20 @@ jobs: const inpTarget = core.getInput('target'); const inpVars = Util.getInputList('vars'); const inpMetaImages = core.getMultilineInput('meta-images'); + const inpMetaVersion = core.getInput('meta-version'); + const inpMetaTags = core.getMultilineInput('meta-tags'); const inpSetMetaAnnotations = core.getBooleanInput('set-meta-annotations'); const inpSetMetaLabels = core.getBooleanInput('set-meta-labels'); const inpBakeFileTags = core.getInput('bake-file-tags'); const inpBakeFileAnnotations = core.getInput('bake-file-annotations'); const inpBakeFileLabels = core.getInput('bake-file-labels'); const inpGitHubToken = core.getInput('github-token'); + + const meta = { + version: inpMetaVersion, + tags: inpMetaTags + }; + const renderTemplate = value => Handlebars.compile(value, {noEscape: true})({meta}); const bakeSource = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}.git#${process.env.GITHUB_REF}:${inpContext}`; await core.group(`Set source output`, async () => { @@ -719,8 +738,15 @@ jobs: return; } - let bakeOverrides = [...inpSet, outputOverride]; + let bakeOverrides = []; + try { + bakeOverrides = inpSet.map(override => renderTemplate(override)); + } catch (err) { + core.setFailed(`Failed to render Handlebars template: ${err.message}`); + return; + } await core.group(`Set bake overrides`, async () => { + bakeOverrides.push(outputOverride); bakeOverrides.push('*.tags='); if (GitHub.context.payload.repository?.private ?? false) { // if this is a private repository, we set min provenance mode @@ -915,7 +941,7 @@ jobs: - build steps: - - name: Install @docker/actions-toolkit + name: Install dependencies uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_DAT-MODULE: ${{ env.DOCKER_ACTIONS_TOOLKIT_MODULE }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa2d77c7..fd9581a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -157,6 +157,7 @@ env: SBOM_IMAGE: "docker/buildkit-syft-scanner:1.10.0" BINFMT_IMAGE: "tonistiigi/binfmt:qemu-v10.2.1-65" DOCKER_ACTIONS_TOOLKIT_MODULE: "@docker/actions-toolkit@0.81.0" + HANDLEBARS_MODULE: "handlebars@4.7.8" COSIGN_VERSION: "v3.0.2" LOCAL_EXPORT_DIR: "/tmp/buildx-output" MATRIX_SIZE_LIMIT: "20" @@ -171,7 +172,7 @@ jobs: ghaCacheSign: ${{ steps.set.outputs.ghaCacheSign }} steps: - - name: Install @docker/actions-toolkit + name: Install dependencies uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_DAT-MODULE: ${{ env.DOCKER_ACTIONS_TOOLKIT_MODULE }} @@ -360,13 +361,20 @@ jobs: result_19: ${{ steps.result.outputs.result_19 }} steps: - - name: Install @docker/actions-toolkit + name: Install dependencies uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_DAT-MODULE: ${{ env.DOCKER_ACTIONS_TOOLKIT_MODULE }} + INPUT_HANDLEBARS-MODULE: ${{ env.HANDLEBARS_MODULE }} with: script: | - await exec.exec('npm', ['install', '--prefer-offline', '--ignore-scripts', core.getInput('dat-module')]); + await exec.exec('npm', [ + 'install', + '--prefer-offline', + '--ignore-scripts', + core.getInput('dat-module'), + core.getInput('handlebars-module') + ]); - name: Docker meta id: meta @@ -501,6 +509,7 @@ jobs: INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} INPUT_DISTRIBUTE: ${{ inputs.distribute }} INPUT_ANNOTATIONS: ${{ inputs.annotations }} + INPUT_BUILD-ARGS: ${{ inputs.build-args }} INPUT_CACHE: ${{ inputs.cache }} INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} INPUT_CACHE-MODE: ${{ inputs.cache-mode }} @@ -512,6 +521,8 @@ jobs: INPUT_SBOM: ${{ inputs.sbom }} INPUT_TARGET: ${{ inputs.target }} INPUT_META-IMAGES: ${{ inputs.meta-images }} + INPUT_META-VERSION: ${{ steps.meta.outputs.version }} + INPUT_META-TAGS: ${{ steps.meta.outputs.tags }} INPUT_SET-META-ANNOTATIONS: ${{ inputs.set-meta-annotations }} INPUT_META-ANNOTATIONS: ${{ steps.meta.outputs.annotations }} INPUT_SET-META-LABELS: ${{ inputs.set-meta-labels }} @@ -520,6 +531,7 @@ jobs: script: | const { Build } = require('@docker/actions-toolkit/lib/buildx/build'); const { GitHub } = require('@docker/actions-toolkit/lib/github/github'); + const Handlebars = require('handlebars'); const inpPlatform = core.getInput('platform'); const platformPairSuffix = inpPlatform ? `-${inpPlatform.replace(/\//g, '-')}` : ''; @@ -529,12 +541,13 @@ jobs: const inpLocalExportDir = core.getInput('local-export-dir'); const inpDistribute = core.getBooleanInput('distribute'); - const inpAnnotations = core.getMultilineInput('annotations'); + const inpAnnotations = core.getInput('annotations'); + const inpBuildArgs = core.getInput('build-args'); const inpCache = core.getBooleanInput('cache'); const inpCacheScope = core.getInput('cache-scope'); const inpCacheMode = core.getInput('cache-mode'); const inpContext = core.getInput('context'); - const inpLabels = core.getMultilineInput('labels'); + const inpLabels = core.getInput('labels'); const inpOutput = core.getInput('output'); const inpPlatforms = core.getInput('platforms'); const inpPush = core.getBooleanInput('push'); @@ -542,11 +555,21 @@ jobs: const inpTarget = core.getInput('target'); const inpMetaImages = core.getMultilineInput('meta-images'); + const inpMetaVersion = core.getInput('meta-version'); + const inpMetaTags = core.getMultilineInput('meta-tags'); const inpSetMetaAnnotations = core.getBooleanInput('set-meta-annotations'); const inpMetaAnnotations = core.getMultilineInput('meta-annotations'); const inpSetMetaLabels = core.getBooleanInput('set-meta-labels'); const inpMetaLabels = core.getMultilineInput('meta-labels'); + const meta = { + version: inpMetaVersion, + tags: inpMetaTags + }; + + const renderTemplate = value => Handlebars.compile(value, {noEscape: true})({meta}); + const toMultilineInput = value => value.split(/\r?\n/).map(line => line.trim()).filter(Boolean); + const buildContext = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}.git#${process.env.GITHUB_REF}:${inpContext}`; core.setOutput('context', buildContext); @@ -579,15 +602,28 @@ jobs: core.setOutput('cache-to', `type=gha,ignore-error=true,scope=${inpCacheScope || inpTarget || 'buildkit'}${platformPairSuffix},mode=${inpCacheMode}`); } + let annotations; + let labels; + let buildArgs; + try { + annotations = toMultilineInput(renderTemplate(inpAnnotations)); + labels = toMultilineInput(renderTemplate(inpLabels)); + buildArgs = renderTemplate(inpBuildArgs); + } catch (err) { + core.setFailed(`Failed to render Handlebars template: ${err.message}`); + return; + } + if (inpSetMetaAnnotations && inpMetaAnnotations.length > 0) { - inpAnnotations.push(...inpMetaAnnotations); + annotations.push(...inpMetaAnnotations); } - core.setOutput('annotations', inpAnnotations.join('\n')); + core.setOutput('annotations', annotations.join('\n')); if (inpSetMetaLabels && inpMetaLabels.length > 0) { - inpLabels.push(...inpMetaLabels); + labels.push(...inpMetaLabels); } - core.setOutput('labels', inpLabels.join('\n')); + core.setOutput('labels', labels.join('\n')); + core.setOutput('build-args', buildArgs); if (GitHub.context.payload.repository?.private ?? false) { // if this is a private repository, we set min provenance mode @@ -608,7 +644,7 @@ jobs: uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0 with: annotations: ${{ steps.prepare.outputs.annotations }} - build-args: ${{ inputs.build-args }} + build-args: ${{ steps.prepare.outputs.build-args }} cache-from: ${{ steps.prepare.outputs.cache-from }} cache-to: ${{ steps.prepare.outputs.cache-to }} context: ${{ steps.prepare.outputs.context }} @@ -769,7 +805,7 @@ jobs: - build steps: - - name: Install @docker/actions-toolkit + name: Install dependencies uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_DAT-MODULE: ${{ env.DOCKER_ACTIONS_TOOLKIT_MODULE }} diff --git a/README.md b/README.md index c15f5c31..742320d2 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,29 @@ on: | `meta-tags` | List | | [List of tags](https://github.com/docker/metadata-action?tab=readme-ov-file#tags-input) as key-value pair attributes | | `meta-flavor` | List | | [Flavor](https://github.com/docker/metadata-action?tab=readme-ov-file#flavor-input) defines a global behavior for `meta-tags` | +> [!TIP] +> When `output=image`, following inputs support Handlebars templates rendered +> from selected `docker/metadata-action` outputs: +> - `annotations` +> - `build-args` +> - `labels` +> +> The template context is exposed as `meta` with: +> - `meta.version` +> - `meta.tags` +> +> Example: +> ```yaml +> jobs: +> build: +> uses: docker/github-builder/.github/workflows/build.yml@v1 +> with: +> output: image +> build-args: | +> VERSION={{meta.version}} +> meta-images: name/app +> ``` + #### Secrets | Name | Default | Description | @@ -369,6 +392,26 @@ on: | `meta-annotations` | List | | [List of custom annotations](https://github.com/docker/metadata-action?tab=readme-ov-file#overwrite-labels-and-annotations) | | `meta-flavor` | List | | [Flavor](https://github.com/docker/metadata-action?tab=readme-ov-file#flavor-input) defines a global behavior for `meta-tags` | +> [!TIP] +> When `output=image`, the `set` input supports Handlebars templates rendered +> from selected `docker/metadata-action` outputs. +> +> The template context is exposed as `meta` with: +> - `meta.version` +> - `meta.tags` +> +> Example: +> ```yaml +> jobs: +> bake: +> uses: docker/github-builder/.github/workflows/bake.yml@v1 +> with: +> output: image +> set: | +> *.args.VERSION={{meta.version}} +> meta-images: name/app +> ``` + #### Secrets | Name | Default | Description | diff --git a/test/hello.Dockerfile b/test/hello.Dockerfile index 8b6ad54d..78fa5b8f 100644 --- a/test/hello.Dockerfile +++ b/test/hello.Dockerfile @@ -2,7 +2,8 @@ FROM alpine AS base ARG TARGETPLATFORM -RUN echo "Hello, World! This is ${TARGETPLATFORM}" > /hello.txt +ARG VERSION="unknown" +RUN echo "Hello, World! This is ${VERSION} running on ${TARGETPLATFORM}" > /hello.txt ARG BUILDKIT_SBOM_SCAN_STAGE=true FROM scratch