Merge pull request #213 from tonistiigi/dockerfile-dedupe

frontend: make dockerfile.v0 and external frontend use same code
docker-18.09
Akihiro Suda 2017-12-14 11:07:21 +09:00 committed by GitHub
commit 8fa227c801
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 437 additions and 421 deletions

45
cache/fsutil.go vendored Normal file
View File

@ -0,0 +1,45 @@
package cache
import (
"io/ioutil"
"path/filepath"
"github.com/docker/docker/pkg/symlink"
"github.com/moby/buildkit/snapshot"
"golang.org/x/net/context"
)
func ReadFile(ctx context.Context, ref ImmutableRef, p string) ([]byte, error) {
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()
}
}()
fp, err := symlink.FollowSymlinkInScope(filepath.Join(root, p), root)
if err != nil {
return nil, err
}
dt, err := ioutil.ReadFile(fp)
if err != nil {
return nil, err
}
if err := lm.Unmount(); err != nil {
return nil, err
}
lm = nil
return dt, err
}

View File

@ -0,0 +1,110 @@
package builder
import (
"context"
"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/pkg/errors"
)
const (
LocalNameContext = "context"
LocalNameDockerfile = "dockerfile"
keyTarget = "target"
keyFilename = "filename"
exporterImageConfig = "containerimage.config"
defaultDockerfileName = "Dockerfile"
buildArgPrefix = "build-arg:"
gitPrefix = "git://"
)
func Build(ctx context.Context, c client.Client) error {
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()),
)
var buildContext *llb.State
if strings.HasPrefix(opts[LocalNameContext], gitPrefix) {
src = parseGitSource(opts[LocalNameContext])
buildContext = &src
}
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(),
BuildContext: buildContext,
})
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
}
func parseGitSource(ref string) llb.State {
ref = strings.TrimPrefix(ref, gitPrefix)
parts := strings.SplitN(ref, "#", 2)
branch := ""
if len(parts) > 1 {
branch = parts[1]
}
return llb.Git(parts[0], branch)
}

View File

@ -1,29 +1,13 @@
package main package main
import ( import (
"encoding/json" "github.com/moby/buildkit/frontend/dockerfile/builder"
"path" "github.com/moby/buildkit/frontend/gateway/grpcclient"
"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/moby/buildkit/util/appcontext"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
const (
keyTarget = "target"
keyFilename = "filename"
exporterImageConfig = "containerimage.config"
defaultDockerfileName = "Dockerfile"
localNameDockerfile = "dockerfile"
buildArgPrefix = "build-arg:"
localNameContext = "context"
gitPrefix = "git://"
)
func main() { func main() {
if err := run(); err != nil { if err := run(); err != nil {
logrus.Errorf("fatal error: %+v", err) logrus.Errorf("fatal error: %+v", err)
@ -32,94 +16,10 @@ func main() {
} }
func run() error { func run() error {
c, err := client.Current() c, err := grpcclient.Current()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to create client") return errors.Wrap(err, "failed to create client")
} }
ctx := appcontext.Context() return builder.Build(appcontext.Context(), c)
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()),
)
var buildContext *llb.State
if strings.HasPrefix(opts[localNameContext], gitPrefix) {
src = parseGitSource(opts[localNameContext])
buildContext = &src
}
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(),
BuildContext: buildContext,
})
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
}
func parseGitSource(ref string) llb.State {
ref = strings.TrimPrefix(ref, gitPrefix)
parts := strings.SplitN(ref, "#", 2)
branch := ""
if len(parts) > 1 {
branch = parts[1]
}
return llb.Git(parts[0], branch)
} }

View File

