diff --git a/.github/subaction-list-targets.png b/.github/subaction-list-targets.png index b0a0086..f6e82db 100644 Binary files a/.github/subaction-list-targets.png and b/.github/subaction-list-targets.png differ diff --git a/.github/subaction-matrix.png b/.github/subaction-matrix.png new file mode 100644 index 0000000..4a67d9e Binary files /dev/null and b/.github/subaction-matrix.png differ diff --git a/.github/workflows/ci-subaction.yml b/.github/workflows/ci-subaction.yml index ebd7028..4235b1d 100644 --- a/.github/workflows/ci-subaction.yml +++ b/.github/workflows/ci-subaction.yml @@ -25,8 +25,28 @@ on: - 'test/**' jobs: - list-targets-group: + list-targets: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - + testdir: group + expected: > + ["t1","t2"] + - + testdir: group-matrix + target: validate + expected: > + ["lint-default","lint-labs","lint-nydus","lint-proto","lint-yaml","validate-doctoc","validate-vendor"] + - + testdir: multi-files + files: | + docker-bake.json + docker-bake.hcl + expected: > + ["v1-tag","v2-tag"] steps: - name: Checkout @@ -36,20 +56,57 @@ jobs: id: gen uses: ./subaction/list-targets with: - workdir: ./test/group + workdir: ./test/${{ matrix.testdir }} + files: ${{ matrix.files }} + target: ${{ matrix.target }} - - name: Check targets + name: Check output uses: actions/github-script@v7 + env: + INPUT_TARGETS: ${{ steps.gen.outputs.targets }} + INPUT_EXPECTED: ${{ matrix.expected }} with: script: | - const targets = `${{ steps.gen.outputs.targets }}`; - if (!targets) { - core.setFailed('No targets generated'); + const targets = JSON.stringify(JSON.parse(core.getInput('targets'))); + const expected = JSON.stringify(JSON.parse(core.getInput('expected'))); + if (targets !== expected) { + throw new Error(`Targets do not match expected values: ${targets} != ${expected}`); + } else { + core.info(`✅`); } - core.info(`targets=${targets}`); - list-targets-group-matrix: + matrix: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - + testdir: group + expected: > + [{"target":"t1"},{"target":"t2"}] + - + testdir: group-matrix + target: validate + expected: > + [{"target":"lint-default"},{"target":"lint-labs"},{"target":"lint-nydus"},{"target":"lint-proto"},{"target":"lint-yaml"},{"target":"validate-doctoc"},{"target":"validate-vendor"}] + - + testdir: group-with-platform + target: validate + expected: > + [{"target":"lint"},{"target":"lint-gopls"},{"target":"validate-docs"},{"target":"validate-vendor"}] + - + testdir: group-with-platform + target: validate + fields: platforms + expected: > + [{"target":"lint","platforms":"darwin/amd64"},{"target":"lint","platforms":"darwin/arm64"},{"target":"lint","platforms":"linux/amd64"},{"target":"lint","platforms":"linux/arm64"},{"target":"lint","platforms":"linux/s390x"},{"target":"lint","platforms":"linux/ppc64le"},{"target":"lint","platforms":"linux/riscv64"},{"target":"lint","platforms":"windows/amd64"},{"target":"lint","platforms":"windows/arm64"},{"target":"lint-gopls","platforms":"darwin/amd64"},{"target":"lint-gopls","platforms":"darwin/arm64"},{"target":"lint-gopls","platforms":"linux/amd64"},{"target":"lint-gopls","platforms":"linux/arm64"},{"target":"lint-gopls","platforms":"linux/s390x"},{"target":"lint-gopls","platforms":"linux/ppc64le"},{"target":"lint-gopls","platforms":"linux/riscv64"},{"target":"lint-gopls","platforms":"windows/amd64"},{"target":"lint-gopls","platforms":"windows/arm64"},{"target":"validate-docs"},{"target":"validate-vendor"}] + - + testdir: group-with-platform + target: validate + fields: platforms,dockerfile + expected: > + [{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"darwin/amd64"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"darwin/arm64"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/amd64"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/arm64"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/s390x"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/ppc64le"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/riscv64"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"windows/amd64"},{"target":"lint","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"windows/arm64"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"darwin/amd64"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"darwin/arm64"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/amd64"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/arm64"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/s390x"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/ppc64le"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"linux/riscv64"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"windows/amd64"},{"target":"lint-gopls","dockerfile":"./hack/dockerfiles/lint.Dockerfile","platforms":"windows/arm64"},{"target":"validate-docs","dockerfile":"./hack/dockerfiles/docs.Dockerfile"},{"target":"validate-vendor","dockerfile":"./hack/dockerfiles/vendor.Dockerfile"}] steps: - name: Checkout @@ -57,43 +114,23 @@ jobs: - name: Matrix gen id: gen - uses: ./subaction/list-targets + uses: ./subaction/matrix with: - workdir: ./test/group-matrix - target: validate + workdir: ./test/${{ matrix.testdir }} + target: ${{ matrix.target }} + fields: ${{ matrix.fields }} - - name: Check targets + name: Check output uses: actions/github-script@v7 + env: + INPUT_MATRIX: ${{ steps.gen.outputs.matrix }} + INPUT_EXPECTED: ${{ matrix.expected }} with: script: | - const targets = `${{ steps.gen.outputs.targets }}`; - if (!targets) { - core.setFailed('No targets generated'); + const matrix = JSON.stringify(JSON.parse(core.getInput('matrix'))); + const expected = JSON.stringify(JSON.parse(core.getInput('expected'))); + if (matrix !== expected) { + throw new Error(`Matrix do not match expected values: ${matrix} != ${expected}`); + } else { + core.info(`✅`); } - core.info(`targets=${targets}`); - - list-targets-multi-files: - runs-on: ubuntu-latest - steps: - - - name: Checkout - uses: actions/checkout@v5 - - - name: Matrix gen - id: gen - uses: ./subaction/list-targets - with: - workdir: ./test/multi-files - files: | - docker-bake.json - docker-bake.hcl - - - name: Check targets - uses: actions/github-script@v7 - with: - script: | - const targets = `${{ steps.gen.outputs.targets }}`; - if (!targets) { - core.setFailed('No targets generated'); - } - core.info(`targets=${targets}`); diff --git a/README.md b/README.md index 10fc84a..cb03aa7 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ ___ * [outputs](#outputs) * [environment variables](#environment-variables) * [Subactions](#subactions) - * [`list-targets`](subaction/list-targets) + * [`matrix`](subaction/matrix) * [Contributing](#contributing) ## Usage @@ -234,7 +234,7 @@ The following outputs are available ## Subactions -* [`list-targets`](subaction/list-targets) +* [`matrix`](subaction/matrix) ## Contributing diff --git a/subaction/list-targets/README.md b/subaction/list-targets/README.md index bdc02b1..7b42f22 100644 --- a/subaction/list-targets/README.md +++ b/subaction/list-targets/README.md @@ -1,9 +1,14 @@ +> [!WARNING] +> `docker/bake-action/subaction/list-targets` is deprecated and will be removed +> in a future release. Please use [`docker/bake-action/subaction/matrix`](../matrix) +> instead. + ## About This subaction generates a list of Bake targets that can be used in a [GitHub matrix](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix), so you can distribute your builds across multiple runners. -![Screenshot](../../.github/bake-action.png) +![Screenshot](../../.github/subaction-list-targets.png) ___ @@ -55,9 +60,6 @@ jobs: matrix: target: ${{ fromJson(needs.prepare.outputs.targets) }} steps: - - - name: Checkout - uses: actions/checkout@v4 - name: Validate uses: docker/bake-action@v6 @@ -79,6 +81,6 @@ jobs: The following outputs are available -| Name | Type | Description | -|------------|----------|----------------------------| -| `targets` | List/CSV | List of extracted targest | +| Name | Type | Description | +|------------|----------|---------------------------| +| `targets` | List/CSV | List of extracted targets | diff --git a/subaction/list-targets/action.yml b/subaction/list-targets/action.yml index 9613b09..e4ef2d3 100644 --- a/subaction/list-targets/action.yml +++ b/subaction/list-targets/action.yml @@ -22,18 +22,6 @@ outputs: runs: using: composite steps: - - - name: Install npm dependencies - uses: actions/github-script@v7 - env: - INPUT_ACTIONS-TOOLKIT-VERSION: '0.62.1' - with: - script: | - const version = core.getInput('actions-toolkit-version') || 'latest'; - const dep = `@docker/actions-toolkit@${version}`; - await core.group(`Installing ${dep}`, async () => { - await exec.exec('npm', ['install', dep]); - }); - name: Generate id: generate @@ -44,10 +32,14 @@ runs: INPUT_TARGET: ${{ inputs.target }} with: script: | - const { Util } = require('@docker/actions-toolkit/lib/util'); + core.warning(`docker/bake-action/subaction/list-targets is deprecated and will be removed in a future release. Please use docker/bake-action/subaction/matrix instead.`); + + function getInputList(name) { + return core.getInput(name) ? core.getInput(name).split(/[\r?\n,]+/).filter(x => x !== '') : []; + } const workdir = core.getInput('workdir'); - const files = Util.getInputList('files'); + const files = getInputList('files'); const target = core.getInput('target'); let def = {}; diff --git a/subaction/matrix/README.md b/subaction/matrix/README.md new file mode 100644 index 0000000..a84d9a3 --- /dev/null +++ b/subaction/matrix/README.md @@ -0,0 +1,140 @@ +## About + +This subaction generates a multi-dimension matrix that can be used in a [GitHub matrix](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) +through the [`include` property](https://docs.github.com/en/actions/how-tos/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#expanding-or-adding-matrix-configurations) +so you can distribute your builds across multiple runners. + +![Screenshot](../../.github/subaction-matrix.png) + +___ + +* [Usage](#usage) +* [Customizing](#customizing) + * [inputs](#inputs) + * [outputs](#outputs) + +## Usage + +### List targets + +```hcl +# docker-bake.hcl +group "validate" { + targets = ["lint", "doctoc"] +} + +target "lint" { + target = "lint" +} + +target "doctoc" { + target = "doctoc" +} +``` + +```yaml +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.generate.outputs.matrix }} + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Generate matrix + id: generate + uses: docker/bake-action/subaction/matrix@v6 + with: + target: validate + + validate: + runs-on: ubuntu-latest + needs: + - prepare + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.prepare.outputs.matrix) }} + steps: + - + name: Validate + uses: docker/bake-action@v6 + with: + targets: ${{ matrix.target }} +``` + +### Platforms split + +```hcl +# docker-bake.hcl +target "lint" { + dockerfile = "./hack/dockerfiles/lint.Dockerfile" + output = ["type=cacheonly"] + platforms = [ + "darwin/amd64", + "darwin/arm64", + "linux/amd64", + "linux/arm64", + "linux/s390x", + "linux/ppc64le", + "linux/riscv64", + "windows/amd64", + "windows/arm64" + ] +} +``` + +```yaml +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.generate.outputs.matrix }} + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Generate matrix + id: generate + uses: docker/bake-action/subaction/matrix@v6 + with: + target: lint + fields: platforms + + lint: + runs-on: ${{ startsWith(matrix.platforms, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} + needs: + - prepare + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.prepare.outputs.matrix) }} + steps: + - + name: Lint + uses: docker/bake-action@v6 + with: + targets: ${{ matrix.target }} + set: | + *.platform=${{ matrix.platforms }} +``` + +## Customizing + +### inputs + +| Name | Type | Description | +|-----------|----------|------------------------------------------------------------------------------------------------| +| `workdir` | String | Working directory to use (defaults to `.`) | +| `files` | List/CSV | List of [bake definition files](https://docs.docker.com/build/customize/bake/file-definition/) | +| `target` | String | The target to use within the bake file | +| `fields` | String | List of extra fields to include in the matrix | + +### outputs + +| Name | Type | Description | +|----------|------|----------------------| +| `matrix` | JSON | Matrix configuration | diff --git a/subaction/matrix/action.yml b/subaction/matrix/action.yml new file mode 100644 index 0000000..32b2000 --- /dev/null +++ b/subaction/matrix/action.yml @@ -0,0 +1,101 @@ +# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions +name: 'Matrix' +description: 'Generate a matrix from a Bake definition to help distributing builds in your workflow' + +inputs: + workdir: + description: Working directory + default: '.' + required: false + files: + description: List of Bake files + required: false + target: + description: Bake target + required: false + fields: + description: List of extra fields to include in the matrix + required: false + +outputs: + matrix: + description: Matrix configuration + value: ${{ steps.generate.outputs.includes }} + +runs: + using: composite + steps: + - + name: Generate + id: generate + uses: actions/github-script@v7 + env: + INPUT_WORKDIR: ${{ inputs.workdir }} + INPUT_FILES: ${{ inputs.files }} + INPUT_TARGET: ${{ inputs.target }} + INPUT_FIELDS: ${{ inputs.fields }} + with: + script: | + function getInputList(name) { + return core.getInput(name) ? core.getInput(name).split(/[\r?\n,]+/).filter(x => x !== '') : []; + } + + const workdir = core.getInput('workdir'); + const files = getInputList('files'); + const target = core.getInput('target'); + const fields = getInputList('fields'); + + let def = {}; + await core.group(`Parsing definition`, async () => { + let args = ['buildx', 'bake']; + for (const file of files) { + args.push('--file', file); + } + if (target) { + args.push(target); + } + args.push('--print'); + const res = await exec.getExecOutput('docker', args, { + ignoreReturnCode: true, + silent: true, + cwd: workdir + }); + if (res.stderr.length > 0 && res.exitCode != 0) { + throw new Error(res.stderr); + } + def = JSON.parse(res.stdout.trim()); + core.info(JSON.stringify(def, null, 2)); + }); + + await core.group(`Generating matrix`, async () => { + const result = []; + for (const targetName of Object.keys(def.target)) { + const target = def.target[targetName]; + const entry = { target: targetName }; + if (fields.length === 0) { + result.push({ ...entry }); + continue; + } + let fieldFound = false; + Object.keys(target).forEach(field => { + if (fields.includes(field)) { + fieldFound = true; + const value = target[field]; + if (Array.isArray(value)) { + value.forEach((v) => { + entry[field] = v; + result.push({ ...entry }); + }); + } else { + entry[field] = value; + result.push({ ...entry }); + } + } + }); + if (!fieldFound) { + result.push({ ...entry }); + } + } + core.info(JSON.stringify(result, null, 2)); + core.setOutput('includes', JSON.stringify(result)); + }); diff --git a/test/group-with-platform/docker-bake.hcl b/test/group-with-platform/docker-bake.hcl new file mode 100644 index 0000000..bfba830 --- /dev/null +++ b/test/group-with-platform/docker-bake.hcl @@ -0,0 +1,36 @@ +group "validate" { + targets = ["lint", "lint-gopls", "validate-vendor", "validate-docs"] +} + +target "lint" { + dockerfile = "./hack/dockerfiles/lint.Dockerfile" + output = ["type=cacheonly"] + platforms = [ + "darwin/amd64", + "darwin/arm64", + "linux/amd64", + "linux/arm64", + "linux/s390x", + "linux/ppc64le", + "linux/riscv64", + "windows/amd64", + "windows/arm64" + ] +} + +target "lint-gopls" { + inherits = ["lint"] + target = "gopls-analyze" +} + +target "validate-vendor" { + dockerfile = "./hack/dockerfiles/vendor.Dockerfile" + target = "validate" + output = ["type=cacheonly"] +} + +target "validate-docs" { + dockerfile = "./hack/dockerfiles/docs.Dockerfile" + target = "validate" + output = ["type=cacheonly"] +}