Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
802 changes: 671 additions & 131 deletions api/services/control/control.pb.go

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions api/services/control/control.proto
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ message SolveRequest {
CacheOptions Cache = 8 [(gogoproto.nullable) = false];
repeated string Entitlements = 9 [(gogoproto.customtype) = "github.com/moby/buildkit/util/entitlements.Entitlement" ];
map<string, pb.Definition> FrontendInputs = 10;
SourcePolicy SourcePolicy = 11;
}

message CacheOptions {
Expand Down Expand Up @@ -163,3 +164,15 @@ message InfoRequest {}
message InfoResponse {
moby.buildkit.v1.types.BuildkitVersion buildkitVersion = 1;
}

message SourcePolicy {
// 1 is reserved for versioning
repeated SourcePolicySource Sources = 2;
Copy link
Member

@tonistiigi tonistiigi Aug 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

message SourcePolicySource {
	string Type = 1;
	SourcePolicyAction Action = 2; 
	string SourceIdentifier = 3; // wildcard string
	repeated SourceAttrConstraint = 4;
	
	string DestinationIdentifier = 5;
	map<string, string> DestinationAttr= 6;
	# repeated TrustChain RequiredSignatures = 7;
}

enum SourcePolicyAction {
	ALLOW
	DENY
	CONVERT
}

message SourceAttrConstraint {
	string AttrKey
	string AttrValue
	AttrMatch Condition
}

enum AttrMatch {
	EQUAL
	NOTEQUAL
	MATCHES
}

message SourcePolicy {
	int64 Version = 1; // Currently 1
	repeated SourcePolicySource Sources = 2;
}

I think this should be quite future compatible. SourceIdentifier is a wildcard comparison against https://github.com/moby/buildkit/blob/d1b0d8a/solver/pb/ops.proto#L179-L181 . Maybe without the protocol. Type is separately so that identifier value can be normalized based on it. Attrs can be used to match a specific attribute value.

Policy can allow, deny or convert a source. In case on conversion identifier is replaced and new attribute values can be defined. For example in images this would mean setting identifier to ref@sha256 and for HTTP it would mean setting the http.checksum attribute.

Every source is checked against all policy conditions. Earlier rule might deny the source, and next may allow it. If source is converted, then the new identifier needs to match the policy again (there needs to be some infinite loop protection).

Some examples:

No images are allowed except if they are busybox latest or any alpine:

[
{"docker-image", "DENY", "*"},
{"docker-image", "ALLOW", "docker.io/library/alpine:*"},
{"docker-image", "ALLOW", "docker.io/library/busybox:latest"},
]

Any Busybox points to Alpine (like a mirror), Alpine:latest is pinned (meaning busybox:latest is pinned as well)?

[
{"docker-image", "CONVERT", "docker.io/library/busybox:*", nil, "docker-image://docker.io/library/alpine:$1"},
{"docker-image", "CONVERT", "docker.io/library/alpine:latest", nil, "docker-image://docker.io/library/alpine:latest@sha256:123456"},
]

(might need a special key that sets the value a regexp?)

If build uses Alpine then use the local OCI-layout version instead

[
{"docker-image", "CONVERT", "docker.io/library/alpine:latest", nil, "oci-layout://contentstoreid@sha256"},
]

@AkihiroSuda WDYT?

fyi @slimslenderslacks

Copy link
Member Author

@AkihiroSuda AkihiroSuda Aug 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tonistiigi

SourcePolicyAction

One concern is that this policy structure looks an imperative program (like iptables rules) rather than a declarative structure (like go.mod and go.sum).
It might be hard to implement an automated locking tool like go mod tidy.

map<string, string>= 6;

What is this map for?

int64 Version = 1; // Currently 1

Do we need version? #2943 (comment)

docker-image://docker.io/library/alpine:$1

What is $1?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One concern is that this policy structure looks an imperative program (like iptables rules) rather than a declarative structure (like go.mod and go.sum).

If the goal is for user to actually define policies, eg. images that are allowed/denied, require certain signatures etc then it can't be done with something like go mod tidy. If you are only interested of pinning, then you would only have convert rules in your policy and then you can do automatic update based on some conditions etc. Pinning is a subset, but you can use only that if you want to.

The way I would imagine this defined is that you have one policy for your project and one for your whole host. Then they would be applied on top of each other. In the host file you, for example, would likely not put any pins. In the project one, you would likely not deny a specific image but either deny all (undefined) images or define specific signatures that your images require(also something that tidy possibly can't do). Then there are the use-cases where you use conversion rules the same as --build-context today during dev without a specific policy file.

What is this map for?

New Attrs for the conversion result, fixed.

Do we need version?

No, it was just carried over.

What is $1?

First match for the wildcard component. Same as regexp. I'm flexible on how this would be actually defined. Eg. in that example busybox:test would be converted to alpine:test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpuguy83 SGTY?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of using a oneof instead of special keys like oci-layout://? I guess the API has to be appended to for any new format to be supported (maybe not bad for policy like this).

I'm a bit worried about bugs in the policy engine leading to infinite loops or possibly suprising behaviors with the way things are suggested here.
Taking the example:

[
{"docker-image", "DENY", "*"},
{"docker-image", "ALLOW", "docker.io/library/alpine:*"},
{"docker-image", "ALLOW", "docker.io/library/busybox:latest"},
]

I would almost say if someone were to append a CONVERT rule where the original ref is not already allowed then the image should be denied.

Just trying to think of all the cases here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have consensus on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cpuguy83 no objections in over a week, so tempted to say that we do have consensus 😄

Copy link
Member Author

@AkihiroSuda AkihiroSuda Oct 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consensus on avoiding OPA/Rego, and considering wasm later.
But still not sure how the buildkit-specific config would look like (to machine and humans).

Is anyone working on design and POC?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to machine

Anything missing from the proposal on top of this comment?

and humans

I think this shouldn't be a requirement for this PR. I imagine the human format would also for example have policy for pushes that is not part of buildkit solver at all, just validation for the build parameters.

}

message SourcePolicySource {
string Type = 1;
string Ref = 2;
string Pin = 3;
// TODO: string (?) Replace = 4;
}
82 changes: 82 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"github.com/moby/buildkit/session/sshforward/sshprovider"
"github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/sourcepolicy"
binfotypes "github.com/moby/buildkit/util/buildinfo/types"
"github.com/moby/buildkit/util/contentutil"
"github.com/moby/buildkit/util/entitlements"
Expand Down Expand Up @@ -167,6 +168,7 @@ func TestIntegration(t *testing.T) {
testCallInfo,
testPullWithLayerLimit,
testExportAnnotations,
testSourcePolicy,
)
tests = append(tests, diffOpTestCases()...)
integration.Run(t, tests, mirrors)
Expand Down Expand Up @@ -6325,3 +6327,83 @@ func fixedWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser
return wc, nil
}
}

