Proxy-network runtime digests drop source locations and provenance associations
Contributing guidelines and issue reporting guide
- I've read the contributing guidelines and wholeheartedly agree. I've also read the issue reporting guide.
Well-formed report checklist
- I have found a bug that the documentation does not mention anything about my problem
- I have found a bug that there are no open or closed issues that are related to my problem
- I have provided version/information about my environment and done my best to provide a reproducer
Description of bug
Bug description
BuildKit intentionally gives affected vertices different runtime digests in proxy-network mode. recomputeDigests records the old-to-new digest mapping locally, but loadLLB does not preserve or expose that mapping to diagnostic and provenance consumers. Rewriting an exec digest also changes downstream digests because their input digests change.
This breaks three joins:
wrapErrorlooks up a runtimeVertexError.Digestin the originalDefinition.Source.Locations, so vertex errors can lose Dockerfile/source locations.- Resource samples are stored under the runtime
op.Digest(), but max-provenance build steps look them up with frontend definition digests. - Provenance keys layer records by
CacheExportResult.EdgeVertex. That value comes from the runtime vertex digest in the cache key and is emitted by the exporter. The definition-derived build-step lookup silently skips unmatched runtime digests.
Expected: Proxy enforcement does not change diagnostic or provenance associations.
Actual: Frontend-digest lookups fail for affected runtime digests.
Reproduction: errors lose Dockerfile locations
This regression test exercises the production wrapError source-location lookup:
func TestProxyNetworkVertexErrorKeepsSourceLocation(t *testing.T) {
for _, tc := range []struct {
name string
proxy bool
}{
{name: "disabled", proxy: false},
{name: "enabled", proxy: true},
} {
t.Run(tc.name, func(t *testing.T) {
def := proxyNetworkTestDefinition(t)
definitionDigest := digest.FromBytes(def.Def[1])
def.Source = &pb.Source{
Infos: []*pb.SourceInfo{{Filename: "Dockerfile"}},
Locations: map[string]*pb.Locations{
definitionDigest.String(): {
Locations: []*pb.Location{{SourceIndex: 0}},
},
},
}
edge, err := loadWithProxyNetwork(t.Context(), def, nil, tc.proxy)
require.NoError(t, err)
runtimeDigest := edge.Vertex.Digest()
t.Logf("definition digest=%s runtime digest=%s", definitionDigest, runtimeDigest)
rp := &resultProxy{req: frontend.SolveRequest{Definition: def}}
wrapped := rp.wrapError(errdefs.WrapVertex(errors.New("boom"), runtimeDigest))
require.Len(t, errdefs.Sources(wrapped), 1)
})
}
}go test ./solver/llbsolver -run '^TestProxyNetworkVertexErrorKeepsSourceLocation$' -count=1 -vThe disabled subtest passes with equal digests. The enabled subtest has different digests and no source location:
definition digest=sha256:26194252659a6c033bedd158ea0f77bb22094ee1d2105a392af272a994621f71
runtime digest=sha256:5ea906b68b9ea01cf0fdbac9fa58b4c32249eb55f595324c271721e2fb6cfc10
Error: "[]" should have 1 item(s), but has 0
FAILReproduction: provenance build steps lose resource usage
This join-level regression test exercises resource-usage association in package provenance:
func TestProxyNetworkResourceUsageKeepsBuildStepAssociation(t *testing.T) {
for _, tc := range []struct {
name string
proxy bool
}{
{name: "disabled", proxy: false},
{name: "enabled", proxy: true},
} {
t.Run(tc.name, func(t *testing.T) {
def, execBytes := proxyNetworkBuildConfigDefinition(t)
definitionDigest := digest.FromBytes(execBytes)
runtimeDigest := definitionDigest
if tc.proxy {
salted := append([]byte(nil), execBytes...)
salted = append(salted, []byte("\x00buildkit.proxy-network.v0")...)
runtimeDigest = digest.FromBytes(salted)
}
t.Logf("definition digest=%s runtime digest=%s", definitionDigest, runtimeDigest)
want := &resourcestypes.Samples{Samples: []*resourcestypes.Sample{{}}}
capture := &Capture{Samples: map[digest.Digest]*resourcestypes.Samples{runtimeDigest: want}}
steps, _, err := toBuildSteps(def, capture, true)
require.NoError(t, err)
for _, step := range steps {
if step.Op.GetExec() != nil {
require.Same(t, want, step.ResourceUsage)
return
}
}
t.Fatal("exec build step not found")
})
}
}
func proxyNetworkBuildConfigDefinition(t *testing.T) (*pb.Definition, []byte) {
t.Helper()
marshal := func(op *pb.Op) (digest.Digest, []byte) {
dt, err := op.Marshal()
require.NoError(t, err)
return digest.FromBytes(dt), dt
}
sourceDigest, sourceBytes := marshal(&pb.Op{
Op: &pb.Op_Source{Source: &pb.SourceOp{Identifier: "local://context"}},
})
execDigest, execBytes := marshal(&pb.Op{
Inputs: []*pb.Input{{Digest: sourceDigest.String()}},
Op: &pb.Op_Exec{Exec: &pb.ExecOp{
Meta: &pb.Meta{Args: []string{"true"}},
Mounts: []*pb.Mount{{Input: 0, Dest: pb.RootMount}},
}},
})
_, rootBytes := marshal(&pb.Op{
Inputs: []*pb.Input{{Digest: execDigest.String()}},
})
return &pb.Definition{Def: [][]byte{sourceBytes, execBytes, rootBytes}}, execBytes
}go test ./solver/llbsolver/provenance -run '^TestProxyNetworkResourceUsageKeepsBuildStepAssociation$' -count=1 -vThe disabled control passes with equal digests. Proxy-network mode produces the same digest difference shown above, and the exec build step has no resource usage:
Error: Not same:
expected: &types.Samples{...}
actual: (*types.Samples)(nil)
FAILVersion information
- BuildKit
master - commit bb80b2a1ca4a10aaa66b77bc6accca688837a729
- Go version
go1.26.6darwin/arm64
Source: moby/buildkit