From bae830a1b1e05f31df2344a530ee921e499bb722 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 6 Apr 2020 11:22:34 -0400 Subject: [PATCH] Add initial backend proto and example Signed-off-by: Michael Crosby --- .gitignore | 1 + Makefile | 7 +- README.md | 9 ++ backend/v1/backend.pb.go | 172 +++++++++++++++++++++++++++++++++++++++ client/client.go | 49 +++++++++++ example/backend/main.go | 112 +++++++++++++++++++++++++ server/server.go | 56 +++++++++++++ 7 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 backend/v1/backend.pb.go create mode 100644 client/client.go create mode 100644 example/backend/main.go create mode 100644 server/server.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e660fd93d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/ diff --git a/Makefile b/Makefile index 3b5018c0b..5f0f64546 100644 --- a/Makefile +++ b/Makefile @@ -27,11 +27,16 @@ PACKAGES=$(shell go list ./... | grep -v /vendor/) GIT_COMMIT=$(shell git rev-parse --short HEAD) GOOS ?= $(shell go env GOOS) +export GO111MODULE=off + all: protos protos: @protobuild --quiet ${PACKAGES} +example: + cd example/backend && go build -v -o ../../bin/backend-example + FORCE: -.PHONY: protos +.PHONY: protos example diff --git a/README.md b/README.md index afb95c46a..d3475a530 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,12 @@ to install the correct version of protobufs on your system and get the protobuil ```bash > make ``` + +## Build the example backend + +The example backend code is located in `/example/backend`. +Build the service with the resulting binary placed in the `/bin` directory. + +```bash +> make example +``` diff --git a/backend/v1/backend.pb.go b/backend/v1/backend.pb.go new file mode 100644 index 000000000..5b1e4eece --- /dev/null +++ b/backend/v1/backend.pb.go @@ -0,0 +1,172 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: github.com/docker/api/backend/v1/backend.proto + +package v1 + +import ( + context "context" + fmt "fmt" + proto "github.com/gogo/protobuf/proto" + types "github.com/gogo/protobuf/types" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type BackendInformationResponse struct { + ID string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *BackendInformationResponse) Reset() { *m = BackendInformationResponse{} } +func (m *BackendInformationResponse) String() string { return proto.CompactTextString(m) } +func (*BackendInformationResponse) ProtoMessage() {} +func (*BackendInformationResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_511f43e0e6c515ac, []int{0} +} +func (m *BackendInformationResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_BackendInformationResponse.Unmarshal(m, b) +} +func (m *BackendInformationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_BackendInformationResponse.Marshal(b, m, deterministic) +} +func (m *BackendInformationResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_BackendInformationResponse.Merge(m, src) +} +func (m *BackendInformationResponse) XXX_Size() int { + return xxx_messageInfo_BackendInformationResponse.Size(m) +} +func (m *BackendInformationResponse) XXX_DiscardUnknown() { + xxx_messageInfo_BackendInformationResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_BackendInformationResponse proto.InternalMessageInfo + +func (m *BackendInformationResponse) GetID() string { + if m != nil { + return m.ID + } + return "" +} + +func init() { + proto.RegisterType((*BackendInformationResponse)(nil), "com.docker.api.backend.v1.BackendInformationResponse") +} + +func init() { + proto.RegisterFile("github.com/docker/api/backend/v1/backend.proto", fileDescriptor_511f43e0e6c515ac) +} + +var fileDescriptor_511f43e0e6c515ac = []byte{ + // 218 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4b, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xc9, 0x4f, 0xce, 0x4e, 0x2d, 0xd2, 0x4f, 0x2c, + 0xc8, 0xd4, 0x4f, 0x4a, 0x4c, 0xce, 0x4e, 0xcd, 0x4b, 0xd1, 0x2f, 0x33, 0x84, 0x31, 0xf5, 0x0a, + 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x24, 0x93, 0xf3, 0x73, 0xf5, 0x20, 0x0a, 0xf5, 0x12, 0x0b, 0x32, + 0xf5, 0x60, 0xb2, 0x65, 0x86, 0x52, 0x22, 0xe9, 0xf9, 0xe9, 0xf9, 0x60, 0x55, 0xfa, 0x20, 0x16, + 0x44, 0x83, 0x94, 0x74, 0x7a, 0x7e, 0x7e, 0x7a, 0x4e, 0xaa, 0x3e, 0x98, 0x97, 0x54, 0x9a, 0xa6, + 0x9f, 0x9a, 0x5b, 0x50, 0x52, 0x09, 0x91, 0x54, 0x32, 0xe1, 0x92, 0x72, 0x82, 0x18, 0xe0, 0x99, + 0x97, 0x96, 0x5f, 0x94, 0x9b, 0x58, 0x92, 0x99, 0x9f, 0x17, 0x94, 0x5a, 0x5c, 0x90, 0x9f, 0x57, + 0x9c, 0x2a, 0x24, 0xc6, 0xc5, 0x94, 0x99, 0x22, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0xe9, 0xc4, 0xf6, + 0xe8, 0x9e, 0x3c, 0x93, 0xa7, 0x4b, 0x10, 0x53, 0x66, 0x8a, 0x51, 0x1e, 0x17, 0x3b, 0x54, 0x97, + 0x50, 0x32, 0x97, 0x10, 0xa6, 0x01, 0x42, 0x62, 0x7a, 0x10, 0x4b, 0xf5, 0x60, 0x96, 0xea, 0xb9, + 0x82, 0x2c, 0x95, 0x32, 0xd5, 0xc3, 0xe9, 0x7a, 0x3d, 0xdc, 0xee, 0x70, 0x52, 0x8d, 0x52, 0x26, + 0x14, 0x4a, 0xd6, 0x65, 0x86, 0x11, 0x0c, 0x49, 0x6c, 0x60, 0xfb, 0x8c, 0x01, 0x01, 0x00, 0x00, + 0xff, 0xff, 0x3d, 0x73, 0x02, 0xd8, 0x55, 0x01, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// BackendClient is the client API for Backend service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type BackendClient interface { + // BackendInformation returns implementation specific information on the backend. + BackendInformation(ctx context.Context, in *types.Empty, opts ...grpc.CallOption) (*BackendInformationResponse, error) +} + +type backendClient struct { + cc *grpc.ClientConn +} + +func NewBackendClient(cc *grpc.ClientConn) BackendClient { + return &backendClient{cc} +} + +func (c *backendClient) BackendInformation(ctx context.Context, in *types.Empty, opts ...grpc.CallOption) (*BackendInformationResponse, error) { + out := new(BackendInformationResponse) + err := c.cc.Invoke(ctx, "/com.docker.api.backend.v1.Backend/BackendInformation", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BackendServer is the server API for Backend service. +type BackendServer interface { + // BackendInformation returns implementation specific information on the backend. + BackendInformation(context.Context, *types.Empty) (*BackendInformationResponse, error) +} + +// UnimplementedBackendServer can be embedded to have forward compatible implementations. +type UnimplementedBackendServer struct { +} + +func (*UnimplementedBackendServer) BackendInformation(ctx context.Context, req *types.Empty) (*BackendInformationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method BackendInformation not implemented") +} + +func RegisterBackendServer(s *grpc.Server, srv BackendServer) { + s.RegisterService(&_Backend_serviceDesc, srv) +} + +func _Backend_BackendInformation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(types.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BackendServer).BackendInformation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/com.docker.api.backend.v1.Backend/BackendInformation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BackendServer).BackendInformation(ctx, req.(*types.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +var _Backend_serviceDesc = grpc.ServiceDesc{ + ServiceName: "com.docker.api.backend.v1.Backend", + HandlerType: (*BackendServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "BackendInformation", + Handler: _Backend_BackendInformation_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "github.com/docker/api/backend/v1/backend.proto", +} diff --git a/client/client.go b/client/client.go new file mode 100644 index 000000000..3652bfea8 --- /dev/null +++ b/client/client.go @@ -0,0 +1,49 @@ +/* + Copyright (c) 2019 Docker Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH + THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package client + +import "google.golang.org/grpc" + +// New returns a GRPC client +func New(address string) (*Client, error) { + conn, err := grpc.Dial(address, grpc.WithInsecure()) + if err != nil { + return nil, err + } + return &Client{ + conn: conn, + }, nil +} + +type Client struct { + conn *grpc.ClientConn +} + +func (c *Client) Close() error { + return c.conn.Close() +} diff --git a/example/backend/main.go b/example/backend/main.go new file mode 100644 index 000000000..5a7b5b743 --- /dev/null +++ b/example/backend/main.go @@ -0,0 +1,112 @@ +/* + Copyright (c) 2019 Docker Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH + THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package main + +import ( + "context" + "fmt" + "net" + "os" + "os/signal" + "syscall" + + "github.com/docker/api/server" + _ "github.com/gogo/googleapis/google/rpc" + _ "github.com/gogo/protobuf/types" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "example" + app.Usage = "example backend" + app.Description = "" + app.UseShortOptionHandling = true + app.EnableBashCompletion = true + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + Usage: "enable debug output in the logs", + }, + cli.StringFlag{ + Name: "address,a", + Usage: "address of the server", + }, + } + app.Before = func(clix *cli.Context) error { + if clix.GlobalBool("debug") { + logrus.SetLevel(logrus.DebugLevel) + } + return nil + } + app.Action = func(clix *cli.Context) error { + ctx, cancel := cancelContext() + defer cancel() + + // create a new GRPC server with the provided server package + s := server.New() + + // listen on a socket to accept connects + l, err := net.Listen("tcp", clix.GlobalString("address")) + if err != nil { + return errors.Wrap(err, "listen tcp") + } + defer l.Close() + + // handle context being closed or canceled + go func() { + <-ctx.Done() + logrus.Info("backend signaled to stop") + + s.Stop() + }() + + logrus.WithField("address", clix.GlobalString("address")).Info("serving daemon API") + // start the GRPC server to serve on the listener + return s.Serve(l) + } + if err := app.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +// cancelContext is a context that is canceled when a signal is +// sent to the process +func cancelContext() (context.Context, func()) { + ctx, cancel := context.WithCancel(context.Background()) + s := make(chan os.Signal) + signal.Notify(s, syscall.SIGTERM, syscall.SIGINT) + go func() { + <-s + cancel() + }() + return ctx, cancel +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 000000000..6adb16b97 --- /dev/null +++ b/server/server.go @@ -0,0 +1,56 @@ +/* + Copyright (c) 2019 Docker Inc. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, + DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH + THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package server + +import ( + "context" + + grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/health" + "google.golang.org/grpc/health/grpc_health_v1" +) + +// New returns a new GRPC server. +func New() *grpc.Server { + s := grpc.NewServer( + grpc.UnaryInterceptor(unary), + grpc.StreamInterceptor(stream), + ) + hs := health.NewServer() + grpc_health_v1.RegisterHealthServer(s, hs) + return s +} + +func unary(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + return grpc_prometheus.UnaryServerInterceptor(ctx, req, info, handler) +} + +func stream(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + return grpc_prometheus.StreamServerInterceptor(srv, ss, info, handler) +}