mirror of
https://github.com/docker/setup-buildx-action.git
synced 2025-03-31 08:46:34 +00:00
If the action is configured to install buildx by default using the input then docker buildx sets up docker build as an alias for buildx making all docker build calls use the buildx builder instead of traditional builders. The action didn't perform cleanup in this case to uninstall the aliases which meant that any future workflows running on same GitHub Actions runner would get the buildx builders even if it did not explicitly request it. This commit tracks if the aliases were installed and removes them during post step of the action if so. Signed-off-by: Ashhar Hasan <hashhar_dev@outlook.com>
295 lines
12 KiB
TypeScript
295 lines
12 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as yaml from 'js-yaml';
|
|
import * as uuid from 'uuid';
|
|
import * as core from '@actions/core';
|
|
import * as actionsToolkit from '@docker/actions-toolkit';
|
|
|
|
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx';
|
|
import {Builder} from '@docker/actions-toolkit/lib/buildx/builder';
|
|
import {Docker} from '@docker/actions-toolkit/lib/docker/docker';
|
|
import {Exec} from '@docker/actions-toolkit/lib/exec';
|
|
import {Toolkit} from '@docker/actions-toolkit/lib/toolkit';
|
|
import {Util} from '@docker/actions-toolkit/lib/util';
|
|
|
|
import {Node} from '@docker/actions-toolkit/lib/types/buildx/builder';
|
|
import {ContextInfo} from '@docker/actions-toolkit/lib/types/docker/docker';
|
|
|
|
import * as context from './context';
|
|
import * as stateHelper from './state-helper';
|
|
|
|
actionsToolkit.run(
|
|
// main
|
|
async () => {
|
|
const inputs: context.Inputs = await context.getInputs();
|
|
stateHelper.setCleanup(inputs.cleanup);
|
|
|
|
const toolkit = new Toolkit();
|
|
const standalone = await toolkit.buildx.isStandalone();
|
|
stateHelper.setStandalone(standalone);
|
|
|
|
await core.group(`Docker info`, async () => {
|
|
try {
|
|
await Docker.printVersion();
|
|
await Docker.printInfo();
|
|
} catch (e) {
|
|
core.info(e.message);
|
|
}
|
|
});
|
|
|
|
let toolPath;
|
|
if (Util.isValidRef(inputs.version)) {
|
|
if (standalone) {
|
|
throw new Error(`Cannot build from source without the Docker CLI`);
|
|
}
|
|
await core.group(`Build buildx from source`, async () => {
|
|
toolPath = await toolkit.buildxInstall.build(inputs.version, !inputs.cacheBinary);
|
|
});
|
|
} 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', !inputs.cacheBinary);
|
|
});
|
|
}
|
|
if (toolPath) {
|
|
await core.group(`Install buildx`, async () => {
|
|
if (standalone) {
|
|
await toolkit.buildxInstall.installStandalone(toolPath);
|
|
} else {
|
|
await toolkit.buildxInstall.installPlugin(toolPath);
|
|
}
|
|
});
|
|
}
|
|
|
|
await core.group(`Buildx version`, async () => {
|
|
await toolkit.buildx.printVersion();
|
|
});
|
|
|
|
core.setOutput('name', inputs.name);
|
|
stateHelper.setBuilderName(inputs.name);
|
|
stateHelper.setBuilderDriver(inputs.driver);
|
|
|
|
fs.mkdirSync(Buildx.certsDir, {recursive: true});
|
|
stateHelper.setCertsDir(Buildx.certsDir);
|
|
|
|
// if the default context has TLS data loaded and endpoint is not set, then
|
|
// we create a temporary docker context only if driver is docker-container
|
|
// https://github.com/docker/buildx/blob/b96ad59f64d40873e4959336d294b648bb3937fe/builder/builder.go#L489
|
|
// https://github.com/docker/setup-buildx-action/issues/105
|
|
if (!standalone && inputs.driver == 'docker-container' && (await Docker.context()) == 'default' && inputs.endpoint.length == 0) {
|
|
let defaultContextWithTLS: boolean = false;
|
|
await core.group(`Inspecting default docker context`, async () => {
|
|
await Docker.getExecOutput(['context', 'inspect', '--format=json', 'default'], {
|
|
ignoreReturnCode: true,
|
|
silent: true
|
|
}).then(res => {
|
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
core.info(`Cannot inspect default docker context: ${res.stderr.trim()}`);
|
|
} else {
|
|
try {
|
|
const contextInfo = (<Array<ContextInfo>>JSON.parse(res.stdout.trim()))[0];
|
|
core.info(JSON.stringify(JSON.parse(res.stdout.trim()), undefined, 2));
|
|
const hasTLSData = Object.keys(contextInfo.Endpoints).length > 0 && Object.values(contextInfo.Endpoints)[0].TLSData !== undefined;
|
|
const hasTLSMaterial = Object.keys(contextInfo.TLSMaterial).length > 0 && Object.values(contextInfo.TLSMaterial)[0].length > 0;
|
|
defaultContextWithTLS = hasTLSData || hasTLSMaterial;
|
|
} catch (e) {
|
|
core.info(`Unable to parse default docker context info: ${e}`);
|
|
core.info(res.stdout.trim());
|
|
}
|
|
}
|
|
});
|
|
});
|
|
if (defaultContextWithTLS) {
|
|
const tmpDockerContext = `buildx-${uuid.v4()}`;
|
|
await core.group(`Creating temp docker context (TLS data loaded in default one)`, async () => {
|
|
await Docker.getExecOutput(['context', 'create', tmpDockerContext], {
|
|
ignoreReturnCode: true
|
|
}).then(res => {
|
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
core.warning(`Cannot create docker context ${tmpDockerContext}: ${res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error'}`);
|
|
} else {
|
|
core.info(`Setting builder endpoint to ${tmpDockerContext} context`);
|
|
inputs.endpoint = tmpDockerContext;
|
|
stateHelper.setTmpDockerContext(tmpDockerContext);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
if (inputs.driver !== 'docker') {
|
|
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.getExecOutput(createCmd.command, createCmd.args, {
|
|
ignoreReturnCode: true
|
|
}).then(res => {
|
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (inputs.append) {
|
|
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.getExecOutput(appendCmd.command, appendCmd.args, {
|
|
ignoreReturnCode: true
|
|
}).then(res => {
|
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
throw new Error(`Failed to append node ${node.name}: ${res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error'}`);
|
|
}
|
|
});
|
|
nodeIndex++;
|
|
}
|
|
});
|
|
}
|
|
|
|
await core.group(`Booting builder`, async () => {
|
|
const inspectCmd = await toolkit.buildx.getCommand(await context.getInspectArgs(inputs, toolkit));
|
|
await Exec.getExecOutput(inspectCmd.command, inspectCmd.args, {
|
|
ignoreReturnCode: true
|
|
}).then(res => {
|
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
|
|
}
|
|
});
|
|
});
|
|
|
|
if (inputs.install) {
|
|
if (standalone) {
|
|
throw new Error(`Cannot set buildx as default builder without the Docker CLI`);
|
|
}
|
|
await core.group(`Setting buildx as default builder`, async () => {
|
|
stateHelper.setBuildxIsDefaultBuilder(true);
|
|
const installCmd = await toolkit.buildx.getCommand(['install']);
|
|
await Exec.getExecOutput(installCmd.command, installCmd.args, {
|
|
ignoreReturnCode: true
|
|
}).then(res => {
|
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
throw new Error(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
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 && 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');
|
|
}
|
|
},
|
|
// post
|
|
async () => {
|
|
if (stateHelper.IsDebug && stateHelper.containerName.length > 0) {
|
|
await core.group(`BuildKit container logs`, async () => {
|
|
await Docker.getExecOutput(['logs', `${stateHelper.containerName}`], {
|
|
ignoreReturnCode: true
|
|
}).then(res => {
|
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
core.warning(res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (!stateHelper.cleanup) {
|
|
return;
|
|
}
|
|
|
|
if (stateHelper.builderDriver != 'docker' && stateHelper.builderName.length > 0) {
|
|
await core.group(`Removing builder`, async () => {
|
|
const buildx = new Buildx({standalone: stateHelper.standalone});
|
|
const builder = new Builder({buildx: buildx});
|
|
if (await builder.exists(stateHelper.builderName)) {
|
|
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.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error');
|
|
}
|
|
});
|
|
} else {
|
|
core.info(`${stateHelper.builderName} does not exist`);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (stateHelper.tmpDockerContext) {
|
|
await core.group(`Removing temp docker context`, async () => {
|
|
await Exec.getExecOutput('docker', ['context', 'rm', '-f', stateHelper.tmpDockerContext], {
|
|
ignoreReturnCode: true
|
|
}).then(res => {
|
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
core.warning(`${res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error'}`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
if (stateHelper.certsDir.length > 0 && fs.existsSync(stateHelper.certsDir)) {
|
|
await core.group(`Cleaning up certificates`, async () => {
|
|
fs.rmSync(stateHelper.certsDir, {recursive: true});
|
|
});
|
|
}
|
|
|
|
if (stateHelper.buildxIsDefaultBuilder) {
|
|
await core.group(`Restoring default builder`, async () => {
|
|
await Exec.getExecOutput('docker', ['buildx', 'uninstall'], {
|
|
ignoreReturnCode: true
|
|
}).then(res => {
|
|
if (res.stderr.length > 0 && res.exitCode != 0) {
|
|
core.warning(`${res.stderr.match(/(.*)\s*$/)?.[0]?.trim() ?? 'unknown error'}`);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
);
|