1
0
Fork 0
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:
Aditya Maru 2025-02-16 23:58:32 +05:30
parent ca7f4ddd0c
commit 4a3e86e9c9
9 changed files with 116 additions and 103 deletions

View file

@ -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;
}
/**