Skip to content

Commit 2cdb3f3

Browse files
feat: add server-driven config infrastructure for GC and pre-commit hooks
Add BuildkitdConfig and PreCommitHook interfaces (server-config.ts) that define the contract for server-driven cache lifecycle management. The action reads BuildkitdConfig from the GetStickyDisk RPC response (with hardcoded fallback for Phase 1) and runs pre-commit hooks from the PrepareCommit RPC response before committing. Refactor writeBuildkitdTomlFile to accept a BuildkitdConfig parameter instead of hardcoding GC policy values. This makes it trivial to switch from hardcoded defaults to server-provided config when the proto is updated (Phase 2). Add runPreCommitHooks dispatcher that executes an ordered list of opaque shell commands with timeout and failure mode handling. The commit flow now goes through prepareCommit -> runHooks -> conditionalCommit, with Phase 1 returning unconditional commit + empty hooks. Phase 1 (now): hardcoded gc=true, keepDuration=192h, no hooks Phase 2: server returns BuildkitdConfig in GetStickyDiskResponse Phase 3: server returns hooks via PrepareCommit RPC Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent abed39f commit 2cdb3f3

5 files changed

Lines changed: 152 additions & 25 deletions

File tree

dist/index.js

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
import { shutdownBuildkitd } from "./shutdown";
3434
import { resolveRemoteBuilderPlatforms } from "./platform-utils";
3535
import { checkPreviousStepFailures } from "./step-checker";
36+
import { runPreCommitHooks, type PrepareCommitResponse } from "./server-config";
3637
import { Metric_MetricType } from "@buf/blacksmith_vm-agent.bufbuild_es/stickydisk/v1/stickydisk_pb.js";
3738