@ -1,33 +1,14 @@
package dockerfile package dockerfile
import ( import (
"encoding/json"
"io/ioutil"
"path"
"path/filepath"
"strings"
"github.com/moby/buildkit/cache" "github.com/moby/buildkit/cache"
"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/builder"
"github.com/moby/buildkit/session" "github.com/moby/buildkit/util/appcontext"
"github.com/moby/buildkit/snapshot"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
const (
keyTarget = "target"
keyFilename = "filename"
exporterImageConfig = "containerimage.config"
defaultDockerfileName = "Dockerfile"
localNameDockerfile = "dockerfile"
buildArgPrefix = "build-arg:"
localNameContext = "context"
gitPrefix = "git://"
)
func NewDockerfileFrontend() frontend.Frontend { func NewDockerfileFrontend() frontend.Frontend {
return &dfFrontend{} return &dfFrontend{}
} }
@ -35,127 +16,27 @@ 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][]byte, 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 == "" {
filename = defaultDockerfileName
}
if path.Base(filename) != filename {
return nil, nil, errors.Errorf("invalid filename %s", filename)
}
sid := session.FromContext(ctx) c, err := llbBridgeToGatewayClient(ctx, llbBridge, opts)
src := llb.Local(localNameDockerfile,
llb.IncludePatterns([]string{filename}),
llb.SessionID(sid),
)
var buildContext *llb.State
if strings.HasPrefix(opts[localNameContext], gitPrefix) {
src = parseGitSource(opts[localNameContext])
buildContext = &src
}
def, err := src.Marshal()
if err != nil {
return nil, nil, err
}
ref, _, err := llbBridge.Solve(ctx, frontend.SolveRequest{
Definition: def.ToPB(),
})
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer func() { defer func() {
if ref != nil { for _, r := range c.refs {
ref.Release(context.TODO()) if r != nil && (c.final != r || retErr != nil) {
r.Release(context.TODO())
}
} }
}() }()
mount, err := ref.Mount(ctx, false) if err := builder.Build(appcontext.Context(), c); err != nil {
if err != nil {
return nil, nil, err return nil, nil, err
} }
lm := snapshot.LocalMounter(mount) if c.final == nil {
return nil, nil, errors.Errorf("invalid empty return") // shouldn't happen
root, err := lm.Mount()
if err != nil {
return nil, nil, err
} }
defer func() { return c.final.ImmutableRef, c.exporterAttr, nil
if lm != nil {
lm.Unmount()
}
}()
dtDockerfile, err := ioutil.ReadFile(filepath.Join(root, filename))
if err != nil {
return nil, nil, err
}
if err := lm.Unmount(); err != nil {
return nil, nil, err
}
lm = nil
if err := ref.Release(context.TODO()); err != nil {
return nil, nil, err
}
ref = nil
st, img, err := dockerfile2llb.Dockerfile2LLB(ctx, dtDockerfile, dockerfile2llb.ConvertOpt{
Target: opts[keyTarget],
MetaResolver: llbBridge,
BuildArgs: filterBuildArgs(opts),
SessionID: sid,
BuildContext: buildContext,
})
if err != nil {
return nil, nil, err
}
def, err = st.Marshal()
if err != nil {
return nil, nil, err
}
retRef, _, err = llbBridge.Solve(ctx, frontend.SolveRequest{
Definition: def.ToPB(),
})
if err != nil {
return nil, nil, err
}
config, err := json.Marshal(img)
if err != nil {
return nil, nil, err
}
return retRef, map[string][]byte{
exporterImageConfig: config,
}, 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
}
func parseGitSource(ref string) llb.State {
ref = strings.TrimPrefix(ref, gitPrefix)
parts := strings.SplitN(ref, "#", 2)
branch := ""
if len(parts) > 1 {
branch = parts[1]
}
return llb.Git(parts[0], branch)
} }

View File

