Skip to content

Commit 865047e

Browse files
action: implement multi-arch buildkitd with insecure mode
Add multiarch.ts module that handles: - Multi-arch detection from platforms input - Follower sandbox spawning via POST /api/sandbox - Ephemeral ED25519 SSH keypair generation for inter-VM comms - Buildkitd startup on follower via SSH exec - Port exposure via tunnel manager (localhost:8377/expose-port) Integrate into main.ts: - Detect multi-arch need from platforms input - Spawn follower sandbox on opposite arch - Create multi-node buildx builder (host + follower) - QEMU fallback if follower setup fails - Cleanup follower sandbox in post-action phase Update state-helper.ts: - Add follower VM ID, arch, and buildkitd addr state management Depends on BLACKSMITH_SANDBOX_TOKEN being available in VM env. Ref: BLA-790 Co-Authored-By: maru@blacksmith.sh <adityamaru@gmail.com>
1 parent 64f56a8 commit 865047e

3 files changed

Lines changed: 518 additions & 28 deletions

File tree

src/main.ts

Lines changed: 124 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ import {
3232
import { shutdownBuildkitd } from "./shutdown";
3333
import { resolveRemoteBuilderPlatforms } from "./platform-utils";
3434
import { checkPreviousStepFailures } from "./step-checker";
35+
import {
36+
getRequiredFollowerArch,
37+
setupMultiArchFollower,
38+
teardownFollower,
39+
type FollowerInfo,
40+
} from "./multiarch";
3541
import { Metric_MetricType } from "@buf/blacksmith_vm-agent.bufbuild_es/stickydisk/v1/stickydisk_pb.js";
3642

3743
const DEFAULT_BUILDX_VERSION = "v0.23.0";
@@ -564,9 +570,7 @@ async function maybeShutdownBuildkitd(): Promise<void> {
564570
await pruneBuildkitCache();
565571
core.info("BuildKit cache pruned");
566572
} catch (error) {
567-
core.warning(
568-
`Error pruning BuildKit cache: ${(error as Error).message}`,
569-
);
573+
core.warning(`Error pruning BuildKit cache: ${(error as Error).message}`);
570574
}
571575

572576
const buildkitdShutdownStartTime = Date.now();
@@ -594,9 +598,7 @@ async function logBuildkitdCrashLogs(): Promise<void> {
594598
core.info("Last 100 lines of buildkitd.log:");
595599
core.info(stdout);
596600
} catch (error) {
597-
core.warning(
598-
`Could not read buildkitd logs: ${(error as Error).message}`,
599-
);
601+
core.warning(`Could not read buildkitd logs: ${(error as Error).message}`);
600602
}
601603
}
602604

@@ -665,35 +667,119 @@ void actionsToolkit.run(
665667
});
666668

