Step 21 session C: BwrapBuildRunner delegates to the werkdock CLI
The runner no longer assembles raw bwrap argv: it checks image existence via 'werkdock images', loads the rootfs archive once per source as image werkator-buildenv-<hash> into werkdock's store (shared across every repository of the OS user — resolving step 22's buildenv-sharing question), and runs builds through 'werkdock run --rm' with the git-metadata mask expressed as ordered -v/--tmpfs flags. New pinned config key bwrap.werkdock (the executing binary, default via PATH) — a branch must not substitute the executing binary; synced in WerkatorConfig, BwrapOverrides, the pinned-key strip, the init template, docs/configuration.md, and AGENTS.md. werkdock's --clearenv means the server environment no longer leaks into builds; the TMPDIR workaround is gone with the raw invocation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
f2d685932e
commit
823c8adc36
@@ -35,11 +35,18 @@ class BwrapBuildRunnerTest : FunSpec() {
|
||||
),
|
||||
)
|
||||
|
||||
private fun rootfsUnpacked(rootfs: String = "/srv/buildenv.tar.zst"): Path =
|
||||
repoDir
|
||||
.resolve(BwrapBuildRunner.BUILDENV_DIR)
|
||||
.resolve(rootfs.sha12())
|
||||
.resolve(BwrapBuildRunner.ROOTFS_DIR)
|
||||
private fun imageName(rootfs: String = "/srv/buildenv.tar.zst"): String = "werkator-buildenv-${rootfs.sha12()}"
|
||||
|
||||
/** The image is already loaded: `werkdock images` lists it, so no load runs. */
|
||||
private fun givenImageLoaded(rootfs: String = "/srv/buildenv.tar.zst") {
|
||||
every { commandRunner.runOrThrow(listOf("werkdock", "images"), repoDir, any(), any()) } returns
|
||||
GitCommandResult(0, imageName(rootfs) + "\n", "")
|
||||
}
|
||||
|
||||
private fun givenImageMissing() {
|
||||
every { commandRunner.runOrThrow(listOf("werkdock", "images"), repoDir, any(), any()) } returns
|
||||
GitCommandResult(0, "some-other-image\n", "")
|
||||
}
|
||||
|
||||
init {
|
||||
beforeEach {
|
||||
@@ -54,110 +61,92 @@ class BwrapBuildRunnerTest : FunSpec() {
|
||||
}
|
||||
}
|
||||
|
||||
test("unpacks the rootfs on demand and assembles the exact bwrap command") {
|
||||
every {
|
||||
commandRunner.runOrThrow(
|
||||
listOf("tar", "--no-same-owner", "-xf", "/srv/buildenv.tar.zst", "-C", rootfsUnpacked().toString()),
|
||||
repoDir,
|
||||
any(),
|
||||
any(),
|
||||
)
|
||||
} returns
|
||||
GitCommandResult(0, "", "")
|
||||
test("assembles the exact werkdock run command for a loaded image") {
|
||||
givenImageLoaded()
|
||||
|
||||
runner.start("./gradlew test", workspace, mapOf("branch" to "main"), repoDir, bwrapBranchConfig())
|
||||
|
||||
val args = captured.single()
|
||||
val rootfsDir = args[args.indexOf("--ro-bind") + 1]
|
||||
args shouldBe
|
||||
captured.single() shouldBe
|
||||
listOf(
|
||||
"bwrap",
|
||||
"--unshare-user",
|
||||
"--unshare-pid",
|
||||
"--die-with-parent",
|
||||
"--uid",
|
||||
"0",
|
||||
"--gid",
|
||||
"0",
|
||||
"--ro-bind",
|
||||
rootfsUnpacked().toString(),
|
||||
"/",
|
||||
"--bind",
|
||||
repoDir.toString(),
|
||||
repoDir.toString(),
|
||||
"--bind",
|
||||
workspace.toString(),
|
||||
workspace.toString(),
|
||||
"--bind",
|
||||
repoDir.resolve(".git/werkator/buildenv/home").toString(),
|
||||
"/root",
|
||||
"--ro-bind",
|
||||
"/etc/resolv.conf",
|
||||
"/etc/resolv.conf",
|
||||
"--proc",
|
||||
"/proc",
|
||||
"--dev",
|
||||
"/dev",
|
||||
"--tmpfs",
|
||||
"/tmp",
|
||||
"--setenv",
|
||||
"HOME",
|
||||
"/root",
|
||||
"--setenv",
|
||||
"TMPDIR",
|
||||
"/tmp",
|
||||
"--setenv",
|
||||
"TMP",
|
||||
"/tmp",
|
||||
"--setenv",
|
||||
"branch",
|
||||
"main",
|
||||
"--chdir",
|
||||
"werkdock",
|
||||
"run",
|
||||
"--rm",
|
||||
"-v",
|
||||
"$repoDir:$repoDir",
|
||||
"-v",
|
||||
"$workspace:$workspace",
|
||||
"-v",
|
||||
"${repoDir.resolve(".git/werkator/buildenv/home")}:/root",
|
||||
"-e",
|
||||
"branch=main",
|
||||
"-w",
|
||||
workspace.toString(),
|
||||
imageName(),
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"./gradlew test",
|
||||
)
|
||||
Files.isDirectory(rootfsUnpacked()) shouldBe true
|
||||
}
|
||||
|
||||
test("binds a relative workspace path at its absolute location") {
|
||||
// bwrap creates mountpoints for bind destinations inside the sandbox;
|
||||
// a relative path would land in the read-only rootfs and fail with
|
||||
// "Can't mkdir parents ...: Read-only file system" (seen on the webspace).
|
||||
test("loads the image once when werkdock does not know it yet") {
|
||||
givenImageMissing()
|
||||
every {
|
||||
commandRunner.runOrThrow(
|
||||
listOf("tar", "--no-same-owner", "-xf", "/srv/buildenv.tar.zst", "-C", rootfsUnpacked().toString()),
|
||||
listOf("werkdock", "load", "-i", "/srv/buildenv.tar.zst", "--name", imageName()),
|
||||
repoDir,
|
||||
any(),
|
||||
any(),
|
||||
)
|
||||
} returns
|
||||
GitCommandResult(0, "", "")
|
||||
} returns GitCommandResult(0, "", "")
|
||||
|
||||
runner.start("./gradlew test", workspace, mapOf("branch" to "main"), repoDir, bwrapBranchConfig())
|
||||
|
||||
verify {
|
||||
commandRunner.runOrThrow(
|
||||
listOf("werkdock", "load", "-i", "/srv/buildenv.tar.zst", "--name", imageName()),
|
||||
repoDir,
|
||||
any(),
|
||||
any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test("does not load an image werkdock already has") {
|
||||
givenImageLoaded()
|
||||
|
||||
runner.start("./gradlew test", workspace, mapOf("branch" to "main"), repoDir, bwrapBranchConfig())
|
||||
|
||||
verify(exactly = 0) { commandRunner.runOrThrow(match { "load" in it }, any(), any(), any()) }
|
||||
}
|
||||
|
||||
test("uses the configured werkdock binary path") {
|
||||
every { commandRunner.runOrThrow(listOf("/opt/bin/werkdock", "images"), repoDir, any(), any()) } returns
|
||||
GitCommandResult(0, imageName() + "\n", "")
|
||||
val branchConfig =
|
||||
BranchConfig(
|
||||
bwrap = BwrapConfig(enabled = true, rootfs = "/srv/buildenv.tar.zst", werkdock = "/opt/bin/werkdock"),
|
||||
)
|
||||
|
||||
runner.start("./gradlew test", workspace, emptyMap(), repoDir, branchConfig)
|
||||
|
||||
captured.single().first() shouldBe "/opt/bin/werkdock"
|
||||
}
|
||||
|
||||
test("mounts a relative workspace path at its absolute location") {
|
||||
givenImageLoaded()
|
||||
val relativeWorkspace = repoDir.relativize(workspace)
|
||||
|
||||
runner.start("./gradlew test", relativeWorkspace, mapOf("branch" to "main"), repoDir, bwrapBranchConfig())
|
||||
|
||||
val args = captured.single()
|
||||
val absolute = workspace.toAbsolutePath().normalize().toString()
|
||||
val bindIdx = args.withIndex().filter { it.value == "--bind" }.map { it.index }
|
||||
// first bind is the repo dir (mountpoint base), second is the workspace
|
||||
args[bindIdx[1] + 1] shouldBe absolute
|
||||
args[bindIdx[1] + 2] shouldBe absolute
|
||||
args[args.indexOf("--chdir") + 1] shouldBe absolute
|
||||
args shouldContainElement "-v"
|
||||
args[args.indexOf("-w") + 1] shouldBe absolute
|
||||
args.count { it == "$absolute:$absolute" } shouldBe 1
|
||||
}
|
||||
|
||||
test("does not re-unpack an already prepared rootfs") {
|
||||
Files.createDirectories(rootfsUnpacked())
|
||||
|
||||
runner.start("./gradlew test", workspace, mapOf("branch" to "main"), repoDir, bwrapBranchConfig())
|
||||
|
||||
verify(exactly = 0) { commandRunner.runOrThrow(match { it.first() == "tar" }, any(), any(), any()) }
|
||||
}
|
||||
|
||||
test("adds bwrap env and passes the branch environment through") {
|
||||
Files.createDirectories(rootfsUnpacked())
|
||||
test("adds bwrap env after the branch environment") {
|
||||
givenImageLoaded()
|
||||
|
||||
runner.start(
|
||||
"./gradlew test",
|
||||
@@ -168,39 +157,43 @@ class BwrapBuildRunnerTest : FunSpec() {
|
||||
)
|
||||
|
||||
val args = captured.single()
|
||||
args[args.indexOf("branch") - 1] shouldBe "--setenv"
|
||||
args[args.indexOf("branch") + 1] shouldBe "main"
|
||||
args[args.indexOf("FOO") - 1] shouldBe "--setenv"
|
||||
args[args.indexOf("FOO") + 1] shouldBe "bar"
|
||||
args[args.indexOf("branch=main") - 1] shouldBe "-e"
|
||||
args[args.indexOf("FOO=bar") - 1] shouldBe "-e"
|
||||
args.indexOf("branch=main") shouldBe args.indexOf("FOO=bar") - 2
|
||||
}
|
||||
|
||||
test("exposes git metadata read-only with the werkator dir masked for a worktree workspace") {
|
||||
test("exposes git metadata read-only with the werkator dir masked, in mount order") {
|
||||
val gitDir = repoDir.resolve(".git")
|
||||
val adminDir = gitDir.resolve("worktrees/workspace")
|
||||
Files.createDirectories(adminDir)
|
||||
Files.createDirectories(gitDir.resolve("werkator"))
|
||||
Files.createDirectories(workspace)
|
||||
Files.writeString(workspace.resolve(".git"), "gitdir: $adminDir\n")
|
||||
Files.createDirectories(rootfsUnpacked())
|
||||
givenImageLoaded()
|
||||
|
||||
runner.start("./gradlew test", workspace, mapOf("branch" to "main"), repoDir, bwrapBranchConfig())
|
||||
|
||||
val args = captured.single()
|
||||
args[args.indexOf(gitDir.toString()) - 1] shouldBe "--ro-bind"
|
||||
args[args.indexOf("$gitDir:$gitDir:ro") - 1] shouldBe "-v"
|
||||
args[args.indexOf("$gitDir/werkator") - 1] shouldBe "--tmpfs"
|
||||
args[args.indexOf(adminDir.toString()) - 1] shouldBe "--bind"
|
||||
args[args.indexOf("$adminDir:$adminDir") - 1] shouldBe "-v"
|
||||
// order: ro .git, tmpfs mask, admin dir, then the workspace bind that
|
||||
// shadows the mask at its own path
|
||||
val roGit = args.indexOf("$gitDir:$gitDir:ro")
|
||||
val mask = args.indexOf("$gitDir/werkator")
|
||||
val admin = args.indexOf("$adminDir:$adminDir")
|
||||
val workspaceBind = args.indexOf("$workspace:$workspace")
|
||||
(roGit < mask && mask < admin && admin < workspaceBind) shouldBe true
|
||||
}
|
||||
|
||||
test("mounts no git metadata when the workspace is not a worktree") {
|
||||
Files.createDirectories(rootfsUnpacked())
|
||||
givenImageLoaded()
|
||||
Files.createDirectories(workspace)
|
||||
|
||||
runner.start("./gradlew test", workspace, mapOf("branch" to "main"), repoDir, bwrapBranchConfig())
|
||||
|
||||
val args = captured.single()
|
||||
val gitDir = repoDir.resolve(".git")
|
||||
// the sandbox's own /tmp tmpfs is always present; the point is that no tmpfs
|
||||
// masks .git/werkator and no worktree admin dir is bound
|
||||
args.none { it == "$gitDir/werkator" } shouldBe true
|
||||
args.none { it.contains("worktrees/") } shouldBe true
|
||||
}
|
||||
@@ -216,16 +209,17 @@ class BwrapBuildRunnerTest : FunSpec() {
|
||||
exception.message shouldContain "bwrap.rootfs"
|
||||
}
|
||||
|
||||
test("downloads a URL rootfs once before unpacking") {
|
||||
test("downloads a URL rootfs once before loading it") {
|
||||
val url = "https://example.test/buildenv.tar.zst"
|
||||
val downloadTarget =
|
||||
repoDir
|
||||
.resolve(BwrapBuildRunner.BUILDENV_DIR)
|
||||
.resolve(url.sha12())
|
||||
.resolve("buildenv.tar.zst")
|
||||
givenImageMissing()
|
||||
every { commandRunner.runOrThrow(listOf("curl", "-fsSL", "-o", downloadTarget.toString(), url), repoDir, any(), any()) } returns
|
||||
GitCommandResult(0, "", "")
|
||||
every { commandRunner.runOrThrow(match { it.first() == "tar" }, any(), any(), any()) } returns
|
||||
every { commandRunner.runOrThrow(match { "load" in it }, any(), any(), any()) } returns
|
||||
GitCommandResult(0, "", "")
|
||||
|
||||
runner.start("./gradlew test", workspace, mapOf("branch" to "main"), repoDir, bwrapBranchConfig(rootfs = url))
|
||||
@@ -233,10 +227,19 @@ class BwrapBuildRunnerTest : FunSpec() {
|
||||
verify {
|
||||
commandRunner.runOrThrow(listOf("curl", "-fsSL", "-o", downloadTarget.toString(), url), repoDir, any(), any())
|
||||
}
|
||||
verify { commandRunner.runOrThrow(match { it.first() == "tar" }, repoDir, any(), any()) }
|
||||
verify {
|
||||
commandRunner.runOrThrow(
|
||||
listOf("werkdock", "load", "-i", downloadTarget.toString(), "--name", "werkator-buildenv-${url.sha12()}"),
|
||||
repoDir,
|
||||
any(),
|
||||
any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private infix fun List<String>.shouldContainElement(element: String) = (element in this) shouldBe true
|
||||
|
||||
private fun String.sha12(): String =
|
||||
java.security.MessageDigest
|
||||
.getInstance("SHA-256")
|
||||
|
||||
Reference in New Issue
Block a user