@ -22,6 +22,7 @@ import (
"github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/platforms" "github.com/containerd/containerd/platforms"
"github.com/moby/buildkit/client" "github.com/moby/buildkit/client"
"github.com/moby/buildkit/frontend/dockerfile/builder"
"github.com/moby/buildkit/identity" "github.com/moby/buildkit/identity"
"github.com/moby/buildkit/util/testutil/httpserver" "github.com/moby/buildkit/util/testutil/httpserver"
"github.com/moby/buildkit/util/testutil/integration" "github.com/moby/buildkit/util/testutil/integration"
@ -454,8 +455,8 @@ EXPOSE 5000
"name": target, "name": target,
}, },
LocalDirs: map[string]string{ LocalDirs: map[string]string{
localNameDockerfile: dir, builder.LocalNameDockerfile: dir,
localNameContext: dir, builder.LocalNameContext: dir,
}, },
}, nil) }, nil)
require.NoError(t, err) require.NoError(t, err)
@ -611,8 +612,8 @@ USER nobody
"output": destDir, "output": destDir,
}, },
LocalDirs: map[string]string{ LocalDirs: map[string]string{
localNameDockerfile: dir, builder.LocalNameDockerfile: dir,
localNameContext: dir, builder.LocalNameContext: dir,
}, },
}, nil) }, nil)
require.NoError(t, err) require.NoError(t, err)
@ -634,8 +635,8 @@ USER nobody
"name": target, "name": target,
}, },
LocalDirs: map[string]string{ LocalDirs: map[string]string{
localNameDockerfile: dir, builder.LocalNameDockerfile: dir,
localNameContext: dir, builder.LocalNameContext: dir,
}, },
}, nil) }, nil)
require.NoError(t, err) require.NoError(t, err)

View File

