mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 16:13:45 +08:00 
			
		
		
		
	build: set record provenance in response
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
		| @@ -79,11 +79,12 @@ type Options struct { | ||||
| 	Target        string | ||||
| 	Ulimits       *opts.UlimitOpt | ||||
|  | ||||
| 	Session      []session.Attachable | ||||
| 	Linked       bool // Linked marks this target as exclusively linked (not requested by the user). | ||||
| 	PrintFunc    *PrintFunc | ||||
| 	SourcePolicy *spb.Policy | ||||
| 	GroupRef     string | ||||
| 	Session                []session.Attachable | ||||
| 	Linked                 bool // Linked marks this target as exclusively linked (not requested by the user). | ||||
| 	PrintFunc              *PrintFunc | ||||
| 	WithProvenanceResponse bool | ||||
| 	SourcePolicy           *spb.Policy | ||||
| 	GroupRef               string | ||||
| } | ||||
|  | ||||
| type PrintFunc struct { | ||||
| @@ -488,6 +489,11 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s | ||||
| 						rr.ExporterResponse[k] = string(v) | ||||
| 					} | ||||
| 					rr.ExporterResponse["buildx.build.ref"] = buildRef | ||||
| 					if opt.WithProvenanceResponse && node.Driver.HistoryAPISupported(ctx) { | ||||
| 						if err := setRecordProvenance(ctx, c, rr, so.Ref, pw); err != nil { | ||||
| 							return err | ||||
| 						} | ||||
| 					} | ||||
|  | ||||
| 					node := dp.Node().Driver | ||||
| 					if node.IsMobyDriver() { | ||||
|   | ||||
							
								
								
									
										157
									
								
								build/provenance.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								build/provenance.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,157 @@ | ||||
| package build | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/json" | ||||
| 	"io" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
|  | ||||
| 	"github.com/containerd/containerd/content" | ||||
| 	"github.com/containerd/containerd/content/proxy" | ||||
| 	"github.com/docker/buildx/util/confutil" | ||||
| 	"github.com/docker/buildx/util/progress" | ||||
| 	controlapi "github.com/moby/buildkit/api/services/control" | ||||
| 	"github.com/moby/buildkit/client" | ||||
| 	provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types" | ||||
| 	ocispecs "github.com/opencontainers/image-spec/specs-go/v1" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"golang.org/x/sync/errgroup" | ||||
| ) | ||||
|  | ||||
| type provenancePredicate struct { | ||||
| 	Builder *provenanceBuilder `json:"builder,omitempty"` | ||||
| 	provenancetypes.ProvenancePredicate | ||||
| } | ||||
|  | ||||
| type provenanceBuilder struct { | ||||
| 	ID string `json:"id,omitempty"` | ||||
| } | ||||
|  | ||||
| func setRecordProvenance(ctx context.Context, c *client.Client, sr *client.SolveResponse, ref string, pw progress.Writer) error { | ||||
| 	mode := confutil.MetadataProvenance() | ||||
| 	if mode == confutil.MetadataProvenanceModeDisabled { | ||||
| 		return nil | ||||
| 	} | ||||
| 	pw = progress.ResetTime(pw) | ||||
| 	return progress.Wrap("resolving provenance for metadata file", pw.Write, func(l progress.SubLogger) error { | ||||
| 		res, err := fetchProvenance(ctx, c, ref, mode) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		for k, v := range res { | ||||
| 			sr.ExporterResponse[k] = v | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func fetchProvenance(ctx context.Context, c *client.Client, ref string, mode confutil.MetadataProvenanceMode) (out map[string]string, err error) { | ||||
| 	cl, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{ | ||||
| 		Ref:       ref, | ||||
| 		EarlyExit: true, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	var mu sync.Mutex | ||||
| 	eg, ctx := errgroup.WithContext(ctx) | ||||
| 	store := proxy.NewContentStore(c.ContentClient()) | ||||
| 	for { | ||||
| 		ev, err := cl.Recv() | ||||
| 		if errors.Is(err, io.EOF) { | ||||
| 			break | ||||
| 		} else if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if ev.Record == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		if ev.Record.Result != nil { | ||||
| 			desc := lookupProvenance(ev.Record.Result) | ||||
| 			if desc == nil { | ||||
| 				continue | ||||
| 			} | ||||
| 			eg.Go(func() error { | ||||
| 				dt, err := content.ReadBlob(ctx, store, *desc) | ||||
| 				if err != nil { | ||||
| 					return errors.Wrapf(err, "failed to load provenance blob from build record") | ||||
| 				} | ||||
| 				prv, err := encodeProvenance(dt, mode) | ||||
| 				if err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				mu.Lock() | ||||
| 				if out == nil { | ||||
| 					out = make(map[string]string) | ||||
| 				} | ||||
| 				out["buildx.build.provenance"] = prv | ||||
| 				mu.Unlock() | ||||
| 				return nil | ||||
| 			}) | ||||
| 		} else if ev.Record.Results != nil { | ||||
| 			for platform, res := range ev.Record.Results { | ||||
| 				platform := platform | ||||
| 				desc := lookupProvenance(res) | ||||
| 				if desc == nil { | ||||
| 					continue | ||||
| 				} | ||||
| 				eg.Go(func() error { | ||||
| 					dt, err := content.ReadBlob(ctx, store, *desc) | ||||
| 					if err != nil { | ||||
| 						return errors.Wrapf(err, "failed to load provenance blob from build record") | ||||
| 					} | ||||
| 					prv, err := encodeProvenance(dt, mode) | ||||
| 					if err != nil { | ||||
| 						return err | ||||
| 					} | ||||
| 					mu.Lock() | ||||
| 					if out == nil { | ||||
| 						out = make(map[string]string) | ||||
| 					} | ||||
| 					out["buildx.build.provenance/"+platform] = prv | ||||
| 					mu.Unlock() | ||||
| 					return nil | ||||
| 				}) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return out, eg.Wait() | ||||
| } | ||||
|  | ||||
| func lookupProvenance(res *controlapi.BuildResultInfo) *ocispecs.Descriptor { | ||||
| 	for _, a := range res.Attestations { | ||||
| 		if a.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(a.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") { | ||||
| 			return &ocispecs.Descriptor{ | ||||
| 				Digest:      a.Digest, | ||||
| 				Size:        a.Size_, | ||||
| 				MediaType:   a.MediaType, | ||||
| 				Annotations: a.Annotations, | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func encodeProvenance(dt []byte, mode confutil.MetadataProvenanceMode) (string, error) { | ||||
| 	var prv provenancePredicate | ||||
| 	if err := json.Unmarshal(dt, &prv); err != nil { | ||||
| 		return "", errors.Wrapf(err, "failed to unmarshal provenance") | ||||
| 	} | ||||
| 	if prv.Builder != nil && prv.Builder.ID == "" { | ||||
| 		// reset builder if id is empty | ||||
| 		prv.Builder = nil | ||||
| 	} | ||||
| 	if mode == confutil.MetadataProvenanceModeMin { | ||||
| 		// reset fields for minimal provenance | ||||
| 		prv.BuildConfig = nil | ||||
| 		prv.Metadata = nil | ||||
| 	} | ||||
| 	dtprv, err := json.Marshal(prv) | ||||
| 	if err != nil { | ||||
| 		return "", errors.Wrapf(err, "failed to marshal provenance") | ||||
| 	} | ||||
| 	return base64.StdEncoding.EncodeToString(dtprv), nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 CrazyMax
					CrazyMax