Allow building buildx from source

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax 2021-07-02 07:02:22 +02:00
parent a1c666d855
commit f40e8894f1
No known key found for this signature in database
GPG Key ID: 3248E46B6BB8C7F7
14 changed files with 342 additions and 44 deletions

View File

@ -313,3 +313,37 @@ jobs:
echo "Status: ${{ steps.buildx.outputs.status }}" echo "Status: ${{ steps.buildx.outputs.status }}"
echo "Flags: ${{ steps.buildx.outputs.flags }}" echo "Flags: ${{ steps.buildx.outputs.flags }}"
echo "Platforms: ${{ steps.buildx.outputs.platforms }}" echo "Platforms: ${{ steps.buildx.outputs.platforms }}"
build-ref:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ref:
- master
- refs/tags/v0.5.1
- refs/pull/648/head
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Set up Docker Buildx
uses: ./
with:
version: https://github.com/docker/buildx.git#${{ matrix.ref }}
-
name: Check version
run: |
docker buildx version
-
name: Create Dockerfile
run: |
cat > ./Dockerfile <<EOL
FROM alpine
EOL
-
name: Build
uses: docker/build-push-action@master
with:
context: .

View File

@ -1,5 +1,5 @@
{ {
"printWidth": 120, "printWidth": 240,
"tabWidth": 2, "tabWidth": 2,
"useTabs": false, "useTabs": false,
"semi": true, "semi": true,

View File

@ -125,7 +125,7 @@ Following inputs can be used as `step.with` keys
| Name | Type | Description | | Name | Type | Description |
|--------------------|---------|-----------------------------------| |--------------------|---------|-----------------------------------|
| `version` | String | [Buildx](https://github.com/docker/buildx) version. (eg. `v0.3.0`, `latest`) | | `version` | String | [buildx](https://github.com/docker/buildx) version. (eg. `v0.3.0`, `latest`, `https://github.com/docker/buildx.git#master`) |
| `driver` | String | Sets the [builder driver](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver) to be used (default `docker-container`) | | `driver` | String | Sets the [builder driver](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver) to be used (default `docker-container`) |
| `driver-opts` | CSV | List of additional [driver-specific options](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver-opt) (eg. `image=moby/buildkit:master`) | | `driver-opts` | CSV | List of additional [driver-specific options](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#driver-opt) (eg. `image=moby/buildkit:master`) |
| `buildkitd-flags` | String | [Flags for buildkitd](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) daemon (since [buildx v0.3.0](https://github.com/docker/buildx/releases/tag/v0.3.0)) | | `buildkitd-flags` | String | [Flags for buildkitd](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) daemon (since [buildx v0.3.0](https://github.com/docker/buildx/releases/tag/v0.3.0)) |

View File

@ -1,10 +1,19 @@
import fs = require('fs'); import * as fs from 'fs';
import * as buildx from '../src/buildx';
import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path';
import * as buildx from '../src/buildx';
import * as context from '../src/context';
import * as semver from 'semver'; import * as semver from 'semver';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
const tmpDir = path.join('/tmp/.docker-setup-buildx-jest').split(path.sep).join(path.posix.sep);
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
describe('isAvailable', () => { describe('isAvailable', () => {
const execSpy: jest.SpyInstance = jest.spyOn(exec, 'getExecOutput'); const execSpy: jest.SpyInstance = jest.spyOn(exec, 'getExecOutput');
buildx.isAvailable(); buildx.isAvailable();
@ -41,9 +50,20 @@ describe('parseVersion', () => {
test.each([ test.each([
['github.com/docker/buildx 0.4.1+azure bda4882a65349ca359216b135896bddc1d92461c', '0.4.1'], ['github.com/docker/buildx 0.4.1+azure bda4882a65349ca359216b135896bddc1d92461c', '0.4.1'],
['github.com/docker/buildx v0.4.1 bda4882a65349ca359216b135896bddc1d92461c', '0.4.1'], ['github.com/docker/buildx v0.4.1 bda4882a65349ca359216b135896bddc1d92461c', '0.4.1'],
['github.com/docker/buildx v0.4.2 fb7b670b764764dc4716df3eba07ffdae4cc47b2', '0.4.2'] ['github.com/docker/buildx v0.4.2 fb7b670b764764dc4716df3eba07ffdae4cc47b2', '0.4.2'],
['github.com/docker/buildx f117971 f11797113e5a9b86bd976329c5dbb8a8bfdfadfa', 'f117971']
])('given %p', async (stdout, expected) => { ])('given %p', async (stdout, expected) => {
expect(await buildx.parseVersion(stdout)).toEqual(expected); expect(buildx.parseVersion(stdout)).toEqual(expected);
});
});
describe('satisfies', () => {
test.each([
['0.4.1', '>=0.3.2', true],
['bda4882a65349ca359216b135896bddc1d92461c', '>0.1.0', false],
['f117971', '>0.6.0', true]
])('given %p', async (version, range, expected) => {
expect(buildx.satisfies(version, range)).toBe(expected);
}); });
}); });
@ -72,6 +92,15 @@ describe('inspect', () => {
); );
}); });
describe('build', () => {
it.skip('valid', async () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-buildx-'));
const buildxBin = await buildx.build('https://github.com/docker/buildx.git#refs/pull/648/head', tmpDir);
console.log(buildxBin);
expect(fs.existsSync(buildxBin)).toBe(true);
}, 100000);
});
describe('install', () => { describe('install', () => {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-buildx-')); const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-buildx-'));
it('acquires v0.4.1 version of buildx', async () => { it('acquires v0.4.1 version of buildx', async () => {

View File

@ -1,6 +1,16 @@
import * as fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import * as path from 'path';
import * as context from '../src/context'; import * as context from '../src/context';
jest.spyOn(context, 'tmpDir').mockImplementation((): string => {
const tmpDir = path.join('/tmp/.docker-setup-buildx-jest').split(path.sep).join(path.posix.sep);
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, {recursive: true});
}
return tmpDir;
});
describe('getInputList', () => { describe('getInputList', () => {
it('handles single line correctly', async () => { it('handles single line correctly', async () => {
await setInput('foo', 'bar'); await setInput('foo', 'bar');

9
__tests__/git.test.ts Normal file
View File

@ -0,0 +1,9 @@
import * as git from '../src/git';
describe('git', () => {
it('returns git remote ref', async () => {
const ref: string = await git.getRemoteSha('https://github.com/docker/buildx.git', 'refs/pull/648/head');
console.log(`ref: ${ref}`);
expect(ref).toEqual('f11797113e5a9b86bd976329c5dbb8a8bfdfadfa');
});
});

11
__tests__/util.test.ts Normal file
View File

@ -0,0 +1,11 @@
import * as util from '../src/util';
describe('isValidUrl', () => {
test.each([
['https://github.com/docker/buildx.git', true],
['https://github.com/docker/buildx.git#refs/pull/648/head', true],
['v0.4.1', false]
])('given %p', async (url, expected) => {
expect(util.isValidUrl(url)).toEqual(expected);
});
});

3
codecov.yml Normal file
View File

@ -0,0 +1,3 @@
comment: false
github_checks:
annotations: false

167
dist/index.js generated vendored
View File

@ -35,12 +35,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
}); });
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.getBuildKitVersion = exports.install = exports.inspect = exports.parseVersion = exports.getVersion = exports.isAvailable = void 0; exports.getBuildKitVersion = exports.install = exports.build = exports.inspect = exports.satisfies = exports.parseVersion = exports.getVersion = exports.isAvailable = void 0;
const fs = __importStar(__nccwpck_require__(5747)); const fs = __importStar(__nccwpck_require__(5747));
const path = __importStar(__nccwpck_require__(5622)); const path = __importStar(__nccwpck_require__(5622));
const semver = __importStar(__nccwpck_require__(1383)); const semver = __importStar(__nccwpck_require__(1383));
const util = __importStar(__nccwpck_require__(1669)); const util = __importStar(__nccwpck_require__(1669));
const context = __importStar(__nccwpck_require__(3842)); const context = __importStar(__nccwpck_require__(3842));
const git = __importStar(__nccwpck_require__(3374));
const github = __importStar(__nccwpck_require__(5928)); const github = __importStar(__nccwpck_require__(5928));
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const exec = __importStar(__nccwpck_require__(1514)); const exec = __importStar(__nccwpck_require__(1514));
@ -78,15 +79,17 @@ function getVersion() {
} }
exports.getVersion = getVersion; exports.getVersion = getVersion;
function parseVersion(stdout) { function parseVersion(stdout) {
return __awaiter(this, void 0, void 0, function* () { const matches = /\sv?([0-9a-f]{7}|[0-9.]+)/.exec(stdout);
const matches = /\sv?([0-9.]+)/.exec(stdout); if (!matches) {
if (!matches) { throw new Error(`Cannot parse buildx version`);
throw new Error(`Cannot parse buildx version`); }
} return matches[1];
return semver.clean(matches[1]);
});
} }
exports.parseVersion = parseVersion; exports.parseVersion = parseVersion;
function satisfies(version, range) {
return semver.satisfies(version, range) || /^[0-9a-f]{7}$/.exec(version) !== null;
}
exports.satisfies = satisfies;
function inspect(name) { function inspect(name) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
return yield exec return yield exec
@ -142,6 +145,33 @@ function inspect(name) {
}); });
} }
exports.inspect = inspect; exports.inspect = inspect;
function build(inputBuildRef, dockerConfigHome) {
return __awaiter(this, void 0, void 0, function* () {
let [repo, ref] = inputBuildRef.split('#');
if (ref.length == 0) {
ref = 'master';
}
const sha = yield git.getRemoteSha(repo, ref);
core.debug(`Remote ref ${sha} found`);
let toolPath;
toolPath = tc.find('buildx', sha);
if (!toolPath) {
const outFolder = path.join(context.tmpDir(), 'out').split(path.sep).join(path.posix.sep);
toolPath = yield exec
.getExecOutput('docker', ['buildx', 'build', '--target', 'binaries', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', '--output', `type=local,dest=${outFolder}`, inputBuildRef], {
ignoreReturnCode: true
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
core.warning(res.stderr.trim());
}
return tc.cacheFile(`${outFolder}/buildx`, context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx', 'buildx', sha);
});
}
return setPlugin(toolPath, dockerConfigHome);
});
}
exports.build = build;
function install(inputVersion, dockerConfigHome) { function install(inputVersion, dockerConfigHome) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const release = yield github.getRelease(inputVersion); const release = yield github.getRelease(inputVersion);
@ -159,6 +189,12 @@ function install(inputVersion, dockerConfigHome) {
} }
toolPath = yield download(version); toolPath = yield download(version);
} }
return setPlugin(toolPath, dockerConfigHome);
});
}
exports.install = install;
function setPlugin(toolPath, dockerConfigHome) {
return __awaiter(this, void 0, void 0, function* () {
const pluginsDir = path.join(dockerConfigHome, 'cli-plugins'); const pluginsDir = path.join(dockerConfigHome, 'cli-plugins');
core.debug(`Plugins dir is ${pluginsDir}`); core.debug(`Plugins dir is ${pluginsDir}`);
if (!fs.existsSync(pluginsDir)) { if (!fs.existsSync(pluginsDir)) {
@ -173,7 +209,6 @@ function install(inputVersion, dockerConfigHome) {
return pluginPath; return pluginPath;
}); });
} }
exports.install = install;
function download(version) { function download(version) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
const targetFile = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; const targetFile = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx';
@ -286,21 +321,33 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); 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 })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.setOutput = exports.asyncForEach = exports.getInputList = exports.getInputs = exports.osArch = exports.osPlat = void 0; exports.setOutput = exports.asyncForEach = exports.getInputList = exports.getInputs = exports.tmpDir = exports.osArch = exports.osPlat = void 0;
const fs_1 = __importDefault(__nccwpck_require__(5747));
const os = __importStar(__nccwpck_require__(2087)); const os = __importStar(__nccwpck_require__(2087));
const path_1 = __importDefault(__nccwpck_require__(5622));
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const command_1 = __nccwpck_require__(7351); const command_1 = __nccwpck_require__(7351);
let _tmpDir;
exports.osPlat = os.platform(); exports.osPlat = os.platform();
exports.osArch = os.arch(); exports.osArch = os.arch();
function tmpDir() {
if (!_tmpDir) {
_tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os.tmpdir(), 'docker-setup-buildx-')).split(path_1.default.sep).join(path_1.default.posix.sep);
}
return _tmpDir;
}
exports.tmpDir = tmpDir;
function getInputs() { function getInputs() {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
return { return {
version: core.getInput('version'), version: core.getInput('version'),
driver: core.getInput('driver') || 'docker-container', driver: core.getInput('driver') || 'docker-container',
driverOpts: yield getInputList('driver-opts', true), driverOpts: yield getInputList('driver-opts', true),
buildkitdFlags: core.getInput('buildkitd-flags') || buildkitdFlags: core.getInput('buildkitd-flags') || '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
'--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
install: core.getBooleanInput('install'), install: core.getBooleanInput('install'),
use: core.getBooleanInput('use'), use: core.getBooleanInput('use'),
endpoint: core.getInput('endpoint'), endpoint: core.getInput('endpoint'),
@ -337,6 +384,66 @@ exports.setOutput = setOutput;
/***/ }), /***/ }),
/***/ 3374:
/***/ (function(__unused_webpack_module, exports, __nccwpck_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.prototype.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.getRemoteSha = void 0;
const exec = __importStar(__nccwpck_require__(1514));
function getRemoteSha(repo, ref) {
return __awaiter(this, void 0, void 0, function* () {
return yield exec
.getExecOutput(`git`, ['ls-remote', repo, ref], {
ignoreReturnCode: true,
silent: true
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr);
}
const [rsha, rref] = res.stdout.trim().split(/[\s\t]/);
if (rsha.length == 0) {
throw new Error(`Cannot find remote ref for ${repo}#${ref}`);
}
return rsha;
});
});
}
exports.getRemoteSha = getRemoteSha;
//# sourceMappingURL=git.js.map
/***/ }),
/***/ 5928: /***/ 5928:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
@ -419,10 +526,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
const os = __importStar(__nccwpck_require__(2087)); const os = __importStar(__nccwpck_require__(2087));
const path = __importStar(__nccwpck_require__(5622)); const path = __importStar(__nccwpck_require__(5622));
const semver = __importStar(__nccwpck_require__(1383));
const buildx = __importStar(__nccwpck_require__(9295)); const buildx = __importStar(__nccwpck_require__(9295));
const context = __importStar(__nccwpck_require__(3842)); const context = __importStar(__nccwpck_require__(3842));
const stateHelper = __importStar(__nccwpck_require__(8647)); const stateHelper = __importStar(__nccwpck_require__(8647));
const util = __importStar(__nccwpck_require__(4024));
const core = __importStar(__nccwpck_require__(2186)); const core = __importStar(__nccwpck_require__(2186));
const exec = __importStar(__nccwpck_require__(1514)); const exec = __importStar(__nccwpck_require__(1514));
function run() { function run() {
@ -435,8 +542,13 @@ function run() {
core.endGroup(); core.endGroup();
const inputs = yield context.getInputs(); const inputs = yield context.getInputs();
const dockerConfigHome = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); const dockerConfigHome = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker');
if (!(yield buildx.isAvailable()) || inputs.version) { if (util.isValidUrl(inputs.version)) {
core.startGroup(`Installing buildx`); core.startGroup(`Build and install buildx`);
yield buildx.build(inputs.version, dockerConfigHome);
core.endGroup();
}
else if (!(yield buildx.isAvailable()) || inputs.version) {
core.startGroup(`Download and install buildx`);
yield buildx.install(inputs.version || 'latest', dockerConfigHome); yield buildx.install(inputs.version || 'latest', dockerConfigHome);
core.endGroup(); core.endGroup();
} }
@ -447,7 +559,7 @@ function run() {
if (inputs.driver !== 'docker') { if (inputs.driver !== 'docker') {
core.startGroup(`Creating a new builder instance`); core.startGroup(`Creating a new builder instance`);
let createArgs = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver]; let createArgs = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver];
if (semver.satisfies(buildxVersion, '>=0.3.0')) { if (buildx.satisfies(buildxVersion, '>=0.3.0')) {
yield context.asyncForEach(inputs.driverOpts, (driverOpt) => __awaiter(this, void 0, void 0, function* () { yield context.asyncForEach(inputs.driverOpts, (driverOpt) => __awaiter(this, void 0, void 0, function* () {
createArgs.push('--driver-opt', driverOpt); createArgs.push('--driver-opt', driverOpt);
})); }));
@ -468,7 +580,7 @@ function run() {
core.endGroup(); core.endGroup();
core.startGroup(`Booting builder`); core.startGroup(`Booting builder`);
let bootstrapArgs = ['buildx', 'inspect', '--bootstrap']; let bootstrapArgs = ['buildx', 'inspect', '--bootstrap'];
if (semver.satisfies(buildxVersion, '>=0.4.0')) { if (buildx.satisfies(buildxVersion, '>=0.4.0')) {
bootstrapArgs.push('--builder', builderName); bootstrapArgs.push('--builder', builderName);
} }
yield exec.exec('docker', bootstrapArgs); yield exec.exec('docker', bootstrapArgs);
@ -593,6 +705,27 @@ if (!exports.IsPost) {
/***/ }), /***/ }),
/***/ 4024:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.isValidUrl = void 0;
function isValidUrl(url) {
try {
new URL(url);
}
catch (e) {
return false;
}
return true;
}
exports.isValidUrl = isValidUrl;
//# sourceMappingURL=util.js.map
/***/ }),
/***/ 7351: /***/ 7351:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {

View File

@ -3,6 +3,7 @@ import * as path from 'path';
import * as semver from 'semver'; import * as semver from 'semver';
import * as util from 'util'; import * as util from 'util';
import * as context from './context'; import * as context from './context';
import * as git from './git';
import * as github from './github'; import * as github from './github';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
@ -46,12 +47,16 @@ export async function getVersion(): Promise<string> {
}); });
} }
export async function parseVersion(stdout: string): Promise<string> { export function parseVersion(stdout: string): string {
const matches = /\sv?([0-9.]+)/.exec(stdout); const matches = /\sv?([0-9a-f]{7}|[0-9.]+)/.exec(stdout);
if (!matches) { if (!matches) {
throw new Error(`Cannot parse buildx version`); throw new Error(`Cannot parse buildx version`);
} }
return semver.clean(matches[1]); return matches[1];
}
export function satisfies(version: string, range: string): boolean {
return semver.satisfies(version, range) || /^[0-9a-f]{7}$/.exec(version) !== null;
} }
export async function inspect(name: string): Promise<Builder> { export async function inspect(name: string): Promise<Builder> {
@ -106,6 +111,34 @@ export async function inspect(name: string): Promise<Builder> {
}); });
} }
export async function build(inputBuildRef: string, dockerConfigHome: string): Promise<string> {
let [repo, ref] = inputBuildRef.split('#');
if (ref.length == 0) {
ref = 'master';
}
const sha = await git.getRemoteSha(repo, ref);
core.debug(`Remote ref ${sha} found`);
let toolPath: string;
toolPath = tc.find('buildx', sha);
if (!toolPath) {
const outFolder = path.join(context.tmpDir(), 'out').split(path.sep).join(path.posix.sep);
toolPath = await exec
.getExecOutput('docker', ['buildx', 'build', '--target', 'binaries', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', '--output', `type=local,dest=${outFolder}`, inputBuildRef], {
ignoreReturnCode: true
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
core.warning(res.stderr.trim());
}
return tc.cacheFile(`${outFolder}/buildx`, context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx', 'buildx', sha);
});
}
return setPlugin(toolPath, dockerConfigHome);
}
export async function install(inputVersion: string, dockerConfigHome: string): Promise<string> { export async function install(inputVersion: string, dockerConfigHome: string): Promise<string> {
const release: github.GitHubRelease | null = await github.getRelease(inputVersion); const release: github.GitHubRelease | null = await github.getRelease(inputVersion);
if (!release) { if (!release) {
@ -124,6 +157,10 @@ export async function install(inputVersion: string, dockerConfigHome: string): P
toolPath = await download(version); toolPath = await download(version);
} }
return setPlugin(toolPath, dockerConfigHome);
}
async function setPlugin(toolPath: string, dockerConfigHome: string): Promise<string> {
const pluginsDir: string = path.join(dockerConfigHome, 'cli-plugins'); const pluginsDir: string = path.join(dockerConfigHome, 'cli-plugins');
core.debug(`Plugins dir is ${pluginsDir}`); core.debug(`Plugins dir is ${pluginsDir}`);
if (!fs.existsSync(pluginsDir)) { if (!fs.existsSync(pluginsDir)) {
@ -143,11 +180,7 @@ export async function install(inputVersion: string, dockerConfigHome: string): P
async function download(version: string): Promise<string> { async function download(version: string): Promise<string> {
const targetFile: string = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; const targetFile: string = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx';
const downloadUrl = util.format( const downloadUrl = util.format('https://github.com/docker/buildx/releases/download/v%s/%s', version, await filename(version));
'https://github.com/docker/buildx/releases/download/v%s/%s',
version,
await filename(version)
);
let downloadPath: string; let downloadPath: string;
try { try {

View File

@ -1,10 +1,20 @@
import fs from 'fs';
import * as os from 'os'; import * as os from 'os';
import path from 'path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import {issueCommand} from '@actions/core/lib/command'; import {issueCommand} from '@actions/core/lib/command';
let _tmpDir: string;
export const osPlat: string = os.platform(); export const osPlat: string = os.platform();
export const osArch: string = os.arch(); export const osArch: string = os.arch();
export function tmpDir(): string {
if (!_tmpDir) {
_tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'docker-setup-buildx-')).split(path.sep).join(path.posix.sep);
}
return _tmpDir;
}
export interface Inputs { export interface Inputs {
version: string; version: string;
driver: string; driver: string;
@ -21,9 +31,7 @@ export async function getInputs(): Promise<Inputs> {
version: core.getInput('version'), version: core.getInput('version'),
driver: core.getInput('driver') || 'docker-container', driver: core.getInput('driver') || 'docker-container',
driverOpts: await getInputList('driver-opts', true), driverOpts: await getInputList('driver-opts', true),
buildkitdFlags: buildkitdFlags: core.getInput('buildkitd-flags') || '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
core.getInput('buildkitd-flags') ||
'--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
install: core.getBooleanInput('install'), install: core.getBooleanInput('install'),
use: core.getBooleanInput('use'), use: core.getBooleanInput('use'),
endpoint: core.getInput('endpoint'), endpoint: core.getInput('endpoint'),
@ -39,10 +47,7 @@ export async function getInputList(name: string, ignoreComma?: boolean): Promise
return items return items
.split(/\r?\n/) .split(/\r?\n/)
.filter(x => x) .filter(x => x)
.reduce<string[]>( .reduce<string[]>((acc, line) => acc.concat(!ignoreComma ? line.split(',').filter(x => x) : line).map(pat => pat.trim()), []);
(acc, line) => acc.concat(!ignoreComma ? line.split(',').filter(x => x) : line).map(pat => pat.trim()),
[]
);
} }
export const asyncForEach = async (array, callback) => { export const asyncForEach = async (array, callback) => {

19
src/git.ts Normal file
View File

@ -0,0 +1,19 @@
import * as exec from '@actions/exec';
export async function getRemoteSha(repo: string, ref: string): Promise<string> {
return await exec
.getExecOutput(`git`, ['ls-remote', repo, ref], {
ignoreReturnCode: true,
silent: true
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr);
}
const [rsha, rref] = res.stdout.trim().split(/[\s\t]/);
if (rsha.length == 0) {
throw new Error(`Cannot find remote ref for ${repo}#${ref}`);
}
return rsha;
});
}

View File

@ -1,9 +1,9 @@
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as semver from 'semver';
import * as buildx from './buildx'; import * as buildx from './buildx';
import * as context from './context'; import * as context from './context';
import * as stateHelper from './state-helper'; import * as stateHelper from './state-helper';
import * as util from './util';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
@ -17,8 +17,12 @@ async function run(): Promise<void> {
const inputs: context.Inputs = await context.getInputs(); const inputs: context.Inputs = await context.getInputs();
const dockerConfigHome: string = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); const dockerConfigHome: string = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker');
if (!(await buildx.isAvailable()) || inputs.version) { if (util.isValidUrl(inputs.version)) {
core.startGroup(`Installing buildx`); core.startGroup(`Build and install buildx`);
await buildx.build(inputs.version, dockerConfigHome);
core.endGroup();
} else if (!(await buildx.isAvailable()) || inputs.version) {
core.startGroup(`Download and install buildx`);
await buildx.install(inputs.version || 'latest', dockerConfigHome); await buildx.install(inputs.version || 'latest', dockerConfigHome);
core.endGroup(); core.endGroup();
} }
@ -31,7 +35,7 @@ async function run(): Promise<void> {
if (inputs.driver !== 'docker') { if (inputs.driver !== 'docker') {
core.startGroup(`Creating a new builder instance`); core.startGroup(`Creating a new builder instance`);
let createArgs: Array<string> = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver]; let createArgs: Array<string> = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver];
if (semver.satisfies(buildxVersion, '>=0.3.0')) { if (buildx.satisfies(buildxVersion, '>=0.3.0')) {
await context.asyncForEach(inputs.driverOpts, async driverOpt => { await context.asyncForEach(inputs.driverOpts, async driverOpt => {
createArgs.push('--driver-opt', driverOpt); createArgs.push('--driver-opt', driverOpt);
}); });
@ -53,7 +57,7 @@ async function run(): Promise<void> {
core.startGroup(`Booting builder`); core.startGroup(`Booting builder`);
let bootstrapArgs: Array<string> = ['buildx', 'inspect', '--bootstrap']; let bootstrapArgs: Array<string> = ['buildx', 'inspect', '--bootstrap'];
if (semver.satisfies(buildxVersion, '>=0.4.0')) { if (buildx.satisfies(buildxVersion, '>=0.4.0')) {
bootstrapArgs.push('--builder', builderName); bootstrapArgs.push('--builder', builderName);
} }
await exec.exec('docker', bootstrapArgs); await exec.exec('docker', bootstrapArgs);

8
src/util.ts Normal file
View File

@ -0,0 +1,8 @@
export function isValidUrl(url: string): boolean {
try {
new URL(url);
} catch (e) {
return false;
}
return true;
}