diff --git a/cache/fsutil.go b/cache/fsutil.go new file mode 100644 index 00000000..fdc65b75 --- /dev/null +++ b/cache/fsutil.go @@ -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 +} diff --git a/frontend/dockerfile/builder/build.go b/frontend/dockerfile/builder/build.go new file mode 100644 index 00000000..ebb86840 --- /dev/null +++ b/frontend/dockerfile/builder/build.go @@ -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) +} diff --git a/frontend/dockerfile/cmd/dockerfile-frontend/main.go b/frontend/dockerfile/cmd/dockerfile-frontend/main.go index c1a8b016..15be488f 100644 --- a/frontend/dockerfile/cmd/dockerfile-frontend/main.go +++ b/frontend/dockerfile/cmd/dockerfile-frontend/main.go @@ -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) } diff --git a/frontend/dockerfile/dockerfile.go b/frontend/dockerfile/dockerfile.go index 30f0b2e6..1c2df0c7 100644 --- a/frontend/dockerfile/dockerfile.go +++ b/frontend/dockerfile/dockerfile.go @@ -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 } diff --git a/frontend/dockerfile/dockerfile_test.go b/frontend/dockerfile/dockerfile_test.go index 90a7f4df..3407420f 100644 --- a/frontend/dockerfile/dockerfile_test.go +++ b/frontend/dockerfile/dockerfile_test.go @@ -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) diff --git a/frontend/dockerfile/forward.go b/frontend/dockerfile/forward.go new file mode 100644 index 00000000..fa67082b --- /dev/null +++ b/frontend/dockerfile/forward.go @@ -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) +} diff --git a/frontend/gateway/client/client.go b/frontend/gateway/client/client.go index 737fd9b1..af2d26e8 100644 --- a/frontend/gateway/client/client.go +++ b/frontend/gateway/client/client.go @@ -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) } diff --git a/frontend/gateway/gateway.go b/frontend/gateway/gateway.go index dc38af8d..8e8ede21 100644 --- a/frontend/gateway/gateway.go +++ b/frontend/gateway/gateway.go @@ -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 } diff --git a/frontend/gateway/grpcclient/client.go b/frontend/gateway/grpcclient/client.go new file mode 100644 index 00000000..a4fb7af1 --- /dev/null +++ b/frontend/gateway/grpcclient/client.go @@ -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") +} diff --git a/hack/test b/hack/test index a3466745..050dbb78 100755 --- a/hack/test +++ b/hack/test @@ -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