|
|
|
|
@ -16,15 +16,122 @@ import {Util} from '@docker/actions-toolkit/lib/util';
|
|
|
|
|
import {BuilderInfo} from '@docker/actions-toolkit/lib/types/buildx/builder';
|
|
|
|
|
import {ConfigFile} from '@docker/actions-toolkit/lib/types/docker/docker';
|
|
|
|
|
import {UploadArtifactResponse} from '@docker/actions-toolkit/lib/types/github';
|
|
|
|
|
import axios, {AxiosInstance} from 'axios';
|
|
|
|
|
|
|
|
|
|
import * as context from './context';
|
|
|
|
|
|
|
|
|
|
const buildxVersion = 'v0.17.0';
|
|
|
|
|
|
|
|
|
|
async function getBlacksmithHttpClient(): Promise<AxiosInstance> {
|
|
|
|
|
return axios.create({
|
|
|
|
|
baseURL: process.env.BUILDER_URL || 'https://d04fa050a7b2.ngrok.app/build_tasks',
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${process.env.BLACKSMITH_ANVIL_TOKEN}`
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function reportBuildCompleted() {
|
|
|
|
|
try {
|
|
|
|
|
const client = await getBlacksmithHttpClient();
|
|
|
|
|
const response = await client.post(`/${stateHelper.blacksmithBuildTaskId}/complete`);
|
|
|
|
|
core.info(`Blacksmith builder ${stateHelper.blacksmithBuildTaskId} completed: ${JSON.stringify(response.data)}`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
core.warning('Error completing Blacksmith build:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function reportBuildFailed() {
|
|
|
|
|
try {
|
|
|
|
|
const client = await getBlacksmithHttpClient();
|
|
|
|
|
const response = await client.post(`/${stateHelper.blacksmithBuildTaskId}/fail`);
|
|
|
|
|
core.info(`Docker build failed, tearing down Blacksmith builder for ${stateHelper.blacksmithBuildTaskId}: ${JSON.stringify(response.data)}`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
core.warning('Error completing Blacksmith build:', error);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// getRemoteBuilderAddr resolves the address to a remote Docker builder.
|
|
|
|
|
// If it is unable to do so because of a timeout or an error it returns null.
|
|
|
|
|
async function getRemoteBuilderAddr(inputs: context.Inputs): Promise<string | null> {
|
|
|
|
|
const controller = new AbortController();
|
|
|
|
|
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
|
|
|
|
try {
|
|
|
|
|
const client = await getBlacksmithHttpClient();
|
|
|
|
|
const dockerfilePath = context.getDockerfilePath(inputs);
|
|
|
|
|
let payload = {};
|
|
|
|
|
if (dockerfilePath && dockerfilePath.length > 0) {
|
|
|
|
|
payload = {dockerfile_path: dockerfilePath};
|
|
|
|
|
core.info(`Using dockerfile path: ${dockerfilePath}`);
|
|
|
|
|
}
|
|
|
|
|
core.info(`Waiting for Blacksmith builder agent to be ready...`);
|
|
|
|
|
const response = await client.post('', payload);
|
|
|
|
|
|
|
|
|
|
const data = response.data;
|
|
|
|
|
const taskId = data['id'] as string;
|
|
|
|
|
stateHelper.setBlacksmithBuildTaskId(taskId);
|
|
|
|
|
const clientKey = data['client_key'] as string;
|
|
|
|
|
stateHelper.setBlacksmithClientKey(clientKey);
|
|
|
|
|
const clientCaCertificate = data['client_ca_certificate'] as string;
|
|
|
|
|
stateHelper.setBlacksmithClientCaCertificate(clientCaCertificate);
|
|
|
|
|
const rootCaCertificate = data['root_ca_certificate'] as string;
|
|
|
|
|
stateHelper.setBlacksmithRootCaCertificate(rootCaCertificate);
|
|
|
|
|
|
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
while (Date.now() - startTime < 60000) {
|
|
|
|
|
const response = await client.get(`/${taskId}`);
|
|
|
|
|
const data = response.data;
|
|
|
|
|
const ec2Instance = data['ec2_instance'] ?? null;
|
|
|
|
|
if (ec2Instance) {
|
|
|
|
|
const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
|
|
|
core.info(`Blacksmith builder agent ready after ${elapsedTime} seconds`);
|
|
|
|
|
return `tcp://${ec2Instance['instance_ip']}:4242` as string;
|
|
|
|
|
}
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await client.post(`/${stateHelper.blacksmithBuildTaskId}/abandon`);
|
|
|
|
|
return null;
|
|
|
|
|
} catch (error) {
|
|
|
|
|
core.warning(`Error in getBuildkitdAddr: ${error.message}`);
|
|
|
|
|
return null;
|
|
|
|
|
} finally {
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function setupBuildx(version: string, toolkit: Toolkit): Promise<void> {
|
|
|
|
|
let toolPath;
|
|
|
|
|
const standalone = await toolkit.buildx.isStandalone();
|
|
|
|
|
|
|
|
|
|
if (!(await toolkit.buildx.isAvailable()) || version) {
|
|
|
|
|
await core.group(`Download buildx from GitHub Releases`, async () => {
|
|
|
|
|
toolPath = await toolkit.buildxInstall.download(version || 'latest', true);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actionsToolkit.run(
|
|
|
|
|
// main
|
|
|
|
|
async () => {
|
|
|
|
|
const startedTime = new Date();
|
|
|
|
|
const inputs: context.Inputs = await context.getInputs();
|
|
|
|
|
core.debug(`inputs: ${JSON.stringify(inputs)}`);
|
|
|
|
|
stateHelper.setInputs(inputs);
|
|
|
|
|
|
|
|
|
|
const toolkit = new Toolkit();
|
|
|
|
|
@ -46,6 +153,59 @@ actionsToolkit.run(
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await core.group(`Setup buildx`, async () => {
|
|
|
|
|
await setupBuildx(buildxVersion, toolkit);
|
|
|
|
|
|
|
|
|
|
if (!(await toolkit.buildx.isAvailable())) {
|
|
|
|
|
core.setFailed(`Docker buildx is required. See https://github.com/docker/setup-buildx-action to set up buildx.`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let remoteBuilderAddr: string | null = null;
|
|
|
|
|
await core.group(`Starting Blacksmith remote builder`, async () => {
|
|
|
|
|
remoteBuilderAddr = await getRemoteBuilderAddr(inputs);
|
|
|
|
|
if (!remoteBuilderAddr) {
|
|
|
|
|
core.warning('Failed to obtain Blacksmith remote builder address. Falling back to a local build.');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (remoteBuilderAddr) {
|
|
|
|
|
await core.group(`Creating a remote builder instance`, async () => {
|
|
|
|
|
const name = `blacksmith`;
|
|
|
|
|
const createCmd = await toolkit.buildx.getCommand(await context.getRemoteBuilderArgs(name, remoteBuilderAddr!));
|
|
|
|
|
core.info(`Creating builder with command: ${createCmd.command}`);
|
|
|
|
|
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');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// If we failed to obtain the address, let's check if we have an already configured builder.
|
|
|
|
|
await core.group(`Checking for configured builder`, async () => {
|
|
|
|
|
try {
|
|
|
|
|
const builder = await toolkit.builder.inspect();
|
|
|
|
|
if (builder) {
|
|
|
|
|
core.info(`Found configured builder: ${builder.name}`);
|
|
|
|
|
} else {
|
|
|
|
|
// Create a local builder using the docker-container driver (which is the default driver in setup-buildx)
|
|
|
|
|
const createLocalBuilderCmd = 'docker buildx create --name local --driver docker-container --use';
|
|
|
|
|
try {
|
|
|
|
|
await Exec.exec(createLocalBuilderCmd);
|
|
|
|
|
core.info('Created and set a local builder for use');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
core.setFailed(`Failed to create local builder: ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
core.setFailed(`Error configuring builder: ${error.message}`);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await core.group(`Proxy configuration`, async () => {
|
|
|
|
|
let dockerConfig: ConfigFile | undefined;
|
|
|
|
|
let dockerConfigMalformed = false;
|
|
|
|
|
@ -71,17 +231,8 @@ actionsToolkit.run(
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!(await toolkit.buildx.isAvailable())) {
|
|
|
|
|
core.setFailed(`Docker buildx is required. See https://github.com/docker/setup-buildx-action to set up buildx.`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stateHelper.setTmpDir(Context.tmpDir());
|
|
|
|
|
|
|
|
|
|
await core.group(`Buildx version`, async () => {
|
|
|
|
|
await toolkit.buildx.printVersion();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let builder: BuilderInfo;
|
|
|
|
|
await core.group(`Builder info`, async () => {
|
|
|
|
|
builder = await toolkit.builder.inspect(inputs.builder);
|
|
|
|
|
@ -176,8 +327,14 @@ actionsToolkit.run(
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
|
if (remoteBuilderAddr) {
|
|
|
|
|
stateHelper.setRemoteDockerBuildStatus('failure');
|
|
|
|
|
}
|
|
|
|
|
throw err;
|
|
|
|
|
}
|
|
|
|
|
if (remoteBuilderAddr) {
|
|
|
|
|
stateHelper.setRemoteDockerBuildStatus('success');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// post
|
|
|
|
|
async () => {
|
|
|
|
|
@ -215,6 +372,13 @@ actionsToolkit.run(
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
if (stateHelper.remoteDockerBuildStatus != '') {
|
|
|
|
|
if (stateHelper.remoteDockerBuildStatus == 'success') {
|
|
|
|
|
await reportBuildCompleted();
|
|
|
|
|
} else {
|
|
|
|
|
await reportBuildFailed();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (stateHelper.tmpDir.length > 0) {
|
|
|
|
|
await core.group(`Removing temp folder ${stateHelper.tmpDir}`, async () => {
|
|
|
|
|
fs.rmSync(stateHelper.tmpDir, {recursive: true});
|
|
|
|
|
|