buildkit/frontend/gateway/forwarder/forward.go

354 lines
8.6 KiB
Go

package forwarder
import (
"context"
"sync"
cacheutil "github.com/moby/buildkit/cache/util"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/frontend"
"github.com/moby/buildkit/frontend/gateway"
"github.com/moby/buildkit/frontend/gateway/client"
gwpb "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/snapshot"
"github.com/moby/buildkit/solver"
"github.com/moby/buildkit/solver/errdefs"
llberrdefs "github.com/moby/buildkit/solver/llbsolver/errdefs"
opspb "github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/apicaps"
"github.com/moby/buildkit/worker"
"github.com/pkg/errors"
fstypes "github.com/tonistiigi/fsutil/types"
"golang.org/x/sync/errgroup"
)
func llbBridgeToGatewayClient(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string, inputs map[string]*opspb.Definition, w worker.Infos, sid string, sm *session.Manager) (*bridgeClient, error) {
return &bridgeClient{
opts: opts,
inputs: inputs,
FrontendLLBBridge: llbBridge,
sid: sid,
sm: sm,
workers: w,
final: map[*ref]struct{}{},
workerRefByID: make(map[string]*worker.WorkerRef),
}, nil
}
type bridgeClient struct {
frontend.FrontendLLBBridge
mu sync.Mutex
opts map[string]string
inputs map[string]*opspb.Definition
final map[*ref]struct{}
sid string
sm *session.Manager
refs []*ref
workers worker.Infos
workerRefByID map[string]*worker.WorkerRef
}
func (c *bridgeClient) Solve(ctx context.Context, req client.SolveRequest) (*client.Result, error) {
res, err := c.FrontendLLBBridge.Solve(ctx, frontend.SolveRequest{
Evaluate: req.Evaluate,
Definition: req.Definition,
Frontend: req.Frontend,
FrontendOpt: req.FrontendOpt,
FrontendInputs: req.FrontendInputs,
CacheImports: req.CacheImports,
}, c.sid)
if err != nil {
return nil, c.wrapSolveError(err)
}
cRes := &client.Result{}
c.mu.Lock()
for k, r := range res.Refs {
rr, err := c.newRef(r, session.NewGroup(c.sid))
if err != nil {
return nil, err
}
c.refs = append(c.refs, rr)
cRes.AddRef(k, rr)
}
if r := res.Ref; r != nil {
rr, err := c.newRef(r, session.NewGroup(c.sid))
if err != nil {
return nil, err
}
c.refs = append(c.refs, rr)
cRes.SetRef(rr)
}
c.mu.Unlock()
cRes.Metadata = res.Metadata
return cRes, nil
}
func (c *bridgeClient) BuildOpts() client.BuildOpts {
workers := make([]client.WorkerInfo, 0, len(c.workers.WorkerInfos()))
for _, w := range c.workers.WorkerInfos() {
workers = append(workers, client.WorkerInfo{
ID: w.ID,
Labels: w.Labels,
Platforms: w.Platforms,
})
}
return client.BuildOpts{
Opts: c.opts,
SessionID: c.sid,
Workers: workers,
Product: apicaps.ExportedProduct,
Caps: gwpb.Caps.CapSet(gwpb.Caps.All()),
LLBCaps: opspb.Caps.CapSet(opspb.Caps.All()),
}
}
func (c *bridgeClient) Inputs(ctx context.Context) (map[string]llb.State, error) {
inputs := make(map[string]llb.State)
for key, def := range c.inputs {
defop, err := llb.NewDefinitionOp(def)
if err != nil {
return nil, err
}
inputs[key] = llb.NewState(defop)
}
return inputs, nil
}
func (c *bridgeClient) wrapSolveError(solveErr error) error {
var (
ee *llberrdefs.ExecError
fae *llberrdefs.FileActionError
sce *solver.SlowCacheError
inputIDs []string
mountIDs []string
subject errdefs.IsSolve_Subject
)
if errors.As(solveErr, &ee) {
var err error
inputIDs, err = c.registerResultIDs(ee.Inputs...)
if err != nil {
return err
}
mountIDs, err = c.registerResultIDs(ee.Mounts...)
if err != nil {
return err
}
}
if errors.As(solveErr, &fae) {
subject = fae.ToSubject()
}
if errors.As(solveErr, &sce) {
var err error
inputIDs, err = c.registerResultIDs(sce.Result)
if err != nil {
return err
}
subject = sce.ToSubject()
}
return errdefs.WithSolveError(solveErr, subject, inputIDs, mountIDs)
}
func (c *bridgeClient) registerResultIDs(results ...solver.Result) (ids []string, err error) {
c.mu.Lock()
defer c.mu.Unlock()
ids = make([]string, len(results))
for i, res := range results {
if res == nil {
continue
}
workerRef, ok := res.Sys().(*worker.WorkerRef)
if !ok {
return ids, errors.Errorf("unexpected type for result, got %T", res.Sys())
}
ids[i] = workerRef.ID()
c.workerRefByID[workerRef.ID()] = workerRef
}
return ids, nil
}
func (c *bridgeClient) toFrontendResult(r *client.Result) (*frontend.Result, error) {
if r == nil {
return nil, nil
}
res := &frontend.Result{}
if r.Refs != nil {
res.Refs = make(map[string]solver.ResultProxy, len(r.Refs))
for k, r := range r.Refs {
rr, ok := r.(*ref)
if !ok {
return nil, errors.Errorf("invalid reference type for forward %T", r)
}
c.final[rr] = struct{}{}
res.Refs[k] = rr.ResultProxy
}
}
if r := r.Ref; r != nil {
rr, ok := r.(*ref)
if !ok {
return nil, errors.Errorf("invalid reference type for forward %T", r)
}
c.final[rr] = struct{}{}
res.Ref = rr.ResultProxy
}
res.Metadata = r.Metadata
return res, nil
}
func (c *bridgeClient) discard(err error) {
for id, workerRef := range c.workerRefByID {
workerRef.ImmutableRef.Release(context.TODO())
delete(c.workerRefByID, id)
}
for _, r := range c.refs {
if r != nil {
if _, ok := c.final[r]; !ok || err != nil {
r.Release(context.TODO())
}
}
}
}
func (c *bridgeClient) NewContainer(ctx context.Context, req client.NewContainerRequest) (client.Container, error) {
ctrReq := gateway.NewContainerRequest{
ContainerID: identity.NewID(),
NetMode: req.NetMode,
Mounts: make([]gateway.Mount, len(req.Mounts)),
}
eg, ctx := errgroup.WithContext(ctx)
for i, m := range req.Mounts {
i, m := i, m
eg.Go(func() error {
var workerRef *worker.WorkerRef
if m.Ref != nil {
refProxy, ok := m.Ref.(*ref)
if !ok {
return errors.Errorf("unexpected Ref type: %T", m.Ref)
}
res, err := refProxy.Result(ctx)
if err != nil {
return err
}
workerRef, ok = res.Sys().(*worker.WorkerRef)
if !ok {
return errors.Errorf("invalid ref: %T", res.Sys())
}
} else if m.ResultID != "" {
var ok bool
workerRef, ok = c.workerRefByID[m.ResultID]
if !ok {
return errors.Errorf("failed to find ref %s for %q mount", m.ResultID, m.Dest)
}
}
ctrReq.Mounts[i] = gateway.Mount{
WorkerRef: workerRef,
Mount: &opspb.Mount{
Dest: m.Dest,
Selector: m.Selector,
Readonly: m.Readonly,
MountType: m.MountType,
CacheOpt: m.CacheOpt,
SecretOpt: m.SecretOpt,
SSHOpt: m.SSHOpt,
},
}
return nil
})
}
err := eg.Wait()
if err != nil {
return nil, err
}
w, err := c.workers.GetDefault()
if err != nil {
return nil, err
}
group := session.NewGroup(c.sid)
ctr, err := gateway.NewContainer(ctx, w, c.sm, group, ctrReq)
if err != nil {
return nil, err
}
return ctr, nil
}
type ref struct {
solver.ResultProxy
session session.Group
c *bridgeClient
}
func (c *bridgeClient) newRef(r solver.ResultProxy, s session.Group) (*ref, error) {
return &ref{ResultProxy: r, session: s, c: c}, nil
}
func (r *ref) ToState() (st llb.State, err error) {
defop, err := llb.NewDefinitionOp(r.Definition())
if err != nil {
return st, err
}
return llb.NewState(defop), nil
}
func (r *ref) ReadFile(ctx context.Context, req client.ReadRequest) ([]byte, error) {
m, err := r.getMountable(ctx)
if err != nil {
return nil, err
}
newReq := cacheutil.ReadRequest{
Filename: req.Filename,
}
if r := req.Range; r != nil {
newReq.Range = &cacheutil.FileRange{
Offset: r.Offset,
Length: r.Length,
}
}
return cacheutil.ReadFile(ctx, m, newReq)
}
func (r *ref) ReadDir(ctx context.Context, req client.ReadDirRequest) ([]*fstypes.Stat, error) {
m, err := r.getMountable(ctx)
if err != nil {
return nil, err
}
newReq := cacheutil.ReadDirRequest{
Path: req.Path,
IncludePattern: req.IncludePattern,
}
return cacheutil.ReadDir(ctx, m, newReq)
}
func (r *ref) StatFile(ctx context.Context, req client.StatRequest) (*fstypes.Stat, error) {
m, err := r.getMountable(ctx)
if err != nil {
return nil, err
}
return cacheutil.StatFile(ctx, m, req.Path)
}
func (r *ref) getMountable(ctx context.Context) (snapshot.Mountable, error) {
rr, err := r.ResultProxy.Result(ctx)
if err != nil {
return nil, r.c.wrapSolveError(err)
}
ref, ok := rr.Sys().(*worker.WorkerRef)
if !ok {
return nil, errors.Errorf("invalid ref: %T", rr.Sys())
}
return ref.ImmutableRef.Mount(ctx, true, r.session)
}