diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16391b2..4837fbc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,9 +107,11 @@ jobs: strategy: fail-fast: false matrix: - driver-opt: + driver-opts: - image=moby/buildkit:latest - - image=moby/buildkit:master + - | + image=moby/buildkit:master + network=host steps: - name: Checkout @@ -119,7 +121,7 @@ jobs: uses: ./ with: driver: docker-container - driver-opt: ${{ matrix.driver-opt }} + driver-opts: ${{ matrix.driver-opts }} docker-driver: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 981a2c4..434ac14 100644 --- a/README.md +++ b/README.md @@ -130,11 +130,21 @@ Following inputs can be used as `step.with` keys |--------------------|---------|-----------------------------------| | `version` | String | [Buildx](https://github.com/docker/buildx) version. (e.g. `v0.3.0`, `latest`) | | `driver` | String | Sets the [builder driver](https://github.com/docker/buildx#--driver-driver) to be used (default `docker-container`) | -| `driver-opt` | String | Passes additional [driver-specific options](https://github.com/docker/buildx#--driver-opt-options) | +| `driver-opts` | CSV | List of additional [driver-specific options](https://github.com/docker/buildx#--driver-opt-options) | | `buildkitd-flags` | String | [Flags for buildkitd](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) daemon | | `install` | Bool | Sets up `docker build` command as an alias to `docker buildx` (default `false`) | | `use` | Bool | Switch to this builder instance (default `true`) | +> `CSV` type must be a newline-delimited string +> ```yaml +> driver-opts: image=moby/buildkit:master +> ``` +> ```yaml +> driver-opts: | +> image=moby/buildkit:master +> network=host +> ``` + ### outputs Following outputs are available diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts new file mode 100644 index 0000000..63b0ef8 --- /dev/null +++ b/__tests__/context.test.ts @@ -0,0 +1,74 @@ +import * as context from '../src/context'; + +describe('getInputList', () => { + it('handles single line correctly', async () => { + await setInput('foo', 'bar'); + const res = await context.getInputList('foo'); + console.log(res); + expect(res).toEqual(['bar']); + }); + + it('handles multiple lines correctly', async () => { + setInput('foo', 'bar\nbaz'); + const res = await context.getInputList('foo'); + console.log(res); + expect(res).toEqual(['bar', 'baz']); + }); + + it('handles comma correctly', async () => { + setInput('foo', 'bar,baz'); + const res = await context.getInputList('foo'); + console.log(res); + expect(res).toEqual(['bar', 'baz']); + }); + + it('handles different new lines correctly', async () => { + setInput('foo', 'bar\r\nbaz'); + const res = await context.getInputList('foo'); + console.log(res); + expect(res).toEqual(['bar', 'baz']); + }); + + it('handles different new lines and comma correctly', async () => { + setInput('foo', 'bar\r\nbaz,bat'); + const res = await context.getInputList('foo'); + console.log(res); + expect(res).toEqual(['bar', 'baz', 'bat']); + }); + + it('handles multiple lines and ignoring comma correctly', async () => { + setInput('driver-opts', 'image=moby/buildkit:master\nnetwork=host'); + const res = await context.getInputList('driver-opts', true); + console.log(res); + expect(res).toEqual(['image=moby/buildkit:master', 'network=host']); + }); + + it('handles different new lines and ignoring comma correctly', async () => { + setInput('driver-opts', 'image=moby/buildkit:master\r\nnetwork=host'); + const res = await context.getInputList('driver-opts', true); + console.log(res); + expect(res).toEqual(['image=moby/buildkit:master', 'network=host']); + }); +}); + +describe('asyncForEach', () => { + it('executes async tasks sequentially', async () => { + const testValues = [1, 2, 3, 4, 5]; + const results: number[] = []; + + await context.asyncForEach(testValues, async value => { + results.push(value); + }); + + expect(results).toEqual(testValues); + }); +}); + +// 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()}`; +} + +function setInput(name: string, value: string): void { + process.env[getInputName(name)] = value; +} diff --git a/action.yml b/action.yml index 8ed2b25..369c8f3 100644 --- a/action.yml +++ b/action.yml @@ -14,8 +14,8 @@ inputs: description: 'Sets the builder driver to be used' default: 'docker-container' required: false - driver-opt: - description: 'Passes additional driver-specific options. Eg. image=moby/buildkit:master' + driver-opts: + description: 'List of additional driver-specific options. Eg. image=moby/buildkit:master' required: false buildkitd-flags: description: 'Flags for buildkitd daemon' diff --git a/dist/index.js b/dist/index.js index 0f2557c..db03322 100644 --- a/dist/index.js +++ b/dist/index.js @@ -501,6 +501,7 @@ const exec = __importStar(__webpack_require__(514)); const os = __importStar(__webpack_require__(87)); const path = __importStar(__webpack_require__(622)); const buildx = __importStar(__webpack_require__(295)); +const context = __importStar(__webpack_require__(842)); const mexec = __importStar(__webpack_require__(757)); const stateHelper = __importStar(__webpack_require__(647)); function run() { @@ -510,38 +511,33 @@ function run() { core.setFailed('Only supported on linux platform'); return; } - const bxVersion = core.getInput('version'); - const bxDriver = core.getInput('driver') || 'docker-container'; - const bxDriverOpt = core.getInput('driver-opt'); - const bxBuildkitdFlags = core.getInput('buildkitd-flags'); - const bxInstall = /true/i.test(core.getInput('install')); - const bxUse = /true/i.test(core.getInput('use')); + const inputs = yield context.getInputs(); const dockerConfigHome = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); - if (!(yield buildx.isAvailable()) || bxVersion) { - yield buildx.install(bxVersion || 'latest', dockerConfigHome); + if (!(yield buildx.isAvailable()) || inputs.version) { + yield buildx.install(inputs.version || 'latest', dockerConfigHome); } core.info('📣 Buildx info'); yield exec.exec('docker', ['buildx', 'version']); - const builderName = bxDriver == 'docker' ? 'default' : `builder-${process.env.GITHUB_JOB}-${(yield buildx.countBuilders()) + 1}`; + const builderName = inputs.driver == 'docker' ? 'default' : `builder-${process.env.GITHUB_JOB}-${(yield buildx.countBuilders()) + 1}`; core.setOutput('name', builderName); stateHelper.setBuilderName(builderName); - if (bxDriver != 'docker') { + if (inputs.driver !== 'docker') { core.info('🔨 Creating a new builder instance...'); - let createArgs = ['buildx', 'create', '--name', builderName, '--driver', bxDriver]; - if (bxDriverOpt) { - createArgs.push('--driver-opt', bxDriverOpt); + let createArgs = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver]; + yield context.asyncForEach(inputs.driverOpts, (driverOpt) => __awaiter(this, void 0, void 0, function* () { + createArgs.push('--driver-opt', driverOpt); + })); + if (inputs.buildkitdFlags) { + createArgs.push('--buildkitd-flags', inputs.buildkitdFlags); } - if (bxBuildkitdFlags) { - createArgs.push('--buildkitd-flags', bxBuildkitdFlags); - } - if (bxUse) { + if (inputs.use) { createArgs.push('--use'); } yield exec.exec('docker', createArgs); core.info('🏃 Booting builder...'); yield exec.exec('docker', ['buildx', 'inspect', '--bootstrap']); } - if (bxInstall) { + if (inputs.install) { core.info('🤝 Setting buildx as default builder...'); yield exec.exec('docker', ['buildx', 'install']); } @@ -1942,15 +1938,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge Object.defineProperty(exports, "__esModule", { value: true }); exports.install = exports.platforms = exports.countBuilders = exports.isAvailable = void 0; const fs = __importStar(__webpack_require__(747)); -const os = __importStar(__webpack_require__(87)); const path = __importStar(__webpack_require__(622)); const semver = __importStar(__webpack_require__(383)); const util = __importStar(__webpack_require__(669)); +const context = __importStar(__webpack_require__(842)); const exec = __importStar(__webpack_require__(757)); const github = __importStar(__webpack_require__(928)); const core = __importStar(__webpack_require__(186)); const tc = __importStar(__webpack_require__(784)); -const osPlat = os.platform(); function isAvailable() { return __awaiter(this, void 0, void 0, function* () { return yield exec.exec(`docker`, ['buildx'], true).then(res => { @@ -2010,7 +2005,7 @@ function install(inputVersion, dockerConfigHome) { if (!fs.existsSync(pluginsDir)) { fs.mkdirSync(pluginsDir, { recursive: true }); } - const filename = osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; + const filename = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; const pluginPath = path.join(pluginsDir, filename); core.debug(`Plugin path is ${pluginPath}`); fs.copyFileSync(path.join(toolPath, filename), pluginPath); @@ -2023,10 +2018,10 @@ exports.install = install; function download(version) { return __awaiter(this, void 0, void 0, function* () { version = semver.clean(version) || ''; - const platform = osPlat == 'win32' ? 'windows' : osPlat; - const ext = osPlat == 'win32' ? '.exe' : ''; + const platform = context.osPlat == 'win32' ? 'windows' : context.osPlat; + const ext = context.osPlat == 'win32' ? '.exe' : ''; const filename = util.format('buildx-v%s.%s-amd64%s', version, platform, ext); - const targetFile = osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; + const targetFile = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; const downloadUrl = util.format('https://github.com/docker/buildx/releases/download/v%s/%s', version, filename); let downloadPath; try { @@ -6489,6 +6484,78 @@ module.exports = require("url"); /***/ }), +/***/ 842: +/***/ (function(__unusedmodule, exports, __webpack_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.asyncForEach = exports.getInputList = exports.getInputs = exports.osPlat = void 0; +const os = __importStar(__webpack_require__(87)); +const core = __importStar(__webpack_require__(186)); +exports.osPlat = os.platform(); +function getInputs() { + return __awaiter(this, void 0, void 0, function* () { + return { + version: core.getInput('version'), + driver: core.getInput('driver') || 'docker-container', + driverOpts: yield getInputList('driver-opts', true), + buildkitdFlags: core.getInput('buildkitd-flags'), + install: /true/i.test(core.getInput('install')), + use: /true/i.test(core.getInput('use')) + }; + }); +} +exports.getInputs = getInputs; +function getInputList(name, ignoreComma) { + return __awaiter(this, void 0, void 0, function* () { + const items = core.getInput(name); + if (items == '') { + return []; + } + return items + .split(/\r?\n/) + .reduce((acc, line) => acc.concat(!ignoreComma ? line.split(',') : line).map(pat => pat.trim()), []); + }); +} +exports.getInputList = getInputList; +exports.asyncForEach = (array, callback) => __awaiter(void 0, void 0, void 0, function* () { + for (let index = 0; index < array.length; index++) { + yield callback(array[index], index, array); + } +}); +//# sourceMappingURL=context.js.map + +/***/ }), + /***/ 848: /***/ (function(module, __unusedexports, __webpack_require__) { diff --git a/src/buildx.ts b/src/buildx.ts index 88fd5af..244fe7c 100644 --- a/src/buildx.ts +++ b/src/buildx.ts @@ -1,15 +1,13 @@ import * as fs from 'fs'; -import * as os from 'os'; import * as path from 'path'; import * as semver from 'semver'; import * as util from 'util'; +import * as context from './context'; import * as exec from './exec'; import * as github from './github'; import * as core from '@actions/core'; import * as tc from '@actions/tool-cache'; -const osPlat: string = os.platform(); - export async function isAvailable(): Promise { return await exec.exec(`docker`, ['buildx'], true).then(res => { if (res.stderr != '' && !res.success) { @@ -65,7 +63,7 @@ export async function install(inputVersion: string, dockerConfigHome: string): P fs.mkdirSync(pluginsDir, {recursive: true}); } - const filename: string = osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; + const filename: string = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; const pluginPath: string = path.join(pluginsDir, filename); core.debug(`Plugin path is ${pluginPath}`); fs.copyFileSync(path.join(toolPath, filename), pluginPath); @@ -78,10 +76,10 @@ export async function install(inputVersion: string, dockerConfigHome: string): P async function download(version: string): Promise { version = semver.clean(version) || ''; - const platform: string = osPlat == 'win32' ? 'windows' : osPlat; - const ext: string = osPlat == 'win32' ? '.exe' : ''; + const platform: string = context.osPlat == 'win32' ? 'windows' : context.osPlat; + const ext: string = context.osPlat == 'win32' ? '.exe' : ''; const filename: string = util.format('buildx-v%s.%s-amd64%s', version, platform, ext); - const targetFile: string = osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; + const targetFile: string = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; const downloadUrl = util.format('https://github.com/docker/buildx/releases/download/v%s/%s', version, filename); let downloadPath: string; diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..c2af0f3 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,40 @@ +import * as os from 'os'; +import * as core from '@actions/core'; + +export const osPlat: string = os.platform(); + +export interface Inputs { + version: string; + driver: string; + driverOpts: string[]; + buildkitdFlags: string; + install: boolean; + use: boolean; +} + +export async function getInputs(): Promise { + return { + version: core.getInput('version'), + driver: core.getInput('driver') || 'docker-container', + driverOpts: await getInputList('driver-opts', true), + buildkitdFlags: core.getInput('buildkitd-flags'), + install: /true/i.test(core.getInput('install')), + use: /true/i.test(core.getInput('use')) + }; +} + +export async function getInputList(name: string, ignoreComma?: boolean): Promise { + const items = core.getInput(name); + if (items == '') { + return []; + } + return items + .split(/\r?\n/) + .reduce((acc, line) => acc.concat(!ignoreComma ? line.split(',') : line).map(pat => pat.trim()), []); +} + +export const asyncForEach = async (array, callback) => { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +}; diff --git a/src/main.ts b/src/main.ts index 8c6a422..3fa19e0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,6 +3,7 @@ import * as exec from '@actions/exec'; import * as os from 'os'; import * as path from 'path'; import * as buildx from './buildx'; +import * as context from './context'; import * as mexec from './exec'; import * as stateHelper from './state-helper'; @@ -13,48 +14,40 @@ async function run(): Promise { return; } - const bxVersion: string = core.getInput('version'); - const bxDriver: string = core.getInput('driver') || 'docker-container'; - const bxDriverOpt: string = core.getInput('driver-opt'); - const bxBuildkitdFlags: string = core.getInput('buildkitd-flags'); - const bxInstall: boolean = /true/i.test(core.getInput('install')); - const bxUse: boolean = /true/i.test(core.getInput('use')); - + const inputs: context.Inputs = await context.getInputs(); const dockerConfigHome: string = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); - if (!(await buildx.isAvailable()) || bxVersion) { - await buildx.install(bxVersion || 'latest', dockerConfigHome); + if (!(await buildx.isAvailable()) || inputs.version) { + await buildx.install(inputs.version || 'latest', dockerConfigHome); } core.info('📣 Buildx info'); await exec.exec('docker', ['buildx', 'version']); const builderName: string = - bxDriver == 'docker' ? 'default' : `builder-${process.env.GITHUB_JOB}-${(await buildx.countBuilders()) + 1}`; - + inputs.driver == 'docker' ? 'default' : `builder-${process.env.GITHUB_JOB}-${(await buildx.countBuilders()) + 1}`; core.setOutput('name', builderName); stateHelper.setBuilderName(builderName); - if (bxDriver != 'docker') { + if (inputs.driver !== 'docker') { core.info('🔨 Creating a new builder instance...'); - let createArgs: Array = ['buildx', 'create', '--name', builderName, '--driver', bxDriver]; - if (bxDriverOpt) { - createArgs.push('--driver-opt', bxDriverOpt); + let createArgs: Array = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver]; + await context.asyncForEach(inputs.driverOpts, async driverOpt => { + createArgs.push('--driver-opt', driverOpt); + }); + if (inputs.buildkitdFlags) { + createArgs.push('--buildkitd-flags', inputs.buildkitdFlags); } - if (bxBuildkitdFlags) { - createArgs.push('--buildkitd-flags', bxBuildkitdFlags); - } - if (bxUse) { + if (inputs.use) { createArgs.push('--use'); } - await exec.exec('docker', createArgs); core.info('🏃 Booting builder...'); await exec.exec('docker', ['buildx', 'inspect', '--bootstrap']); } - if (bxInstall) { + if (inputs.install) { core.info('🤝 Setting buildx as default builder...'); await exec.exec('docker', ['buildx', 'install']); }