Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
},
"dependencies": {
"@actions/core": "^1.10.1",
"@buf/blacksmith_vm-agent.bufbuild_es": "2.11.0-20260224204715-065f59654bc1.1",
"@bufbuild/protobuf": "^2.11.0",
"@buf/blacksmith_vm-agent.bufbuild_es": "2.14.1-20260903120834-51945c9a3232.2",
"@bufbuild/protobuf": "^2.14.1",
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-node": "^2.1.1",
"@docker/actions-toolkit": "0.37.1",
Expand Down
39 changes: 22 additions & 17 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions src/driver-opts.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import * as core from "@actions/core";
import * as fs from "fs";
import { startBuildkitd } from "./setup_builder";
import { buildkitdConfigFromServer } from "./server-config";
import { execa } from "execa";

vi.mock("@actions/core");
Expand Down Expand Up @@ -180,3 +182,49 @@ describe("driver-opts parsing", () => {
expect(commandCall).toContain("EQUALS='key=value'");
});
});

describe("buildkitd.toml GC policy", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(execa).mockReturnValue({
on: vi.fn(),
stdout: { pipe: vi.fn() },
stderr: { pipe: vi.fn() },
} as unknown as ReturnType<typeof execa>);
});

function writtenBuildkitdToml(): string {
const call = vi
.mocked(fs.promises.writeFile)
.mock.calls.find(([path]) => path === "buildkitd.toml");
expect(call).toBeDefined();
return call![1] as string;
}

it("writes the 192h default when no server config is passed", async () => {
await startBuildkitd(4, "tcp://127.0.0.1:1234");

const toml = writtenBuildkitdToml();
expect(toml).toContain("gc = true");
expect(toml).toContain('keepDuration = "192h"');
expect(toml).toContain("all = true");
expect(toml).toContain("max-parallelism = 4");
});

it("writes the backend keepDuration when the agent supplied one", async () => {
await startBuildkitd(
4,
"tcp://127.0.0.1:1234",
undefined,
undefined,
buildkitdConfigFromServer(72n),
);

const toml = writtenBuildkitdToml();
expect(toml).toContain("gc = true");
expect(toml).toContain('keepDuration = "72h"');
expect(toml).not.toContain("192h");
expect(toml).toContain("all = true");
expect(toml).toContain("max-parallelism = 4");
});
});
1 change: 1 addition & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ async function startBlacksmithBuilder(
parallelism,
buildkitdPath,
inputs["driver-opts"],
stickyDiskSetup.buildkitdConfig,
);
} finally {
const buildkitdDurationMs = Date.now() - buildkitdStartTime;
Expand Down
51 changes: 51 additions & 0 deletions src/server-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import * as core from "@actions/core";
import {
buildkitdConfigFromServer,
DEFAULT_BUILDKITD_CONFIG,
} from "./server-config";

vi.mock("@actions/core", () => ({
debug: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
error: vi.fn(),
}));

describe("buildkitdConfigFromServer", () => {
beforeEach(() => {
vi.clearAllMocks();
});

it("falls back to the default config when the backend sent no policy", () => {
expect(buildkitdConfigFromServer(undefined)).toBe(DEFAULT_BUILDKITD_CONFIG);
expect(buildkitdConfigFromServer(0)).toBe(DEFAULT_BUILDKITD_CONFIG);
expect(core.warning).not.toHaveBeenCalled();
});

it("overrides only keepDuration, keeping the rest of the default policy", () => {
const config = buildkitdConfigFromServer(72n);

expect(config.gc).toBe(true);
expect(config.gcPolicy).toEqual([{ keepDuration: "72h", all: true }]);
expect(DEFAULT_BUILDKITD_CONFIG.gcPolicy?.[0]?.keepDuration).toBe("192h");
expect(core.warning).not.toHaveBeenCalled();
});

it("accepts plain numbers and the range bounds", () => {
expect(buildkitdConfigFromServer(1).gcPolicy?.[0]?.keepDuration).toBe("1h");
expect(buildkitdConfigFromServer(8760n).gcPolicy?.[0]?.keepDuration).toBe(
"8760h",
);
});

it.each([-1, 0.5, 8761, Number.NaN, 2n ** 40n])(
"warns and falls back for invalid value %s",
(value) => {
expect(buildkitdConfigFromServer(value)).toBe(DEFAULT_BUILDKITD_CONFIG);
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining("Ignoring invalid buildkitd GC keepDuration"),
);
},
);
});
47 changes: 47 additions & 0 deletions src/server-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,53 @@ export const DEFAULT_BUILDKITD_CONFIG: BuildkitdConfig = {
],
};

