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:
+5
-16
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/policy"
|
"github.com/docker/buildx/policy"
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/sourcemeta"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/client/llb/sourceresolver"
|
"github.com/moby/buildkit/client/llb/sourceresolver"
|
||||||
"github.com/moby/buildkit/frontend/dockerui"
|
"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,
|
OS: defaultPlatform.OS,
|
||||||
Variant: defaultPlatform.Variant,
|
Variant: defaultPlatform.Variant,
|
||||||
}
|
}
|
||||||
openClient, release, err := gatewayClientFactory(c)
|
metaResolver := sourcemeta.NewResolver(c)
|
||||||
if err != nil {
|
defer metaResolver.Close()
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer release()
|
|
||||||
|
|
||||||
platform := &pb.Platform{
|
platform := &pb.Platform{
|
||||||
Architecture: p.Architecture,
|
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 {
|
if err := policy.AddUnknowns(req, toReload); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gwClient, err := openClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
opt := sourceResolverOpt(req, &p)
|
opt := sourceResolverOpt(req, &p)
|
||||||
resp, err := gwClient.ResolveSourceMetadata(ctx, src, opt)
|
resp, err := metaResolver.ResolveSourceMetadata(ctx, src, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -240,12 +233,8 @@ func runEval(ctx context.Context, dockerCli command.Cli, source string, opts eva
|
|||||||
return evalDecisionError(decision)
|
return evalDecisionError(decision)
|
||||||
}
|
}
|
||||||
|
|
||||||
gwClient, err := openClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
opt := sourceResolverOpt(next, &p)
|
opt := sourceResolverOpt(next, &p)
|
||||||
resp, err := gwClient.ResolveSourceMetadata(ctx, src, opt)
|
resp, err := metaResolver.ResolveSourceMetadata(ctx, src, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/docker/buildx/policy"
|
"github.com/docker/buildx/policy"
|
||||||
"github.com/docker/buildx/util/cobrautil"
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/sourcemeta"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
@@ -30,9 +31,9 @@ func testCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
|||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
resolver := newPolicyTestResolver(dockerCli, rootOpts.Builder)
|
optionsProvider := newPolicyTestOptionsProvider(dockerCli, rootOpts.Builder)
|
||||||
opts.Resolver = resolver.Options()
|
opts.Provider = optionsProvider.TestOptionsProvider()
|
||||||
defer resolver.Close()
|
defer optionsProvider.Close()
|
||||||
return runTest(cmd.Context(), cmd.OutOrStdout(), args[0], opts)
|
return runTest(cmd.Context(), cmd.OutOrStdout(), args[0], opts)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -114,63 +115,58 @@ func withInputPrefix(keys []string) []string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
type policyTestResolver struct {
|
type policyTestOptionsProvider struct {
|
||||||
dockerCli command.Cli
|
dockerCli command.Cli
|
||||||
builderName *string
|
builderName *string
|
||||||
|
|
||||||
once sync.Once
|
once sync.Once
|
||||||
platform *ocispecs.Platform
|
platform *ocispecs.Platform
|
||||||
openClient gatewayClientOpener
|
metaResolver *sourcemeta.Resolver
|
||||||
release func() error
|
err error
|
||||||
err error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPolicyTestResolver(dockerCli command.Cli, builderName *string) *policyTestResolver {
|
func newPolicyTestOptionsProvider(dockerCli command.Cli, builderName *string) *policyTestOptionsProvider {
|
||||||
return &policyTestResolver{
|
return &policyTestOptionsProvider{
|
||||||
dockerCli: dockerCli,
|
dockerCli: dockerCli,
|
||||||
builderName: builderName,
|
builderName: builderName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *policyTestResolver) Options() *policy.TestResolver {
|
func (r *policyTestOptionsProvider) TestOptionsProvider() *policy.TestOptionsProvider {
|
||||||
return &policy.TestResolver{
|
return &policy.TestOptionsProvider{
|
||||||
Resolve: r.Resolve,
|
Resolve: r.Resolve,
|
||||||
Platform: r.Platform,
|
Platform: r.Platform,
|
||||||
VerifierProvider: policy.SignatureVerifier(confutil.NewConfig(r.dockerCli)),
|
VerifierProvider: policy.SignatureVerifier(confutil.NewConfig(r.dockerCli)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *policyTestResolver) Close() error {
|
func (r *policyTestOptionsProvider) Close() error {
|
||||||
if r.release == nil {
|
if r.metaResolver == nil {
|
||||||
return 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 {
|
if err := r.init(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return r.platform, nil
|
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 {
|
if err := r.init(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
gwClient, err := r.openClient(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
opt := sourceResolverOpt(req, r.platform)
|
opt := sourceResolverOpt(req, r.platform)
|
||||||
resp, err := gwClient.ResolveSourceMetadata(ctx, source, opt)
|
resp, err := r.metaResolver.ResolveSourceMetadata(ctx, source, opt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return buildSourceMetaResponse(resp), nil
|
return buildSourceMetaResponse(resp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *policyTestResolver) init(ctx context.Context) error {
|
func (r *policyTestOptionsProvider) init(ctx context.Context) error {
|
||||||
r.once.Do(func() {
|
r.once.Do(func() {
|
||||||
bopts := []builder.Option{}
|
bopts := []builder.Option{}
|
||||||
if r.builderName != nil {
|
if r.builderName != nil {
|
||||||
@@ -208,13 +204,7 @@ func (r *policyTestResolver) init(ctx context.Context) error {
|
|||||||
OS: defaultPlatform.OS,
|
OS: defaultPlatform.OS,
|
||||||
Variant: defaultPlatform.Variant,
|
Variant: defaultPlatform.Variant,
|
||||||
}
|
}
|
||||||
openClient, release, err := gatewayClientFactory(c)
|
r.metaResolver = sourcemeta.NewResolver(c)
|
||||||
if err != nil {
|
|
||||||
r.err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.openClient = openClient
|
|
||||||
r.release = release
|
|
||||||
})
|
})
|
||||||
return r.err
|
return r.err
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -25,7 +25,7 @@ type TestOptions struct {
|
|||||||
Run string
|
Run string
|
||||||
Filename string
|
Filename string
|
||||||
Root fs.StatFS
|
Root fs.StatFS
|
||||||
Resolver *TestResolver
|
Provider *TestOptionsProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestSummary struct {
|
type TestSummary struct {
|
||||||
@@ -50,7 +50,7 @@ type testDef struct {
|
|||||||
PkgPath string
|
PkgPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestResolver struct {
|
type TestOptionsProvider struct {
|
||||||
Resolve func(context.Context, *pb.SourceOp, *gwpb.ResolveSourceMetaRequest) (*gwpb.ResolveSourceMetaResponse, error)
|
Resolve func(context.Context, *pb.SourceOp, *gwpb.ResolveSourceMetaRequest) (*gwpb.ResolveSourceMetaResponse, error)
|
||||||
Platform func(context.Context) (*ocispecs.Platform, error)
|
Platform func(context.Context) (*ocispecs.Platform, error)
|
||||||
VerifierProvider PolicyVerifierProvider
|
VerifierProvider PolicyVerifierProvider
|
||||||
@@ -286,8 +286,8 @@ func runPolicyTest(ctx context.Context, policyModules map[string]*ast.Module, te
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
effectiveInput := input
|
effectiveInput := input
|
||||||
if opts.Resolver != nil {
|
if opts.Provider != nil {
|
||||||
resolvedInput, ok, err := resolveTestInput(ctx, policyFiles, opts.Resolver, policyPackageModules, input, fsProvider)
|
resolvedInput, ok, err := resolveTestInput(ctx, policyFiles, opts.Provider, policyPackageModules, input, fsProvider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@@ -397,7 +397,7 @@ func stateFromInput(input *Input) *state {
|
|||||||
return st
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveTestInput(ctx context.Context, files []File, resolver *TestResolver, policyModules []*ast.Module, input *Input, fsProvider func() (fs.StatFS, func() error, error)) (*Input, bool, error) {
|
func resolveTestInput(ctx context.Context, files []File, resolver *TestOptionsProvider, policyModules []*ast.Module, input *Input, fsProvider func() (fs.StatFS, func() error, error)) (*Input, bool, error) {
|
||||||
if resolver == nil {
|
if resolver == nil {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package sourcemeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/client/llb/sourceresolver"
|
||||||
|
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ sourceresolver.MetaResolver = &Resolver{}
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
|
startOnce sync.Once
|
||||||
|
closeOnce sync.Once
|
||||||
|
started atomic.Bool
|
||||||
|
mu sync.Mutex
|
||||||
|
|
||||||
|
ready chan sourceresolver.MetaResolver
|
||||||
|
done chan struct{}
|
||||||
|
openErr error
|
||||||
|
doneErr error
|
||||||
|
cancel context.CancelCauseFunc
|
||||||
|
|
||||||
|
metaResolver sourceresolver.MetaResolver
|
||||||
|
run func(context.Context, chan<- sourceresolver.MetaResolver) error
|
||||||
|
closeErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResolver(c *client.Client) *Resolver {
|
||||||
|
return newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||||
|
_, err := c.Build(ctx, client.SolveOpt{Internal: true}, "buildx", func(ctx context.Context, gw gwclient.Client) (*gwclient.Result, error) {
|
||||||
|
ready <- gw
|
||||||
|
<-ctx.Done()
|
||||||
|
return nil, context.Cause(ctx)
|
||||||
|
}, nil)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newWithRun(run func(context.Context, chan<- sourceresolver.MetaResolver) error) *Resolver {
|
||||||
|
return &Resolver{
|
||||||
|
ready: make(chan sourceresolver.MetaResolver, 1),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
run: run,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) ResolveSourceMetadata(ctx context.Context, op *pb.SourceOp, opt sourceresolver.Opt) (*sourceresolver.MetaResponse, error) {
|
||||||
|
mr, err := r.open(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return mr.ResolveSourceMetadata(ctx, op, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) Close() error {
|
||||||
|
r.closeOnce.Do(func() {
|
||||||
|
if !r.started.Load() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.cancel != nil {
|
||||||
|
r.cancel(context.Canceled)
|
||||||
|
}
|
||||||
|
<-r.done
|
||||||
|
err := r.doneErr
|
||||||
|
if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.closeErr = err
|
||||||
|
})
|
||||||
|
return r.closeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) open(ctx context.Context) (sourceresolver.MetaResolver, error) {
|
||||||
|
r.startOnce.Do(func() {
|
||||||
|
r.started.Store(true)
|
||||||
|
buildCtx, cancel := context.WithCancelCause(context.Background())
|
||||||
|
r.cancel = cancel
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
r.doneErr = r.run(buildCtx, r.ready)
|
||||||
|
close(r.done)
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
|
for {
|
||||||
|
r.mu.Lock()
|
||||||
|
if r.metaResolver != nil {
|
||||||
|
mr := r.metaResolver
|
||||||
|
r.mu.Unlock()
|
||||||
|
return mr, nil
|
||||||
|
}
|
||||||
|
if r.openErr != nil {
|
||||||
|
err := r.openErr
|
||||||
|
r.mu.Unlock()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.mu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case mr := <-r.ready:
|
||||||
|
r.mu.Lock()
|
||||||
|
if r.metaResolver == nil {
|
||||||
|
r.metaResolver = mr
|
||||||
|
}
|
||||||
|
r.mu.Unlock()
|
||||||
|
case <-r.done:
|
||||||
|
r.mu.Lock()
|
||||||
|
if r.metaResolver == nil && r.openErr == nil {
|
||||||
|
err := r.doneErr
|
||||||
|
if err == nil {
|
||||||
|
err = errors.New("gateway build finished without a source metadata resolver")
|
||||||
|
}
|
||||||
|
r.openErr = err
|
||||||
|
}
|
||||||
|
r.mu.Unlock()
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, context.Cause(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
package sourcemeta
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/client/llb/sourceresolver"
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeMetaResolver struct {
|
||||||
|
calls atomic.Int32
|
||||||
|
resp *sourceresolver.MetaResponse
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeMetaResolver) ResolveSourceMetadata(ctx context.Context, op *pb.SourceOp, opt sourceresolver.Opt) (*sourceresolver.MetaResponse, error) {
|
||||||
|
f.calls.Add(1)
|
||||||
|
return f.resp, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolverCloseNoopBeforeResolve(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var called atomic.Int32
|
||||||
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||||
|
called.Add(1)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, r.Close())
|
||||||
|
require.EqualValues(t, 0, called.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolverResolveOpensOnce(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var runs atomic.Int32
|
||||||
|
mr := &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
||||||
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||||
|
runs.Add(1)
|
||||||
|
ready <- mr
|
||||||
|
<-ctx.Done()
|
||||||
|
return context.Cause(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
op := &pb.SourceOp{}
|
||||||
|
_, err := r.ResolveSourceMetadata(t.Context(), op, sourceresolver.Opt{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = r.ResolveSourceMetadata(t.Context(), op, sourceresolver.Opt{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.EqualValues(t, 1, runs.Load())
|
||||||
|
require.EqualValues(t, 2, mr.calls.Load())
|
||||||
|
require.NoError(t, r.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolverCloseAfterOpenCancelsBuild(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var canceled atomic.Bool
|
||||||
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||||
|
ready <- &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
||||||
|
<-ctx.Done()
|
||||||
|
canceled.Store(true)
|
||||||
|
return context.Cause(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, r.Close())
|
||||||
|
require.True(t, canceled.Load())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolverOpenFailureIsSticky(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
expected := errors.New("boom")
|
||||||
|
var runs atomic.Int32
|
||||||
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||||
|
runs.Add(1)
|
||||||
|
return expected
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
|
||||||
|
require.ErrorIs(t, err, expected)
|
||||||
|
_, err = r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
|
||||||
|
require.ErrorIs(t, err, expected)
|
||||||
|
require.EqualValues(t, 1, runs.Load())
|
||||||
|
require.ErrorIs(t, r.Close(), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolverCloseIgnoresTerminalContextErrors(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{name: "canceled", err: context.Canceled},
|
||||||
|
{name: "deadline", err: context.DeadlineExceeded},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||||
|
return tc.err
|
||||||
|
})
|
||||||
|
_, err := r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
|
||||||
|
require.ErrorIs(t, err, tc.err)
|
||||||
|
require.NoError(t, r.Close())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolverConcurrentResolveUsesSingleOpen(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
var runs atomic.Int32
|
||||||
|
mr := &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
||||||
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||||
|
runs.Add(1)
|
||||||
|
ready <- mr
|
||||||
|
<-ctx.Done()
|
||||||
|
return context.Cause(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
const n = 16
|
||||||
|
errCh := make(chan error, n)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(n)
|
||||||
|
for range n {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
_, err := r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
|
||||||
|
errCh <- err
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
close(errCh)
|
||||||
|
|
||||||
|
for err := range errCh {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
require.EqualValues(t, 1, runs.Load())
|
||||||
|
require.EqualValues(t, n, mr.calls.Load())
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
closeErr := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
closeErr <- r.Close()
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
require.NoError(t, <-closeErr)
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("close timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolverFirstCanceledContextDoesNotPoisonFutureCalls(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
mr := &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
||||||
|
started := make(chan struct{})
|
||||||
|
release := make(chan struct{})
|
||||||
|
|
||||||
|
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||||
|
close(started)
|
||||||
|
<-release
|
||||||
|
ready <- mr
|
||||||
|
<-ctx.Done()
|
||||||
|
return context.Cause(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
canceledCtx, cancel := context.WithCancelCause(t.Context())
|
||||||
|
cancel(context.Canceled)
|
||||||
|
_, err := r.ResolveSourceMetadata(canceledCtx, &pb.SourceOp{}, sourceresolver.Opt{})
|
||||||
|
require.ErrorIs(t, err, context.Canceled)
|
||||||
|
|
||||||
|
<-started
|
||||||
|
close(release)
|
||||||
|
|
||||||
|
_, err = r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, r.Close())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user