3839
const DEFAULT_BUILDX_VERSION = "v0.23.0";
@@ -933,17 +934,40 @@ void actionsToolkit.run(
933934
"Skipping sticky disk commit because SIGKILL was used to terminate buildkitd - disk may be in a bad state",
934935
);
935936
} else {
936-
// No failures detected and cleanup was successful
937+
// No failures detected and cleanup was successful — proceed with commit flow.
938+
// Phase 3: call PrepareCommit RPC here to get shouldCommit + hooks from the
939+
// server. For now, fall back to unconditional commit with no hooks.
940+
const commitDecision: PrepareCommitResponse = {
941+
shouldCommit: true,
942+
hooks: [],
943+
};
944+
937945
try {
938-
core.info(
939-
"No previous step failures detected, committing sticky disk after successful cleanup",
940-
);
946+
if (commitDecision.hooks.length > 0) {
947+
const hookResult = await runPreCommitHooks(
948+
commitDecision.hooks,
949+
);
950+
if (!hookResult.shouldProceedWithCommit) {
951+
core.warning(
952+
"Pre-commit hook indicated commit should be skipped",
953+
);
954+
commitDecision.shouldCommit = false;
955+
}
956+
}
941957

942-
await reporter.commitStickyDisk(
943-
exposeId,
944-
fsDiskUsageBytes,
945-
stateHelper.getCacheKey(),
946-
);
958+
if (!commitDecision.shouldCommit) {
959+
core.info("Server indicated commit should be skipped");
960+
} else {
961+
core.info(
962+
"No previous step failures detected, committing sticky disk after successful cleanup",
963+
);
964+
965+
await reporter.commitStickyDisk(
966+
exposeId,
967+
fsDiskUsageBytes,
968+
stateHelper.getCacheKey(),
969+
);
970+
}
947971
} catch (error) {
948972
core.error(
949973
`Failed to commit sticky disk: ${(error as Error).message}`,

src/server-config.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import * as core from "@actions/core";
2+
import { exec } from "child_process";
3+
import { promisify } from "util";
4+
5+
const execAsync = promisify(exec);
6+
7+
/**
8+
* Server-driven buildkitd daemon configuration.
9+
* Returned by the GetStickyDisk RPC (Phase 2).
10+
* When absent, the action falls back to DEFAULT_BUILDKITD_CONFIG.
11+
*/
12+
export interface GCPolicy {
13+
keepDuration?: string;
14+
keepBytes?: number;
15+
all?: boolean;
16+
filters?: string[];
17+
}
18+
19+
export interface BuildkitdConfig {
20+
gc: boolean;
21+
gcPolicy?: GCPolicy[];
22+
maxParallelism?: number;
23+
}
24+
25+
/**
26+
* Server-driven pre-commit hook definition.
27+
* Returned by the PrepareCommit RPC (Phase 3).
28+
*/
29+
export interface PreCommitHook {
30+
command: string;
31+
timeoutSeconds?: number;
32+
failureMode: "skip_commit" | "commit_anyway" | "abort";
33+
}
34+
35+
/**
36+
* Response from the PrepareCommit RPC (Phase 3).
37+
* When the RPC is unavailable (old agent), the action falls back to
38+
* unconditional commit with no hooks.
39+
*/
40+
export interface PrepareCommitResponse {
41+
shouldCommit: boolean;
42+
hooks: PreCommitHook[];
43+
}
44+
45+
export const DEFAULT_BUILDKITD_CONFIG: BuildkitdConfig = {
46+
gc: true,
47+
gcPolicy: [
48+
{
49+
keepDuration: "192h",
50+
all: true,
51+
},
52+
],
53+
};
54+
55+
/**
56+
* Runs an ordered list of pre-commit hooks. Returns whether the commit
57+
* should proceed based on hook results and their failure modes.
58+
*/
59+
export async function runPreCommitHooks(
60+
hooks: PreCommitHook[],
61+
): Promise<{ shouldProceedWithCommit: boolean }> {
62+
for (const hook of hooks) {
63+
const timeout = hook.timeoutSeconds ?? 300;
64+
core.info(
65+
`Running pre-commit hook: ${hook.command} (timeout: ${timeout}s)`,
66+
);
67+
68+
try {
69+
const { stdout, stderr } = await execAsync(hook.command, {
70+
timeout: timeout * 1000,
71+
});
72+
if (stdout) core.debug(`Hook stdout: ${stdout.slice(0, 1000)}`);
73+
if (stderr) core.debug(`Hook stderr: ${stderr.slice(0, 1000)}`);
74+
core.info(`Pre-commit hook completed successfully`);
75+
} catch (error) {
76+
const errorMsg = error instanceof Error ? error.message : String(error);
77+
core.warning(`Pre-commit hook failed: ${errorMsg}`);
78+
79+
switch (hook.failureMode) {
80+
case "skip_commit":
81+
core.warning("Hook failure mode is skip_commit, skipping commit");
82+
return { shouldProceedWithCommit: false };
83+
case "abort":
84+
core.error("Hook failure mode is abort, failing the action");
85+
throw new Error(`Pre-commit hook aborted: ${errorMsg}`);
86+
case "commit_anyway":
87+
core.warning("Hook failure mode is commit_anyway, continuing");
88+
break;
89+
}
90+
}
91+
}
92+
93+
return { shouldProceedWithCommit: true };
94+
}

src/setup_builder.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as reporter from "./reporter";
77
import { execa } from "execa";
88
import * as stateHelper from "./state-helper";
99
import { BOLT_CHECK_MAX_FILE_BYTES } from "./exec-utils";
10+
import { BuildkitdConfig, DEFAULT_BUILDKITD_CONFIG } from "./server-config";
1011

1112
// Constants for configuration.
1213
const BUILDKIT_DAEMON_ADDR = "tcp://127.0.0.1:1234";
@@ -166,7 +167,20 @@ async function writeBuildkitdTomlFile(
166167
parallelism: number,
167168
addr: string,
168169
dnsNameservers: string[],
170+
serverConfig?: BuildkitdConfig,
169171
): Promise<void> {
172+
const config = serverConfig ?? DEFAULT_BUILDKITD_CONFIG;
173+
const effectiveParallelism = config.maxParallelism ?? parallelism;
174+
175+
const gcPolicy = config.gcPolicy?.map((p) => {
176+
const policy: TOML.JsonMap = {};
177+
if (p.keepDuration) policy.keepDuration = p.keepDuration;
178+
if (p.keepBytes) policy.keepBytes = p.keepBytes;
179+
if (p.all !== undefined) policy.all = p.all;
180+
if (p.filters) policy.filters = p.filters;
181+
return policy;
182+
});
183+
170184
const jsonConfig: TOML.JsonMap = {
171185
root: "/var/lib/buildkit",
172186
grpc: {
@@ -184,14 +198,9 @@ async function writeBuildkitdTomlFile(
184198
worker: {
185199
oci: {
186200
enabled: true,
187-
gc: true,
188-
gcpolicy: [
189-
{
190-
keepDuration: "192h",
191-
all: true,
192-
},
193-
],
194-
"max-parallelism": parallelism,
201+
gc: config.gc,
202+
...(gcPolicy && gcPolicy.length > 0 ? { gcpolicy: gcPolicy } : {}),
203+
"max-parallelism": effectiveParallelism,
195204
snapshotter: "overlayfs",
196205
},
197206
containerd: {

0 commit comments

Comments
 (0)