@ -0,0 +1,66 @@
package dockerfile
import (
"os"
"github.com/moby/buildkit/cache"
"github.com/moby/buildkit/frontend"
"github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/solver/pb"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
func llbBridgeToGatewayClient(ctx context.Context, llbBridge frontend.FrontendLLBBridge, opts map[string]string) (*bridgeClient, error) {
return &bridgeClient{opts: opts, FrontendLLBBridge: llbBridge, sid: session.FromContext(ctx)}, nil
}
type bridgeClient struct {
frontend.FrontendLLBBridge
opts map[string]string
final *ref
sid string
exporterAttr map[string][]byte
refs []*ref
}
func (c *bridgeClient) Solve(ctx context.Context, def *pb.Definition, f string, exporterAttr map[string][]byte, final bool) (client.Reference, error) {
r, exporterAttrRes, err := c.FrontendLLBBridge.Solve(ctx, frontend.SolveRequest{
Definition: def,
Frontend: f,
})
if err != nil {
return nil, err
}
rr := &ref{r}
c.refs = append(c.refs, rr)
if final {
c.final = rr
if exporterAttr == nil {
exporterAttr = make(map[string][]byte)
}
for k, v := range exporterAttrRes {
exporterAttr[k] = v
}
c.exporterAttr = exporterAttr
}
return rr, nil
}
func (c *bridgeClient) Opts() map[string]string {
return c.opts
}
func (c *bridgeClient) SessionID() string {
return c.sid
}
type ref struct {
cache.ImmutableRef
}
func (r *ref) ReadFile(ctx context.Context, fp string) ([]byte, error) {
if r.ImmutableRef == nil {
return nil, errors.Wrapf(os.ErrNotExist, "%s no found", fp)
}
return cache.ReadFile(ctx, r.ImmutableRef, fp)
}

View File

@ -1,163 +1,19 @@
package client package client
import ( import (
"encoding/json" "github.com/moby/buildkit/solver/pb"
"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" digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
const frontendPrefix = "BUILDKIT_FRONTEND_OPT_" // TODO: make this take same options as LLBBridge. Add Return()
type Client interface {
func Current() (*Client, error) { Solve(ctx context.Context, def *pb.Definition, frontend string, exporterAttr map[string][]byte, final bool) (Reference, error)
ctx, conn, err := grpcClientConn(context.Background()) ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error)
if err != nil { Opts() map[string]string
return nil, err SessionID() string
}
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 { type Reference interface {
client pb.LLBBridgeClient ReadFile(ctx context.Context, fp string) ([]byte, error)
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")
} }

View File

@ -4,10 +4,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net" "net"
"os" "os"
"path/filepath"
"strings" "strings"
"time" "time"
@ -19,7 +17,6 @@ import (
pb "github.com/moby/buildkit/frontend/gateway/pb" pb "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/identity" "github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session" "github.com/moby/buildkit/session"
"github.com/moby/buildkit/snapshot"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -75,6 +72,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer ref.Release(context.TODO())
rootFS = ref rootFS = ref
config, ok := exp[exporterImageConfig] config, ok := exp[exporterImageConfig]
if ok { if ok {
@ -115,6 +113,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
defer ref.Release(context.TODO())
rootFS = ref rootFS = ref
} }
@ -144,6 +143,14 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
env = append(env, "BUILDKIT_SESSION_ID="+sid) env = append(env, "BUILDKIT_SESSION_ID="+sid)
defer func() {
for _, r := range lbf.refs {
if r != nil && (lbf.lastRef != r || retErr != nil) {
r.Release(context.TODO())
}
}
}()
err = llbBridge.Exec(ctx, executor.Meta{ err = llbBridge.Exec(ctx, executor.Meta{
Env: env, Env: env,
Args: args, Args: args,
@ -271,6 +278,9 @@ func (lbf *llbBrideForwarder) Solve(ctx context.Context, req *pb.SolveRequest) (
lbf.lastRef = ref lbf.lastRef = ref
lbf.exporterAttr = exp lbf.exporterAttr = exp
} }
if ref == nil {
id = ""
}
return &pb.SolveResponse{Ref: id}, nil return &pb.SolveResponse{Ref: id}, nil
} }
func (lbf *llbBrideForwarder) ReadFile(ctx context.Context, req *pb.ReadFileRequest) (*pb.ReadFileResponse, error) { func (lbf *llbBrideForwarder) ReadFile(ctx context.Context, req *pb.ReadFileRequest) (*pb.ReadFileResponse, error) {
@ -278,35 +288,14 @@ func (lbf *llbBrideForwarder) ReadFile(ctx context.Context, req *pb.ReadFileRequ
if !ok { if !ok {
return nil, errors.Errorf("no such ref: %v", req.Ref) return nil, errors.Errorf("no such ref: %v", req.Ref)
} }
if ref == nil {
mount, err := ref.Mount(ctx, false) return nil, errors.Wrapf(os.ErrNotExist, "%s no found", req.FilePath)
}
dt, err := cache.ReadFile(ctx, ref, req.FilePath)
if err != nil { if err != nil {
return nil, err 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 return &pb.ReadFileResponse{Data: dt}, nil
} }

View File

@ -0,0 +1,167 @@
package grpcclient
import (
"encoding/json"
"io"
"net"
"os"
"strings"
"time"
"github.com/moby/buildkit/frontend/gateway/client"
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.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 &grpcClient{client: c, opts: opts(), sessionID: sessionID()}, nil
}
type grpcClient struct {
client pb.LLBBridgeClient
opts map[string]string
sessionID string
}
func (c *grpcClient) Solve(ctx context.Context, def *opspb.Definition, frontend string, exporterAttr map[string][]byte, final bool) (client.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
}
if resp.Ref == "" {
return nil, nil
}
return &reference{id: resp.Ref, c: c}, nil
}
func (c *grpcClient) 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 *grpcClient) Opts() map[string]string {
return c.opts
}
func (c *grpcClient) SessionID() string {
return c.sessionID
}
type reference struct {
id string
c *grpcClient
}
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")
}

View File

@ -8,3 +8,4 @@ docker build -t buildkit:test --target integration-tests -f ./hack/dockerfiles/t
docker run --rm -v /tmp --privileged buildkit:test go test -tags standalone ${TESTFLAGS:--v} ${TESTPKGS:-./...} docker run --rm -v /tmp --privileged buildkit:test go test -tags standalone ${TESTFLAGS:--v} ${TESTPKGS:-./...}
docker run --rm buildkit:test go build ./frontend/gateway/client docker run --rm buildkit:test go build ./frontend/gateway/client
docker run --rm buildkit:test go build ./frontend/dockerfile/cmd/dockerfile-frontend