From 38c1f188caf104ce9b6fe1e46eabc1044f698b33 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Mon, 17 Aug 2020 22:18:15 +0200 Subject: [PATCH] Add digest output Fix platforms and allow inputs Signed-off-by: CrazyMax --- .github/workflows/ci.yml | 49 +++++++++++++++++++++++- README.md | 52 +++++++++++++++++++++++++- __tests__/buildx.test.ts | 15 ++++++++ action.yml | 16 ++++---- dist/index.js | 80 +++++++++++++++++++++++++++++----------- src/buildx.ts | 15 ++++++++ src/context.ts | 78 +++++++++++++++++++++------------------ src/main.ts | 7 ++++ 8 files changed, 244 insertions(+), 68 deletions(-) create mode 100644 __tests__/buildx.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a031acb..411ae0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,54 @@ on: - v2-working-branch # remove when merged to master jobs: - main: + single: + runs-on: ubuntu-latest + steps: + - + name: Run local registry + run: | + docker run -d -p 5000:5000 registry:2 + - + name: Checkout + uses: actions/checkout@v2.3.1 + - + name: Set up QEMU + uses: ./setup-qemu/ # change to docker/setup-qemu-action@master + with: + platforms: all + - + name: Set up Docker Buildx + id: buildx + uses: ./setup-buildx/ # change to docker/setup-buildx-action@master + with: + driver-opt: network=host + buildkitd-flags: --allow-insecure-entitlement security.insecure + - + name: Build and push + id: docker_build + uses: ./ + with: + context: ./test + file: ./test/Dockerfile + builder: ${{ steps.buildx.outputs.name }} + allow: network.host,security.insecure + push: true + tags: | + localhost:5000/name/app:latest + localhost:5000/name/app:1.0.0 + - + name: Inspect + run: | + docker buildx imagetools inspect localhost:5000/name/app:1.0.0 + - + name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} + - + name: Dump context + if: always() + uses: crazy-max/ghaction-dump-context@v1 + + multi: runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/README.md b/README.md index 808f5fb..c522d0b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,56 @@ on: tags: jobs: - buildx: + main: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v1 + with: + platforms: all + - + name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v1 + - + name: Login to DockerHub + uses: crazy-max/ghaction-docker-login@v1 # switch to docker/login-action@v1 when available + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - + name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + with: + builder: ${{ steps.buildx.outputs.name }} + push: true + tags: | + user/app:latest + user/app:1.0.0 + - + name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} +``` + +### Multi-platform image + +```yaml +name: ci + +on: + pull_request: + branches: master + push: + branches: master + tags: + +jobs: + multi: runs-on: ubuntu-latest steps: - @@ -58,7 +107,6 @@ jobs: uses: docker/build-push-action@v2 with: builder: ${{ steps.buildx.outputs.name }} - platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/386,linux/ppc64le,linux/s390x push: true tags: | user/app:latest diff --git a/__tests__/buildx.test.ts b/__tests__/buildx.test.ts new file mode 100644 index 0000000..7e05f5e --- /dev/null +++ b/__tests__/buildx.test.ts @@ -0,0 +1,15 @@ +import fs from 'fs'; +import * as buildx from '../src/buildx'; + +const digest = 'sha256:bfb45ab72e46908183546477a08f8867fc40cebadd00af54b071b097aed127a9'; + +describe('getImageID', () => { + it('matches', async () => { + const imageIDFile = await buildx.getImageIDFile(); + console.log(`imageIDFile: ${imageIDFile}`); + await fs.writeFileSync(imageIDFile, digest); + const imageID = await buildx.getImageID(); + console.log(`imageID: ${imageID}`); + expect(imageID).toEqual(digest); + }); +}); diff --git a/action.yml b/action.yml index 8bd6603..42adf66 100644 --- a/action.yml +++ b/action.yml @@ -19,13 +19,13 @@ inputs: required: false default: './Dockerfile' build-args: - description: "Newline-delimited list of build-time variables" + description: "List of build-time variables" required: false labels: - description: "Newline-delimited list of metadata for an image" + description: "List of metadata for an image" required: false tags: - description: "Newline-delimited list of tags" + description: "List of tags" required: false pull: description: "Always attempt to pull a newer version of the image" @@ -35,14 +35,14 @@ inputs: description: "Sets the target stage to build" required: false allow: - description: "Allow extra privileged entitlement (eg. network.host,security.insecure)" + description: "List of extra privileged entitlement (eg. network.host,security.insecure)" required: false no-cache: description: "Do not use cache when building the image" required: false default: 'false' platforms: - description: "Comma-delimited list of target platforms for build" + description: "List of target platforms for build" required: false load: description: "Load is a shorthand for --output=type=docker" @@ -53,13 +53,13 @@ inputs: required: false default: 'false' outputs: - description: "Newline-delimited list of output destinations (format: type=local,dest=path)" + description: "List of output destinations (format: type=local,dest=path)" required: false cache-from: - description: "Newline-delimited list of external cache sources for buildx (eg. user/app:cache, type=local,src=path/to/dir)" + description: "List of external cache sources for buildx (eg. user/app:cache, type=local,src=path/to/dir)" required: false cache-to: - description: "Newline-delimited list of cache export destinations for buildx (eg. user/app:cache, type=local,dest=path/to/dir)" + description: "List of cache export destinations for buildx (eg. user/app:cache, type=local,dest=path/to/dir)" required: false outputs: diff --git a/dist/index.js b/dist/index.js index 44b2147..cb6eabe 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1025,6 +1025,12 @@ function run() { core.info(`🏃 Starting build...`); const args = yield context_1.getArgs(inputs); yield exec.exec('docker', args); + const imageID = yield buildx.getImageID(); + if (imageID) { + core.info('🛒 Extracting digest...'); + core.info(`${imageID}`); + core.setOutput('digest', imageID); + } } catch (error) { core.setFailed(error.message); @@ -1405,8 +1411,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.asyncForEach = exports.getInputList = exports.getArgs = exports.getInputs = void 0; +exports.asyncForEach = exports.getInputList = exports.getArgs = exports.getInputs = exports.tmpDir = void 0; +const fs = __importStar(__webpack_require__(747)); +const os = __importStar(__webpack_require__(87)); +const path = __importStar(__webpack_require__(622)); +const buildx = __importStar(__webpack_require__(982)); const core = __importStar(__webpack_require__(470)); +exports.tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-build-push-')); function getInputs() { return __awaiter(this, void 0, void 0, function* () { return { @@ -1440,24 +1451,6 @@ function getArgs(inputs) { }); } exports.getArgs = getArgs; -function getCommonArgs(inputs) { - return __awaiter(this, void 0, void 0, function* () { - let args = []; - if (inputs.noCache) { - args.push('--no-cache'); - } - if (inputs.pull) { - args.push('--pull'); - } - if (inputs.load) { - args.push('--load'); - } - if (inputs.push) { - args.push('--push'); - } - return args; - }); -} function getBuildArgs(inputs) { return __awaiter(this, void 0, void 0, function* () { let args = ['build']; @@ -1473,12 +1466,15 @@ function getBuildArgs(inputs) { if (inputs.target) { args.push('--target', inputs.target); } - if (inputs.allow) { + if (inputs.allow.length > 0) { args.push('--allow', inputs.allow.join(',')); } - if (inputs.platforms) { + if (inputs.platforms.length > 0) { args.push('--platform', inputs.platforms.join(',')); } + else { + args.push('--iidfile', yield buildx.getImageIDFile()); + } yield exports.asyncForEach(inputs.outputs, (output) => __awaiter(this, void 0, void 0, function* () { args.push('--output', output); })); @@ -1494,6 +1490,24 @@ function getBuildArgs(inputs) { return args; }); } +function getCommonArgs(inputs) { + return __awaiter(this, void 0, void 0, function* () { + let args = []; + if (inputs.noCache) { + args.push('--no-cache'); + } + if (inputs.pull) { + args.push('--pull'); + } + if (inputs.load) { + args.push('--load'); + } + if (inputs.push) { + args.push('--push'); + } + return args; + }); +} function getInputList(name) { return __awaiter(this, void 0, void 0, function* () { const items = core.getInput(name); @@ -1838,9 +1852,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.use = exports.isAvailable = void 0; +exports.use = exports.isAvailable = exports.getImageID = exports.getImageIDFile = void 0; +const fs_1 = __importDefault(__webpack_require__(747)); +const path_1 = __importDefault(__webpack_require__(622)); +const context = __importStar(__webpack_require__(482)); const exec = __importStar(__webpack_require__(807)); +function getImageIDFile() { + return __awaiter(this, void 0, void 0, function* () { + return path_1.default.join(context.tmpDir, 'iidfile'); + }); +} +exports.getImageIDFile = getImageIDFile; +function getImageID() { + return __awaiter(this, void 0, void 0, function* () { + const iidFile = yield getImageIDFile(); + if (!fs_1.default.existsSync(iidFile)) { + return undefined; + } + return fs_1.default.readFileSync(iidFile, { encoding: 'utf-8' }); + }); +} +exports.getImageID = getImageID; function isAvailable() { return __awaiter(this, void 0, void 0, function* () { return yield exec.exec(`docker`, ['buildx'], true).then(res => { diff --git a/src/buildx.ts b/src/buildx.ts index 38f38ef..0574cd5 100644 --- a/src/buildx.ts +++ b/src/buildx.ts @@ -1,5 +1,20 @@ +import fs from 'fs'; +import path from 'path'; +import * as context from './context'; import * as exec from './exec'; +export async function getImageIDFile(): Promise { + return path.join(context.tmpDir, 'iidfile'); +} + +export async function getImageID(): Promise { + const iidFile = await getImageIDFile(); + if (!fs.existsSync(iidFile)) { + return undefined; + } + return fs.readFileSync(iidFile, {encoding: 'utf-8'}); +} + export async function isAvailable(): Promise { return await exec.exec(`docker`, ['buildx'], true).then(res => { if (res.stderr != '' && !res.success) { diff --git a/src/context.ts b/src/context.ts index 2d8a6d7..2c55af5 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,5 +1,11 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as buildx from './buildx'; import * as core from '@actions/core'; +export const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-build-push-')); + export interface Inputs { context: string; file: string; @@ -48,6 +54,43 @@ export async function getArgs(inputs: Inputs): Promise> { return args; } +async function getBuildArgs(inputs: Inputs): Promise> { + let args: Array = ['build']; + await asyncForEach(inputs.buildArgs, async buildArg => { + args.push('--build-arg', buildArg); + }); + await asyncForEach(inputs.labels, async label => { + args.push('--label', label); + }); + await asyncForEach(inputs.tags, async tag => { + args.push('--tag', tag); + }); + if (inputs.target) { + args.push('--target', inputs.target); + } + if (inputs.allow.length > 0) { + args.push('--allow', inputs.allow.join(',')); + } + if (inputs.platforms.length > 0) { + args.push('--platform', inputs.platforms.join(',')); + } else { + args.push('--iidfile', await buildx.getImageIDFile()); + } + await asyncForEach(inputs.outputs, async output => { + args.push('--output', output); + }); + await asyncForEach(inputs.cacheFrom, async cacheFrom => { + args.push('--cache-from', cacheFrom); + }); + await asyncForEach(inputs.cacheTo, async cacheTo => { + args.push('--cache-to', cacheTo); + }); + if (inputs.file) { + args.push('--file', inputs.file); + } + return args; +} + async function getCommonArgs(inputs: Inputs): Promise> { let args: Array = []; if (inputs.noCache) { @@ -65,41 +108,6 @@ async function getCommonArgs(inputs: Inputs): Promise> { return args; } -async function getBuildArgs(inputs: Inputs): Promise> { - let args: Array = ['build']; - await asyncForEach(inputs.buildArgs, async buildArg => { - args.push('--build-arg', buildArg); - }); - await asyncForEach(inputs.labels, async label => { - args.push('--label', label); - }); - await asyncForEach(inputs.tags, async tag => { - args.push('--tag', tag); - }); - if (inputs.target) { - args.push('--target', inputs.target); - } - if (inputs.allow) { - args.push('--allow', inputs.allow.join(',')); - } - if (inputs.platforms) { - args.push('--platform', inputs.platforms.join(',')); - } - await asyncForEach(inputs.outputs, async output => { - args.push('--output', output); - }); - await asyncForEach(inputs.cacheFrom, async cacheFrom => { - args.push('--cache-from', cacheFrom); - }); - await asyncForEach(inputs.cacheTo, async cacheTo => { - args.push('--cache-to', cacheTo); - }); - if (inputs.file) { - args.push('--file', inputs.file); - } - return args; -} - export async function getInputList(name: string): Promise { const items = core.getInput(name); if (items == '') { diff --git a/src/main.ts b/src/main.ts index 67732e5..ee27c94 100644 --- a/src/main.ts +++ b/src/main.ts @@ -25,6 +25,13 @@ async function run(): Promise { core.info(`🏃 Starting build...`); const args: string[] = await getArgs(inputs); await exec.exec('docker', args); + + const imageID = await buildx.getImageID(); + if (imageID) { + core.info('🛒 Extracting digest...'); + core.info(`${imageID}`); + core.setOutput('digest', imageID); + } } catch (error) { core.setFailed(error.message); }