Skip to content

Proxy-network runtime digests drop source locations and provenance associations #7084

Description

@gmarmstrong

Contributing guidelines and 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:

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:

Source-location regression test
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 -v

The 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
FAIL

Reproduction: provenance build steps lose resource usage

This join-level regression test exercises resource-usage association in package provenance:

Resource-usage regression test
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 -v

The 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)
FAIL

Version information

  • BuildKit master
  • commit bb80b2a
  • Go version go1.26.6 darwin/arm64

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions