diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd30beda6..187a805c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,10 @@ jobs: docker buildx imagetools inspect localhost:5000/name/app:1.0.0 - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + run: echo "${{ steps.docker_build.outputs.digest }}" + - + name: Build result metadata + run: echo "${{ steps.docker_build.outputs.build-metadata }}" | jq - name: Check digest run: | @@ -135,7 +138,10 @@ jobs: docker buildx imagetools inspect localhost:5000/name/app:1.0.0 - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + run: echo "${{ steps.docker_build.outputs.digest }}" + - + name: Build result metadata + run: echo "${{ steps.docker_build.outputs.build-metadata }}" | jq - name: Check digest run: | @@ -193,7 +199,10 @@ jobs: docker buildx imagetools inspect localhost:5000/name/app:1.0.0 - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + run: echo "${{ steps.docker_build.outputs.digest }}" + - + name: Build result metadata + run: echo "${{ steps.docker_build.outputs.build-metadata }}" | jq - name: Check digest run: | @@ -394,7 +403,10 @@ jobs: docker buildx imagetools inspect localhost:5000/name/app:1.0.0 - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + run: echo "${{ steps.docker_build.outputs.digest }}" + - + name: Build result metadata + run: echo "${{ steps.docker_build.outputs.build-metadata }}" | jq - name: Check digest run: | @@ -449,7 +461,10 @@ jobs: docker buildx imagetools inspect localhost:5000/name/app:latest - name: Image digest (1) - run: echo ${{ steps.docker_build.outputs.digest }} + run: echo "${{ steps.docker_build.outputs.digest }}" + - + name: Build result metadata (1) + run: echo "${{ steps.docker_build.outputs.build-metadata }}" | jq - name: Check digest (1) run: | @@ -482,7 +497,10 @@ jobs: docker buildx imagetools inspect localhost:5000/name/app:latest - name: Image digest (2) - run: echo ${{ steps.docker_build2.outputs.digest }} + run: echo "${{ steps.docker_build2.outputs.digest }}" + - + name: Build result metadata (2) + run: echo "${{ steps.docker_build2.outputs.build-metadata }}" | jq - name: Check digest (2) run: | @@ -559,7 +577,10 @@ jobs: docker buildx imagetools inspect localhost:5000/name/app:1.0.0 - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + run: echo "${{ steps.docker_build.outputs.digest }}" + - + name: Build result metadata + run: echo "${{ steps.docker_build.outputs.build-metadata }}" | jq - name: Check digest run: | @@ -624,7 +645,10 @@ jobs: docker buildx imagetools inspect localhost:5000/name/app:1.0.0 - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} + run: echo "${{ steps.docker_build.outputs.digest }}" + - + name: Build result metadata + run: echo "${{ steps.docker_build.outputs.build-metadata }}" | jq - name: Check digest run: | diff --git a/README.md b/README.md index 5bdb96e34..b095b5277 100644 --- a/README.md +++ b/README.md @@ -223,9 +223,10 @@ Following inputs can be used as `step.with` keys Following outputs are available -| Name | Type | Description | -|---------------|---------|---------------------------------------| -| `digest` | String | Image content-addressable identifier also called a digest | +| Name | Type | Description | +|-------------------|---------|---------------------------------------| +| `digest` | String | Image content-addressable identifier also called a digest | +| `build-metadata` | JSON | Build result metadata | ## Troubleshooting diff --git a/__tests__/buildx.test.ts b/__tests__/buildx.test.ts index 8e6565185..0f2261afb 100644 --- a/__tests__/buildx.test.ts +++ b/__tests__/buildx.test.ts @@ -8,6 +8,10 @@ import * as context from '../src/context'; const tmpNameSync = path.join('/tmp/.docker-build-push-jest', '.tmpname-jest').split(path.sep).join(path.posix.sep); const digest = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9'; +const buildMetadata = `{ + "containerimage.config.digest": "sha256:059b68a595b22564a1cbc167af369349fdc2ecc1f7bc092c2235cbf601a795fd", + "containerimage.digest": "sha256:b09b9482c72371486bb2c1d2c2a2633ed1d0b8389e12c8d52b9e052725c0c83c" +}`; jest.spyOn(context, 'tmpDir').mockImplementation((): string => { const tmpDir = path.join('/tmp/.docker-build-push-jest').split(path.sep).join(path.posix.sep); @@ -32,6 +36,17 @@ describe('getImageID', () => { }); }); +describe('getBuildMetadata', () => { + it('matches', async () => { + const metadataFile = await buildx.getMetadataFile(); + console.log(`metadataFile: ${metadataFile}`); + await fs.writeFileSync(metadataFile, buildMetadata); + const metadata = await buildx.getBuildMetadata(); + console.log(`metadata: ${metadata}`); + expect(metadata).toEqual(buildMetadata); + }); +}); + describe('isLocalOrTarExporter', () => { // prettier-ignore test.each([ diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts index 3fc2cfe38..3435701e1 100644 --- a/__tests__/context.test.ts +++ b/__tests__/context.test.ts @@ -425,7 +425,30 @@ ccc`], '--output', 'type=local,dest=./release-out', '.' ] - ] + ], + [ + '0.6.0', + new Map([ + ['context', '.'], + ['tag', 'localhost:5000/name/app:latest'], + ['file', './test/Dockerfile'], + ['network', 'host'], + ['load', 'false'], + ['no-cache', 'false'], + ['push', 'true'], + ['pull', 'false'] + ]), + [ + 'buildx', + 'build', + '--iidfile', '/tmp/.docker-build-push-jest/iidfile', + '--metadata-file', '/tmp/.docker-build-push-jest/metadata-file', + '--file', './test/Dockerfile', + '--network', 'host', + '--push', + '.' + ] + ], ])( 'given %p with %p as inputs, returns %p', async (buildxVersion: string, inputs: Map, expected: Array) => { diff --git a/action.yml b/action.yml index 49f08fb21..4e310b65f 100644 --- a/action.yml +++ b/action.yml @@ -79,6 +79,8 @@ inputs: outputs: digest: description: 'Image content-addressable identifier also called a digest' + build-metadata: + description: 'Build result metadata' runs: using: 'node12' diff --git a/dist/index.js b/dist/index.js index ead03d6ab..66c1b465f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -38,7 +38,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.satisfies = exports.parseVersion = exports.getVersion = exports.isAvailable = exports.hasGitAuthToken = exports.isLocalOrTarExporter = exports.getSecret = exports.getSecretFile = exports.getSecretString = exports.getImageID = exports.getImageIDFile = void 0; +exports.satisfies = exports.parseVersion = exports.getVersion = exports.isAvailable = exports.hasGitAuthToken = exports.isLocalOrTarExporter = exports.getSecret = exports.getSecretFile = exports.getSecretString = exports.getBuildMetadata = exports.getMetadataFile = exports.getImageID = exports.getImageIDFile = void 0; const sync_1 = __importDefault(__nccwpck_require__(8750)); const fs_1 = __importDefault(__nccwpck_require__(5747)); const path_1 = __importDefault(__nccwpck_require__(5622)); @@ -61,6 +61,22 @@ function getImageID() { }); } exports.getImageID = getImageID; +function getMetadataFile() { + return __awaiter(this, void 0, void 0, function* () { + return path_1.default.join(context.tmpDir(), 'metadata-file').split(path_1.default.sep).join(path_1.default.posix.sep); + }); +} +exports.getMetadataFile = getMetadataFile; +function getBuildMetadata() { + return __awaiter(this, void 0, void 0, function* () { + const metadataFile = yield getMetadataFile(); + if (!fs_1.default.existsSync(metadataFile)) { + return undefined; + } + return fs_1.default.readFileSync(metadataFile, { encoding: 'utf-8' }); + }); +} +exports.getBuildMetadata = getBuildMetadata; function getSecretString(kvp) { return __awaiter(this, void 0, void 0, function* () { return getSecret(kvp, false); @@ -311,6 +327,9 @@ function getBuildArgs(inputs, defaultContext, buildxVersion) { if (!buildx.isLocalOrTarExporter(inputs.outputs) && (inputs.platforms.length == 0 || buildx.satisfies(buildxVersion, '>=0.4.2'))) { args.push('--iidfile', yield buildx.getImageIDFile()); } + if (buildx.satisfies(buildxVersion, '>=0.6.0')) { + args.push('--metadata-file', yield buildx.getMetadataFile()); + } yield exports.asyncForEach(inputs.cacheFrom, (cacheFrom) => __awaiter(this, void 0, void 0, function* () { args.push('--cache-from', cacheFrom); })); @@ -476,13 +495,18 @@ function run() { throw new Error(`buildx failed with: ${res.stderr.match(/(.*)\s*$/)[0].trim()}`); } }); - const imageID = yield buildx.getImageID(); - if (imageID) { - core.startGroup(`Extracting digest`); - core.info(`${imageID}`); - context.setOutput('digest', imageID); - core.endGroup(); - } + yield core.group(`Setting outputs`, () => __awaiter(this, void 0, void 0, function* () { + const imageID = yield buildx.getImageID(); + const buildMetadata = yield buildx.getBuildMetadata(); + if (imageID) { + core.info(`digest=${imageID}`); + context.setOutput('digest', imageID); + } + if (buildMetadata) { + core.info(`build-metadata=${buildMetadata}`); + context.setOutput('build-metadata', buildMetadata); + } + })); } catch (error) { core.setFailed(error.message); diff --git a/src/buildx.ts b/src/buildx.ts index 116fd2afa..f2b8282dd 100644 --- a/src/buildx.ts +++ b/src/buildx.ts @@ -18,6 +18,18 @@ export async function getImageID(): Promise { return fs.readFileSync(iidFile, {encoding: 'utf-8'}); } +export async function getMetadataFile(): Promise { + return path.join(context.tmpDir(), 'metadata-file').split(path.sep).join(path.posix.sep); +} + +export async function getBuildMetadata(): Promise { + const metadataFile = await getMetadataFile(); + if (!fs.existsSync(metadataFile)) { + return undefined; + } + return fs.readFileSync(metadataFile, {encoding: 'utf-8'}); +} + export async function getSecretString(kvp: string): Promise { return getSecret(kvp, false); } diff --git a/src/context.ts b/src/context.ts index 552b0fe6e..b951a9be8 100644 --- a/src/context.ts +++ b/src/context.ts @@ -2,7 +2,6 @@ import csvparse from 'csv-parse/lib/sync'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import * as semver from 'semver'; import * as tmp from 'tmp'; import * as core from '@actions/core'; @@ -122,6 +121,9 @@ async function getBuildArgs(inputs: Inputs, defaultContext: string, buildxVersio if (!buildx.isLocalOrTarExporter(inputs.outputs) && (inputs.platforms.length == 0 || buildx.satisfies(buildxVersion, '>=0.4.2'))) { args.push('--iidfile', await buildx.getImageIDFile()); } + if (buildx.satisfies(buildxVersion, '>=0.6.0')) { + args.push('--metadata-file', await buildx.getMetadataFile()); + } await asyncForEach(inputs.cacheFrom, async cacheFrom => { args.push('--cache-from', cacheFrom); }); diff --git a/src/main.ts b/src/main.ts index 4b6af66f3..85c124e0a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -33,13 +33,18 @@ async function run(): Promise { } }); - const imageID = await buildx.getImageID(); - if (imageID) { - core.startGroup(`Extracting digest`); - core.info(`${imageID}`); - context.setOutput('digest', imageID); - core.endGroup(); - } + await core.group(`Setting outputs`, async () => { + const imageID = await buildx.getImageID(); + const buildMetadata = await buildx.getBuildMetadata(); + if (imageID) { + core.info(`digest=${imageID}`); + context.setOutput('digest', imageID); + } + if (buildMetadata) { + core.info(`build-metadata=${buildMetadata}`); + context.setOutput('build-metadata', buildMetadata); + } + }); } catch (error) { core.setFailed(error.message); }