From 33915997efea8151a617f37357f14f78e8ddebc0 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Mon, 13 Apr 2026 13:47:58 +0200 Subject: [PATCH] add registry-login input for optional registry auth before build Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- .github/workflows/.test-bake.yml | 22 ++++++++++++++++++ .github/workflows/.test-build.yml | 21 +++++++++++++++++ .github/workflows/bake.yml | 28 +++++++++++++++++++---- .github/workflows/build.yml | 22 ++++++++++++++++-- README.md | 38 ++++++++++++++++++++++++------- test/dhi.Dockerfile | 9 ++++++++ test/docker-bake.hcl | 5 ++++ 7 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 test/dhi.Dockerfile diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index 6516e5d2..977f600f 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -621,3 +621,25 @@ jobs: - registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + + bake-local-login: + uses: ./.github/workflows/bake.yml + if: ${{ github.event_name != 'pull_request' }} + permissions: + contents: read + id-token: write + with: + artifact-name: bake-login-output + artifact-upload: true + context: test + output: local + registry-login: true + sbom: true + sign: true + target: dhi + secrets: + registry-auths: | + - registry: dhi.io + username: ${{ vars.DOCKERPUBLICBOT_USERNAME }} + password: ${{ secrets.DOCKERPUBLICBOT_READ_PAT }} + scope: 'dhi.io@pull' diff --git a/.github/workflows/.test-build.yml b/.github/workflows/.test-build.yml index 13639a0b..89076cd1 100644 --- a/.github/workflows/.test-build.yml +++ b/.github/workflows/.test-build.yml @@ -640,3 +640,24 @@ jobs: - registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + + build-local-login: + uses: ./.github/workflows/build.yml + if: ${{ github.event_name != 'pull_request' }} + permissions: + contents: read + id-token: write + with: + artifact-name: build-login-output + artifact-upload: true + file: test/dhi.Dockerfile + output: local + registry-login: true + sbom: true + sign: true + secrets: + registry-auths: | + - registry: dhi.io + username: ${{ vars.DOCKERPUBLICBOT_USERNAME }} + password: ${{ secrets.DOCKERPUBLICBOT_READ_PAT }} + scope: 'dhi.io@pull' diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index 16a40e19..ee056de7 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -65,6 +65,11 @@ on: description: "Push image to the registry (for image output)" required: false default: false + registry-login: + type: string + description: "Login to registry before build (one of auto, true or false). Auto enables login only when output is image and push is true" + required: false + default: auto sbom: type: boolean description: "Generate SBOM attestation for the build" @@ -125,7 +130,7 @@ on: required: false secrets: registry-auths: - description: "Raw authentication to registries, defined as YAML objects (for image output)" + description: "Raw authentication to registries, defined as YAML objects" required: false github-token: description: "GitHub Token used to authenticate against the repository for Git context" @@ -172,6 +177,7 @@ jobs: includes: ${{ steps.set.outputs.includes }} sign: ${{ steps.set.outputs.sign }} ghaCacheSign: ${{ steps.set.outputs.ghaCacheSign }} + registryLogin: ${{ steps.set.outputs.registryLogin }} steps: - name: Install dependencies @@ -258,6 +264,7 @@ jobs: INPUT_FILES: ${{ inputs.files }} INPUT_OUTPUT: ${{ inputs.output }} INPUT_PUSH: ${{ inputs.push }} + INPUT_REGISTRY-LOGIN: ${{ inputs.registry-login }} INPUT_SBOM: ${{ inputs.sbom }} INPUT_SET: ${{ inputs.set }} INPUT_SIGN: ${{ inputs.sign }} @@ -284,6 +291,7 @@ jobs: const inpFiles = Util.getInputList('files'); const inpOutput = core.getInput('output'); const inpPush = core.getBooleanInput('push'); + const inpRegistryLogin = core.getInput('registry-login'); const inpSbom = core.getBooleanInput('sbom'); const inpSet = Util.getInputList('set', {ignoreComma: true, quote: false}); const inpSign = core.getInput('sign'); @@ -314,12 +322,18 @@ jobs: core.setFailed(`signing attestation manifests requires push to be enabled`); return; } - + const bakeSource = await new Build().gitContext({subdir: inpContext}); await core.group(`Set bake source`, async () => { core.info(bakeSource); }); + if (!['auto', 'true', 'false'].includes(inpRegistryLogin)) { + core.setFailed(`Invalid registry-login input: ${inpRegistryLogin}`); + return; + } + const registryLogin = inpRegistryLogin === 'auto' ? inpOutput === 'image' && inpPush : inpRegistryLogin === 'true'; + const envs = Object.assign({}, inpVars ? inpVars.reduce((acc, curr) => { const idx = curr.indexOf('='); @@ -336,7 +350,7 @@ jobs: await core.group(`Set envs`, async () => { core.info(JSON.stringify(envs, null, 2)); }); - + let def; let target; try { @@ -405,7 +419,7 @@ jobs: core.setFailed(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); return; } - + const privateRepo = GitHub.context.payload.repository?.private ?? false; await core.group(`Set privateRepo output`, async () => { core.info(`privateRepo: ${privateRepo}`); @@ -440,6 +454,10 @@ jobs: core.info(`ghaCacheSign: ${ghaCacheSign}`); core.setOutput('ghaCacheSign', ghaCacheSign); }); + await core.group(`Set registryLogin output`, async () => { + core.info(`registryLogin: ${registryLogin}`); + core.setOutput('registryLogin', registryLogin); + }); build: runs-on: ${{ matrix.runner }} @@ -782,7 +800,7 @@ jobs: }); - name: Login to registry - if: ${{ inputs.push && inputs.output == 'image' }} + if: ${{ needs.prepare.outputs.registryLogin == 'true' }} uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry-auth: ${{ secrets.registry-auths }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c94ac78f..4c11f7f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,6 +81,11 @@ on: description: "Push image to the registry (for image output)" required: false default: false + registry-login: + type: string + description: "Login to registry before build (one of auto, true or false). Auto enables login only when output is image and push is true" + required: false + default: auto sbom: type: boolean description: "Generate SBOM attestation for the build" @@ -128,7 +133,7 @@ on: required: false secrets: registry-auths: - description: "Raw authentication to registries, defined as YAML objects (for image output)" + description: "Raw authentication to registries, defined as YAML objects" required: false github-token: description: "GitHub Token used to authenticate against the repository for Git context" @@ -176,6 +181,7 @@ jobs: sign: ${{ steps.set.outputs.sign }} privateRepo: ${{ steps.set.outputs.privateRepo }} ghaCacheSign: ${{ steps.set.outputs.ghaCacheSign }} + registryLogin: ${{ steps.set.outputs.registryLogin }} steps: - name: Install dependencies @@ -252,6 +258,7 @@ jobs: INPUT_OUTPUT: ${{ inputs.output }} INPUT_PLATFORMS: ${{ inputs.platforms }} INPUT_PUSH: ${{ inputs.push }} + INPUT_REGISTRY-LOGIN: ${{ inputs.registry-login }} INPUT_SIGN: ${{ inputs.sign }} with: script: | @@ -267,6 +274,7 @@ jobs: const inpPlatforms = Util.getInputList('platforms'); const inpOutput = core.getInput('output'); const inpPush = core.getBooleanInput('push'); + const inpRegistryLogin = core.getInput('registry-login'); const inpSign = core.getInput('sign'); let runner = inpRunner; @@ -294,6 +302,12 @@ jobs: return; } + if (!['auto', 'true', 'false'].includes(inpRegistryLogin)) { + core.setFailed(`Invalid registry-login input: ${inpRegistryLogin}`); + return; + } + const registryLogin = inpRegistryLogin === 'auto' ? inpOutput === 'image' && inpPush : inpRegistryLogin === 'true'; + if (inpDistribute && inpPlatforms.length > inpMatrixSizeLimit) { core.setFailed(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); return; @@ -333,6 +347,10 @@ jobs: core.info(`ghaCacheSign: ${ghaCacheSign}`); core.setOutput('ghaCacheSign', ghaCacheSign); }); + await core.group(`Set registryLogin output`, async () => { + core.info(`registryLogin: ${registryLogin}`); + core.setOutput('registryLogin', registryLogin); + }); build: runs-on: ${{ matrix.runner }} @@ -640,7 +658,7 @@ jobs: } - name: Login to registry - if: ${{ inputs.push && inputs.output == 'image' }} + if: ${{ needs.prepare.outputs.registryLogin == 'true' }} uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry-auth: ${{ secrets.registry-auths }} diff --git a/README.md b/README.md index 25eb5535..477e6f59 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,7 @@ jobs: | `output` | String | | Build output destination (one of [`image`](https://docs.docker.com/build/exporters/image-registry/) or [`local`](https://docs.docker.com/build/exporters/local-tar/)). Unlike the `build-push-action`, it only accepts `image` or `local`. The reusable workflow takes care of setting the `outputs` attribute | | `platforms` | List/CSV | | List of [target platforms](https://docs.docker.com/engine/reference/commandline/buildx_build/#platform) to build | | `push` | Bool | `false` | [Push](https://docs.docker.com/engine/reference/commandline/buildx_build/#push) image to the registry (for `image` output) | +| `registry-login` | String | `auto` | Login to registry before build (one of `auto`, `true` or `false`). `auto` enables login only when output is `image` and push is `true` | | `sbom` | Bool | `false` | Generate [SBOM](https://docs.docker.com/build/attestations/sbom/) attestation for the build | | `shm-size` | String | | Size of [`/dev/shm`](https://docs.docker.com/engine/reference/commandline/buildx_build/#shm-size) (e.g., `2g`) | | `sign` | String | `auto` | Sign attestation manifest for `image` output or artifacts for `local` output, can be one of `auto`, `true` or `false`. The `auto` mode will enable signing if `push` is enabled for pushing the `image` or if `artifact-upload` is enabled for uploading the `local` build output as GitHub Artifact | @@ -250,6 +251,16 @@ jobs: | `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` | +> [!NOTE] +> `registry-login: true` forces a pre-build login attempt and will fail if the +> resolved credentials are empty, for example, on forked pull requests where +> secrets are not exposed. Gate this input at the caller side if you need +> fork-safe behavior: +> +> ```yaml +> registry-login: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }} +> ``` + > [!TIP] > When `output=image`, following inputs support Handlebars templates rendered > from selected `docker/metadata-action` outputs: @@ -275,10 +286,10 @@ jobs: #### Secrets -| Name | Default | Description | -|------------------|-----------------------|--------------------------------------------------------------------------------| -| `registry-auths` | | Raw authentication to registries, defined as YAML objects (for `image` output) | -| `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | +| Name | Default | Description | +|------------------|-----------------------|----------------------------------------------------------------------------------------------------------------| +| `registry-auths` | | Raw authentication to registries, defined as YAML objects (used for push/signing and optional pre-build login) | +| `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | #### Outputs @@ -384,6 +395,7 @@ jobs: | `files` | List | `{context}/docker-bake.hcl` | List of bake definition files | | `output` | String | | Build output destination (one of [`image`](https://docs.docker.com/build/exporters/image-registry/) or [`local`](https://docs.docker.com/build/exporters/local-tar/)). | | `push` | Bool | `false` | Push image to the registry (for `image` output) | +| `registry-login` | String | `auto` | Login to registry before build (one of `auto`, `true` or `false`). `auto` enables login only when output is `image` and push is `true` | | `sbom` | Bool | `false` | Generate [SBOM](https://docs.docker.com/build/attestations/sbom/) attestation for the build | | `set` | List | | List of [target values to override](https://docs.docker.com/engine/reference/commandline/buildx_bake/#set) (e.g., `targetpattern.key=value`) | | `sign` | String | `auto` | Sign attestation manifest for `image` output or artifacts for `local` output, can be one of `auto`, `true` or `false`. The `auto` mode will enable signing if `push` is enabled for pushing the `image` or if `artifact-upload` is enabled for uploading the `local` build output as GitHub Artifact | @@ -397,6 +409,16 @@ jobs: | `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` | +> [!NOTE] +> `registry-login: true` forces a pre-build login attempt and will fail if the +> resolved credentials are empty, for example, on forked pull requests where +> secrets are not exposed. Gate this input at the caller side if you need +> fork-safe behavior: +> +> ```yaml +> registry-login: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }} +> ``` + > [!TIP] > When `output=image`, the `set` input supports Handlebars templates rendered > from selected `docker/metadata-action` outputs. @@ -419,10 +441,10 @@ jobs: #### Secrets -| Name | Default | Description | -|------------------|-----------------------|--------------------------------------------------------------------------------| -| `registry-auths` | | Raw authentication to registries, defined as YAML objects (for `image` output) | -| `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | +| Name | Default | Description | +|------------------|-----------------------|----------------------------------------------------------------------------------------------------------------| +| `registry-auths` | | Raw authentication to registries, defined as YAML objects (used for push/signing and optional pre-build login) | +| `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | #### Outputs diff --git a/test/dhi.Dockerfile b/test/dhi.Dockerfile new file mode 100644 index 00000000..dc651c19 --- /dev/null +++ b/test/dhi.Dockerfile @@ -0,0 +1,9 @@ +# syntax=docker/dockerfile:1 + +FROM dhi.io/alpine-base:3.23 AS base +ARG TARGETPLATFORM +RUN echo "Hello, World! This is ${TARGETPLATFORM}" > /tmp/hello.txt +ARG BUILDKIT_SBOM_SCAN_STAGE=true + +FROM scratch +COPY --from=base /tmp/hello.txt / diff --git a/test/docker-bake.hcl b/test/docker-bake.hcl index fbea740c..b00cec7b 100644 --- a/test/docker-bake.hcl +++ b/test/docker-bake.hcl @@ -67,3 +67,8 @@ target "generated-hello2" { dockerfile = "hello.Dockerfile" output = ["type=cacheonly"] } + +target "dhi" { + inherits = ["docker-metadata-action"] + dockerfile = "dhi.Dockerfile" +}