Merge pull request #134 from tonistiigi/gateway-frontend

load frontends from images
docker-18.09
Akihiro Suda 2017-10-04 14:38:51 +09:00 committed by GitHub
commit 7d0fea5b60
22 changed files with 2412 additions and 50 deletions

View File

@ -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 nil, err
} }
return &controlapi.SolveResponse{}, nil return &controlapi.SolveResponse{}, nil

View File

@ -18,6 +18,7 @@ import (
localexporter "github.com/moby/buildkit/exporter/local" localexporter "github.com/moby/buildkit/exporter/local"
"github.com/moby/buildkit/frontend" "github.com/moby/buildkit/frontend"
"github.com/moby/buildkit/frontend/dockerfile" "github.com/moby/buildkit/frontend/dockerfile"
"github.com/moby/buildkit/frontend/gateway"
"github.com/moby/buildkit/session" "github.com/moby/buildkit/session"
"github.com/moby/buildkit/snapshot/blobmapping" "github.com/moby/buildkit/snapshot/blobmapping"
"github.com/moby/buildkit/source" "github.com/moby/buildkit/source"
@ -128,6 +129,7 @@ func defaultControllerOpts(root string, pd pullDeps) (*Opt, error) {
frontends := map[string]frontend.Frontend{} frontends := map[string]frontend.Frontend{}
frontends["dockerfile.v0"] = dockerfile.NewDockerfileFrontend() frontends["dockerfile.v0"] = dockerfile.NewDockerfileFrontend()
frontends["gateway.v0"] = gateway.NewGatewayFrontend()
return &Opt{ return &Opt{
Snapshotter: snapshotter, Snapshotter: snapshotter,

View File

@ -115,7 +115,7 @@ func TestControl(t *testing.T) {
stderr := bytes.NewBuffer(nil) 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 assert.Error(t, err) // Read-only root
// typical error is like `mkdir /.../rootfs/proc: read-only file system`. // typical error is like `mkdir /.../rootfs/proc: read-only file system`.
// make sure the error is caused before running `echo foo > /bar`. // 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) root, err := cm.New(ctx, snap)
assert.NoError(t, err) 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) assert.NoError(t, err)
rf, err := root.Commit(ctx) rf, err := root.Commit(ctx)

View File

@ -15,7 +15,6 @@ import (
"github.com/moby/buildkit/cache" "github.com/moby/buildkit/cache"
"github.com/moby/buildkit/cache/metadata" "github.com/moby/buildkit/cache/metadata"
"github.com/moby/buildkit/exporter" "github.com/moby/buildkit/exporter"
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
"github.com/moby/buildkit/snapshot" "github.com/moby/buildkit/snapshot"
"github.com/moby/buildkit/util/flightcontrol" "github.com/moby/buildkit/util/flightcontrol"
"github.com/moby/buildkit/util/progress" "github.com/moby/buildkit/util/progress"
@ -155,7 +154,7 @@ func (e *imageExporterInstance) Name() string {
return "exporting to image" 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") layersDone := oneOffProgress(ctx, "exporting layers")
diffPairs, err := e.getBlobs(ctx, ref) diffPairs, err := e.getBlobs(ctx, ref)
if err != nil { if err != nil {
@ -169,15 +168,10 @@ func (e *imageExporterInstance) Export(ctx context.Context, ref cache.ImmutableR
} }
var dt []byte var dt []byte
if imgInterface, ok := opt[exporterImageConfig]; ok { if config, ok := opt[exporterImageConfig]; ok {
img, ok := imgInterface.(*dockerfile2llb.Image) dt, err = setDiffIDs(config, diffIDs)
if !ok {
return errors.Errorf("invalid image config")
}
setDiffIDs(img, diffIDs)
dt, err = json.Marshal(img)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal image config") return err
} }
} else { } else {
dt, err = json.Marshal(imageConfig(diffIDs)) dt, err = json.Marshal(imageConfig(diffIDs))
@ -270,9 +264,20 @@ func imageConfig(diffIDs []digest.Digest) ocispec.Image {
return img return img
} }
func setDiffIDs(img *dockerfile2llb.Image, diffIDs []digest.Digest) { func setDiffIDs(config []byte, diffIDs []digest.Digest) ([]byte, error) {
img.RootFS.Type = "layers" mp := map[string]json.RawMessage{}
img.RootFS.DiffIDs = diffIDs 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 { func oneOffProgress(ctx context.Context, id string) func(err error) error {

View File

@ -11,5 +11,5 @@ type Exporter interface {
type ExporterInstance interface { type ExporterInstance interface {
Name() string Name() string
Export(context.Context, cache.ImmutableRef, map[string]interface{}) error Export(context.Context, cache.ImmutableRef, map[string][]byte) error
} }

View File

@ -55,7 +55,7 @@ func (e *localExporterInstance) Name() string {
return "exporting to client" 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) mount, err := ref.Mount(ctx, true)
if err != nil { if err != nil {
return err return err

View File

@ -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"]

View File

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

View File

@ -1,6 +1,7 @@
package dockerfile package dockerfile
import ( import (
"encoding/json"
"io/ioutil" "io/ioutil"
"path" "path"
"path/filepath" "path/filepath"
@ -10,6 +11,7 @@ import (
"github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/frontend" "github.com/moby/buildkit/frontend"
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb" "github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/snapshot" "github.com/moby/buildkit/snapshot"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -30,7 +32,7 @@ func NewDockerfileFrontend() frontend.Frontend {
type dfFrontend struct{} 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] filename := opts[keyFilename]
if filename == "" { 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) 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() def, err := src.Marshal()
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defPB := def.ToPB()
ref, err := llbBridge.Solve(ctx, defPB) ref, _, err := llbBridge.Solve(ctx, def.ToPB(), "", nil)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -95,6 +101,7 @@ func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBr
Target: opts[keyTarget], Target: opts[keyTarget],
MetaResolver: llbBridge, MetaResolver: llbBridge,
BuildArgs: filterBuildArgs(opts), BuildArgs: filterBuildArgs(opts),
SessionID: sid,
}) })
if err != nil { if err != nil {
@ -105,14 +112,18 @@ func (f *dfFrontend) Solve(ctx context.Context, llbBridge frontend.FrontendLLBBr
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defPB = def.ToPB() retRef, _, err = llbBridge.Solve(ctx, def.ToPB(), "", nil)
retRef, err = llbBridge.Solve(ctx, defPB)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return retRef, map[string]interface{}{ config, err := json.Marshal(img)
exporterImageConfig: img, if err != nil {
return nil, nil, err
}
return retRef, map[string][]byte{
exporterImageConfig: config,
}, nil }, nil
} }

View File

@ -28,6 +28,7 @@ type ConvertOpt struct {
Target string Target string
MetaResolver llb.ImageMetaResolver MetaResolver llb.ImageMetaResolver
BuildArgs map[string]string BuildArgs map[string]string
SessionID string
} }
func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error) { 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, metaArgs: metaArgs,
buildArgValues: opt.BuildArgs, buildArgValues: opt.BuildArgs,
shlex: shlex, shlex: shlex,
sessionID: opt.SessionID,
} }
if err = dispatchOnBuild(d, d.image.Config.OnBuild, opt); err != nil { if err = dispatchOnBuild(d, d.image.Config.OnBuild, opt); err != nil {
@ -187,6 +189,7 @@ type dispatchOpt struct {
metaArgs []instructions.ArgCommand metaArgs []instructions.ArgCommand
buildArgValues map[string]string buildArgValues map[string]string
shlex *ShellLex shlex *ShellLex
sessionID string
} }
func dispatch(d *dispatchState, cmd instructions.Command, opt dispatchOpt) error { 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: case *instructions.WorkdirCommand:
err = dispatchWorkdir(d, c) err = dispatchWorkdir(d, c)
case *instructions.AddCommand: 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: case *instructions.LabelCommand:
err = dispatchLabel(d, c) err = dispatchLabel(d, c)
case *instructions.OnbuildCommand: case *instructions.OnbuildCommand:
@ -232,7 +235,7 @@ func dispatch(d *dispatchState, cmd instructions.Command, opt dispatchOpt) error
case *instructions.ArgCommand: case *instructions.ArgCommand:
err = dispatchArg(d, c, opt.metaArgs, opt.buildArgValues) err = dispatchArg(d, c, opt.metaArgs, opt.buildArgValues)
case *instructions.CopyCommand: case *instructions.CopyCommand:
l := llb.Local(localNameContext) l := llb.Local(localNameContext, llb.SessionID(opt.sessionID))
if c.From != "" { if c.From != "" {
index, err := strconv.Atoi(c.From) index, err := strconv.Atoi(c.From)
if err != nil { if err != nil {

View File

@ -1,18 +1,21 @@
package frontend package frontend
import ( import (
digest "github.com/opencontainers/go-digest" "io"
"golang.org/x/net/context"
"github.com/moby/buildkit/cache" "github.com/moby/buildkit/cache"
"github.com/moby/buildkit/solver/pb" "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 { 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 { 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) 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
} }

View File

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

315
frontend/gateway/gateway.go Normal file
View File

@ -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

View File

@ -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{
}

View File

@ -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

View File

@ -110,7 +110,7 @@ func (e *execOp) Run(ctx context.Context, inputs []Reference) ([]Reference, erro
defer stdout.Close() defer stdout.Close()
defer stderr.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) return nil, errors.Wrapf(err, "worker failed running %v", meta.Args)
} }

View File

@ -3,6 +3,7 @@ package solver
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"sync" "sync"
"time" "time"
@ -44,7 +45,7 @@ func NewLLBSolver(opt LLBOpt) *Solver {
default: default:
return nil, nil return nil, nil
} }
}, opt.InstructionCache, opt.ImageSource) }, opt.InstructionCache, opt.ImageSource, opt.Worker, opt.CacheManager)
return s return s
} }
@ -76,13 +77,15 @@ type Solver struct {
jobs *jobList jobs *jobList
cache InstructionCache cache InstructionCache
imageSource source.Source imageSource source.Source
worker worker.Worker
cm cache.Manager // TODO: remove with immutableRef.New()
} }
func New(resolve ResolveOpFunc, cache InstructionCache, imageSource source.Source) *Solver { 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} 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) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
@ -96,7 +99,7 @@ func (s *Solver) Solve(ctx context.Context, id string, f frontend.Frontend, def
} }
var ref Reference var ref Reference
var exporterOpt map[string]interface{} var exporterOpt map[string][]byte
if def != nil { if def != nil {
var inp *Input var inp *Input
inp, err = j.load(def, s.resolve) 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) ref, err = j.getRef(ctx, inp.Vertex.(*vertex), inp.Index)
} else { } else {
ref, exporterOpt, err = f.Solve(ctx, &llbBridge{ ref, exporterOpt, err = f.Solve(ctx, &llbBridge{
worker: s.worker,
job: j, job: j,
cm: s.cm,
resolveOp: s.resolve, resolveOp: s.resolve,
resolveImageConfig: s.imageSource.(resolveImageConfig), resolveImageConfig: s.imageSource.(resolveImageConfig),
allFrontends: allFrontends,
}, frontendOpt) }, frontendOpt)
} }
j.discard() j.discard()
@ -568,27 +574,54 @@ type llbBridge struct {
resolveImageConfig resolveImageConfig
job *job job *job
resolveOp ResolveOpFunc resolveOp ResolveOpFunc
worker worker.Worker
allFrontends map[string]frontend.Frontend
cm cache.Manager
} }
type resolveImageConfig interface { type resolveImageConfig interface {
ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) 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) {
inp, err := s.job.load(def, s.resolveOp) if def == nil {
if err != nil { f, ok := s.allFrontends[frontend]
return nil, err if !ok {
return nil, nil, errors.Errorf("invalid frontend: %s", frontend)
} }
ref, err := s.job.getRef(ctx, inp.Vertex.(*vertex), inp.Index) ref, exporterOpt, err := f.Solve(ctx, s, opts)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
immutable, ok := toImmutableRef(ref) immutable, ok := toImmutableRef(ref)
if !ok { 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, exporterOpt, nil
}
inp, err := s.job.load(def, s.resolveOp)
if err != nil {
return nil, nil, err
}
ref, err := s.job.getRef(ctx, inp.Vertex.(*vertex), inp.Index)
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, 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 { func cacheKeyForIndex(dgst digest.Digest, index Index) digest.Digest {

View File

@ -2,6 +2,7 @@ package solver
import ( import (
"encoding/json" "encoding/json"
"strings"
"sync" "sync"
"github.com/moby/buildkit/solver/pb" "github.com/moby/buildkit/solver/pb"
@ -51,6 +52,10 @@ func (s *sourceOp) instance(ctx context.Context) (source.SourceInstance, error)
switch k { switch k {
case pb.AttrLocalSessionID: case pb.AttrLocalSessionID:
id.SessionID = v 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: case pb.AttrIncludePatterns:
var patterns []string var patterns []string
if err := json.Unmarshal([]byte(v), &patterns); err != nil { if err := json.Unmarshal([]byte(v), &patterns); err != nil {

View File

@ -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() id := identity.NewID()
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts) 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) 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 { if err != nil {
return err return err
} }
@ -69,3 +73,13 @@ func (w containerdWorker) Exec(ctx context.Context, meta worker.Meta, root cache
return nil return nil
} }
type emptyReadCloser struct{}
func (_ *emptyReadCloser) Read([]byte) (int, error) {
return 0, io.EOF
}
func (_ *emptyReadCloser) Close() error {
return nil
}

View File

@ -54,7 +54,7 @@ func New(root string) (worker.Worker, error) {
return w, nil 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) rootMount, err := root.Mount(ctx, false)
if err != nil { 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) logrus.Debugf("> running %s %v", id, meta.Args)
status, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{ 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) logrus.Debugf("< completed %s %v %v", id, status, err)
if status != 0 { if status != 0 {
@ -124,6 +124,7 @@ func (w *runcworker) Exec(ctx context.Context, meta worker.Meta, root cache.Moun
} }
type forwardIO struct { type forwardIO struct {
stdin io.ReadCloser
stdout, stderr io.WriteCloser stdout, stderr io.WriteCloser
} }
@ -132,6 +133,7 @@ func (s *forwardIO) Close() error {
} }
func (s *forwardIO) Set(cmd *exec.Cmd) { func (s *forwardIO) Set(cmd *exec.Cmd) {
cmd.Stdin = s.stdin
cmd.Stdout = s.stdout cmd.Stdout = s.stdout
cmd.Stderr = s.stderr cmd.Stderr = s.stderr
} }

View File

@ -25,5 +25,5 @@ type Mount struct {
type Worker interface { type Worker interface {
// TODO: add stdout/err // 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
} }