func testSourcePolicy(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)
c, err := New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

frontend := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
st := llb.Image("busybox:1.34.1-uclibc").File(
llb.Copy(llb.HTTP("https://raw.githubusercontent.com/moby/buildkit/v0.10.1/README.md"),
"README.md", "README.md"))
def, err := st.Marshal(sb.Context())
if err != nil {
return nil, err
}
return c.Solve(ctx, gateway.SolveRequest{
Definition: def.ToPB(),
})
}

type testCase struct {
srcPol *sourcepolicy.SourcePolicy
expectedErr string
}
testCases := []testCase{
{
// Valid
srcPol: &sourcepolicy.SourcePolicy{
Sources: []sourcepolicy.Source{
{
Type: "docker-image",
Ref: "docker.io/library/busybox:1.34.1-uclibc",
Pin: "sha256:3614ca5eacf0a3a1bcc361c939202a974b4902b9334ff36eb29ffe9011aaad83",
},
{
Type: "http",
Ref: "https://raw.githubusercontent.com/moby/buildkit/v0.10.1/README.md",
Pin: "sha256:6e4b94fc270e708e1068be28bd3551dc6917a4fc5a61293d51bb36e6b75c4b53",
},
},
},
expectedErr: "",
},
{
// Invalid docker-image source
srcPol: &sourcepolicy.SourcePolicy{
Sources: []sourcepolicy.Source{
{
Type: "docker-image",
Ref: "docker.io/library/busybox:1.34.1-uclibc",
Pin: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // invalid
},
},
},
expectedErr: "docker.io/library/busybox:1.34.1-uclibc@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: not found",
},
{
// Invalid http source
srcPol: &sourcepolicy.SourcePolicy{
Sources: []sourcepolicy.Source{
{
Type: "http",
Ref: "https://raw.githubusercontent.com/moby/buildkit/v0.10.1/README.md",
Pin: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", // invalid
},
},
},
expectedErr: "digest mismatch sha256:6e4b94fc270e708e1068be28bd3551dc6917a4fc5a61293d51bb36e6b75c4b53: sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
},
}
for _, tc := range testCases {
_, err = c.Build(sb.Context(), SolveOpt{SourcePolicy: tc.srcPol}, "", frontend, nil)
if tc.expectedErr == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
require.Contains(t, err.Error(), tc.expectedErr)
}
}
}
16 changes: 8 additions & 8 deletions client/llb/llbtest/platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestCustomPlatform(t *testing.T) {
def, err := s.Marshal(context.TODO())
require.NoError(t, err)

e, err := llbsolver.Load(def.ToPB())
e, err := llbsolver.Load(context.TODO(), def.ToPB(), nil)
require.NoError(t, err)

require.Equal(t, depth(e), 5)
Expand Down Expand Up @@ -56,7 +56,7 @@ func TestDefaultPlatform(t *testing.T) {
def, err := s.Marshal(context.TODO())
require.NoError(t, err)

e, err := llbsolver.Load(def.ToPB())
e, err := llbsolver.Load(context.TODO(), def.ToPB(), nil)
require.NoError(t, err)

require.Equal(t, depth(e), 2)
Expand All @@ -80,7 +80,7 @@ func TestPlatformOnMarshal(t *testing.T) {
def, err := s.Marshal(context.TODO(), llb.Windows)
require.NoError(t, err)

e, err := llbsolver.Load(def.ToPB())
e, err := llbsolver.Load(context.TODO(), def.ToPB(), nil)
require.NoError(t, err)

expected := ocispecs.Platform{OS: "windows", Architecture: "amd64"}
Expand All @@ -100,7 +100,7 @@ func TestPlatformMixed(t *testing.T) {
def, err := s1.Marshal(context.TODO(), llb.LinuxAmd64)
require.NoError(t, err)

e, err := llbsolver.Load(def.ToPB())
e, err := llbsolver.Load(context.TODO(), def.ToPB(), nil)
require.NoError(t, err)

require.Equal(t, depth(e), 4)
Expand Down Expand Up @@ -129,7 +129,7 @@ func TestFallbackPath(t *testing.T) {
// the cap.
def, err := llb.Scratch().Run(llb.Shlex("cmd")).Marshal(context.TODO(), llb.LinuxAmd64)
require.NoError(t, err)
e, err := llbsolver.Load(def.ToPB())
e, err := llbsolver.Load(context.TODO(), def.ToPB(), nil)
require.NoError(t, err)
require.False(t, def.Metadata[e.Vertex.Digest()].Caps[pb.CapExecMetaSetsDefaultPath])
_, ok := getenv(e, "PATH")
Expand All @@ -141,7 +141,7 @@ func TestFallbackPath(t *testing.T) {
require.Error(t, cs.Supports(pb.CapExecMetaSetsDefaultPath))
def, err = llb.Scratch().Run(llb.Shlex("cmd")).Marshal(context.TODO(), llb.LinuxAmd64, llb.WithCaps(cs))
require.NoError(t, err)
e, err = llbsolver.Load(def.ToPB())
e, err = llbsolver.Load(context.TODO(), def.ToPB(), nil)
require.NoError(t, err)
require.False(t, def.Metadata[e.Vertex.Digest()].Caps[pb.CapExecMetaSetsDefaultPath])
v, ok := getenv(e, "PATH")
Expand All @@ -155,7 +155,7 @@ func TestFallbackPath(t *testing.T) {
require.NoError(t, cs.Supports(pb.CapExecMetaSetsDefaultPath))
def, err = llb.Scratch().Run(llb.Shlex("cmd")).Marshal(context.TODO(), llb.LinuxAmd64, llb.WithCaps(cs))
require.NoError(t, err)
e, err = llbsolver.Load(def.ToPB())
e, err = llbsolver.Load(context.TODO(), def.ToPB(), nil)
require.NoError(t, err)
require.True(t, def.Metadata[e.Vertex.Digest()].Caps[pb.CapExecMetaSetsDefaultPath])
_, ok = getenv(e, "PATH")
Expand All @@ -171,7 +171,7 @@ func TestFallbackPath(t *testing.T) {
} {
def, err = llb.Scratch().AddEnv("PATH", "foo").Run(llb.Shlex("cmd")).Marshal(context.TODO(), append(cos, llb.LinuxAmd64)...)
require.NoError(t, err)
e, err = llbsolver.Load(def.ToPB())
e, err = llbsolver.Load(context.TODO(), def.ToPB(), nil)
require.NoError(t, err)
// pb.CapExecMetaSetsDefaultPath setting is irrelevant (and variable).
v, ok = getenv(e, "PATH")
Expand Down
15 changes: 15 additions & 0 deletions client/solve.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/moby/buildkit/session/filesync"
"github.com/moby/buildkit/session/grpchijack"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/sourcepolicy"
"github.com/moby/buildkit/util/bklog"
"github.com/moby/buildkit/util/entitlements"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
Expand All @@ -44,6 +45,7 @@ type SolveOpt struct {
AllowedEntitlements []entitlements.Entitlement
SharedSession *session.Session // TODO: refactor to better session syncing
SessionPreInitialized bool // TODO: refactor to better session syncing
SourcePolicy *sourcepolicy.SourcePolicy
}

type ExportEntry struct {
Expand Down Expand Up @@ -198,6 +200,18 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG
opt.FrontendAttrs[k] = v
}

var srcPol *controlapi.SourcePolicy
if opt.SourcePolicy != nil {
srcPol = &controlapi.SourcePolicy{}
for _, f := range opt.SourcePolicy.Sources {
srcPol.Sources = append(srcPol.Sources, &controlapi.SourcePolicySource{
Type: string(f.Type),
Ref: f.Ref,
Pin: f.Pin,
})
}
}

solveCtx, cancelSolve := context.WithCancel(ctx)
var res *SolveResponse
eg.Go(func() error {
Expand Down Expand Up @@ -239,6 +253,7 @@ func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runG
FrontendInputs: frontendInputs,
Cache: cacheOpt.options,
Entitlements: opt.AllowedEntitlements,
SourcePolicy: srcPol,
})
if err != nil {
return errors.Wrap(err, "failed to solve")
Expand Down
19 changes: 19 additions & 0 deletions cmd/buildctl/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/moby/buildkit/session/auth/authprovider"
"github.com/moby/buildkit/session/sshforward/sshprovider"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/sourcepolicy"
"github.com/moby/buildkit/util/progress/progresswriter"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
Expand Down Expand Up @@ -91,6 +92,10 @@ var buildCommand = cli.Command{
Name: "metadata-file",
Usage: "Output build metadata (e.g., image digest) to a file as JSON",
},
cli.StringFlag{
Name: "source-policy-file",
Usage: "Read source policy file from a JSON file",
},
},
}

Expand Down Expand Up @@ -185,6 +190,19 @@ func buildAction(clicontext *cli.Context) error {
return err
}

var srcPol *sourcepolicy.SourcePolicy
if srcPolFile := clicontext.String("source-policy-file"); srcPolFile != "" {
b, err := os.ReadFile(srcPolFile)
if err != nil {
return err
}
var srcPolStruct sourcepolicy.SourcePolicy
if err := json.Unmarshal(b, &srcPolStruct); err != nil {
return errors.Wrapf(err, "failed to unmarshal source-policy-file %q", srcPolFile)
}
srcPol = &srcPolStruct
}

eg, ctx := errgroup.WithContext(bccommon.CommandContext(clicontext))

solveOpt := client.SolveOpt{
Expand All @@ -197,6 +215,7 @@ func buildAction(clicontext *cli.Context) error {
CacheImports: cacheImports,
Session: attachable,
AllowedEntitlements: allowed,
SourcePolicy: srcPol,
}

solveOpt.FrontendAttrs, err = build.ParseOpt(clicontext.StringSlice("opt"))
Expand Down
15 changes: 14 additions & 1 deletion control/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/moby/buildkit/solver"
"github.com/moby/buildkit/solver/llbsolver"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/sourcepolicy"
"github.com/moby/buildkit/util/bklog"
"github.com/moby/buildkit/util/imageutil"
"github.com/moby/buildkit/util/throttle"
Expand Down Expand Up @@ -306,6 +307,18 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
Attrs: im.Attrs,
})
}
var srcPol *sourcepolicy.SourcePolicy
if req.SourcePolicy != nil {
srcPol = &sourcepolicy.SourcePolicy{}
for _, f := range req.SourcePolicy.Sources {
s := sourcepolicy.Source{
Type: sourcepolicy.SourceType(f.Type),
Ref: f.Ref,
Pin: f.Pin,
}
srcPol.Sources = append(srcPol.Sources, s)
}
}

resp, err := c.solver.Solve(ctx, req.Ref, req.Session, frontend.SolveRequest{
Frontend: req.Frontend,
Expand All @@ -317,7 +330,7 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
Exporter: expi,
CacheExporter: cacheExporter,
CacheExportMode: cacheExportMode,
}, req.Entitlements)
}, req.Entitlements, srcPol)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the policy is part of controlapi.SolveRequest, it should probably be passed as part of frontend.SolveRequest?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decoupled from frontend.SolveRequest to follow the entitlement model
#2816 (comment)

if err != nil {
return nil, err
}
Expand Down
35 changes: 35 additions & 0 deletions docs/build-repro.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,38 @@ jq '.' metadata.json
"containerimage.digest": "sha256:..."
}
```

### Reproducing the pinned dependencies

<!-- TODO: s/master/v0.11/ after the release -->
Reproducing the pinned dependencies is supported in the master branch of BuildKit.

e.g.,
```bash
buildctl build --frontend dockerfile.v0 --local dockerfile=. --local context=. --source-policy-file Dockerfile.pol
```

An example `Dockerfile.pol`:
```json
{
"sources": [
{
"type": "docker-image",
"ref": "docker.io/library/alpine:latest",
"pin": "sha256:4edbd2beb5f78b1014028f4fbb99f3237d9561100b6881aabbf5acce2c4f9454"
},
{
"type": "http",
"ref": "https://raw.githubusercontent.com/moby/buildkit/v0.10.1/README.md",
"pin": "sha256:6e4b94fc270e708e1068be28bd3551dc6917a4fc5a61293d51bb36e6b75c4b53"
}
]
}
```

* `sources`: a subset of the `."containerimage.buildinfo".sources` property of the `metadata.json`

Reproduction is currently supported for the following source types:
* `docker-image`
* `http`
<!-- TODO: git -->
15 changes: 14 additions & 1 deletion solver/llbsolver/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import (
"github.com/moby/buildkit/solver"
"github.com/moby/buildkit/solver/errdefs"
llberrdefs "github.com/moby/buildkit/solver/llbsolver/errdefs"
"github.com/moby/buildkit/solver/llbsolver/llbmutator"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/sourcepolicy/sourcepolicyllbmutator"
"github.com/moby/buildkit/util/bklog"
"github.com/moby/buildkit/util/buildinfo"
"github.com/moby/buildkit/util/flightcontrol"
Expand Down Expand Up @@ -72,6 +74,17 @@ func (b *llbBridge) loadResult(ctx context.Context, def *pb.Definition, cacheImp
if err != nil {
return nil, nil, err
}
srcPol, err := loadSourcePolicy(b.builder)
if err != nil {
return nil, nil, err
}
var llbMutator llbmutator.LLBMutator
if srcPol != nil {
llbMutator, err = sourcepolicyllbmutator.New(srcPol)
if err != nil {
return nil, nil, err
}
}
var cms []solver.CacheManager
for _, im := range cacheImports {
cmID, err := cmKey(im)
Expand Down Expand Up @@ -111,7 +124,7 @@ func (b *llbBridge) loadResult(ctx context.Context, def *pb.Definition, cacheImp
}
dpc := &detectPrunedCacheID{}

edge, err := Load(def, dpc.Load, ValidateEntitlements(ent), WithCacheSources(cms), NormalizeRuntimePlatforms(), WithValidateCaps())
edge, err := Load(ctx, def, llbMutator, dpc.Load, ValidateEntitlements(ent), WithCacheSources(cms), NormalizeRuntimePlatforms(), WithValidateCaps())
if err != nil {
return nil, nil, errors.Wrap(err, "failed to load LLB")
}
Expand Down
Loading