mirror of
https://github.com/docker/build-push-action.git
synced 2025-05-06 21:49:33 +02:00
src: add scaffolding for support multi-platform builds
This commit is contained in:
parent
ca7f4ddd0c
commit
4a3e86e9c9
9 changed files with 116 additions and 103 deletions
|
@ -10,6 +10,16 @@ const BUILDKIT_DAEMON_ADDR = 'tcp://127.0.0.1:1234';
|
|||
const mountPoint = '/var/lib/buildkit';
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
export async function getTailscaleIP(): Promise<string | null> {
|
||||
try {
|
||||
const {stdout} = await execAsync('tailscale ip -4');
|
||||
return stdout.trim();
|
||||
} catch (error) {
|
||||
core.debug(`Error getting tailscale IP: ${error.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function maybeFormatBlockDevice(device: string): Promise<string> {
|
||||
try {
|
||||
// Check if device is formatted with ext4
|
||||
|
@ -52,11 +62,11 @@ export async function getNumCPUs(): Promise<number> {
|
|||
}
|
||||
}
|
||||
|
||||
async function writeBuildkitdTomlFile(parallelism: number): Promise<void> {
|
||||
async function writeBuildkitdTomlFile(parallelism: number, addr: string): Promise<void> {
|
||||
const jsonConfig: TOML.JsonMap = {
|
||||
root: '/var/lib/buildkit',
|
||||
grpc: {
|
||||
address: [BUILDKIT_DAEMON_ADDR]
|
||||
address: [addr]
|
||||
},
|
||||
registry: {
|
||||
'docker.io': {
|
||||
|
@ -95,13 +105,12 @@ async function writeBuildkitdTomlFile(parallelism: number): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
async function startBuildkitd(parallelism: number): Promise<string> {
|
||||
export async function startBuildkitd(parallelism: number, addr: string): Promise<string> {
|
||||
try {
|
||||
await writeBuildkitdTomlFile(parallelism);
|
||||
const addr = BUILDKIT_DAEMON_ADDR;
|
||||
await writeBuildkitdTomlFile(parallelism, addr);
|
||||
|
||||
const logStream = fs.createWriteStream('buildkitd.log');
|
||||
const buildkitd = spawn('sudo', ['buildkitd', '--debug', '--addr', addr, '--allow-insecure-entitlement', 'security.insecure', '--config=buildkitd.toml', '--allow-insecure-entitlement', 'network.host'], {
|
||||
const buildkitd = spawn('sudo', ['buildkitd', '--debug', '--config=buildkitd.toml', '--allow-insecure-entitlement', 'security.insecure', '--allow-insecure-entitlement', 'network.host'], {
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
|
@ -138,20 +147,6 @@ async function startBuildkitd(parallelism: number): Promise<string> {
|
|||
}
|
||||
}
|
||||
|
||||
async function getDiskSize(device: string): Promise<number> {
|
||||
try {
|
||||
const {stdout} = await execAsync(`sudo lsblk -b -n -o SIZE ${device}`);
|
||||
const sizeInBytes = parseInt(stdout.trim(), 10);
|
||||
if (isNaN(sizeInBytes)) {
|
||||
throw new Error('Failed to parse disk size');
|
||||
}
|
||||
return sizeInBytes;
|
||||
} catch (error) {
|
||||
console.error(`Error getting disk size: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getStickyDisk(options?: {signal?: AbortSignal}): Promise<{expose_id: string; device: string}> {
|
||||
const client = await reporter.createBlacksmithAgentClient();
|
||||
|
||||
|
@ -181,14 +176,65 @@ export async function getStickyDisk(options?: {signal?: AbortSignal}): Promise<{
|
|||
};
|
||||
}
|
||||
|
||||
export async function joinTailnet(): Promise<void> {
|
||||
const token = process.env.BLACKSMITH_TAILSCALE_TOKEN;
|
||||
if (!token || token === 'unset') {
|
||||
core.debug('BLACKSMITH_TAILSCALE_TOKEN environment variable not set, skipping tailnet join');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await execAsync(`sudo tailscale up --authkey=${token} --hostname=${process.env.VM_ID}`);
|
||||
|
||||
core.info('Successfully joined tailnet');
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to join tailnet: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function leaveTailnet(): Promise<void> {
|
||||
try {
|
||||
// Check if we're part of a tailnet before trying to leave
|
||||
const {stdout} = await execAsync('sudo tailscale status');
|
||||
if (stdout.trim() !== '') {
|
||||
await execAsync('sudo tailscale down');
|
||||
core.debug('Successfully left tailnet');
|
||||
} else {
|
||||
core.debug('Not part of a tailnet, skipping leave');
|
||||
}
|
||||
} catch (error) {
|
||||
core.warning(`Error leaving tailnet: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// buildkitdTimeoutMs states the max amount of time this action will wait for the buildkitd
|
||||
// daemon to start have its socket ready. It also additionally governs how long we will wait for
|
||||
// the buildkitd workers to be ready.
|
||||
const buildkitdTimeoutMs = 30000;
|
||||
|
||||
export async function startAndConfigureBuildkitd(parallelism: number): Promise<string> {
|
||||
const buildkitdAddr = await startBuildkitd(parallelism);
|
||||
core.debug(`buildkitd daemon started at addr ${buildkitdAddr}`);
|
||||
export async function startAndConfigureBuildkitd(parallelism: number, platforms?: string[]): Promise<string> {
|
||||
// For multi-platform builds, we need to use the tailscale IP
|
||||
let buildkitdAddr = BUILDKIT_DAEMON_ADDR;
|
||||
|
||||
// If we are doing a multi-platform build, we need to join the tailnet and bind buildkitd to the tailscale IP.
|
||||
// We do this so that the remote VM can join the same buildkitd cluster as a worker.
|
||||
if (platforms && platforms.length > 1) {
|
||||
await joinTailnet();
|
||||
const tailscaleIP = await getTailscaleIP();
|
||||
if (!tailscaleIP) {
|
||||
throw new Error('Failed to get tailscale IP for multi-platform build');
|
||||
}
|
||||
buildkitdAddr = `tcp://${tailscaleIP}:1234`;
|
||||
core.info(`Using tailscale IP for multi-platform build: ${buildkitdAddr}`);
|
||||
}
|
||||
|
||||
const addr = await startBuildkitd(parallelism, buildkitdAddr);
|
||||
core.debug(`buildkitd daemon started at addr ${addr}`);
|
||||
|
||||
if (platforms && platforms.length > 1) {
|
||||
// TODO(adityamaru): Queue docker job for multi-platform build with a well known tailscale hostname.
|
||||
// TODO(adityamaru): Wait until the VM joins the tailnet.
|
||||
}
|
||||
|
||||
// Check that buildkit instance is ready by querying workers for up to 30s
|
||||
const startTimeBuildkitReady = Date.now();
|
||||
|
@ -196,10 +242,12 @@ export async function startAndConfigureBuildkitd(parallelism: number): Promise<s
|
|||
|
||||
while (Date.now() - startTimeBuildkitReady < timeoutBuildkitReady) {
|
||||
try {
|
||||
const {stdout} = await execAsync(`sudo buildctl --addr ${BUILDKIT_DAEMON_ADDR} debug workers`);
|
||||
const {stdout} = await execAsync(`sudo buildctl --addr ${addr} debug workers`);
|
||||
const lines = stdout.trim().split('\n');
|
||||
if (lines.length > 1) {
|
||||
// Check if we have output lines beyond the header
|
||||
// For multi-platform builds, we need at least 2 workers
|
||||
const requiredWorkers = platforms && platforms.length > 1 ? 2 : 1;
|
||||
if (lines.length > requiredWorkers) {
|
||||
core.info(`Found ${lines.length - 1} workers, required ${requiredWorkers}`);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -210,10 +258,11 @@ export async function startAndConfigureBuildkitd(parallelism: number): Promise<s
|
|||
|
||||
// Final check after timeout.
|
||||
try {
|
||||
const {stdout} = await execAsync(`sudo buildctl --addr ${BUILDKIT_DAEMON_ADDR} debug workers`);
|
||||
const {stdout} = await execAsync(`sudo buildctl --addr ${addr} debug workers`);
|
||||
const lines = stdout.trim().split('\n');
|
||||
if (lines.length <= 1) {
|
||||
throw new Error('buildkit workers not ready after 15s timeout');
|
||||
const requiredWorkers = platforms && platforms.length > 1 ? 2 : 1;
|
||||
if (lines.length <= requiredWorkers) {
|
||||
throw new Error(`buildkit workers not ready after ${buildkitdTimeoutMs}ms timeout. Found ${lines.length - 1} workers, required ${requiredWorkers}`);
|
||||
}
|
||||
} catch (error) {
|
||||
core.warning(`Error checking buildkit workers: ${error.message}`);
|
||||
|
@ -225,7 +274,7 @@ export async function startAndConfigureBuildkitd(parallelism: number): Promise<s
|
|||
core.warning(`Background cache pruning failed: ${error.message}`);
|
||||
});
|
||||
|
||||
return buildkitdAddr;
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue