commit
7d0fea5b60
|
@ -105,7 +105,7 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*
|
|||
}
|
||||
}
|
||||
|
||||
if err := c.solver.Solve(ctx, req.Ref, frontend, req.Definition, expi, req.FrontendAttrs); err != nil {
|
||||
if err := c.solver.Solve(ctx, req.Ref, frontend, req.Definition, expi, req.FrontendAttrs, c.opt.Frontends); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &controlapi.SolveResponse{}, nil
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
localexporter "github.com/moby/buildkit/exporter/local"
|
||||
"github.com/moby/buildkit/frontend"
|
||||
"github.com/moby/buildkit/frontend/dockerfile"
|
||||
"github.com/moby/buildkit/frontend/gateway"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/snapshot/blobmapping"
|
||||
"github.com/moby/buildkit/source"
|
||||
|
@ -128,6 +129,7 @@ func defaultControllerOpts(root string, pd pullDeps) (*Opt, error) {
|
|||
|
||||
frontends := map[string]frontend.Frontend{}
|
||||
frontends["dockerfile.v0"] = dockerfile.NewDockerfileFrontend()
|
||||
frontends["gateway.v0"] = gateway.NewGatewayFrontend()
|
||||
|
||||
return &Opt{
|
||||
Snapshotter: snapshotter,
|
||||
|
|
|
@ -115,7 +115,7 @@ func TestControl(t *testing.T) {
|
|||
|
||||
stderr := bytes.NewBuffer(nil)
|
||||
|
||||
err = w.Exec(ctx, meta, snap, nil, nil, &nopCloser{stderr})
|
||||
err = w.Exec(ctx, meta, snap, nil, nil, nil, &nopCloser{stderr})
|
||||
assert.Error(t, err) // Read-only root
|
||||
// typical error is like `mkdir /.../rootfs/proc: read-only file system`.
|
||||
// make sure the error is caused before running `echo foo > /bar`.
|
||||
|
@ -124,7 +124,7 @@ func TestControl(t *testing.T) {
|
|||
root, err := cm.New(ctx, snap)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = w.Exec(ctx, meta, root, nil, nil, nil)
|
||||
err = w.Exec(ctx, meta, root, nil, nil, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rf, err := root.Commit(ctx)
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"github.com/moby/buildkit/cache"
|
||||
"github.com/moby/buildkit/cache/metadata"
|
||||
"github.com/moby/buildkit/exporter"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
||||
"github.com/moby/buildkit/snapshot"
|
||||
"github.com/moby/buildkit/util/flightcontrol"
|
||||
"github.com/moby/buildkit/util/progress"
|
||||
|
@ -155,7 +154,7 @@ func (e *imageExporterInstance) Name() string {
|
|||
return "exporting to image"
|
||||
}
|
||||
|
||||
func (e *imageExporterInstance) Export(ctx context.Context, ref cache.ImmutableRef, opt map[string]interface{}) error {
|
||||
func (e *imageExporterInstance) Export(ctx context.Context, ref cache.ImmutableRef, opt map[string][]byte) error {
|
||||
layersDone := oneOffProgress(ctx, "exporting layers")
|
||||
diffPairs, err := e.getBlobs(ctx, ref)
|
||||
if err != nil {
|
||||
|
@ -169,15 +168,10 @@ func (e *imageExporterInstance) Export(ctx context.Context, ref cache.ImmutableR
|
|||
}
|
||||
|
||||
var dt []byte
|
||||
if imgInterface, ok := opt[exporterImageConfig]; ok {
|
||||
img, ok := imgInterface.(*dockerfile2llb.Image)
|
||||
if !ok {
|
||||
return errors.Errorf("invalid image config")
|
||||
}
|
||||
setDiffIDs(img, diffIDs)
|
||||
dt, err = json.Marshal(img)
|
||||
if config, ok := opt[exporterImageConfig]; ok {
|
||||
dt, err = setDiffIDs(config, diffIDs)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal image config")
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dt, err = json.Marshal(imageConfig(diffIDs))
|
||||
|
@ -270,9 +264,20 @@ func imageConfig(diffIDs []digest.Digest) ocispec.Image {
|
|||
return img
|
||||
}
|
||||
|
||||
func setDiffIDs(img *dockerfile2llb.Image, diffIDs []digest.Digest) {
|
||||
img.RootFS.Type = "layers"
|
||||
img.RootFS.DiffIDs = diffIDs
|
||||
func setDiffIDs(config []byte, diffIDs []digest.Digest) ([]byte, error) {
|
||||
mp := map[string]json.RawMessage{}
|
||||
if err := json.Unmarshal(config, &mp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rootFS ocispec.RootFS
|
||||
rootFS.Type = "layers"
|
||||
rootFS.DiffIDs = diffIDs
|
||||
dt, err := json.Marshal(rootFS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mp["rootfs"] = dt
|
||||
return json.Marshal(mp)
|
||||
}
|
||||
|
||||
func oneOffProgress(ctx context.Context, id string) func(err error) error {
|
||||
|
|
|
@ -11,5 +11,5 @@ type Exporter interface {
|
|||
|
||||
type ExporterInstance interface {
|
||||
Name() string
|
||||
Export(context.Context, cache.ImmutableRef, map[string]interface{}) error
|
||||
Export(context.Context, cache.ImmutableRef, map[string][]byte) error
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ func (e *localExporterInstance) Name() string {
|
|||
return "exporting to client"
|
||||
}
|
||||
|
||||
func (e *localExporterInstance) Export(ctx context.Context, ref cache.ImmutableRef, opt map[string]interface{}) error {
|
||||
func (e *localExporterInstance) Export(ctx context.Context, ref cache.ImmutableRef, opt map[string][]byte) error {
|
||||
mount, err := ref.Mount(ctx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
FROM golang:1.8-alpine AS builder
|
||||
COPY . /go/src/github.com/moby/buildkit
|
||||
RUN CGO_ENABLED=0 go build -o /dockerfile-frontend --ldflags '-extldflags "-static"' github.com/moby/buildkit/frontend/dockerfile/cmd/dockerfile-frontend
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /dockerfile-frontend /bin/dockerfile-frontend
|
||||
ENTRYPOINT ["/bin/dockerfile-frontend"]
|
|
@ -0,0 +1,107 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
||||
"github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
keyTarget = "target"
|
||||
keyFilename = "filename"
|
||||
exporterImageConfig = "containerimage.config"
|
||||
defaultDockerfileName = "Dockerfile"
|
||||
localNameDockerfile = "dockerfile"
|
||||
buildArgPrefix = "build-arg:"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
logrus.Errorf("fatal error: %+v", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
c, err := client.Current()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create client")
|
||||
}
|
||||
|
||||
ctx := appcontext.Context()
|
||||
|
||||
opts := c.Opts()
|
||||
|
||||
filename := opts[keyFilename]
|
||||
if filename == "" {
|
||||
filename = defaultDockerfileName
|
||||
}
|
||||
if path.Base(filename) != filename {
|
||||
return errors.Errorf("invalid filename: %s", filename)
|
||||
}
|
||||
|
||||
src := llb.Local(localNameDockerfile,
|
||||
llb.IncludePatterns([]string{filename}),
|
||||
llb.SessionID(c.SessionID()),
|
||||
)
|
||||
def, err := src.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ref, err := c.Solve(ctx, def.ToPB(), "", nil, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dtDockerfile, err := ref.ReadFile(ctx, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
st, img, err := dockerfile2llb.Dockerfile2LLB(ctx, dtDockerfile, dockerfile2llb.ConvertOpt{
|
||||
Target: opts[keyTarget],
|
||||
MetaResolver: c,
|
||||
BuildArgs: filterBuildArgs(opts),
|
||||
SessionID: c.SessionID(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
def, err = st.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config, err := json.Marshal(img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.Solve(ctx, def.ToPB(), "", map[string][]byte{
|
||||
exporterImageConfig: config,
|
||||
}, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterBuildArgs(opt map[string]string) map[string]string {
|
||||
m := map[string]string{}
|
||||
for k, v := range opt {
|
||||
if strings.HasPrefix(k, buildArgPrefix) {
|
||||
m[strings.TrimPrefix(k, buildArgPrefix)] = v
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package dockerfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
@ -10,6 +11,7 @@ import (
|
|||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/frontend"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/snapshot"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
|
@ -30,7 +32,7 @@ func NewDockerfileFrontend() frontend.Frontend {
|
|||
|
||||
type dfFrontend struct{}
|
||||
|
||||
func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string) (retRef cache.ImmutableRef, exporterAttr map[string]interface{}, retErr error) {
|
||||
func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string) (retRef cache.ImmutableRef, exporterAttr map[string][]byte, retErr error) {
|
||||
|
||||
filename := opts[keyFilename]
|
||||
if filename == "" {
|
||||
|
@ -40,14 +42,18 @@ func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBr
|
|||
return nil, nil, errors.Errorf("invalid filename %s", filename)
|
||||
}
|
||||
|
||||
src := llb.Local(localNameDockerfile, llb.IncludePatterns([]string{filename}))
|
||||
sid := session.FromContext(ctx)
|
||||
|
||||
src := llb.Local(localNameDockerfile,
|
||||
llb.IncludePatterns([]string{filename}),
|
||||
llb.SessionID(sid),
|
||||
)
|
||||
def, err := src.Marshal()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defPB := def.ToPB()
|
||||
|
||||
ref, err := llbBridge.Solve(ctx, defPB)
|
||||
ref, _, err := llbBridge.Solve(ctx, def.ToPB(), "", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -95,6 +101,7 @@ func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBr
|
|||
Target: opts[keyTarget],
|
||||
MetaResolver: llbBridge,
|
||||
BuildArgs: filterBuildArgs(opts),
|
||||
SessionID: sid,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
@ -105,14 +112,18 @@ func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBr
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defPB = def.ToPB()
|
||||
retRef, err = llbBridge.Solve(ctx, defPB)
|
||||
retRef, _, err = llbBridge.Solve(ctx, def.ToPB(), "", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return retRef, map[string]interface{}{
|
||||
exporterImageConfig: img,
|
||||
config, err := json.Marshal(img)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return retRef, map[string][]byte{
|
||||
exporterImageConfig: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ type ConvertOpt struct {
|
|||
Target string
|
||||
MetaResolver llb.ImageMetaResolver
|
||||
BuildArgs map[string]string
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error) {
|
||||
|
@ -157,6 +158,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
|
|||
metaArgs: metaArgs,
|
||||
buildArgValues: opt.BuildArgs,
|
||||
shlex: shlex,
|
||||
sessionID: opt.SessionID,
|
||||
}
|
||||
|
||||
if err = dispatchOnBuild(d, d.image.Config.OnBuild, opt); err != nil {
|
||||
|
@ -187,6 +189,7 @@ type dispatchOpt struct {
|
|||
metaArgs []instructions.ArgCommand
|
||||
buildArgValues map[string]string
|
||||
shlex *ShellLex
|
||||
sessionID string
|
||||
}
|
||||
|
||||
func dispatch(d *dispatchState, cmd instructions.Command, opt dispatchOpt) error {
|
||||
|
@ -208,7 +211,7 @@ func dispatch(d *dispatchState, cmd instructions.Command, opt dispatchOpt) error
|
|||
case *instructions.WorkdirCommand:
|
||||
err = dispatchWorkdir(d, c)
|
||||
case *instructions.AddCommand:
|
||||
err = dispatchCopy(d, c.SourcesAndDest, llb.Local(localNameContext))
|
||||
err = dispatchCopy(d, c.SourcesAndDest, llb.Local(localNameContext, llb.SessionID(opt.sessionID)))
|
||||
case *instructions.LabelCommand:
|
||||
err = dispatchLabel(d, c)
|
||||
case *instructions.OnbuildCommand:
|
||||
|
@ -232,7 +235,7 @@ func dispatch(d *dispatchState, cmd instructions.Command, opt dispatchOpt) error
|
|||
case *instructions.ArgCommand:
|
||||
err = dispatchArg(d, c, opt.metaArgs, opt.buildArgValues)
|
||||
case *instructions.CopyCommand:
|
||||
l := llb.Local(localNameContext)
|
||||
l := llb.Local(localNameContext, llb.SessionID(opt.sessionID))
|
||||
if c.From != "" {
|
||||
index, err := strconv.Atoi(c.From)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
package frontend
|
||||
|
||||
import (
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"golang.org/x/net/context"
|
||||
"io"
|
||||
|
||||
"github.com/moby/buildkit/cache"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
"github.com/moby/buildkit/worker"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Frontend interface {
|
||||
Solve(ctx context.Context, llbBridge FrontendLLBBridge, opt map[string]string) (cache.ImmutableRef, map[string]interface{}, error)
|
||||
Solve(ctx context.Context, llb FrontendLLBBridge, opt map[string]string) (cache.ImmutableRef, map[string][]byte, error)
|
||||
}
|
||||
|
||||
type FrontendLLBBridge interface {
|
||||
Solve(ctx context.Context, def *pb.Definition) (cache.ImmutableRef, error)
|
||||
Solve(ctx context.Context, def *pb.Definition, frontend string, opts map[string]string) (cache.ImmutableRef, map[string][]byte, error)
|
||||
ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error)
|
||||
Exec(ctx context.Context, meta worker.Meta, rootfs cache.ImmutableRef, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error
|
||||
}
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||
opspb "github.com/moby/buildkit/solver/pb"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const frontendPrefix = "BUILDKIT_FRONTEND_OPT_"
|
||||
|
||||
func Current() (*Client, error) {
|
||||
ctx, conn, err := grpcClientConn(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := pb.NewLLBBridgeClient(conn)
|
||||
|
||||
_, err = c.Ping(ctx, &pb.PingRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Client{client: c, opts: opts(), sessionID: sessionID()}, nil
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
client pb.LLBBridgeClient
|
||||
opts map[string]string
|
||||
sessionID string
|
||||
}
|
||||
|
||||
func (c *Client) Solve(ctx context.Context, def *opspb.Definition, frontend string, exporterAttr map[string][]byte, final bool) (*Reference, error) {
|
||||
dt, err := json.Marshal(exporterAttr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := &pb.SolveRequest{Definition: def, Frontend: frontend, Final: final, ExporterAttr: dt}
|
||||
resp, err := c.client.Solve(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Reference{id: resp.Ref, c: c}, nil
|
||||
}
|
||||
|
||||
func (c *Client) ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) {
|
||||
resp, err := c.client.ResolveImageConfig(ctx, &pb.ResolveImageConfigRequest{Ref: ref})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return resp.Digest, resp.Config, nil
|
||||
}
|
||||
|
||||
func (c *Client) Opts() map[string]string {
|
||||
return c.opts
|
||||
}
|
||||
|
||||
func (c *Client) SessionID() string {
|
||||
return c.sessionID
|
||||
}
|
||||
|
||||
type Reference struct {
|
||||
id string
|
||||
c *Client
|
||||
}
|
||||
|
||||
func (r *Reference) ReadFile(ctx context.Context, fp string) ([]byte, error) {
|
||||
resp, err := r.c.client.ReadFile(ctx, &pb.ReadFileRequest{FilePath: fp, Ref: r.id})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
func grpcClientConn(ctx context.Context) (context.Context, *grpc.ClientConn, error) {
|
||||
dialOpt := grpc.WithDialer(func(addr string, d time.Duration) (net.Conn, error) {
|
||||
return stdioConn(), nil
|
||||
})
|
||||
|
||||
cc, err := grpc.DialContext(ctx, "", dialOpt, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed to create grpc client")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
_ = cancel
|
||||
// go monitorHealth(ctx, cc, cancel)
|
||||
|
||||
return ctx, cc, nil
|
||||
}
|
||||
|
||||
func stdioConn() net.Conn {
|
||||
return &conn{os.Stdin, os.Stdout, os.Stdout}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
func opts() map[string]string {
|
||||
opts := map[string]string{}
|
||||
for _, env := range os.Environ() {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
k := parts[0]
|
||||
v := ""
|
||||
if len(parts) == 2 {
|
||||
v = parts[1]
|
||||
}
|
||||
if !strings.HasPrefix(k, frontendPrefix) {
|
||||
continue
|
||||
}
|
||||
parts = strings.SplitN(v, "=", 2)
|
||||
v = ""
|
||||
if len(parts) == 2 {
|
||||
v = parts[1]
|
||||
}
|
||||
opts[parts[0]] = v
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func sessionID() string {
|
||||
return os.Getenv("BUILDKIT_SESSION_ID")
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
package gateway
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/moby/buildkit/cache"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/frontend"
|
||||
pb "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/worker"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/http2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/health"
|
||||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
)
|
||||
|
||||
const (
|
||||
keySource = "source"
|
||||
keyDevel = "gateway-devel"
|
||||
exporterImageConfig = "containerimage.config"
|
||||
)
|
||||
|
||||
func NewGatewayFrontend() frontend.Frontend {
|
||||
return &gatewayFrontend{}
|
||||
}
|
||||
|
||||
type gatewayFrontend struct {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string) (retRef cache.ImmutableRef, exporterAttr map[string][]byte, retErr error) {
|
||||
|
||||
source, ok := opts[keySource]
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("no source specified for gateway")
|
||||
}
|
||||
|
||||
sid := session.FromContext(ctx)
|
||||
|
||||
_, isDevel := opts[keyDevel]
|
||||
var img ocispec.Image
|
||||
var rootFS cache.ImmutableRef
|
||||
|
||||
if isDevel {
|
||||
ref, exp, err := llbBridge.Solve(session.NewContext(ctx, "gateway:"+sid), nil, source, filterPrefix(opts, "gateway-"))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rootFS = ref
|
||||
config, ok := exp[exporterImageConfig]
|
||||
if ok {
|
||||
if err := json.Unmarshal(config, &img); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sourceRef, err := reference.ParseNormalizedNamed(source)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dgst, config, err := llbBridge.ResolveImageConfig(ctx, sourceRef.String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(config, &img); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sourceRef, err = reference.WithDigest(sourceRef, dgst)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
src := llb.Image(sourceRef.String())
|
||||
|
||||
def, err := src.Marshal()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ref, _, err := llbBridge.Solve(ctx, def.ToPB(), "", nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rootFS = ref
|
||||
}
|
||||
|
||||
lbf, err := newLLBBrideForwarder(ctx, llbBridge)
|
||||
defer lbf.conn.Close()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
err = llbBridge.Exec(ctx, worker.Meta{
|
||||
Env: env,
|
||||
Args: args,
|
||||
Cwd: cwd,
|
||||
}, rootFS, lbf.Stdin, lbf.Stdout, os.Stderr)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return lbf.lastRef, lbf.exporterAttr, nil
|
||||
}
|
||||
|
||||
func newLLBBrideForwarder(ctx context.Context, llbBridge frontend.FrontendLLBBridge) (*llbBrideForwarder, error) {
|
||||
lbf := &llbBrideForwarder{
|
||||
llbBridge: llbBridge,
|
||||
refs: map[string]cache.ImmutableRef{},
|
||||
pipe: newPipe(),
|
||||
}
|
||||
|
||||
server := grpc.NewServer()
|
||||
grpc_health_v1.RegisterHealthServer(server, health.NewServer())
|
||||
pb.RegisterLLBBridgeServer(server, lbf)
|
||||
|
||||
go serve(ctx, server, lbf.conn)
|
||||
|
||||
return lbf, nil
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
type llbBrideForwarder struct {
|
||||
llbBridge frontend.FrontendLLBBridge
|
||||
refs map[string]cache.ImmutableRef
|
||||
lastRef cache.ImmutableRef
|
||||
exporterAttr map[string][]byte
|
||||
*pipe
|
||||
}
|
||||
|
||||
func (lbf *llbBrideForwarder) ResolveImageConfig(ctx context.Context, req *pb.ResolveImageConfigRequest) (*pb.ResolveImageConfigResponse, error) {
|
||||
dgst, dt, err := lbf.llbBridge.ResolveImageConfig(ctx, req.Ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.ResolveImageConfigResponse{
|
||||
Digest: dgst,
|
||||
Config: dt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (lbf *llbBrideForwarder) Solve(ctx context.Context, req *pb.SolveRequest) (*pb.SolveResponse, error) {
|
||||
ref, expResp, err := lbf.llbBridge.Solve(ctx, req.Definition, req.Frontend, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exp := map[string][]byte{}
|
||||
if err := json.Unmarshal(req.ExporterAttr, &exp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if expResp != nil {
|
||||
for k, v := range expResp {
|
||||
exp[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
id := identity.NewID()
|
||||
lbf.refs[id] = ref
|
||||
if req.Final {
|
||||
lbf.lastRef = ref
|
||||
lbf.exporterAttr = exp
|
||||
}
|
||||
return &pb.SolveResponse{Ref: id}, nil
|
||||
}
|
||||
func (lbf *llbBrideForwarder) ReadFile(ctx context.Context, req *pb.ReadFileRequest) (*pb.ReadFileResponse, error) {
|
||||
ref, ok := lbf.refs[req.Ref]
|
||||
if !ok {
|
||||
return nil, errors.Errorf("no such ref: %v", req.Ref)
|
||||
}
|
||||
|
||||
mount, err := ref.Mount(ctx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lm := snapshot.LocalMounter(mount)
|
||||
|
||||
root, err := lm.Mount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if lm != nil {
|
||||
lm.Unmount()
|
||||
}
|
||||
}()
|
||||
|
||||
dt, err := ioutil.ReadFile(filepath.Join(root, req.FilePath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := lm.Unmount(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lm = nil
|
||||
|
||||
return &pb.ReadFileResponse{Data: dt}, nil
|
||||
}
|
||||
|
||||
func (lbf *llbBrideForwarder) Ping(context.Context, *pb.PingRequest) (*pb.PongResponse, error) {
|
||||
return &pb.PongResponse{}, nil
|
||||
}
|
||||
|
||||
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})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,52 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package moby.buildkit.v1.frontend;
|
||||
|
||||
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
|
||||
import "github.com/moby/buildkit/solver/pb/ops.proto";
|
||||
|
||||
option (gogoproto.sizer_all) = true;
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
|
||||
service LLBBridge {
|
||||
rpc ResolveImageConfig(ResolveImageConfigRequest) returns (ResolveImageConfigResponse);
|
||||
rpc Solve(SolveRequest) returns (SolveResponse);
|
||||
rpc ReadFile(ReadFileRequest) returns (ReadFileResponse);
|
||||
rpc Ping(PingRequest) returns (PongResponse);
|
||||
}
|
||||
|
||||
message ResolveImageConfigRequest {
|
||||
string Ref = 1;
|
||||
}
|
||||
|
||||
message ResolveImageConfigResponse {
|
||||
string Digest = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
|
||||
bytes Config = 2;
|
||||
}
|
||||
|
||||
message SolveRequest {
|
||||
pb.Definition Definition = 1;
|
||||
string Frontend = 2;
|
||||
bool Final = 10;
|
||||
bytes ExporterAttr = 11;
|
||||
}
|
||||
|
||||
message SolveResponse {
|
||||
string Ref = 1; // can be used by readfile request
|
||||
bytes ExporterAttr = 2;
|
||||
}
|
||||
|
||||
message ReadFileRequest {
|
||||
string Ref = 1;
|
||||
string FilePath = 2;
|
||||
}
|
||||
|
||||
message ReadFileResponse {
|
||||
bytes Data = 1;
|
||||
}
|
||||
|
||||
message PingRequest{
|
||||
}
|
||||
message PongResponse{
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package moby_buildkit_v1_frontend
|
||||
|
||||
//go:generate protoc -I=. -I=../../../vendor/ -I=/Users/tonistiigi/gocode/src --gogo_out=plugins=grpc:. gateway.proto
|
|
@ -110,7 +110,7 @@ func (e *execOp) Run(ctx context.Context, inputs []Reference) ([]Reference, erro
|
|||
defer stdout.Close()
|
||||
defer stderr.Close()
|
||||
|
||||
if err := e.w.Exec(ctx, meta, root, mounts, stdout, stderr); err != nil {
|
||||
if err := e.w.Exec(ctx, meta, root, mounts, nil, stdout, stderr); err != nil {
|
||||
return nil, errors.Wrapf(err, "worker failed running %v", meta.Args)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package solver
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -44,7 +45,7 @@ func NewLLBSolver(opt LLBOpt) *Solver {
|
|||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}, opt.InstructionCache, opt.ImageSource)
|
||||
}, opt.InstructionCache, opt.ImageSource, opt.Worker, opt.CacheManager)
|
||||
return s
|
||||
}
|
||||
|
||||
|
@ -76,13 +77,15 @@ type Solver struct {
|
|||
jobs *jobList
|
||||
cache InstructionCache
|
||||
imageSource source.Source
|
||||
worker worker.Worker
|
||||
cm cache.Manager // TODO: remove with immutableRef.New()
|
||||
}
|
||||
|
||||
func New(resolve ResolveOpFunc, cache InstructionCache, imageSource source.Source) *Solver {
|
||||
return &Solver{resolve: resolve, jobs: newJobList(), cache: cache, imageSource: imageSource}
|
||||
func New(resolve ResolveOpFunc, cache InstructionCache, imageSource source.Source, worker worker.Worker, cm cache.Manager) *Solver {
|
||||
return &Solver{resolve: resolve, jobs: newJobList(), cache: cache, imageSource: imageSource, worker: worker, cm: cm}
|
||||
}
|
||||
|
||||
func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, def *pb.Definition, exp exporter.ExporterInstance, frontendOpt map[string]string) error {
|
||||
func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, def *pb.Definition, exp exporter.ExporterInstance, frontendOpt map[string]string, allFrontends map[string]frontend.Frontend) error {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
@ -96,7 +99,7 @@ func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, def
|
|||
}
|
||||
|
||||
var ref Reference
|
||||
var exporterOpt map[string]interface{}
|
||||
var exporterOpt map[string][]byte
|
||||
if def != nil {
|
||||
var inp *Input
|
||||
inp, err = j.load(def, s.resolve)
|
||||
|
@ -107,9 +110,12 @@ func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, def
|
|||
ref, err = j.getRef(ctx, inp.Vertex.(*vertex), inp.Index)
|
||||
} else {
|
||||
ref, exporterOpt, err = f.Solve(ctx, &llbBridge{
|
||||
worker: s.worker,
|
||||
job: j,
|
||||
cm: s.cm,
|
||||
resolveOp: s.resolve,
|
||||
resolveImageConfig: s.imageSource.(resolveImageConfig),
|
||||
allFrontends: allFrontends,
|
||||
}, frontendOpt)
|
||||
}
|
||||
j.discard()
|
||||
|
@ -566,29 +572,56 @@ type VertexResult struct {
|
|||
// llbBridge is an helper used by frontends
|
||||
type llbBridge struct {
|
||||
resolveImageConfig
|
||||
job *job
|
||||
resolveOp ResolveOpFunc
|
||||
job *job
|
||||
resolveOp ResolveOpFunc
|
||||
worker worker.Worker
|
||||
allFrontends map[string]frontend.Frontend
|
||||
cm cache.Manager
|
||||
}
|
||||
|
||||
type resolveImageConfig interface {
|
||||
ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error)
|
||||
}
|
||||
|
||||
func (s *llbBridge) Solve(ctx context.Context, def *pb.Definition) (cache.ImmutableRef, error) {
|
||||
func (s *llbBridge) Solve(ctx context.Context, def *pb.Definition, frontend string, opts map[string]string) (cache.ImmutableRef, map[string][]byte, error) {
|
||||
if def == nil {
|
||||
f, ok := s.allFrontends[frontend]
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("invalid frontend: %s", frontend)
|
||||
}
|
||||
ref, exporterOpt, err := f.Solve(ctx, s, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
immutable, ok := toImmutableRef(ref)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("invalid reference for exporting: %T", ref)
|
||||
}
|
||||
return immutable, exporterOpt, nil
|
||||
}
|
||||
inp, err := s.job.load(def, s.resolveOp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
ref, err := s.job.getRef(ctx, inp.Vertex.(*vertex), inp.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
immutable, ok := toImmutableRef(ref)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("invalid reference for exporting: %T", ref)
|
||||
return nil, nil, errors.Errorf("invalid reference for exporting: %T", ref)
|
||||
}
|
||||
|
||||
return immutable, nil
|
||||
return immutable, nil, nil
|
||||
}
|
||||
|
||||
func (s *llbBridge) Exec(ctx context.Context, meta worker.Meta, rootFS cache.ImmutableRef, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
|
||||
active, err := s.cm.New(ctx, rootFS)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer active.Release(context.TODO())
|
||||
return s.worker.Exec(ctx, meta, active, nil, stdin, stdout, stderr)
|
||||
}
|
||||
|
||||
func cacheKeyForIndex(dgst digest.Digest, index Index) digest.Digest {
|
||||
|
|
|
@ -2,6 +2,7 @@ package solver
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
|
@ -51,6 +52,10 @@ func (s *sourceOp) instance(ctx context.Context) (source.SourceInstance, error)
|
|||
switch k {
|
||||
case pb.AttrLocalSessionID:
|
||||
id.SessionID = v
|
||||
if p := strings.SplitN(v, ":", 2); len(p) == 2 {
|
||||
id.Name = p[0] + "-" + id.Name
|
||||
id.SessionID = p[1]
|
||||
}
|
||||
case pb.AttrIncludePatterns:
|
||||
var patterns []string
|
||||
if err := json.Unmarshal([]byte(v), &patterns); err != nil {
|
||||
|
|
|
@ -22,7 +22,7 @@ func New(client *containerd.Client) worker.Worker {
|
|||
}
|
||||
}
|
||||
|
||||
func (w containerdWorker) Exec(ctx context.Context, meta worker.Meta, root cache.Mountable, mounts []worker.Mount, stdout, stderr io.WriteCloser) error {
|
||||
func (w containerdWorker) Exec(ctx context.Context, meta worker.Meta, root cache.Mountable, mounts []worker.Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
|
||||
id := identity.NewID()
|
||||
|
||||
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts)
|
||||
|
@ -44,7 +44,11 @@ func (w containerdWorker) Exec(ctx context.Context, meta worker.Meta, root cache
|
|||
}
|
||||
defer container.Delete(ctx)
|
||||
|
||||
task, err := container.NewTask(ctx, containerd.Stdio, containerd.WithRootFS(rootMounts))
|
||||
if stdin == nil {
|
||||
stdin = &emptyReadCloser{}
|
||||
}
|
||||
|
||||
task, err := container.NewTask(ctx, containerd.NewIO(stdin, stdout, stderr), containerd.WithRootFS(rootMounts))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -69,3 +73,13 @@ func (w containerdWorker) Exec(ctx context.Context, meta worker.Meta, root cache
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
type emptyReadCloser struct{}
|
||||
|
||||
func (_ *emptyReadCloser) Read([]byte) (int, error) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
func (_ *emptyReadCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ func New(root string) (worker.Worker, error) {
|
|||
return w, nil
|
||||
}
|
||||
|
||||
func (w *runcworker) Exec(ctx context.Context, meta worker.Meta, root cache.Mountable, mounts []worker.Mount, stdout, stderr io.WriteCloser) error {
|
||||
func (w *runcworker) Exec(ctx context.Context, meta worker.Meta, root cache.Mountable, mounts []worker.Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
|
||||
|
||||
rootMount, err := root.Mount(ctx, false)
|
||||
if err != nil {
|
||||
|
@ -107,7 +107,7 @@ func (w *runcworker) Exec(ctx context.Context, meta worker.Meta, root cache.Moun
|
|||
logrus.Debugf("> running %s %v", id, meta.Args)
|
||||
|
||||
status, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{
|
||||
IO: &forwardIO{stdout: stdout, stderr: stderr},
|
||||
IO: &forwardIO{stdin: stdin, stdout: stdout, stderr: stderr},
|
||||
})
|
||||
logrus.Debugf("< completed %s %v %v", id, status, err)
|
||||
if status != 0 {
|
||||
|
@ -124,6 +124,7 @@ func (w *runcworker) Exec(ctx context.Context, meta worker.Meta, root cache.Moun
|
|||
}
|
||||
|
||||
type forwardIO struct {
|
||||
stdin io.ReadCloser
|
||||
stdout, stderr io.WriteCloser
|
||||
}
|
||||
|
||||
|
@ -132,6 +133,7 @@ func (s *forwardIO) Close() error {
|
|||
}
|
||||
|
||||
func (s *forwardIO) Set(cmd *exec.Cmd) {
|
||||
cmd.Stdin = s.stdin
|
||||
cmd.Stdout = s.stdout
|
||||
cmd.Stderr = s.stderr
|
||||
}
|
||||
|
|
|
@ -25,5 +25,5 @@ type Mount struct {
|
|||
|
||||
type Worker interface {
|
||||
// TODO: add stdout/err
|
||||
Exec(ctx context.Context, meta Meta, rootfs cache.Mountable, mounts []Mount, stdout, stderr io.WriteCloser) error
|
||||
Exec(ctx context.Context, meta Meta, rootfs cache.Mountable, mounts []Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue