diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff13308..a8b7f69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -121,6 +121,27 @@ jobs: exit 1 fi + error-source: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v6 + - + name: Build + id: bake + continue-on-error: true + uses: ./ + with: + source: ./does-not-exist + - + name: Check + run: | + if [ "${{ steps.bake.outcome }}" != "failure" ] || [ "${{ steps.bake.conclusion }}" != "success" ]; then + echo "::error::Should have failed" + exit 1 + fi + standalone: runs-on: ubuntu-latest steps: @@ -190,8 +211,7 @@ jobs: name: Build uses: ./ with: - workdir: ./test/go - source: . + source: ./test/go targets: binary provenance: ${{ matrix.attrs }} set: | @@ -232,8 +252,7 @@ jobs: name: Build uses: ./ with: - workdir: ./test/go - source: . + source: ./test/go targets: ${{ matrix.target }} sbom: true set: | @@ -279,8 +298,7 @@ jobs: name: Build uses: ./ with: - workdir: ./test/go - source: . + source: ./test/go set: | *.platform=linux/amd64 *.output=type=image,"name=localhost:5000/name/app:v1.0.0,localhost:5000/name/app:latest",push=true @@ -309,8 +327,7 @@ jobs: name: Build and push uses: ./ with: - workdir: ./test/group - source: . + source: ./test/group push: true set: | t1.tags=localhost:5000/name/app:t1 @@ -472,8 +489,7 @@ jobs: name: Build and push uses: ./ with: - workdir: ./test/go - source: . + source: ./test/go set: | *.output=type=image,name=localhost:5000/name/app:latest,push=true *.output=type=docker,name=app:local @@ -516,8 +532,7 @@ jobs: name: Build and push uses: ./ with: - workdir: ./test/go - source: . + source: ./test/go targets: image load: true push: true @@ -651,8 +666,7 @@ jobs: name: Build uses: ./ with: - workdir: ./test - source: . + source: ./test files: | ./lint.hcl @@ -673,8 +687,7 @@ jobs: name: Build uses: ./ with: - workdir: ./test - source: . + source: ./test files: | ./lint.hcl env: @@ -745,8 +758,7 @@ jobs: continue-on-error: true uses: ./ with: - workdir: ./test - source: . + source: ./test files: | ./lint.hcl call: check @@ -778,8 +790,7 @@ jobs: continue-on-error: true uses: ./ with: - workdir: ./test - source: . + source: ./test files: | ./lint.hcl call: check @@ -832,5 +843,4 @@ jobs: name: Build and push uses: ./ with: - workdir: ./test/attest - source: . + source: ./test/attest diff --git a/README.md b/README.md index 00d600d..ed47e57 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ ___ * [environment variables](#environment-variables) * [Subactions](#subactions) * [`matrix`](subaction/matrix) +* [Notes](#notes) + * [Source semantics](#source-semantics) * [Contributing](#contributing) ## Usage @@ -143,6 +145,31 @@ jobs: *.tags=user/app:latest ``` +If you point `source` to a subdirectory, relative paths are resolved from that +subdirectory: + +```yaml + - + name: Build and push + uses: docker/bake-action@v6 + with: + source: ./subdir + files: ./docker-bake.hcl +``` + +For example, if `./subdir/docker-bake.hcl` contains: + +```hcl +target "default" { + output = ["type=local,dest=./artifacts"] +} +``` + +The output will be written to `./subdir/artifacts` in the workspace. + +> [!NOTE] +> More info about `source` semantics in the [Source semantics](#source-semantics) section. + ## Summaries This action generates a [job summary](https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/) @@ -197,23 +224,22 @@ The following inputs can be used as `step.with` keys > targets: default,release > ``` -| Name | Type | Description | -|----------------|-------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `builder` | String | Builder instance (see [setup-buildx](https://github.com/docker/setup-buildx-action) action) | -| `workdir` | String | Working directory of execution | -| `source` | String | Context to build from. Can be either local (`.`) or a [remote bake definition](https://docs.docker.com/build/bake/remote-definition/) | -| `allow` | List/CSV | Allow build to access specified resources (e.g., `network.host`) | -| `call` | String | Set method for evaluating build (e.g., check) | -| `files` | List/CSV | List of [bake definition files](https://docs.docker.com/build/customize/bake/file-definition/) | -| `no-cache` | Bool | Do not use cache when building the image (default `false`) | -| `pull` | Bool | Always attempt to pull a newer version of the image (default `false`) | -| `load` | Bool | Load is a shorthand for `--set=*.output=type=docker` (default `false`) | -| `provenance` | Bool/String | [Provenance](https://docs.docker.com/build/attestations/slsa-provenance/) is a shorthand for `--set=*.attest=type=provenance` | -| `push` | Bool | Push is a shorthand for `--set=*.output=type=registry` (default `false`) | -| `sbom` | Bool/String | [SBOM](https://docs.docker.com/build/attestations/sbom/) is a shorthand for `--set=*.attest=type=sbom` | -| `set` | List | List of [targets values to override](https://docs.docker.com/engine/reference/commandline/buildx_bake/#set) (e.g., `targetpattern.key=value`) | -| `targets` | List/CSV | List of bake targets (`default` target used if empty) | -| `github-token` | String | API token used to authenticate to a Git repository for [remote definitions](https://docs.docker.com/build/bake/remote-definition/) (default `${{ github.token }}`) | +| Name | Type | Description | +|----------------|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `builder` | String | Builder instance (see [setup-buildx](https://github.com/docker/setup-buildx-action) action) | +| `allow` | List/CSV | Allow build to access specified resources (e.g., `network.host`) | +| `call` | String | Set method for evaluating build (e.g., check) | +| `files` | List/CSV | List of [bake definition files](https://docs.docker.com/build/customize/bake/file-definition/) | +| `no-cache` | Bool | Do not use cache when building the image (default `false`) | +| `pull` | Bool | Always attempt to pull a newer version of the image (default `false`) | +| `load` | Bool | Load is a shorthand for `--set=*.output=type=docker` (default `false`) | +| `provenance` | Bool/String | [Provenance](https://docs.docker.com/build/attestations/slsa-provenance/) is a shorthand for `--set=*.attest=type=provenance` | +| `push` | Bool | Push is a shorthand for `--set=*.output=type=registry` (default `false`) | +| `sbom` | Bool/String | [SBOM](https://docs.docker.com/build/attestations/sbom/) is a shorthand for `--set=*.attest=type=sbom` | +| `set` | List | List of [targets values to override](https://docs.docker.com/engine/reference/commandline/buildx_bake/#set) (e.g., `targetpattern.key=value`) | +| `source` | String | Build source to use. Supports local path and [remote bake definition](https://docs.docker.com/build/bake/remote-definition/). With a local path, Bake runs from that directory, so all relative paths are resolved from it. See [Source semantics](#source-semantics). | +| `targets` | List/CSV | List of bake targets (`default` target used if empty) | +| `github-token` | String | API token used to authenticate to a Git repository for [remote definitions](https://docs.docker.com/build/bake/remote-definition/) (default `${{ github.token }}`) | ### outputs @@ -236,6 +262,23 @@ The following outputs are available * [`matrix`](subaction/matrix) +## Notes + +### Source semantics + +`source` accepts either a Git/remote bake definition (for example `{{defaultContext}}` or `{{defaultContext}}:subdir`) +or a local path (for example `.` or `./subdir`). When `source` is a local path, +the action runs Bake from that directory (equivalent to `cd && docker buildx bake`). + +This local path mode affects all relative paths resolved by Bake, not only +target `context` fields. This includes paths used by local outputs, cache +import/export, and `cwd://` references. + +| `source` | Behavior | +|-----------------------------------------------------------------------|------------------------------------------------------------------------------------------------| +| Git/remote (`{{defaultContext}}`, `https://...git#ref`, `...:subdir`) | Uses [remote bake definition](https://docs.docker.com/build/bake/remote-definition/) behavior. | +| Local path (`.`, `./subdir`) | Changes Bake working directory to that path before invoking Bake. | + ## Contributing Want to contribute? Awesome! You can find information about contributing to diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts index 2c95387..f1e6cba 100644 --- a/__tests__/context.test.ts +++ b/__tests__/context.test.ts @@ -368,11 +368,11 @@ describe('getArgs', () => { provenance: inp.provenance, push: inp.push, sbom: inp.sbom, - source: inp.source, + source: inp.source.remoteRef, targets: inp.targets }, { - cwd: inp.workdir + cwd: inp.source.workdir, } ); const res = await context.getArgs(inp, definition, toolkit); diff --git a/action.yml b/action.yml index 592d9b1..d042f39 100644 --- a/action.yml +++ b/action.yml @@ -10,13 +10,6 @@ inputs: builder: description: "Builder instance" required: false - workdir: - description: "Working directory of bake execution" - required: false - default: '.' - source: - description: "Context to build from. Can be either local or a remote bake definition" - required: false allow: description: "Allow build to access specified resources (e.g., network.host)" required: false @@ -51,6 +44,9 @@ inputs: set: description: "List of targets values to override (eg. targetpattern.key=value)" required: false + source: + description: "Context to build from. Can be either local to specify the working directory or a remote bake definition" + required: false targets: description: "List of bake targets" required: false diff --git a/src/context.ts b/src/context.ts index 41e7580..6df7826 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,3 +1,4 @@ +import * as fs from 'fs'; import * as core from '@actions/core'; import * as handlebars from 'handlebars'; @@ -10,10 +11,13 @@ import {Util} from '@docker/actions-toolkit/lib/util.js'; import {BakeDefinition} from '@docker/actions-toolkit/lib/types/buildx/bake.js'; +export interface BakeContext { + remoteRef?: string; + workdir?: string; +} + export interface Inputs { builder: string; - workdir: string; - source: string; allow: string[]; call: string; files: string[]; @@ -24,6 +28,7 @@ export interface Inputs { push: boolean; sbom: string; set: string[]; + source: BakeContext; targets: string[]; 'github-token': string; } @@ -31,8 +36,6 @@ export interface Inputs { export async function getInputs(): Promise { return { builder: core.getInput('builder'), - workdir: core.getInput('workdir') || '.', - source: getSourceInput('source'), allow: Util.getInputList('allow'), call: core.getInput('call'), files: Util.getInputList('files'), @@ -43,6 +46,7 @@ export async function getInputs(): Promise { push: core.getBooleanInput('push'), sbom: core.getInput('sbom'), set: Util.getInputList('set', {ignoreComma: true, quote: false}), + source: getBakeContext(core.getInput('source')), targets: Util.getInputList('targets'), 'github-token': core.getInput('github-token') }; @@ -59,8 +63,8 @@ export async function getArgs(inputs: Inputs, definition: BakeDefinition, toolki async function getBakeArgs(inputs: Inputs, definition: BakeDefinition, toolkit: Toolkit): Promise> { const args: Array = ['bake']; - if (inputs.source) { - args.push(inputs.source); + if (inputs.source.remoteRef) { + args.push(inputs.source.remoteRef); } if (await toolkit.buildx.versionSatisfies('>=0.17.0')) { if (await toolkit.buildx.versionSatisfies('>=0.18.0')) { @@ -135,17 +139,26 @@ async function getCommonArgs(inputs: Inputs): Promise> { return args; } -function getSourceInput(name: string): string { - let source = handlebars.compile(core.getInput(name))({ +function getBakeContext(sourceInput: string): BakeContext { + let bakeContext = handlebars.compile(sourceInput)({ defaultContext: Context.gitContext() }); - if (!source) { - source = Context.gitContext(); + if (!bakeContext) { + bakeContext = Context.gitContext(); } - if (source === '.') { - source = ''; + if (Util.isValidRef(bakeContext)) { + return { + remoteRef: bakeContext + }; } - return source; + try { + fs.statSync(sourceInput).isDirectory(); + } catch { + throw new Error(`Invalid source: ${sourceInput} does not exist or is not a directory.`); + } + return { + workdir: bakeContext + }; } function noDefaultAttestations(): boolean { diff --git a/src/main.ts b/src/main.ts index a20b6f6..e6dd12b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -107,12 +107,12 @@ actionsToolkit.run( provenance: inputs.provenance, push: inputs.push, sbom: inputs.sbom, - source: inputs.source, + source: inputs.source.remoteRef, targets: inputs.targets, githubToken: gitAuthToken }, { - cwd: inputs.workdir + cwd: inputs.source.workdir } ); }); @@ -132,7 +132,7 @@ actionsToolkit.run( await core.group(`Bake definition`, async () => { await Exec.getExecOutput(buildCmd.command, [...buildCmd.args, '--print'], { - cwd: inputs.workdir, + cwd: inputs.source.workdir, env: buildEnv, ignoreReturnCode: true }).then(res => { @@ -144,7 +144,7 @@ actionsToolkit.run( let err: Error | undefined; await Exec.getExecOutput(buildCmd.command, buildCmd.args, { - cwd: inputs.workdir, + cwd: inputs.source.workdir, env: buildEnv, ignoreReturnCode: true }).then(res => { diff --git a/src/state-helper.ts b/src/state-helper.ts index 89ee619..8731ac1 100644 --- a/src/state-helper.ts +++ b/src/state-helper.ts @@ -40,8 +40,11 @@ export function setSummarySupported() { export function setSummaryInputs(inputs: Inputs) { const res = {}; + if (inputs.source.remoteRef || inputs.source.workdir) { + res['source'] = inputs.source.remoteRef || inputs.source.workdir; + } for (const key of Object.keys(inputs)) { - if (key === 'github-token') { + if (key === 'source' || key === 'github-token') { continue; } const value: string | string[] | boolean = inputs[key];