667669
if (builderInfo.addr) {
670+
// Check if multi-arch follower is needed
671+
let followerInfo: FollowerInfo | null = null;
672+
const followerArch = getRequiredFollowerArch(inputs.platforms);
673+
if (followerArch) {
674+
await core.group(
675+
`Setting up multi-arch follower (${followerArch})`,
676+
async () => {
677+
try {
678+
followerInfo = await setupMultiArchFollower(followerArch);
679+
stateHelper.setFollowerVmId(followerInfo.vmId);
680+
stateHelper.setFollowerArch(followerInfo.arch);
681+
stateHelper.setFollowerBuildkitdAddr(followerInfo.buildkitdAddr);
682+
core.info(
683+
`Multi-arch follower ready: arch=${followerInfo.arch} addr=${followerInfo.buildkitdAddr}`,
684+
);
685+
} catch (error) {
686+
core.warning(
687+
`Failed to set up multi-arch follower: ${(error as Error).message}. ` +
688+
`Multi-arch builds will fall back to QEMU emulation.`,
689+
);
690+
followerInfo = null;
691+
}
692+
},
693+
);
694+
}
695+
668696
// Create and configure the builder
669697
await core.group(`Creating builder instance`, async () => {
670698
const name = `blacksmith-${Date.now().toString(36)}`;
671699
stateHelper.setBuilderName(name);
672700

673-
// Create the builder with platform configuration
674-
const createArgs = ["create", "--name", name, "--driver", "remote"];
701+
if (followerInfo) {
702+
// Multi-arch: create builder with host arch node, then append follower node.
703+
const hostArch = resolveRemoteBuilderPlatforms();
704+
core.info(
705+
`Creating multi-arch builder: host=${hostArch}, follower=linux/${followerInfo.arch}`,
706+
);
707+
708+
// Create first node for host architecture.
709+
const createArgs = [
710+
"create",
711+
"--name",
712+
name,
713+
"--driver",
714+
"remote",
715+
"--platform",
716+
hostArch,
717+
builderInfo.addr!,
718+
];
719+
const createCmd = await toolkit.buildx.getCommand(createArgs);
720+
core.info(
721+
`Creating host builder node: ${createCmd.command} ${createCmd.args.join(" ")}`,
722+
);
723+
await Exec.getExecOutput(createCmd.command, createCmd.args, {
724+
ignoreReturnCode: true,
725+
}).then((res) => {
726+
if (res.stderr.length > 0 && res.exitCode != 0) {
727+
throw new Error(
728+
/(.*)\ s*$/.exec(res.stderr)?.[0]?.trim() ?? "unknown error",
729+
);
730+
}
731+
});
732+
733+
// Append follower node for the other architecture.
734+
const appendArgs = [
735+
"create",
736+
"--name",
737+
name,
738+
"--append",
739+
"--driver",
740+
"remote",
741+
"--platform",
742+
`linux/${followerInfo.arch}`,
743+
followerInfo.buildkitdAddr,
744+
];
745+
const appendCmd = await toolkit.buildx.getCommand(appendArgs);
746+
core.info(
747+
`Appending follower builder node: ${appendCmd.command} ${appendCmd.args.join(" ")}`,
748+
);
749+
await Exec.getExecOutput(appendCmd.command, appendCmd.args, {
750+
ignoreReturnCode: true,
751+
}).then((res) => {
752+
if (res.stderr.length > 0 && res.exitCode != 0) {
753+
throw new Error(
754+
/(.*)\ s*$/.exec(res.stderr)?.[0]?.trim() ?? "unknown error",
755+
);
756+
}
757+
});
758+
} else {
759+
// Single-arch: create builder with all platforms on one node.
760+
const createArgs = ["create", "--name", name, "--driver", "remote"];
675761

676-
// Add platform flag - use user-supplied platforms or fallback to host arch
677-
const platformFlag = resolveRemoteBuilderPlatforms(inputs.platforms);
678-
core.info(`Determined remote builder platform(s): ${platformFlag}`);
679-
createArgs.push("--platform", platformFlag);
762+
const platformFlag = resolveRemoteBuilderPlatforms(inputs.platforms);
763+
core.info(`Determined remote builder platform(s): ${platformFlag}`);
764+
createArgs.push("--platform", platformFlag);
680765

681-
createArgs.push(builderInfo.addr!);
766+
createArgs.push(builderInfo.addr!);
682767

683-
const createCmd = await toolkit.buildx.getCommand(createArgs);
768+
const createCmd = await toolkit.buildx.getCommand(createArgs);
684769

685-
core.info(
686-
`Creating builder with command: ${createCmd.command} ${createCmd.args.join(" ")}`,
687-
);
688-
await Exec.getExecOutput(createCmd.command, createCmd.args, {
689-
ignoreReturnCode: true,
690-
}).then((res) => {
691-
if (res.stderr.length > 0 && res.exitCode != 0) {
692-
throw new Error(
693-
/(.*)\s*$/.exec(res.stderr)?.[0]?.trim() ?? "unknown error",
694-
);
695-
}
696-
});
770+
core.info(
771+
`Creating builder with command: ${createCmd.command} ${createCmd.args.join(" ")}`,
772+
);
773+
await Exec.getExecOutput(createCmd.command, createCmd.args, {
774+
ignoreReturnCode: true,
775+
}).then((res) => {
776+
if (res.stderr.length > 0 && res.exitCode != 0) {
777+
throw new Error(
778+
/(.*)\ s*$/.exec(res.stderr)?.[0]?.trim() ?? "unknown error",
779+
);
780+
}
781+
});
782+
}
697783

698784
// Set as default builder
699785
const useCmd = await toolkit.buildx.getCommand(["use", name]);
@@ -703,7 +789,7 @@ void actionsToolkit.run(
703789
}).then((res) => {
704790
if (res.stderr.length > 0 && res.exitCode != 0) {
705791
throw new Error(
706-
/(.*)\s*$/.exec(res.stderr)?.[0]?.trim() ?? "unknown error",
792+
/(.*)\ s*$/.exec(res.stderr)?.[0]?.trim() ?? "unknown error",
707793
);
708794
}
709795
});
@@ -724,7 +810,9 @@ void actionsToolkit.run(
724810
try {
725811
const builder = await toolkit.builder.inspect();
726812
if (builder && builder.driver !== "docker") {
727-
core.info(`Found configured builder: ${builder.name} (driver: ${builder.driver})`);
813+
core.info(
814+
`Found configured builder: ${builder.name} (driver: ${builder.driver})`,
815+
);
728816
} else {
729817
// Create a local builder
730818
const createLocalBuilderCmd =
@@ -750,6 +838,14 @@ void actionsToolkit.run(
750838
},
751839
// post action - cleanup
752840
async () => {
841+
// Clean up follower sandbox if one was created.
842+
const followerVmId = stateHelper.getFollowerVmId();
843+
if (followerVmId) {
844+
await core.group("Cleaning up multi-arch follower sandbox", async () => {
845+
await teardownFollower(followerVmId);
846+
});
847+
}
848+
753849
await core.group("Cleaning up Docker builder", async () => {
754850
const exposeId = stateHelper.getExposeId();
755851
let cleanupError: Error | null = null;

0 commit comments

Comments
 (0)