Skip to content

Commit b71956d

Browse files
committed
policy: simplify recursive material resolution
Unify root/material unknown resolution with recursive Input traversal. Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 2809ca7 commit b71956d

File tree

27 files changed

+1829
-249
lines changed

27 files changed

+1829
-249
lines changed

commands/policy/eval.go

Lines changed: 161 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
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

3332
type 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

346395
func evalDecisionError(decision *policysession.DecisionResponse) error {

0 commit comments

Comments
 (0)