55 "encoding/json"
66 "fmt"
77 "io/fs"
8+ "maps"
89 "os"
910 "path/filepath"
1011 "slices"
@@ -17,7 +18,6 @@ import (
1718 "github.com/docker/buildx/util/confutil"
1819 "github.com/docker/buildx/util/sourcemeta"
1920 "github.com/docker/cli/cli/command"
20- "github.com/moby/buildkit/client/llb/sourceresolver"
2121 "github.com/moby/buildkit/frontend/dockerui"
2222 gwpb "github.com/moby/buildkit/frontend/gateway/pb"
2323 "github.com/moby/buildkit/solver/pb"
@@ -27,7 +27,6 @@ import (
2727 "github.com/pkg/errors"
2828 "github.com/sirupsen/logrus"
2929 "github.com/spf13/cobra"
30- "google.golang.org/protobuf/types/known/timestamppb"
3130)
3231
3332type evalOpts struct {
@@ -111,60 +110,66 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
111110 srcReq := & gwpb.ResolveSourceMetaResponse {
112111 Source : src ,
113112 }
113+ input , err := policy .SourceToInput (ctx , verifier , srcReq , & p , nil )
114+ if err != nil {
115+ return err
116+ }
114117 maxAttempts := 5
115- var unknowns []string
116118 var lastUnknowns []string
117119 var trimmedUnknowns []string
118- var input policy.Input
119- var doneInvalidCheck bool
120120 var invalidFields []string
121121 for {
122122 maxAttempts --
123123 if maxAttempts <= 0 {
124124 return errors .New ("maximum attempts reached for resolving source metadata" )
125125 }
126- input , unknowns , err = policy .SourceToInput (ctx , verifier , srcReq , & p )
127- if err != nil {
128- return err
126+ unknowns := input .Unknowns ()
127+ trimmedUnknowns = make ([]string , 0 , len (unknowns ))
128+ for _ , u := range unknowns {
129+ trimmedUnknowns = append (trimmedUnknowns , strings .TrimPrefix (u , "input." ))
129130 }
130- trimmedUnknowns = trimInputPrefixSlice (unknowns )
131131 if lastUnknowns != nil && slices .Equal (trimmedUnknowns , lastUnknowns ) {
132132 break
133133 }
134134 lastUnknowns = slices .Clone (trimmedUnknowns )
135- toReload := []string {}
136- for _ , f := range opts .fields {
137- if slices .Contains (trimmedUnknowns , f ) {
138- toReload = append (toReload , f )
139- } else if ! doneInvalidCheck {
140- invalidFields = append (invalidFields , f )
141- }
142- }
143- doneInvalidCheck = true
135+ toReload , invalid := selectReloadFields (opts .fields , trimmedUnknowns )
136+ invalidFields = invalid
144137 if len (toReload ) > 0 {
145- req := & gwpb.ResolveSourceMetaRequest {}
146- if err := policy .AddUnknowns (req , toReload ); err != nil {
147- return err
148- }
149- opt := sourceResolverOpt (req , & p )
150- resp , err := metaResolver .ResolveSourceMetadata (ctx , src , opt )
138+ retry , next , err := policy .ResolveInputUnknowns (ctx , & input , srcReq .Source , toReload , platform , & p , metaResolver , verifier , nil )
151139 if err != nil {
152140 return err
153141 }
154- srcReq = buildSourceMetaResponse (resp )
155- continue
142+ if next != nil {
143+ resp , err := metaResolver .ResolveSourceMetadata (ctx , next .Source , sourcemeta .ToResolverOpt (next , & p ))
144+ if err != nil {
145+ return err
146+ }
147+ srcReq = sourcemeta .ToGatewayMetaResponse (resp )
148+ input , err = policy .SourceToInput (ctx , verifier , srcReq , & p , nil )
149+ if err != nil {
150+ return err
151+ }
152+ continue
153+ }
154+ if retry {
155+ continue
156+ }
156157 }
157158 break
158159 }
159160
160161 if len (invalidFields ) > 0 {
161162 logrus .Warnf ("invalid fields: %v" , strings .Join (invalidFields , ", " ))
162163 }
163- if len (trimmedUnknowns ) > 0 {
164- logrus .Infof ("unresolved fields: %v" , strings .Join (trimmedUnknowns , ", " ))
164+ reportedUnknowns := summarizeEvalUnknowns (trimmedUnknowns , opts .fields )
165+ if len (reportedUnknowns ) > 0 {
166+ logrus .Infof ("unresolved fields: %v" , strings .Join (reportedUnknowns , ", " ))
165167 }
166168
167- dt , err := json .MarshalIndent (input , "" , " " )
169+ printInput := input
170+ sanitizePrintInput (& printInput )
171+
172+ dt , err := json .MarshalIndent (printInput , "" , " " )
168173 if err != nil {
169174 return errors .Wrap (err , "failed to marshal policy input" )
170175 }
@@ -198,6 +203,9 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
198203 env := policy.Env {
199204 Filename : filepath .Base (policyName ),
200205 }
206+ policyLog := func (_ logrus.Level , msg string ) {
207+ logrus .Debug (msg )
208+ }
201209
202210 policyEval := policy .NewPolicy (policy.Opt {
203211 Files : []policy.File {
@@ -207,9 +215,11 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
207215 },
208216 },
209217 Env : env ,
218+ Log : policyLog ,
210219 FS : fsProvider ,
211220 VerifierProvider : verifier ,
212221 DefaultPlatform : & p ,
222+ SourceResolver : metaResolver ,
213223 })
214224
215225 srcReq := & gwpb.ResolveSourceMetaResponse {
@@ -233,114 +243,153 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
233243 return evalDecisionError (decision )
234244 }
235245
236- opt := sourceResolverOpt (next , & p )
237- resp , err := metaResolver .ResolveSourceMetadata (ctx , src , opt )
246+ opt := sourcemeta .ToResolverOpt (next , & p )
247+ target := src
248+ if next .Source != nil {
249+ target = next .Source
250+ }
251+ resp , err := metaResolver .ResolveSourceMetadata (ctx , target , opt )
238252 if err != nil {
239253 return err
240254 }
241- srcReq = buildSourceMetaResponse (resp )
255+ srcReq = sourcemeta . ToGatewayMetaResponse (resp )
242256 }
243257}
244258
245- func toGatewayDescriptor (desc ocispecs.Descriptor ) * gwpb.Descriptor {
246- return & gwpb.Descriptor {
247- MediaType : desc .MediaType ,
248- Digest : desc .Digest .String (),
249- Size : desc .Size ,
250- Annotations : desc .Annotations ,
251- }
252- }
253-
254- func toGatewayAttestationChain (chain * sourceresolver.AttestationChain ) * gwpb.AttestationChain {
255- if chain == nil {
256- return nil
257- }
258- signatures := make ([]string , 0 , len (chain .SignatureManifests ))
259- for _ , dgst := range chain .SignatureManifests {
260- signatures = append (signatures , dgst .String ())
259+ func selectReloadFields (fields []string , unknowns []string ) ([]string , []string ) {
260+ if len (fields ) == 0 {
261+ return nil , nil
261262 }
262- blobs := make (map [string ]* gwpb.Blob , len (chain .Blobs ))
263- for dgst , blob := range chain .Blobs {
264- blobs [dgst .String ()] = & gwpb.Blob {
265- Descriptor_ : toGatewayDescriptor (blob .Descriptor ),
266- Data : blob .Data ,
263+ reload := map [string ]struct {}{}
264+ var invalid []string
265+ for _ , field := range fields {
266+ if prereq , ok := materialFieldPrerequisites (field ); ok {
267+ added := false
268+ for _ , p := range prereq {
269+ if slices .Contains (unknowns , p ) {
270+ reload [p ] = struct {}{}
271+ added = true
272+ }
273+ }
274+ if slices .Contains (unknowns , field ) {
275+ reload [field ] = struct {}{}
276+ added = true
277+ } else if ancestor := findUnknownAncestor (field , unknowns ); ancestor != "" {
278+ reload [ancestor ] = struct {}{}
279+ added = true
280+ }
281+ if ! added {
282+ invalid = append (invalid , field )
283+ }
284+ continue
267285 }
286+ if slices .Contains (unknowns , field ) {
287+ reload [field ] = struct {}{}
288+ continue
289+ }
290+ invalid = append (invalid , field )
268291 }
269- return & gwpb.AttestationChain {
270- Root : chain .Root .String (),
271- ImageManifest : chain .ImageManifest .String (),
272- AttestationManifest : chain .AttestationManifest .String (),
273- SignatureManifests : signatures ,
274- Blobs : blobs ,
275- }
292+ return slices .Collect (maps .Keys (reload )), invalid
276293}
277294
278- func sourceResolverOpt (req * gwpb.ResolveSourceMetaRequest , platform * ocispecs.Platform ) sourceresolver.Opt {
279- opt := sourceresolver.Opt {
280- LogName : req .LogName ,
281- SourcePolicies : req .SourcePolicies ,
282- }
283- if req .Image != nil {
284- opt .ImageOpt = & sourceresolver.ResolveImageOpt {
285- NoConfig : req .Image .NoConfig ,
286- AttestationChain : req .Image .AttestationChain ,
287- ResolveAttestations : slices .Clone (req .Image .ResolveAttestations ),
288- Platform : platform ,
289- ResolveMode : req .ResolveMode ,
295+ func findUnknownAncestor (field string , unknowns []string ) string {
296+ var best string
297+ for _ , unknown := range unknowns {
298+ if field == unknown {
299+ return unknown
290300 }
291- }
292- if req .Git != nil {
293- opt .GitOpt = & sourceresolver.ResolveGitOpt {
294- ReturnObject : req .Git .ReturnObject ,
301+ if strings .HasPrefix (field , unknown + "." ) {
302+ if len (unknown ) > len (best ) {
303+ best = unknown
304+ }
305+ continue
306+ }
307+ if strings .HasPrefix (field , unknown + "[" ) {
308+ if len (unknown ) > len (best ) {
309+ best = unknown
310+ }
295311 }
296312 }
297- return opt
313+ return best
314+ }
315+
316+ func materialFieldPrerequisites (field string ) ([]string , bool ) {
317+ const seg = ".image.provenance.materials["
318+ if ! strings .HasPrefix (field , seg [1 :]) {
319+ return nil , false
320+ }
321+ provenancePath := strings .TrimSuffix (seg , ".materials[" )
322+ out := map [string ]struct {}{strings .TrimPrefix (provenancePath , "." ): {}}
323+ collectMaterialPrerequisites (field , seg , provenancePath , 0 , out )
324+ keys := slices .Collect (maps .Keys (out ))
325+ slices .Sort (keys )
326+ return keys , true
327+ }
328+
329+ func collectMaterialPrerequisites (field , seg , provenancePath string , start int , out map [string ]struct {}) {
330+ i := strings .Index (field [start :], seg )
331+ if i < 0 {
332+ return
333+ }
334+ i += start
335+ out [field [:i ]+ provenancePath ] = struct {}{}
336+ collectMaterialPrerequisites (field , seg , provenancePath , i + len (seg ), out )
298337}
299338
300- func buildSourceMetaResponse ( resp * sourceresolver. MetaResponse ) * gwpb. ResolveSourceMetaResponse {
301- out := & gwpb. ResolveSourceMetaResponse {
302- Source : resp . Op ,
339+ func summarizeEvalUnknowns ( unknowns , requested [] string ) [] string {
340+ if len ( unknowns ) == 0 {
341+ return nil
303342 }
304- if resp .Image != nil {
305- chain := toGatewayAttestationChain (resp .Image .AttestationChain )
306- out .Image = & gwpb.ResolveSourceImageResponse {
307- Digest : resp .Image .Digest .String (),
308- Config : resp .Image .Config ,
309- AttestationChain : chain ,
343+ if len (requested ) > 0 {
344+ out := map [string ]struct {}{}
345+ for _ , field := range requested {
346+ if slices .Contains (unknowns , field ) {
347+ out [field ] = struct {}{}
348+ continue
349+ }
350+ if ancestor := findUnknownAncestor (field , unknowns ); ancestor != "" {
351+ out [ancestor ] = struct {}{}
352+ }
310353 }
354+ keys := slices .Collect (maps .Keys (out ))
355+ slices .Sort (keys )
356+ return keys
311357 }
312- if resp .Git != nil {
313- out .Git = & gwpb.ResolveSourceGitResponse {
314- Checksum : resp .Git .Checksum ,
315- Ref : resp .Git .Ref ,
316- CommitChecksum : resp .Git .CommitChecksum ,
317- CommitObject : resp .Git .CommitObject ,
318- TagObject : resp .Git .TagObject ,
319- }
358+
359+ out := map [string ]struct {}{}
360+ for _ , u := range unknowns {
361+ out [summarizeUnknownField (u )] = struct {}{}
320362 }
321- if resp .HTTP != nil {
322- var lastModified * timestamppb.Timestamp
323- if resp .HTTP .LastModified != nil {
324- lastModified = timestamppb .New (* resp .HTTP .LastModified )
325- }
326- out .HTTP = & gwpb.ResolveSourceHTTPResponse {
327- Checksum : resp .HTTP .Digest .String (),
328- Filename : resp .HTTP .Filename ,
329- LastModified : lastModified ,
330- }
363+ keys := slices .Collect (maps .Keys (out ))
364+ slices .Sort (keys )
365+ return keys
366+ }
367+
368+ func summarizeUnknownField (field string ) string {
369+ if base , _ , ok := strings .Cut (field , ".materials[" ); ok {
370+ return base + ".materials"
371+ }
372+ if strings .HasPrefix (field , "materials[" ) {
373+ return "materials"
374+ }
375+ parts := strings .Split (field , "." )
376+ if len (parts ) > 1 {
377+ return strings .Join (parts [:2 ], "." )
331378 }
332- return out
379+ return field
333380}
334381
335- func trimInputPrefixSlice ( fields [] string ) [] string {
336- if len ( fields ) == 0 {
337- return fields
382+ func sanitizePrintInput ( inp * policy. Input ) {
383+ if inp == nil {
384+ return
338385 }
339- out := make ([]string , 0 , len (fields ))
340- for _ , field := range fields {
341- out = append (out , strings .TrimPrefix (field , "input." ))
386+ inp .Env .Depth = 0
387+ if inp .Image == nil || inp .Image .Provenance == nil || len (inp .Image .Provenance .Materials ) == 0 {
388+ return
389+ }
390+ for i := range inp .Image .Provenance .Materials {
391+ sanitizePrintInput (& inp .Image .Provenance .Materials [i ])
342392 }
343- return out
344393}
345394
346395func evalDecisionError (decision * policysession.DecisionResponse ) error {
0 commit comments