diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index 23a4e3d1..d756cb97 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -667,3 +667,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 7f74b8e1..2df6984b 100644 --- a/.github/workflows/.test-build.yml +++ b/.github/workflows/.test-build.yml @@ -685,3 +685,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 e9b3a0a4..0ef55055 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -68,6 +68,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: metaImages: ${{ steps.set.outputs.metaImages }} sign: ${{ steps.set.outputs.sign }} ghaCacheSign: ${{ steps.set.outputs.ghaCacheSign }} + registryLogin: ${{ steps.set.outputs.registryLogin }} steps: - name: Install dependencies @@ -263,6 +269,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 }} @@ -290,6 +297,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'); @@ -418,7 +426,13 @@ jobs: 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('='); @@ -504,7 +518,7 @@ jobs: core.setFailed(error); return; } - + const platforms = def.target[target].platforms || []; if (inpDistribute && platforms.length > inpMatrixSizeLimit) { core.setFailed(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); @@ -545,6 +559,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 }} @@ -909,7 +927,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 d1a46c25..57833651 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,6 +84,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" @@ -131,7 +136,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" @@ -180,6 +185,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 @@ -257,6 +263,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: | @@ -273,6 +280,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'); const parseRunnerConfig = value => { @@ -392,7 +400,13 @@ jobs: core.setFailed(`signing attestation manifests requires push to be enabled`); 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; @@ -438,6 +452,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 }} @@ -768,7 +786,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 3f7d0314..b43bce2b 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ ___ * [Outputs](#outputs-1) * [Notes](#notes) * [Runner mapping](#runner-mapping) + * [Registry login](#registry-login) * [Metadata templates](#metadata-templates) ## Overview @@ -223,6 +224,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](#registry-login) before build (one of `auto`, `true` or `false`) | | `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 | @@ -236,10 +238,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 | +| `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | ### Outputs @@ -328,8 +330,9 @@ jobs: | `cache-mode` | String | `min` | [Cache layers to export](https://docs.docker.com/build/cache/backends/#cache-mode) if cache enabled (`min` or `max`). In `min` cache mode, only layers that are exported into the resulting image are cached, while in `max` cache mode, all layers are cached, even those of intermediate steps | | `context` | String | `.` | Context to build from in the Git working tree | | `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/)). | +| `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](#registry-login) before build (one of `auto`, `true` or `false`) | | `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 | @@ -345,10 +348,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 | +| `github-token` | `${{ github.token }}` | GitHub Token used to authenticate against the repository for Git context | ### Outputs @@ -399,6 +402,21 @@ runner: | For example, `linux` matches all Linux platforms, `linux/arm` matches variants such as `linux/arm/v7`, and `linux/arm64` is separate from `linux/arm`. +### Registry login + +The `registry-login` input controls whether the workflows run a registry login +before the build step. `auto` preserves the existing behavior and enables login +only when `output=image` and `push=true`. + +When `registry-login=true`, the workflow always attempts pre-build login. If +credentials resolve to empty values, for example on forked pull requests where +secrets are not exposed, login fails. Gate the input at the caller side for +fork-safe workflows: + +```yaml +registry-login: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }} +``` + ### Metadata templates When `output=image`, some inputs support Handlebars templates rendered from 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" +}