policy: separate sourcemetaresolver utility to reusable pkg

Turn the current utility used by policy helper commands that
allows getting gateway.Client lazily into reusable package.

This is preparation for using this utility also in the policy
verification, outside of commands.

Only expose SourceMetaResolver as other methods of gateway client
are not needed atm by any callers.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2026-02-13 13:59:33 -08:00
parent 044e152008
commit dce5289722
6 changed files with 350 additions and 132 deletions
+5 -16
View File
@@ -15,6 +15,7 @@ import (
"github.com/docker/buildx/builder"
"github.com/docker/buildx/policy"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/sourcemeta"
"github.com/docker/cli/cli/command"
"github.com/moby/buildkit/client/llb/sourceresolver"
"github.com/moby/buildkit/frontend/dockerui"
@@ -96,11 +97,8 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
OS: defaultPlatform.OS,
Variant: defaultPlatform.Variant,
}
openClient, release, err := gatewayClientFactory(c)
if err != nil {
return err
}
defer release()
metaResolver := sourcemeta.NewResolver(c)
defer metaResolver.Close()
platform := &pb.Platform{
Architecture: p.Architecture,
@@ -148,13 +146,8 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
if err := policy.AddUnknowns(req, toReload); err != nil {
return err
}
gwClient, err := openClient(ctx)
if err != nil {
return err
}
opt := sourceResolverOpt(req, &p)
resp, err := gwClient.ResolveSourceMetadata(ctx, src, opt)
resp, err := metaResolver.ResolveSourceMetadata(ctx, src, opt)
if err != nil {
return err
}
@@ -240,12 +233,8 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
return evalDecisionError(decision)
}
gwClient, err := openClient(ctx)
if err != nil {
return err
}
opt := sourceResolverOpt(next, &p)
resp, err := gwClient.ResolveSourceMetadata(ctx, src, opt)
resp, err := metaResolver.ResolveSourceMetadata(ctx, src, opt)
if err != nil {
return err
}
-80
View File
@@ -1,80 +0,0 @@
package policy
import (
"context"
"errors"
"sync"
"sync/atomic"
"github.com/moby/buildkit/client"
gwclient "github.com/moby/buildkit/frontend/gateway/client"
)
type gatewayClientOpener func(context.Context) (gwclient.Client, error)
func gatewayClientFactory(c *client.Client) (gatewayClientOpener, func() error, error) {
var (
once sync.Once
releaseOnce sync.Once
started atomic.Bool
ready = make(chan gwclient.Client, 1)
done = make(chan error, 1)
openErr error
releaseErr error
gwClient gwclient.Client
cancel context.CancelCauseFunc
)
open := func(ctx context.Context) (gwclient.Client, error) {
once.Do(func() {
started.Store(true)
buildCtx, cancelFn := context.WithCancelCause(ctx)
cancel = cancelFn
go func() {
_, err := c.Build(buildCtx, client.SolveOpt{Internal: true}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
ready <- c
<-buildCtx.Done()
return nil, context.Cause(buildCtx)
}, nil)
done <- err
}()
select {
case gwClient = <-ready:
case err := <-done:
if err == nil {
err = errors.New("gateway build finished without a client")
}
openErr = err
case <-ctx.Done():
openErr = context.Cause(ctx)
cancelFn(openErr)
}
})
if openErr != nil {
return nil, openErr
}
return gwClient, nil
}
release := func() error {
releaseOnce.Do(func() {
if !started.Load() {
return
}
if cancel != nil {
cancel(context.Canceled)
}
err := <-done
if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
return
}
releaseErr = err
})
return releaseErr
}
return open, release, nil
}
+21 -31
View File
@@ -14,6 +14,7 @@ import (
"github.com/docker/buildx/policy"
"github.com/docker/buildx/util/cobrautil"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/sourcemeta"
"github.com/docker/cli/cli/command"
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/solver/pb"
@@ -30,9 +31,9 @@ func testCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
Args: cobra.ExactArgs(1),
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) error {
resolver := newPolicyTestResolver(dockerCli, rootOpts.Builder)
opts.Resolver = resolver.Options()
defer resolver.Close()
optionsProvider := newPolicyTestOptionsProvider(dockerCli, rootOpts.Builder)
opts.Provider = optionsProvider.TestOptionsProvider()
defer optionsProvider.Close()
return runTest(cmd.Context(), cmd.OutOrStdout(), args[0], opts)
},
}
@@ -114,63 +115,58 @@ func withInputPrefix(keys []string) []string {
return out
}
type policyTestResolver struct {
type policyTestOptionsProvider struct {
dockerCli command.Cli
builderName *string
once sync.Once
platform *ocispecs.Platform
openClient gatewayClientOpener
release func() error
err error
once sync.Once
platform *ocispecs.Platform
metaResolver *sourcemeta.Resolver
err error
}
func newPolicyTestResolver(dockerCli command.Cli, builderName *string) *policyTestResolver {
return &policyTestResolver{
func newPolicyTestOptionsProvider(dockerCli command.Cli, builderName *string) *policyTestOptionsProvider {
return &policyTestOptionsProvider{
dockerCli: dockerCli,
builderName: builderName,
}
}
func (r *policyTestResolver) Options() *policy.TestResolver {
return &policy.TestResolver{
func (r *policyTestOptionsProvider) TestOptionsProvider() *policy.TestOptionsProvider {
return &policy.TestOptionsProvider{
Resolve: r.Resolve,
Platform: r.Platform,
VerifierProvider: policy.SignatureVerifier(confutil.NewConfig(r.dockerCli)),
}
}
func (r *policyTestResolver) Close() error {
if r.release == nil {
func (r *policyTestOptionsProvider) Close() error {
if r.metaResolver == nil {
return nil
}
return r.release()
return r.metaResolver.Close()
}
func (r *policyTestResolver) Platform(ctx context.Context) (*ocispecs.Platform, error) {
func (r *policyTestOptionsProvider) Platform(ctx context.Context) (*ocispecs.Platform, error) {
if err := r.init(ctx); err != nil {
return nil, err
}
return r.platform, nil
}
func (r *policyTestResolver) Resolve(ctx context.Context, source *pb.SourceOp, req *gwpb.ResolveSourceMetaRequest) (*gwpb.ResolveSourceMetaResponse, error) {
func (r *policyTestOptionsProvider) Resolve(ctx context.Context, source *pb.SourceOp, req *gwpb.ResolveSourceMetaRequest) (*gwpb.ResolveSourceMetaResponse, error) {
if err := r.init(ctx); err != nil {
return nil, err
}
gwClient, err := r.openClient(ctx)
if err != nil {
return nil, err
}
opt := sourceResolverOpt(req, r.platform)
resp, err := gwClient.ResolveSourceMetadata(ctx, source, opt)
resp, err := r.metaResolver.ResolveSourceMetadata(ctx, source, opt)
if err != nil {
return nil, err
}
return buildSourceMetaResponse(resp), nil
}
func (r *policyTestResolver) init(ctx context.Context) error {
func (r *policyTestOptionsProvider) init(ctx context.Context) error {
r.once.Do(func() {
bopts := []builder.Option{}
if r.builderName != nil {
@@ -208,13 +204,7 @@ func (r *policyTestResolver) init(ctx context.Context) error {
OS: defaultPlatform.OS,
Variant: defaultPlatform.Variant,
}
openClient, release, err := gatewayClientFactory(c)
if err != nil {
r.err = err
return
}
r.openClient = openClient
r.release = release
r.metaResolver = sourcemeta.NewResolver(c)
})
return r.err
}