Merge pull request #213 from tonistiigi/dockerfile-dedupe
frontend: make dockerfile.v0 and external frontend use same codedocker-18.09
commit
8fa227c801
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,29 +1,13 @@
|
|||
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/frontend/dockerfile/builder"
|
||||
"github.com/moby/buildkit/frontend/gateway/grpcclient"
|
||||
"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:"
|
||||
localNameContext = "context"
|
||||
gitPrefix = "git://"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
logrus.Errorf("fatal error: %+v", err)
|
||||
|
@ -32,94 +16,10 @@ func main() {
|
|||
}
|
||||
|
||||
func run() error {
|
||||
c, err := client.Current()
|
||||
c, err := grpcclient.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()),
|
||||
)
|
||||
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)
|
||||
return builder.Build(appcontext.Context(), c)
|
||||
}
|
||||
|
|
|
@ -1,33 +1,14 @@
|
|||
package dockerfile
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/moby/buildkit/cache"
|
||||
"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/moby/buildkit/frontend/dockerfile/builder"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"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 {
|
||||
return &dfFrontend{}
|
||||
}
|
||||
|
@ -35,127 +16,27 @@ 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][]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)
|
||||
|
||||
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(),
|
||||
})
|
||||
c, err := llbBridgeToGatewayClient(ctx, llbBridge, opts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if ref != nil {
|
||||
ref.Release(context.TODO())
|
||||
for _, r := range c.refs {
|
||||
if r != nil && (c.final != r || retErr != nil) {
|
||||
r.Release(context.TODO())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
mount, err := ref.Mount(ctx, false)
|
||||
if err != nil {
|
||||
if err := builder.Build(appcontext.Context(), c); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
lm := snapshot.LocalMounter(mount)
|
||||
|
||||
root, err := lm.Mount()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
if c.final == nil {
|
||||
return nil, nil, errors.Errorf("invalid empty return") // shouldn't happen
|
||||
}
|
||||
|
||||
defer func() {
|
||||
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)
|
||||
return c.final.ImmutableRef, c.exporterAttr, nil
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/builder"
|
||||
"github.com/moby/buildkit/identity"
|
||||
"github.com/moby/buildkit/util/testutil/httpserver"
|
||||
"github.com/moby/buildkit/util/testutil/integration"
|
||||
|
@ -454,8 +455,8 @@ EXPOSE 5000
|
|||
"name": target,
|
||||
},
|
||||
LocalDirs: map[string]string{
|
||||
localNameDockerfile: dir,
|
||||
localNameContext: dir,
|
||||
builder.LocalNameDockerfile: dir,
|
||||
builder.LocalNameContext: dir,
|
||||
},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
@ -611,8 +612,8 @@ USER nobody
|
|||
"output": destDir,
|
||||
},
|
||||
LocalDirs: map[string]string{
|
||||
localNameDockerfile: dir,
|
||||
localNameContext: dir,
|
||||
builder.LocalNameDockerfile: dir,
|
||||
builder.LocalNameContext: dir,
|
||||
},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
@ -634,8 +635,8 @@ USER nobody
|
|||
"name": target,
|
||||
},
|
||||
LocalDirs: map[string]string{
|
||||
localNameDockerfile: dir,
|
||||
localNameContext: dir,
|
||||
builder.LocalNameDockerfile: dir,
|
||||
builder.LocalNameContext: dir,
|
||||
},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -1,163 +1,19 @@
|
|||
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"
|
||||
"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
|
||||
// TODO: make this take same options as LLBBridge. Add Return()
|
||||
type Client interface {
|
||||
Solve(ctx context.Context, def *pb.Definition, frontend string, exporterAttr map[string][]byte, final bool) (Reference, error)
|
||||
ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error)
|
||||
Opts() map[string]string
|
||||
SessionID() string
|
||||
}
|
||||
|
||||
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")
|
||||
type Reference interface {
|
||||
ReadFile(ctx context.Context, fp string) ([]byte, error)
|
||||
}
|
||||
|
|
|
@ -4,10 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -19,7 +17,6 @@ import (
|
|||
pb "github.com/moby/buildkit/frontend/gateway/pb"
|
||||
"github.com/moby/buildkit/identity"
|
||||
"github.com/moby/buildkit/session"
|
||||
"github.com/moby/buildkit/snapshot"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -75,6 +72,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer ref.Release(context.TODO())
|
||||
rootFS = ref
|
||||
config, ok := exp[exporterImageConfig]
|
||||
if ok {
|
||||
|
@ -115,6 +113,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer ref.Release(context.TODO())
|
||||
rootFS = ref
|
||||
}
|
||||
|
||||
|
@ -144,6 +143,14 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
|
|||
|
||||
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{
|
||||
Env: env,
|
||||
Args: args,
|
||||
|
@ -271,6 +278,9 @@ func (lbf *llbBrideForwarder) Solve(ctx context.Context, req *pb.SolveRequest) (
|
|||
lbf.lastRef = ref
|
||||
lbf.exporterAttr = exp
|
||||
}
|
||||
if ref == nil {
|
||||
id = ""
|
||||
}
|
||||
return &pb.SolveResponse{Ref: id}, nil
|
||||
}
|
||||
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 {
|
||||
return nil, errors.Errorf("no such ref: %v", req.Ref)
|
||||
}
|
||||
|
||||
mount, err := ref.Mount(ctx, false)
|
||||
if ref == nil {
|
||||
return nil, errors.Wrapf(os.ErrNotExist, "%s no found", req.FilePath)
|
||||
}
|
||||
dt, err := cache.ReadFile(ctx, ref, req.FilePath)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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 buildkit:test go build ./frontend/gateway/client
|
||||
docker run --rm buildkit:test go build ./frontend/dockerfile/cmd/dockerfile-frontend
|
||||
|
|
Loading…
Reference in New Issue