From 72750233ac0032cdc7770c4d4d71cf9b7dc8f755 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Fri, 23 Apr 2021 18:14:38 +0200 Subject: [PATCH] Enhance builder inspection Signed-off-by: CrazyMax --- .github/workflows/ci.yml | 42 ++++++++++++++-------- .github/workflows/test.yml | 3 ++ README.md | 22 +++++++----- __tests__/buildx.test.ts | 12 ++++--- __tests__/context.test.ts | 30 ++++++++++++++++ action.yml | 14 ++++++-- dist/index.js | 71 +++++++++++++++++++++++++++++++------- src/buildx.ts | 54 ++++++++++++++++++++++++++--- src/context.ts | 6 ++++ src/main.ts | 14 +++++--- 10 files changed, 215 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac756a2..9c75748 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,11 +35,13 @@ jobs: with: version: ${{ matrix.buildx-version }} - - name: Builder instance name - run: echo ${{ steps.buildx.outputs.name }} - - - name: Available platforms - run: echo ${{ steps.buildx.outputs.platforms }} + name: Inspect builder + run: | + echo "Name: ${{ steps.buildx.outputs.name }}" + echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" + echo "Status: ${{ steps.buildx.outputs.status }}" + echo "Flags: ${{ steps.buildx.outputs.flags }}" + echo "Platforms: ${{ steps.buildx.outputs.platforms }}" - name: Dump context uses: crazy-max/ghaction-dump-context@v1 @@ -55,15 +57,25 @@ jobs: id: buildx1 uses: ./ - - name: Builder 1 instance name - run: echo ${{ steps.buildx1.outputs.name }} + name: Inspect builder 1 + run: | + echo "Name: ${{ steps.buildx1.outputs.name }}" + echo "Endpoint: ${{ steps.buildx1.outputs.endpoint }}" + echo "Status: ${{ steps.buildx1.outputs.status }}" + echo "Flags: ${{ steps.buildx1.outputs.flags }}" + echo "Platforms: ${{ steps.buildx1.outputs.platforms }}" - name: Set up Docker Buildx 2 id: buildx2 uses: ./ - - name: Builder 2 instance name - run: echo ${{ steps.buildx2.outputs.name }} + name: Inspect builder 2 + run: | + echo "Name: ${{ steps.buildx2.outputs.name }}" + echo "Endpoint: ${{ steps.buildx2.outputs.endpoint }}" + echo "Status: ${{ steps.buildx2.outputs.status }}" + echo "Flags: ${{ steps.buildx2.outputs.flags }}" + echo "Platforms: ${{ steps.buildx2.outputs.platforms }}" install: runs-on: ubuntu-latest @@ -204,8 +216,10 @@ jobs: with: version: ${{ matrix.buildx-version }} - - name: Available platforms - run: echo ${{ steps.buildx.outputs.platforms }} - - - name: Builder instance name - run: echo ${{ steps.buildx.outputs.name }} + name: Inspect builder + run: | + echo "Name: ${{ steps.buildx.outputs.name }}" + echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" + echo "Status: ${{ steps.buildx.outputs.status }}" + echo "Flags: ${{ steps.buildx.outputs.flags }}" + echo "Platforms: ${{ steps.buildx.outputs.platforms }}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 93790e9..2bbdbda 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,6 +22,9 @@ jobs: uses: docker/bake-action@v1 with: targets: validate + - + name: Set up Docker Buildx + uses: ./ - name: Test uses: docker/bake-action@v1 diff --git a/README.md b/README.md index 85f8500..2b611a7 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,13 @@ jobs: id: buildx uses: docker/setup-buildx-action@v1 - - name: Builder instance name - run: echo ${{ steps.buildx.outputs.name }} - - - name: Available platforms - run: echo ${{ steps.buildx.outputs.platforms }} + name: Inspect builder + run: | + echo "Name: ${{ steps.buildx.outputs.name }}" + echo "Endpoint: ${{ steps.buildx.outputs.endpoint }}" + echo "Status: ${{ steps.buildx.outputs.status }}" + echo "Flags: ${{ steps.buildx.outputs.flags }}" + echo "Platforms: ${{ steps.buildx.outputs.platforms }}" ``` ### With QEMU @@ -129,7 +131,7 @@ Following inputs can be used as `step.with` keys | `install` | Bool | Sets up `docker build` command as an alias to `docker buildx` (default `false`) | | `use` | Bool | Switch to this builder instance (default `true`) | | `endpoint` | String | [Optional address for docker socket](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#description) or context from `docker context ls` | -| `config` | String | [Optional config file path](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#config) | +| `config` | String | [BuildKit config file](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#config) | > `CSV` type must be a newline-delimited string > ```yaml @@ -147,8 +149,12 @@ Following outputs are available | Name | Type | Description | |---------------|---------|---------------------------------------| -| `name` | String | Builder instance name | -| `platforms` | String | Available platforms (comma separated) | +| `name` | String | Builder name | +| `driver` | String | Builder driver | +| `endpoint` | String | Builder node endpoint | +| `status` | String | Builder node status | +| `flags` | String | Builder node flags (if applicable) | +| `platforms` | String | Builder node platforms available (comma separated) | ### environment variables diff --git a/__tests__/buildx.test.ts b/__tests__/buildx.test.ts index d95ae6c..de68108 100644 --- a/__tests__/buildx.test.ts +++ b/__tests__/buildx.test.ts @@ -25,17 +25,19 @@ describe('parseVersion', () => { }); }); -describe('platforms', () => { +describe('inspect', () => { async function isDaemonRunning() { return await docker.isDaemonRunning(); } (isDaemonRunning() ? it : it.skip)( 'valid', async () => { - const platforms = buildx.platforms(); - console.log(`platforms: ${platforms}`); - expect(platforms).not.toBeUndefined(); - expect(platforms).not.toEqual(''); + const builder = await buildx.inspect(''); + console.log('builder', builder); + expect(builder).not.toBeUndefined(); + expect(builder.name).not.toEqual(''); + expect(builder.driver).not.toEqual(''); + expect(builder.node_platforms).not.toEqual(''); }, 100000 ); diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts index 5b2349a..7d884c6 100644 --- a/__tests__/context.test.ts +++ b/__tests__/context.test.ts @@ -1,3 +1,4 @@ +import * as os from 'os'; import * as context from '../src/context'; describe('getInputList', () => { @@ -78,6 +79,27 @@ describe('asyncForEach', () => { }); }); +describe('setOutput', () => { + beforeEach(() => { + process.stdout.write = jest.fn(); + }); + + it('setOutput produces the correct command', () => { + context.setOutput('some output', 'some value'); + assertWriteCalls([`::set-output name=some output::some value${os.EOL}`]); + }); + + it('setOutput handles bools', () => { + context.setOutput('some output', false); + assertWriteCalls([`::set-output name=some output::false${os.EOL}`]); + }); + + it('setOutput handles numbers', () => { + context.setOutput('some output', 1.01); + assertWriteCalls([`::set-output name=some output::1.01${os.EOL}`]); + }); +}); + // See: https://github.com/actions/toolkit/blob/master/packages/core/src/core.ts#L67 function getInputName(name: string): string { return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`; @@ -86,3 +108,11 @@ function getInputName(name: string): string { function setInput(name: string, value: string): void { process.env[getInputName(name)] = value; } + +// Assert that process.stdout.write calls called only with the given arguments. +function assertWriteCalls(calls: string[]): void { + expect(process.stdout.write).toHaveBeenCalledTimes(calls.length); + for (let i = 0; i < calls.length; i++) { + expect(process.stdout.write).toHaveBeenNthCalledWith(i + 1, calls[i]); + } +} diff --git a/action.yml b/action.yml index e218e16..233619a 100644 --- a/action.yml +++ b/action.yml @@ -33,14 +33,22 @@ inputs: description: 'Optional address for docker socket or context from `docker context ls`' required: false config: - description: 'Optional config file path' + description: 'BuildKit config file' required: false outputs: name: - description: 'Builder instance name' + description: 'Builder name' + driver: + description: 'Builder driver' + endpoint: + description: 'Builder node endpoint' + status: + description: 'Builder node status' + flags: + description: 'Builder node flags (if applicable)' platforms: - description: 'Available platforms (comma separated)' + description: 'Builder node platforms available (comma separated)' runs: using: 'node12' diff --git a/dist/index.js b/dist/index.js index 987b31e..93457d4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -535,7 +535,7 @@ function run() { const buildxVersion = yield buildx.getVersion(); core.info(`Using buildx ${buildxVersion}`); const builderName = inputs.driver == 'docker' ? 'default' : `builder-${__webpack_require__(840).v4()}`; - core.setOutput('name', builderName); + context.setOutput('name', builderName); stateHelper.setBuilderName(builderName); if (inputs.driver !== 'docker') { core.startGroup(`Creating a new builder instance`); @@ -572,10 +572,14 @@ function run() { yield exec.exec('docker', ['buildx', 'install']); core.endGroup(); } - core.startGroup(`Extracting available platforms`); - const platforms = yield buildx.platforms(); - core.info(`${platforms}`); - core.setOutput('platforms', platforms); + core.startGroup(`Inspect builder`); + const builder = yield buildx.inspect(builderName); + core.info(JSON.stringify(builder, undefined, 2)); + context.setOutput('driver', builder.driver); + context.setOutput('endpoint', builder.node_endpoint); + context.setOutput('status', builder.node_status); + context.setOutput('flags', builder.node_flags); + context.setOutput('platforms', builder.node_platforms); core.endGroup(); } catch (error) { @@ -2126,7 +2130,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.install = exports.platforms = exports.isAvailable = exports.parseVersion = exports.getVersion = void 0; +exports.install = exports.inspect = exports.isAvailable = exports.parseVersion = exports.getVersion = void 0; const fs = __importStar(__webpack_require__(747)); const path = __importStar(__webpack_require__(622)); const semver = __importStar(__webpack_require__(383)); @@ -2168,21 +2172,56 @@ function isAvailable() { }); } exports.isAvailable = isAvailable; -function platforms() { +function inspect(name) { return __awaiter(this, void 0, void 0, function* () { - return yield exec.exec(`docker`, ['buildx', 'inspect'], true).then(res => { + return yield exec.exec(`docker`, ['buildx', 'inspect', name], true).then(res => { if (res.stderr != '' && !res.success) { throw new Error(res.stderr); } - for (const line of res.stdout.trim().split(`\n`)) { - if (line.startsWith('Platforms')) { - return line.replace('Platforms: ', '').replace(/\s/g, '').trim(); + const builder = {}; + itlines: for (const line of res.stdout.trim().split(`\n`)) { + const [key, ...rest] = line.split(':'); + const value = rest.map(v => v.trim()).join(':'); + if (key.length == 0 || value.length == 0) { + continue; + } + switch (key) { + case 'Name': { + if (builder.name == undefined) { + builder.name = value; + } + else { + builder.node_name = value; + } + break; + } + case 'Driver': { + builder.driver = value; + break; + } + case 'Endpoint': { + builder.node_endpoint = value; + break; + } + case 'Status': { + builder.node_status = value; + break; + } + case 'Flags': { + builder.node_flags = value; + break; + } + case 'Platforms': { + builder.node_platforms = value.replace(/\s/g, ''); + break itlines; + } } } + return builder; }); }); } -exports.platforms = platforms; +exports.inspect = inspect; function install(inputVersion, dockerConfigHome) { return __awaiter(this, void 0, void 0, function* () { const release = yield github.getRelease(inputVersion); @@ -8011,9 +8050,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.asyncForEach = exports.getInputList = exports.getInputs = exports.osArch = exports.osPlat = void 0; +exports.setOutput = exports.asyncForEach = exports.getInputList = exports.getInputs = exports.osArch = exports.osPlat = void 0; const os = __importStar(__webpack_require__(87)); const core = __importStar(__webpack_require__(186)); +const command_1 = __webpack_require__(351); exports.osPlat = os.platform(); exports.osArch = os.arch(); function getInputs() { @@ -8050,6 +8090,11 @@ exports.asyncForEach = (array, callback) => __awaiter(void 0, void 0, void 0, fu yield callback(array[index], index, array); } }); +// FIXME: Temp fix https://github.com/actions/toolkit/issues/777 +function setOutput(name, value) { + command_1.issueCommand('set-output', { name }, value); +} +exports.setOutput = setOutput; //# sourceMappingURL=context.js.map /***/ }), diff --git a/src/buildx.ts b/src/buildx.ts index e1ee9f3..f9f4a79 100644 --- a/src/buildx.ts +++ b/src/buildx.ts @@ -8,6 +8,16 @@ import * as github from './github'; import * as core from '@actions/core'; import * as tc from '@actions/tool-cache'; +export type Builder = { + name?: string; + driver?: string; + node_name?: string; + node_endpoint?: string; + node_status?: string; + node_flags?: string; + node_platforms?: string; +}; + export async function getVersion(): Promise { return await exec.exec(`docker`, ['buildx', 'version'], true).then(res => { if (res.stderr != '' && !res.success) { @@ -34,16 +44,50 @@ export async function isAvailable(): Promise { }); } -export async function platforms(): Promise { - return await exec.exec(`docker`, ['buildx', 'inspect'], true).then(res => { +export async function inspect(name: string): Promise { + return await exec.exec(`docker`, ['buildx', 'inspect', name], true).then(res => { if (res.stderr != '' && !res.success) { throw new Error(res.stderr); } - for (const line of res.stdout.trim().split(`\n`)) { - if (line.startsWith('Platforms')) { - return line.replace('Platforms: ', '').replace(/\s/g, '').trim(); + const builder: Builder = {}; + itlines: for (const line of res.stdout.trim().split(`\n`)) { + const [key, ...rest] = line.split(':'); + const value = rest.map(v => v.trim()).join(':'); + if (key.length == 0 || value.length == 0) { + continue; + } + switch (key) { + case 'Name': { + if (builder.name == undefined) { + builder.name = value; + } else { + builder.node_name = value; + } + break; + } + case 'Driver': { + builder.driver = value; + break; + } + case 'Endpoint': { + builder.node_endpoint = value; + break; + } + case 'Status': { + builder.node_status = value; + break; + } + case 'Flags': { + builder.node_flags = value; + break; + } + case 'Platforms': { + builder.node_platforms = value.replace(/\s/g, ''); + break itlines; + } } } + return builder; }); } diff --git a/src/context.ts b/src/context.ts index daa8fe1..51f4f48 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,5 +1,6 @@ import * as os from 'os'; import * as core from '@actions/core'; +import {issueCommand} from '@actions/core/lib/command'; export const osPlat: string = os.platform(); export const osArch: string = os.arch(); @@ -49,3 +50,8 @@ export const asyncForEach = async (array, callback) => { await callback(array[index], index, array); } }; + +// FIXME: Temp fix https://github.com/actions/toolkit/issues/777 +export function setOutput(name: string, value: any): void { + issueCommand('set-output', {name}, value); +} diff --git a/src/main.ts b/src/main.ts index 6427c1c..5a1e2b9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -28,7 +28,7 @@ async function run(): Promise { core.info(`Using buildx ${buildxVersion}`); const builderName: string = inputs.driver == 'docker' ? 'default' : `builder-${require('uuid').v4()}`; - core.setOutput('name', builderName); + context.setOutput('name', builderName); stateHelper.setBuilderName(builderName); if (inputs.driver !== 'docker') { @@ -69,10 +69,14 @@ async function run(): Promise { core.endGroup(); } - core.startGroup(`Extracting available platforms`); - const platforms = await buildx.platforms(); - core.info(`${platforms}`); - core.setOutput('platforms', platforms); + core.startGroup(`Inspect builder`); + const builder = await buildx.inspect(builderName); + core.info(JSON.stringify(builder, undefined, 2)); + context.setOutput('driver', builder.driver); + context.setOutput('endpoint', builder.node_endpoint); + context.setOutput('status', builder.node_status); + context.setOutput('flags', builder.node_flags); + context.setOutput('platforms', builder.node_platforms); core.endGroup(); } catch (error) { core.setFailed(error.message);