diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml index f568c33..382851b 100644 --- a/.github/workflows/.test-bake.yml +++ b/.github/workflows/.test-bake.yml @@ -547,3 +547,17 @@ jobs: - registry: registry-1-stage.docker.io username: ${{ vars.DOCKERHUB_STAGE_USERNAME }} password: ${{ secrets.DOCKERHUB_STAGE_TOKEN }} + + bake-namedcontexts: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + artifact-name: bake-namedcontexts-output + artifact-upload: true + context: test + output: local + sbom: true + sign: ${{ github.event_name != 'pull_request' }} + target: go-cross-with-contexts diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index 7a75366..773c428 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -335,11 +335,46 @@ jobs: if (!def) { throw new Error('Bake definition not set'); } - const targets = Object.keys(def.target); - if (targets.length > 1) { - throw new Error(`Only one target can be built at once, found: ${targets.join(', ')}`); + const targetDefs = def.target || {}; + const targets = Object.keys(targetDefs); + if (targets.length === 0) { + throw new Error('Bake definition does not contain any targets'); + } + const parseContextTarget = value => { + if (typeof value !== 'string') { + return undefined; + } + const match = value.match(/^target:(.+)$/); + return match ? match[1] : undefined; + }; + const resolveTarget = () => { + if (targetDefs[inpTarget]) { + return inpTarget; + } + throw new Error(`Unable to resolve ${inpTarget} target, found: ${targets.join(', ')}`); + }; + target = resolveTarget(); + const allowedTargets = new Set([target]); + const stack = [target]; + while (stack.length > 0) { + const current = stack.pop(); + const contexts = targetDefs[current]?.contexts || {}; + for (const contextValue of Object.values(contexts)) { + const dependencyTarget = parseContextTarget(contextValue); + if (!dependencyTarget || allowedTargets.has(dependencyTarget)) { + continue; + } + if (!targetDefs[dependencyTarget]) { + throw new Error(`Target ${current} uses unknown named context target ${dependencyTarget}`); + } + allowedTargets.add(dependencyTarget); + stack.push(dependencyTarget); + } + } + const unsupportedTargets = targets.filter(name => !allowedTargets.has(name)); + if (unsupportedTargets.length > 0) { + throw new Error(`Only one target can be built at once, found unsupported targets: ${unsupportedTargets.join(', ')}`); } - target = targets[0]; }); } catch (error) { core.setFailed(error); @@ -581,7 +616,6 @@ jobs: with: script: | const os = require('os'); - const { Bake } = require('@docker/actions-toolkit/lib/buildx/bake'); const { Build } = require('@docker/actions-toolkit/lib/buildx/build'); const { GitHub } = require('@docker/actions-toolkit/lib/github'); const { Util } = require('@docker/actions-toolkit/lib/util'); @@ -613,11 +647,16 @@ jobs: const inpGitHubToken = core.getInput('github-token'); const bakeSource = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}.git#${process.env.GITHUB_REF}:${inpContext}`; - await core.group(`Set bake source`, async () => { + await core.group(`Set source output`, async () => { core.info(bakeSource); core.setOutput('source', bakeSource); }); + await core.group(`Set target output`, async () => { + core.info(inpTarget); + core.setOutput('target', inpTarget); + }); + const sbom = inpSbom ? `generator=${inpSbomImage}` : 'false'; await core.group(`Set sbom`, async () => { core.info(sbom); @@ -642,34 +681,6 @@ jobs: core.setOutput('envs', JSON.stringify(envs)); }); - let target; - try { - await core.group(`Validating definition`, async () => { - const bake = new Bake(); - const def = await bake.getDefinition({ - files: inpFiles, - overrides: inpSet, - sbom: sbom, - source: bakeSource, - targets: [inpTarget] - }, { - env: Object.keys(envs).length > 0 ? envs : undefined - }); - if (!def) { - throw new Error('Bake definition not set'); - } - const targets = Object.keys(def.target); - if (targets.length > 1) { - throw new Error(`Only one target can be built at once, found: ${targets.join(', ')}`); - } - target = targets[0]; - core.setOutput('target', target); - }); - } catch (error) { - core.setFailed(error); - return; - } - let bakeFiles = inpFiles; await core.group(`Set bake files`, async () => { if (bakeFiles.length === 0) { @@ -719,8 +730,8 @@ jobs: bakeOverrides.push(`*.platform=${inpPlatform}`); } if (inpCache) { - bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || target}${platformPairSuffix}`); - bakeOverrides.push(`*.cache-to=type=gha,ignore-error=true,scope=${inpCacheScope || target}${platformPairSuffix},mode=${inpCacheMode}`); + bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || inpTarget}${platformPairSuffix}`); + bakeOverrides.push(`*.cache-to=type=gha,ignore-error=true,scope=${inpCacheScope || inpTarget}${platformPairSuffix},mode=${inpCacheMode}`); } core.info(JSON.stringify(bakeOverrides, null, 2)); core.setOutput('overrides', bakeOverrides.join(os.EOL)); diff --git a/test/docker-bake.hcl b/test/docker-bake.hcl index af453ff..fbea740 100644 --- a/test/docker-bake.hcl +++ b/test/docker-bake.hcl @@ -37,3 +37,33 @@ target "hello-cross" { inherits = ["hello"] platforms = ["linux/amd64", "linux/arm64"] } + +target "go-cross-with-contexts" { + inherits = ["go-cross"] + contexts = { + gen = "target:generated-files" + } +} + +target "generated-files" { + contexts = { + generated-hello1 = "target:generated-hello1" + generated-hello2 = "target:generated-hello2" + } + dockerfile-inline = <<-EOT + FROM scratch AS generated-files + COPY --from=generated-hello1 / /hello1 + COPY --from=generated-hello2 / /hello2 + EOT + output = ["type=cacheonly"] +} + +target "generated-hello1" { + dockerfile = "hello.Dockerfile" + output = ["type=cacheonly"] +} + +target "generated-hello2" { + dockerfile = "hello.Dockerfile" + output = ["type=cacheonly"] +} diff --git a/test/go.Dockerfile b/test/go.Dockerfile index 2d8455d..7e00ad7 100644 --- a/test/go.Dockerfile +++ b/test/go.Dockerfile @@ -6,6 +6,8 @@ ARG XX_VERSION="1.7.0" # xx is a helper for cross-compilation FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx +FROM scratch AS gen + FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS base COPY --from=xx / / RUN apk add --no-cache file git @@ -14,6 +16,7 @@ WORKDIR /src FROM base AS build ARG TARGETPLATFORM +COPY --from=gen / /out RUN --mount=type=bind,target=. \ --mount=target=/root/.cache,type=cache \ xx-go build -trimpath -o /out/myapp . \