diff --git a/client/client_control_test.go b/client/client_control_test.go index 693e27a34..1ab4c0355 100644 --- a/client/client_control_test.go +++ b/client/client_control_test.go @@ -54,6 +54,37 @@ func testClientCustomGRPCOpts(t *testing.T, sb integration.Sandbox) { require.Contains(t, interceptedMethods, "/moby.buildkit.v1.Control/Solve") } +func testBuildHistoryDisabled(t *testing.T, sb integration.Sandbox) { + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + successRef := identity.NewID() + successDef, err := llb.Scratch().File(llb.Mkfile("file", 0o644, nil)).Marshal(sb.Context()) + require.NoError(t, err) + _, err = c.Solve(sb.Context(), successDef, SolveOpt{Ref: successRef}, nil) + require.NoError(t, err) + requireNoBuildHistory(t, c, sb, successRef) + + failureRef := identity.NewID() + failureDef, err := llb.Scratch().File(llb.Rm("missing")).Marshal(sb.Context()) + require.NoError(t, err) + _, err = c.Solve(sb.Context(), failureDef, SolveOpt{Ref: failureRef}, nil) + require.Error(t, err) + requireNoBuildHistory(t, c, sb, failureRef) +} + +func requireNoBuildHistory(t *testing.T, c *Client, sb integration.Sandbox, ref string) { + t.Helper() + history, err := c.ControlClient().ListenBuildHistory(sb.Context(), &controlapi.BuildHistoryRequest{ + Ref: ref, + EarlyExit: true, + }) + require.NoError(t, err) + _, err = history.Recv() + require.ErrorIs(t, err, io.EOF) +} + func testListenBuildHistoryExcludesSoftDeletedRecords(t *testing.T, sb integration.Sandbox) { c, err := New(sb.Context(), sb.Address()) require.NoError(t, err) @@ -128,3 +159,9 @@ func testListenBuildHistoryExcludesSoftDeletedRecords(t *testing.T, sb integrati } } } + +type historyDisabled struct{} + +func (*historyDisabled) UpdateConfigFile(in string) (string, func() error) { + return in + "\n\n[history]\n maxEntries = 0\n", nil +} diff --git a/client/client_test.go b/client/client_test.go index d3e66f24f..85c084e39 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -319,6 +319,12 @@ func testIntegration(t *testing.T, funcs ...func(t *testing.T, sb integration.Sa tests = append(tests, diffOpTestCases()...) integration.Run(t, tests, mirrors) + integration.Run(t, integration.TestFuncs( + testBuildHistoryDisabled, + ), mirrors, integration.WithMatrix("history", map[string]any{ + "disabled": &historyDisabled{}, + })) + // the rest of the tests are meant for non-Windows, skipping on Windows. integration.SkipOnPlatform(t, "windows") diff --git a/docs/buildkitd.toml.md b/docs/buildkitd.toml.md index 6f037544a..e4340b0e9 100644 --- a/docs/buildkitd.toml.md +++ b/docs/buildkitd.toml.md @@ -65,6 +65,7 @@ provenanceEnvDir = "/etc/buildkit/provenance.d" # maxAge is the maximum age of history entries to keep, in seconds. maxAge = 172800 # maxEntries is the maximum number of history entries to keep. + # Setting this value to 0 disables build history. maxEntries = 50 [worker.oci] diff --git a/solver/llbsolver/history/buildhistory.go b/solver/llbsolver/history/buildhistory.go index b693e2330..e2aa3f70d 100644 --- a/solver/llbsolver/history/buildhistory.go +++ b/solver/llbsolver/history/buildhistory.go @@ -77,6 +77,12 @@ type StatusImportResult struct { NumWarnings int } +// Enabled reports whether completed build history should be recorded. +// A configured maximum of zero disables history recording entirely. +func (h *Queue) Enabled() bool { + return h.opt.CleanConfig.MaxEntries != 0 +} + func NewQueue(opt QueueOpt) (*Queue, error) { if opt.CleanConfig == nil { opt.CleanConfig = &config.HistoryConfig{ diff --git a/solver/llbsolver/history/buildhistory_test.go b/solver/llbsolver/history/buildhistory_test.go new file mode 100644 index 000000000..9e9df2087 --- /dev/null +++ b/solver/llbsolver/history/buildhistory_test.go @@ -0,0 +1,27 @@ +package history + +import ( + "testing" + + "github.com/moby/buildkit/cmd/buildkitd/config" +) + +func TestQueueEnabled(t *testing.T) { + tests := []struct { + name string + maxEntries int64 + want bool + }{ + {name: "disabled", maxEntries: 0, want: false}, + {name: "enabled", maxEntries: 50, want: true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + q := &Queue{opt: QueueOpt{CleanConfig: &config.HistoryConfig{MaxEntries: tc.maxEntries}}} + if got := q.Enabled(); got != tc.want { + t.Fatalf("Enabled() = %v, want %v", got, tc.want) + } + }) + } +} diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index d168bdd6f..36d99e31f 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -281,7 +281,7 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro defer s.gatewayForwarder.UnregisterBuild(context.Background(), id) } - if !internal { + if !internal && s.history.Enabled() { rec, err1 := s.recordBuildHistory(ctx, id, req, exp, j, usage) if err1 != nil { defer j.CloseProgress() @@ -290,6 +290,8 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro defer func() { err = rec(context.WithoutCancel(ctx), resProv, descrefs, err) }() + } else if !internal { + defer j.CloseProgress() } if fwd != nil {