// Bounds for the backend-supplied GC keepDuration. Anything outside is
// treated as a backend bug and ignored rather than written into buildkitd.toml.
export const MIN_GC_KEEP_DURATION_HOURS = 1;
export const MAX_GC_KEEP_DURATION_HOURS = 8760;

/**
* Builds the buildkitd config for this job from the GC keepDuration (whole
* hours) the backend attached to the sticky disk. The backend only overrides
* the TTL; every other default is kept. Missing or invalid values yield
* DEFAULT_BUILDKITD_CONFIG so an old agent/backend, or a bad rollout value,
* never changes GC behavior.
*/
export function buildkitdConfigFromServer(
gcKeepDurationHours: bigint | number | undefined,
): BuildkitdConfig {
if (gcKeepDurationHours === undefined || gcKeepDurationHours === 0) {
core.info(
`No buildkitd GC policy from backend; using default keepDuration ${DEFAULT_BUILDKITD_CONFIG.gcPolicy?.[0]?.keepDuration}`,
);
return DEFAULT_BUILDKITD_CONFIG;
}

const hours = Number(gcKeepDurationHours);
if (
!Number.isInteger(hours) ||
hours < MIN_GC_KEEP_DURATION_HOURS ||
hours > MAX_GC_KEEP_DURATION_HOURS
) {
core.warning(
`Ignoring invalid buildkitd GC keepDuration from backend: ${String(gcKeepDurationHours)}h ` +
`(expected ${MIN_GC_KEEP_DURATION_HOURS}-${MAX_GC_KEEP_DURATION_HOURS}); using default ` +
`${DEFAULT_BUILDKITD_CONFIG.gcPolicy?.[0]?.keepDuration}`,
);
return DEFAULT_BUILDKITD_CONFIG;
}

const keepDuration = `${hours}h`;
core.info(`Using backend buildkitd GC keepDuration ${keepDuration}`);
return {
...DEFAULT_BUILDKITD_CONFIG,
gcPolicy: (DEFAULT_BUILDKITD_CONFIG.gcPolicy ?? []).map((policy) => ({
...policy,
keepDuration,
})),
};
}

/**
* Runs an ordered list of pre-commit hooks. Returns whether the commit
* should proceed based on hook results and their failure modes.
Expand Down
40 changes: 40 additions & 0 deletions src/setup-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,46 @@ describe("setup_builder", () => {
);
expect(reporter.createBlacksmithAgentClient).not.toHaveBeenCalled();
});

async function getStickyDiskWithAgentResponse(response: object) {
vi.mocked(core.getInput).mockReturnValue("my-repo/my-image");
const reporter = await import("./reporter");
vi.mocked(reporter.createBlacksmithAgentClient).mockReturnValue({
up: vi.fn().mockResolvedValue({}),
getStickyDisk: vi.fn().mockResolvedValue(response),
} as unknown as ReturnType<typeof reporter.createBlacksmithAgentClient>);

return setupBuilder.getStickyDisk();
}

it("applies the backend GC keepDuration from the agent response", async () => {
const result = await getStickyDiskWithAgentResponse({
exposeId: "expose-1",
diskIdentifier: "/dev/vdb",
parentSnapshotName: "snap-1",
cloneName: "clone-1",
buildkitdConfig: { gcKeepDurationHours: 72n },
});

expect(result.device).toBe("/dev/vdb");
expect(result.buildkitd_config.gcPolicy).toEqual([
{ keepDuration: "72h", all: true },
]);
});

it("keeps the default GC policy when an older agent omits buildkitdConfig", async () => {
const result = await getStickyDiskWithAgentResponse({
exposeId: "expose-1",
diskIdentifier: "/dev/vdb",
parentSnapshotName: "snap-1",
cloneName: "clone-1",
});

expect(result.buildkitd_config.gcPolicy).toEqual([
{ keepDuration: "192h", all: true },
]);
expect(core.warning).not.toHaveBeenCalled();
});
});

describe("getNumCPUs", () => {
Expand Down
Loading
Loading