From b592f11aca75b919253aa3e1322570c54483e790 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Thu, 1 Jul 2021 01:27:22 +0200 Subject: [PATCH] Add `build-ref` input Signed-off-by: CrazyMax --- .github/workflows/ci.yml | 23 +++++++ __tests__/buildx.test.ts | 38 +++++++++++- __tests__/context.test.ts | 10 ++++ __tests__/git.test.ts | 9 +++ action.yml | 3 + dist/index.js | 122 ++++++++++++++++++++++++++++++++++++-- src/buildx.ts | 42 +++++++++++++ src/context.ts | 12 ++++ src/git.ts | 19 ++++++ src/main.ts | 8 ++- 10 files changed, 276 insertions(+), 10 deletions(-) create mode 100644 __tests__/git.test.ts create mode 100644 src/git.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c55fdea9..21a4c650 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -313,3 +313,26 @@ jobs: echo "Status: ${{ steps.buildx.outputs.status }}" echo "Flags: ${{ steps.buildx.outputs.flags }}" 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: + build-ref: https://github.com/docker/buildx.git#${{ matrix.ref }} + - + name: Check version + run: | + docker buildx version diff --git a/__tests__/buildx.test.ts b/__tests__/buildx.test.ts index b6e4a852..100d88a0 100644 --- a/__tests__/buildx.test.ts +++ b/__tests__/buildx.test.ts @@ -1,10 +1,19 @@ -import fs = require('fs'); -import * as buildx from '../src/buildx'; -import * as path from 'path'; +import * as fs from 'fs'; 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 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', () => { const execSpy: jest.SpyInstance = jest.spyOn(exec, 'getExecOutput'); buildx.isAvailable(); @@ -72,6 +81,29 @@ describe('inspect', () => { ); }); +describe('build', () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-buildx-')); + async function isDaemonRunning() { + return await exec + .getExecOutput(`docker`, ['version', '--format', '{{.Server.Os}}'], { + ignoreReturnCode: true, + silent: true + }) + .then(res => { + return !res.stdout.trim().includes(' ') && res.exitCode == 0; + }); + } + (isDaemonRunning() ? it : it.skip)( + 'ref https://github.com/docker/buildx.git#refs/pull/648/head', + async () => { + 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', () => { const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-buildx-')); it('acquires v0.4.1 version of buildx', async () => { diff --git a/__tests__/context.test.ts b/__tests__/context.test.ts index 7d884c65..c61ad8a4 100644 --- a/__tests__/context.test.ts +++ b/__tests__/context.test.ts @@ -1,6 +1,16 @@ +import * as fs from 'fs'; import * as os from 'os'; +import * as path from 'path'; 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', () => { it('handles single line correctly', async () => { await setInput('foo', 'bar'); diff --git a/__tests__/git.test.ts b/__tests__/git.test.ts new file mode 100644 index 00000000..1fd57ef2 --- /dev/null +++ b/__tests__/git.test.ts @@ -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'); + }); +}); diff --git a/action.yml b/action.yml index 233619a9..a731a15f 100644 --- a/action.yml +++ b/action.yml @@ -10,6 +10,9 @@ inputs: version: description: 'Buildx version. (eg. v0.3.0)' required: false + build-ref: + description: 'Build and install buildx from a Git ref' + required: false driver: description: 'Sets the builder driver to be used' default: 'docker-container' diff --git a/dist/index.js b/dist/index.js index 745e1388..90b3f308 100644 --- a/dist/index.js +++ b/dist/index.js @@ -35,12 +35,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; 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.parseVersion = exports.getVersion = exports.isAvailable = void 0; const fs = __importStar(__nccwpck_require__(5747)); const path = __importStar(__nccwpck_require__(5622)); const semver = __importStar(__nccwpck_require__(1383)); const util = __importStar(__nccwpck_require__(1669)); const context = __importStar(__nccwpck_require__(3842)); +const git = __importStar(__nccwpck_require__(3374)); const github = __importStar(__nccwpck_require__(5928)); const core = __importStar(__nccwpck_require__(2186)); const exec = __importStar(__nccwpck_require__(1514)); @@ -142,6 +143,33 @@ function inspect(name) { }); } 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', '--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) { return __awaiter(this, void 0, void 0, function* () { const release = yield github.getRelease(inputVersion); @@ -159,6 +187,12 @@ function install(inputVersion, dockerConfigHome) { } 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'); core.debug(`Plugins dir is ${pluginsDir}`); if (!fs.existsSync(pluginsDir)) { @@ -173,7 +207,6 @@ function install(inputVersion, dockerConfigHome) { return pluginPath; }); } -exports.install = install; function download(version) { return __awaiter(this, void 0, void 0, function* () { const targetFile = context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx'; @@ -286,17 +319,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.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 path_1 = __importDefault(__nccwpck_require__(5622)); const core = __importStar(__nccwpck_require__(2186)); const command_1 = __nccwpck_require__(7351); +let _tmpDir; exports.osPlat = os.platform(); 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() { return __awaiter(this, void 0, void 0, function* () { return { version: core.getInput('version'), + buildRef: core.getInput('build-ref'), driver: core.getInput('driver') || 'docker-container', driverOpts: yield getInputList('driver-opts', true), buildkitdFlags: core.getInput('buildkitd-flags') || @@ -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: /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { @@ -435,8 +542,13 @@ function run() { core.endGroup(); const inputs = yield context.getInputs(); const dockerConfigHome = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); - if (!(yield buildx.isAvailable()) || inputs.version) { - core.startGroup(`Installing buildx`); + if (inputs.buildRef) { + core.startGroup(`Build and install buildx from ${inputs.buildRef}`); + yield buildx.build(inputs.buildRef, dockerConfigHome); + core.endGroup(); + } + else if (!(yield buildx.isAvailable()) || inputs.version) { + core.startGroup(`Download and install buildx`); yield buildx.install(inputs.version || 'latest', dockerConfigHome); core.endGroup(); } diff --git a/src/buildx.ts b/src/buildx.ts index 7c2152f8..657151d3 100644 --- a/src/buildx.ts +++ b/src/buildx.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import * as semver from 'semver'; import * as util from 'util'; import * as context from './context'; +import * as git from './git'; import * as github from './github'; import * as core from '@actions/core'; import * as exec from '@actions/exec'; @@ -106,6 +107,43 @@ export async function inspect(name: string): Promise { }); } +export async function build(inputBuildRef: string, dockerConfigHome: string): Promise { + 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', '--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 { const release: github.GitHubRelease | null = await github.getRelease(inputVersion); if (!release) { @@ -124,6 +162,10 @@ export async function install(inputVersion: string, dockerConfigHome: string): P toolPath = await download(version); } + return setPlugin(toolPath, dockerConfigHome); +} + +async function setPlugin(toolPath: string, dockerConfigHome: string): Promise { const pluginsDir: string = path.join(dockerConfigHome, 'cli-plugins'); core.debug(`Plugins dir is ${pluginsDir}`); if (!fs.existsSync(pluginsDir)) { diff --git a/src/context.ts b/src/context.ts index 234ccd45..41458ffa 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,12 +1,23 @@ +import fs from 'fs'; import * as os from 'os'; +import path from 'path'; import * as core from '@actions/core'; import {issueCommand} from '@actions/core/lib/command'; +let _tmpDir: string; export const osPlat: string = os.platform(); 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 { version: string; + buildRef: string; driver: string; driverOpts: string[]; buildkitdFlags: string; @@ -19,6 +30,7 @@ export interface Inputs { export async function getInputs(): Promise { return { version: core.getInput('version'), + buildRef: core.getInput('build-ref'), driver: core.getInput('driver') || 'docker-container', driverOpts: await getInputList('driver-opts', true), buildkitdFlags: diff --git a/src/git.ts b/src/git.ts new file mode 100644 index 00000000..85d514b8 --- /dev/null +++ b/src/git.ts @@ -0,0 +1,19 @@ +import * as exec from '@actions/exec'; + +export async function getRemoteSha(repo: string, ref: string): Promise { + 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; + }); +} diff --git a/src/main.ts b/src/main.ts index 0d5d202d..7d7648da 100644 --- a/src/main.ts +++ b/src/main.ts @@ -17,8 +17,12 @@ async function run(): Promise { const inputs: context.Inputs = await context.getInputs(); const dockerConfigHome: string = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); - if (!(await buildx.isAvailable()) || inputs.version) { - core.startGroup(`Installing buildx`); + if (inputs.buildRef) { + core.startGroup(`Build and install buildx from ${inputs.buildRef}`); + await buildx.build(inputs.buildRef, dockerConfigHome); + core.endGroup(); + } else if (!(await buildx.isAvailable()) || inputs.version) { + core.startGroup(`Download and install buildx`); await buildx.install(inputs.version || 'latest', dockerConfigHome); core.endGroup(); }