diff --git a/.github/workflows/.test.yml b/.github/workflows/.test.yml index 7a352530..a66a7de3 100644 --- a/.github/workflows/.test.yml +++ b/.github/workflows/.test.yml @@ -26,10 +26,11 @@ jobs: id-token: write with: output: ${{ github.event_name != 'pull_request' && 'registry' || 'cacheonly' }} + cache: true meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | - type=raw,value=ghbuilder-single-${{ github.run_id }} + type=raw,value=build-ghbuilder-single-${{ github.run_id }} build-file: test/hello.Dockerfile build-sbom: true secrets: @@ -46,10 +47,12 @@ jobs: id-token: write with: output: ${{ github.event_name != 'pull_request' && 'registry' || 'cacheonly' }} + cache: true + cache-scope: build-aws meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | - type=raw,value=ghbuilder-${{ github.run_id }} + type=raw,value=build-ghbuilder-${{ github.run_id }} build-file: test/hello.Dockerfile build-sbom: true build-platforms: linux/amd64,linux/arm64 @@ -102,7 +105,7 @@ jobs: output: ${{ github.event_name != 'pull_request' && 'registry' || 'cacheonly' }} meta-images: ghcr.io/docker/github-builder-test meta-tags: | - type=raw,value=${{ github.run_id }} + type=raw,value=build-${{ github.run_id }} build-file: test/hello.Dockerfile build-sbom: true build-platforms: linux/amd64,linux/arm64 @@ -122,7 +125,7 @@ jobs: output: ${{ github.event_name != 'pull_request' && 'registry' || 'cacheonly' }} meta-images: registry-1-stage.docker.io/docker/github-builder-test meta-tags: | - type=raw,value=${{ github.run_id }} + type=raw,value=build-${{ github.run_id }} build-file: test/hello.Dockerfile build-sbom: true build-platforms: linux/amd64,linux/arm64 @@ -163,7 +166,7 @@ jobs: ghcr.io/docker/github-builder-test public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | - type=raw,value=${{ github.run_id }},prefix=ghcr-and-aws- + type=raw,value=${{ github.run_id }},prefix=build-ghcr-and-aws- build-file: test/hello.Dockerfile build-sbom: true build-platforms: linux/amd64,linux/arm64 @@ -184,6 +187,7 @@ jobs: id-token: write with: output: ${{ github.event_name != 'pull_request' && 'local' || 'cacheonly' }} + artifact-name: build-output build-file: test/hello.Dockerfile build-sbom: true build-platforms: linux/amd64,linux/arm64 @@ -218,3 +222,104 @@ jobs: for (const cmd of cosignVerifyCommands) { await exec.exec(cmd); } + + bake-aws: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + packages: write + id-token: write + with: + context: test + target: hello-cross + output: ${{ github.event_name != 'pull_request' && 'registry' || 'cacheonly' }} + cache: true + cache-scope: bake-aws + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-${{ github.run_id }} + bake-sbom: true + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-verify: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-aws + steps: + - + name: Install Cosign + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + with: + cosign-release: ${{ needs.bake-aws.outputs.cosign-version }} + - + name: Login to registry + uses: docker/login-action@v3 + with: + registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + - + name: Verify signatures + uses: actions/github-script@v8 + env: + INPUT_COSIGN-VERSION: ${{ needs.bake-aws.outputs.cosign-version }} + INPUT_COSIGN-VERIFY-COMMANDS: ${{ needs.bake-aws.outputs.cosign-verify-commands }} + with: + script: | + const cosignVersion = core.getInput('cosign-version'); + core.info(`Cosign version used by Docker GitHub Builder: ${cosignVersion}`); + const cosignVerifyCommands = core.getMultilineInput('cosign-verify-commands'); + for (const cmd of cosignVerifyCommands) { + await exec.exec(cmd); + } + + bake-local: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + packages: write + id-token: write + with: + context: test + target: hello-cross + output: ${{ github.event_name != 'pull_request' && 'local' || 'cacheonly' }} + cache: true + artifact-name: bake-output + bake-sbom: true + + bake-local-verify: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-local + steps: + - + name: Install Cosign + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + with: + cosign-release: ${{ needs.bake-local.outputs.cosign-version }} + - + name: Download artifact + uses: actions/download-artifact@v5 + with: + name: ${{ needs.bake-local.outputs.artifact-name }} + - + name: Verify signatures + uses: actions/github-script@v8 + env: + INPUT_COSIGN-VERSION: ${{ needs.bake-local.outputs.cosign-version }} + INPUT_COSIGN-VERIFY-COMMANDS: ${{ needs.bake-local.outputs.cosign-verify-commands }} + with: + script: | + const cosignVersion = core.getInput('cosign-version'); + core.info(`Cosign version used by Docker GitHub Builder: ${cosignVersion}`); + const cosignVerifyCommands = core.getMultilineInput('cosign-verify-commands'); + for (const cmd of cosignVerifyCommands) { + await exec.exec(cmd); + } diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml new file mode 100644 index 00000000..37e59cd9 --- /dev/null +++ b/.github/workflows/bake.yml @@ -0,0 +1,473 @@ +name: bake + +on: + workflow_call: + inputs: + context: + type: string + description: "Context to build from, defaults to repository root" + required: false + default: . + target: + type: string + description: "Target to build" + required: true + default: default + output: + type: string + description: "Build output destination (one of cacheonly, registry, local)" + default: 'cacheonly' + required: false + artifact-name: + type: string + description: "Name of the uploaded artifact (for local output)" + required: false + default: 'docker-github-builder-assets' + envs: + type: string + description: "Environment variables to set" + required: 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, bake-target) + meta-images: + type: string + description: "List of images to use as base name for tags" + required: false + 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 for metadata (default is docker-metadata-action)" + 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 + 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 (default: docker-bake.hcl)" + required: false + bake-pull: + type: boolean + description: "Always attempt to pull all referenced images" + required: false + default: 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: + registry-auths: + description: "Registry authentication details as YAML objects" + required: false + github-token: + description: "GitHub Token used to authenticate against a repository for Git context" + required: false + outputs: + cosign-version: + description: Cosign version used for verification + value: ${{ jobs.build.outputs.cosign-version }} + cosign-verify-commands: + description: Cosign verify commands + value: ${{ jobs.build.outputs.cosign-verify-commands }} + artifact-name: + description: Name of the uploaded artifact (for local output) + value: ${{ jobs.build.outputs.artifact-name }} + +env: + DOCKER_ACTIONS_TOOLKIT_MODULE: "@docker/actions-toolkit@0.67.0" + COSIGN_VERSION: "v3.0.2" + LOCAL_EXPORT_DIR: "/tmp/buildx-output" + +jobs: + build: + runs-on: ubuntu-latest + outputs: + cosign-version: ${{ env.COSIGN_VERSION }} + cosign-verify-commands: ${{ steps.signing-attestation-manifests.outputs.verify-commands || steps.signing-local-artifacts.outputs.verify-commands }} + artifact-name: ${{ inputs.artifact-name }} + permissions: + contents: read + id-token: write # needed for signing the images with GitHub OIDC Token + packages: write # needed to push images to GitHub Container Registry + steps: + - + name: Environment variables + uses: actions/github-script@v8 + env: + INPUT_ENVS: ${{ inputs.envs }} + with: + script: | + for (const env of core.getMultilineInput('envs')) { + core.info(env); + const [key, value] = env.split('=', 2); + core.exportVariable(key, value); + } + - + name: Install @docker/actions-toolkit + uses: actions/github-script@v8 + env: + INPUT_DAT-MODULE: ${{ env.DOCKER_ACTIONS_TOOLKIT_MODULE }} + with: + script: | + await exec.exec('npm', ['install', '--prefer-offline', '--no-audit', core.getInput('dat-module')]); + - + name: Docker meta + id: meta + if: ${{ inputs.output == 'registry' }} + uses: docker/metadata-action@v5 + with: + images: ${{ inputs.meta-images }} + tags: ${{ inputs.meta-tags }} + flavor: ${{ inputs.meta-flavor }} + labels: ${{ inputs.meta-labels }} + annotations: ${{ inputs.meta-annotations }} + bake-target: ${{ inputs.meta-bake-target }} + - + 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: latest + buildkitd-flags: --debug + - + name: Prepare + id: prepare + uses: actions/github-script@v8 + env: + INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} + INPUT_CONTEXT: ${{ inputs.context }} + INPUT_TARGET: ${{ inputs.target }} + INPUT_OUTPUT: ${{ inputs.output }} + INPUT_CACHE: ${{ inputs.cache }} + INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} + INPUT_CACHE-MODE: ${{ inputs.cache-mode }} + INPUT_META-IMAGES: ${{ inputs.meta-images }} + 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 }} + INPUT_BAKE-FILE-ANNOTATIONS: ${{ steps.meta.outputs.bake-file-annotations }} + INPUT_BAKE-FILE-LABELS: ${{ steps.meta.outputs.bake-file-labels }} + INPUT_BAKE-ALLOW: ${{ inputs.bake-allow }} + INPUT_BAKE-FILES: ${{ inputs.bake-files }} + INPUT_BAKE-PULL: ${{ inputs.bake-pull }} + INPUT_BAKE-SBOM: ${{ inputs.bake-sbom }} + INPUT_BAKE-SET: ${{ inputs.bake-set }} + INPUT_GITHUB-TOKEN: ${{ secrets.github-token || github.token }} + with: + script: | + const os = require('os'); + const { Bake } = require('@docker/actions-toolkit/lib/buildx/bake'); + const { Util } = require('@docker/actions-toolkit/lib/util'); + + const inpLocalExportDir = core.getInput('local-export-dir'); + const inpContext = core.getInput('context'); + const inpTarget = core.getInput('target'); + const inpOutput = core.getInput('output'); + const inpCache = core.getBooleanInput('cache'); + const inpCacheScope = core.getInput('cache-scope'); + const inpCacheMode = core.getInput('cache-mode'); + const inpMetaImages = core.getMultilineInput('meta-images'); + 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 inpBakeAllow = core.getInput('bake-allow'); + const inpBakeFiles = Util.getInputList('bake-files'); + const inpBakePull = core.getBooleanInput('bake-pull'); + const inpBakeSbom = core.getInput('bake-sbom'); + const inpBakeSet = Util.getInputList('bake-set', {ignoreComma: true, quote: false}); + 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 () => { + core.info(bakeSource); + core.setOutput('source', bakeSource); + }); + + let target; + await core.group(`Validating definition`, async () => { + const bake = new Bake(); + const def = await bake.getDefinition({ + allow: inpBakeAllow, + files: inpBakeFiles, + overrides: inpBakeSet, + sbom: inpBakeSbom, + source: bakeSource, + targets: [inpTarget], + githubToken: inpGitHubToken + }); + 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); + }); + + let bakeFiles = inpBakeFiles; + await core.group(`Set bake files`, async () => { + if (bakeFiles.length === 0) { + bakeFiles = ['docker-bake.hcl']; + } + if (inpBakeFileTags) { + bakeFiles.push(`cwd://${inpBakeFileTags}`); + } + if (inpSetMetaAnnotations && inpBakeFileAnnotations) { + bakeFiles.push(`cwd://${inpBakeFileAnnotations}`); + } + if (inpSetMetaLabels && inpBakeFileLabels) { + bakeFiles.push(`cwd://${inpBakeFileLabels}`); + } + core.info(JSON.stringify(bakeFiles, null, 2)); + core.setOutput('files', bakeFiles.join(os.EOL)); + }); + + let outputOverride = ''; + switch (inpOutput) { + case 'cacheonly': + outputOverride = '*.output=type=cacheonly'; + break; + case 'registry': + if (inpMetaImages.length == 0) { + core.setFailed('meta-images is required when output is registry'); + } + outputOverride = `*.output=type=registry,"name=${inpMetaImages.join(',')}",oci-artifact=true,push-by-digest=true,name-canonical=true`; + break; + case 'local': + outputOverride = `*.output=type=local,dest=${inpLocalExportDir}`; + break; + default: + core.setFailed(`Invalid output: ${inpOutput}`); + } + + let bakeOverrides = [...inpBakeSet, outputOverride]; + await core.group(`Set bake overrides`, async () => { + bakeOverrides.push('*.attest=type=provenance,mode=max,version=v1', '*.tags='); + if (inpCache) { + bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || target}`); + bakeOverrides.push(`*.cache-to=type=gha,scope=${inpCacheScope || target},mode=${inpCacheMode}`); + } + core.info(JSON.stringify(bakeOverrides, null, 2)); + core.setOutput('overrides', bakeOverrides.join(os.EOL)); + }); + - + name: Login to registry + if: ${{ inputs.output == 'registry' }} + # TODO: switch to docker/login-action when OIDC is supported + uses: crazy-max/docker-login-action@dockerhub-oidc + with: + registry-auth: ${{ secrets.registry-auths }} + - + name: Build + id: bake + uses: docker/bake-action@v6 + with: + source: ${{ steps.prepare.outputs.source }} + files: ${{ steps.prepare.outputs.files }} + targets: ${{ steps.prepare.outputs.target }} + allow: ${{ inputs.bake-allow }} + pull: ${{ inputs.bake-pull }} + sbom: ${{ inputs.bake-sbom }} + set: ${{ steps.prepare.outputs.overrides }} + github-token: ${{ secrets.github-token || github.token }} + env: + BUILDKIT_MULTI_PLATFORM: 1 + - + name: Get image digest + id: get-image-digest + if: ${{ inputs.output == 'registry' }} + uses: actions/github-script@v8 + env: + INPUT_TARGET: ${{ steps.prepare.outputs.target }} + INPUT_METADATA: ${{ steps.bake.outputs.metadata }} + with: + script: | + const inpTarget = core.getInput('target'); + const inpMetadata = JSON.parse(core.getInput('metadata')); + const imageDigest = inpMetadata[inpTarget]['containerimage.digest']; + core.info(imageDigest); + core.setOutput('digest', imageDigest); + - + name: Install Cosign + if: ${{ inputs.output != 'cacheonly' }} + uses: actions/github-script@v8 + env: + INPUT_COSIGN-VERSION: ${{ env.COSIGN_VERSION }} + with: + script: | + const { Cosign } = require('@docker/actions-toolkit/lib/cosign/cosign'); + const { Install } = require('@docker/actions-toolkit/lib/cosign/install'); + + const cosignInstall = new Install(); + const cosignBinPath = await cosignInstall.download(core.getInput('cosign-version'), false, true); + await cosignInstall.install(cosignBinPath); + + const cosign = new Cosign(); + await cosign.printVersion(); + - + name: Signing attestation manifests + id: signing-attestation-manifests + if: ${{ inputs.output == 'registry' }} + uses: actions/github-script@v8 + env: + INPUT_IMAGE-NAMES: ${{ inputs.meta-images }} + INPUT_IMAGE-DIGEST: ${{ steps.get-image-digest.outputs.digest }} + with: + script: | + const { Sigstore } = require('@docker/actions-toolkit/lib/sigstore/sigstore'); + + const inpImageNames = core.getMultilineInput('image-names'); + const inpImageDigest = core.getInput('image-digest'); + + const sigstore = new Sigstore(); + const signResults = await sigstore.signAttestationManifests({ + imageNames: inpImageNames, + imageDigest: inpImageDigest + }); + + const verifyResults = await sigstore.verifySignedManifests( + { certificateIdentityRegexp: `^https://github.com/docker/github-builder-experimental/.github/workflows/bake.yml.*$` }, + signResults + ); + + await core.group(`Verify commands`, async () => { + const verifyCommands = []; + for (const [attestationRef, verifyResult] of Object.entries(verifyResults)) { + const cmd = `cosign ${verifyResult.cosignArgs.join(' ')} ${attestationRef}`; + core.info(cmd); + verifyCommands.push(cmd); + } + core.setOutput('verify-commands', verifyCommands.join('\n')); + }); + - + name: Signing local artifacts + id: signing-local-artifacts + if: ${{ inputs.output == 'local' }} + uses: actions/github-script@v8 + env: + INPUT_LOCAL-OUTPUT-DIR: ${{ env.LOCAL_EXPORT_DIR }} + with: + script: | + const path = require('path'); + const { Sigstore } = require('@docker/actions-toolkit/lib/sigstore/sigstore'); + const inplocalExportDir = core.getInput('local-output-dir'); + + const sigstore = new Sigstore(); + const signResults = await sigstore.signProvenanceBlobs({ + localExportDir: inplocalExportDir + }); + + const verifyResults = await sigstore.verifySignedArtifacts( + { certificateIdentityRegexp: `^https://github.com/docker/github-builder-experimental/.github/workflows/bake.yml.*$` }, + signResults + ); + + await core.group(`Verify commands`, async () => { + const verifyCommands = []; + for (const [artifactPath, verifyResult] of Object.entries(verifyResults)) { + const cmd = `cosign ${verifyResult.cosignArgs.join(' ')} --bundle ${path.relative(inplocalExportDir, verifyResult.bundlePath)} ${path.relative(inplocalExportDir, artifactPath)}`; + core.info(cmd); + verifyCommands.push(cmd); + } + core.setOutput('verify-commands', verifyCommands.join('\n')); + }); + - + name: Create manifest + if: ${{ inputs.output == 'registry' }} + uses: actions/github-script@v8 + env: + INPUT_IMAGE-NAMES: ${{ inputs.meta-images }} + INPUT_TAG-NAMES: ${{ steps.meta.outputs.tag-names }} + INPUT_IMAGE-DIGEST: ${{ steps.get-image-digest.outputs.digest }} + with: + script: | + for (const imageName of core.getMultilineInput('image-names')) { + let createArgs = ['buildx', 'imagetools', 'create']; + for (const tag of core.getMultilineInput('tag-names')) { + createArgs.push('-t', `${imageName}:${tag}`); + } + createArgs.push(core.getInput('image-digest')); + await exec.getExecOutput('docker', createArgs, { + ignoreReturnCode: true + }).then(res => { + if (res.stderr.length > 0 && res.exitCode != 0) { + throw new Error(res.stderr); + } + }); + } + - + name: List local output + if: ${{ inputs.output == 'local' }} + run: | + tree -nh ${{ env.LOCAL_EXPORT_DIR }} + - + name: Upload artifact + if: ${{ inputs.output == 'local' }} + uses: actions/upload-artifact@v5 + with: + name: ${{ inputs.artifact-name }} + path: ${{ env.LOCAL_EXPORT_DIR }} + if-no-files-found: error diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13ef8d09..aa3fb03c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,11 +8,20 @@ on: description: "Build output destination (one of cacheonly, registry, local)" default: 'cacheonly' required: false + artifact-name: + type: string + description: "Name of the uploaded artifact (for local output)" + required: false + default: 'docker-github-builder-assets' 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 if set)" + required: false cache-mode: type: string description: "Cache layers to export if cache enabled (min or max)" @@ -123,7 +132,6 @@ env: DOCKER_ACTIONS_TOOLKIT_MODULE: "@docker/actions-toolkit@0.67.0" COSIGN_VERSION: "v3.0.2" LOCAL_EXPORT_DIR: "/tmp/buildx-output" - LOCAL_ARTIFACT_NAME: "docker-github-builder-assets" jobs: build: @@ -131,7 +139,7 @@ jobs: outputs: cosign-version: ${{ env.COSIGN_VERSION }} cosign-verify-commands: ${{ steps.signing-attestation-manifests.outputs.verify-commands || steps.signing-local-artifacts.outputs.verify-commands }} - artifact-name: ${{ env.LOCAL_ARTIFACT_NAME }} + artifact-name: ${{ inputs.artifact-name }} permissions: contents: read id-token: write # needed for signing the images with GitHub OIDC Token @@ -155,6 +163,7 @@ jobs: env: INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} INPUT_CACHE: ${{ inputs.cache }} + INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} INPUT_CACHE-MODE: ${{ inputs.cache-mode }} INPUT_META-IMAGES: ${{ inputs.meta-images }} INPUT_BUILD-OUTPUT: ${{ inputs.output }} @@ -169,6 +178,7 @@ jobs: script: | const inpLocalExportDir = core.getInput('local-export-dir'); const inpCache = core.getBooleanInput('cache'); + const inpCacheScope = core.getInput('cache-scope'); const inpCacheMode = core.getInput('cache-mode'); const inpMetaImages = core.getMultilineInput('meta-images'); const inpBuildOutput = core.getInput('build-output'); @@ -198,8 +208,8 @@ jobs: } if (inpCache) { - core.setOutput('cache-from', `type=gha,scope=docker-github-builder`); - core.setOutput('cache-to', `type=gha,scope=docker-github-builder,mode=${inpCacheMode}`); + core.setOutput('cache-from', `type=gha,scope=${inpCacheScope || inpBuildTarget || 'buildkit'}`); + core.setOutput('cache-to', `type=gha,scope=${inpCacheScope || inpBuildTarget || 'buildkit'},mode=${inpCacheMode}`); } if (inpSetMetaAnnotations && inpMetaAnnotations.length > 0) { @@ -379,6 +389,6 @@ jobs: if: ${{ inputs.output == 'local' }} uses: actions/upload-artifact@v5 with: - name: ${{ env.LOCAL_ARTIFACT_NAME }} + name: ${{ inputs.artifact-name }} path: ${{ env.LOCAL_EXPORT_DIR }} if-no-files-found: error diff --git a/README.md b/README.md index 7e8a43d8..85fd119e 100644 --- a/README.md +++ b/README.md @@ -83,3 +83,70 @@ on: ``` You can find the list of available inputs in [`.github/workflows/build.yml`](.github/workflows/build.yml). + +## Bake reusable workflow + +```yaml +name: ci + +permissions: + contents: read + +on: + push: + branches: + - 'main' + tags: + - 'v*' + pull_request: + + bake: + uses: docker/github-builder-experimental/.github/workflows/bake.yml@main + permissions: + contents: read + id-token: write # for signing attestation manifests with GitHub OIDC Token + packages: write # needed to push images to GitHub Container Registry + with: + output: ${{ github.event_name != 'pull_request' && 'registry' || 'cacheonly' }} + meta-images: name/app + meta-tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + secrets: + registry-auths: | + - registry: docker.io + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + bake-verify: + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake + steps: + - + name: Install Cosign + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 + with: + cosign-release: ${{ needs.bake.outputs.cosign-version }} + - + name: Login to registry + uses: docker/login-action@v3 + with: + registry: docker.io + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Verify signatures + uses: actions/github-script@v8 + env: + INPUT_COSIGN-VERIFY-COMMANDS: ${{ needs.bake.outputs.cosign-verify-commands }} + with: + script: | + for (const cmd of core.getMultilineInput('cosign-verify-commands')) { + await exec.exec(cmd); + } +``` + +You can find the list of available inputs in [`.github/workflows/bake.yml`](.github/workflows/bake.yml). diff --git a/test/docker-bake.hcl b/test/docker-bake.hcl new file mode 100644 index 00000000..83c61d41 --- /dev/null +++ b/test/docker-bake.hcl @@ -0,0 +1,32 @@ +# Special target: https://github.com/docker/metadata-action#bake-definition +target "docker-metadata-action" { + tags = ["github-builder:local"] +} + +group "default" { + targets = ["hello-cross"] +} + +group "grp" { + targets = ["go", "hello"] +} + +target "go" { + inherits = ["docker-metadata-action"] + dockerfile = "go.Dockerfile" +} + +target "go-cross" { + inherits = ["go"] + platforms = ["linux/amd64", "linux/arm64"] +} + +target "hello" { + inherits = ["docker-metadata-action"] + dockerfile = "hello.Dockerfile" +} + +target "hello-cross" { + inherits = ["hello"] + platforms = ["linux/amd64", "linux/arm64"] +}