Files
buildx/util/sourcemeta/resolver.go
T
Tonis Tiigi dce5289722 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>
2026-02-13 13:59:33 -08:00

127 lines
2.8 KiB
Go

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)
}
}
}