Add undocumented /grpc endpoint and register BuildKit's controller
| ... | ... |
@@ -14,6 +14,7 @@ import ( |
| 14 | 14 |
"github.com/docker/docker/pkg/stringid" |
| 15 | 15 |
"github.com/pkg/errors" |
| 16 | 16 |
"golang.org/x/sync/errgroup" |
| 17 |
+ "google.golang.org/grpc" |
|
| 17 | 18 |
) |
| 18 | 19 |
|
| 19 | 20 |
// ImageComponent provides an interface for working with images |
| ... | ... |
@@ -40,6 +41,13 @@ func NewBackend(components ImageComponent, builder Builder, fsCache *fscache.FSC |
| 40 | 40 |
return &Backend{imageComponent: components, builder: builder, fsCache: fsCache, buildkit: buildkit}, nil
|
| 41 | 41 |
} |
| 42 | 42 |
|
| 43 |
+// RegisterGRPC registers buildkit controller to the grpc server. |
|
| 44 |
+func (b *Backend) RegisterGRPC(s *grpc.Server) {
|
|
| 45 |
+ if b.buildkit != nil {
|
|
| 46 |
+ b.buildkit.RegisterGRPC(s) |
|
| 47 |
+ } |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 43 | 50 |
// Build builds an image from a Source |
| 44 | 51 |
func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string, error) {
|
| 45 | 52 |
options := config.Options |
| 0 | 8 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,37 @@ |
| 0 |
+package grpc // import "github.com/docker/docker/api/server/router/grpc" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/api/server/router" |
|
| 4 |
+ "golang.org/x/net/http2" |
|
| 5 |
+ "google.golang.org/grpc" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+type grpcRouter struct {
|
|
| 9 |
+ routes []router.Route |
|
| 10 |
+ grpcServer *grpc.Server |
|
| 11 |
+ h2Server *http2.Server |
|
| 12 |
+} |
|
| 13 |
+ |
|
| 14 |
+// NewRouter initializes a new grpc http router |
|
| 15 |
+func NewRouter(backends ...Backend) router.Router {
|
|
| 16 |
+ r := &grpcRouter{
|
|
| 17 |
+ h2Server: &http2.Server{},
|
|
| 18 |
+ grpcServer: grpc.NewServer(), |
|
| 19 |
+ } |
|
| 20 |
+ for _, b := range backends {
|
|
| 21 |
+ b.RegisterGRPC(r.grpcServer) |
|
| 22 |
+ } |
|
| 23 |
+ r.initRoutes() |
|
| 24 |
+ return r |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+// Routes returns the available routers to the session controller |
|
| 28 |
+func (r *grpcRouter) Routes() []router.Route {
|
|
| 29 |
+ return r.routes |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func (r *grpcRouter) initRoutes() {
|
|
| 33 |
+ r.routes = []router.Route{
|
|
| 34 |
+ router.NewPostRoute("/grpc", r.serveGRPC),
|
|
| 35 |
+ } |
|
| 36 |
+} |
| 0 | 37 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,45 @@ |
| 0 |
+package grpc // import "github.com/docker/docker/api/server/router/grpc" |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "context" |
|
| 4 |
+ "net/http" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/pkg/errors" |
|
| 7 |
+ "golang.org/x/net/http2" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func (gr *grpcRouter) serveGRPC(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
| 11 |
+ h, ok := w.(http.Hijacker) |
|
| 12 |
+ if !ok {
|
|
| 13 |
+ return errors.New("handler does not support hijack")
|
|
| 14 |
+ } |
|
| 15 |
+ proto := r.Header.Get("Upgrade")
|
|
| 16 |
+ if proto == "" {
|
|
| 17 |
+ return errors.New("no upgrade proto in request")
|
|
| 18 |
+ } |
|
| 19 |
+ if proto != "h2c" {
|
|
| 20 |
+ return errors.Errorf("protocol %s not supported", proto)
|
|
| 21 |
+ } |
|
| 22 |
+ |
|
| 23 |
+ conn, _, err := h.Hijack() |
|
| 24 |
+ if err != nil {
|
|
| 25 |
+ return err |
|
| 26 |
+ } |
|
| 27 |
+ resp := &http.Response{
|
|
| 28 |
+ StatusCode: http.StatusSwitchingProtocols, |
|
| 29 |
+ ProtoMajor: 1, |
|
| 30 |
+ ProtoMinor: 1, |
|
| 31 |
+ Header: http.Header{},
|
|
| 32 |
+ } |
|
| 33 |
+ resp.Header.Set("Connection", "Upgrade")
|
|
| 34 |
+ resp.Header.Set("Upgrade", proto)
|
|
| 35 |
+ |
|
| 36 |
+ // set raw mode |
|
| 37 |
+ conn.Write([]byte{})
|
|
| 38 |
+ resp.Write(conn) |
|
| 39 |
+ |
|
| 40 |
+ // https://godoc.org/golang.org/x/net/http2#Server.ServeConn |
|
| 41 |
+ // TODO: is it a problem that conn has already been written to? |
|
| 42 |
+ gr.h2Server.ServeConn(conn, &http2.ServeConnOpts{Handler: gr.grpcServer})
|
|
| 43 |
+ return nil |
|
| 44 |
+} |
| ... | ... |
@@ -31,6 +31,7 @@ import ( |
| 31 | 31 |
"github.com/moby/buildkit/util/tracing" |
| 32 | 32 |
"github.com/pkg/errors" |
| 33 | 33 |
"golang.org/x/sync/errgroup" |
| 34 |
+ "google.golang.org/grpc" |
|
| 34 | 35 |
grpcmetadata "google.golang.org/grpc/metadata" |
| 35 | 36 |
) |
| 36 | 37 |
|
| ... | ... |
@@ -104,6 +105,11 @@ func New(opt Opt) (*Builder, error) {
|
| 104 | 104 |
return b, nil |
| 105 | 105 |
} |
| 106 | 106 |
|
| 107 |
+// RegisterGRPC registers controller to the grpc server. |
|
| 108 |
+func (b *Builder) RegisterGRPC(s *grpc.Server) {
|
|
| 109 |
+ b.controller.Register(s) |
|
| 110 |
+} |
|
| 111 |
+ |
|
| 107 | 112 |
// Cancel cancels a build using ID |
| 108 | 113 |
func (b *Builder) Cancel(ctx context.Context, id string) error {
|
| 109 | 114 |
b.mu.Lock() |
| ... | ... |
@@ -38,6 +38,17 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu |
| 38 | 38 |
return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err
|
| 39 | 39 |
} |
| 40 | 40 |
|
| 41 |
+// DialHijack returns a hijacked connection with negotiated protocol proto. |
|
| 42 |
+func (cli *Client) DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) {
|
|
| 43 |
+ req, err := http.NewRequest("POST", url, nil)
|
|
| 44 |
+ if err != nil {
|
|
| 45 |
+ return nil, err |
|
| 46 |
+ } |
|
| 47 |
+ req = cli.addHeaders(req, meta) |
|
| 48 |
+ |
|
| 49 |
+ return cli.setupHijackConn(ctx, req, proto) |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 41 | 52 |
// fallbackDial is used when WithDialer() was not called. |
| 42 | 53 |
// See cli.Dialer(). |
| 43 | 54 |
func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
| ... | ... |
@@ -38,7 +38,7 @@ type CommonAPIClient interface {
|
| 38 | 38 |
ServerVersion(ctx context.Context) (types.Version, error) |
| 39 | 39 |
NegotiateAPIVersion(ctx context.Context) |
| 40 | 40 |
NegotiateAPIVersionPing(types.Ping) |
| 41 |
- DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) |
|
| 41 |
+ DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) |
|
| 42 | 42 |
Dialer() func(context.Context) (net.Conn, error) |
| 43 | 43 |
Close() error |
| 44 | 44 |
} |
| 45 | 45 |
deleted file mode 100644 |
| ... | ... |
@@ -1,18 +0,0 @@ |
| 1 |
-package client // import "github.com/docker/docker/client" |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "context" |
|
| 5 |
- "net" |
|
| 6 |
- "net/http" |
|
| 7 |
-) |
|
| 8 |
- |
|
| 9 |
-// DialSession returns a connection that can be used communication with daemon |
|
| 10 |
-func (cli *Client) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
|
| 11 |
- req, err := http.NewRequest("POST", "/session", nil)
|
|
| 12 |
- if err != nil {
|
|
| 13 |
- return nil, err |
|
| 14 |
- } |
|
| 15 |
- req = cli.addHeaders(req, meta) |
|
| 16 |
- |
|
| 17 |
- return cli.setupHijackConn(ctx, req, proto) |
|
| 18 |
-} |
| ... | ... |
@@ -21,6 +21,7 @@ import ( |
| 21 | 21 |
checkpointrouter "github.com/docker/docker/api/server/router/checkpoint" |
| 22 | 22 |
"github.com/docker/docker/api/server/router/container" |
| 23 | 23 |
distributionrouter "github.com/docker/docker/api/server/router/distribution" |
| 24 |
+ grpcrouter "github.com/docker/docker/api/server/router/grpc" |
|
| 24 | 25 |
"github.com/docker/docker/api/server/router/image" |
| 25 | 26 |
"github.com/docker/docker/api/server/router/network" |
| 26 | 27 |
pluginrouter "github.com/docker/docker/api/server/router/plugin" |
| ... | ... |
@@ -481,6 +482,16 @@ func initRouter(opts routerOptions) {
|
| 481 | 481 |
distributionrouter.NewRouter(opts.daemon.ImageService()), |
| 482 | 482 |
} |
| 483 | 483 |
|
| 484 |
+ grpcBackends := []grpcrouter.Backend{}
|
|
| 485 |
+ for _, b := range []interface{}{opts.daemon, opts.buildBackend} {
|
|
| 486 |
+ if b, ok := b.(grpcrouter.Backend); ok {
|
|
| 487 |
+ grpcBackends = append(grpcBackends, b) |
|
| 488 |
+ } |
|
| 489 |
+ } |
|
| 490 |
+ if len(grpcBackends) > 0 {
|
|
| 491 |
+ routers = append(routers, grpcrouter.NewRouter(grpcBackends...)) |
|
| 492 |
+ } |
|
| 493 |
+ |
|
| 484 | 494 |
if opts.daemon.NetworkControllerEnabled() {
|
| 485 | 495 |
routers = append(routers, network.NewRouter(opts.daemon, opts.cluster)) |
| 486 | 496 |
} |
| ... | ... |
@@ -3,6 +3,7 @@ package build |
| 3 | 3 |
import ( |
| 4 | 4 |
"context" |
| 5 | 5 |
"io/ioutil" |
| 6 |
+ "net" |
|
| 6 | 7 |
"net/http" |
| 7 | 8 |
"strings" |
| 8 | 9 |
"testing" |
| ... | ... |
@@ -109,7 +110,9 @@ func testBuildWithSession(t *testing.T, client dclient.APIClient, daemonHost str |
| 109 | 109 |
g, ctx := errgroup.WithContext(ctx) |
| 110 | 110 |
|
| 111 | 111 |
g.Go(func() error {
|
| 112 |
- return sess.Run(ctx, client.DialSession) |
|
| 112 |
+ return sess.Run(ctx, func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
|
| 113 |
+ return client.DialHijack(ctx, "/session", "h2c", meta) |
|
| 114 |
+ }) |
|
| 113 | 115 |
}) |
| 114 | 116 |
|
| 115 | 117 |
g.Go(func() error {
|