diff --git a/.github/workflows/.test-bake.yml b/.github/workflows/.test-bake.yml new file mode 100644 index 0000000..203f01b --- /dev/null +++ b/.github/workflows/.test-bake.yml @@ -0,0 +1,389 @@ +name: .test-bake + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: + - 'main' + - 'releases/v*' + tags: + - 'v*' + paths: + - '.github/workflows/.test-bake.yml' + - '.github/workflows/verify.yml' + - 'test/**' + pull_request: + paths: + - '.github/workflows/.test-bake.yml' + - '.github/workflows/verify.yml' + - 'test/**' + +jobs: + bake-aws-single: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + context: test + output: image + push: ${{ github.event_name != 'pull_request' }} + sbom: true + target: hello + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-single-${{ github.run_id }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-single-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-aws-single + with: + builder-outputs: ${{ toJSON(needs.bake-aws-single.outputs) }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-single-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-aws-single + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws-single.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-aws: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + context: test + output: image + push: ${{ github.event_name != 'pull_request' }} + sbom: true + target: hello-cross + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-${{ github.run_id }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-aws + with: + builder-outputs: ${{ toJSON(needs.bake-aws.outputs) }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-aws + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-aws-nosign: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + context: test + output: image + push: ${{ github.event_name != 'pull_request' }} + sbom: true + sign: false + target: hello-cross + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-nosign-${{ github.run_id }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-nosign-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-aws-nosign + with: + builder-outputs: ${{ toJSON(needs.bake-aws-nosign.outputs) }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-aws-nosign-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-aws-nosign + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws-nosign.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-ghcr-and-aws: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + packages: write + with: + context: test + output: image + push: ${{ github.event_name != 'pull_request' }} + sbom: true + target: hello-cross + meta-images: | + ghcr.io/docker/github-builder-test + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=${{ github.run_id }},prefix=bake-ghcr-and-aws- + secrets: + registry-auths: | + - registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-ghcr-and-aws-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-ghcr-and-aws + with: + builder-outputs: ${{ toJSON(needs.bake-ghcr-and-aws.outputs) }} + secrets: + registry-auths: | + - registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + bake-ghcr-and-aws-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-ghcr-and-aws + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-ghcr-and-aws.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-local: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + artifact-name: bake-output + artifact-upload: true + context: test + output: local + sbom: true + sign: ${{ github.event_name != 'pull_request' }} + target: hello-cross + + bake-local-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-local + with: + builder-outputs: ${{ toJSON(needs.bake-local.outputs) }} + + bake-local-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-local-single: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + artifact-name: bake-single-output + artifact-upload: true + context: test + output: local + sbom: true + sign: ${{ github.event_name != 'pull_request' }} + target: hello + + bake-local-single-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - bake-local-single + with: + builder-outputs: ${{ toJSON(needs.bake-local-single.outputs) }} + + bake-local-single-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local-single + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local-single.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-local-noupload: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + artifact-upload: false + context: test + output: local + sbom: true + target: hello-cross + + bake-local-noupload-verify: + uses: ./.github/workflows/verify.yml + needs: + - bake-local-noupload + with: + builder-outputs: ${{ toJSON(needs.bake-local-noupload.outputs) }} + + bake-local-noupload-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local-noupload + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local-noupload.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-local-nosign: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + artifact-name: bake-nosign-output + artifact-upload: true + context: test + output: local + sbom: true + sign: false + target: hello-cross + + bake-local-nosign-verify: + uses: ./.github/workflows/verify.yml + needs: + - bake-local-nosign + with: + builder-outputs: ${{ toJSON(needs.bake-local-nosign.outputs) }} + + build-local-nosign-outputs: + runs-on: ubuntu-24.04 + needs: + - bake-local-nosign + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-local-nosign.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + + bake-set-runner: + uses: ./.github/workflows/bake.yml + permissions: + contents: read + id-token: write + with: + runner: amd64 + context: test + output: image + push: false + target: hello-cross + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=bake-ghbuilder-${{ github.run_id }} diff --git a/.github/workflows/.test.yml b/.github/workflows/.test-build.yml similarity index 65% rename from .github/workflows/.test.yml rename to .github/workflows/.test-build.yml index b62a0cd..0bd6e0c 100644 --- a/.github/workflows/.test.yml +++ b/.github/workflows/.test-build.yml @@ -1,4 +1,4 @@ -name: .test +name: .test-build concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -12,7 +12,15 @@ on: - 'releases/v*' tags: - 'v*' + paths: + - '.github/workflows/.test-build.yml' + - '.github/workflows/verify.yml' + - 'test/**' pull_request: + paths: + - '.github/workflows/.test-build.yml' + - '.github/workflows/verify.yml' + - 'test/**' jobs: build-aws-single: @@ -21,15 +29,15 @@ jobs: contents: read id-token: write with: + file: test/hello.Dockerfile output: image push: ${{ github.event_name != 'pull_request' }} - cache: true + sbom: true meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | type=raw,value=build-ghbuilder-single-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-sbom: true + secrets: registry-auths: | - registry: public.ecr.aws @@ -70,17 +78,15 @@ jobs: contents: read id-token: write with: + file: test/hello.Dockerfile output: image + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} - cache: true - cache-scope: build-aws + sbom: true meta-images: | public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | type=raw,value=build-ghbuilder-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-sbom: true - build-platforms: linux/amd64,linux/arm64 secrets: registry-auths: | - registry: public.ecr.aws @@ -115,6 +121,56 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); + build-aws-nosign: + uses: ./.github/workflows/build.yml + permissions: + contents: read + id-token: write + with: + file: test/hello.Dockerfile + output: image + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + sbom: true + sign: false + meta-images: | + public.ecr.aws/q3b5f1u4/test-docker-action + meta-tags: | + type=raw,value=build-ghbuilder-nosign--${{ github.run_id }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + build-aws-nosign-verify: + uses: ./.github/workflows/verify.yml + if: ${{ github.event_name != 'pull_request' }} + needs: + - build-aws-nosign + with: + builder-outputs: ${{ toJSON(needs.build-aws-nosign.outputs) }} + secrets: + registry-auths: | + - registry: public.ecr.aws + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + build-aws-nosign-outputs: + runs-on: ubuntu-24.04 + needs: + - build-aws-nosign + steps: + - + name: Builder outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + env: + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-aws-nosign.outputs) }} + with: + script: | + const builderOutputs = JSON.parse(core.getInput('builder-outputs')); + core.info(JSON.stringify(builderOutputs, null, 2)); + build-ghcr: uses: ./.github/workflows/build.yml permissions: @@ -122,14 +178,14 @@ jobs: id-token: write packages: write with: + file: test/hello.Dockerfile output: image + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} + sbom: true meta-images: ghcr.io/docker/github-builder-test meta-tags: | type=raw,value=build-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-sbom: true - build-platforms: linux/amd64,linux/arm64 secrets: registry-auths: | - registry: ghcr.io @@ -170,14 +226,14 @@ jobs: contents: read id-token: write with: + file: test/hello.Dockerfile output: image + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} + sbom: true meta-images: registry-1-stage.docker.io/docker/github-builder-test meta-tags: | type=raw,value=build-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-sbom: true - build-platforms: linux/amd64,linux/arm64 secrets: registry-auths: | - registry: registry-1-stage.docker.io @@ -219,16 +275,16 @@ jobs: id-token: write packages: write with: + file: test/hello.Dockerfile output: image + platforms: linux/amd64,linux/arm64 push: ${{ github.event_name != 'pull_request' }} + sbom: true meta-images: | ghcr.io/docker/github-builder-test public.ecr.aws/q3b5f1u4/test-docker-action meta-tags: | 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 secrets: registry-auths: | - registry: ghcr.io @@ -275,12 +331,13 @@ jobs: contents: read id-token: write with: - output: local - push: ${{ github.event_name != 'pull_request' }} artifact-name: build-output - build-file: test/hello.Dockerfile - build-sbom: true - build-platforms: linux/amd64,linux/arm64 + artifact-upload: true + file: test/hello.Dockerfile + output: local + platforms: linux/amd64,linux/arm64 + sbom: true + sign: ${{ github.event_name != 'pull_request' }} build-local-verify: uses: ./.github/workflows/verify.yml @@ -311,11 +368,12 @@ jobs: contents: read id-token: write with: - output: local - push: ${{ github.event_name != 'pull_request' }} artifact-name: build-single-output - build-file: test/hello.Dockerfile - build-sbom: true + artifact-upload: true + file: test/hello.Dockerfile + output: local + sbom: true + sign: ${{ github.event_name != 'pull_request' }} build-local-single-verify: uses: ./.github/workflows/verify.yml @@ -340,268 +398,87 @@ jobs: const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); - build-set-runner: + build-local-noupload: uses: ./.github/workflows/build.yml permissions: contents: read id-token: write with: - runner: amd64 - output: image - push: false - meta-images: ghcr.io/docker/github-builder-test - meta-tags: | - type=raw,value=build-${{ github.run_id }} - build-file: test/hello.Dockerfile - build-platforms: linux/amd64,linux/arm64 - - bake-aws-single: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - with: - context: test - target: hello - output: image - push: ${{ github.event_name != 'pull_request' }} - cache: true - cache-scope: bake-aws - meta-images: | - public.ecr.aws/q3b5f1u4/test-docker-action - meta-tags: | - type=raw,value=bake-ghbuilder-single-${{ 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-single-verify: - uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} - needs: - - bake-aws-single - with: - builder-outputs: ${{ toJSON(needs.bake-aws-single.outputs) }} - secrets: - registry-auths: | - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-aws-single-outputs: - runs-on: ubuntu-24.04 - needs: - - bake-aws-single - steps: - - - name: Builder outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws-single.outputs) }} - with: - script: | - const builderOutputs = JSON.parse(core.getInput('builder-outputs')); - core.info(JSON.stringify(builderOutputs, null, 2)); - - bake-aws: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - with: - context: test - target: hello-cross - output: image - push: ${{ github.event_name != 'pull_request' }} - 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: - uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} - needs: - - bake-aws - with: - builder-outputs: ${{ toJSON(needs.bake-aws.outputs) }} - secrets: - registry-auths: | - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-aws-outputs: - runs-on: ubuntu-24.04 - needs: - - bake-aws - steps: - - - name: Builder outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-aws.outputs) }} - with: - script: | - const builderOutputs = JSON.parse(core.getInput('builder-outputs')); - core.info(JSON.stringify(builderOutputs, null, 2)); - - bake-ghcr-and-aws: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - packages: write - with: - context: test - target: hello-cross - output: image - push: ${{ github.event_name != 'pull_request' }} - cache: true - cache-scope: bake-aws - meta-images: | - ghcr.io/docker/github-builder-test - public.ecr.aws/q3b5f1u4/test-docker-action - meta-tags: | - type=raw,value=${{ github.run_id }},prefix=bake-ghcr-and-aws- - bake-sbom: true - secrets: - registry-auths: | - - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-ghcr-and-aws-verify: - uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} - needs: - - bake-ghcr-and-aws - with: - builder-outputs: ${{ toJSON(needs.bake-ghcr-and-aws.outputs) }} - secrets: - registry-auths: | - - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - registry: public.ecr.aws - username: ${{ secrets.AWS_ACCESS_KEY_ID }} - password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - bake-ghcr-and-aws-outputs: - runs-on: ubuntu-24.04 - needs: - - bake-ghcr-and-aws - steps: - - - name: Builder outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-ghcr-and-aws.outputs) }} - with: - script: | - const builderOutputs = JSON.parse(core.getInput('builder-outputs')); - core.info(JSON.stringify(builderOutputs, null, 2)); - - bake-local: - uses: ./.github/workflows/bake.yml - permissions: - contents: read - id-token: write - with: - context: test - target: hello-cross + artifact-upload: false + file: test/hello.Dockerfile output: local - push: ${{ github.event_name != 'pull_request' }} - cache: true - artifact-name: bake-output - bake-sbom: true + platforms: linux/amd64,linux/arm64 + sbom: true - bake-local-verify: + build-local-noupload-verify: uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} needs: - - bake-local + - build-local-noupload with: - builder-outputs: ${{ toJSON(needs.bake-local.outputs) }} + builder-outputs: ${{ toJSON(needs.build-local-noupload.outputs) }} - bake-local-outputs: + build-local-noupload-outputs: runs-on: ubuntu-24.04 needs: - - bake-local + - build-local-noupload steps: - name: Builder outputs uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local.outputs) }} + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-local-noupload.outputs) }} with: script: | const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); - bake-local-single: - uses: ./.github/workflows/bake.yml + build-local-nosign: + uses: ./.github/workflows/build.yml permissions: contents: read id-token: write with: - context: test - target: hello + artifact-name: build-nosign-output + artifact-upload: true + file: test/hello.Dockerfile output: local - push: ${{ github.event_name != 'pull_request' }} - cache: true - artifact-name: bake-single-output - bake-sbom: true + platforms: linux/amd64,linux/arm64 + sbom: true + sign: false - bake-local-single-verify: + build-local-nosign-verify: uses: ./.github/workflows/verify.yml - if: ${{ github.event_name != 'pull_request' }} needs: - - bake-local-single + - build-local-nosign with: - builder-outputs: ${{ toJSON(needs.bake-local-single.outputs) }} + builder-outputs: ${{ toJSON(needs.build-local-nosign.outputs) }} - bake-local-single-outputs: + build-local-nosign-outputs: runs-on: ubuntu-24.04 needs: - - bake-local-single + - build-local-nosign steps: - name: Builder outputs uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: - INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.bake-local-single.outputs) }} + INPUT_BUILDER-OUTPUTS: ${{ toJSON(needs.build-local-nosign.outputs) }} with: script: | const builderOutputs = JSON.parse(core.getInput('builder-outputs')); core.info(JSON.stringify(builderOutputs, null, 2)); - bake-set-runner: - uses: ./.github/workflows/bake.yml + build-set-runner: + uses: ./.github/workflows/build.yml permissions: contents: read id-token: write with: runner: amd64 - context: test - target: hello-cross + file: test/hello.Dockerfile output: image + platforms: linux/amd64,linux/arm64 push: false - meta-images: | - public.ecr.aws/q3b5f1u4/test-docker-action + meta-images: ghcr.io/docker/github-builder-test meta-tags: | - type=raw,value=bake-ghbuilder-${{ github.run_id }} + type=raw,value=build-${{ github.run_id }} diff --git a/.github/workflows/bake.yml b/.github/workflows/bake.yml index 07e9645..310fdad 100644 --- a/.github/workflows/bake.yml +++ b/.github/workflows/bake.yml @@ -5,70 +5,78 @@ on: inputs: runner: type: string - description: "Linux machine to run build on. Can be one of auto, amd64, arm64 (defaults to auto which selects best-matching runner based on target platform)" + description: "Ubuntu GitHub Hosted Runner to build on (one of auto, amd64, arm64). The auto runner selects the best-matching runner based on target platforms. You can set it to amd64 if your build doesn't require emulation (e.g. cross-compilation)" required: false default: 'auto' - context: - type: string - description: "Context to build from (defaults to repository root)" + setup-qemu: + type: boolean + description: "Runs the setup-qemu-action step to install QEMU static binaries" required: false - default: . - target: - type: string - description: "Target to build" - required: true - default: default - output: - type: string - description: "Build output destination (image or local)" - required: true + default: false artifact-name: type: string - description: "Name of the uploaded artifact (for local output)" + description: "Name of the uploaded GitHub artifact (for local output)" required: false default: 'docker-github-builder-assets' + artifact-upload: + type: boolean + description: "Upload build output GitHub artifact (for local output)" + required: false + default: false envs: type: string - description: "Environment variables to set" + description: "Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the GitHub Actions env context that is currently not available when calling a reusable workflow" + required: false + context: + type: string + description: "Context to build from in the Git working tree" + required: false + default: . + files: + type: string + description: "List of bake definition files" required: false + output: + type: string + description: "Build output destination (one of image or local). Unlike the build-push-action, it only accepts image or local. The reusable workflow takes care of setting the outputs attribute" + required: true push: type: boolean - description: "Push image to the registry (for image output) and/or sign attestation manifests or local artifacts" + description: "Push image to the registry (for image output)" required: false default: false - cache: - type: boolean - description: "Enable cache to GitHub Actions cache backend" + sbom: + type: string + description: "Generate SBOM attestation for the build" required: false - default: false - cache-scope: + set: type: string - description: "Which scope cache object belongs to if cache enabled (defaults to target name)" + description: "List of targets values to override (eg. targetpattern.key=value)" required: false - cache-mode: + sign: type: string - description: "Cache layers to export if cache enabled (min or max)" + description: "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" required: false - default: 'min' + default: auto + target: + type: string + description: "Bake target to build" + required: true + default: default + # docker/metadata-action set-meta-annotations: type: boolean - description: "Set metadata-action annotations" + description: "Append OCI Image Format Specification annotations generated by docker/metadata-action" 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" + description: "Append OCI Image Format Specification labels generated by docker/metadata-action" required: false default: false - # 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" + description: "List of images to use as base name for tags (required for image output)" required: false meta-tags: type: string @@ -76,57 +84,22 @@ on: 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" + description: "Flavor defines a global behavior for meta-tags" required: false meta-bake-target: type: string description: "Bake target name for metadata (defaults to 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 (defaults to 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" + description: "Raw authentication to registries, defined as YAML objects (for image output)" required: false github-token: - description: "GitHub Token used to authenticate against a repository for Git context" + description: "GitHub Token used to authenticate against the repository for Git context" required: false outputs: meta-json: - description: "Metadata JSON output (only for image output)" + description: "Metadata JSON output (for image output)" value: ${{ jobs.finalize.outputs.meta-json }} cosign-version: description: "Cosign version used for verification" @@ -140,6 +113,9 @@ on: output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} + signed: + description: "Whether attestations manifests or artifacts were signed" + value: ${{ jobs.finalize.outputs.signed }} env: BUILDX_VERSION: "v0.30.1" @@ -154,6 +130,7 @@ jobs: runs-on: ubuntu-24.04 outputs: includes: ${{ steps.set.outputs.includes }} + sign: ${{ steps.set.outputs.sign }} steps: - name: Environment variables @@ -176,19 +153,21 @@ jobs: script: | await exec.exec('npm', ['install', '--prefer-offline', '--no-audit', '--ignore-scripts', core.getInput('dat-module')]); - - name: Set includes + name: Set outputs id: set uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_MATRIX-SIZE-LIMIT: ${{ env.MATRIX_SIZE_LIMIT }} INPUT_RUNNER: ${{ inputs.runner }} + INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} INPUT_CONTEXT: ${{ inputs.context }} + INPUT_FILES: ${{ inputs.files }} + INPUT_OUTPUT: ${{ inputs.output }} + INPUT_PUSH: ${{ inputs.push }} + INPUT_SBOM: ${{ inputs.sbom }} + INPUT_SET: ${{ inputs.set }} + INPUT_SIGN: ${{ inputs.sign }} INPUT_TARGET: ${{ inputs.target }} - 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: | @@ -200,13 +179,15 @@ jobs: const inpMatrixSizeLimit = parseInt(core.getInput('matrix-size-limit'), 10); const inpRunner = core.getInput('runner'); + const inpArtifactUpload = core.getBooleanInput('artifact-upload'); const inpContext = core.getInput('context'); + const inpFiles = Util.getInputList('files'); + const inpOutput = core.getInput('output'); + const inpPush = core.getBooleanInput('push'); + const inpSbom = core.getInput('sbom'); + const inpSet = Util.getInputList('set', {ignoreComma: true, quote: false}); + const inpSign = core.getInput('sign'); const inpTarget = core.getInput('target'); - 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'); let runner = inpRunner; @@ -215,7 +196,23 @@ jobs: } else if (inpRunner === 'arm64') { runner = 'ubuntu-24.04-arm'; } else if (inpRunner !== 'auto') { - throw new Error(`Invalid runner input: ${inpRunner}`); + core.setFailed(`Invalid runner input: ${inpRunner}`); + return; + } + + const sign = + inpSign === 'auto' + ? (inpOutput === 'image' && inpPush) || (inpOutput === 'local' && inpArtifactUpload) + : inpSign === 'true'; + + if (inpOutput === 'local' && inpPush) { + core.warning(`push is ignored when output is local`); + } else if (inpOutput === 'image' && inpArtifactUpload) { + core.warning(`artifact-upload is ignored when output is image`); + } + if (inpOutput === 'image' && !inpPush && sign) { + core.setFailed(`signing attestation manifests requires push to be enabled`); + return; } const bakeSource = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}.git#${process.env.GITHUB_REF}:${inpContext}`; @@ -225,50 +222,63 @@ jobs: let def; let target; - await core.group(`Validating definition`, async () => { - const bake = new Bake(); - def = await bake.getDefinition({ - allow: inpBakeAllow, - files: inpBakeFiles, - overrides: inpBakeSet, - sbom: inpBakeSbom, - source: bakeSource, - targets: [inpTarget], - githubToken: inpGitHubToken + try { + await core.group(`Validating definition`, async () => { + const bake = new Bake(); + def = await bake.getDefinition({ + files: inpFiles, + overrides: inpSet, + sbom: inpSbom, + 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]; }); - 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]; - }); + } catch (error) { + core.setFailed(error); + return; + } + + const platforms = def.target[target].platforms || []; + if (platforms.length > inpMatrixSizeLimit) { + core.setFailed(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); + return; + } const privateRepo = GitHub.context.payload.repository?.private ?? false; - await core.group(`Set includes`, async () => { + await core.group(`Set includes output`, async () => { let includes = []; - const platforms = def.target[target].platforms || []; - if (platforms.length > inpMatrixSizeLimit) { - throw new Error(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); - } else if (platforms.length === 0) { + if (platforms.length === 0) { includes.push({ index: 0, - runner: runner === 'auto' ? 'ubuntu-24.04' : runner + runner: runner === 'auto' ? 'ubuntu-24.04' : runner, + sign: sign }); } else { platforms.forEach((platform, index) => { includes.push({ index: index, platform: platform, - runner: runner === 'auto' ? ((!privateRepo && platform.startsWith('linux/arm')) ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner + runner: runner === 'auto' ? ((!privateRepo && platform.startsWith('linux/arm')) ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner, + sign: sign }); }); } core.info(JSON.stringify(includes, null, 2)); core.setOutput('includes', JSON.stringify(includes)); }); + await core.group(`Set sign output`, async () => { + core.info(`sign: ${sign}`); + core.setOutput('sign', sign); + }); build: runs-on: ${{ matrix.runner }} @@ -339,7 +349,7 @@ jobs: uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 if: ${{ inputs.setup-qemu }} with: - image: ${{ inputs.qemu-image }} + cache-image: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -347,6 +357,7 @@ jobs: version: ${{ env.BUILDX_VERSION }} buildkitd-flags: --debug driver-opts: image=${{ env.BUILDKIT_IMAGE }} + cache-binary: false - name: Prepare id: prepare @@ -355,23 +366,18 @@ jobs: INPUT_PLATFORM: ${{ matrix.platform }} INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} INPUT_CONTEXT: ${{ inputs.context }} - INPUT_TARGET: ${{ inputs.target }} + INPUT_FILES: ${{ inputs.files }} INPUT_OUTPUT: ${{ inputs.output }} INPUT_PUSH: ${{ inputs.push }} - INPUT_CACHE: ${{ inputs.cache }} - INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} - INPUT_CACHE-MODE: ${{ inputs.cache-mode }} + INPUT_SBOM: ${{ inputs.sbom }} + INPUT_SET: ${{ inputs.set }} + INPUT_TARGET: ${{ inputs.target }} 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: | @@ -387,24 +393,18 @@ jobs: const inpLocalExportDir = core.getInput('local-export-dir'); const inpContext = core.getInput('context'); - const inpTarget = core.getInput('target'); + const inpFiles = Util.getInputList('files'); const inpOutput = core.getInput('output'); const inpPush = core.getBooleanInput('push'); - const inpCache = core.getBooleanInput('cache'); - const inpCacheScope = core.getInput('cache-scope'); - const inpCacheMode = core.getInput('cache-mode'); + const inpSbom = core.getInput('sbom'); + const inpSet = Util.getInputList('set', {ignoreComma: true, quote: false}); + const inpTarget = core.getInput('target'); 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}`; @@ -414,29 +414,33 @@ jobs: }); 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 + try { + await core.group(`Validating definition`, async () => { + const bake = new Bake(); + const def = await bake.getDefinition({ + files: inpFiles, + overrides: inpSet, + sbom: inpSbom, + 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); }); - 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 = inpBakeFiles; + let bakeFiles = inpFiles; await core.group(`Set bake files`, async () => { if (bakeFiles.length === 0) { bakeFiles = ['docker-bake.hcl']; @@ -459,6 +463,7 @@ jobs: case 'image': if (inpMetaImages.length == 0) { core.setFailed('meta-images is required when output is image'); + return; } outputOverride = `*.output=type=image,"name=${inpMetaImages.join(',')}",oci-artifact=true,push-by-digest=true,name-canonical=true,push=${inpPush}`; break; @@ -467,9 +472,10 @@ jobs: break; default: core.setFailed(`Invalid output: ${inpOutput}`); + return; } - let bakeOverrides = [...inpBakeSet, outputOverride]; + let bakeOverrides = [...inpSet, outputOverride]; await core.group(`Set bake overrides`, async () => { bakeOverrides.push('*.tags='); if (GitHub.context.payload.repository?.private ?? false) { @@ -483,10 +489,6 @@ jobs: if (inpPlatform) { bakeOverrides.push(`*.platform=${inpPlatform}`); } - if (inpCache) { - bakeOverrides.push(`*.cache-from=type=gha,scope=${inpCacheScope || target}${platformPairSuffix}`); - bakeOverrides.push(`*.cache-to=type=gha,scope=${inpCacheScope || target}${platformPairSuffix},mode=${inpCacheMode}`); - } core.info(JSON.stringify(bakeOverrides, null, 2)); core.setOutput('overrides', bakeOverrides.join(os.EOL)); }); @@ -504,9 +506,7 @@ jobs: 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 }} + sbom: ${{ inputs.sbom }} set: ${{ steps.prepare.outputs.overrides }} env: BUILDKIT_MULTI_PLATFORM: 1 @@ -528,7 +528,7 @@ jobs: core.setOutput('digest', imageDigest); - name: Install Cosign - if: ${{ inputs.push }} + if: ${{ needs.prepare.outputs.sign }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_COSIGN-VERSION: ${{ env.COSIGN_VERSION }} @@ -538,7 +538,7 @@ jobs: const { Install } = require('@docker/actions-toolkit/lib/cosign/install'); const cosignInstall = new Install(); - const cosignBinPath = await cosignInstall.download(core.getInput('cosign-version'), false, true); + const cosignBinPath = await cosignInstall.download(core.getInput('cosign-version'), true, true); await cosignInstall.install(cosignBinPath); const cosign = new Cosign(); @@ -546,7 +546,7 @@ jobs: - name: Signing attestation manifests id: signing-attestation-manifests - if: ${{ inputs.push && inputs.output == 'image' }} + if: ${{ matrix.sign && inputs.output == 'image' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_IMAGE-NAMES: ${{ inputs.meta-images }} @@ -593,7 +593,7 @@ jobs: - name: Signing local artifacts id: signing-local-artifacts - if: ${{ inputs.push && inputs.output == 'local' }} + if: ${{ matrix.sign && inputs.output == 'local' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_LOCAL-OUTPUT-DIR: ${{ env.LOCAL_EXPORT_DIR }} @@ -629,7 +629,7 @@ jobs: tree -nh ${{ env.LOCAL_EXPORT_DIR }} - name: Upload artifact - if: ${{ inputs.output == 'local' }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix || '0' }} @@ -644,17 +644,22 @@ jobs: INPUT_VERIFY-COMMANDS: ${{ steps.signing-attestation-manifests.outputs.verify-commands || steps.signing-local-artifacts.outputs.verify-commands }} INPUT_IMAGE-DIGEST: ${{ steps.get-image-digest.outputs.digest }} INPUT_ARTIFACT-NAME: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix }} + INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} + INPUT_SIGNED: ${{ matrix.sign }} with: script: | const inpIndex = core.getInput('index'); const inpVerifyCommands = core.getInput('verify-commands'); const inpImageDigest = core.getInput('image-digest'); const inpArtifactName = core.getInput('artifact-name'); + const inpArtifactUpload = core.getBooleanInput('artifact-upload'); + const inpSigned = core.getBooleanInput('signed'); const result = { verifyCommands: inpVerifyCommands, imageDigest: inpImageDigest, - artifactName: inpArtifactName + artifactName: inpArtifactUpload ? inpArtifactName : '', + signed: inpSigned } core.info(JSON.stringify(result, null, 2)); @@ -666,9 +671,11 @@ jobs: meta-json: ${{ steps.meta.outputs.json }} cosign-version: ${{ env.COSIGN_VERSION }} cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} - artifact-name: ${{ inputs.artifact-name }} + artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} output-type: ${{ inputs.output }} + signed: ${{ needs.prepare.outputs.sign }} needs: + - prepare - build steps: - @@ -697,6 +704,7 @@ jobs: version: ${{ env.BUILDX_VERSION }} buildkitd-flags: --debug driver-opts: image=${{ env.BUILDKIT_IMAGE }} + cache-binary: false - name: Create manifest if: ${{ inputs.output == 'image' }} @@ -721,7 +729,8 @@ jobs: } } if (digests.length === 0) { - throw new Error('No image digests found from build outputs'); + core.setFailed('No image digests found from build outputs'); + return; } for (const imageName of inpImageNames) { @@ -733,13 +742,7 @@ jobs: createArgs.push(digest); } if (inpPush) { - await exec.getExecOutput('docker', createArgs, { - ignoreReturnCode: true - }).then(res => { - if (res.stderr.length > 0 && res.exitCode != 0) { - throw new Error(res.stderr); - } - }); + await exec.exec('docker', createArgs); } else { await core.group(`Generated imagetools create command for ${imageName}`, async () => { core.info(`docker ${createArgs.join(' ')}`); @@ -748,7 +751,7 @@ jobs: } - name: Merge artifacts - if: ${{ inputs.output == 'local' }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact/merge@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }} @@ -760,14 +763,19 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_BUILD-OUTPUTS: ${{ toJSON(needs.build.outputs) }} + INPUT_SIGNED: ${{ needs.prepare.outputs.sign }} with: script: | const inpBuildOutputs = JSON.parse(core.getInput('build-outputs')); - const verifyCommands = []; - for (const key of Object.keys(inpBuildOutputs)) { - const output = JSON.parse(inpBuildOutputs[key]); - if (output.verifyCommands) { - verifyCommands.push(output.verifyCommands); + const inpSigned = core.getBooleanInput('signed'); + + if (inpSigned) { + const verifyCommands = []; + for (const key of Object.keys(inpBuildOutputs)) { + const output = JSON.parse(inpBuildOutputs[key]); + if (output.verifyCommands) { + verifyCommands.push(output.verifyCommands); + } } + core.setOutput('cosign-verify-commands', verifyCommands.join('\n')); } - core.setOutput('cosign-verify-commands', verifyCommands.join('\n')); diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 178b414..fab9004 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,139 +5,116 @@ on: inputs: runner: type: string - description: "Linux machine to run build on. Can be one of auto, amd64, arm64 (defaults to auto which selects best-matching runner based on target platform)" + description: "Ubuntu GitHub Hosted Runner to build on (one of auto, amd64, arm64). The auto runner selects the best-matching runner based on target platforms. You can set it to amd64 if your build doesn't require emulation (e.g. cross-compilation)" required: false default: 'auto' - context: - type: string - description: "Context to build from (defaults to repository root)" + setup-qemu: + type: boolean + description: "Runs the setup-qemu-action step to install QEMU static binaries" required: false - default: . - output: - type: string - description: "Build output destination (image or local)" - required: true + default: false artifact-name: type: string - description: "Name of the uploaded artifact (for local output)" + description: "Name of the uploaded GitHub artifact (for local output)" required: false default: 'docker-github-builder-assets' - envs: - type: string - description: "Environment variables to set" - required: false - push: - type: boolean - description: "Push image to the registry (for image output) and/or sign attestation manifests or local artifacts" - required: false - default: false - cache: + artifact-upload: type: boolean - description: "Enable cache to GitHub Actions cache backend" + description: "Upload build output GitHub artifact (for local output)" required: false default: false - cache-scope: + envs: type: string - description: "Which scope cache object belongs to if cache enabled (defaults to target name if set)" + description: "Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the GitHub Actions env context that is currently not available when calling a reusable workflow" required: false - cache-mode: + annotations: type: string - description: "Cache layers to export if cache enabled (min or max)" + description: "List of annotations to set to the image (for image output)" 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: false - # same as docker/metadata-action inputs (minus sep-tags, sep-labels, sep-annotations, bake-target) - meta-images: + build-args: type: string - description: "List of images to use as base name for tags" + description: "List of build-time variables. If you want to set a build-arg through an environment variable, use the envs input" required: false - meta-tags: + context: type: string - description: "List of tags as key-value pair attributes" + description: "Context to build from in the Git working tree" required: false - meta-flavor: + default: . + file: type: string - description: "Flavors to apply" + description: "Path to the Dockerfile" required: false - meta-labels: + labels: type: string - description: "List of custom labels" + description: "List of labels for an image (for image output)" required: false - meta-annotations: + output: type: string - description: "List of custom annotations" - required: false - # same as docker/setup-qemu-action inputs (minus platforms, cache-image) - qemu-image: + description: "Build output destination (one of image or local). Unlike the build-push-action, it only accepts image or local. The reusable workflow takes care of setting the outputs attribute" + required: true + platforms: type: string - description: "QEMU static binaries Docker image (e.g. tonistiigi/binfmt:latest)" + description: "List of target platforms to build" + required: false + push: + type: boolean + description: "Push image to the registry (for image output)" required: false - # same as docker/build-push-action inputs - build-annotations: + default: false + sbom: type: string - description: "List of annotation to set to the image" + description: "Generate SBOM attestation for the build" required: false - build-args: + shm-size: type: string - description: "List of build-time variables" + description: "Size of /dev/shm (e.g., 2g)" required: false - build-file: + sign: type: string - description: "Path to the Dockerfile" + description: "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" required: false - build-labels: + default: auto + target: type: string - description: "List of metadata for an image" + description: "Sets the target stage to build" required: false - build-platforms: + ulimit: type: string - description: "List of target platforms to build" + description: "Ulimit options (e.g., nofile=1024:1024)" required: false - build-pull: + # docker/metadata-action + set-meta-annotations: type: boolean - description: "Always attempt to pull all referenced images" + description: "Append OCI Image Format Specification annotations generated by docker/metadata-action" required: false default: false - build-sbom: - type: string - description: "Generate SBOM attestation for the build (shorthand for --attest=type=sbom)" + set-meta-labels: + type: boolean + description: "Append OCI Image Format Specification labels generated by docker/metadata-action" required: false - build-shm-size: + default: false + meta-images: type: string - description: "Size of /dev/shm (e.g., 2g)" + description: "List of images to use as base name for tags (required for image output)" required: false - build-target: + meta-tags: type: string - description: "Sets the target stage to build" + description: "List of tags as key-value pair attributes" required: false - build-ulimit: + meta-flavor: type: string - description: "Ulimit options (e.g., nofile=1024:1024)" + description: "Flavor defines a global behavior for meta-tags" required: false secrets: registry-auths: - description: "Registry authentication details as YAML objects" + description: "Raw authentication to registries, defined as YAML objects (for image output)" required: false github-token: - description: "GitHub Token used to authenticate against a repository for Git context" + description: "GitHub Token used to authenticate against the repository for Git context" required: false outputs: meta-json: - description: "Metadata JSON output (only for image output)" + description: "Metadata JSON output (for image output)" value: ${{ jobs.finalize.outputs.meta-json }} cosign-version: description: "Cosign version used for verification" @@ -151,6 +128,9 @@ on: output-type: description: "Build output type" value: ${{ jobs.finalize.outputs.output-type }} + signed: + description: "Whether attestations manifests or artifacts were signed" + value: ${{ jobs.finalize.outputs.signed }} env: BUILDX_VERSION: "v0.30.1" @@ -165,6 +145,7 @@ jobs: runs-on: ubuntu-24.04 outputs: includes: ${{ steps.set.outputs.includes }} + sign: ${{ steps.set.outputs.sign }} steps: - name: Install @docker/actions-toolkit @@ -175,13 +156,17 @@ jobs: script: | await exec.exec('npm', ['install', '--prefer-offline', '--no-audit', '--ignore-scripts', core.getInput('dat-module')]); - - name: Set includes + name: Set outputs id: set uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_MATRIX-SIZE-LIMIT: ${{ env.MATRIX_SIZE_LIMIT }} INPUT_RUNNER: ${{ inputs.runner }} - INPUT_BUILD-PLATFORMS: ${{ inputs.build-platforms }} + INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} + INPUT_OUTPUT: ${{ inputs.output }} + INPUT_PLATFORMS: ${{ inputs.platforms }} + INPUT_PUSH: ${{ inputs.push }} + INPUT_SIGN: ${{ inputs.sign }} with: script: | const { GitHub } = require('@docker/actions-toolkit/lib/github'); @@ -190,7 +175,11 @@ jobs: const inpMatrixSizeLimit = parseInt(core.getInput('matrix-size-limit'), 10); const inpRunner = core.getInput('runner'); - const inpBuildPlatforms = Util.getInputList('build-platforms'); + const inpArtifactUpload = core.getBooleanInput('artifact-upload'); + const inpPlatforms = Util.getInputList('platforms'); + const inpOutput = core.getInput('output'); + const inpPush = core.getBooleanInput('push'); + const inpSign = core.getInput('sign'); let runner = inpRunner; if (inpRunner === 'amd64') { @@ -198,31 +187,56 @@ jobs: } else if (inpRunner === 'arm64') { runner = 'ubuntu-24.04-arm'; } else if (inpRunner !== 'auto') { - throw new Error(`Invalid runner input: ${inpRunner}`); + core.setFailed(`Invalid runner input: ${inpRunner}`); + return; + } + + const sign = + inpSign === 'auto' + ? (inpOutput === 'image' && inpPush) || (inpOutput === 'local' && inpArtifactUpload) + : inpSign === 'true'; + + if (inpOutput === 'local' && inpPush) { + core.warning(`push is ignored when output is local`); + } else if (inpOutput === 'image' && inpArtifactUpload) { + core.warning(`artifact-upload is ignored when output is image`); + } + if (inpOutput === 'image' && !inpPush && sign) { + core.setFailed(`signing attestation manifests requires push to be enabled`); + return; + } + + if (inpPlatforms.length > inpMatrixSizeLimit) { + core.setFailed(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); + return; } const privateRepo = GitHub.context.payload.repository?.private ?? false; - await core.group(`Set includes`, async () => { + await core.group(`Set includes output`, async () => { let includes = []; - if (inpBuildPlatforms.length > inpMatrixSizeLimit) { - throw new Error(`Platforms to build exceed matrix size limit of ${inpMatrixSizeLimit}`); - } else if (inpBuildPlatforms.length === 0) { + if (inpPlatforms.length === 0) { includes.push({ index: 0, - runner: runner === 'auto' ? 'ubuntu-24.04' : runner + runner: runner === 'auto' ? 'ubuntu-24.04' : runner, + sign: sign }); } else { - inpBuildPlatforms.forEach((platform, index) => { + inpPlatforms.forEach((platform, index) => { includes.push({ index: index, platform: platform, - runner: runner === 'auto' ? ((!privateRepo && platform.startsWith('linux/arm')) ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner + runner: runner === 'auto' ? ((!privateRepo && platform.startsWith('linux/arm')) ? 'ubuntu-24.04-arm' : 'ubuntu-24.04') : runner, + sign: sign }); }); } core.info(JSON.stringify(includes, null, 2)); core.setOutput('includes', JSON.stringify(includes)); }); + await core.group(`Set sign output`, async () => { + core.info(`sign: ${sign}`); + core.setOutput('sign', sign); + }); build: runs-on: ${{ matrix.runner }} @@ -292,7 +306,7 @@ jobs: uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0 if: ${{ inputs.setup-qemu }} with: - image: ${{ inputs.qemu-image }} + cache-image: false - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -300,6 +314,7 @@ jobs: version: ${{ env.BUILDX_VERSION }} buildkitd-flags: --debug driver-opts: image=${{ env.BUILDKIT_IMAGE }} + cache-binary: false - name: Prepare id: prepare @@ -307,20 +322,17 @@ jobs: env: INPUT_PLATFORM: ${{ matrix.platform }} INPUT_LOCAL-EXPORT-DIR: ${{ env.LOCAL_EXPORT_DIR }} + INPUT_ANNOTATIONS: ${{ inputs.annotations }} + INPUT_LABELS: ${{ inputs.labels }} INPUT_CONTEXT: ${{ inputs.context }} INPUT_OUTPUT: ${{ inputs.output }} INPUT_PUSH: ${{ inputs.push }} - INPUT_CACHE: ${{ inputs.cache }} - INPUT_CACHE-SCOPE: ${{ inputs.cache-scope }} - INPUT_CACHE-MODE: ${{ inputs.cache-mode }} + INPUT_TARGET: ${{ inputs.target }} INPUT_META-IMAGES: ${{ inputs.meta-images }} - INPUT_BUILD-ANNOTATIONS: ${{ inputs.build-annotations }} INPUT_SET-META-ANNOTATIONS: ${{ inputs.set-meta-annotations }} INPUT_META-ANNOTATIONS: ${{ steps.meta.outputs.annotations }} - INPUT_BUILD-LABELS: ${{ inputs.build-labels }} INPUT_SET-META-LABELS: ${{ inputs.set-meta-labels }} INPUT_META-LABELS: ${{ steps.meta.outputs.labels }} - INPUT_BUILD-TARGET: ${{ inputs.build-target }} with: script: | const { GitHub } = require('@docker/actions-toolkit/lib/github'); @@ -328,23 +340,20 @@ jobs: const inpPlatform = core.getInput('platform'); const platformPairSuffix = inpPlatform ? `-${inpPlatform.replace(/\//g, '-')}` : ''; core.setOutput('platform-pair-suffix', platformPairSuffix); - const inpLocalExportDir = core.getInput('local-export-dir'); + const inpAnnotations = core.getMultilineInput('annotations'); const inpContext = core.getInput('context'); + const inpLabels = core.getMultilineInput('labels'); const inpOutput = core.getInput('output'); const inpPush = core.getBooleanInput('push'); - const inpCache = core.getBooleanInput('cache'); - const inpCacheScope = core.getInput('cache-scope'); - const inpCacheMode = core.getInput('cache-mode'); + const inpTarget = core.getInput('target'); + const inpMetaImages = core.getMultilineInput('meta-images'); const inpSetMetaAnnotations = core.getBooleanInput('set-meta-annotations'); - const inpBuildAnnotations = core.getMultilineInput('build-annotations'); const inpMetaAnnotations = core.getMultilineInput('meta-annotations'); const inpSetMetaLabels = core.getBooleanInput('set-meta-labels'); - const inpBuildLabels = core.getMultilineInput('build-labels'); const inpMetaLabels = core.getMultilineInput('meta-labels'); - const inpBuildTarget = core.getInput('build-target'); const buildContext = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}.git#${process.env.GITHUB_REF}:${inpContext}`; core.setOutput('context', buildContext); @@ -353,6 +362,7 @@ jobs: case 'image': if (inpMetaImages.length == 0) { core.setFailed('meta-images is required when output is image'); + return; } core.setOutput('output', `type=image,"name=${inpMetaImages.join(',')}",oci-artifact=true,push-by-digest=true,name-canonical=true,push=${inpPush}`); break; @@ -361,26 +371,22 @@ jobs: break; default: core.setFailed(`Invalid output: ${inpOutput}`); + return; } if (inpPlatform) { core.setOutput('platform', inpPlatform); } - if (inpCache) { - core.setOutput('cache-from', `type=gha,scope=${inpCacheScope || inpBuildTarget || 'buildkit'}${platformPairSuffix}`); - core.setOutput('cache-to', `type=gha,scope=${inpCacheScope || inpBuildTarget || 'buildkit'}${platformPairSuffix},mode=${inpCacheMode}`); - } - if (inpSetMetaAnnotations && inpMetaAnnotations.length > 0) { - inpBuildAnnotations.push(...inpMetaAnnotations); + inpAnnotations.push(...inpMetaAnnotations); } - core.setOutput('annotations', inpBuildAnnotations.join('\n')); + core.setOutput('annotations', inpAnnotations.join('\n')); if (inpSetMetaLabels && inpMetaLabels.length > 0) { - inpBuildLabels.push(...inpMetaLabels); + inpLabels.push(...inpMetaLabels); } - core.setOutput('labels', inpBuildLabels.join('\n')); + core.setOutput('labels', inpLabels.join('\n')); if (GitHub.context.payload.repository?.private ?? false) { // if this is a private repository, we set the default provenance @@ -403,26 +409,23 @@ jobs: with: annotations: ${{ steps.prepare.outputs.annotations }} build-args: ${{ inputs.build-args }} - cache-from: ${{ steps.prepare.outputs.cache-from }} - cache-to: ${{ steps.prepare.outputs.cache-to }} context: ${{ steps.prepare.outputs.context }} - file: ${{ inputs.build-file }} + file: ${{ inputs.file }} labels: ${{ steps.prepare.outputs.labels }} outputs: ${{ steps.prepare.outputs.output }} platforms: ${{ steps.prepare.outputs.platform }} provenance: ${{ steps.prepare.outputs.provenance }} - pull: ${{ inputs.build-pull }} - sbom: ${{ inputs.build-sbom }} + sbom: ${{ inputs.sbom }} secret-envs: GIT_AUTH_TOKEN=GIT_AUTH_TOKEN - shm-size: ${{ inputs.build-shm-size }} - target: ${{ inputs.build-target }} - ulimit: ${{ inputs.build-ulimit }} + shm-size: ${{ inputs.shm-size }} + target: ${{ inputs.target }} + ulimit: ${{ inputs.ulimit }} env: BUILDKIT_MULTI_PLATFORM: 1 GIT_AUTH_TOKEN: ${{ secrets.github-token || github.token }} - name: Install Cosign - if: ${{ inputs.push }} + if: ${{ needs.prepare.outputs.sign }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_COSIGN-VERSION: ${{ env.COSIGN_VERSION }} @@ -432,7 +435,7 @@ jobs: const { Install } = require('@docker/actions-toolkit/lib/cosign/install'); const cosignInstall = new Install(); - const cosignBinPath = await cosignInstall.download(core.getInput('cosign-version'), false, true); + const cosignBinPath = await cosignInstall.download(core.getInput('cosign-version'), true, true); await cosignInstall.install(cosignBinPath); const cosign = new Cosign(); @@ -440,7 +443,7 @@ jobs: - name: Signing attestation manifests id: signing-attestation-manifests - if: ${{ inputs.push && inputs.output == 'image' }} + if: ${{ matrix.sign && inputs.output == 'image' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_IMAGE-NAMES: ${{ inputs.meta-images }} @@ -487,7 +490,7 @@ jobs: - name: Signing local artifacts id: signing-local-artifacts - if: ${{ inputs.push && inputs.output == 'local' }} + if: ${{ matrix.sign && inputs.output == 'local' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_LOCAL-OUTPUT-DIR: ${{ env.LOCAL_EXPORT_DIR }} @@ -523,7 +526,7 @@ jobs: tree -nh ${{ env.LOCAL_EXPORT_DIR }} - name: Upload artifact - if: ${{ inputs.output == 'local' }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix || '0' }} @@ -538,17 +541,22 @@ jobs: INPUT_VERIFY-COMMANDS: ${{ steps.signing-attestation-manifests.outputs.verify-commands || steps.signing-local-artifacts.outputs.verify-commands }} INPUT_IMAGE-DIGEST: ${{ steps.build.outputs.digest }} INPUT_ARTIFACT-NAME: ${{ inputs.artifact-name }}${{ steps.prepare.outputs.platform-pair-suffix }} + INPUT_ARTIFACT-UPLOAD: ${{ inputs.artifact-upload }} + INPUT_SIGNED: ${{ matrix.sign }} with: script: | const inpIndex = core.getInput('index'); const inpVerifyCommands = core.getInput('verify-commands'); const inpImageDigest = core.getInput('image-digest'); const inpArtifactName = core.getInput('artifact-name'); + const inpArtifactUpload = core.getBooleanInput('artifact-upload'); + const inpSigned = core.getBooleanInput('signed'); const result = { verifyCommands: inpVerifyCommands, imageDigest: inpImageDigest, - artifactName: inpArtifactName + artifactName: inpArtifactUpload ? inpArtifactName : '', + signed: inpSigned } core.info(JSON.stringify(result, null, 2)); @@ -560,9 +568,11 @@ jobs: meta-json: ${{ steps.meta.outputs.json }} cosign-version: ${{ env.COSIGN_VERSION }} cosign-verify-commands: ${{ steps.set.outputs.cosign-verify-commands }} - artifact-name: ${{ inputs.artifact-name }} + artifact-name: ${{ inputs.artifact-upload && inputs.artifact-name || '' }} output-type: ${{ inputs.output }} + signed: ${{ needs.prepare.outputs.sign }} needs: + - prepare - build steps: - @@ -590,6 +600,7 @@ jobs: version: ${{ env.BUILDX_VERSION }} buildkitd-flags: --debug driver-opts: image=${{ env.BUILDKIT_IMAGE }} + cache-binary: false - name: Create manifest if: ${{ inputs.output == 'image' }} @@ -614,7 +625,8 @@ jobs: } } if (digests.length === 0) { - throw new Error('No image digests found from build outputs'); + core.setFailed('No image digests found from build outputs'); + return; } for (const imageName of inpImageNames) { @@ -626,13 +638,7 @@ jobs: createArgs.push(digest); } if (inpPush) { - await exec.getExecOutput('docker', createArgs, { - ignoreReturnCode: true - }).then(res => { - if (res.stderr.length > 0 && res.exitCode != 0) { - throw new Error(res.stderr); - } - }); + await exec.exec('docker', createArgs); } else { await core.group(`Generated imagetools create command for ${imageName}`, async () => { core.info(`docker ${createArgs.join(' ')}`); @@ -641,7 +647,7 @@ jobs: } - name: Merge artifacts - if: ${{ inputs.output == 'local' }} + if: ${{ inputs.output == 'local' && inputs.artifact-upload }} uses: actions/upload-artifact/merge@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 with: name: ${{ inputs.artifact-name }} @@ -653,14 +659,19 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_BUILD-OUTPUTS: ${{ toJSON(needs.build.outputs) }} + INPUT_SIGNED: ${{ needs.prepare.outputs.sign }} with: script: | const inpBuildOutputs = JSON.parse(core.getInput('build-outputs')); - const verifyCommands = []; - for (const key of Object.keys(inpBuildOutputs)) { - const output = JSON.parse(inpBuildOutputs[key]); - if (output.verifyCommands) { - verifyCommands.push(output.verifyCommands); + const inpSigned = core.getBooleanInput('signed'); + + if (inpSigned) { + const verifyCommands = []; + for (const key of Object.keys(inpBuildOutputs)) { + const output = JSON.parse(inpBuildOutputs[key]); + if (output.verifyCommands) { + verifyCommands.push(output.verifyCommands); + } } + core.setOutput('cosign-verify-commands', verifyCommands.join('\n')); } - core.setOutput('cosign-verify-commands', verifyCommands.join('\n')); diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index d71bcdb..11a9789 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -31,33 +31,40 @@ jobs: const cosignVerifyCommands = builderOutputs['cosign-verify-commands']; const artifactName = builderOutputs['artifact-name']; const outputType = builderOutputs['output-type']; - if (!cosignVersion || !cosignVerifyCommands || !artifactName || !outputType) { - throw new Error('Missing required build outputs for verification'); + const signed = builderOutputs['signed'] === 'true'; + if (!signed) { + core.warning('No signatures to verify, skipping verification steps'); + } else if (!cosignVersion || !cosignVerifyCommands || !outputType || (outputType === 'local' && !artifactName)) { + core.setFailed('Missing required builder outputs for signature verification'); + return; } core.setOutput('cosign-version', cosignVersion); core.setOutput('cosign-verify-commands', cosignVerifyCommands); core.setOutput('artifact-name', artifactName); core.setOutput('output-type', outputType); + core.setOutput('signed', signed); - name: Install Cosign + if: ${{ steps.vars.outputs.signed == 'true' }} uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 with: cosign-release: ${{ steps.vars.outputs.cosign-version }} - name: Login to registry - if: ${{ steps.vars.outputs.output-type == 'image' }} + if: ${{ steps.vars.outputs.signed == 'true' && steps.vars.outputs.output-type == 'image' }} uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry-auth: ${{ secrets.registry-auths }} - name: Download artifacts - if: ${{ steps.vars.outputs.output-type == 'local' }} + if: ${{ steps.vars.outputs.signed == 'true' && steps.vars.outputs.output-type == 'local' }} uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 with: name: ${{ steps.vars.outputs.artifact-name }} - name: Verify signatures + if: ${{ steps.vars.outputs.signed == 'true' }} uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: INPUT_COSIGN-VERIFY-COMMANDS: ${{ steps.vars.outputs.cosign-verify-commands }} diff --git a/README.md b/README.md index ce8d62e..c746d47 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -[![Test workflow](https://img.shields.io/github/actions/workflow/status/docker/github-builder-experimental/.test.yml?label=test&logo=github&style=flat-square)](https://github.com/docker/github-builder-experimental/actions?workflow=.test) +[![Test build workflow](https://img.shields.io/github/actions/workflow/status/docker/github-builder-experimental/.test-build.yml?label=test%20build&logo=github&style=flat-square)](https://github.com/docker/github-builder-experimental/actions?workflow=.test-build) +[![Test bake workflow](https://img.shields.io/github/actions/workflow/status/docker/github-builder-experimental/.test-bake.yml?label=test%20bake&logo=github&style=flat-square)](https://github.com/docker/github-builder-experimental/actions?workflow=.test-bake) > [!CAUTION] > Do not use it for your production workflows yet! @@ -18,15 +19,20 @@ ___ * [Isolation & Reliability](#isolation--reliability) * [Usage](#usage) * [Build reusable workflow](#build-reusable-workflow) + * [Inputs](#inputs) + * [Secrets](#secrets) * [Bake reusable workflow](#bake-reusable-workflow) + * [Inputs](#inputs) + * [Secrets](#secrets) ## Overview This repository provides official Docker-maintained [reusable GitHub Actions workflows](https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows) to securely build container images and artifacts using Docker best practices. The reusable workflows incorporate functionality from our GitHub Actions like -`build-push-action`, `login-action`, `metadata-action`, etc., into a single -workflow: +[`docker/build-push-action`](https://github.com/docker/build-push-action/), +[`docker/metadata-action`](https://github.com/docker/metadata-action/), etc., +into a single workflow: ```yaml name: ci @@ -74,12 +80,6 @@ toward higher levels of security and trust. requiring emulation or [custom CI logic](https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners) or self-managed runners. -* **Optimized cache warming & reuse.** - The builder can use the GitHub Actions cache backend to persist layers across - branches, PRs, and rebuilds. This significantly reduces cold-start times and - avoids repeating expensive dependency installations, even for external - contributors' pull requests. - * **Centralized build configuration.** Repositories no longer need to configure buildx drivers, tune storage, or adjust resource limits. The reusable workflows encapsulate the recommended @@ -145,6 +145,14 @@ toward higher levels of security and trust. ### Build reusable workflow +The [`build.yml` reusable workflow](.github/workflows/build.yml) lets you build +container images and artifacts from a Dockerfile with a user experience similar +to [`docker/build-push-action`](https://github.com/docker/build-push-action/). +It provides a Docker-maintained, opinionated build pipeline that applies best +practices for security, performance, and reliability by default, including +isolated execution and signed SLSA provenance while keeping per-repository +configuration minimal. + ```yaml name: ci @@ -167,12 +175,12 @@ on: with: output: image push: ${{ github.event_name != 'pull_request' }} + platforms: linux/amd64,linux/arm64 meta-images: name/app meta-tags: | type=ref,event=branch type=ref,event=pr type=semver,pattern={{version}} - build-platforms: linux/amd64,linux/arm64 secrets: registry-auths: | - registry: docker.io @@ -196,10 +204,64 @@ on: password: ${{ secrets.DOCKERHUB_TOKEN }} ``` -You can find the list of available inputs in [`.github/workflows/build.yml`](.github/workflows/build.yml). +#### Inputs + +> [!NOTE] +> `List` type is a newline-delimited string +> ```yaml +> cache-from: | +> user/app:cache +> type=local,src=path/to/dir +> ``` +> +> `CSV` type is a comma-delimited string +> ```yaml +> tags: name/app:latest,name/app:1.0.0 +> ``` + +| Name | Type | Default | Description | +|------------------------|-------------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `runner` | String | `auto` | [Ubuntu GitHub Hosted Runner](https://github.com/actions/runner-images?tab=readme-ov-file#available-images) to build on (one of `auto`, `amd64`, `arm64`). The `auto` runner selects the best-matching runner based on target `platforms`. You can set it to `amd64` if your build doesn't require emulation (e.g. cross-compilation) | +| `setup-qemu` | Bool | `false` | Runs the `setup-qemu-action` step to install QEMU static binaries | +| `artifact-name` | String | `docker-github-builder-assets` | Name of the uploaded GitHub artifact (for `local` output) | +| `artifact-upload` | Bool | `false` | Upload build output GitHub artifact (for `local` output) | +| `envs` | List | | Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the [GitHub Actions `env` context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#env-context) that is currently not available when calling a reusable workflow | +| `annotations` | List | | List of annotations to set to the image (for `image` output) | +| `build-args` | List | `auto` | List of [build-time variables](https://docs.docker.com/engine/reference/commandline/buildx_build/#build-arg). If you want to set a build-arg through an environment variable, use the `envs` input | +| `context` | String | `.` | Context to build from in the Git working tree | +| `file` | String | `{context}/Dockerfile` | Path to the Dockerfile | +| `labels` | List | | List of labels for an image (for `image` output) | +| `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) | +| `sbom` | Bool/String | | 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 | +| `target` | String | | Sets the target stage to build | +| `ulimit` | List | | [Ulimit](https://docs.docker.com/engine/reference/commandline/buildx_build/#ulimit) options (e.g., `nofile=1024:1024`) | +| `set-meta-annotations` | Bool | `false` | Append OCI Image Format Specification annotations generated by `docker/metadata-action` | +| `set-meta-labels` | Bool | `false` | Append OCI Image Format Specification labels generated by `docker/metadata-action` | +| `meta-images` | List | | [List of images](https://github.com/docker/metadata-action?tab=readme-ov-file#images-input) to use as base name for tags (required for image output) | +| `meta-tags` | List | `auto` | [List of tags](https://github.com/docker/metadata-action?tab=readme-ov-file#tags-input) as key-value pair attributes | +| `meta-flavor` | List | `auto` | [Flavor](https://github.com/docker/metadata-action?tab=readme-ov-file#flavor-input) defines a global behavior for `meta-tags` | + +#### 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 | ### Bake reusable workflow +The [`bake.yml` reusable workflow](.github/workflows/build.yml) lets you build +container images and artifacts from a [Bake definition](https://docs.docker.com/build/bake/) +with a user experience similar to [`docker/bake-action`](https://github.com/docker/bake-action/). +It provides a Docker-maintained, opinionated build pipeline that applies best +practices for security, performance, and reliability by default, including +isolated execution and signed SLSA provenance while keeping per-repository +configuration minimal. + ```yaml name: ci @@ -250,4 +312,42 @@ on: password: ${{ secrets.DOCKERHUB_TOKEN }} ``` -You can find the list of available inputs in [`.github/workflows/bake.yml`](.github/workflows/bake.yml). +#### Inputs + +> `List` type is a newline-delimited string +> ```yaml +> set: target.args.mybuildarg=value +> ``` +> ```yaml +> set: | +> target.args.mybuildarg=value +> foo*.args.mybuildarg=value +> ``` + +| Name | Type | Default | Description | +|------------------------|-------------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `runner` | String | `auto` | [Ubuntu GitHub Hosted Runner](https://github.com/actions/runner-images?tab=readme-ov-file#available-images) to build on (one of `auto`, `amd64`, `arm64`). The `auto` runner selects the best-matching runner based on target `platforms`. You can set it to `amd64` if your build doesn't require emulation (e.g. cross-compilation) | +| `setup-qemu` | Bool | `false` | Runs the `setup-qemu-action` step to install QEMU static binaries | +| `artifact-name` | String | `docker-github-builder-assets` | Name of the uploaded GitHub artifact (for `local` output) | +| `artifact-upload` | Bool | `false` | Upload build output GitHub artifact (for `local` output) | +| `envs` | List | | Environment variables to inject in the reusable workflow as list of key-value pair. This is similar to the [GitHub Actions `env` context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#env-context) that is currently not available when calling a reusable workflow | +| `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/)). | +| `push` | Bool | `false` | Push image to the registry (for `image` output) | +| `sbom` | Bool/String | | 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 | +| `target` | String | `default` | Bake target to build | +| `set-meta-annotations` | Bool | `false` | Append OCI Image Format Specification annotations generated by `docker/metadata-action` | +| `set-meta-labels` | Bool | `false` | Append OCI Image Format Specification labels generated by `docker/metadata-action` | +| `meta-images` | List | | [List of images](https://github.com/docker/metadata-action?tab=readme-ov-file#images-input) to use as base name for tags (required for image output) | +| `meta-tags` | List | `auto` | [List of tags](https://github.com/docker/metadata-action?tab=readme-ov-file#tags-input) as key-value pair attributes | +| `meta-flavor` | List | `auto` | [Flavor](https://github.com/docker/metadata-action?tab=readme-ov-file#flavor-input) defines a global behavior for `meta-tags` | + +#### 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 |