2017-10-01 00:58:07 +00:00
|
|
|
package gateway
|
|
|
|
|
|
|
|
import (
|
2018-01-16 22:30:10 +00:00
|
|
|
"context"
|
2017-10-01 00:58:07 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"os"
|
2019-01-18 23:31:51 +00:00
|
|
|
"strconv"
|
2017-10-03 06:33:25 +00:00
|
|
|
"strings"
|
2018-06-20 18:17:05 +00:00
|
|
|
"sync"
|
2017-10-01 00:58:07 +00:00
|
|
|
"time"
|
|
|
|
|
2020-08-05 01:11:32 +00:00
|
|
|
"github.com/containerd/containerd"
|
2017-10-01 00:58:07 +00:00
|
|
|
"github.com/docker/distribution/reference"
|
2020-04-21 21:05:49 +00:00
|
|
|
gogotypes "github.com/gogo/protobuf/types"
|
|
|
|
"github.com/golang/protobuf/ptypes/any"
|
2018-06-30 01:35:39 +00:00
|
|
|
apitypes "github.com/moby/buildkit/api/types"
|
2017-10-01 00:58:07 +00:00
|
|
|
"github.com/moby/buildkit/cache"
|
2018-10-02 11:17:57 +00:00
|
|
|
cacheutil "github.com/moby/buildkit/cache/util"
|
2018-07-26 20:51:28 +00:00
|
|
|
"github.com/moby/buildkit/client"
|
2017-10-01 00:58:07 +00:00
|
|
|
"github.com/moby/buildkit/client/llb"
|
2017-11-21 08:08:36 +00:00
|
|
|
"github.com/moby/buildkit/executor"
|
2018-07-13 01:02:36 +00:00
|
|
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
2017-10-01 00:58:07 +00:00
|
|
|
"github.com/moby/buildkit/frontend"
|
2020-08-05 01:11:32 +00:00
|
|
|
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
2017-10-01 00:58:07 +00:00
|
|
|
pb "github.com/moby/buildkit/frontend/gateway/pb"
|
|
|
|
"github.com/moby/buildkit/identity"
|
2018-05-11 05:58:41 +00:00
|
|
|
"github.com/moby/buildkit/solver"
|
2020-08-05 01:11:32 +00:00
|
|
|
"github.com/moby/buildkit/solver/errdefs"
|
2018-06-30 01:35:39 +00:00
|
|
|
opspb "github.com/moby/buildkit/solver/pb"
|
2018-07-09 22:02:52 +00:00
|
|
|
"github.com/moby/buildkit/util/apicaps"
|
2020-04-23 00:10:31 +00:00
|
|
|
"github.com/moby/buildkit/util/grpcerrors"
|
2020-08-05 01:11:32 +00:00
|
|
|
"github.com/moby/buildkit/util/stack"
|
2018-01-07 01:17:45 +00:00
|
|
|
"github.com/moby/buildkit/util/tracing"
|
2018-04-13 21:13:48 +00:00
|
|
|
"github.com/moby/buildkit/worker"
|
2018-06-24 05:52:19 +00:00
|
|
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
2017-10-01 00:58:07 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"golang.org/x/net/http2"
|
2020-08-05 01:11:32 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
2018-07-11 23:51:41 +00:00
|
|
|
spb "google.golang.org/genproto/googleapis/rpc/status"
|
2017-10-01 00:58:07 +00:00
|
|
|
"google.golang.org/grpc"
|
2020-08-05 01:11:32 +00:00
|
|
|
"google.golang.org/grpc/codes"
|
2017-10-01 00:58:07 +00:00
|
|
|
"google.golang.org/grpc/health"
|
|
|
|
"google.golang.org/grpc/health/grpc_health_v1"
|
2018-07-11 23:51:41 +00:00
|
|
|
"google.golang.org/grpc/status"
|
2017-10-01 00:58:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2018-07-13 01:02:36 +00:00
|
|
|
keySource = "source"
|
|
|
|
keyDevel = "gateway-devel"
|
2017-10-01 00:58:07 +00:00
|
|
|
)
|
|
|
|
|
2018-06-30 01:35:39 +00:00
|
|
|
func NewGatewayFrontend(w frontend.WorkerInfos) frontend.Frontend {
|
|
|
|
return &gatewayFrontend{
|
|
|
|
workers: w,
|
|
|
|
}
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type gatewayFrontend struct {
|
2018-06-30 01:35:39 +00:00
|
|
|
workers frontend.WorkerInfos
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
2017-10-03 06:33:25 +00:00
|
|
|
func filterPrefix(opts map[string]string, pfx string) map[string]string {
|
|
|
|
m := map[string]string{}
|
|
|
|
for k, v := range opts {
|
|
|
|
if strings.HasPrefix(k, pfx) {
|
|
|
|
m[strings.TrimPrefix(k, pfx)] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|
|
|
|
|
2020-06-30 01:06:02 +00:00
|
|
|
func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string, inputs map[string]*opspb.Definition, sid string) (*frontend.Result, error) {
|
2017-10-01 00:58:07 +00:00
|
|
|
source, ok := opts[keySource]
|
|
|
|
if !ok {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, errors.Errorf("no source specified for gateway")
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
_, isDevel := opts[keyDevel]
|
2018-06-24 05:52:19 +00:00
|
|
|
var img specs.Image
|
2020-07-16 21:48:42 +00:00
|
|
|
var rootFS cache.MutableRef
|
2018-03-26 13:51:08 +00:00
|
|
|
var readonly bool // TODO: try to switch to read-only by default.
|
2017-10-01 00:58:07 +00:00
|
|
|
|
|
|
|
if isDevel {
|
2020-06-30 01:06:02 +00:00
|
|
|
devRes, err := llbBridge.Solve(ctx,
|
2017-10-05 05:31:18 +00:00
|
|
|
frontend.SolveRequest{
|
2020-02-07 19:53:18 +00:00
|
|
|
Frontend: source,
|
|
|
|
FrontendOpt: filterPrefix(opts, "gateway-"),
|
|
|
|
FrontendInputs: inputs,
|
2020-06-30 01:06:02 +00:00
|
|
|
}, "gateway:"+sid)
|
2017-10-01 00:58:07 +00:00
|
|
|
if err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, err
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
2018-07-05 23:32:01 +00:00
|
|
|
defer func() {
|
2020-02-04 23:00:44 +00:00
|
|
|
devRes.EachRef(func(ref solver.ResultProxy) error {
|
2018-07-11 23:51:41 +00:00
|
|
|
return ref.Release(context.TODO())
|
|
|
|
})
|
2018-07-05 23:32:01 +00:00
|
|
|
}()
|
2018-07-11 23:51:41 +00:00
|
|
|
if devRes.Ref == nil {
|
|
|
|
return nil, errors.Errorf("development gateway didn't return default result")
|
2018-07-05 23:32:01 +00:00
|
|
|
}
|
2020-02-04 23:00:44 +00:00
|
|
|
res, err := devRes.Ref.Result(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
workerRef, ok := res.Sys().(*worker.WorkerRef)
|
2018-04-13 21:13:48 +00:00
|
|
|
if !ok {
|
2020-02-04 23:00:44 +00:00
|
|
|
return nil, errors.Errorf("invalid ref: %T", res.Sys())
|
2018-04-13 21:13:48 +00:00
|
|
|
}
|
2020-07-16 21:48:42 +00:00
|
|
|
|
2020-07-17 00:54:15 +00:00
|
|
|
rootFS, err = workerRef.Worker.CacheManager().New(ctx, workerRef.ImmutableRef)
|
2020-07-16 21:48:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-17 02:08:03 +00:00
|
|
|
defer rootFS.Release(context.TODO())
|
2018-07-13 01:02:36 +00:00
|
|
|
config, ok := devRes.Metadata[exptypes.ExporterImageConfigKey]
|
2017-10-01 00:58:07 +00:00
|
|
|
if ok {
|
2017-10-03 06:33:25 +00:00
|
|
|
if err := json.Unmarshal(config, &img); err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, err
|
2017-10-03 06:33:25 +00:00
|
|
|
}
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sourceRef, err := reference.ParseNormalizedNamed(source)
|
|
|
|
if err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, err
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
2019-12-09 20:50:21 +00:00
|
|
|
dgst, config, err := llbBridge.ResolveImageConfig(ctx, reference.TagNameOnly(sourceRef).String(), llb.ResolveImageConfigOpt{})
|
2017-10-01 00:58:07 +00:00
|
|
|
if err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, err
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(config, &img); err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, err
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
2018-06-20 18:09:32 +00:00
|
|
|
if dgst != "" {
|
|
|
|
sourceRef, err = reference.WithDigest(sourceRef, dgst)
|
|
|
|
if err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, err
|
2018-06-20 18:09:32 +00:00
|
|
|
}
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-26 20:51:28 +00:00
|
|
|
src := llb.Image(sourceRef.String(), &markTypeFrontend{})
|
2017-10-01 00:58:07 +00:00
|
|
|
|
2020-03-25 22:39:32 +00:00
|
|
|
def, err := src.Marshal(ctx)
|
2017-10-01 00:58:07 +00:00
|
|
|
if err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, err
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-11 23:51:41 +00:00
|
|
|
res, err := llbBridge.Solve(ctx, frontend.SolveRequest{
|
2017-10-05 05:31:18 +00:00
|
|
|
Definition: def.ToPB(),
|
2020-06-30 01:06:02 +00:00
|
|
|
}, sid)
|
2017-10-01 00:58:07 +00:00
|
|
|
if err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, err
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
2018-07-05 23:32:01 +00:00
|
|
|
defer func() {
|
2020-02-04 23:00:44 +00:00
|
|
|
res.EachRef(func(ref solver.ResultProxy) error {
|
2018-07-11 23:51:41 +00:00
|
|
|
return ref.Release(context.TODO())
|
|
|
|
})
|
2018-07-05 23:32:01 +00:00
|
|
|
}()
|
2018-07-11 23:51:41 +00:00
|
|
|
if res.Ref == nil {
|
|
|
|
return nil, errors.Errorf("gateway source didn't return default result")
|
|
|
|
|
2018-07-05 23:32:01 +00:00
|
|
|
}
|
2020-02-04 23:00:44 +00:00
|
|
|
r, err := res.Ref.Result(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
workerRef, ok := r.Sys().(*worker.WorkerRef)
|
2018-04-13 21:13:48 +00:00
|
|
|
if !ok {
|
2020-02-04 23:00:44 +00:00
|
|
|
return nil, errors.Errorf("invalid ref: %T", r.Sys())
|
2018-04-13 21:13:48 +00:00
|
|
|
}
|
2020-07-17 00:54:15 +00:00
|
|
|
rootFS, err = workerRef.Worker.CacheManager().New(ctx, workerRef.ImmutableRef)
|
2020-07-16 21:48:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-17 02:08:03 +00:00
|
|
|
defer rootFS.Release(context.TODO())
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
2020-07-18 19:20:51 +00:00
|
|
|
lbf, ctx, err := serveLLBBridgeForwarder(ctx, llbBridge, gf.workers, inputs, sid)
|
2020-07-18 21:52:44 +00:00
|
|
|
defer lbf.conn.Close() //nolint
|
2017-10-01 00:58:07 +00:00
|
|
|
if err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, err
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{"/run"}
|
|
|
|
env := []string{}
|
|
|
|
cwd := "/"
|
|
|
|
if img.Config.Env != nil {
|
|
|
|
env = img.Config.Env
|
|
|
|
}
|
|
|
|
if img.Config.Entrypoint != nil {
|
|
|
|
args = img.Config.Entrypoint
|
|
|
|
}
|
|
|
|
if img.Config.WorkingDir != "" {
|
|
|
|
cwd = img.Config.WorkingDir
|
|
|
|
}
|
|
|
|
i := 0
|
|
|
|
for k, v := range opts {
|
|
|
|
env = append(env, fmt.Sprintf("BUILDKIT_FRONTEND_OPT_%d", i)+"="+k+"="+v)
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
|
|
|
|
env = append(env, "BUILDKIT_SESSION_ID="+sid)
|
|
|
|
|
2018-07-04 02:47:40 +00:00
|
|
|
dt, err := json.Marshal(gf.workers.WorkerInfos())
|
|
|
|
if err != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, errors.Wrap(err, "failed to marshal workers array")
|
2018-07-04 02:47:40 +00:00
|
|
|
}
|
|
|
|
env = append(env, "BUILDKIT_WORKERS="+string(dt))
|
|
|
|
|
2018-08-20 13:09:28 +00:00
|
|
|
defer lbf.Discard()
|
2017-12-13 23:47:05 +00:00
|
|
|
|
2018-07-12 23:04:58 +00:00
|
|
|
env = append(env, "BUILDKIT_EXPORTEDPRODUCT="+apicaps.ExportedProduct)
|
2018-07-09 22:02:52 +00:00
|
|
|
|
2019-01-18 23:31:51 +00:00
|
|
|
meta := executor.Meta{
|
2018-03-26 13:51:08 +00:00
|
|
|
Env: env,
|
|
|
|
Args: args,
|
|
|
|
Cwd: cwd,
|
|
|
|
ReadonlyRootFS: readonly,
|
2019-01-18 23:31:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if v, ok := img.Config.Labels["moby.buildkit.frontend.network.none"]; ok {
|
|
|
|
if ok, _ := strconv.ParseBool(v); ok {
|
|
|
|
meta.NetMode = opspb.NetMode_NONE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-16 21:48:42 +00:00
|
|
|
err = llbBridge.Run(ctx, "", rootFS, nil, executor.ProcessInfo{Meta: meta, Stdin: lbf.Stdin, Stdout: lbf.Stdout, Stderr: os.Stderr}, nil)
|
2017-10-01 00:58:07 +00:00
|
|
|
|
2018-07-30 13:55:26 +00:00
|
|
|
if err != nil {
|
2020-04-19 05:17:47 +00:00
|
|
|
if errors.Is(err, context.Canceled) && lbf.isErrServerClosed {
|
2019-05-12 05:40:02 +00:00
|
|
|
err = errors.Errorf("frontend grpc server closed unexpectedly")
|
|
|
|
}
|
2018-07-30 13:55:26 +00:00
|
|
|
// An existing error (set via Return rpc) takes
|
|
|
|
// precedence over this error, which in turn takes
|
|
|
|
// precedence over a success reported via Return.
|
|
|
|
lbf.mu.Lock()
|
|
|
|
if lbf.err == nil {
|
|
|
|
lbf.result = nil
|
|
|
|
lbf.err = err
|
|
|
|
}
|
|
|
|
lbf.mu.Unlock()
|
2018-07-09 21:26:31 +00:00
|
|
|
}
|
|
|
|
|
2018-07-30 13:55:26 +00:00
|
|
|
return lbf.Result()
|
|
|
|
}
|
|
|
|
|
2018-08-20 13:09:28 +00:00
|
|
|
func (lbf *llbBridgeForwarder) Discard() {
|
2018-08-30 14:03:29 +00:00
|
|
|
lbf.mu.Lock()
|
|
|
|
defer lbf.mu.Unlock()
|
2018-08-30 14:05:41 +00:00
|
|
|
for id, r := range lbf.refs {
|
2018-08-20 13:09:28 +00:00
|
|
|
if lbf.err == nil && lbf.result != nil {
|
|
|
|
keep := false
|
2020-02-04 23:00:44 +00:00
|
|
|
lbf.result.EachRef(func(r2 solver.ResultProxy) error {
|
2018-08-20 13:09:28 +00:00
|
|
|
if r == r2 {
|
|
|
|
keep = true
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if keep {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r.Release(context.TODO())
|
2018-08-30 14:05:41 +00:00
|
|
|
delete(lbf.refs, id)
|
2018-08-20 13:09:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-19 16:34:44 +00:00
|
|
|
func (lbf *llbBridgeForwarder) Done() <-chan struct{} {
|
|
|
|
return lbf.doneCh
|
|
|
|
}
|
|
|
|
|
2018-07-30 13:55:26 +00:00
|
|
|
func (lbf *llbBridgeForwarder) setResult(r *frontend.Result, err error) (*pb.ReturnResponse, error) {
|
|
|
|
lbf.mu.Lock()
|
|
|
|
defer lbf.mu.Unlock()
|
|
|
|
|
|
|
|
if (r == nil) == (err == nil) {
|
|
|
|
return nil, errors.New("gateway return must be either result or err")
|
|
|
|
}
|
|
|
|
|
|
|
|
if lbf.result != nil || lbf.err != nil {
|
|
|
|
return nil, errors.New("gateway result is already set")
|
|
|
|
}
|
|
|
|
|
|
|
|
lbf.result = r
|
|
|
|
lbf.err = err
|
2018-07-19 16:34:44 +00:00
|
|
|
close(lbf.doneCh)
|
2018-07-30 13:55:26 +00:00
|
|
|
return &pb.ReturnResponse{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lbf *llbBridgeForwarder) Result() (*frontend.Result, error) {
|
|
|
|
lbf.mu.Lock()
|
|
|
|
defer lbf.mu.Unlock()
|
|
|
|
|
|
|
|
if lbf.result == nil && lbf.err == nil {
|
|
|
|
return nil, errors.New("no result for incomplete build")
|
|
|
|
}
|
|
|
|
|
|
|
|
if lbf.err != nil {
|
|
|
|
return nil, lbf.err
|
2018-07-09 21:26:31 +00:00
|
|
|
}
|
|
|
|
|
2018-07-11 23:51:41 +00:00
|
|
|
return lbf.result, nil
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
2020-07-18 19:20:51 +00:00
|
|
|
func NewBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, workers frontend.WorkerInfos, inputs map[string]*opspb.Definition, sid string) LLBBridgeForwarder {
|
|
|
|
return newBridgeForwarder(ctx, llbBridge, workers, inputs, sid)
|
|
|
|
}
|
|
|
|
|
|
|
|
func newBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, workers frontend.WorkerInfos, inputs map[string]*opspb.Definition, sid string) *llbBridgeForwarder {
|
2018-03-08 18:08:32 +00:00
|
|
|
lbf := &llbBridgeForwarder{
|
2018-01-07 01:17:45 +00:00
|
|
|
callCtx: ctx,
|
2017-10-01 00:58:07 +00:00
|
|
|
llbBridge: llbBridge,
|
2020-02-04 23:00:44 +00:00
|
|
|
refs: map[string]solver.ResultProxy{},
|
2018-07-19 16:34:44 +00:00
|
|
|
doneCh: make(chan struct{}),
|
2017-10-01 00:58:07 +00:00
|
|
|
pipe: newPipe(),
|
2018-06-30 01:35:39 +00:00
|
|
|
workers: workers,
|
2020-02-07 19:53:18 +00:00
|
|
|
inputs: inputs,
|
2020-06-30 01:06:02 +00:00
|
|
|
sid: sid,
|
2020-08-05 01:11:32 +00:00
|
|
|
ctrs: map[string]Container{},
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
2018-07-19 16:34:44 +00:00
|
|
|
return lbf
|
|
|
|
}
|
2017-10-01 00:58:07 +00:00
|
|
|
|
2020-07-18 19:20:51 +00:00
|
|
|
func serveLLBBridgeForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge, workers frontend.WorkerInfos, inputs map[string]*opspb.Definition, sid string) (*llbBridgeForwarder, context.Context, error) {
|
2019-05-12 05:40:02 +00:00
|
|
|
ctx, cancel := context.WithCancel(ctx)
|
2020-07-18 19:20:51 +00:00
|
|
|
lbf := newBridgeForwarder(ctx, llbBridge, workers, inputs, sid)
|
2020-04-23 00:10:31 +00:00
|
|
|
server := grpc.NewServer(grpc.UnaryInterceptor(grpcerrors.UnaryServerInterceptor), grpc.StreamInterceptor(grpcerrors.StreamServerInterceptor))
|
2017-10-01 00:58:07 +00:00
|
|
|
grpc_health_v1.RegisterHealthServer(server, health.NewServer())
|
2020-04-22 22:59:39 +00:00
|
|
|
pb.RegisterLLBBridgeServer(server, lbf)
|
2017-10-01 00:58:07 +00:00
|
|
|
|
2019-05-12 05:40:02 +00:00
|
|
|
go func() {
|
|
|
|
serve(ctx, server, lbf.conn)
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
default:
|
|
|
|
lbf.isErrServerClosed = true
|
|
|
|
}
|
|
|
|
cancel()
|
|
|
|
}()
|
2017-10-01 00:58:07 +00:00
|
|
|
|
2019-05-12 05:40:02 +00:00
|
|
|
return lbf, ctx, nil
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type pipe struct {
|
|
|
|
Stdin io.ReadCloser
|
|
|
|
Stdout io.WriteCloser
|
|
|
|
conn net.Conn
|
|
|
|
}
|
|
|
|
|
|
|
|
func newPipe() *pipe {
|
|
|
|
pr1, pw1, _ := os.Pipe()
|
|
|
|
pr2, pw2, _ := os.Pipe()
|
|
|
|
return &pipe{
|
|
|
|
Stdin: pr1,
|
|
|
|
Stdout: pw2,
|
|
|
|
conn: &conn{
|
|
|
|
Reader: pr2,
|
|
|
|
Writer: pw1,
|
|
|
|
Closer: pw2,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type conn struct {
|
|
|
|
io.Reader
|
|
|
|
io.Writer
|
|
|
|
io.Closer
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *conn) LocalAddr() net.Addr {
|
|
|
|
return dummyAddr{}
|
|
|
|
}
|
|
|
|
func (s *conn) RemoteAddr() net.Addr {
|
|
|
|
return dummyAddr{}
|
|
|
|
}
|
|
|
|
func (s *conn) SetDeadline(t time.Time) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func (s *conn) SetReadDeadline(t time.Time) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func (s *conn) SetWriteDeadline(t time.Time) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type dummyAddr struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d dummyAddr) Network() string {
|
|
|
|
return "pipe"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d dummyAddr) String() string {
|
|
|
|
return "localhost"
|
|
|
|
}
|
|
|
|
|
2018-07-19 16:34:44 +00:00
|
|
|
type LLBBridgeForwarder interface {
|
|
|
|
pb.LLBBridgeServer
|
|
|
|
Done() <-chan struct{}
|
|
|
|
Result() (*frontend.Result, error)
|
2020-07-18 19:20:51 +00:00
|
|
|
Discard()
|
2018-07-19 16:34:44 +00:00
|
|
|
}
|
|
|
|
|
2018-03-08 18:08:32 +00:00
|
|
|
type llbBridgeForwarder struct {
|
2018-07-11 23:51:41 +00:00
|
|
|
mu sync.Mutex
|
|
|
|
callCtx context.Context
|
|
|
|
llbBridge frontend.FrontendLLBBridge
|
2020-02-04 23:00:44 +00:00
|
|
|
refs map[string]solver.ResultProxy
|
2018-07-11 23:51:41 +00:00
|
|
|
// lastRef solver.CachedResult
|
|
|
|
// lastRefs map[string]solver.CachedResult
|
|
|
|
// err error
|
2019-05-12 05:40:02 +00:00
|
|
|
doneCh chan struct{} // closed when result or err become valid through a call to a Return
|
|
|
|
result *frontend.Result
|
|
|
|
err error
|
|
|
|
workers frontend.WorkerInfos
|
2020-02-21 18:19:19 +00:00
|
|
|
inputs map[string]*opspb.Definition
|
2019-05-12 05:40:02 +00:00
|
|
|
isErrServerClosed bool
|
2020-06-30 01:06:02 +00:00
|
|
|
sid string
|
2017-10-01 00:58:07 +00:00
|
|
|
*pipe
|
2020-08-05 01:11:32 +00:00
|
|
|
ctrs map[string]Container
|
|
|
|
ctrsMu sync.Mutex
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
2018-05-01 01:10:54 +00:00
|
|
|
func (lbf *llbBridgeForwarder) ResolveImageConfig(ctx context.Context, req *pb.ResolveImageConfigRequest) (*pb.ResolveImageConfigResponse, error) {
|
2018-01-07 01:17:45 +00:00
|
|
|
ctx = tracing.ContextWithSpanFromContext(ctx, lbf.callCtx)
|
2018-06-24 05:52:19 +00:00
|
|
|
var platform *specs.Platform
|
|
|
|
if p := req.Platform; p != nil {
|
|
|
|
platform = &specs.Platform{
|
|
|
|
OS: p.OS,
|
|
|
|
Architecture: p.Architecture,
|
|
|
|
Variant: p.Variant,
|
|
|
|
OSVersion: p.OSVersion,
|
|
|
|
OSFeatures: p.OSFeatures,
|
|
|
|
}
|
|
|
|
}
|
2019-12-09 20:50:21 +00:00
|
|
|
dgst, dt, err := lbf.llbBridge.ResolveImageConfig(ctx, req.Ref, llb.ResolveImageConfigOpt{
|
2018-07-25 06:18:21 +00:00
|
|
|
Platform: platform,
|
|
|
|
ResolveMode: req.ResolveMode,
|
2018-07-28 00:27:41 +00:00
|
|
|
LogName: req.LogName,
|
2018-07-25 06:18:21 +00:00
|
|
|
})
|
2017-10-01 00:58:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &pb.ResolveImageConfigResponse{
|
|
|
|
Digest: dgst,
|
|
|
|
Config: dt,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-09-11 08:02:46 +00:00
|
|
|
func translateLegacySolveRequest(req *pb.SolveRequest) error {
|
|
|
|
// translates ImportCacheRefs to new CacheImports (v0.4.0)
|
|
|
|
for _, legacyImportRef := range req.ImportCacheRefsDeprecated {
|
|
|
|
im := &pb.CacheOptionsEntry{
|
|
|
|
Type: "registry",
|
|
|
|
Attrs: map[string]string{"ref": legacyImportRef},
|
|
|
|
}
|
|
|
|
// FIXME(AkihiroSuda): skip append if already exists
|
|
|
|
req.CacheImports = append(req.CacheImports, im)
|
|
|
|
}
|
|
|
|
req.ImportCacheRefsDeprecated = nil
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-05-01 01:10:54 +00:00
|
|
|
func (lbf *llbBridgeForwarder) Solve(ctx context.Context, req *pb.SolveRequest) (*pb.SolveResponse, error) {
|
2018-09-11 08:02:46 +00:00
|
|
|
if err := translateLegacySolveRequest(req); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
var cacheImports []frontend.CacheOptionsEntry
|
|
|
|
for _, e := range req.CacheImports {
|
|
|
|
cacheImports = append(cacheImports, frontend.CacheOptionsEntry{
|
|
|
|
Type: e.Type,
|
|
|
|
Attrs: e.Attrs,
|
|
|
|
})
|
|
|
|
}
|
2020-02-07 19:53:18 +00:00
|
|
|
|
2018-01-07 01:17:45 +00:00
|
|
|
ctx = tracing.ContextWithSpanFromContext(ctx, lbf.callCtx)
|
2018-07-11 23:51:41 +00:00
|
|
|
res, err := lbf.llbBridge.Solve(ctx, frontend.SolveRequest{
|
2020-02-07 19:53:18 +00:00
|
|
|
Definition: req.Definition,
|
|
|
|
Frontend: req.Frontend,
|
|
|
|
FrontendOpt: req.FrontendOpt,
|
2020-02-21 18:19:19 +00:00
|
|
|
FrontendInputs: req.FrontendInputs,
|
2020-02-07 19:53:18 +00:00
|
|
|
CacheImports: cacheImports,
|
2020-06-30 01:06:02 +00:00
|
|
|
}, lbf.sid)
|
2017-10-01 00:58:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-07-11 23:51:41 +00:00
|
|
|
if len(res.Refs) > 0 && !req.AllowResultReturn {
|
|
|
|
// this should never happen because old client shouldn't make a map request
|
|
|
|
return nil, errors.Errorf("solve did not return default result")
|
|
|
|
}
|
|
|
|
|
2020-04-17 23:13:33 +00:00
|
|
|
pbRes := &pb.Result{
|
|
|
|
Metadata: res.Metadata,
|
|
|
|
}
|
2018-07-11 23:51:41 +00:00
|
|
|
var defaultID string
|
2017-10-01 00:58:07 +00:00
|
|
|
|
2018-07-05 23:32:01 +00:00
|
|
|
lbf.mu.Lock()
|
2018-07-11 23:51:41 +00:00
|
|
|
if res.Refs != nil {
|
|
|
|
ids := make(map[string]string, len(res.Refs))
|
2019-12-09 20:50:21 +00:00
|
|
|
defs := make(map[string]*opspb.Definition, len(res.Refs))
|
2018-07-11 23:51:41 +00:00
|
|
|
for k, ref := range res.Refs {
|
|
|
|
id := identity.NewID()
|
|
|
|
if ref == nil {
|
|
|
|
id = ""
|
|
|
|
} else {
|
|
|
|
lbf.refs[id] = ref
|
|
|
|
}
|
|
|
|
ids[k] = id
|
2020-02-04 23:00:44 +00:00
|
|
|
defs[k] = ref.Definition()
|
2018-07-11 23:51:41 +00:00
|
|
|
}
|
2019-11-15 23:21:32 +00:00
|
|
|
|
|
|
|
if req.AllowResultArrayRef {
|
|
|
|
refMap := make(map[string]*pb.Ref, len(res.Refs))
|
|
|
|
for k, id := range ids {
|
2020-02-21 21:35:45 +00:00
|
|
|
refMap[k] = &pb.Ref{Id: id, Def: defs[k]}
|
2019-11-15 23:21:32 +00:00
|
|
|
}
|
|
|
|
pbRes.Result = &pb.Result_Refs{Refs: &pb.RefMap{Refs: refMap}}
|
|
|
|
} else {
|
|
|
|
pbRes.Result = &pb.Result_RefsDeprecated{RefsDeprecated: &pb.RefMapDeprecated{Refs: ids}}
|
|
|
|
}
|
2018-07-11 23:51:41 +00:00
|
|
|
} else {
|
2019-12-09 20:50:21 +00:00
|
|
|
ref := res.Ref
|
2018-07-05 23:32:01 +00:00
|
|
|
id := identity.NewID()
|
2019-12-09 20:50:21 +00:00
|
|
|
|
|
|
|
var def *opspb.Definition
|
|
|
|
if ref == nil {
|
2018-07-05 23:32:01 +00:00
|
|
|
id = ""
|
2018-07-11 23:51:41 +00:00
|
|
|
} else {
|
2020-02-04 23:00:44 +00:00
|
|
|
def = ref.Definition()
|
2019-12-09 20:50:21 +00:00
|
|
|
lbf.refs[id] = ref
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
2018-07-11 23:51:41 +00:00
|
|
|
defaultID = id
|
2019-11-15 23:21:32 +00:00
|
|
|
|
|
|
|
if req.AllowResultArrayRef {
|
2020-02-21 21:35:45 +00:00
|
|
|
pbRes.Result = &pb.Result_Ref{Ref: &pb.Ref{Id: id, Def: def}}
|
2019-11-15 23:21:32 +00:00
|
|
|
} else {
|
|
|
|
pbRes.Result = &pb.Result_RefDeprecated{RefDeprecated: id}
|
|
|
|
}
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
2018-06-20 18:17:05 +00:00
|
|
|
lbf.mu.Unlock()
|
2018-07-05 23:32:01 +00:00
|
|
|
|
|
|
|
// compatibility mode for older clients
|
2017-10-01 00:58:07 +00:00
|
|
|
if req.Final {
|
2018-07-11 23:51:41 +00:00
|
|
|
exp := map[string][]byte{}
|
2018-07-05 23:32:01 +00:00
|
|
|
if err := json.Unmarshal(req.ExporterAttr, &exp); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-07-11 23:51:41 +00:00
|
|
|
for k, v := range res.Metadata {
|
|
|
|
exp[k] = v
|
2018-07-05 23:32:01 +00:00
|
|
|
}
|
|
|
|
|
2018-08-30 14:03:29 +00:00
|
|
|
lbf.mu.Lock()
|
2018-07-11 23:51:41 +00:00
|
|
|
lbf.result = &frontend.Result{
|
|
|
|
Ref: lbf.refs[defaultID],
|
|
|
|
Metadata: exp,
|
|
|
|
}
|
2018-08-30 14:03:29 +00:00
|
|
|
lbf.mu.Unlock()
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
2018-07-05 23:32:01 +00:00
|
|
|
|
2018-07-11 23:51:41 +00:00
|
|
|
resp := &pb.SolveResponse{
|
|
|
|
Result: pbRes,
|
|
|
|
}
|
2018-07-05 23:32:01 +00:00
|
|
|
|
2018-07-11 23:51:41 +00:00
|
|
|
if !req.AllowResultReturn {
|
|
|
|
resp.Ref = defaultID
|
2017-12-14 01:29:03 +00:00
|
|
|
}
|
2018-07-05 23:32:01 +00:00
|
|
|
|
|
|
|
return resp, nil
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
2018-05-01 01:10:54 +00:00
|
|
|
func (lbf *llbBridgeForwarder) ReadFile(ctx context.Context, req *pb.ReadFileRequest) (*pb.ReadFileResponse, error) {
|
2018-01-07 01:17:45 +00:00
|
|
|
ctx = tracing.ContextWithSpanFromContext(ctx, lbf.callCtx)
|
2018-06-20 18:17:05 +00:00
|
|
|
lbf.mu.Lock()
|
2017-10-01 00:58:07 +00:00
|
|
|
ref, ok := lbf.refs[req.Ref]
|
2018-06-20 18:17:05 +00:00
|
|
|
lbf.mu.Unlock()
|
2017-10-01 00:58:07 +00:00
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("no such ref: %v", req.Ref)
|
|
|
|
}
|
2017-12-14 01:29:03 +00:00
|
|
|
if ref == nil {
|
2018-09-21 12:48:30 +00:00
|
|
|
return nil, errors.Wrapf(os.ErrNotExist, "%s not found", req.FilePath)
|
2017-12-14 01:29:03 +00:00
|
|
|
}
|
2020-02-04 23:00:44 +00:00
|
|
|
r, err := ref.Result(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
workerRef, ok := r.Sys().(*worker.WorkerRef)
|
2018-04-13 21:13:48 +00:00
|
|
|
if !ok {
|
2020-02-04 23:00:44 +00:00
|
|
|
return nil, errors.Errorf("invalid ref: %T", r.Sys())
|
2018-04-13 21:13:48 +00:00
|
|
|
}
|
2018-05-29 21:15:13 +00:00
|
|
|
|
2018-10-02 11:17:57 +00:00
|
|
|
newReq := cacheutil.ReadRequest{
|
2018-05-29 21:15:13 +00:00
|
|
|
Filename: req.FilePath,
|
|
|
|
}
|
|
|
|
if r := req.Range; r != nil {
|
2018-10-02 11:17:57 +00:00
|
|
|
newReq.Range = &cacheutil.FileRange{
|
2018-05-29 21:15:13 +00:00
|
|
|
Offset: int(r.Offset),
|
|
|
|
Length: int(r.Length),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-02 11:17:57 +00:00
|
|
|
dt, err := cacheutil.ReadFile(ctx, workerRef.ImmutableRef, newReq)
|
2017-10-01 00:58:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pb.ReadFileResponse{Data: dt}, nil
|
|
|
|
}
|
|
|
|
|
2018-09-21 12:48:30 +00:00
|
|
|
func (lbf *llbBridgeForwarder) ReadDir(ctx context.Context, req *pb.ReadDirRequest) (*pb.ReadDirResponse, error) {
|
|
|
|
ctx = tracing.ContextWithSpanFromContext(ctx, lbf.callCtx)
|
|
|
|
lbf.mu.Lock()
|
|
|
|
ref, ok := lbf.refs[req.Ref]
|
|
|
|
lbf.mu.Unlock()
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("no such ref: %v", req.Ref)
|
|
|
|
}
|
|
|
|
if ref == nil {
|
|
|
|
return nil, errors.Wrapf(os.ErrNotExist, "%s not found", req.DirPath)
|
|
|
|
}
|
2020-02-04 23:00:44 +00:00
|
|
|
r, err := ref.Result(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
workerRef, ok := r.Sys().(*worker.WorkerRef)
|
2018-09-21 12:48:30 +00:00
|
|
|
if !ok {
|
2020-02-04 23:00:44 +00:00
|
|
|
return nil, errors.Errorf("invalid ref: %T", r.Sys())
|
2018-09-21 12:48:30 +00:00
|
|
|
}
|
|
|
|
|
2018-10-02 11:17:57 +00:00
|
|
|
newReq := cacheutil.ReadDirRequest{
|
2018-09-21 12:48:30 +00:00
|
|
|
Path: req.DirPath,
|
|
|
|
IncludePattern: req.IncludePattern,
|
|
|
|
}
|
2018-10-02 11:17:57 +00:00
|
|
|
entries, err := cacheutil.ReadDir(ctx, workerRef.ImmutableRef, newReq)
|
2018-09-21 12:48:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pb.ReadDirResponse{Entries: entries}, nil
|
|
|
|
}
|
|
|
|
|
2018-10-02 13:45:39 +00:00
|
|
|
func (lbf *llbBridgeForwarder) StatFile(ctx context.Context, req *pb.StatFileRequest) (*pb.StatFileResponse, error) {
|
|
|
|
ctx = tracing.ContextWithSpanFromContext(ctx, lbf.callCtx)
|
|
|
|
lbf.mu.Lock()
|
|
|
|
ref, ok := lbf.refs[req.Ref]
|
|
|
|
lbf.mu.Unlock()
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("no such ref: %v", req.Ref)
|
|
|
|
}
|
|
|
|
if ref == nil {
|
|
|
|
return nil, errors.Wrapf(os.ErrNotExist, "%s not found", req.Path)
|
|
|
|
}
|
2020-02-04 23:00:44 +00:00
|
|
|
r, err := ref.Result(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
workerRef, ok := r.Sys().(*worker.WorkerRef)
|
2018-10-02 13:45:39 +00:00
|
|
|
if !ok {
|
2020-02-04 23:00:44 +00:00
|
|
|
return nil, errors.Errorf("invalid ref: %T", r.Sys())
|
2018-10-02 13:45:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
st, err := cacheutil.StatFile(ctx, workerRef.ImmutableRef, req.Path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &pb.StatFileResponse{Stat: st}, nil
|
|
|
|
}
|
|
|
|
|
2018-05-01 01:10:54 +00:00
|
|
|
func (lbf *llbBridgeForwarder) Ping(context.Context, *pb.PingRequest) (*pb.PongResponse, error) {
|
2018-06-30 01:35:39 +00:00
|
|
|
|
|
|
|
workers := lbf.workers.WorkerInfos()
|
|
|
|
pbWorkers := make([]*apitypes.WorkerRecord, 0, len(workers))
|
|
|
|
for _, w := range workers {
|
|
|
|
pbWorkers = append(pbWorkers, &apitypes.WorkerRecord{
|
|
|
|
ID: w.ID,
|
|
|
|
Labels: w.Labels,
|
|
|
|
Platforms: opspb.PlatformsFromSpec(w.Platforms),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-06-25 23:49:06 +00:00
|
|
|
return &pb.PongResponse{
|
|
|
|
FrontendAPICaps: pb.Caps.All(),
|
2018-06-30 01:35:39 +00:00
|
|
|
Workers: pbWorkers,
|
2018-07-18 22:32:37 +00:00
|
|
|
LLBCaps: opspb.Caps.All(),
|
2018-06-25 23:49:06 +00:00
|
|
|
}, nil
|
2017-10-01 00:58:07 +00:00
|
|
|
}
|
|
|
|
|
2018-07-05 21:15:14 +00:00
|
|
|
func (lbf *llbBridgeForwarder) Return(ctx context.Context, in *pb.ReturnRequest) (*pb.ReturnResponse, error) {
|
2018-07-09 21:26:31 +00:00
|
|
|
if in.Error != nil {
|
2020-04-23 01:30:19 +00:00
|
|
|
return lbf.setResult(nil, grpcerrors.FromGRPC(status.ErrorProto(&spb.Status{
|
2018-07-11 23:51:41 +00:00
|
|
|
Code: in.Error.Code,
|
|
|
|
Message: in.Error.Message,
|
2020-04-21 21:05:49 +00:00
|
|
|
Details: convertGogoAny(in.Error.Details),
|
|
|
|
})))
|
2020-07-18 16:11:39 +00:00
|
|
|
}
|
|
|
|
r := &frontend.Result{
|
|
|
|
Metadata: in.Result.Metadata,
|
|
|
|
}
|
2018-07-11 23:51:41 +00:00
|
|
|
|
2020-07-18 16:11:39 +00:00
|
|
|
switch res := in.Result.Result.(type) {
|
|
|
|
case *pb.Result_RefDeprecated:
|
|
|
|
ref, err := lbf.convertRef(res.RefDeprecated)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r.Ref = ref
|
|
|
|
case *pb.Result_RefsDeprecated:
|
|
|
|
m := map[string]solver.ResultProxy{}
|
|
|
|
for k, id := range res.RefsDeprecated.Refs {
|
|
|
|
ref, err := lbf.convertRef(id)
|
2019-11-15 23:21:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-18 16:11:39 +00:00
|
|
|
m[k] = ref
|
|
|
|
}
|
|
|
|
r.Refs = m
|
|
|
|
case *pb.Result_Ref:
|
|
|
|
ref, err := lbf.convertRef(res.Ref.Id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
r.Ref = ref
|
|
|
|
case *pb.Result_Refs:
|
|
|
|
m := map[string]solver.ResultProxy{}
|
|
|
|
for k, ref := range res.Refs.Refs {
|
|
|
|
ref, err := lbf.convertRef(ref.Id)
|
2018-07-11 23:51:41 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-18 16:11:39 +00:00
|
|
|
m[k] = ref
|
2018-07-09 21:26:31 +00:00
|
|
|
}
|
2020-07-18 16:11:39 +00:00
|
|
|
r.Refs = m
|
2018-07-09 21:26:31 +00:00
|
|
|
}
|
2020-07-18 16:11:39 +00:00
|
|
|
return lbf.setResult(r, nil)
|
2018-07-09 21:26:31 +00:00
|
|
|
}
|
|
|
|
|
2020-02-07 19:53:18 +00:00
|
|
|
func (lbf *llbBridgeForwarder) Inputs(ctx context.Context, in *pb.InputsRequest) (*pb.InputsResponse, error) {
|
|
|
|
return &pb.InputsResponse{
|
2020-02-21 18:19:19 +00:00
|
|
|
Definitions: lbf.inputs,
|
2020-02-07 19:53:18 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2020-08-05 01:11:32 +00:00
|
|
|
func (lbf *llbBridgeForwarder) NewContainer(ctx context.Context, in *pb.NewContainerRequest) (_ *pb.NewContainerResponse, err error) {
|
|
|
|
logrus.Debugf("|<--- NewContainer %s", in.ContainerID)
|
|
|
|
ctrReq := NewContainerRequest{
|
|
|
|
ContainerID: in.ContainerID,
|
|
|
|
NetMode: in.Network,
|
|
|
|
SecurityMode: in.Security,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range in.Mounts {
|
|
|
|
refProxy, err := lbf.convertRef(m.ResultID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "Failed to find ref %s for %q mount", m.ResultID, m.Dest)
|
|
|
|
}
|
|
|
|
ctrReq.Mounts = append(ctrReq.Mounts, Mount{
|
|
|
|
Dest: m.Dest,
|
|
|
|
Selector: m.Selector,
|
|
|
|
Readonly: m.Readonly,
|
|
|
|
MountType: m.MountType,
|
|
|
|
RefProxy: refProxy,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not using `ctx` here because it will get cancelled as soon as NewContainer returns
|
|
|
|
// and we want the context to live for the duration of the container.
|
|
|
|
ctr, err := NewContainer(context.Background(), lbf.llbBridge, ctrReq)
|
|
|
|
if err != nil {
|
|
|
|
return nil, stack.Enable(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
lbf.ctrsMu.Lock()
|
|
|
|
// ensure we are not clobbering a dup container id request
|
|
|
|
if _, ok := lbf.ctrs[in.ContainerID]; ok {
|
|
|
|
lbf.ctrsMu.Unlock()
|
|
|
|
ctr.Release(ctx)
|
|
|
|
return nil, stack.Enable(status.Errorf(codes.AlreadyExists, "Container %s already exists", in.ContainerID))
|
|
|
|
}
|
|
|
|
lbf.ctrs[in.ContainerID] = ctr
|
|
|
|
lbf.ctrsMu.Unlock()
|
|
|
|
return &pb.NewContainerResponse{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lbf *llbBridgeForwarder) ReleaseContainer(ctx context.Context, in *pb.ReleaseContainerRequest) (*pb.ReleaseContainerResponse, error) {
|
|
|
|
logrus.Debugf("|<--- ReleaseContainer %s", in.ContainerID)
|
|
|
|
lbf.ctrsMu.Lock()
|
|
|
|
ctr, ok := lbf.ctrs[in.ContainerID]
|
|
|
|
delete(lbf.ctrs, in.ContainerID)
|
|
|
|
lbf.ctrsMu.Unlock()
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("Container details for %s not found", in.ContainerID)
|
|
|
|
}
|
|
|
|
err := ctr.Release(ctx)
|
|
|
|
return &pb.ReleaseContainerResponse{}, stack.Enable(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
type processIO struct {
|
|
|
|
id string
|
|
|
|
mu sync.Mutex
|
|
|
|
resize func(context.Context, gwclient.WinSize) error
|
|
|
|
done chan struct{}
|
|
|
|
doneOnce sync.Once
|
|
|
|
// these track the process side of the io pipe for
|
|
|
|
// read (fd=0) and write (fd=1, fd=2)
|
|
|
|
processReaders map[uint32]io.ReadCloser
|
|
|
|
processWriters map[uint32]io.WriteCloser
|
|
|
|
// these track the server side of the io pipe, so
|
|
|
|
// when we receive an EOF over grpc, we will close
|
|
|
|
// this end
|
|
|
|
serverWriters map[uint32]io.WriteCloser
|
|
|
|
serverReaders map[uint32]io.ReadCloser
|
|
|
|
}
|
|
|
|
|
|
|
|
func newProcessIO(id string, openFds []uint32) *processIO {
|
|
|
|
pio := &processIO{
|
|
|
|
id: id,
|
|
|
|
processReaders: map[uint32]io.ReadCloser{},
|
|
|
|
processWriters: map[uint32]io.WriteCloser{},
|
|
|
|
serverReaders: map[uint32]io.ReadCloser{},
|
|
|
|
serverWriters: map[uint32]io.WriteCloser{},
|
|
|
|
done: make(chan struct{}),
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, fd := range openFds {
|
|
|
|
// TODO do we know which way to pipe each fd? For now assume fd0 is for
|
|
|
|
// reading, and the rest are for writing
|
|
|
|
r, w := io.Pipe()
|
|
|
|
if fd == 0 {
|
|
|
|
pio.processReaders[fd] = r
|
|
|
|
pio.serverWriters[fd] = w
|
|
|
|
} else {
|
|
|
|
pio.processWriters[fd] = w
|
|
|
|
pio.serverReaders[fd] = r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return pio
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pio *processIO) Close() (err error) {
|
|
|
|
pio.mu.Lock()
|
|
|
|
defer pio.mu.Unlock()
|
|
|
|
for fd, r := range pio.processReaders {
|
|
|
|
delete(pio.processReaders, fd)
|
|
|
|
err1 := r.Close()
|
|
|
|
if err1 != nil && err == nil {
|
|
|
|
err = stack.Enable(err1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for fd, w := range pio.serverReaders {
|
|
|
|
delete(pio.serverReaders, fd)
|
|
|
|
err1 := w.Close()
|
|
|
|
if err1 != nil && err == nil {
|
|
|
|
err = stack.Enable(err1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pio.Done()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pio *processIO) Done() {
|
|
|
|
stillOpen := len(pio.processReaders) + len(pio.processWriters) + len(pio.serverReaders) + len(pio.serverWriters)
|
|
|
|
if stillOpen == 0 {
|
|
|
|
pio.doneOnce.Do(func() {
|
|
|
|
close(pio.done)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pio *processIO) Write(f *pb.FdMessage) (err error) {
|
|
|
|
pio.mu.Lock()
|
|
|
|
writer := pio.serverWriters[f.Fd]
|
|
|
|
pio.mu.Unlock()
|
|
|
|
if writer == nil {
|
|
|
|
return status.Errorf(codes.OutOfRange, "fd %d unavailable to write", f.Fd)
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err != nil || f.EOF {
|
|
|
|
writer.Close()
|
|
|
|
pio.mu.Lock()
|
|
|
|
defer pio.mu.Unlock()
|
|
|
|
delete(pio.serverWriters, f.Fd)
|
|
|
|
pio.Done()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
if len(f.Data) > 0 {
|
|
|
|
_, err = writer.Write(f.Data)
|
|
|
|
return stack.Enable(err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type outputWriter struct {
|
|
|
|
stream pb.LLBBridge_ExecProcessServer
|
|
|
|
fd uint32
|
|
|
|
processID string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (w *outputWriter) Write(msg []byte) (int, error) {
|
|
|
|
logrus.Debugf("|---> File Message %s, fd=%d, %d bytes", w.processID, w.fd, len(msg))
|
|
|
|
err := w.stream.Send(&pb.ExecMessage{
|
|
|
|
ProcessID: w.processID,
|
|
|
|
Input: &pb.ExecMessage_File{
|
|
|
|
File: &pb.FdMessage{
|
|
|
|
Fd: w.fd,
|
|
|
|
Data: msg,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return len(msg), stack.Enable(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (lbf *llbBridgeForwarder) ExecProcess(srv pb.LLBBridge_ExecProcessServer) error {
|
|
|
|
eg, ctx := errgroup.WithContext(srv.Context())
|
|
|
|
|
|
|
|
msgs := make(chan *pb.ExecMessage)
|
|
|
|
|
|
|
|
eg.Go(func() error {
|
|
|
|
defer close(msgs)
|
|
|
|
for {
|
|
|
|
execMsg, err := srv.Recv()
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, io.EOF) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return stack.Enable(err)
|
|
|
|
}
|
|
|
|
switch m := execMsg.GetInput().(type) {
|
|
|
|
case *pb.ExecMessage_Init:
|
|
|
|
logrus.Debugf("|<--- Init Message %s", execMsg.ProcessID)
|
|
|
|
case *pb.ExecMessage_File:
|
|
|
|
if m.File.EOF {
|
|
|
|
logrus.Debugf("|<--- File Message %s, fd=%d, EOF", execMsg.ProcessID, m.File.Fd)
|
|
|
|
} else {
|
|
|
|
logrus.Debugf("|<--- File Message %s, fd=%d, %d bytes", execMsg.ProcessID, m.File.Fd, len(m.File.Data))
|
|
|
|
}
|
|
|
|
case *pb.ExecMessage_Resize:
|
|
|
|
logrus.Debugf("|<--- Resize Message %s", execMsg.ProcessID)
|
|
|
|
}
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
case msgs <- execMsg:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
eg.Go(func() error {
|
|
|
|
pios := make(map[string]*processIO)
|
|
|
|
// close any stray pios on exit to make sure
|
|
|
|
// all the associated resources get cleaned up
|
|
|
|
defer func() {
|
|
|
|
for _, pio := range pios {
|
|
|
|
pio.Close()
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
for {
|
|
|
|
var execMsg *pb.ExecMessage
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return nil
|
|
|
|
case execMsg = <-msgs:
|
|
|
|
}
|
|
|
|
if execMsg == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
pid := execMsg.ProcessID
|
|
|
|
if pid == "" {
|
|
|
|
return stack.Enable(status.Errorf(codes.InvalidArgument, "ProcessID required"))
|
|
|
|
}
|
|
|
|
|
|
|
|
pio, pioFound := pios[pid]
|
|
|
|
|
|
|
|
if data := execMsg.GetFile(); data != nil {
|
|
|
|
if !pioFound {
|
|
|
|
return stack.Enable(status.Errorf(codes.NotFound, "IO for process %q not found", pid))
|
|
|
|
}
|
|
|
|
err := pio.Write(data)
|
|
|
|
if err != nil {
|
|
|
|
return stack.Enable(err)
|
|
|
|
}
|
|
|
|
} else if resize := execMsg.GetResize(); resize != nil {
|
|
|
|
if !pioFound {
|
|
|
|
return stack.Enable(status.Errorf(codes.NotFound, "IO for process %q not found", pid))
|
|
|
|
}
|
|
|
|
pio.resize(ctx, gwclient.WinSize{
|
|
|
|
Cols: resize.Cols,
|
|
|
|
Rows: resize.Rows,
|
|
|
|
})
|
|
|
|
} else if init := execMsg.GetInit(); init != nil {
|
|
|
|
if pioFound {
|
|
|
|
return stack.Enable(status.Errorf(codes.AlreadyExists, "Process %s already exists", pid))
|
|
|
|
}
|
|
|
|
id := init.ContainerID
|
|
|
|
lbf.ctrsMu.Lock()
|
|
|
|
ctr, ok := lbf.ctrs[id]
|
|
|
|
lbf.ctrsMu.Unlock()
|
|
|
|
if !ok {
|
|
|
|
return stack.Enable(status.Errorf(codes.NotFound, "container %q previously released or not created", id))
|
|
|
|
}
|
|
|
|
|
|
|
|
initCtx, initCancel := context.WithCancel(context.Background())
|
|
|
|
defer initCancel()
|
|
|
|
|
|
|
|
pio := newProcessIO(pid, init.Fds)
|
|
|
|
pios[pid] = pio
|
|
|
|
|
|
|
|
proc, err := ctr.Start(initCtx, gwclient.StartRequest{
|
|
|
|
Args: init.Meta.Args,
|
|
|
|
Env: init.Meta.Env,
|
|
|
|
User: init.Meta.User,
|
|
|
|
Cwd: init.Meta.Cwd,
|
|
|
|
Tty: init.Tty,
|
|
|
|
Stdin: pio.processReaders[0],
|
|
|
|
Stdout: pio.processWriters[1],
|
|
|
|
Stderr: pio.processWriters[2],
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return stack.Enable(err)
|
|
|
|
}
|
|
|
|
pio.resize = proc.Resize
|
|
|
|
|
|
|
|
// ensure process has been canceled if the container is released
|
|
|
|
ctr.OnRelease(func() error {
|
|
|
|
initCancel()
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
eg.Go(func() error {
|
|
|
|
<-pio.done
|
|
|
|
logrus.Debugf("|---> Done Message %s", pid)
|
|
|
|
err := srv.Send(&pb.ExecMessage{
|
|
|
|
ProcessID: pid,
|
|
|
|
Input: &pb.ExecMessage_Done{
|
|
|
|
Done: &pb.DoneMessage{},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return stack.Enable(err)
|
|
|
|
})
|
|
|
|
|
|
|
|
eg.Go(func() error {
|
|
|
|
defer func() {
|
|
|
|
pio.Close()
|
|
|
|
}()
|
|
|
|
err := proc.Wait()
|
|
|
|
|
|
|
|
var status uint32
|
|
|
|
var exitError *errdefs.ExitError
|
|
|
|
var errMsg string
|
|
|
|
if err != nil {
|
|
|
|
status = containerd.UnknownExitStatus
|
|
|
|
errMsg = err.Error()
|
|
|
|
}
|
|
|
|
if errors.As(err, &exitError) {
|
|
|
|
status = exitError.ExitCode
|
|
|
|
}
|
|
|
|
logrus.Debugf("|---> Exit Message %s, code=%d, error=%s", pid, status, errMsg)
|
|
|
|
sendErr := srv.Send(&pb.ExecMessage{
|
|
|
|
ProcessID: pid,
|
|
|
|
Input: &pb.ExecMessage_Exit{
|
|
|
|
Exit: &pb.ExitMessage{
|
|
|
|
Code: status,
|
|
|
|
Error: errMsg,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
if sendErr != nil && err != nil {
|
|
|
|
return errors.Wrap(sendErr, err.Error())
|
|
|
|
} else if sendErr != nil {
|
|
|
|
return stack.Enable(sendErr)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil && status != 0 {
|
|
|
|
// this was a container exit error which is "normal" so
|
|
|
|
// don't return this error from the errgroup
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return stack.Enable(err)
|
|
|
|
})
|
|
|
|
|
|
|
|
logrus.Debugf("|---> Started Message %s", pid)
|
|
|
|
err = srv.Send(&pb.ExecMessage{
|
|
|
|
ProcessID: pid,
|
|
|
|
Input: &pb.ExecMessage_Started{
|
|
|
|
Started: &pb.StartedMessage{},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return stack.Enable(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// start sending Fd output back to client, this is done after
|
|
|
|
// StartedMessage so that Fd output will not potentially arrive
|
|
|
|
// to the client before "Started" as the container starts up.
|
|
|
|
for fd, file := range pio.serverReaders {
|
|
|
|
fd, file := fd, file
|
|
|
|
eg.Go(func() error {
|
|
|
|
defer func() {
|
|
|
|
file.Close()
|
|
|
|
pio.mu.Lock()
|
|
|
|
defer pio.mu.Unlock()
|
|
|
|
w := pio.processWriters[fd]
|
|
|
|
if w != nil {
|
|
|
|
w.Close()
|
|
|
|
}
|
|
|
|
delete(pio.processWriters, fd)
|
|
|
|
pio.Done()
|
|
|
|
}()
|
|
|
|
dest := &outputWriter{
|
|
|
|
stream: srv,
|
|
|
|
fd: uint32(fd),
|
|
|
|
processID: pid,
|
|
|
|
}
|
|
|
|
_, err := io.Copy(dest, file)
|
|
|
|
// ignore ErrClosedPipe, it is EOF for our usage.
|
|
|
|
if err != nil && !errors.Is(err, io.ErrClosedPipe) {
|
|
|
|
return stack.Enable(err)
|
|
|
|
}
|
|
|
|
// no error so must be EOF
|
|
|
|
logrus.Debugf("|---> File Message %s, fd=%d, EOF", pid, fd)
|
|
|
|
err = srv.Send(&pb.ExecMessage{
|
|
|
|
ProcessID: pid,
|
|
|
|
Input: &pb.ExecMessage_File{
|
|
|
|
File: &pb.FdMessage{
|
|
|
|
Fd: uint32(fd),
|
|
|
|
EOF: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
return stack.Enable(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
err := eg.Wait()
|
|
|
|
return stack.Enable(err)
|
|
|
|
}
|
|
|
|
|
2020-02-21 21:35:45 +00:00
|
|
|
func (lbf *llbBridgeForwarder) convertRef(id string) (solver.ResultProxy, error) {
|
|
|
|
if id == "" {
|
2018-07-11 23:51:41 +00:00
|
|
|
return nil, nil
|
|
|
|
}
|
2019-11-15 23:21:32 +00:00
|
|
|
|
2018-08-30 14:03:29 +00:00
|
|
|
lbf.mu.Lock()
|
|
|
|
defer lbf.mu.Unlock()
|
2019-11-15 23:21:32 +00:00
|
|
|
|
2018-07-11 23:51:41 +00:00
|
|
|
r, ok := lbf.refs[id]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("return reference %s not found", id)
|
2018-07-09 21:26:31 +00:00
|
|
|
}
|
2019-11-15 23:21:32 +00:00
|
|
|
|
2018-07-11 23:51:41 +00:00
|
|
|
return r, nil
|
2018-07-05 21:15:14 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 00:58:07 +00:00
|
|
|
func serve(ctx context.Context, grpcServer *grpc.Server, conn net.Conn) {
|
|
|
|
go func() {
|
|
|
|
<-ctx.Done()
|
|
|
|
conn.Close()
|
|
|
|
}()
|
|
|
|
logrus.Debugf("serving grpc connection")
|
|
|
|
(&http2.Server{}).ServeConn(conn, &http2.ServeConnOpts{Handler: grpcServer})
|
|
|
|
}
|
2018-07-26 20:51:28 +00:00
|
|
|
|
|
|
|
type markTypeFrontend struct{}
|
|
|
|
|
|
|
|
func (*markTypeFrontend) SetImageOption(ii *llb.ImageInfo) {
|
|
|
|
ii.RecordType = string(client.UsageRecordTypeFrontend)
|
|
|
|
}
|
2020-04-21 21:05:49 +00:00
|
|
|
|
|
|
|
func convertGogoAny(in []*gogotypes.Any) []*any.Any {
|
|
|
|
out := make([]*any.Any, len(in))
|
|
|
|
for i := range in {
|
|
|
|
out[i] = &any.Any{TypeUrl: in[i].TypeUrl, Value: in[i].Value}
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|