diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0e6546..1b846b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -165,6 +165,30 @@ jobs: org.opencontainers.image.description=Another description org.opencontainers.image.vendor=MyCompany + json: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Docker meta + id: meta + uses: ./ + with: + images: | + ${{ env.DOCKER_IMAGE }} + ghcr.io/name/app + labels: | + maintainer=CrazyMax + - + name: JSON output + run: | + echo "maintainer=${{ fromJSON(steps.meta.outputs.json).labels['maintainer'] }}" + echo "version=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}" + echo "revision=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}" + echo "created=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}" + docker-push: runs-on: ubuntu-latest services: diff --git a/README.md b/README.md index 9b96426..e11f8d3 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ ___ * [Latest tag](#latest-tag) * [Global expressions](#global-expressions) * [Major version zero](#major-version-zero) + * [JSON output object](#json-output-object) * [Overwrite labels](#overwrite-labels) * [Keep up-to-date with GitHub Dependabot](#keep-up-to-date-with-github-dependabot) @@ -274,6 +275,7 @@ Following outputs are available | `version` | String | Docker image version | | `tags` | String | Docker tags | | `labels` | String | Docker labels | +| `json` | String | JSON output of tags and labels | | `bake-file` | File | [Bake definition file](https://github.com/docker/buildx#file-definition) path | ## `flavor` input @@ -586,6 +588,30 @@ tags: | type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }} ``` +### JSON output object + +The `json` output is a JSON object composed of the generated tags and labels so that you can reuse them further in your +workflow using the [`fromJSON` function](https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#fromjson): + +```yaml + - + name: Docker meta + uses: docker/metadata-action@v3 + id: meta + with: + images: name/app + - + name: Build and push + uses: docker/build-push-action@v2 + with: + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + BUILDTIME=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} + VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} + REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} +``` + ### Overwrite labels If some of the [OCI Image Format Specification](https://github.com/opencontainers/image-spec/blob/master/annotations.md) @@ -594,7 +620,7 @@ labels generated are not suitable, you can overwrite them like this: ```yaml - name: Docker meta - id: docker_meta + id: meta uses: docker/metadata-action@v3 with: images: name/app diff --git a/__tests__/meta.test.ts b/__tests__/meta.test.ts index 7017459..fb4bc1d 100644 --- a/__tests__/meta.test.ts +++ b/__tests__/meta.test.ts @@ -2394,6 +2394,258 @@ describe('raw', () => { ])('given %p wth %p event', tagsLabelsTest); }); +describe('json', () => { + // prettier-ignore + test.each([ + [ + 'json01', + 'event_push.env', + { + images: ['user/app'], + tags: [ + `type=ref,event=branch`, + `type=raw,my`, + `type=raw,custom`, + `type=raw,tags` + ], + labels: [ + "invalid" + ] + } as Inputs, + { + "tags": [ + "user/app:dev", + "user/app:my", + "user/app:custom", + "user/app:tags" + ], + "labels": { + "org.opencontainers.image.title": "Hello-World", + "org.opencontainers.image.description": "This your first repo!", + "org.opencontainers.image.url": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.source": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.version": "dev", + "org.opencontainers.image.created": "2020-01-10T00:30:00.000Z", + "org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071", + "org.opencontainers.image.licenses": "MIT" + } + } + ], + [ + 'json02', + 'event_push.env', + { + images: ['user/app'], + tags: [ + `type=ref,event=branch`, + `type=raw,my` + ] + } as Inputs, + { + "tags": [ + "user/app:dev", + "user/app:my", + ], + "labels": { + "org.opencontainers.image.title": "Hello-World", + "org.opencontainers.image.description": "This your first repo!", + "org.opencontainers.image.url": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.source": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.version": "dev", + "org.opencontainers.image.created": "2020-01-10T00:30:00.000Z", + "org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071", + "org.opencontainers.image.licenses": "MIT" + } + } + ], + [ + 'json03', + 'event_tag_release1.env', + { + images: ['user/app'], + tags: [ + `type=ref,event=tag`, + `type=raw,my`, + `type=raw,custom`, + `type=raw,tags` + ], + bakeTarget: "meta" + } as Inputs, + { + "tags": [ + "user/app:release1", + "user/app:my", + "user/app:custom", + "user/app:tags", + "user/app:latest" + ], + "labels": { + "org.opencontainers.image.title": "Hello-World", + "org.opencontainers.image.description": "This your first repo!", + "org.opencontainers.image.url": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.source": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.version": "release1", + "org.opencontainers.image.created": "2020-01-10T00:30:00.000Z", + "org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071", + "org.opencontainers.image.licenses": "MIT" + } + } + ], + [ + 'json04', + 'event_tag_20200110-RC2.env', + { + images: ['user/app'], + tags: [ + `type=match,pattern=\\d{8}`, + `type=raw,my`, + `type=raw,custom`, + `type=raw,tags` + ], + flavor: [ + `latest=false` + ] + } as Inputs, + { + "tags": [ + "user/app:20200110", + "user/app:my", + "user/app:custom", + "user/app:tags" + ], + "labels": { + "org.opencontainers.image.title": "Hello-World", + "org.opencontainers.image.description": "This your first repo!", + "org.opencontainers.image.url": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.source": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.version": "20200110", + "org.opencontainers.image.created": "2020-01-10T00:30:00.000Z", + "org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071", + "org.opencontainers.image.licenses": "MIT" + } + } + ], + [ + 'json05', + 'event_tag_v1.1.1.env', + { + images: ['org/app', 'ghcr.io/user/app'], + tags: [ + `type=semver,pattern={{version}}`, + `type=semver,pattern={{major}}.{{minor}}`, + `type=semver,pattern={{major}}`, + `type=raw,my`, + `type=raw,custom`, + `type=raw,tags` + ] + } as Inputs, + { + "tags": [ + "org/app:1.1.1", + "org/app:1.1", + "org/app:1", + "org/app:my", + "org/app:custom", + "org/app:tags", + "org/app:latest", + "ghcr.io/user/app:1.1.1", + "ghcr.io/user/app:1.1", + "ghcr.io/user/app:1", + "ghcr.io/user/app:my", + "ghcr.io/user/app:custom", + "ghcr.io/user/app:tags", + "ghcr.io/user/app:latest" + ], + "labels": { + "org.opencontainers.image.title": "Hello-World", + "org.opencontainers.image.description": "This your first repo!", + "org.opencontainers.image.url": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.source": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.version": "1.1.1", + "org.opencontainers.image.created": "2020-01-10T00:30:00.000Z", + "org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071", + "org.opencontainers.image.licenses": "MIT" + } + } + ], + [ + 'json06', + 'event_tag_v1.1.1.env', + { + images: ['org/app', 'ghcr.io/user/app'], + tags: [ + `type=raw,my`, + `type=raw,custom`, + `type=raw,tags` + ] + } as Inputs, + { + "tags": [ + "org/app:my", + "org/app:custom", + "org/app:tags", + "ghcr.io/user/app:my", + "ghcr.io/user/app:custom", + "ghcr.io/user/app:tags" + ], + "labels": { + "org.opencontainers.image.title": "Hello-World", + "org.opencontainers.image.description": "This your first repo!", + "org.opencontainers.image.url": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.source": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.version": "my", + "org.opencontainers.image.created": "2020-01-10T00:30:00.000Z", + "org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071", + "org.opencontainers.image.licenses": "MIT" + } + } + ], + [ + 'json07', + 'event_tag_v1.1.1.env', + { + images: ['org/app'], + labels: [ + "foo", + "maintainer=CrazyMax", + "org.opencontainers.image.title=MyCustom=Title", + "org.opencontainers.image.description=Another description", + "org.opencontainers.image.vendor=MyCompany", + ], + } as Inputs, + { + "tags": [ + "org/app:v1.1.1", + "org/app:latest" + ], + "labels": { + "maintainer": "CrazyMax", + "org.opencontainers.image.title": "MyCustom=Title", + "org.opencontainers.image.description": "Another description", + "org.opencontainers.image.url": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.source": "https://github.com/octocat/Hello-World", + "org.opencontainers.image.vendor": "MyCompany", + "org.opencontainers.image.version": "v1.1.1", + "org.opencontainers.image.created": "2020-01-10T00:30:00.000Z", + "org.opencontainers.image.revision": "90dd6032fac8bda1b6c4436a2e65de27961ed071", + "org.opencontainers.image.licenses": "MIT" + } + } + ] + ])('given %p with %p event', async (name: string, envFile: string, inputs: Inputs, exJSON: {}) => { + process.env = dotenv.parse(fs.readFileSync(path.join(__dirname, 'fixtures', envFile))); + const context = github.context(); + console.log(process.env, context); + + const repo = await github.repo(process.env.GITHUB_TOKEN || ''); + const meta = new Meta({...getInputs(), ...inputs}, context, repo); + + const jsonOutput = meta.getJSON(); + console.log('json', jsonOutput); + expect(jsonOutput).toEqual(exJSON); + }); +}); + describe('bake', () => { // prettier-ignore test.each([ diff --git a/dist/index.js b/dist/index.js index 88f3d4e..3887e51 100644 --- a/dist/index.js +++ b/dist/index.js @@ -321,6 +321,12 @@ function run() { } core.endGroup(); context_1.setOutput('labels', labels.join(inputs.sepLabels)); + // JSON + const jsonOutput = meta.getJSON(); + core.startGroup(`JSON output`); + core.info(JSON.stringify(jsonOutput, null, 2)); + core.endGroup(); + context_1.setOutput('json', jsonOutput); // Bake definition file const bakeFile = meta.getBakeFile(); core.startGroup(`Bake definition file`); @@ -644,21 +650,33 @@ class Meta { labels.push(...this.inputs.labels); return labels; } + getJSON() { + return { + tags: this.getTags(), + labels: this.getLabels().reduce((res, label) => { + const matches = label.match(/([^=]*)=(.*)/); + if (!matches) { + return res; + } + res[matches[1]] = matches[2]; + return res; + }, {}) + }; + } getBakeFile() { - let jsonLabels = {}; - for (let label of this.getLabels()) { - const matches = label.match(/([^=]*)=(.*)/); - if (!matches) { - continue; - } - jsonLabels[matches[1]] = matches[2]; - } const bakeFile = path.join(context_1.tmpDir(), 'docker-metadata-action-bake.json').split(path.sep).join(path.posix.sep); fs.writeFileSync(bakeFile, JSON.stringify({ target: { [this.inputs.bakeTarget]: { tags: this.getTags(), - labels: jsonLabels, + labels: this.getLabels().reduce((res, label) => { + const matches = label.match(/([^=]*)=(.*)/); + if (!matches) { + return res; + } + res[matches[1]] = matches[2]; + return res; + }, {}), args: { DOCKER_META_IMAGES: this.inputs.images.join(','), DOCKER_META_VERSION: this.version.main diff --git a/src/main.ts b/src/main.ts index a41ea4c..32b671e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -60,6 +60,13 @@ async function run() { core.endGroup(); setOutput('labels', labels.join(inputs.sepLabels)); + // JSON + const jsonOutput = meta.getJSON(); + core.startGroup(`JSON output`); + core.info(JSON.stringify(jsonOutput, null, 2)); + core.endGroup(); + setOutput('json', jsonOutput); + // Bake definition file const bakeFile: string = meta.getBakeFile(); core.startGroup(`Bake definition file`); diff --git a/src/meta.ts b/src/meta.ts index 972f3d9..b608f6e 100644 --- a/src/meta.ts +++ b/src/meta.ts @@ -318,16 +318,21 @@ export class Meta { return labels; } - public getBakeFile(): string { - let jsonLabels = {}; - for (let label of this.getLabels()) { - const matches = label.match(/([^=]*)=(.*)/); - if (!matches) { - continue; - } - jsonLabels[matches[1]] = matches[2]; - } + public getJSON(): {} { + return { + tags: this.getTags(), + labels: this.getLabels().reduce((res, label) => { + const matches = label.match(/([^=]*)=(.*)/); + if (!matches) { + return res; + } + res[matches[1]] = matches[2]; + return res; + }, {}) + }; + } + public getBakeFile(): string { const bakeFile = path.join(tmpDir(), 'docker-metadata-action-bake.json').split(path.sep).join(path.posix.sep); fs.writeFileSync( bakeFile, @@ -336,7 +341,14 @@ export class Meta { target: { [this.inputs.bakeTarget]: { tags: this.getTags(), - labels: jsonLabels, + labels: this.getLabels().reduce((res, label) => { + const matches = label.match(/([^=]*)=(.*)/); + if (!matches) { + return res; + } + res[matches[1]] = matches[2]; + return res; + }, {}), args: { DOCKER_META_IMAGES: this.inputs.images.join(','), DOCKER_META_VERSION: this.version.main