mirror of
https://github.com/docker/setup-buildx-action.git
synced 2025-04-23 08:26:38 +02:00
switch to actions-toolkit implementation
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
parent
2dd22fa31c
commit
987520896f
28 changed files with 209 additions and 1271 deletions
51
src/auth.ts
51
src/auth.ts
|
@ -1,51 +0,0 @@
|
|||
import * as fs from 'fs';
|
||||
|
||||
export const envPrefix = 'BUILDER_NODE';
|
||||
|
||||
export function setCredentials(credsdir: string, index: number, driver: string, endpoint: string): Array<string> {
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(endpoint);
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
switch (url.protocol) {
|
||||
case 'tcp:': {
|
||||
return setBuildKitClientCerts(credsdir, index, driver, url);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function setBuildKitClientCerts(credsdir: string, index: number, driver: string, endpoint: URL): Array<string> {
|
||||
const driverOpts: Array<string> = [];
|
||||
const buildkitCacert = process.env[`${envPrefix}_${index}_AUTH_TLS_CACERT`] || '';
|
||||
const buildkitCert = process.env[`${envPrefix}_${index}_AUTH_TLS_CERT`] || '';
|
||||
const buildkitKey = process.env[`${envPrefix}_${index}_AUTH_TLS_KEY`] || '';
|
||||
if (buildkitCacert.length == 0 && buildkitCert.length == 0 && buildkitKey.length == 0) {
|
||||
return driverOpts;
|
||||
}
|
||||
let host = endpoint.hostname;
|
||||
if (endpoint.port.length > 0) {
|
||||
host += `-${endpoint.port}`;
|
||||
}
|
||||
if (buildkitCacert.length > 0) {
|
||||
const cacertpath = `${credsdir}/cacert_${host}.pem`;
|
||||
fs.writeFileSync(cacertpath, buildkitCacert);
|
||||
driverOpts.push(`cacert=${cacertpath}`);
|
||||
}
|
||||
if (buildkitCert.length > 0) {
|
||||
const certpath = `${credsdir}/cert_${host}.pem`;
|
||||
fs.writeFileSync(certpath, buildkitCert);
|
||||
driverOpts.push(`cert=${certpath}`);
|
||||
}
|
||||
if (buildkitKey.length > 0) {
|
||||
const keypath = `${credsdir}/key_${host}.pem`;
|
||||
fs.writeFileSync(keypath, buildkitKey);
|
||||
driverOpts.push(`key=${keypath}`);
|
||||
}
|
||||
if (driver != 'remote') {
|
||||
return [];
|
||||
}
|
||||
return driverOpts;
|
||||
}
|
376
src/buildx.ts
376
src/buildx.ts
|
@ -1,376 +0,0 @@
|
|||
import * as fs from 'fs';
|
||||
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 core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import {Install as BuildxInstall} from '@docker/actions-toolkit/lib/buildx/install';
|
||||
import {GitHubRelease} from '@docker/actions-toolkit/lib/types/github';
|
||||
|
||||
export type Builder = {
|
||||
name?: string;
|
||||
driver?: string;
|
||||
nodes: Node[];
|
||||
};
|
||||
|
||||
export type Node = {
|
||||
name?: string;
|
||||
endpoint?: string;
|
||||
'driver-opts'?: Array<string>;
|
||||
status?: string;
|
||||
'buildkitd-flags'?: string;
|
||||
buildkit?: string;
|
||||
platforms?: string;
|
||||
};
|
||||
|
||||
export async function getConfigInline(s: string): Promise<string> {
|
||||
return getConfig(s, false);
|
||||
}
|
||||
|
||||
export async function getConfigFile(s: string): Promise<string> {
|
||||
return getConfig(s, true);
|
||||
}
|
||||
|
||||
export async function getConfig(s: string, file: boolean): Promise<string> {
|
||||
if (file) {
|
||||
if (!fs.existsSync(s)) {
|
||||
throw new Error(`config file ${s} not found`);
|
||||
}
|
||||
s = fs.readFileSync(s, {encoding: 'utf-8'});
|
||||
}
|
||||
const configFile = context.tmpNameSync({
|
||||
tmpdir: context.tmpDir()
|
||||
});
|
||||
fs.writeFileSync(configFile, s);
|
||||
return configFile;
|
||||
}
|
||||
|
||||
export async function isAvailable(standalone?: boolean): Promise<boolean> {
|
||||
const cmd = getCommand([], standalone);
|
||||
return await exec
|
||||
.getExecOutput(cmd.commandLine, cmd.args, {
|
||||
ignoreReturnCode: true,
|
||||
silent: true
|
||||
})
|
||||
.then(res => {
|
||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||
return false;
|
||||
}
|
||||
return res.exitCode == 0;
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.catch(error => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
export async function getVersion(standalone?: boolean): Promise<string> {
|
||||
const cmd = getCommand(['version'], standalone);
|
||||
return await exec
|
||||
.getExecOutput(cmd.commandLine, cmd.args, {
|
||||
ignoreReturnCode: true,
|
||||
silent: true
|
||||
})
|
||||
.then(res => {
|
||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||
throw new Error(res.stderr.trim());
|
||||
}
|
||||
return parseVersion(res.stdout.trim());
|
||||
});
|
||||
}
|
||||
|
||||
export function parseVersion(stdout: string): string {
|
||||
const matches = /\sv?([0-9a-f]{7}|[0-9.]+)/.exec(stdout);
|
||||
if (!matches) {
|
||||
throw new Error(`Cannot parse buildx version`);
|
||||
}
|
||||
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, standalone?: boolean): Promise<Builder> {
|
||||
const cmd = getCommand(['inspect', name], standalone);
|
||||
return await exec
|
||||
.getExecOutput(cmd.commandLine, cmd.args, {
|
||||
ignoreReturnCode: true,
|
||||
silent: true
|
||||
})
|
||||
.then(res => {
|
||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||
throw new Error(res.stderr.trim());
|
||||
}
|
||||
return parseInspect(res.stdout);
|
||||
});
|
||||
}
|
||||
|
||||
export async function parseInspect(data: string): Promise<Builder> {
|
||||
const builder: Builder = {
|
||||
nodes: []
|
||||
};
|
||||
let node: Node = {};
|
||||
for (const line of data.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.toLowerCase()) {
|
||||
case 'name': {
|
||||
if (builder.name == undefined) {
|
||||
builder.name = value;
|
||||
} else {
|
||||
if (Object.keys(node).length > 0) {
|
||||
builder.nodes.push(node);
|
||||
node = {};
|
||||
}
|
||||
node.name = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'driver': {
|
||||
builder.driver = value;
|
||||
break;
|
||||
}
|
||||
case 'endpoint': {
|
||||
node.endpoint = value;
|
||||
break;
|
||||
}
|
||||
case 'driver options': {
|
||||
node['driver-opts'] = (value.match(/(\w+)="([^"]*)"/g) || []).map(v => v.replace(/^(.*)="(.*)"$/g, '$1=$2'));
|
||||
break;
|
||||
}
|
||||
case 'status': {
|
||||
node.status = value;
|
||||
break;
|
||||
}
|
||||
case 'flags': {
|
||||
node['buildkitd-flags'] = value;
|
||||
break;
|
||||
}
|
||||
case 'buildkit': {
|
||||
node.buildkit = value;
|
||||
break;
|
||||
}
|
||||
case 'platforms': {
|
||||
let platforms: Array<string> = [];
|
||||
// if a preferred platform is being set then use only these
|
||||
// https://docs.docker.com/engine/reference/commandline/buildx_inspect/#get-information-about-a-builder-instance
|
||||
if (value.includes('*')) {
|
||||
for (const platform of value.split(', ')) {
|
||||
if (platform.includes('*')) {
|
||||
platforms.push(platform.replace('*', ''));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// otherwise set all platforms available
|
||||
platforms = value.split(', ');
|
||||
}
|
||||
node.platforms = platforms.join(',');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(node).length > 0) {
|
||||
builder.nodes.push(node);
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
|
||||
export async function build(inputBuildRef: string, dest: string, standalone: boolean): Promise<string> {
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [repo, ref] = inputBuildRef.split('#');
|
||||
if (ref.length == 0) {
|
||||
ref = 'master';
|
||||
}
|
||||
|
||||
let vspec: string;
|
||||
if (ref.match(/^[0-9a-fA-F]{40}$/)) {
|
||||
vspec = ref;
|
||||
} else {
|
||||
vspec = await git.getRemoteSha(repo, ref);
|
||||
}
|
||||
core.debug(`Tool version spec ${vspec}`);
|
||||
|
||||
let toolPath: string;
|
||||
toolPath = tc.find('buildx', vspec);
|
||||
if (!toolPath) {
|
||||
const outFolder = path.join(context.tmpDir(), 'out').split(path.sep).join(path.posix.sep);
|
||||
let buildWithStandalone = false;
|
||||
const standaloneFound = await isAvailable(true);
|
||||
const pluginFound = await isAvailable(false);
|
||||
if (standalone && standaloneFound) {
|
||||
core.debug(`Buildx standalone found, build with it`);
|
||||
buildWithStandalone = true;
|
||||
} else if (!standalone && pluginFound) {
|
||||
core.debug(`Buildx plugin found, build with it`);
|
||||
buildWithStandalone = false;
|
||||
} else if (standaloneFound) {
|
||||
core.debug(`Buildx plugin not found, but standalone found so trying to build with it`);
|
||||
buildWithStandalone = true;
|
||||
} else if (pluginFound) {
|
||||
core.debug(`Buildx standalone not found, but plugin found so trying to build with it`);
|
||||
buildWithStandalone = false;
|
||||
} else {
|
||||
throw new Error(`Neither buildx standalone or plugin have been found to build from ref`);
|
||||
}
|
||||
const buildCmd = getCommand(['build', '--target', 'binaries', '--build-arg', 'BUILDKIT_CONTEXT_KEEP_GIT_DIR=1', '--output', `type=local,dest=${outFolder}`, inputBuildRef], buildWithStandalone);
|
||||
toolPath = await exec
|
||||
.getExecOutput(buildCmd.commandLine, buildCmd.args, {
|
||||
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', vspec);
|
||||
});
|
||||
}
|
||||
|
||||
if (standalone) {
|
||||
return setStandalone(toolPath, dest);
|
||||
}
|
||||
return setPlugin(toolPath, dest);
|
||||
}
|
||||
|
||||
export async function install(inputVersion: string, dest: string, standalone: boolean): Promise<string> {
|
||||
const release: GitHubRelease = await BuildxInstall.getRelease(inputVersion);
|
||||
core.debug(`Release ${release.tag_name} found`);
|
||||
const version = release.tag_name.replace(/^v+|v+$/g, '');
|
||||
|
||||
let toolPath: string;
|
||||
toolPath = tc.find('buildx', version);
|
||||
if (!toolPath) {
|
||||
const c = semver.clean(version) || '';
|
||||
if (!semver.valid(c)) {
|
||||
throw new Error(`Invalid Buildx version "${version}".`);
|
||||
}
|
||||
toolPath = await download(version);
|
||||
}
|
||||
|
||||
if (standalone) {
|
||||
return setStandalone(toolPath, dest);
|
||||
}
|
||||
return setPlugin(toolPath, dest);
|
||||
}
|
||||
|
||||
async function setStandalone(toolPath: string, dest: string): Promise<string> {
|
||||
core.info('Standalone mode');
|
||||
const toolBinPath = path.join(toolPath, context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx');
|
||||
|
||||
const binDir = path.join(dest, 'bin');
|
||||
core.debug(`Bin dir is ${binDir}`);
|
||||
if (!fs.existsSync(binDir)) {
|
||||
fs.mkdirSync(binDir, {recursive: true});
|
||||
}
|
||||
|
||||
const filename: string = context.osPlat == 'win32' ? 'buildx.exe' : 'buildx';
|
||||
const buildxPath: string = path.join(binDir, filename);
|
||||
core.debug(`Bin path is ${buildxPath}`);
|
||||
fs.copyFileSync(toolBinPath, buildxPath);
|
||||
|
||||
core.info('Fixing perms');
|
||||
fs.chmodSync(buildxPath, '0755');
|
||||
|
||||
core.addPath(binDir);
|
||||
core.info('Added buildx to the path');
|
||||
|
||||
return buildxPath;
|
||||
}
|
||||
|
||||
async function setPlugin(toolPath: string, dockerConfigHome: string): Promise<string> {
|
||||
core.info('Docker plugin mode');
|
||||
const toolBinPath = path.join(toolPath, context.osPlat == 'win32' ? 'docker-buildx.exe' : 'docker-buildx');
|
||||
|
||||
const pluginsDir: string = path.join(dockerConfigHome, 'cli-plugins');
|
||||
core.debug(`Plugins dir is ${pluginsDir}`);
|
||||
if (!fs.existsSync(pluginsDir)) {
|
||||
fs.mkdirSync(pluginsDir, {recursive: true});
|
||||
}
|
||||
|
||||
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(toolBinPath, pluginPath);
|
||||
|
||||
core.info('Fixing perms');
|
||||
fs.chmodSync(pluginPath, '0755');
|
||||
|
||||
return pluginPath;
|
||||
}
|
||||
|
||||
async function download(version: string): Promise<string> {
|
||||
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, await filename(version));
|
||||
core.info(`Downloading ${downloadUrl}`);
|
||||
const downloadPath = await tc.downloadTool(downloadUrl);
|
||||
core.debug(`Downloaded to ${downloadPath}`);
|
||||
return await tc.cacheFile(downloadPath, targetFile, 'buildx', version);
|
||||
}
|
||||
|
||||
async function filename(version: string): Promise<string> {
|
||||
let arch: string;
|
||||
switch (context.osArch) {
|
||||
case 'x64': {
|
||||
arch = 'amd64';
|
||||
break;
|
||||
}
|
||||
case 'ppc64': {
|
||||
arch = 'ppc64le';
|
||||
break;
|
||||
}
|
||||
case 'arm': {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const arm_version = (process.config.variables as any).arm_version;
|
||||
arch = arm_version ? 'arm-v' + arm_version : 'arm';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
arch = context.osArch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const platform: string = context.osPlat == 'win32' ? 'windows' : context.osPlat;
|
||||
const ext: string = context.osPlat == 'win32' ? '.exe' : '';
|
||||
return util.format('buildx-v%s.%s-%s%s', version, platform, arch, ext);
|
||||
}
|
||||
|
||||
export async function getBuildKitVersion(containerID: string): Promise<string> {
|
||||
return exec
|
||||
.getExecOutput(`docker`, ['inspect', '--format', '{{.Config.Image}}', containerID], {
|
||||
ignoreReturnCode: true,
|
||||
silent: true
|
||||
})
|
||||
.then(bkitimage => {
|
||||
if (bkitimage.exitCode == 0 && bkitimage.stdout.length > 0) {
|
||||
return exec
|
||||
.getExecOutput(`docker`, ['run', '--rm', bkitimage.stdout.trim(), '--version'], {
|
||||
ignoreReturnCode: true,
|
||||
silent: true
|
||||
})
|
||||
.then(bkitversion => {
|
||||
if (bkitversion.exitCode == 0 && bkitversion.stdout.length > 0) {
|
||||
return `${bkitimage.stdout.trim()} => ${bkitversion.stdout.trim()}`;
|
||||
} else if (bkitversion.stderr.length > 0) {
|
||||
core.warning(bkitversion.stderr.trim());
|
||||
}
|
||||
return bkitversion.stdout.trim();
|
||||
});
|
||||
} else if (bkitimage.stderr.length > 0) {
|
||||
core.warning(bkitimage.stderr.trim());
|
||||
}
|
||||
return bkitimage.stdout.trim();
|
||||
});
|
||||
}
|
||||
|
||||
export function getCommand(args: Array<string>, standalone?: boolean) {
|
||||
return {
|
||||
commandLine: standalone ? 'buildx' : 'docker',
|
||||
args: standalone ? args : ['buildx', ...args]
|
||||
};
|
||||
}
|
|
@ -1,27 +1,10 @@
|
|||
import fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import path from 'path';
|
||||
import * as tmp from 'tmp';
|
||||
import * as uuid from 'uuid';
|
||||
import {parse} from 'csv-parse/sync';
|
||||
import * as buildx from './buildx';
|
||||
import * as nodes from './nodes';
|
||||
import * as core from '@actions/core';
|
||||
import {Util} from '@docker/actions-toolkit/lib/util';
|
||||
import {Toolkit} from '@docker/actions-toolkit/lib/toolkit';
|
||||
import {Node} from '@docker/actions-toolkit/lib/types/builder';
|
||||
|
||||
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 function tmpNameSync(options?: tmp.TmpNameOptions): string {
|
||||
return tmp.tmpNameSync(options);
|
||||
}
|
||||
export const builderNodeEnvPrefix = 'BUILDER_NODE';
|
||||
|
||||
export interface Inputs {
|
||||
version: string;
|
||||
|
@ -43,9 +26,9 @@ export async function getInputs(): Promise<Inputs> {
|
|||
version: core.getInput('version'),
|
||||
name: getBuilderName(core.getInput('driver') || 'docker-container'),
|
||||
driver: core.getInput('driver') || 'docker-container',
|
||||
driverOpts: await getInputList('driver-opts', true),
|
||||
driverOpts: Util.getInputList('driver-opts', {ignoreComma: true, quote: false}),
|
||||
buildkitdFlags: core.getInput('buildkitd-flags') || '--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host',
|
||||
platforms: await getInputList('platforms', false, true),
|
||||
platforms: Util.getInputList('platforms'),
|
||||
install: core.getBooleanInput('install'),
|
||||
use: core.getBooleanInput('use'),
|
||||
endpoint: core.getInput('endpoint'),
|
||||
|
@ -59,10 +42,10 @@ export function getBuilderName(driver: string): string {
|
|||
return driver == 'docker' ? 'default' : `builder-${uuid.v4()}`;
|
||||
}
|
||||
|
||||
export async function getCreateArgs(inputs: Inputs, buildxVersion: string): Promise<Array<string>> {
|
||||
export async function getCreateArgs(inputs: Inputs, toolkit: Toolkit): Promise<Array<string>> {
|
||||
const args: Array<string> = ['create', '--name', inputs.name, '--driver', inputs.driver];
|
||||
if (buildx.satisfies(buildxVersion, '>=0.3.0')) {
|
||||
await asyncForEach(inputs.driverOpts, async driverOpt => {
|
||||
if (await toolkit.buildx.versionSatisfies('>=0.3.0')) {
|
||||
await Util.asyncForEach(inputs.driverOpts, async driverOpt => {
|
||||
args.push('--driver-opt', driverOpt);
|
||||
});
|
||||
if (inputs.driver != 'remote' && inputs.buildkitdFlags) {
|
||||
|
@ -77,9 +60,9 @@ export async function getCreateArgs(inputs: Inputs, buildxVersion: string): Prom
|
|||
}
|
||||
if (inputs.driver != 'remote') {
|
||||
if (inputs.config) {
|
||||
args.push('--config', await buildx.getConfigFile(inputs.config));
|
||||
args.push('--config', toolkit.buildkit.config.resolveFromFile(inputs.config));
|
||||
} else if (inputs.configInline) {
|
||||
args.push('--config', await buildx.getConfigInline(inputs.configInline));
|
||||
args.push('--config', toolkit.buildkit.config.resolveFromString(inputs.configInline));
|
||||
}
|
||||
}
|
||||
if (inputs.endpoint) {
|
||||
|
@ -88,13 +71,13 @@ export async function getCreateArgs(inputs: Inputs, buildxVersion: string): Prom
|
|||
return args;
|
||||
}
|
||||
|
||||
export async function getAppendArgs(inputs: Inputs, node: nodes.Node, buildxVersion: string): Promise<Array<string>> {
|
||||
export async function getAppendArgs(inputs: Inputs, node: Node, toolkit: Toolkit): Promise<Array<string>> {
|
||||
const args: Array<string> = ['create', '--name', inputs.name, '--append'];
|
||||
if (node.name) {
|
||||
args.push('--node', node.name);
|
||||
}
|
||||
if (node['driver-opts'] && buildx.satisfies(buildxVersion, '>=0.3.0')) {
|
||||
await asyncForEach(node['driver-opts'], async driverOpt => {
|
||||
if (node['driver-opts'] && (await toolkit.buildx.versionSatisfies('>=0.3.0'))) {
|
||||
await Util.asyncForEach(node['driver-opts'], async driverOpt => {
|
||||
args.push('--driver-opt', driverOpt);
|
||||
});
|
||||
if (inputs.driver != 'remote' && node['buildkitd-flags']) {
|
||||
|
@ -110,47 +93,10 @@ export async function getAppendArgs(inputs: Inputs, node: nodes.Node, buildxVers
|
|||
return args;
|
||||
}
|
||||
|
||||
export async function getInspectArgs(inputs: Inputs, buildxVersion: string): Promise<Array<string>> {
|
||||
export async function getInspectArgs(inputs: Inputs, toolkit: Toolkit): Promise<Array<string>> {
|
||||
const args: Array<string> = ['inspect', '--bootstrap'];
|
||||
if (buildx.satisfies(buildxVersion, '>=0.4.0')) {
|
||||
if (await toolkit.buildx.versionSatisfies('>=0.4.0')) {
|
||||
args.push('--builder', inputs.name);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
export async function getInputList(name: string, ignoreComma?: boolean, escapeQuotes?: boolean): Promise<string[]> {
|
||||
const res: Array<string> = [];
|
||||
|
||||
const items = core.getInput(name);
|
||||
if (items == '') {
|
||||
return res;
|
||||
}
|
||||
|
||||
const records = parse(items, {
|
||||
columns: false,
|
||||
relaxQuotes: true,
|
||||
comment: '#',
|
||||
relaxColumnCount: true,
|
||||
skipEmptyLines: true,
|
||||
quote: escapeQuotes ? `"` : false
|
||||
});
|
||||
|
||||
for (const record of records as Array<string[]>) {
|
||||
if (record.length == 1) {
|
||||
res.push(record[0]);
|
||||
continue;
|
||||
} else if (!ignoreComma) {
|
||||
res.push(...record);
|
||||
continue;
|
||||
}
|
||||
res.push(record.join(','));
|
||||
}
|
||||
|
||||
return res.filter(item => item).map(pat => pat.trim());
|
||||
}
|
||||
|
||||
export const asyncForEach = async (array, callback) => {
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
await callback(array[index], index, array);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import * as exec from '@actions/exec';
|
||||
|
||||
export async function isAvailable(): Promise<boolean> {
|
||||
return await exec
|
||||
.getExecOutput('docker', undefined, {
|
||||
ignoreReturnCode: true,
|
||||
silent: true
|
||||
})
|
||||
.then(res => {
|
||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||
return false;
|
||||
}
|
||||
return res.exitCode == 0;
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.catch(error => {
|
||||
return false;
|
||||
});
|
||||
}
|
19
src/git.ts
19
src/git.ts
|
@ -1,19 +0,0 @@
|
|||
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] = res.stdout.trim().split(/[\s\t]/);
|
||||
if (rsha.length == 0) {
|
||||
throw new Error(`Cannot find remote ref for ${repo}#${ref}`);
|
||||
}
|
||||
return rsha;
|
||||
});
|
||||
}
|
277
src/main.ts
277
src/main.ts
|
@ -1,182 +1,189 @@
|
|||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as auth from './auth';
|
||||
import * as buildx from './buildx';
|
||||
import * as context from './context';
|
||||
import * as docker from './docker';
|
||||
import * as nodes from './nodes';
|
||||
import * as stateHelper from './state-helper';
|
||||
import * as util from './util';
|
||||
import * as yaml from 'js-yaml';
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as actionsToolkit from '@docker/actions-toolkit';
|
||||
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx';
|
||||
import {Docker} from '@docker/actions-toolkit/lib/docker';
|
||||
import {Toolkit} from '@docker/actions-toolkit/lib/toolkit';
|
||||
import {Util} from '@docker/actions-toolkit/lib/util';
|
||||
import {Node} from '@docker/actions-toolkit/lib/types/builder';
|
||||
|
||||
async function run(): Promise<void> {
|
||||
try {
|
||||
import * as context from './context';
|
||||
import * as stateHelper from './state-helper';
|
||||
|
||||
actionsToolkit.run(
|
||||
// main
|
||||
async () => {
|
||||
const inputs: context.Inputs = await context.getInputs();
|
||||
const dockerConfigHome: string = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker');
|
||||
const toolkit = new Toolkit();
|
||||
|
||||
// standalone if docker cli not available
|
||||
const standalone = !(await docker.isAvailable());
|
||||
const standalone = await toolkit.buildx.isStandalone();
|
||||
stateHelper.setStandalone(standalone);
|
||||
|
||||
core.startGroup(`Docker info`);
|
||||
if (standalone) {
|
||||
core.info(`Docker info skipped in standalone mode`);
|
||||
} else {
|
||||
await exec.exec('docker', ['version'], {
|
||||
failOnStdErr: false
|
||||
});
|
||||
await exec.exec('docker', ['info'], {
|
||||
failOnStdErr: false
|
||||
});
|
||||
}
|
||||
core.endGroup();
|
||||
await core.group(`Docker info`, async () => {
|
||||
try {
|
||||
await Docker.printVersion();
|
||||
await Docker.printInfo();
|
||||
} catch (e) {
|
||||
core.info(e.message);
|
||||
}
|
||||
});
|
||||
|
||||
if (util.isValidUrl(inputs.version)) {
|
||||
let toolPath;
|
||||
if (Util.isValidUrl(inputs.version)) {
|
||||
if (standalone) {
|
||||
throw new Error(`Cannot build from source without the Docker CLI`);
|
||||
}
|
||||
core.startGroup(`Build and install buildx`);
|
||||
await buildx.build(inputs.version, dockerConfigHome, standalone);
|
||||
core.endGroup();
|
||||
} else if (!(await buildx.isAvailable(standalone)) || inputs.version) {
|
||||
core.startGroup(`Download and install buildx`);
|
||||
await buildx.install(inputs.version || 'latest', standalone ? context.tmpDir() : dockerConfigHome, standalone);
|
||||
core.endGroup();
|
||||
await core.group(`Build buildx from source`, async () => {
|
||||
toolPath = await toolkit.buildxInstall.build(inputs.version);
|
||||
});
|
||||
} else if (!(await toolkit.buildx.isAvailable()) || inputs.version) {
|
||||
await core.group(`Download buildx from GitHub Releases`, async () => {
|
||||
toolPath = await toolkit.buildxInstall.download(inputs.version || 'latest');
|
||||
});
|
||||
}
|
||||
if (toolPath) {
|
||||
await core.group(`Install buildx`, async () => {
|
||||
if (standalone) {
|
||||
await toolkit.buildxInstall.installStandalone(toolPath);
|
||||
} else {
|
||||
await toolkit.buildxInstall.installPlugin(toolPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const buildxVersion = await buildx.getVersion(standalone);
|
||||
await core.group(`Buildx version`, async () => {
|
||||
const versionCmd = buildx.getCommand(['version'], standalone);
|
||||
await exec.exec(versionCmd.commandLine, versionCmd.args, {
|
||||
failOnStdErr: false
|
||||
});
|
||||
await toolkit.buildx.printVersion();
|
||||
});
|
||||
|
||||
core.setOutput('name', inputs.name);
|
||||
stateHelper.setBuilderName(inputs.name);
|
||||
|
||||
const credsdir = path.join(dockerConfigHome, 'buildx', 'creds', inputs.name);
|
||||
fs.mkdirSync(credsdir, {recursive: true});
|
||||
stateHelper.setCredsDir(credsdir);
|
||||
fs.mkdirSync(Buildx.certsDir, {recursive: true});
|
||||
stateHelper.setCertsDir(Buildx.certsDir);
|
||||
|
||||
if (inputs.driver !== 'docker') {
|
||||
core.startGroup(`Creating a new builder instance`);
|
||||
const authOpts = auth.setCredentials(credsdir, 0, inputs.driver, inputs.endpoint);
|
||||
if (authOpts.length > 0) {
|
||||
inputs.driverOpts = [...inputs.driverOpts, ...authOpts];
|
||||
}
|
||||
const createCmd = buildx.getCommand(await context.getCreateArgs(inputs, buildxVersion), standalone);
|
||||
await exec.exec(createCmd.commandLine, createCmd.args);
|
||||
core.endGroup();
|
||||
await core.group(`Creating a new builder instance`, async () => {
|
||||
const certsDriverOpts = Buildx.resolveCertsDriverOpts(inputs.driver, inputs.endpoint, {
|
||||
cacert: process.env[`${context.builderNodeEnvPrefix}_0_AUTH_TLS_CACERT`],
|
||||
cert: process.env[`${context.builderNodeEnvPrefix}_0_AUTH_TLS_CERT`],
|
||||
key: process.env[`${context.builderNodeEnvPrefix}_0_AUTH_TLS_KEY`]
|
||||
});
|
||||
if (certsDriverOpts.length > 0) {
|
||||
inputs.driverOpts = [...inputs.driverOpts, ...certsDriverOpts];
|
||||
}
|
||||
const createCmd = await toolkit.buildx.getCommand(await context.getCreateArgs(inputs, toolkit));
|
||||
await exec.exec(createCmd.command, createCmd.args);
|
||||
});
|
||||
}
|
||||
|
||||
if (inputs.append) {
|
||||
core.startGroup(`Appending node(s) to builder`);
|
||||
let nodeIndex = 1;
|
||||
for (const node of nodes.Parse(inputs.append)) {
|
||||
const authOpts = auth.setCredentials(credsdir, nodeIndex, inputs.driver, node.endpoint || '');
|
||||
if (authOpts.length > 0) {
|
||||
node['driver-opts'] = [...(node['driver-opts'] || []), ...authOpts];
|
||||
await core.group(`Appending node(s) to builder`, async () => {
|
||||
let nodeIndex = 1;
|
||||
const nodes = yaml.load(inputs.append) as Node[];
|
||||
for (const node of nodes) {
|
||||
const certsDriverOpts = Buildx.resolveCertsDriverOpts(inputs.driver, `${node.endpoint}`, {
|
||||
cacert: process.env[`${context.builderNodeEnvPrefix}_${nodeIndex}_AUTH_TLS_CACERT`],
|
||||
cert: process.env[`${context.builderNodeEnvPrefix}_${nodeIndex}_AUTH_TLS_CERT`],
|
||||
key: process.env[`${context.builderNodeEnvPrefix}_${nodeIndex}_AUTH_TLS_KEY`]
|
||||
});
|
||||
if (certsDriverOpts.length > 0) {
|
||||
node['driver-opts'] = [...(node['driver-opts'] || []), ...certsDriverOpts];
|
||||
}
|
||||
const appendCmd = await toolkit.buildx.getCommand(await context.getAppendArgs(inputs, node, toolkit));
|
||||
await exec.exec(appendCmd.command, appendCmd.args);
|
||||
nodeIndex++;
|
||||
}
|
||||
const appendCmd = buildx.getCommand(await context.getAppendArgs(inputs, node, buildxVersion), standalone);
|
||||
await exec.exec(appendCmd.commandLine, appendCmd.args);
|
||||
nodeIndex++;
|
||||
}
|
||||
core.endGroup();
|
||||
});
|
||||
}
|
||||
|
||||
core.startGroup(`Booting builder`);
|
||||
const inspectCmd = buildx.getCommand(await context.getInspectArgs(inputs, buildxVersion), standalone);
|
||||
await exec.exec(inspectCmd.commandLine, inspectCmd.args);
|
||||
core.endGroup();
|
||||
await core.group(`Booting builder`, async () => {
|
||||
const inspectCmd = await toolkit.buildx.getCommand(await context.getInspectArgs(inputs, toolkit));
|
||||
await exec.exec(inspectCmd.command, inspectCmd.args);
|
||||
});
|
||||
|
||||
if (inputs.install) {
|
||||
if (standalone) {
|
||||
throw new Error(`Cannot set buildx as default builder without the Docker CLI`);
|
||||
}
|
||||
core.startGroup(`Setting buildx as default builder`);
|
||||
await exec.exec('docker', ['buildx', 'install']);
|
||||
core.endGroup();
|
||||
await core.group(`Setting buildx as default builder`, async () => {
|
||||
const installCmd = await toolkit.buildx.getCommand(['install']);
|
||||
await exec.exec(installCmd.command, installCmd.args);
|
||||
});
|
||||
}
|
||||
|
||||
core.startGroup(`Inspect builder`);
|
||||
const builder = await buildx.inspect(inputs.name, standalone);
|
||||
const firstNode = builder.nodes[0];
|
||||
const reducedPlatforms: Array<string> = [];
|
||||
for (const node of builder.nodes) {
|
||||
for (const platform of node.platforms?.split(',') || []) {
|
||||
if (reducedPlatforms.indexOf(platform) > -1) {
|
||||
continue;
|
||||
const builderInspect = await toolkit.builder.inspect(inputs.name);
|
||||
const firstNode = builderInspect.nodes[0];
|
||||
|
||||
await core.group(`Inspect builder`, async () => {
|
||||
const reducedPlatforms: Array<string> = [];
|
||||
for (const node of builderInspect.nodes) {
|
||||
for (const platform of node.platforms?.split(',') || []) {
|
||||
if (reducedPlatforms.indexOf(platform) > -1) {
|
||||
continue;
|
||||
}
|
||||
reducedPlatforms.push(platform);
|
||||
}
|
||||
reducedPlatforms.push(platform);
|
||||
}
|
||||
}
|
||||
core.info(JSON.stringify(builder, undefined, 2));
|
||||
core.setOutput('driver', builder.driver);
|
||||
core.setOutput('platforms', reducedPlatforms.join(','));
|
||||
core.setOutput('nodes', JSON.stringify(builder.nodes, undefined, 2));
|
||||
core.setOutput('endpoint', firstNode.endpoint); // TODO: deprecated, to be removed in a later version
|
||||
core.setOutput('status', firstNode.status); // TODO: deprecated, to be removed in a later version
|
||||
core.setOutput('flags', firstNode['buildkitd-flags']); // TODO: deprecated, to be removed in a later version
|
||||
core.endGroup();
|
||||
core.info(JSON.stringify(builderInspect, undefined, 2));
|
||||
core.setOutput('driver', builderInspect.driver);
|
||||
core.setOutput('platforms', reducedPlatforms.join(','));
|
||||
core.setOutput('nodes', JSON.stringify(builderInspect.nodes, undefined, 2));
|
||||
core.setOutput('endpoint', firstNode.endpoint); // TODO: deprecated, to be removed in a later version
|
||||
core.setOutput('status', firstNode.status); // TODO: deprecated, to be removed in a later version
|
||||
core.setOutput('flags', firstNode['buildkitd-flags']); // TODO: deprecated, to be removed in a later version
|
||||
});
|
||||
|
||||
if (!standalone && builder.driver == 'docker-container') {
|
||||
stateHelper.setContainerName(`buildx_buildkit_${firstNode.name}`);
|
||||
core.startGroup(`BuildKit version`);
|
||||
for (const node of builder.nodes) {
|
||||
const bkvers = await buildx.getBuildKitVersion(`buildx_buildkit_${node.name}`);
|
||||
core.info(`${node.name}: ${bkvers}`);
|
||||
}
|
||||
core.endGroup();
|
||||
if (!standalone && builderInspect.driver == 'docker-container') {
|
||||
stateHelper.setContainerName(`${Buildx.containerNamePrefix}${firstNode.name}`);
|
||||
await core.group(`BuildKit version`, async () => {
|
||||
for (const node of builderInspect.nodes) {
|
||||
const buildkitVersion = await toolkit.buildkit.getVersion(node);
|
||||
core.info(`${node.name}: ${buildkitVersion}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (core.isDebug() || firstNode['buildkitd-flags']?.includes('--debug')) {
|
||||
stateHelper.setDebug('true');
|
||||
}
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanup(): Promise<void> {
|
||||
if (stateHelper.IsDebug && stateHelper.containerName.length > 0) {
|
||||
core.startGroup(`BuildKit container logs`);
|
||||
await exec
|
||||
.getExecOutput('docker', ['logs', `${stateHelper.containerName}`], {
|
||||
ignoreReturnCode: true
|
||||
})
|
||||
.then(res => {
|
||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||
core.warning(res.stderr.trim());
|
||||
}
|
||||
},
|
||||
// post
|
||||
async () => {
|
||||
if (stateHelper.IsDebug && stateHelper.containerName.length > 0) {
|
||||
await core.group(`BuildKit container logs`, async () => {
|
||||
await exec
|
||||
.getExecOutput('docker', ['logs', `${stateHelper.containerName}`], {
|
||||
ignoreReturnCode: true
|
||||
})
|
||||
.then(res => {
|
||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||
core.warning(res.stderr.trim());
|
||||
}
|
||||
});
|
||||
});
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
if (stateHelper.builderName.length > 0) {
|
||||
core.startGroup(`Removing builder`);
|
||||
const rmCmd = buildx.getCommand(['rm', stateHelper.builderName], /true/i.test(stateHelper.standalone));
|
||||
await exec
|
||||
.getExecOutput(rmCmd.commandLine, rmCmd.args, {
|
||||
ignoreReturnCode: true
|
||||
})
|
||||
.then(res => {
|
||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||
core.warning(res.stderr.trim());
|
||||
}
|
||||
if (stateHelper.builderName.length > 0) {
|
||||
await core.group(`Removing builder`, async () => {
|
||||
const buildx = new Buildx({standalone: /true/i.test(stateHelper.standalone)});
|
||||
const rmCmd = await buildx.getCommand(['rm', stateHelper.builderName]);
|
||||
await exec
|
||||
.getExecOutput(rmCmd.command, rmCmd.args, {
|
||||
ignoreReturnCode: true
|
||||
})
|
||||
.then(res => {
|
||||
if (res.stderr.length > 0 && res.exitCode != 0) {
|
||||
core.warning(res.stderr.trim());
|
||||
}
|
||||
});
|
||||
});
|
||||
core.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
if (stateHelper.credsDir.length > 0 && fs.existsSync(stateHelper.credsDir)) {
|
||||
core.info(`Cleaning up credentials`);
|
||||
fs.rmSync(stateHelper.credsDir, {recursive: true});
|
||||
if (stateHelper.certsDir.length > 0 && fs.existsSync(stateHelper.certsDir)) {
|
||||
await core.group(`Cleaning up certificates`, async () => {
|
||||
fs.rmSync(stateHelper.certsDir, {recursive: true});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateHelper.IsPost) {
|
||||
run();
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
);
|
||||
|
|
13
src/nodes.ts
13
src/nodes.ts
|
@ -1,13 +0,0 @@
|
|||
import * as yaml from 'js-yaml';
|
||||
|
||||
export type Node = {
|
||||
name?: string;
|
||||
endpoint?: string;
|
||||
'driver-opts'?: Array<string>;
|
||||
'buildkitd-flags'?: string;
|
||||
platforms?: string;
|
||||
};
|
||||
|
||||
export function Parse(data: string): Node[] {
|
||||
return yaml.load(data) as Node[];
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import * as core from '@actions/core';
|
||||
|
||||
export const IsPost = !!process.env['STATE_isPost'];
|
||||
export const IsDebug = !!process.env['STATE_isDebug'];
|
||||
export const standalone = process.env['STATE_standalone'] || '';
|
||||
export const builderName = process.env['STATE_builderName'] || '';
|
||||
export const containerName = process.env['STATE_containerName'] || '';
|
||||
export const credsDir = process.env['STATE_credsDir'] || '';
|
||||
export const certsDir = process.env['STATE_certsDir'] || '';
|
||||
|
||||
export function setDebug(debug: string) {
|
||||
core.saveState('isDebug', debug);
|
||||
|
@ -23,10 +22,6 @@ export function setContainerName(containerName: string) {
|
|||
core.saveState('containerName', containerName);
|
||||
}
|
||||
|
||||
export function setCredsDir(credsDir: string) {
|
||||
core.saveState('credsDir', credsDir);
|
||||
}
|
||||
|
||||
if (!IsPost) {
|
||||
core.saveState('isPost', 'true');
|
||||
export function setCertsDir(certsDir: string) {
|
||||
core.saveState('certsDir', certsDir);
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
export function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
new URL(url);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue