diff --git a/CHANGELOG.md b/CHANGELOG.md index 03832592..664b9b01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,26 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [UNRELEASED] - 0000-00-00 + +## [1.0.0-alpha.12] - 2018-03-27 ### Added +- [CLI] `ps` to support container status +- -a flag for logs, ps, env and run command +- commit sha in build struct for git based app +- Docker build logs with websocket on AWS +- Adding cluster-instance-type and controller-instance-type in `datacol init` - AWS elasticsearch support +- Websocket connection for streaming logs and Running one-off commands +- Added `--ref` flag into deploy cmd +- Proxy support through bastion Host +### Fixed +- CLI improvements +- [CLI] Bump default version of GCP cluster to `1.7.14-gke.1` +- [API] Merging app's domain individually +- [CLI] Respect `STACK` env variable to ditermine stack +- [GCP] Ingress should have Path: '/*' to match sub-resources +- Embedding Provider for `datacol login` +- Procfile support for Codecommit based app ## [1.0.0-alpha.11] - 2018-03-02 ### Added diff --git a/Gopkg.lock b/Gopkg.lock index 6d087e13..9199d007 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -469,7 +469,7 @@ [[projects]] name = "golang.org/x/net" - packages = ["context","context/ctxhttp","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"] + packages = ["context","context/ctxhttp","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace","websocket"] revision = "d866cfc389cec985d6fda2859936a575a55a3ab6" [[projects]] @@ -562,6 +562,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "b8efe3ea0ce0c7446c61982bf7d36bb8e82ea5dfa42c4f48f22798a560cdf063" + inputs-digest = "46839c89b027427ff758080f2a6d231f96ae66876d4d84c45f630b36ef9275f2" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Makefile b/Makefile index a1d9e0d3..57bc31e4 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -version=1.0.0-alpha.10 +version=1.0.0-alpha.12 MODEL_PROTO_DIR=./api/models SERVICE_PROTO_DIR=./api/controller VEDNOR_GOOGLE_APIS=./vendor/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis @@ -30,6 +30,10 @@ proto: go install -v ./vendor/github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger go install -v ./vendor/github.com/go-swagger/go-swagger/cmd/swagger +templates: + gsutil cp cmd/provider/aws/kubernetes-cluster.template gs://datacol-dev/ + gsutil acl ch -u AllUsers:R gs://datacol-dev/kubernetes-cluster.template + gentest: protoc -I $(SERVICE_PROTO_DIR) $(PROTOC_INCLUDE_DIR) \ --go_out=plugins=grpc:$(SERVICE_PROTO_DIR) \ diff --git a/api/controller/services.pb.go b/api/controller/services.pb.go index 08ecbc38..b0b53ee2 100644 --- a/api/controller/services.pb.go +++ b/api/controller/services.pb.go @@ -26,7 +26,6 @@ It has these top-level messages: AppResourceReq KubectlReq CmdResponse - BuildLogStreamReq LogStreamReq ProcessRunReq StreamMsg @@ -157,10 +156,11 @@ func (m *AuthRequest) GetPassword() string { } type AuthResponse struct { - Host string `protobuf:"bytes,1,opt,name=host" json:"host,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` - Project string `protobuf:"bytes,3,opt,name=project" json:"project,omitempty"` - Region string `protobuf:"bytes,4,opt,name=region" json:"region,omitempty"` + Host string `protobuf:"bytes,1,opt,name=host" json:"host,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` + Project string `protobuf:"bytes,3,opt,name=project" json:"project,omitempty"` + Region string `protobuf:"bytes,4,opt,name=region" json:"region,omitempty"` + Provider string `protobuf:"bytes,5,opt,name=provider" json:"provider,omitempty"` } func (m *AuthResponse) Reset() { *m = AuthResponse{} } @@ -196,6 +196,13 @@ func (m *AuthResponse) GetRegion() string { return "" } +func (m *AuthResponse) GetProvider() string { + if m != nil { + return m.Provider + } + return "" +} + type CreateBuildRequest struct { App string `protobuf:"bytes,1,opt,name=app" json:"app,omitempty"` Version string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"` @@ -492,38 +499,6 @@ func (m *CmdResponse) GetStdErr() string { return "" } -type BuildLogStreamReq struct { - Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` - Follow bool `protobuf:"varint,2,opt,name=follow" json:"follow,omitempty"` - Since *google_protobuf2.Duration `protobuf:"bytes,3,opt,name=since" json:"since,omitempty"` -} - -func (m *BuildLogStreamReq) Reset() { *m = BuildLogStreamReq{} } -func (m *BuildLogStreamReq) String() string { return proto.CompactTextString(m) } -func (*BuildLogStreamReq) ProtoMessage() {} -func (*BuildLogStreamReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} } - -func (m *BuildLogStreamReq) GetId() string { - if m != nil { - return m.Id - } - return "" -} - -func (m *BuildLogStreamReq) GetFollow() bool { - if m != nil { - return m.Follow - } - return false -} - -func (m *BuildLogStreamReq) GetSince() *google_protobuf2.Duration { - if m != nil { - return m.Since - } - return nil -} - type LogStreamReq struct { Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` Follow bool `protobuf:"varint,2,opt,name=follow" json:"follow,omitempty"` @@ -534,7 +509,7 @@ type LogStreamReq struct { func (m *LogStreamReq) Reset() { *m = LogStreamReq{} } func (m *LogStreamReq) String() string { return proto.CompactTextString(m) } func (*LogStreamReq) ProtoMessage() {} -func (*LogStreamReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } +func (*LogStreamReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} } func (m *LogStreamReq) GetName() string { if m != nil { @@ -572,7 +547,7 @@ type ProcessRunReq struct { func (m *ProcessRunReq) Reset() { *m = ProcessRunReq{} } func (m *ProcessRunReq) String() string { return proto.CompactTextString(m) } func (*ProcessRunReq) ProtoMessage() {} -func (*ProcessRunReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } +func (*ProcessRunReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } func (m *ProcessRunReq) GetName() string { if m != nil { @@ -595,7 +570,7 @@ type StreamMsg struct { func (m *StreamMsg) Reset() { *m = StreamMsg{} } func (m *StreamMsg) String() string { return proto.CompactTextString(m) } func (*StreamMsg) ProtoMessage() {} -func (*StreamMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } +func (*StreamMsg) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } func (m *StreamMsg) GetData() []byte { if m != nil { @@ -611,7 +586,7 @@ type ReleaseListResponse struct { func (m *ReleaseListResponse) Reset() { *m = ReleaseListResponse{} } func (m *ReleaseListResponse) String() string { return proto.CompactTextString(m) } func (*ReleaseListResponse) ProtoMessage() {} -func (*ReleaseListResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} } +func (*ReleaseListResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } func (m *ReleaseListResponse) GetReleases() []*models.Release { if m != nil { @@ -628,7 +603,7 @@ type CreateReleaseRequest struct { func (m *CreateReleaseRequest) Reset() { *m = CreateReleaseRequest{} } func (m *CreateReleaseRequest) String() string { return proto.CompactTextString(m) } func (*CreateReleaseRequest) ProtoMessage() {} -func (*CreateReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{22} } +func (*CreateReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{21} } func (m *CreateReleaseRequest) GetBuild() *models.Build { if m != nil { @@ -662,7 +637,6 @@ func init() { proto.RegisterType((*AppResourceReq)(nil), "controller.AppResourceReq") proto.RegisterType((*KubectlReq)(nil), "controller.KubectlReq") proto.RegisterType((*CmdResponse)(nil), "controller.CmdResponse") - proto.RegisterType((*BuildLogStreamReq)(nil), "controller.BuildLogStreamReq") proto.RegisterType((*LogStreamReq)(nil), "controller.LogStreamReq") proto.RegisterType((*ProcessRunReq)(nil), "controller.ProcessRunReq") proto.RegisterType((*StreamMsg)(nil), "controller.StreamMsg") @@ -696,7 +670,6 @@ type ProviderServiceClient interface { BuildDelete(ctx context.Context, in *AppIdRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) BuildList(ctx context.Context, in *AppRequest, opts ...grpc.CallOption) (*BuildListResponse, error) BuildLogs(ctx context.Context, in *BuildLogRequest, opts ...grpc.CallOption) (*BuildLogResponse, error) - BuildLogsStream(ctx context.Context, in *BuildLogStreamReq, opts ...grpc.CallOption) (ProviderService_BuildLogsStreamClient, error) EnvironmentGet(ctx context.Context, in *AppRequest, opts ...grpc.CallOption) (*models.EnvConfig, error) EnvironmentSet(ctx context.Context, in *EnvSetRequest, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) // Release endpoints @@ -869,38 +842,6 @@ func (c *providerServiceClient) BuildLogs(ctx context.Context, in *BuildLogReque return out, nil } -func (c *providerServiceClient) BuildLogsStream(ctx context.Context, in *BuildLogStreamReq, opts ...grpc.CallOption) (ProviderService_BuildLogsStreamClient, error) { - stream, err := grpc.NewClientStream(ctx, &_ProviderService_serviceDesc.Streams[1], c.cc, "/controller.ProviderService/BuildLogsStream", opts...) - if err != nil { - return nil, err - } - x := &providerServiceBuildLogsStreamClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } - return x, nil -} - -type ProviderService_BuildLogsStreamClient interface { - Recv() (*StreamMsg, error) - grpc.ClientStream -} - -type providerServiceBuildLogsStreamClient struct { - grpc.ClientStream -} - -func (x *providerServiceBuildLogsStreamClient) Recv() (*StreamMsg, error) { - m := new(StreamMsg) - if err := x.ClientStream.RecvMsg(m); err != nil { - return nil, err - } - return m, nil -} - func (c *providerServiceClient) EnvironmentGet(ctx context.Context, in *AppRequest, opts ...grpc.CallOption) (*models.EnvConfig, error) { out := new(models.EnvConfig) err := grpc.Invoke(ctx, "/controller.ProviderService/EnvironmentGet", in, out, c.cc, opts...) @@ -1001,7 +942,7 @@ func (c *providerServiceClient) Kubectl(ctx context.Context, in *KubectlReq, opt } func (c *providerServiceClient) ProcessRun(ctx context.Context, opts ...grpc.CallOption) (ProviderService_ProcessRunClient, error) { - stream, err := grpc.NewClientStream(ctx, &_ProviderService_serviceDesc.Streams[2], c.cc, "/controller.ProviderService/ProcessRun", opts...) + stream, err := grpc.NewClientStream(ctx, &_ProviderService_serviceDesc.Streams[1], c.cc, "/controller.ProviderService/ProcessRun", opts...) if err != nil { return nil, err } @@ -1050,7 +991,7 @@ func (c *providerServiceClient) ProcessSave(ctx context.Context, in *models.Form } func (c *providerServiceClient) LogStream(ctx context.Context, in *LogStreamReq, opts ...grpc.CallOption) (ProviderService_LogStreamClient, error) { - stream, err := grpc.NewClientStream(ctx, &_ProviderService_serviceDesc.Streams[3], c.cc, "/controller.ProviderService/LogStream", opts...) + stream, err := grpc.NewClientStream(ctx, &_ProviderService_serviceDesc.Streams[2], c.cc, "/controller.ProviderService/LogStream", opts...) if err != nil { return nil, err } @@ -1099,7 +1040,6 @@ type ProviderServiceServer interface { BuildDelete(context.Context, *AppIdRequest) (*google_protobuf1.Empty, error) BuildList(context.Context, *AppRequest) (*BuildListResponse, error) BuildLogs(context.Context, *BuildLogRequest) (*BuildLogResponse, error) - BuildLogsStream(*BuildLogStreamReq, ProviderService_BuildLogsStreamServer) error EnvironmentGet(context.Context, *AppRequest) (*models.EnvConfig, error) EnvironmentSet(context.Context, *EnvSetRequest) (*google_protobuf1.Empty, error) // Release endpoints @@ -1368,27 +1308,6 @@ func _ProviderService_BuildLogs_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } -func _ProviderService_BuildLogsStream_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(BuildLogStreamReq) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(ProviderServiceServer).BuildLogsStream(m, &providerServiceBuildLogsStreamServer{stream}) -} - -type ProviderService_BuildLogsStreamServer interface { - Send(*StreamMsg) error - grpc.ServerStream -} - -type providerServiceBuildLogsStreamServer struct { - grpc.ServerStream -} - -func (x *providerServiceBuildLogsStreamServer) Send(m *StreamMsg) error { - return x.ServerStream.SendMsg(m) -} - func _ProviderService_EnvironmentGet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(AppRequest) if err := dec(in); err != nil { @@ -1781,11 +1700,6 @@ var _ProviderService_serviceDesc = grpc.ServiceDesc{ Handler: _ProviderService_BuildImport_Handler, ClientStreams: true, }, - { - StreamName: "BuildLogsStream", - Handler: _ProviderService_BuildLogsStream_Handler, - ServerStreams: true, - }, { StreamName: "ProcessRun", Handler: _ProviderService_ProcessRun_Handler, @@ -1804,106 +1718,104 @@ var _ProviderService_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("services.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 1604 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x58, 0x5b, 0x6f, 0x1b, 0xc5, - 0x17, 0xff, 0x6f, 0xe2, 0x5c, 0x7c, 0xec, 0xdc, 0x26, 0x37, 0xc7, 0x69, 0x2e, 0x9d, 0xea, 0x2f, - 0xd2, 0xa0, 0x7a, 0x43, 0x41, 0x5c, 0x52, 0x78, 0x48, 0x52, 0x17, 0x45, 0x14, 0x1a, 0x36, 0x54, - 0xe1, 0xd2, 0xd2, 0x6e, 0xec, 0xb1, 0xb3, 0x64, 0xbd, 0x33, 0xcc, 0xee, 0xba, 0x44, 0x55, 0x5f, - 0x78, 0xe1, 0x03, 0xf0, 0x89, 0x78, 0xeb, 0x23, 0x12, 0xcf, 0x20, 0x54, 0xf1, 0x41, 0xd0, 0xdc, - 0xd6, 0xbb, 0xf6, 0x3a, 0x84, 0x8a, 0x97, 0x68, 0xce, 0xce, 0xcc, 0xef, 0xfc, 0xe6, 0xcc, 0xef, - 0x9c, 0x33, 0x0e, 0x4c, 0x87, 0x84, 0x77, 0xbd, 0x06, 0x09, 0x6b, 0x8c, 0xd3, 0x88, 0x22, 0x68, - 0xd0, 0x20, 0xe2, 0xd4, 0xf7, 0x09, 0xaf, 0x5e, 0x6b, 0x53, 0xda, 0xf6, 0x89, 0xed, 0x32, 0xcf, - 0x76, 0x83, 0x80, 0x46, 0x6e, 0xe4, 0xd1, 0x40, 0xaf, 0xac, 0x56, 0x58, 0x74, 0xc1, 0x48, 0x68, - 0x93, 0x0e, 0x8b, 0x2e, 0xd4, 0x5f, 0x3d, 0xb3, 0xae, 0x67, 0x9a, 0x31, 0x97, 0x3b, 0x92, 0x81, - 0x9e, 0xbf, 0xd5, 0xf6, 0xa2, 0xb3, 0xf8, 0xb4, 0xd6, 0xa0, 0x1d, 0xbb, 0x4d, 0xdb, 0xd4, 0x96, - 0x9f, 0x4f, 0xe3, 0x96, 0xb4, 0xa4, 0x21, 0x47, 0x7a, 0xf9, 0x3b, 0xa9, 0xe5, 0x4d, 0x37, 0x72, - 0x1b, 0xd4, 0xbf, 0xe5, 0x51, 0x33, 0x94, 0xec, 0x3a, 0xb4, 0x49, 0xfc, 0xd0, 0x96, 0x6e, 0xd5, - 0x2e, 0x7c, 0x07, 0x4a, 0xf7, 0xbd, 0x30, 0x72, 0xc8, 0xf7, 0x31, 0x09, 0x23, 0xb4, 0x04, 0xe3, - 0xb4, 0xd5, 0x0a, 0x49, 0x54, 0xb1, 0x36, 0xad, 0xad, 0x31, 0x47, 0x5b, 0x68, 0x01, 0xc6, 0x7c, - 0xaf, 0xe3, 0x45, 0x95, 0x11, 0xf9, 0x59, 0x19, 0xf8, 0x0e, 0xc0, 0x1e, 0x63, 0x66, 0x2f, 0x82, - 0x42, 0xe0, 0x76, 0x88, 0xdc, 0x59, 0x74, 0xe4, 0x18, 0xad, 0xc0, 0x24, 0x27, 0x8c, 0x3e, 0x89, - 0xb9, 0x2f, 0xb7, 0x16, 0x9d, 0x09, 0x61, 0x3f, 0xe4, 0x3e, 0x6e, 0xc1, 0xcc, 0x1e, 0x63, 0xca, - 0x79, 0xc8, 0x68, 0x10, 0x12, 0xf4, 0x7f, 0x28, 0xb8, 0x8c, 0x85, 0x15, 0x6b, 0x73, 0x74, 0xab, - 0x74, 0xbb, 0x54, 0x53, 0x7c, 0x6b, 0x7b, 0x8c, 0xed, 0x17, 0x5e, 0xfe, 0xb1, 0xf1, 0x3f, 0x47, - 0x4e, 0xe7, 0x93, 0x49, 0x51, 0x1f, 0x4d, 0x53, 0xc7, 0x37, 0xa1, 0xb4, 0x17, 0x47, 0x67, 0x86, - 0x65, 0x15, 0x26, 0x99, 0x1b, 0x86, 0xcf, 0x28, 0x6f, 0x6a, 0xa6, 0x89, 0x8d, 0xcf, 0xa0, 0xac, - 0x96, 0x6a, 0x3e, 0x08, 0x0a, 0x67, 0x34, 0x8c, 0xcc, 0x89, 0xc4, 0x38, 0x39, 0xe5, 0x48, 0xea, - 0x94, 0x15, 0x98, 0x60, 0x9c, 0x7e, 0x47, 0x1a, 0xca, 0x77, 0xd1, 0x31, 0xa6, 0x20, 0xc5, 0x49, - 0xdb, 0xa3, 0x41, 0xa5, 0x20, 0x27, 0xb4, 0x85, 0x1f, 0x01, 0x3a, 0xe0, 0xc4, 0x8d, 0xc8, 0x7e, - 0xec, 0xf9, 0x4d, 0xc3, 0x6d, 0x16, 0x46, 0x5d, 0xc6, 0xb4, 0x3b, 0x31, 0x14, 0xc8, 0x5d, 0xc2, - 0x43, 0x01, 0xa0, 0xc3, 0xa7, 0x4d, 0x79, 0x0e, 0x4e, 0x1b, 0x2d, 0xcf, 0x27, 0xd2, 0x69, 0xd9, - 0x49, 0x6c, 0xbc, 0x03, 0xe5, 0x3d, 0xc6, 0x0e, 0x2f, 0xc1, 0x9d, 0x86, 0x11, 0xaf, 0xa9, 0x21, - 0x47, 0xbc, 0x26, 0xae, 0xc3, 0x8c, 0x64, 0x72, 0x9f, 0xb6, 0xaf, 0xbc, 0x49, 0xac, 0x60, 0x34, - 0xd4, 0xe1, 0x16, 0x43, 0xbc, 0x0b, 0xb3, 0x3d, 0x18, 0x1d, 0x44, 0xbd, 0xca, 0x4a, 0x56, 0xa9, - 0xfb, 0x0b, 0x48, 0x58, 0x19, 0xd9, 0x1c, 0xdd, 0x2a, 0x3a, 0xca, 0xc0, 0xef, 0xc1, 0x54, 0x3d, - 0xe8, 0x1e, 0x93, 0xe8, 0x32, 0x3d, 0x21, 0x28, 0x08, 0x41, 0x9b, 0xe8, 0x8b, 0x31, 0xbe, 0x07, - 0x0b, 0x0e, 0x09, 0x69, 0xcc, 0x1b, 0x24, 0xa3, 0xa6, 0x1a, 0x14, 0xb9, 0xfe, 0x6e, 0x24, 0x35, - 0x6b, 0x24, 0x65, 0x36, 0x38, 0xbd, 0x25, 0x78, 0x17, 0xe6, 0x14, 0xf9, 0xac, 0x24, 0xc7, 0x4f, - 0xc5, 0x47, 0x83, 0x30, 0x65, 0x10, 0xd4, 0xc5, 0xe9, 0x49, 0xfc, 0x21, 0xcc, 0x1f, 0x71, 0xda, - 0x20, 0x61, 0xd8, 0xb7, 0x7b, 0xec, 0x30, 0x22, 0x1d, 0xb3, 0x79, 0xc6, 0x6c, 0xd6, 0x6b, 0x1d, - 0x35, 0x8b, 0x7f, 0xb1, 0x60, 0x51, 0xc9, 0x21, 0xe1, 0x75, 0x79, 0x0c, 0xce, 0xbd, 0xc0, 0x5c, - 0x84, 0x1c, 0xa3, 0x3a, 0x8c, 0x33, 0x97, 0xbb, 0x1d, 0x71, 0x1b, 0xc2, 0xd3, 0xad, 0x5a, 0xaf, - 0x40, 0xd5, 0x72, 0xa1, 0x6b, 0x47, 0x72, 0x7d, 0x3d, 0x88, 0xf8, 0x85, 0xa3, 0x37, 0x57, 0x3f, - 0x80, 0x52, 0xea, 0xb3, 0xb8, 0xba, 0x73, 0x72, 0x61, 0x24, 0x70, 0x4e, 0x2e, 0xc4, 0xd5, 0x75, - 0x5d, 0x3f, 0x36, 0xf2, 0x57, 0xc6, 0xee, 0xc8, 0xfb, 0x16, 0x7e, 0x17, 0xa6, 0x65, 0x2d, 0x48, - 0x9c, 0xe4, 0x08, 0x28, 0x27, 0x77, 0xf0, 0x26, 0xc0, 0x27, 0xf1, 0x29, 0x69, 0x44, 0xbe, 0xd8, - 0x83, 0xa0, 0xe0, 0xf2, 0xb6, 0x8a, 0x57, 0xd1, 0x91, 0x63, 0xec, 0x43, 0xe9, 0xa0, 0xd3, 0x4c, - 0x62, 0xba, 0x0a, 0x45, 0xf2, 0x83, 0x17, 0x3d, 0x69, 0xd0, 0x26, 0xd1, 0xaa, 0x9a, 0x14, 0x1f, - 0x0e, 0x68, 0x53, 0x8a, 0x8d, 0x70, 0xae, 0x1d, 0x88, 0xa1, 0xc8, 0xc0, 0x30, 0x6a, 0x3e, 0x88, - 0x4d, 0x6a, 0x6a, 0x4b, 0x7f, 0xaf, 0x73, 0x6e, 0x32, 0x53, 0x59, 0xd8, 0x37, 0x2a, 0xa0, 0xed, - 0xe3, 0x88, 0x13, 0xb7, 0x23, 0x68, 0x29, 0xe5, 0x5b, 0x89, 0xf2, 0x97, 0x60, 0xbc, 0x45, 0x7d, - 0x9f, 0x3e, 0x93, 0x9e, 0x26, 0x1d, 0x6d, 0x21, 0x1b, 0xc6, 0x42, 0x2f, 0x68, 0xa8, 0x8c, 0x2c, - 0xdd, 0x5e, 0xa9, 0xa9, 0xd6, 0x50, 0x33, 0x95, 0xbb, 0x76, 0x57, 0x97, 0x78, 0x47, 0xad, 0xc3, - 0x3f, 0x59, 0x50, 0xce, 0x78, 0xca, 0xbb, 0xf0, 0xff, 0xca, 0x9b, 0xa9, 0x19, 0xa2, 0xfe, 0xeb, - 0x53, 0x27, 0x36, 0xfe, 0x08, 0xa6, 0x8c, 0x2a, 0xe3, 0x60, 0x18, 0x93, 0x0a, 0x4c, 0x34, 0x68, - 0xa7, 0xe3, 0x4a, 0xf5, 0x89, 0x1b, 0x32, 0x26, 0xde, 0x80, 0xa2, 0x3a, 0xc4, 0xa7, 0x61, 0x3b, - 0xc9, 0x52, 0x4b, 0xd6, 0x25, 0x95, 0xa5, 0xfb, 0x30, 0xef, 0x10, 0x9f, 0xb8, 0x61, 0x36, 0x49, - 0xdf, 0x14, 0x0d, 0x42, 0x7e, 0x1e, 0x48, 0x12, 0xbd, 0xdc, 0x49, 0x16, 0xe0, 0x63, 0x58, 0x30, - 0x5a, 0x56, 0x53, 0x3a, 0x4b, 0x6e, 0xc0, 0x98, 0xcc, 0x43, 0xe9, 0x70, 0x20, 0x47, 0xd5, 0x9c, - 0x88, 0x62, 0x93, 0x76, 0x5c, 0xcf, 0x54, 0x52, 0x6d, 0xdd, 0xfe, 0x7d, 0x01, 0x66, 0x8e, 0x38, - 0xed, 0x7a, 0x4d, 0xc2, 0x8f, 0x55, 0x97, 0x47, 0x9f, 0x41, 0x41, 0x34, 0x02, 0xb4, 0x9c, 0x4e, - 0xa3, 0x54, 0x17, 0xa9, 0x56, 0x06, 0x27, 0xd4, 0x81, 0xf0, 0xfc, 0x8f, 0xbf, 0xfd, 0xf5, 0xf3, - 0xc8, 0x14, 0x9e, 0xb4, 0xbb, 0x6f, 0xd9, 0x6e, 0x1c, 0x9d, 0xed, 0x5a, 0xdb, 0xe8, 0x73, 0x98, - 0xd0, 0xbd, 0x2e, 0x0b, 0x99, 0x6a, 0xbd, 0xd5, 0xd5, 0x0c, 0x64, 0xb6, 0x33, 0xe2, 0x59, 0x89, - 0x0a, 0x48, 0xa1, 0x8a, 0x26, 0x58, 0x87, 0xe2, 0x1e, 0x63, 0x2a, 0x1c, 0x68, 0xa9, 0x6f, 0xaf, - 0xc1, 0x4c, 0xb7, 0xd0, 0x3e, 0x66, 0x8c, 0x85, 0x82, 0xd9, 0x3d, 0x18, 0xdf, 0x63, 0xec, 0x63, - 0x12, 0x5d, 0x0d, 0x63, 0x59, 0x62, 0xcc, 0xa1, 0x19, 0x83, 0x61, 0x3f, 0x17, 0xc2, 0x78, 0x81, - 0x1e, 0xeb, 0xa7, 0x40, 0x18, 0xb9, 0x7c, 0x38, 0xd6, 0xd2, 0x80, 0x44, 0xeb, 0xe2, 0x41, 0x84, - 0x37, 0x24, 0xec, 0x0a, 0x5a, 0xee, 0x83, 0xb5, 0xb9, 0x06, 0xfc, 0x42, 0x9e, 0xf6, 0x2e, 0xf1, - 0xc9, 0x25, 0xa7, 0x1d, 0x86, 0xae, 0x49, 0x6f, 0x0f, 0x90, 0x3e, 0x82, 0x92, 0x94, 0x88, 0x8e, - 0xe2, 0xfa, 0x60, 0xd1, 0x4c, 0xb7, 0xe7, 0x6a, 0x56, 0x57, 0x18, 0x49, 0xd8, 0x32, 0x06, 0x01, - 0xab, 0xfa, 0x00, 0xfa, 0x4a, 0x23, 0x1e, 0x76, 0x18, 0xe5, 0x11, 0x5a, 0x4c, 0x23, 0x26, 0xf9, - 0x31, 0x94, 0xe8, 0x8a, 0x44, 0x9c, 0xc7, 0x73, 0x3d, 0x44, 0xdb, 0x93, 0x48, 0x5b, 0x16, 0x3a, - 0x81, 0xb2, 0x66, 0x23, 0xb5, 0x8f, 0x36, 0xf3, 0x4a, 0x7c, 0x3a, 0x2d, 0xaa, 0xfd, 0x99, 0x84, - 0x17, 0x24, 0xfe, 0x34, 0x2e, 0x0b, 0x7c, 0x93, 0x55, 0xe8, 0x3e, 0x4c, 0x4a, 0x60, 0x21, 0x82, - 0x4a, 0x5f, 0x68, 0x0f, 0x87, 0x1d, 0x3e, 0x23, 0x04, 0x4d, 0xf5, 0xb9, 0xd7, 0x7c, 0x81, 0xbe, - 0xd4, 0x11, 0xd0, 0x77, 0x35, 0x1c, 0xf0, 0x4a, 0xb7, 0x95, 0x46, 0x7e, 0x0c, 0xc5, 0xa4, 0x3f, - 0x0f, 0xd5, 0xc0, 0x5a, 0xfa, 0xfb, 0x40, 0x3b, 0x37, 0x11, 0x46, 0xe9, 0x08, 0x6b, 0x31, 0x34, - 0x0d, 0x3c, 0x6d, 0x87, 0x68, 0x75, 0x10, 0x26, 0x79, 0x19, 0x55, 0xaf, 0xe5, 0x4f, 0x6a, 0x17, - 0xd7, 0xa4, 0x8b, 0x25, 0xb4, 0xd0, 0xc7, 0xdf, 0xf6, 0x05, 0x70, 0xd0, 0x7b, 0x68, 0x85, 0x4a, - 0x10, 0x68, 0x2d, 0x0f, 0x2e, 0xe9, 0x08, 0xd5, 0x7c, 0x0d, 0xe1, 0x1b, 0xd2, 0xcd, 0x1a, 0x5a, - 0xcd, 0x73, 0x63, 0x87, 0x72, 0xdd, 0x8e, 0x85, 0x8e, 0x61, 0xba, 0x1e, 0x74, 0x3d, 0x4e, 0x83, - 0x0e, 0x09, 0xa2, 0xcb, 0xf2, 0x7c, 0xce, 0x5c, 0x70, 0x3d, 0xe8, 0x1e, 0xd0, 0xa0, 0xe5, 0xb5, - 0xf1, 0x92, 0xf4, 0x31, 0x8b, 0xa6, 0x85, 0x0f, 0x12, 0x74, 0x4d, 0xa8, 0xbe, 0xc9, 0x80, 0x1e, - 0x93, 0x08, 0xad, 0xa4, 0x41, 0x33, 0xcf, 0xb8, 0xa1, 0xf7, 0xac, 0xc1, 0x71, 0x3f, 0x78, 0x03, - 0x4a, 0xa9, 0x46, 0x31, 0x94, 0xee, 0x46, 0xfa, 0x7b, 0x4e, 0x67, 0xc1, 0xab, 0x12, 0x7f, 0x11, - 0xcd, 0xa7, 0xc5, 0x6e, 0x9c, 0x9c, 0xc2, 0x94, 0xde, 0xf3, 0xda, 0x3a, 0xd5, 0x35, 0x6b, 0x7b, - 0x39, 0x8b, 0xef, 0x32, 0xf6, 0x42, 0xe9, 0xf5, 0x29, 0x94, 0xd3, 0xef, 0xd2, 0xe1, 0x95, 0x7f, - 0x33, 0x7b, 0x94, 0xc1, 0xa7, 0x2c, 0x5e, 0x94, 0xbe, 0x66, 0xd0, 0x94, 0xf2, 0xa5, 0x5f, 0xac, - 0xe8, 0xa1, 0x08, 0x95, 0x32, 0x2e, 0xbb, 0xd9, 0x81, 0x57, 0x6f, 0x56, 0xa3, 0x09, 0x9e, 0x09, - 0xce, 0x23, 0x98, 0x36, 0x2b, 0x75, 0x65, 0xbc, 0xfe, 0x8f, 0xcf, 0xc9, 0x1c, 0x27, 0x9a, 0x34, - 0xee, 0x23, 0xfd, 0x6d, 0x0f, 0xfd, 0x35, 0xeb, 0xb9, 0x66, 0xbf, 0x9d, 0xcf, 0x9e, 0xa4, 0xc3, - 0x1e, 0x9c, 0xa3, 0xea, 0x00, 0x7a, 0x42, 0x3c, 0x87, 0xf4, 0x1b, 0x12, 0xfb, 0x7a, 0x75, 0x23, - 0x0f, 0xdb, 0xf6, 0xbd, 0xe0, 0x5c, 0x5d, 0x31, 0xf2, 0x7a, 0xc7, 0x78, 0x18, 0xf8, 0xff, 0xde, - 0xd1, 0x4d, 0xe9, 0xe8, 0x46, 0xf5, 0x7a, 0xae, 0xa3, 0x38, 0x48, 0xb9, 0x7a, 0x00, 0x13, 0xfa, - 0x89, 0x9c, 0x0d, 0x55, 0xef, 0xdd, 0x5c, 0xcd, 0x68, 0x2b, 0xf5, 0x5a, 0x36, 0x4d, 0x1f, 0x95, - 0x84, 0x9b, 0x73, 0x8d, 0x72, 0x02, 0xd0, 0x7b, 0xeb, 0x0d, 0x6b, 0x52, 0x43, 0xea, 0x4e, 0xa6, - 0xf0, 0x33, 0x85, 0x62, 0xf3, 0x38, 0xd8, 0xb2, 0x76, 0x2c, 0xf4, 0x14, 0x4a, 0xa9, 0x9f, 0x41, - 0x57, 0xcb, 0xdd, 0x9c, 0xdf, 0x4d, 0xb8, 0x22, 0x9d, 0x20, 0x34, 0x9b, 0x76, 0xe2, 0x0b, 0xc8, - 0x93, 0xc4, 0xc3, 0xb1, 0xdb, 0x25, 0x28, 0x29, 0x5a, 0xf7, 0x28, 0xef, 0xc8, 0xb7, 0xee, 0x50, - 0xd5, 0xac, 0x49, 0xcc, 0x65, 0xb4, 0x98, 0xc6, 0x6c, 0x99, 0x6d, 0xe8, 0x04, 0x8a, 0x49, 0xd9, - 0xcd, 0x56, 0x83, 0xab, 0x54, 0xe3, 0x4c, 0x54, 0x64, 0x05, 0x56, 0x17, 0xb9, 0x63, 0xed, 0x57, - 0x5e, 0xbe, 0x5a, 0xb7, 0x7e, 0x7d, 0xb5, 0x6e, 0xfd, 0xf9, 0x6a, 0xdd, 0xfa, 0x3a, 0xf5, 0x8f, - 0xa3, 0xd3, 0x71, 0xc9, 0xf0, 0xed, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0xf6, 0x67, 0x62, 0x01, - 0x5d, 0x12, 0x00, 0x00, + // 1571 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x58, 0x5b, 0x6f, 0xdb, 0x46, + 0x16, 0x5e, 0xda, 0xf2, 0x45, 0x47, 0xf2, 0x6d, 0x7c, 0x93, 0xe5, 0xc4, 0x76, 0x26, 0x58, 0xac, + 0xe3, 0x45, 0xc4, 0x6c, 0x76, 0xb1, 0x17, 0x67, 0xf7, 0xc1, 0x76, 0x94, 0x85, 0x51, 0xb7, 0x71, + 0xe9, 0x06, 0xee, 0x25, 0x69, 0x42, 0x8b, 0x23, 0x99, 0x35, 0xc9, 0x99, 0x0e, 0x49, 0xa5, 0x46, + 0x90, 0x97, 0xa2, 0x40, 0x7f, 0x40, 0x7f, 0x51, 0xdf, 0xf2, 0x58, 0xa0, 0xef, 0x45, 0x11, 0xf4, + 0xb5, 0xff, 0xa1, 0x98, 0x1b, 0x45, 0xea, 0xe2, 0xba, 0x79, 0x31, 0xe6, 0xcc, 0xe5, 0x3b, 0x1f, + 0xcf, 0x7c, 0xe7, 0xcc, 0x91, 0x61, 0x36, 0x26, 0xbc, 0xeb, 0xb7, 0x48, 0xdc, 0x60, 0x9c, 0x26, + 0x14, 0x41, 0x8b, 0x46, 0x09, 0xa7, 0x41, 0x40, 0x78, 0xfd, 0x46, 0x87, 0xd2, 0x4e, 0x40, 0x6c, + 0x97, 0xf9, 0xb6, 0x1b, 0x45, 0x34, 0x71, 0x13, 0x9f, 0x46, 0x7a, 0x67, 0xbd, 0xc6, 0x92, 0x4b, + 0x46, 0x62, 0x9b, 0x84, 0x2c, 0xb9, 0x54, 0x7f, 0xf5, 0xca, 0x86, 0x5e, 0xf1, 0x52, 0x2e, 0x4f, + 0x64, 0x03, 0xbd, 0x7e, 0xb7, 0xe3, 0x27, 0xe7, 0xe9, 0x59, 0xa3, 0x45, 0x43, 0xbb, 0x43, 0x3b, + 0xd4, 0x96, 0xd3, 0x67, 0x69, 0x5b, 0x5a, 0xd2, 0x90, 0x23, 0xbd, 0xfd, 0x1f, 0xb9, 0xed, 0x9e, + 0x9b, 0xb8, 0x2d, 0x1a, 0xdc, 0xf5, 0xa9, 0x19, 0x4a, 0x76, 0x21, 0xf5, 0x48, 0x10, 0xdb, 0xd2, + 0xad, 0x3a, 0x85, 0x1f, 0x40, 0xe5, 0xc8, 0x8f, 0x13, 0x87, 0x7c, 0x99, 0x92, 0x38, 0x41, 0x2b, + 0x30, 0x49, 0xdb, 0xed, 0x98, 0x24, 0x35, 0x6b, 0xcb, 0xda, 0x9e, 0x70, 0xb4, 0x85, 0x96, 0x60, + 0x22, 0xf0, 0x43, 0x3f, 0xa9, 0x8d, 0xc9, 0x69, 0x65, 0xe0, 0x07, 0x00, 0x7b, 0x8c, 0x99, 0xb3, + 0x08, 0x4a, 0x91, 0x1b, 0x12, 0x79, 0xb2, 0xec, 0xc8, 0x31, 0x5a, 0x83, 0x69, 0x4e, 0x18, 0x7d, + 0x9e, 0xf2, 0x40, 0x1e, 0x2d, 0x3b, 0x53, 0xc2, 0x7e, 0xc2, 0x03, 0xdc, 0x86, 0xb9, 0x3d, 0xc6, + 0x94, 0xf3, 0x98, 0xd1, 0x28, 0x26, 0xe8, 0xcf, 0x50, 0x72, 0x19, 0x8b, 0x6b, 0xd6, 0xd6, 0xf8, + 0x76, 0xe5, 0x7e, 0xa5, 0xa1, 0xf8, 0x36, 0xf6, 0x18, 0xdb, 0x2f, 0xbd, 0xf9, 0x69, 0xf3, 0x4f, + 0x8e, 0x5c, 0x1e, 0x4e, 0x26, 0x47, 0x7d, 0x3c, 0x4f, 0x1d, 0xdf, 0x81, 0xca, 0x5e, 0x9a, 0x9c, + 0x1b, 0x96, 0x75, 0x98, 0x66, 0x6e, 0x1c, 0xbf, 0xa4, 0xdc, 0xd3, 0x4c, 0x33, 0x1b, 0x7f, 0x63, + 0x41, 0x55, 0xed, 0xd5, 0x84, 0x10, 0x94, 0xce, 0x69, 0x9c, 0x98, 0x4f, 0x12, 0xe3, 0xec, 0x33, + 0xc7, 0x72, 0x9f, 0x59, 0x83, 0x29, 0xc6, 0xe9, 0x17, 0xa4, 0xa5, 0x9c, 0x97, 0x1d, 0x63, 0x0a, + 0x56, 0x9c, 0x74, 0x7c, 0x1a, 0xd5, 0x4a, 0x72, 0x41, 0x5b, 0x92, 0x06, 0xa7, 0x5d, 0xdf, 0x23, + 0xbc, 0x36, 0xa1, 0x69, 0x68, 0x1b, 0x3f, 0x05, 0x74, 0xc0, 0x89, 0x9b, 0x90, 0xfd, 0xd4, 0x0f, + 0x3c, 0x43, 0x7c, 0x1e, 0xc6, 0x5d, 0xc6, 0x34, 0x15, 0x31, 0x14, 0x5e, 0xbb, 0x84, 0xc7, 0x02, + 0x5c, 0xc7, 0x56, 0x9b, 0x1a, 0xbd, 0xd5, 0xf6, 0x03, 0x22, 0x09, 0x55, 0x9d, 0xcc, 0xc6, 0xf7, + 0xa0, 0xba, 0xc7, 0xd8, 0xe1, 0x15, 0xb8, 0xb3, 0x30, 0xe6, 0x7b, 0x1a, 0x72, 0xcc, 0xf7, 0x70, + 0x13, 0xe6, 0x24, 0x93, 0x23, 0xda, 0xb9, 0xf6, 0x21, 0xb1, 0x83, 0xd1, 0x58, 0xdf, 0x85, 0x18, + 0xe2, 0x5d, 0x98, 0xef, 0xc1, 0xe8, 0x00, 0xeb, 0x5d, 0x56, 0xb6, 0x4b, 0x5d, 0x6e, 0x44, 0xe2, + 0xda, 0xd8, 0xd6, 0xf8, 0x76, 0xd9, 0x51, 0x06, 0xfe, 0x17, 0xcc, 0x34, 0xa3, 0xee, 0x09, 0x49, + 0xae, 0x12, 0x1b, 0x82, 0x92, 0x50, 0xbb, 0xb9, 0x19, 0x31, 0xc6, 0x8f, 0x60, 0xc9, 0x21, 0x31, + 0x4d, 0x79, 0x8b, 0x14, 0xa4, 0xd6, 0x80, 0x32, 0xd7, 0xf3, 0x46, 0x6f, 0xf3, 0x46, 0x6f, 0xe6, + 0x80, 0xd3, 0xdb, 0x82, 0x77, 0x61, 0x41, 0x91, 0x2f, 0xea, 0x75, 0xf2, 0x4c, 0x4c, 0x1a, 0x84, + 0x19, 0x83, 0xa0, 0x2e, 0x4e, 0x2f, 0xe2, 0xff, 0xc2, 0xe2, 0x31, 0xa7, 0x2d, 0x12, 0xc7, 0x7d, + 0xa7, 0x27, 0x0e, 0x13, 0x12, 0x9a, 0xc3, 0x73, 0xe6, 0xb0, 0xde, 0xeb, 0xa8, 0x55, 0xfc, 0xbd, + 0x05, 0xcb, 0x4a, 0x0e, 0x19, 0xaf, 0xab, 0x63, 0x70, 0xe1, 0x47, 0xe6, 0x22, 0xe4, 0x18, 0x35, + 0x61, 0x92, 0xb9, 0xdc, 0x0d, 0xc5, 0x6d, 0x08, 0x4f, 0x77, 0x1b, 0xbd, 0xea, 0xd5, 0x18, 0x0a, + 0xdd, 0x38, 0x96, 0xfb, 0x9b, 0x51, 0xc2, 0x2f, 0x1d, 0x7d, 0xb8, 0xfe, 0x1f, 0xa8, 0xe4, 0xa6, + 0xc5, 0xd5, 0x5d, 0x90, 0x4b, 0x23, 0x81, 0x0b, 0x72, 0x29, 0xae, 0xae, 0xeb, 0x06, 0xa9, 0x49, + 0x0d, 0x65, 0xec, 0x8e, 0xfd, 0xdb, 0xc2, 0xff, 0x84, 0x59, 0x59, 0x28, 0x32, 0x27, 0x43, 0x04, + 0x34, 0x24, 0xaf, 0xf0, 0x16, 0xc0, 0x7b, 0xe9, 0x19, 0x69, 0x25, 0x81, 0x38, 0x83, 0xa0, 0xe4, + 0xf2, 0x8e, 0x8a, 0x57, 0xd9, 0x91, 0x63, 0x1c, 0x40, 0xe5, 0x20, 0xf4, 0xb2, 0x98, 0xae, 0x43, + 0x99, 0x7c, 0xe5, 0x27, 0xcf, 0x5b, 0xd4, 0x23, 0x5a, 0x55, 0xd3, 0x62, 0xe2, 0x80, 0x7a, 0x52, + 0x6c, 0x84, 0x73, 0xed, 0x40, 0x0c, 0x45, 0x76, 0xc6, 0x89, 0xf7, 0x38, 0x35, 0x69, 0xab, 0x2d, + 0x3d, 0xdf, 0xe4, 0xdc, 0x64, 0xad, 0xb2, 0xf0, 0xb7, 0x16, 0x54, 0x8f, 0x68, 0xe7, 0x24, 0xe1, + 0xc4, 0x0d, 0x35, 0xa5, 0x81, 0x2b, 0x58, 0x81, 0xc9, 0x36, 0x0d, 0x02, 0xfa, 0x52, 0x7a, 0x9a, + 0x76, 0xb4, 0x85, 0x6c, 0x98, 0x88, 0xfd, 0xa8, 0xa5, 0x32, 0xb2, 0x72, 0x7f, 0xad, 0xa1, 0xde, + 0x8d, 0x86, 0x29, 0xeb, 0x8d, 0x87, 0xba, 0xfe, 0x3b, 0x6a, 0x9f, 0xc9, 0x62, 0x51, 0xae, 0x35, + 0x8f, 0xcc, 0xc6, 0xff, 0x83, 0x19, 0xa3, 0x93, 0x34, 0x1a, 0xc5, 0xa4, 0x06, 0x53, 0x2d, 0x1a, + 0x86, 0xae, 0xd4, 0x83, 0x88, 0x99, 0x31, 0xf1, 0x26, 0x94, 0xd5, 0x47, 0xbc, 0x1f, 0x77, 0xb2, + 0xbc, 0xb1, 0x64, 0xa5, 0x50, 0x79, 0xb3, 0x0f, 0x8b, 0x0e, 0x09, 0x88, 0x1b, 0x17, 0xd3, 0xe6, + 0xaf, 0xa2, 0x9e, 0xcb, 0xe9, 0x01, 0xd9, 0xea, 0xed, 0x4e, 0xb6, 0x01, 0x9f, 0xc0, 0x92, 0x51, + 0x97, 0x5a, 0xd2, 0xba, 0xbd, 0x0d, 0x13, 0x32, 0x33, 0xa4, 0xc3, 0x81, 0xac, 0x51, 0x6b, 0x22, + 0x8a, 0x1e, 0x0d, 0x5d, 0xdf, 0xd4, 0x36, 0x6d, 0xdd, 0xff, 0x75, 0x11, 0xe6, 0x8e, 0x75, 0xa5, + 0x3c, 0x51, 0x8f, 0x32, 0xfa, 0x00, 0x4a, 0xa2, 0x6c, 0xa3, 0xd5, 0xbc, 0xb0, 0x73, 0x45, 0xbf, + 0x5e, 0x1b, 0x5c, 0x50, 0x1f, 0x84, 0x17, 0xbf, 0xfe, 0xf1, 0x97, 0xef, 0xc6, 0x66, 0xf0, 0xb4, + 0xdd, 0xfd, 0x9b, 0xed, 0xa6, 0xc9, 0xf9, 0xae, 0xb5, 0x83, 0x3e, 0x84, 0x29, 0xfd, 0x34, 0x15, + 0x21, 0x73, 0x2f, 0x65, 0x7d, 0xbd, 0x00, 0x59, 0x7c, 0xc8, 0xf0, 0xbc, 0x44, 0x05, 0xa4, 0x50, + 0xc5, 0x9b, 0xd5, 0x84, 0xf2, 0x1e, 0x63, 0x2a, 0x1c, 0x68, 0xa5, 0xef, 0xac, 0xc1, 0xcc, 0xbf, + 0x78, 0x7d, 0xcc, 0x18, 0x8b, 0x05, 0xb3, 0x47, 0x30, 0xb9, 0xc7, 0xd8, 0xff, 0x49, 0x72, 0x3d, + 0x8c, 0x55, 0x89, 0xb1, 0x80, 0xe6, 0x0c, 0x86, 0xfd, 0x4a, 0x08, 0xe3, 0x35, 0x7a, 0xa6, 0x5f, + 0xee, 0x38, 0x71, 0xf9, 0x68, 0xac, 0x95, 0x01, 0x89, 0x36, 0x45, 0xff, 0x82, 0x37, 0x25, 0xec, + 0x1a, 0x5a, 0xed, 0x83, 0xb5, 0xb9, 0x06, 0xfc, 0x48, 0x7e, 0xed, 0x43, 0x12, 0x90, 0x2b, 0xbe, + 0x76, 0x14, 0xba, 0x26, 0xbd, 0x33, 0x40, 0xfa, 0x18, 0x2a, 0x52, 0x22, 0x3a, 0x8a, 0x1b, 0x83, + 0x65, 0x2c, 0xff, 0x60, 0xd6, 0x8b, 0xba, 0xc2, 0x48, 0xc2, 0x56, 0x31, 0x08, 0x58, 0x55, 0x99, + 0xd1, 0x27, 0x1a, 0xf1, 0x30, 0x64, 0x94, 0x27, 0x68, 0x39, 0x8f, 0x98, 0xe5, 0xc7, 0x48, 0xa2, + 0x6b, 0x12, 0x71, 0x11, 0x2f, 0xf4, 0x10, 0x6d, 0x5f, 0x22, 0x6d, 0x5b, 0xe8, 0x14, 0xaa, 0x9a, + 0x8d, 0xd4, 0x3e, 0xda, 0x1a, 0x56, 0x74, 0xf3, 0x69, 0x51, 0xef, 0xcf, 0x24, 0xbc, 0x24, 0xf1, + 0x67, 0x71, 0x55, 0xe0, 0x9b, 0xac, 0x42, 0x47, 0x30, 0x2d, 0x81, 0x85, 0x08, 0x6a, 0x7d, 0xa1, + 0x3d, 0x1c, 0xf5, 0xf1, 0x05, 0x21, 0x68, 0xaa, 0xaf, 0x7c, 0xef, 0x35, 0xfa, 0x58, 0x47, 0x40, + 0xdf, 0xd5, 0x68, 0xc0, 0x6b, 0xdd, 0x56, 0x1e, 0xf9, 0x19, 0x94, 0xb3, 0x17, 0x73, 0xa4, 0x06, + 0x6e, 0xe6, 0xe7, 0x07, 0x1e, 0x58, 0x13, 0x61, 0x94, 0x8f, 0xb0, 0x16, 0x83, 0x67, 0xe0, 0x69, + 0x27, 0x46, 0xeb, 0x83, 0x30, 0x59, 0xaf, 0x52, 0xbf, 0x31, 0x7c, 0x51, 0xbb, 0xb8, 0x21, 0x5d, + 0xac, 0xa0, 0xa5, 0x3e, 0xfe, 0x76, 0x20, 0x80, 0x4f, 0x60, 0xb6, 0x19, 0x75, 0x7d, 0x4e, 0xa3, + 0x90, 0x44, 0xc9, 0x55, 0x79, 0xb7, 0x60, 0x02, 0xde, 0x8c, 0xba, 0x07, 0x34, 0x6a, 0xfb, 0x1d, + 0xbc, 0x22, 0xa1, 0xe7, 0xd1, 0xac, 0x80, 0x26, 0x51, 0xd7, 0x50, 0xff, 0xac, 0x00, 0x7a, 0x42, + 0x12, 0xb4, 0x96, 0x07, 0x2d, 0x34, 0x3a, 0x23, 0xe3, 0xae, 0xc1, 0x71, 0x3f, 0x78, 0x0b, 0x2a, + 0xb9, 0xc2, 0x3d, 0x92, 0xee, 0x66, 0x7e, 0x7e, 0x48, 0xa5, 0xc7, 0xeb, 0x12, 0x7f, 0x19, 0x2d, + 0xe6, 0xc5, 0x67, 0x9c, 0x9c, 0xc1, 0x8c, 0x3e, 0xf3, 0xce, 0xba, 0xd1, 0x35, 0x64, 0x67, 0xb5, + 0x88, 0xef, 0x32, 0xf6, 0x5a, 0xe9, 0xe7, 0x05, 0x54, 0xf3, 0x9d, 0xdb, 0xe8, 0x4a, 0xbc, 0x55, + 0xfc, 0x94, 0xc1, 0x66, 0x0f, 0x2f, 0x4b, 0x5f, 0x73, 0x68, 0x46, 0xf9, 0xd2, 0x3d, 0x1d, 0x7a, + 0x22, 0x42, 0xa5, 0x8c, 0xab, 0x6e, 0x76, 0xa0, 0x2f, 0x2c, 0x6a, 0x26, 0xc3, 0x33, 0xc1, 0x79, + 0x0a, 0xb3, 0x66, 0xa7, 0xae, 0x54, 0xb7, 0x7e, 0xb7, 0xe1, 0x1a, 0xe2, 0x44, 0x93, 0xc6, 0x7d, + 0xa4, 0x3f, 0xef, 0xa1, 0xbf, 0x63, 0x7d, 0xd5, 0xec, 0x77, 0x86, 0xb3, 0x27, 0xf9, 0xb0, 0x47, + 0x17, 0xa8, 0x3e, 0x80, 0x9e, 0x11, 0x1f, 0x42, 0xfa, 0x2f, 0x12, 0xfb, 0x56, 0x7d, 0x73, 0x18, + 0xb6, 0x1d, 0xf8, 0xd1, 0x85, 0xba, 0x62, 0xe4, 0xf7, 0x3e, 0xe3, 0x49, 0x14, 0xfc, 0x71, 0x47, + 0x77, 0xa4, 0xa3, 0xdb, 0xf5, 0x5b, 0x43, 0x1d, 0xa5, 0x51, 0xce, 0xd5, 0x63, 0x98, 0xd2, 0x4d, + 0x64, 0x31, 0x54, 0xbd, 0xce, 0xb2, 0x5e, 0xd0, 0x56, 0xae, 0x9f, 0x34, 0x8f, 0x30, 0xaa, 0x08, + 0x37, 0x17, 0x1a, 0xe5, 0x14, 0xa0, 0xd7, 0x7b, 0x8d, 0x7a, 0x34, 0x86, 0x4f, 0x17, 0x0b, 0x31, + 0x53, 0x28, 0x36, 0x4f, 0xa3, 0x6d, 0xeb, 0x9e, 0x85, 0x5e, 0x40, 0x25, 0xf7, 0x43, 0xe1, 0x7a, + 0xb9, 0x3b, 0xe4, 0x97, 0x05, 0xae, 0x49, 0x27, 0x08, 0xcd, 0xe7, 0x9d, 0x04, 0x02, 0xf2, 0x34, + 0xf3, 0x70, 0xe2, 0x76, 0x09, 0xca, 0x8a, 0xd6, 0x23, 0xca, 0x43, 0xd9, 0x7b, 0x8e, 0x54, 0xcd, + 0x4d, 0x89, 0xb9, 0x8a, 0x96, 0xf3, 0x98, 0x6d, 0x73, 0x0c, 0x9d, 0x42, 0x39, 0x6b, 0x8c, 0x8b, + 0xd5, 0x20, 0xdf, 0x2f, 0x5f, 0x2b, 0x2a, 0xa2, 0xf0, 0xea, 0x8b, 0xbc, 0x67, 0xed, 0xd7, 0xde, + 0xbc, 0xdd, 0xb0, 0x7e, 0x78, 0xbb, 0x61, 0xfd, 0xfc, 0x76, 0xc3, 0xfa, 0x34, 0xf7, 0x7f, 0x97, + 0xb3, 0x49, 0xc9, 0xf0, 0xef, 0xbf, 0x05, 0x00, 0x00, 0xff, 0xff, 0x67, 0xc1, 0x55, 0x4a, 0x9c, + 0x11, 0x00, 0x00, } diff --git a/api/controller/services.pb.gw.go b/api/controller/services.pb.gw.go index cc913241..fd17f66c 100644 --- a/api/controller/services.pb.gw.go +++ b/api/controller/services.pb.gw.go @@ -392,49 +392,6 @@ func request_ProviderService_BuildLogs_0(ctx context.Context, marshaler runtime. } -var ( - filter_ProviderService_BuildLogsStream_0 = &utilities.DoubleArray{Encoding: map[string]int{"id": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} -) - -func request_ProviderService_BuildLogsStream_0(ctx context.Context, marshaler runtime.Marshaler, client ProviderServiceClient, req *http.Request, pathParams map[string]string) (ProviderService_BuildLogsStreamClient, runtime.ServerMetadata, error) { - var protoReq BuildLogStreamReq - var metadata runtime.ServerMetadata - - var ( - val string - ok bool - err error - _ = err - ) - - val, ok = pathParams["id"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") - } - - protoReq.Id, err = runtime.String(val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) - } - - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ProviderService_BuildLogsStream_0); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - - stream, err := client.BuildLogsStream(ctx, &protoReq) - if err != nil { - return nil, metadata, err - } - header, err := stream.Header() - if err != nil { - return nil, metadata, err - } - metadata.HeaderMD = header - return stream, metadata, nil - -} - var ( filter_ProviderService_EnvironmentGet_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} ) @@ -1319,35 +1276,6 @@ func RegisterProviderServiceHandlerClient(ctx context.Context, mux *runtime.Serv }) - mux.Handle("GET", pattern_ProviderService_BuildLogsStream_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - if cn, ok := w.(http.CloseNotifier); ok { - go func(done <-chan struct{}, closed <-chan bool) { - select { - case <-done: - case <-closed: - cancel() - } - }(ctx.Done(), cn.CloseNotify()) - } - inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateContext(ctx, mux, req) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - resp, md, err := request_ProviderService_BuildLogsStream_0(rctx, inboundMarshaler, client, req, pathParams) - ctx = runtime.NewServerMetadataContext(ctx, md) - if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) - return - } - - forward_ProviderService_BuildLogsStream_0(ctx, mux, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...) - - }) - mux.Handle("GET", pattern_ProviderService_EnvironmentGet_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -1813,8 +1741,6 @@ var ( pattern_ProviderService_BuildLogs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v1", "builds", "id", "logs"}, "")) - pattern_ProviderService_BuildLogsStream_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 2, 4}, []string{"v1", "builds", "id", "logs", "stream"}, "")) - pattern_ProviderService_EnvironmentGet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "env", "name"}, "")) pattern_ProviderService_EnvironmentSet_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "env", "name"}, "")) @@ -1873,8 +1799,6 @@ var ( forward_ProviderService_BuildLogs_0 = runtime.ForwardResponseMessage - forward_ProviderService_BuildLogsStream_0 = runtime.ForwardResponseStream - forward_ProviderService_EnvironmentGet_0 = runtime.ForwardResponseMessage forward_ProviderService_EnvironmentSet_0 = runtime.ForwardResponseMessage diff --git a/api/controller/services.proto b/api/controller/services.proto index 0fc245b1..95ae5dce 100644 --- a/api/controller/services.proto +++ b/api/controller/services.proto @@ -37,6 +37,7 @@ message AuthResponse { string name = 2; string project = 3; string region = 4; + string provider = 5; } message CreateBuildRequest { @@ -100,12 +101,6 @@ message CmdResponse { string stdErr = 4; } -message BuildLogStreamReq { - string id = 1; - bool follow = 2; - google.protobuf.Duration since = 3; -} - message LogStreamReq { string name = 1; bool follow = 2; @@ -214,12 +209,6 @@ service ProviderService { }; } - rpc BuildLogsStream(BuildLogStreamReq) returns (stream StreamMsg) { - option (google.api.http) = { - get: "/v1/builds/{id}/logs/stream" - }; - } - rpc EnvironmentGet(AppRequest) returns (models.EnvConfig) { option (google.api.http) = { get: "/v1/env/{name}" diff --git a/api/controller/services.swagger.json b/api/controller/services.swagger.json index 3daedc1d..bae34437 100644 --- a/api/controller/services.swagger.json +++ b/api/controller/services.swagger.json @@ -302,53 +302,6 @@ ] } }, - "/v1/builds/{id}/logs/stream": { - "get": { - "operationId": "BuildLogsStream", - "responses": { - "200": { - "description": "(streaming responses)", - "schema": { - "$ref": "#/definitions/controllerStreamMsg" - } - } - }, - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "type": "string" - }, - { - "name": "follow", - "in": "query", - "required": false, - "type": "boolean", - "format": "boolean" - }, - { - "name": "since.seconds", - "description": "Signed seconds of the span of time. Must be from -315,576,000,000\nto +315,576,000,000 inclusive.", - "in": "query", - "required": false, - "type": "string", - "format": "int64" - }, - { - "name": "since.nanos", - "description": "Signed fractions of a second at nanosecond resolution of the span\nof time. Durations less than one second are represented with a 0\n`seconds` field and a positive or negative `nanos` field. For durations\nof one second or more, a non-zero value for the `nanos` field must be\nof the same sign as the `seconds` field. Must be from -999,999,999\nto +999,999,999 inclusive.", - "in": "query", - "required": false, - "type": "integer", - "format": "int32" - } - ], - "tags": [ - "ProviderService" - ] - } - }, "/v1/builds/{name}": { "get": { "operationId": "BuildList", @@ -883,6 +836,9 @@ }, "region": { "type": "string" + }, + "provider": { + "type": "string" } } }, @@ -1026,6 +982,9 @@ "procfile": { "type": "string", "format": "byte" + }, + "version": { + "type": "string" } } }, diff --git a/api/helper.go b/api/helper.go index cde0c2a9..510d40b9 100644 --- a/api/helper.go +++ b/api/helper.go @@ -6,10 +6,14 @@ import ( "errors" "fmt" "io" + "net/http" + "os" "strings" + "sync" log "github.com/Sirupsen/logrus" "golang.org/x/net/context" + "golang.org/x/net/websocket" "google.golang.org/grpc/metadata" ) @@ -67,6 +71,36 @@ func checkHttpAuthorization(value, expected string) bool { return pair[1] == expected } +func checkAPIKey(r *http.Request) bool { + if os.Getenv("DATACOL_API_KEY") == "" { + return true + } + + auth := r.Header.Get("Authorization") + + if auth == "" { + return false + } + + if !strings.HasPrefix(auth, "Basic ") { + return false + } + + c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic ")) + + if err != nil { + return false + } + + parts := strings.SplitN(string(c), ":", 2) + + if len(parts) != 2 || parts[1] != os.Getenv("DATACOL_API_KEY") { + return false + } + + return true +} + func writeToFd(fd io.Writer, data []byte) error { w := 0 n := len(data) @@ -81,3 +115,27 @@ func writeToFd(fd io.Writer, data []byte) error { } } } + +type websocketFunc func(*websocket.Conn) error + +func ws(at string, handler websocketFunc) websocket.Handler { + return websocket.Handler(func(ws *websocket.Conn) { + if !checkAPIKey(ws.Request()) { + ws.Write([]byte("Unable to authenticate. Invalid ApiKey.")) + return + } + + err := handler(ws) + + if err != nil { + log.Error(err) + ws.Write([]byte(fmt.Sprintf("ERROR: %v\n", err))) + return + } + }) +} + +func copyAsync(dst io.Writer, src io.Reader, wg *sync.WaitGroup) { + defer wg.Done() + io.Copy(dst, src) +} diff --git a/api/logs.go b/api/logs.go index f96bcc2e..42980934 100644 --- a/api/logs.go +++ b/api/logs.go @@ -1,22 +1,48 @@ package main import ( + "fmt" + "time" + "github.com/appscode/go/log" pbs "github.com/datacol-io/datacol/api/controller" pb "github.com/datacol-io/datacol/api/models" - "github.com/golang/protobuf/ptypes" + "golang.org/x/net/websocket" ) func (s *Server) LogStream(req *pbs.LogStreamReq, stream pbs.ProviderService_LogStreamServer) error { - since, err := ptypes.Duration(req.Since) - if err != nil { - return err + return fmt.Errorf("Logging over GRPC has been deprecated. Please use use websocket based Path") +} + +func (s *Server) LogStreamWs(ws *websocket.Conn) error { + headers := ws.Request().Header + app := headers.Get("app") + + if app == "" { + return fmt.Errorf("Missing require param: app") + } + + var ( + since time.Duration + follow bool + ) + + if raw := headers.Get("since"); raw != "" { + duration, err := time.ParseDuration(raw) + if err != nil { + return err + } + since = duration + } + + if raw := headers.Get("follow"); raw != "" { + follow = raw == "true" } - return s.Provider.LogStream(req.Name, logStreamW{stream}, pb.LogStreamOptions{ - Follow: req.Follow, + return s.Provider.LogStream(app, ws, pb.LogStreamOptions{ Since: since, - Proctype: req.Proctype, + Follow: follow, + Proctype: headers.Get("process"), }) } diff --git a/api/main.go b/api/main.go index b2c1adab..6affaa91 100644 --- a/api/main.go +++ b/api/main.go @@ -28,17 +28,15 @@ func init() { flag.IntVar(&timeout, "timeout", 2, "wait timeout for rpc proxy") } -func runRpcServer() error { +func runRpcServer(server *Server) { go func() { - if err := newServer().Run(); err != nil { + if err := server.Run(); err != nil { log.Fatalf("serveGRPC err: %v", err) } }() - - return nil } -func run() error { +func runHttpServer(server *Server) error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -51,7 +49,15 @@ func run() error { } mux := http.NewServeMux() + + // Implementing bidi-streaming using GRPC is a lot of headache, + // it's better to use websockets protocols for streaming logs and interactive one-off commands + mux.Handle("/ws/v1/logs", ws("appLogs", server.LogStreamWs)) + mux.Handle("/ws/v1/exec", ws("processExec", server.ProcessRunWs)) + mux.Handle("/ws/v1/proxy", ws("appProxy", server.ResourceProxy)) + mux.Handle("/ws/v1/builds/logs", ws("buildLogs", server.BuildLogStreamReq)) mux.Handle("/", gwmux) + fmt.Printf("Starting server on http=%d and grpc=%d ports\n", port, rpcPort) return http.ListenAndServe(fmt.Sprintf(":%d", port), mux) } @@ -78,14 +84,12 @@ func setupLogging() { func main() { setupLogging() - - if err := runRpcServer(); err != nil { - log.Fatal(err) - } + server := newServer() + runRpcServer(server) time.Sleep(time.Duration(timeout) * time.Second) - if err := run(); err != nil { + if err := runHttpServer(server); err != nil { log.Fatal(err) } } diff --git a/api/models/extra.go b/api/models/extra.go index a7857a97..55469be2 100644 --- a/api/models/extra.go +++ b/api/models/extra.go @@ -42,6 +42,7 @@ type ReleaseOptions struct { Env string Wait bool Domain string + Expose bool // should be expose the service to the public } type LogStreamOptions struct { diff --git a/api/models/types.pb.go b/api/models/types.pb.go index eebd3adb..9c67a5e5 100644 --- a/api/models/types.pb.go +++ b/api/models/types.pb.go @@ -109,6 +109,7 @@ type Build struct { Status string `protobuf:"bytes,4,opt,name=status,proto3" json:"status,omitempty" datastore:"status,noindex"` CreatedAt int32 `protobuf:"varint,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty" datastore:"created_at,noindex"` Procfile []byte `protobuf:"bytes,6,opt,name=procfile,proto3" json:"procfile,omitempty" datastore:"procfile,noindex"` + Version string `protobuf:"bytes,7,opt,name=version,proto3" json:"version,omitempty" datastore:"version,noindex"` } func (m *Build) Reset() { *m = Build{} } @@ -158,6 +159,13 @@ func (m *Build) GetProcfile() []byte { return nil } +func (m *Build) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + type Release struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" datastore:"id"` App string `protobuf:"bytes,2,opt,name=app,proto3" json:"app,omitempty" datastore:"app"` @@ -544,6 +552,12 @@ func (m *Build) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintTypes(dAtA, i, uint64(len(m.Procfile))) i += copy(dAtA[i:], m.Procfile) } + if len(m.Version) > 0 { + dAtA[i] = 0x3a + i++ + i = encodeVarintTypes(dAtA, i, uint64(len(m.Version))) + i += copy(dAtA[i:], m.Version) + } return i, nil } @@ -950,6 +964,10 @@ func (m *Build) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } + l = len(m.Version) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } return n } @@ -1574,6 +1592,35 @@ func (m *Build) Unmarshal(dAtA []byte) error { m.Procfile = []byte{} } iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Version = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) @@ -3231,63 +3278,64 @@ var ( func init() { proto.RegisterFile("types.proto", fileDescriptorTypes) } var fileDescriptorTypes = []byte{ - // 918 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x56, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0x96, 0xe3, 0x24, 0xae, 0x4f, 0xbb, 0x3f, 0x0c, 0xd5, 0x62, 0xa5, 0xdd, 0x38, 0x6b, 0x69, - 0x45, 0x2e, 0x68, 0x82, 0x40, 0xfc, 0x95, 0x05, 0xd4, 0x40, 0x91, 0xba, 0x5a, 0x89, 0x95, 0x61, - 0x11, 0xe2, 0xa6, 0x9a, 0xc6, 0xd3, 0x60, 0x25, 0xf1, 0x58, 0x33, 0xe3, 0x65, 0xfb, 0x0e, 0xbc, - 0x00, 0xe2, 0x11, 0x78, 0x11, 0x24, 0x6e, 0x78, 0x02, 0x0b, 0xf5, 0x11, 0xfc, 0x04, 0x68, 0x7e, - 0x1c, 0x4f, 0x9a, 0x5d, 0x41, 0x24, 0xa4, 0xbd, 0xcb, 0x9c, 0xf3, 0x7d, 0x9f, 0xcf, 0x7c, 0xe7, - 0xf8, 0xc4, 0xb0, 0x2b, 0xae, 0x72, 0xc2, 0x47, 0x39, 0xa3, 0x82, 0xa2, 0xee, 0x92, 0x26, 0x64, - 0xc1, 0x7b, 0x87, 0x33, 0x4a, 0x67, 0x0b, 0x32, 0xc6, 0x79, 0x3a, 0xc6, 0x59, 0x46, 0x05, 0x16, - 0x29, 0xcd, 0x0c, 0xaa, 0x77, 0x34, 0x4b, 0xc5, 0x4f, 0xc5, 0xc5, 0x68, 0x4a, 0x97, 0xe3, 0x19, - 0x9d, 0xd1, 0xb1, 0x0a, 0x5f, 0x14, 0x97, 0xea, 0xa4, 0x0e, 0xea, 0x97, 0x86, 0x47, 0xbf, 0xba, - 0xe0, 0x9e, 0xe4, 0x39, 0x1a, 0x42, 0x3b, 0xc3, 0x4b, 0x12, 0x38, 0x03, 0x67, 0xe8, 0x4f, 0xf6, - 0xab, 0x32, 0xbc, 0x9b, 0x60, 0x81, 0xb9, 0xa0, 0x8c, 0x1c, 0x47, 0x32, 0x15, 0xc5, 0x0a, 0x81, - 0x46, 0xd0, 0xe5, 0x02, 0x8b, 0x82, 0x07, 0x2d, 0x85, 0xbd, 0x57, 0x95, 0x21, 0xb2, 0xb0, 0x3a, - 0x19, 0xc5, 0x06, 0x85, 0x4e, 0x00, 0x18, 0x59, 0x10, 0xcc, 0xc9, 0x79, 0x9a, 0x04, 0xae, 0xe2, - 0x44, 0x55, 0x19, 0xf6, 0x2d, 0x4e, 0x03, 0x78, 0x27, 0xa3, 0x69, 0x96, 0x90, 0x17, 0x51, 0xec, - 0x9b, 0xe0, 0x59, 0x82, 0x1e, 0xc1, 0x0e, 0xc9, 0x92, 0x9c, 0xa6, 0x99, 0x08, 0xda, 0x4a, 0x60, - 0x50, 0x95, 0xe1, 0xa1, 0x25, 0x50, 0xa7, 0x1b, 0xfa, 0x8a, 0x81, 0x3e, 0x85, 0x9d, 0x8b, 0x22, - 0x5d, 0x24, 0xf2, 0xf1, 0x9d, 0x97, 0xb2, 0xeb, 0x74, 0xc3, 0xf6, 0x54, 0xe8, 0x2c, 0x41, 0x9f, - 0x80, 0x97, 0xd0, 0x25, 0x4e, 0x33, 0x1e, 0x74, 0x07, 0xee, 0xd0, 0x9f, 0x84, 0x55, 0x19, 0x1e, - 0x58, 0x5c, 0x93, 0xb5, 0xa8, 0x26, 0x82, 0xbe, 0x00, 0x9f, 0x11, 0x4e, 0x0b, 0x36, 0x25, 0x3c, - 0xf0, 0x14, 0xf9, 0x41, 0x55, 0x86, 0xf7, 0xd7, 0xee, 0x6d, 0xf2, 0x6b, 0xd7, 0x36, 0xb1, 0xe8, - 0xcf, 0x16, 0x74, 0x26, 0xb2, 0x0e, 0x14, 0x41, 0x2b, 0x4d, 0x4c, 0x6f, 0x50, 0x55, 0x86, 0xb7, - 0x2d, 0x8d, 0x34, 0x89, 0xe2, 0x56, 0x9a, 0xa0, 0x87, 0xe0, 0xe2, 0x3c, 0x37, 0x4d, 0x79, 0xb3, - 0x2a, 0xc3, 0x3b, 0x16, 0x08, 0xe7, 0x79, 0x14, 0xcb, 0x3c, 0xfa, 0x5c, 0x56, 0xb5, 0xa4, 0xc2, - 0xea, 0xc6, 0x66, 0x55, 0x26, 0x6f, 0xb9, 0xa9, 0x63, 0x67, 0x09, 0xfa, 0x70, 0xd5, 0x7e, 0xdd, - 0x89, 0x7e, 0x55, 0x86, 0xbd, 0x8d, 0xf6, 0x37, 0x4c, 0x6b, 0x0c, 0xa6, 0x8c, 0x60, 0x41, 0x92, - 0x73, 0x2c, 0x54, 0x1f, 0x3a, 0x1b, 0x63, 0xd0, 0x00, 0x2c, 0x3f, 0x4c, 0xf0, 0x44, 0xc8, 0x31, - 0xc8, 0x19, 0x9d, 0x5e, 0xa6, 0x0b, 0x12, 0x74, 0x07, 0xce, 0x70, 0x6f, 0xa3, 0x91, 0x75, 0xda, - 0x2a, 0xbc, 0x0e, 0x45, 0xbf, 0xb4, 0xc0, 0x8b, 0xf5, 0x48, 0xfd, 0x9f, 0x7e, 0xda, 0xd3, 0xe5, - 0x6e, 0x3b, 0x5d, 0xaf, 0xcf, 0xcc, 0xe8, 0x77, 0x0f, 0x76, 0x62, 0x33, 0x6a, 0x5b, 0xbc, 0xfd, - 0x43, 0x68, 0xcf, 0xd3, 0x2c, 0x31, 0xb6, 0xdc, 0x44, 0xca, 0x54, 0x14, 0x2b, 0x84, 0x75, 0x37, - 0x77, 0xab, 0xbb, 0x3d, 0x86, 0x5b, 0xfa, 0xd7, 0x39, 0x23, 0x98, 0xd3, 0xcc, 0x58, 0xf3, 0xb0, - 0x2a, 0xc3, 0x07, 0x1b, 0x74, 0x83, 0x69, 0x54, 0xf6, 0x74, 0x3c, 0x56, 0x61, 0x74, 0x04, 0xee, - 0xb3, 0xf8, 0x89, 0x79, 0xeb, 0x0f, 0xaa, 0x32, 0x7c, 0xcb, 0x52, 0x78, 0x16, 0x3f, 0x69, 0x78, - 0x12, 0x87, 0xde, 0x86, 0x0e, 0x17, 0x78, 0x3a, 0x57, 0xd3, 0xe5, 0x4f, 0xde, 0xa8, 0xca, 0xf0, - 0x96, 0x45, 0x38, 0x8a, 0x62, 0x9d, 0x47, 0xef, 0x42, 0x1b, 0xe7, 0x79, 0xfd, 0x56, 0x1f, 0x56, - 0x65, 0x18, 0xac, 0x0f, 0x87, 0x75, 0x2f, 0x85, 0x44, 0x3f, 0x80, 0x47, 0x5e, 0xe4, 0x94, 0x09, - 0x1e, 0xec, 0x0c, 0xdc, 0xe1, 0xee, 0x7b, 0xf7, 0x47, 0x7a, 0x9d, 0x8f, 0xea, 0x26, 0x8c, 0x4e, - 0x75, 0xfe, 0x34, 0x13, 0xec, 0x6a, 0x63, 0xcd, 0x18, 0xb2, 0x35, 0x43, 0x26, 0x82, 0x12, 0x80, - 0x1c, 0x33, 0xbc, 0x24, 0x82, 0x30, 0x1e, 0xf8, 0x4a, 0x7c, 0xb0, 0x21, 0xfe, 0x74, 0x05, 0xd1, - 0xfa, 0x37, 0xa7, 0xa5, 0x91, 0x68, 0x1e, 0x61, 0xe9, 0xa2, 0xc7, 0xe0, 0xd1, 0x42, 0xe4, 0x85, - 0xe0, 0x01, 0xbc, 0xa2, 0xfe, 0x6f, 0x74, 0x5e, 0xeb, 0xbf, 0xc4, 0xbb, 0x5a, 0x00, 0x9d, 0x40, - 0x5b, 0xe0, 0x19, 0x0f, 0x76, 0x95, 0x50, 0x6f, 0x43, 0xe8, 0x3b, 0x3c, 0x7b, 0xb5, 0x8a, 0xa2, - 0xf6, 0x8e, 0x61, 0xcf, 0xb6, 0x0b, 0xdd, 0x05, 0x77, 0x4e, 0xae, 0xf4, 0xfc, 0xc6, 0xf2, 0x27, - 0xda, 0x87, 0xce, 0x73, 0xbc, 0x28, 0x88, 0x9e, 0xd4, 0x58, 0x1f, 0x8e, 0x5b, 0x1f, 0x3b, 0xbd, - 0xcf, 0xe0, 0xce, 0x0d, 0x37, 0xb6, 0xa2, 0x1f, 0xc3, 0x9e, 0x7d, 0xd3, 0xad, 0xb8, 0x1f, 0x81, - 0xbf, 0xba, 0xdc, 0x36, 0xc4, 0xe8, 0x03, 0xd8, 0xad, 0xed, 0xf9, 0x1e, 0xb3, 0xff, 0x4a, 0x8d, - 0x0a, 0xf0, 0x4f, 0xb3, 0xe7, 0x5f, 0xd2, 0xec, 0x32, 0x9d, 0xa1, 0x31, 0xb4, 0xa5, 0x95, 0x81, - 0xa3, 0x6c, 0x3f, 0xa8, 0x6d, 0x5f, 0x01, 0x46, 0x5f, 0x61, 0x81, 0x55, 0x69, 0xb1, 0x02, 0xca, - 0x6a, 0x57, 0xa1, 0xad, 0xaa, 0x9d, 0x83, 0xf7, 0x94, 0xd1, 0x29, 0xe1, 0x1c, 0x21, 0x7b, 0xb3, - 0x98, 0x1d, 0xd2, 0xd3, 0x7b, 0x5c, 0x7e, 0xdb, 0x18, 0xee, 0xea, 0x8c, 0x02, 0xf0, 0x7e, 0xa6, - 0x6c, 0x2e, 0x47, 0x59, 0xae, 0x8d, 0x4e, 0x5c, 0x1f, 0xd1, 0xbd, 0xf5, 0x5d, 0x59, 0xef, 0x8b, - 0xe8, 0x37, 0x07, 0xfc, 0xaf, 0x29, 0x5b, 0xaa, 0xaf, 0x20, 0x59, 0xa6, 0xdc, 0xda, 0xa6, 0x4c, - 0xf3, 0x87, 0xc7, 0x05, 0x2b, 0xa6, 0xa2, 0x60, 0xf2, 0x71, 0x6b, 0xaf, 0xc7, 0x8a, 0x37, 0xfa, - 0xb6, 0x86, 0x68, 0x03, 0x1a, 0x4a, 0xef, 0x11, 0xdc, 0x5e, 0x4f, 0xfe, 0x9b, 0x15, 0x1d, 0xcb, - 0x8a, 0xc9, 0xfe, 0x1f, 0xd7, 0x7d, 0xe7, 0xaf, 0xeb, 0xbe, 0xf3, 0xf7, 0x75, 0xdf, 0xf9, 0xd1, - 0x7c, 0xc2, 0x5d, 0x74, 0xd5, 0xc7, 0xd7, 0xfb, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xe6, 0xa1, - 0xcf, 0x78, 0xe0, 0x09, 0x00, 0x00, + // 938 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x56, 0xdf, 0x6e, 0xe3, 0xc4, + 0x17, 0x96, 0xe3, 0x24, 0xae, 0x4f, 0xbb, 0x7f, 0x7e, 0xf3, 0xab, 0x16, 0x2b, 0xed, 0xc6, 0x59, + 0x4b, 0x2b, 0x72, 0x41, 0x13, 0x04, 0xe2, 0x5f, 0x59, 0x40, 0x0d, 0x14, 0xa9, 0xab, 0x95, 0x58, + 0x19, 0x16, 0x21, 0x6e, 0xaa, 0x69, 0x3c, 0x0d, 0x56, 0x12, 0x8f, 0x35, 0x33, 0x2e, 0xdb, 0x17, + 0xe0, 0x8a, 0x17, 0x40, 0x3c, 0x02, 0x2f, 0xc2, 0x25, 0x4f, 0x60, 0xa1, 0x3e, 0x82, 0x9f, 0x00, + 0xcd, 0x1f, 0xc7, 0x93, 0x66, 0x57, 0x10, 0x09, 0x89, 0xbb, 0xcc, 0x39, 0xdf, 0xf7, 0xf9, 0xcc, + 0x39, 0x5f, 0x8e, 0x0d, 0xbb, 0xe2, 0x3a, 0x27, 0x7c, 0x94, 0x33, 0x2a, 0x28, 0xea, 0x2e, 0x69, + 0x42, 0x16, 0xbc, 0x77, 0x38, 0xa3, 0x74, 0xb6, 0x20, 0x63, 0x9c, 0xa7, 0x63, 0x9c, 0x65, 0x54, + 0x60, 0x91, 0xd2, 0xcc, 0xa0, 0x7a, 0x47, 0xb3, 0x54, 0xfc, 0x50, 0x5c, 0x8c, 0xa6, 0x74, 0x39, + 0x9e, 0xd1, 0x19, 0x1d, 0xab, 0xf0, 0x45, 0x71, 0xa9, 0x4e, 0xea, 0xa0, 0x7e, 0x69, 0x78, 0xf4, + 0x8b, 0x0b, 0xee, 0x49, 0x9e, 0xa3, 0x21, 0xb4, 0x33, 0xbc, 0x24, 0x81, 0x33, 0x70, 0x86, 0xfe, + 0x64, 0xbf, 0x2a, 0xc3, 0xfb, 0x09, 0x16, 0x98, 0x0b, 0xca, 0xc8, 0x71, 0x24, 0x53, 0x51, 0xac, + 0x10, 0x68, 0x04, 0x5d, 0x2e, 0xb0, 0x28, 0x78, 0xd0, 0x52, 0xd8, 0x07, 0x55, 0x19, 0x22, 0x0b, + 0xab, 0x93, 0x51, 0x6c, 0x50, 0xe8, 0x04, 0x80, 0x91, 0x05, 0xc1, 0x9c, 0x9c, 0xa7, 0x49, 0xe0, + 0x2a, 0x4e, 0x54, 0x95, 0x61, 0xdf, 0xe2, 0x34, 0x80, 0xb7, 0x32, 0x9a, 0x66, 0x09, 0x79, 0x19, + 0xc5, 0xbe, 0x09, 0x9e, 0x25, 0xe8, 0x09, 0xec, 0x90, 0x2c, 0xc9, 0x69, 0x9a, 0x89, 0xa0, 0xad, + 0x04, 0x06, 0x55, 0x19, 0x1e, 0x5a, 0x02, 0x75, 0xba, 0xa1, 0xaf, 0x18, 0xe8, 0x63, 0xd8, 0xb9, + 0x28, 0xd2, 0x45, 0x22, 0x1f, 0xdf, 0x79, 0x25, 0xbb, 0x4e, 0x37, 0x6c, 0x4f, 0x85, 0xce, 0x12, + 0xf4, 0x11, 0x78, 0x09, 0x5d, 0xe2, 0x34, 0xe3, 0x41, 0x77, 0xe0, 0x0e, 0xfd, 0x49, 0x58, 0x95, + 0xe1, 0x81, 0xc5, 0x35, 0x59, 0x8b, 0x6a, 0x22, 0xe8, 0x33, 0xf0, 0x19, 0xe1, 0xb4, 0x60, 0x53, + 0xc2, 0x03, 0x4f, 0x91, 0x1f, 0x55, 0x65, 0xf8, 0x70, 0xed, 0xde, 0x26, 0xbf, 0x76, 0x6d, 0x13, + 0x8b, 0x7e, 0x72, 0xa1, 0x33, 0x91, 0x75, 0xa0, 0x08, 0x5a, 0x69, 0x62, 0x66, 0x83, 0xaa, 0x32, + 0xbc, 0x6b, 0x69, 0xa4, 0x49, 0x14, 0xb7, 0xd2, 0x04, 0x3d, 0x06, 0x17, 0xe7, 0xb9, 0x19, 0xca, + 0xff, 0xab, 0x32, 0xbc, 0x67, 0x81, 0x70, 0x9e, 0x47, 0xb1, 0xcc, 0xa3, 0x4f, 0x65, 0x55, 0x4b, + 0x2a, 0xac, 0x69, 0x6c, 0x56, 0x65, 0xf2, 0x56, 0x37, 0x75, 0xec, 0x2c, 0x41, 0xef, 0xaf, 0xc6, + 0xaf, 0x27, 0xd1, 0xaf, 0xca, 0xb0, 0xb7, 0x31, 0xfe, 0x86, 0x69, 0xd9, 0x60, 0xca, 0x08, 0x16, + 0x24, 0x39, 0xc7, 0x42, 0xcd, 0xa1, 0xb3, 0x61, 0x83, 0x06, 0x60, 0xf5, 0xc3, 0x04, 0x4f, 0x84, + 0xb4, 0x41, 0xce, 0xe8, 0xf4, 0x32, 0x5d, 0x90, 0xa0, 0x3b, 0x70, 0x86, 0x7b, 0x1b, 0x83, 0xac, + 0xd3, 0x56, 0xe1, 0x75, 0x48, 0x4e, 0xf2, 0x8a, 0x30, 0x9e, 0xd2, 0x2c, 0xf0, 0x54, 0xe5, 0xb7, + 0x27, 0x69, 0xb2, 0xd6, 0x24, 0x4d, 0x24, 0xfa, 0xb9, 0x05, 0x5e, 0xac, 0xdd, 0xf8, 0x6f, 0x8e, + 0xc2, 0x36, 0xa6, 0xbb, 0xad, 0x31, 0xff, 0xbb, 0x39, 0x44, 0xbf, 0x79, 0xb0, 0x13, 0x1b, 0x97, + 0x6e, 0xb1, 0x38, 0x86, 0xd0, 0x9e, 0xa7, 0x59, 0x62, 0xda, 0x72, 0x1b, 0x29, 0x53, 0x51, 0xac, + 0x10, 0xd6, 0xdd, 0xdc, 0xad, 0xee, 0xf6, 0x14, 0xee, 0xe8, 0x5f, 0xe7, 0x8c, 0x60, 0x4e, 0x33, + 0xd3, 0x9a, 0xc7, 0x55, 0x19, 0x3e, 0xda, 0xa0, 0x1b, 0x4c, 0xa3, 0xb2, 0xa7, 0xe3, 0xb1, 0x0a, + 0xa3, 0x23, 0x70, 0x5f, 0xc4, 0xcf, 0xcc, 0xc2, 0x38, 0xa8, 0xca, 0xf0, 0x0d, 0x4b, 0xe1, 0x45, + 0xfc, 0xac, 0xe1, 0x49, 0x1c, 0x7a, 0x13, 0x3a, 0x5c, 0xe0, 0xe9, 0x5c, 0x19, 0xd3, 0x9f, 0xfc, + 0xaf, 0x2a, 0xc3, 0x3b, 0x16, 0xe1, 0x28, 0x8a, 0x75, 0x1e, 0xbd, 0x0d, 0x6d, 0x9c, 0xe7, 0xf5, + 0x42, 0x38, 0xac, 0xca, 0x30, 0x58, 0x37, 0x87, 0x75, 0x2f, 0x85, 0x44, 0xdf, 0x81, 0x47, 0x5e, + 0xe6, 0x94, 0x09, 0x1e, 0xec, 0x0c, 0xdc, 0xe1, 0xee, 0x3b, 0x0f, 0x47, 0xfa, 0x4d, 0x30, 0xaa, + 0x87, 0x30, 0x3a, 0xd5, 0xf9, 0xd3, 0x4c, 0xb0, 0xeb, 0x0d, 0x5f, 0x1b, 0xb2, 0xe5, 0x21, 0x13, + 0x41, 0x09, 0x40, 0x8e, 0x19, 0x5e, 0x12, 0x41, 0x18, 0x0f, 0x7c, 0x25, 0x3e, 0xd8, 0x10, 0x7f, + 0xbe, 0x82, 0x68, 0xfd, 0xdb, 0x6e, 0x69, 0x24, 0x9a, 0x47, 0x58, 0xba, 0xe8, 0x29, 0x78, 0xb4, + 0x10, 0x79, 0x21, 0x78, 0x00, 0xaf, 0xa9, 0xff, 0x2b, 0x9d, 0xd7, 0xfa, 0xaf, 0xe8, 0x5d, 0x2d, + 0x80, 0x4e, 0xa0, 0x2d, 0xf0, 0x8c, 0x07, 0xbb, 0x4a, 0xa8, 0xb7, 0x21, 0xf4, 0x0d, 0x9e, 0xbd, + 0x5e, 0x45, 0x51, 0x7b, 0xc7, 0xb0, 0x67, 0xb7, 0x0b, 0xdd, 0x07, 0x77, 0x4e, 0xae, 0xb5, 0x7f, + 0x63, 0xf9, 0x13, 0xed, 0x43, 0xe7, 0x0a, 0x2f, 0x0a, 0xa2, 0x9d, 0x1a, 0xeb, 0xc3, 0x71, 0xeb, + 0x43, 0xa7, 0xf7, 0x09, 0xdc, 0xbb, 0xd5, 0x8d, 0xad, 0xe8, 0xc7, 0xb0, 0x67, 0xdf, 0x74, 0x2b, + 0xee, 0x07, 0xe0, 0xaf, 0x2e, 0xb7, 0x0d, 0x31, 0x7a, 0x0f, 0x76, 0xeb, 0xf6, 0x7c, 0x8b, 0xd9, + 0x3f, 0xa5, 0x46, 0x05, 0xf8, 0xa7, 0xd9, 0xd5, 0xe7, 0x34, 0xbb, 0x4c, 0x67, 0x68, 0x0c, 0x6d, + 0xd9, 0xca, 0xc0, 0x51, 0x6d, 0x3f, 0xa8, 0xdb, 0xbe, 0x02, 0x8c, 0xbe, 0xc0, 0x02, 0xab, 0xd2, + 0x62, 0x05, 0x94, 0xd5, 0xae, 0x42, 0x5b, 0x55, 0x3b, 0x07, 0xef, 0x39, 0xa3, 0x53, 0xc2, 0x39, + 0x42, 0xf6, 0x66, 0x31, 0x3b, 0xa4, 0xa7, 0x5f, 0x01, 0xf2, 0xb3, 0xc8, 0x70, 0x57, 0x67, 0x14, + 0x80, 0xf7, 0x23, 0x65, 0x73, 0x69, 0x65, 0xb9, 0x36, 0x3a, 0x71, 0x7d, 0x44, 0x0f, 0xd6, 0x77, + 0x65, 0xbd, 0x2f, 0xa2, 0x5f, 0x1d, 0xf0, 0xbf, 0xa4, 0x6c, 0xa9, 0x3e, 0xa0, 0x64, 0x99, 0x72, + 0x6b, 0x9b, 0x32, 0xcd, 0xbb, 0x92, 0x0b, 0x56, 0x4c, 0x45, 0xc1, 0xe4, 0xe3, 0xd6, 0xfe, 0x1e, + 0x2b, 0xde, 0xe8, 0xeb, 0x1a, 0xa2, 0x1b, 0xd0, 0x50, 0x7a, 0x4f, 0xe0, 0xee, 0x7a, 0xf2, 0xef, + 0x5a, 0xd1, 0xb1, 0x5a, 0x31, 0xd9, 0xff, 0xfd, 0xa6, 0xef, 0xfc, 0x71, 0xd3, 0x77, 0xfe, 0xbc, + 0xe9, 0x3b, 0xdf, 0x9b, 0xaf, 0xbf, 0x8b, 0xae, 0xfa, 0x6e, 0x7b, 0xf7, 0xaf, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x83, 0x0d, 0x5c, 0x58, 0x1b, 0x0a, 0x00, 0x00, } diff --git a/api/models/types.proto b/api/models/types.proto index da75f0cf..81b7a4bc 100644 --- a/api/models/types.proto +++ b/api/models/types.proto @@ -26,6 +26,7 @@ message Build { string status = 4 [(gogoproto.moretags) = "datastore:\"status,noindex\""]; int32 created_at = 5 [(gogoproto.moretags) = "datastore:\"created_at,noindex\""]; bytes procfile = 6 [(gogoproto.moretags) = "datastore:\"procfile,noindex\""]; + string version = 7 [(gogoproto.moretags) = "datastore:\"version,noindex\""]; } message Release { diff --git a/api/process.go b/api/process.go index e40a6c4b..c2af92c6 100644 --- a/api/process.go +++ b/api/process.go @@ -1,14 +1,65 @@ package main import ( + "errors" + "fmt" + "net" + "sync" + "time" + log "github.com/Sirupsen/logrus" pbs "github.com/datacol-io/datacol/api/controller" pb "github.com/datacol-io/datacol/api/models" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" + "golang.org/x/net/websocket" "google.golang.org/grpc/metadata" ) +func (s *Server) ResourceProxy(ws *websocket.Conn) error { + headers := ws.Request().Header + host := headers.Get("remotehost") + port := headers.Get("remoteport") + + if host == "" { + return errors.New("Missing required header: remotehost") + } + + if port == "" { + return errors.New("Missing required header: remoteport") + } + + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%s", host, port), 3*time.Second) + + if neterr, ok := err.(net.Error); ok && neterr.Timeout() { + return errors.New("connection timeout out") + } + + if err != nil { + return err + } + + var wg sync.WaitGroup + + wg.Add(2) + go copyAsync(ws, conn, &wg) + go copyAsync(conn, ws, &wg) + wg.Wait() + + return nil +} + +func (s *Server) ProcessRunWs(ws *websocket.Conn) error { + headers := ws.Request().Header + app := headers.Get("app") + + if app == "" { + return fmt.Errorf("Missing require param: app") + } + + return s.Provider.ProcessRun(app, ws, headers.Get("command")) +} + func (s *Server) ProcessRun(srv pbs.ProviderService_ProcessRunServer) error { md, _ := metadata.FromIncomingContext(srv.Context()) app, command := md["app"][0], md["command"][0] diff --git a/api/server.go b/api/server.go index 6c61a45d..1b9930d9 100644 --- a/api/server.go +++ b/api/server.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "io" "io/ioutil" @@ -8,7 +9,7 @@ import ( "os" "strings" - gce_metadata "cloud.google.com/go/compute/metadata" + gcp_metadata "cloud.google.com/go/compute/metadata" log "github.com/Sirupsen/logrus" pbs "github.com/datacol-io/datacol/api/controller" pb "github.com/datacol-io/datacol/api/models" @@ -18,6 +19,7 @@ import ( "github.com/datacol-io/datacol/cloud/local" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" + "golang.org/x/net/websocket" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" @@ -71,6 +73,13 @@ func newServer() *Server { } case "local": + // Local provider uses registry inside minikube VM to store images. Execute following commands to the registry running + // inside minikube vm + // + // minikube start --insecure-registry localhost:5000 && \ + // eval $(minikube docker-env) && \ + // docker run -d -p 5000:5000 --restart=always --name registry registry:2 + provider = &local.LocalCloud{ Name: name, RegistryAddress: "localhost:5000", @@ -114,25 +123,33 @@ func (s *Server) Run() error { } func (s *Server) Auth(ctx context.Context, req *pbs.AuthRequest) (*pbs.AuthResponse, error) { + provider := cloud.CloudProvider(os.Getenv("DATACOL_PROVIDER")) if authorize(ctx, s.Password) { var ip, project string - if gce_metadata.OnGCE() { - _ip, err := gce_metadata.ExternalIP() - if err != nil { - return nil, internalError(err, "couldn't resolve external ip for instance.") - } - ip = _ip - _pid, err := gce_metadata.ProjectID() - if err != nil { - return nil, internalError(err, "couldn't get projectId from metadata server.") + switch provider { + case cloud.GCPProvider: + if gcp_metadata.OnGCE() { + _pid, err := gcp_metadata.ProjectID() + if err != nil { + return nil, internalError(err, "couldn't get projectId from metadata server.") + } + project = _pid } - project = _pid - } else { + case cloud.AwsProvider: + project = "" + case cloud.LocalProvider: ip = "localhost" project = "gcs-local" + default: + return nil, fmt.Errorf("Invalid cloud provider: %s", provider) } - return &pbs.AuthResponse{Name: s.StackName, Host: ip, Project: project}, nil + return &pbs.AuthResponse{ + Provider: string(provider), + Name: s.StackName, + Host: ip, + Project: project, + }, nil } else { return nil, internalError(fmt.Errorf("Invalid login trial with %s", req.Password), "invalid password") } @@ -268,29 +285,20 @@ func (s *Server) BuildLogs(ctx context.Context, req *pbs.BuildLogRequest) (*pbs. return &pbs.BuildLogResponse{Pos: int32(pos), Lines: lines}, nil } -func (s *Server) BuildLogsStream(req *pbs.BuildLogStreamReq, stream pbs.ProviderService_BuildLogsStreamServer) error { - reader, err := s.Provider.BuildLogsStream(req.Id) +// Streaming build logs with websocket +func (s *Server) BuildLogStreamReq(ws *websocket.Conn) error { + buildId := ws.Request().Header.Get("id") + if buildId == "" { + return errors.New("Missing required header: id") + } - if err != nil || reader == nil { + r, err := s.Provider.BuildLogsStream(buildId) + if err != nil { return err } - buf := make([]byte, 0, 4*1024) - - for { - n, err := reader.Read(buf[:cap(buf)]) - if err != nil { - if n == 0 || err == io.EOF { - return nil - } - return err - } - buf = buf[:n] - - if err := stream.Send(&pbs.StreamMsg{Data: buf}); err != nil { - return err - } - } + _, err = io.Copy(ws, r) + return err } // Releases endpoints diff --git a/client/apps.go b/client/apps.go index 424400ab..5eb67254 100644 --- a/client/apps.go +++ b/client/apps.go @@ -1,12 +1,14 @@ package client import ( + "fmt" "io" + "os" + "strconv" "time" pbs "github.com/datacol-io/datacol/api/controller" pb "github.com/datacol-io/datacol/api/models" - "github.com/golang/protobuf/ptypes" "golang.org/x/net/context" ) @@ -40,30 +42,14 @@ func (c *Client) RestartApp(name string) error { return err } -func (c *Client) StreamAppLogs(name string, follow bool, since time.Duration, proctype string, out io.Writer) error { - stream, err := c.ProviderServiceClient.LogStream(ctx, &pbs.LogStreamReq{ - Name: name, - Since: ptypes.DurationProto(since), - Follow: follow, - Proctype: proctype, - }) - if err != nil { - return err - } - defer stream.CloseSend() - - for { - ret, err := stream.Recv() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - if _, err := out.Write(ret.Data); err != nil { - return err - } - } +func (c *Client) StreamAppLogs(name string, follow bool, since time.Duration, process string, out io.Writer) error { + in, out := os.Stdin, os.Stdout + return c.Stream("/ws/v1/logs", map[string]string{ + "app": name, + "since": since.String(), + "follow": strconv.FormatBool(follow), + "Process": process, + }, in, out) } func (c *Client) GetEnvironment(name string) (pb.Environment, error) { @@ -78,3 +64,10 @@ func (c *Client) SetEnvironment(name string, data string) error { _, err := c.ProviderServiceClient.EnvironmentSet(ctx, &pbs.EnvSetRequest{Name: name, Data: data}) return err } + +func (c *Client) ProxyRemote(host string, port int, conn io.ReadWriteCloser) error { + return c.Stream("/ws/v1/proxy", map[string]string{ + "remotehost": host, + "remoteport": fmt.Sprintf("%d", port), + }, conn, conn) +} diff --git a/client/build.go b/client/build.go index c8339425..e7cca526 100644 --- a/client/build.go +++ b/client/build.go @@ -62,10 +62,11 @@ func (c *Client) CreateBuild(app *pb.App, data []byte, procfile []byte) (*pb.Bui return b, err } -func (c *Client) CreateBuildGit(app *pb.App, version string) (*pb.Build, error) { +func (c *Client) CreateBuildGit(app *pb.App, version string, procfile []byte) (*pb.Build, error) { return c.ProviderServiceClient.BuildCreate(ctx, &pbs.CreateBuildRequest{ - App: app.Name, - Version: version, + App: app.Name, + Version: version, + Procfile: procfile, }) } diff --git a/client/client.go b/client/client.go index 9cb25cee..8559702b 100644 --- a/client/client.go +++ b/client/client.go @@ -1,16 +1,22 @@ package client import ( + "crypto/tls" + "encoding/base64" "fmt" + "io" + "sync" "time" log "github.com/Sirupsen/logrus" - "github.com/appscode/go/io" + io_ext "github.com/appscode/go/io" term "github.com/appscode/go/term" pb "github.com/datacol-io/datacol/api/controller" "github.com/datacol-io/datacol/api/models" "github.com/datacol-io/datacol/cmd/stdcli" + "github.com/urfave/cli" "golang.org/x/net/context" + "golang.org/x/net/websocket" "google.golang.org/grpc" ) @@ -22,7 +28,7 @@ const ( func init() { root := models.ConfigPath - if err := io.EnsureDirectory(root); err != nil { + if err := io_ext.EnsureDirectory(root); err != nil { stdcli.ExitOnError(err) } } @@ -45,14 +51,14 @@ func (c *Client) IsLocal() bool { return c.Provider == "local" } -func NewClient(version string) (*Client, func() error) { - auth, _ := stdcli.GetAuthOrDie() +func NewClient(c *cli.Context) (*Client, func() error) { + auth, _ := stdcli.GetAuthOrDie(c) psc, close := GrpcClient(auth.ApiServer, auth.ApiKey) conn := &Client{ - Version: version, + Version: c.App.Version, + Auth: *auth, ProviderServiceClient: psc, - Auth: *auth, } conn.SetStack(auth) @@ -73,6 +79,21 @@ func (c *loginCreds) RequireTransportSecurity() bool { return false } +func (c *Client) SetStack(auth *stdcli.Auth) { + c.Auth = *auth + + if c.IsGCP() { + // for GCP only + if len(c.Project) == 0 { + c.Project = stdcli.ReadSetting(c.Name, "project") + } + + if len(auth.Project) == 0 && len(auth.Region) == 0 { + log.Fatal(fmt.Errorf("GCP project-id not found. Please set `PROJECT_ID` environment variable.")) + } + } +} + func GrpcClient(host, password string) (pb.ProviderServiceClient, func() error) { address := fmt.Sprintf("%s:%d", host, apiRpcPort) log.Debugf("grpc dialing at %s", address) @@ -94,17 +115,66 @@ func GrpcClient(host, password string) (pb.ProviderServiceClient, func() error) return pb.NewProviderServiceClient(conn), conn.Close } -func (c *Client) SetStack(auth *stdcli.Auth) { - c.Auth = *auth +func (c *Client) Stream(path string, headers map[string]string, in io.Reader, out io.Writer) error { + ws, err := c.StreamClient(path, headers) + if err != nil { + return err + } + defer ws.Close() - if c.IsGCP() { - // for GCP only - if len(c.Project) == 0 { - c.Project = stdcli.ReadSetting(c.Name, "project") - } + var wg sync.WaitGroup - if len(auth.Project) == 0 && len(auth.Region) == 0 { - log.Fatal(fmt.Errorf("GCP project-id not found. Please set `PROJECT_ID` environment variable.")) - } + if in != nil { + go io.Copy(ws, in) + } + + if out != nil { + wg.Add(1) + go copyAsync(out, ws, &wg) + } + + wg.Wait() + + return nil +} + +func (c *Client) StreamClient(path string, headers map[string]string) (*websocket.Conn, error) { + return wsConn(path, c.ApiServer, c.Version, c.ApiKey, headers) +} + +func wsConn(path, server, version, apiKey string, headers map[string]string) (*websocket.Conn, error) { + origin := fmt.Sprintf("https://%s:%d", server, apiHttpPort) + endpoint := fmt.Sprintf("ws://%s:%d%s", server, apiHttpPort, path) + + config, err := websocket.NewConfig(endpoint, origin) + + if err != nil { + return nil, err } + + config.TlsConfig = &tls.Config{ + InsecureSkipVerify: true, + } + + config.Header.Set("Version", version) + + userpass := fmt.Sprintf(":%s", apiKey) + userpass_encoded := base64.StdEncoding.EncodeToString([]byte(userpass)) + + config.Header.Add("Authorization", fmt.Sprintf("Basic %s", userpass_encoded)) + + for k, v := range headers { + config.Header.Add(k, v) + } + + config.TlsConfig = &tls.Config{ + InsecureSkipVerify: true, + } + + return websocket.DialConfig(config) +} + +func copyAsync(dst io.Writer, src io.Reader, wg *sync.WaitGroup) { + defer wg.Done() + io.Copy(dst, src) } diff --git a/client/process.go b/client/process.go index 345c67d3..442727b4 100644 --- a/client/process.go +++ b/client/process.go @@ -1,16 +1,13 @@ package client import ( - "io" "os" "strconv" "strings" - "sync" "github.com/appscode/go/term" pbs "github.com/datacol-io/datacol/api/controller" pb "github.com/datacol-io/datacol/api/models" - "google.golang.org/grpc/metadata" ) func (c *Client) ListProcess(name string) ([]*pb.Process, error) { @@ -45,65 +42,8 @@ func (c *Client) SaveProcess(name string, options map[string]string) error { } func (c *Client) RunProcess(name string, args []string) error { - newctx := metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{ + return c.Stream("/ws/v1/exec", map[string]string{ "app": name, "command": strings.Join(args, " "), - })) - - stream, err := c.ProviderServiceClient.ProcessRun(newctx) - if err != nil { - return err //FIXME: not able to make it work for now. Not sure why. - } - - defer stream.CloseSend() - - wg := sync.WaitGroup{} - wg.Add(1) - - r, w := os.Stdin, os.Stdout - - go func(out io.Writer) { - defer wg.Done() - - for { - ret, rerr := stream.Recv() - if rerr == io.EOF { - break - } - - if rerr != nil { - err = rerr - break - } - - if _, err = out.Write(ret.Data); err != nil { - break - } - } - }(w) - - go func(r io.Reader) { - defer wg.Done() - buf := make([]byte, 1024) - - for { - n, serr := r.Read(buf) - if serr == io.EOF { - break - } - if serr != nil { - err = serr - break - } - - if serr := stream.Send(&pbs.StreamMsg{Data: buf[:n]}); serr != nil { - err = serr - break - } - } - }(r) - - wg.Wait() - - return err + }, os.Stdin, os.Stdout) } diff --git a/cloud/aws/builder.go b/cloud/aws/builder.go index 3f3f23fb..e553ca67 100644 --- a/cloud/aws/builder.go +++ b/cloud/aws/builder.go @@ -5,6 +5,7 @@ import ( "io" "io/ioutil" "os" + "sort" "strings" "time" @@ -94,6 +95,10 @@ func (a *AwsCloud) BuildList(app string, limit int) (pb.Builds, error) { builds[i] = a.buildFromItem(item) } + sort.Slice(builds, func(i, j int) bool { + return builds[i].CreatedAt < builds[j].CreatedAt + }) + return builds, nil } @@ -103,6 +108,9 @@ func (a *AwsCloud) BuildImport(id, gzipPath string) error { return err } app := build.App + + // We need to convert gzip to zip format since AWS codebuild only + // supports zip file for s3 based source log.Debugf("converting gzip to zip of %s", gzipPath) zipPath, err := convertGzipToZip(app, gzipPath) if err != nil { @@ -136,19 +144,25 @@ func (a *AwsCloud) BuildImport(id, gzipPath string) error { } func (a *AwsCloud) BuildCreate(app string, req *pb.CreateBuildOptions) (*pb.Build, error) { + Id := generateId("B", 5) + build := &pb.Build{ App: app, - Id: generateId("B", 5), + Id: Id, + Version: req.Version, Procfile: req.Procfile, Status: pb.StatusCreated, CreatedAt: timestampNow(), } - if err := a.buildSave(build); err != nil { - return nil, fmt.Errorf("saving to dynamodb err: %v", err) + //FIXME: If version is not blank, we can trigger the build. this is a hack as of now and + // should be replaced with better build API + // Version os GIT COMMIT hash + if req.Version != "" { + return build, a.startBuild(build, req) } - return build, a.startBuild(build, req) + return build, a.buildSave(build) } func (a *AwsCloud) BuildLogs(app, id string, index int) (int, []string, error) { @@ -164,8 +178,17 @@ func (a *AwsCloud) BuildLogsStream(id string) (io.Reader, error) { fmt.Print("waiting for cloudwatch logstream (1s)") var rb *codebuild.Build + bb, err := a.BuildGet("", id) + if err != nil { + return nil, err + } + + if bb.RemoteId == "" { + return nil, fmt.Errorf("No build process found for Id=%s", bb.Id) + } + for { - b, err := a.fetchRemoteBuild(id) + b, err := a.fetchRemoteBuild(bb.RemoteId) if err != nil { return nil, fmt.Errorf("fetching build err: %v", err) } @@ -189,6 +212,7 @@ func (a *AwsCloud) buildFromItem(item map[string]*dynamodb.AttributeValue) *pb.B Id: coalesce(item["id"], ""), App: coalesce(item["app"], ""), Status: coalesce(item["status"], ""), + Version: coalesce(item["version"], ""), RemoteId: coalesce(item["remote_id"], ""), Procfile: coalesceBytes(item["procfile"]), CreatedAt: int32(coalesceInt(item["created_at"], 0)), @@ -208,6 +232,10 @@ func (a *AwsCloud) buildSave(b *pb.Build) error { req.Item["status"] = &dynamodb.AttributeValue{S: aws.String(b.Status)} } + if b.Version != "" { + req.Item["version"] = &dynamodb.AttributeValue{S: aws.String(b.Version)} + } + if b.RemoteId != "" { req.Item["remote_id"] = &dynamodb.AttributeValue{S: aws.String(b.RemoteId)} } diff --git a/cloud/aws/kubeconfig.go b/cloud/aws/kubeconfig.go index 2c8d7d98..d77cf712 100644 --- a/cloud/aws/kubeconfig.go +++ b/cloud/aws/kubeconfig.go @@ -22,6 +22,7 @@ var ( kcpath = filepath.Join(rootPath, "kubeconfig") pemPathRE = filepath.Join(rootPath, "%s.pem") privateIpAttr = "MasterPrivateIp" + bastionIpAttr = "BastionHostPublicIp" scpCmd = "scp -i %s -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ubuntu@%s:~/kubeconfig %s" ) @@ -65,19 +66,27 @@ func (p *AwsCloud) K8sConfigPath() (string, error) { return kcpath, nil } -func (p *AwsCloud) masterPrivateIp() (string, error) { +func (p *AwsCloud) bastionHostIp() (string, error) { + return p.stackOutputValue(bastionIpAttr) +} + +func (p *AwsCloud) stackOutputValue(attr string) (string, error) { s, err := p.describeStack("") if err != nil { return "", err } for _, o := range s.Outputs { - if o.OutputKey != nil && privateIpAttr == *o.OutputKey { + if o.OutputKey != nil && attr == *o.OutputKey { return *o.OutputValue, nil } } - return "", fmt.Errorf("unable to find MasterPrivateIp from stack output") + return "", fmt.Errorf("unable to find %s from stack output", attr) +} + +func (p *AwsCloud) masterPrivateIp() (string, error) { + return p.stackOutputValue(privateIpAttr) } func getKubeClientSet(name string) (*kubernetes.Clientset, error) { diff --git a/cloud/aws/releases.go b/cloud/aws/releases.go index 7ac87699..299dc90b 100644 --- a/cloud/aws/releases.go +++ b/cloud/aws/releases.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "sort" + "strings" log "github.com/Sirupsen/logrus" "github.com/aws/aws-sdk-go/aws" @@ -74,7 +75,11 @@ func (a *AwsCloud) BuildRelease(b *pb.Build, options pb.ReleaseOptions) (*pb.Rel return r, err } - domains := kube.MergeAppDomains(app.Domains, options.Domain) + domains := app.Domains + for _, domain := range strings.Split(options.Domain, ",") { + domains = kube.MergeAppDomains(domains, domain) + } + if len(app.Domains) != len(domains) { app.Domains = domains } diff --git a/cloud/aws/templates.go b/cloud/aws/templates.go index 27362ba9..6ca0bd3b 100644 --- a/cloud/aws/templates.go +++ b/cloud/aws/templates.go @@ -107,7 +107,7 @@ func cloudAwsTemplatesElasticsearchTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cloud/aws/templates/elasticsearch.tmpl", size: 4023, mode: os.FileMode(436), modTime: time.Unix(1520679139, 0)} + info := bindataFileInfo{name: "cloud/aws/templates/elasticsearch.tmpl", size: 4023, mode: os.FileMode(436), modTime: time.Unix(1520679773, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -147,7 +147,7 @@ func cloudAwsTemplatesPostgresTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cloud/aws/templates/postgres.tmpl", size: 4389, mode: os.FileMode(436), modTime: time.Unix(1520273305, 0)} + info := bindataFileInfo{name: "cloud/aws/templates/postgres.tmpl", size: 4389, mode: os.FileMode(436), modTime: time.Unix(1520679773, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/cloud/common/apps.go b/cloud/common/apps.go index 785e6904..bfd65689 100644 --- a/cloud/common/apps.go +++ b/cloud/common/apps.go @@ -3,6 +3,7 @@ package common import ( "strconv" + log "github.com/Sirupsen/logrus" pb "github.com/datacol-io/datacol/api/models" "github.com/datacol-io/datacol/cloud" "github.com/datacol-io/datacol/cloud/kube" @@ -28,18 +29,32 @@ func UpdateApp(c *kubernetes.Clientset, build *pb.Build, port = p } - procesess, err := kube.ProcessList(c, ns, build.App) + var procesess []*pb.Process + + defaultProctype := GetDefaultProctype(build) + procesess = append(procesess, &pb.Process{ + Proctype: defaultProctype, + Workers: 1, + }) + + runningProcesses, err := kube.ProcessList(c, ns, build.App) if err != nil { return err } - if len(procesess) == 0 { - procesess = append(procesess, &pb.Process{ - Proctype: GetDefaultProctype(build), - Workers: 1, - }) + for _, rp := range runningProcesses { + if rp.Proctype == defaultProctype { + procesess[0].Workers = rp.Workers // set the current worker similar to whatever running currently + } + + // Only append non-default proceses + if rp.Proctype != WebProcessKind && rp.Proctype != CmdProcessKind { + procesess = append(procesess, rp) + } } + log.Debugf("defaultProctype:%s updating processes: %+v", defaultProctype, procesess) + for _, proc := range procesess { proctype := proc.Proctype command, err := GetProcessCommand(proctype, build) diff --git a/cloud/common/util.go b/cloud/common/util.go index 8b26e531..16cbae82 100644 --- a/cloud/common/util.go +++ b/cloud/common/util.go @@ -94,7 +94,9 @@ func GetJobID(name, process_type string) string { func GetDefaultProctype(b *pb.Build) string { proctype := CmdProcessKind + if len(b.Procfile) > 0 { + log.Debugf("build %s have non-empty procfile", b.Id) proctype = WebProcessKind } diff --git a/cloud/google/builder.go b/cloud/google/builder.go index 4b656cda..aaf692d6 100644 --- a/cloud/google/builder.go +++ b/cloud/google/builder.go @@ -87,6 +87,7 @@ func (g *GCPCloud) BuildCreate(app string, req *pb.CreateBuildOptions) (*pb.Buil App: app, Id: id, Status: "CREATED", + Version: req.Version, Procfile: req.Procfile, CreatedAt: timestampNow(), } @@ -145,7 +146,7 @@ func (g *GCPCloud) BuildImport(id, filename string) error { }, Steps: []*cloudbuild.BuildStep{ { - Name: "gcr.io/cloud-builders/docker:17.06.1", + Name: "gcr.io/cloud-builders/docker:17.12.0", Args: append(stepBuildArgs, "."), }, }, @@ -208,7 +209,10 @@ func (g *GCPCloud) BuildRelease(b *pb.Build, options pb.ReleaseOptions) (*pb.Rel return nil, err } - domains := sched.MergeAppDomains(app.Domains, options.Domain) + domains := app.Domains + for _, domain := range strings.Split(options.Domain, ",") { + domains = sched.MergeAppDomains(domains, domain) + } if err := common.UpdateApp(g.kubeClient(), b, g.DeploymentName, image, g.appLinkedDB(app), domains, envVars, cloud.GCPProvider); err != nil { return nil, err diff --git a/cloud/kube/common.go b/cloud/kube/common.go index 532a119f..f5a81ffe 100644 --- a/cloud/kube/common.go +++ b/cloud/kube/common.go @@ -150,8 +150,6 @@ func GetServiceEndpoint(c *kubernetes.Clientset, ns, name string) (string, error return endpoint, err } - log.Debugf("service %s", toJson(svc)) - // If a service is deployed without domainName. We use ServceType = LoadBalancer and cloud load balancer will expose the service if svc.Spec.Type == core_v1.ServiceTypeLoadBalancer && len(svc.Status.LoadBalancer.Ingress) > 0 { ing := svc.Status.LoadBalancer.Ingress[0] @@ -178,10 +176,14 @@ func GetServiceEndpoint(c *kubernetes.Clientset, ns, name string) (string, error return endpoint, err } - log.Debugf("ingress %s", toJson(ing)) - if lBIngresses := ing.Status.LoadBalancer.Ingress; len(lBIngresses) > 0 { - return lBIngresses[0].IP, nil + ingRecord := lBIngresses[0] + endpoint = ingRecord.IP + if endpoint == "" { + endpoint = ingRecord.Hostname + } + + return endpoint, nil } if _, ok := ing.Annotations[ingressAnnotationName]; ok { @@ -207,6 +209,11 @@ func GetServiceEndpoint(c *kubernetes.Clientset, ns, name string) (string, error func LogStreamReq(c *kubernetes.Clientset, w io.Writer, ns, app string, opts pb.LogStreamOptions) error { pods, err := GetAllPods(c, ns, app) + if err != nil { + return err + } + + log.Debugf("Got %d pods for app=%s", len(pods), app) //TODO: consider using https://github.com/djherbis/stream for reading multiple streams var sources []multiplexio.Source diff --git a/cloud/kube/deployment.go b/cloud/kube/deployment.go index 7baf61e1..816f93e1 100644 --- a/cloud/kube/deployment.go +++ b/cloud/kube/deployment.go @@ -141,7 +141,8 @@ func newIngress(payload *DeployResponse, domains []string) *v1beta1.Ingress { Host: domain, IngressRuleValue: v1beta1.IngressRuleValue{HTTP: &v1beta1.HTTPIngressRuleValue{ Paths: []v1beta1.HTTPIngressPath{{ - Path: "/", + // It's important to have * after / since GCP GLBC load balancer doesn't support subresources automatically. + Path: "/*", Backend: v1beta1.IngressBackend{ ServiceName: r.ServiceID, ServicePort: r.ContainerPort, diff --git a/cloud/kube/pod.go b/cloud/kube/pod.go index 04db37b5..0acab836 100644 --- a/cloud/kube/pod.go +++ b/cloud/kube/pod.go @@ -75,6 +75,16 @@ func getAllDeployments(c *kubernetes.Clientset, ns, app string) ([]v1beta1.Deplo return res.Items, nil } +func getPodsForDeployment(c *kubernetes.Clientset, dp *v1beta1.Deployment) ([]v1.Pod, error) { + selector := klabels.Set(dp.Spec.Selector.MatchLabels).AsSelector() + res, err := c.Core().Pods(dp.Namespace).List(metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + return nil, err + } + + return res.Items, nil +} + func getPodByName(c *kubernetes.Clientset, ns, app string) (*v1.Pod, error) { pods, err := GetAllPods(c, ns, app) if err != nil { @@ -96,8 +106,8 @@ func podEvents(c *kubernetes.Clientset, pod *v1.Pod) (*v1.EventList, error) { } res, err := c.Core().Events(pod.Namespace).List(metav1.ListOptions{ - FieldSelector: klabels.Set(fields).AsSelector().String(), - ResourceVersion: pod.ObjectMeta.ResourceVersion, + FieldSelector: klabels.Set(fields).AsSelector().String(), + // ResourceVersion: pod.ObjectMeta.ResourceVersion, }) if err != nil { return res, err @@ -123,11 +133,7 @@ func handleNotReadyPods(c *kubernetes.Clientset, ns string, labels map[string]st continue } - name, ok := labels["name"] - if !ok { - return fmt.Errorf("name not found in %+v", labels) - } - + name := fmt.Sprintf("%s-%s", labels["app"], labels["type"]) cstatus := v1.ContainerStatus{} for _, cs := range pod.Status.ContainerStatuses { if cs.Name == name { @@ -227,10 +233,11 @@ func podPendingStatus(c *kubernetes.Clientset, pod *v1.Pod) (reason string, mess if reason == "ContainerCreating" { if events, err := podEvents(c, pod); err == nil { - ev := events.Items - reason = ev[len(ev)-1].Reason - message = ev[len(ev)-1].Message - return + if ev := events.Items; len(ev) > 0 { + reason = ev[len(ev)-1].Reason + message = ev[len(ev)-1].Message + return + } } } } @@ -372,18 +379,29 @@ const ( podDestroyed ) -func getPodStatus(c *kubernetes.Clientset, pod *v1.Pod) podStatus { - statesMap := map[string]podStatus{ - "Pending": podInitializing, - "ContainerCreating": podCreating, - "Starting": podStarting, - "Running": podUp, - "Terminating": podTerminating, - "Succeeded": podDown, - "Failed": podCrashed, - "Unknown": podError, +var podStatesMap = map[string]podStatus{ + "Pending": podInitializing, + "ContainerCreating": podCreating, + "Starting": podStarting, + "Running": podUp, + "Terminating": podTerminating, + "Succeeded": podDown, + "Failed": podCrashed, + "Unknown": podError, +} + +func getPodStatusStr(c *kubernetes.Clientset, pod *v1.Pod) string { + status := getPodStatus(c, pod) + for key, val := range podStatesMap { + if val == status { + return key + } } + return "" +} + +func getPodStatus(c *kubernetes.Clientset, pod *v1.Pod) podStatus { if pod == nil { return podDestroyed } @@ -394,15 +412,15 @@ func getPodStatus(c *kubernetes.Clientset, pod *v1.Pod) podStatus { } else if pod.Status.Phase == v1.PodRunning { status = podReadinessStatus(pod) if status == "Starting" || status == "Terminating" { - return statesMap[status] + return podStatesMap[status] } else if status == "Running" && podLivenessStatus(pod) { - return statesMap[status] + return podStatesMap[status] } } else { status = string(pod.Status.Phase) } - if v, ok := statesMap[status]; ok { + if v, ok := podStatesMap[status]; ok { return v } else { return podError diff --git a/cloud/kube/process.go b/cloud/kube/process.go index 5f6726c6..8382d8c2 100644 --- a/cloud/kube/process.go +++ b/cloud/kube/process.go @@ -110,6 +110,8 @@ func (p *ExecOptions) Run() error { return p.Executor.Execute("POST", req.URL(), p.Config, stdin, p.Out, p.Err, true) } +// ProcessList will fetch the list of processes running based on deployments Labels +// TODO: fetch the status as well func ProcessList(c *kubernetes.Clientset, ns, app string) ([]*pb.Process, error) { deployments, err := getAllDeployments(c, ns, app) if err != nil { @@ -118,10 +120,22 @@ func ProcessList(c *kubernetes.Clientset, ns, app string) ([]*pb.Process, error) var items []*pb.Process for _, dp := range deployments { + pods, err := getPodsForDeployment(c, &dp) + if err != nil { + return nil, err + } + + var status string + if len(pods) > 0 { + //FIXME: ideally should report status of all pods + status = getPodStatusStr(c, &pods[len(pods)-1]) + } + items = append(items, &pb.Process{ Proctype: dp.ObjectMeta.Labels[typeLabel], Workers: *dp.Spec.Replicas, Name: dp.Name, + Status: status, }) } diff --git a/cloud/local/build.go b/cloud/local/build.go index 9259f7d4..3492099c 100644 --- a/cloud/local/build.go +++ b/cloud/local/build.go @@ -75,7 +75,7 @@ func (g *LocalCloud) BuildImport(id, filename string) error { repo := fmt.Sprintf("%s/%s", g.RegistryAddress, app) tag := id - log.Debugf("Tagging image %s to %s", app, repo, tag) + log.Debugf("Tagging image %s to %s:%s", app, repo, tag) if err := dkr.TagImage(app, docker.TagImageOptions{Repo: repo, Tag: tag}); err != nil { return fmt.Errorf("failed to tag image with %v: %v", tag, err) diff --git a/cmd/apps.go b/cmd/apps.go index a602d4b7..b9d3155d 100644 --- a/cmd/apps.go +++ b/cmd/apps.go @@ -21,9 +21,8 @@ func init() { Name: "create", Action: cmdAppCreate, Flags: []cli.Flag{ - appFlag, cli.StringFlag{ - Name: "app", + Name: "name", Usage: "Appliction name (alphanumeric)", }, cli.StringFlag{ @@ -82,7 +81,7 @@ func cmdAppsList(c *cli.Context) error { } func cmdAppCreate(c *cli.Context) error { - name := c.String("app") + name := c.String("name") if len(name) == 0 { _, n, err := getDirApp(".") diff --git a/cmd/build.go b/cmd/build.go index 5b7162c3..a56079d6 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -20,6 +20,7 @@ import ( "github.com/docker/docker/pkg/fileutils" "github.com/urfave/cli" "golang.org/x/net/context" + "golang.org/x/net/websocket" ) func init() { @@ -107,7 +108,14 @@ func cmdBuild(c *cli.Context) error { } func executeBuildGitSource(api *client.Client, app *pb.App, version string) (*pb.Build, error) { - b, err := api.CreateBuildGit(app, version) + var procfile []byte + if _, err := os.Stat("Procfile"); err == nil { + content, err := parseProcfile() + stdcli.ExitOnError(err) + procfile = content + } + + b, err := api.CreateBuildGit(app, version, procfile) if err != nil { return nil, err } @@ -159,34 +167,9 @@ func createTarball(base string, env map[string]string) ([]byte, error) { } dockerfileName := "Dockerfile" - dkrexists := true if _, err := os.Stat(dockerfileName); os.IsNotExist(err) { - filename := "Dockerfile" - dkrexists = false - - if _, err = os.Stat("app.yaml"); err == nil { - fmt.Printf("Trying to generate %s from app.yaml ...", filename) - if err = gnDockerFromGAE(filename); err != nil { - fmt.Println(" FAILED") - log.Warn(err) - } else { - fmt.Println(" DONE") - dockerfileName = filename - } - } - - if err = mkBuildPackDockerfile(sym, env); err != nil { - return nil, fmt.Errorf("generating Dockerfile err: %v", err) - } - } - - if !dkrexists { - defer func() { - if err := os.Remove(filepath.Join(sym, dockerfileName)); err != nil { - log.Errorf("removing Dockerfile err: %v", err) - } - }() + stdcli.ExitOnError(fmt.Errorf("Dockerfile not found.")) } fmt.Print("Creating tarball ...") @@ -240,55 +223,43 @@ func createTarball(base string, env map[string]string) ([]byte, error) { return bytes, nil } -func finishBuild(api *client.Client, b *pb.Build) error { +func finishBuild(api *client.Client, b *pb.Build) (err error) { + // try to sync local state + b, err = api.GetBuild(b.App, b.Id) + if err != nil { + return err + } + if api.IsGCP() { return finishBuildGCP(api, b) } - return finishBuildAws(api, b) + return finishBuildAwsWs(api, b) } -func finishBuildAws(api *client.Client, b *pb.Build) error { - stream, err := api.ProviderServiceClient.BuildLogsStream(context.TODO(), &pbs.BuildLogStreamReq{Id: b.RemoteId}) +func finishBuildAwsWs(api *client.Client, b *pb.Build) error { + ws, err := api.StreamClient("/ws/v1/builds/logs", map[string]string{"id": b.Id}) if err != nil { return err } - defer stream.CloseSend() - out := os.Stdout - ticker := time.NewTicker(time.Second * 3) - defer ticker.Stop() + defer ws.Close() - buildStatus := b.Status + var ( + done = make(chan bool, 2) + out = os.Stdout + ) - go func() { - for range ticker.C { - newb, _ := api.GetBuild(b.App, b.Id) - if newb.Status != buildStatus { - buildStatus = newb.Status - break - } - } - }() + if out != nil { + go copyAsync(out, ws) + } - for { - if buildStatus != "IN_PROGRESS" { - fmt.Println("Build Id:", b.Id) - fmt.Println("Build status:", buildStatus) - break - } + // We need to pool the build status to short-circuit the streaming logs + // FIXME: waitForAwsBuild might get into zombie goroutine if copyAsync returns quick + go waitforAwsBuild(api, b, done, ws, 5*time.Second) + + <-done - ret, err := stream.Recv() - if err != nil { - if err == io.EOF { - return nil - } - return err - } - if _, err := out.Write(ret.Data); err != nil { - return err - } - } return nil } @@ -332,7 +303,8 @@ OUTER: case "WORKING": default: if b.Status != "" { - return fmt.Errorf("Build status: %s", b.Status) + gcrBuildURL := fmt.Sprintf("https://console.cloud.google.com/gcr/builds/%s?project=%s", b.RemoteId, api.Project) + return fmt.Errorf("Build status: %s\n. Please go to %s to see complete build logs.", b.Status, gcrBuildURL) } } } @@ -340,24 +312,23 @@ OUTER: return nil } -type herokuishOpts struct { - Env map[string]string +func copyAsync(dst io.Writer, src io.Reader) { + io.Copy(dst, src) } -func mkBuildPackDockerfile(dir string, env map[string]string) error { - dockerfile := filepath.Join(dir, "Dockerfile") - content := compileTmpl(herokuishTmpl, herokuishOpts{env}) - log.Debugf("--- generated Dockerfile ------\n %s --------", content) +func waitforAwsBuild(api *client.Client, b *pb.Build, done chan bool, ws *websocket.Conn, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for range ticker.C { + newb, _ := api.GetBuild(b.App, b.Id) + if newb.Status != "IN_PROGRESS" { + fmt.Println("Build Id:", newb.Id) + fmt.Println("Build status:", newb.Status) + break + } + } - return ioutil.WriteFile(dockerfile, []byte(content), 0644) + ws.Close() + done <- true } - -var herokuishTmpl = `FROM gliderlabs/herokuish:v0.3.29 -ADD . /app -{{- $burl := index .Env "BUILDPACK_URL" }} {{ if gt (len $burl) 0 }} -ENV BUILDPACK_URL {{ $burl }} -{{- end }} -RUN /bin/herokuish buildpack build -ENV PORT 8080 -EXPOSE 8080 -CMD ["/start", "web"]` diff --git a/cmd/deploy.go b/cmd/deploy.go index 45be7a33..dbcf298a 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -29,6 +29,10 @@ func init() { Name: "build, b", Usage: "Build id to use", }, + &cli.StringFlag{ + Name: "ref", + Usage: "The commit SHA1 of branch or tag to use", + }, &cli.BoolTFlag{ Name: "wait, w", Usage: "Wait for the app become available", @@ -41,6 +45,11 @@ func init() { Name: "domain, d", Usage: "domain(s) to use with this app", }, + &cli.BoolTFlag{ + //TODO: support expose in API + Name: "expose", + Usage: "expose the service to the public", + }, }, }) } @@ -59,10 +68,16 @@ func cmdDeploy(c *cli.Context) error { } var build *pb.Build - buildID := c.String("build") + commitID, buildID := c.String("ref"), c.String("build") if len(buildID) == 0 { - build, err = executeBuildDir(client, app, dir) + var err error + if commitID == "" { + build, err = executeBuildDir(client, app, dir) + } else { + build, err = executeBuildGitSource(client, app, commitID) + } + stdcli.ExitOnError(err) } else { b, err := client.GetBuild(name, buildID) @@ -85,6 +100,7 @@ func cmdDeploy(c *cli.Context) error { _, err = client.ReleaseBuild(build, pb.ReleaseOptions{ Domain: c.String("domain"), Wait: c.Bool("wait"), + Expose: c.BoolT("expose"), }) stdcli.ExitOnError(err) diff --git a/cmd/env.go b/cmd/env.go index ec5d26a1..8487e56f 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -13,6 +13,7 @@ func init() { Name: "env", Usage: "manage environment variables for an app", Action: cmdConfigList, + Flags: []cli.Flag{&appFlag}, Subcommands: []cli.Command{ { Name: "set", @@ -29,7 +30,7 @@ func init() { } func cmdConfigList(c *cli.Context) error { - _, name, err := getDirApp(".") + name, err := getCurrentApp(c) stdcli.ExitOnError(err) ct, close := getApiClient(c) @@ -53,7 +54,7 @@ func cmdConfigList(c *cli.Context) error { } func cmdConfigSet(c *cli.Context) error { - _, name, err := getDirApp(".") + name, err := getCurrentApp(c) stdcli.ExitOnError(err) ct, close := getApiClient(c) @@ -74,7 +75,8 @@ func cmdConfigSet(c *cli.Context) error { data += fmt.Sprintf("%s\n", value) } - return ct.SetEnvironment(name, data) + stdcli.ExitOnError(ct.SetEnvironment(name, data)) + return nil } func cmdConfigUnset(c *cli.Context) error { @@ -95,5 +97,6 @@ func cmdConfigUnset(c *cli.Context) error { } } - return client.SetEnvironment(name, data) + stdcli.ExitOnError(client.SetEnvironment(name, data)) + return nil } diff --git a/cmd/infra.go b/cmd/infra.go index 9a7a1350..f7446f67 100644 --- a/cmd/infra.go +++ b/cmd/infra.go @@ -53,7 +53,7 @@ func init() { Usage: "create a new resource", Action: cmdResourceCreate, Flags: []cli.Flag{ - stackFlag, + &stackFlag, &cli.BoolFlag{Name: "wait, w"}, }, SkipFlagParsing: true, @@ -63,26 +63,26 @@ func init() { ArgsUsage: "[name]", Usage: "delete a existing resource", Action: cmdResourceDelete, - Flags: []cli.Flag{stackFlag}, + Flags: []cli.Flag{&stackFlag}, }, { Name: "info", ArgsUsage: "[name]", Usage: "get info for a existing resource", Action: cmdResourceInfo, - Flags: []cli.Flag{stackFlag}, + Flags: []cli.Flag{&stackFlag}, }, { Name: "link", Usage: "link an app to resource and setting it up.", Action: cmdLinkCreate, - Flags: []cli.Flag{appFlag}, + Flags: []cli.Flag{&appFlag}, }, { Name: "unlink", Usage: "unlink an resource from an app.", Action: cmdLinkDelete, - Flags: []cli.Flag{appFlag}, + Flags: []cli.Flag{&appFlag}, }, }, }) diff --git a/cmd/init.go b/cmd/init.go index 762cf4c3..5ce7f6a3 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -51,7 +51,7 @@ func Initialize() { } func getApiClient(c *cli.Context) (*client.Client, func() error) { - return client.NewClient(c.App.Version) + return client.NewClient(c) } func getDirApp(path string) (string, string, error) { @@ -66,3 +66,13 @@ func getDirApp(path string) (string, string, error) { } return abs, app, nil } + +// getCurrentApp support -a/--app flag for various subcommand for an APP +func getCurrentApp(c *cli.Context) (string, error) { + if app := c.String("app"); app != "" { + return app, nil + } + + _, app, err := getDirApp(".") + return app, err +} diff --git a/cmd/login.go b/cmd/login.go index 24cc1d8e..2e76d018 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -7,6 +7,7 @@ import ( "strings" log "github.com/Sirupsen/logrus" + term "github.com/appscode/go/term" pbs "github.com/datacol-io/datacol/api/controller" "github.com/datacol-io/datacol/client" "github.com/datacol-io/datacol/cmd/stdcli" @@ -16,20 +17,19 @@ import ( func init() { stdcli.AddCommand(cli.Command{ - Name: "login", - Usage: "login in to datacol", - Action: cmdLogin, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "ip", - Usage: "datacol stack hostname or IP address", - }, - }, + Name: "login", + ArgsUsage: "[host]", + Usage: "login in to datacol", + Action: cmdLogin, }) } func cmdLogin(c *cli.Context) error { - stdcli.CheckFlagsPresence(c, "ip") + host := c.Args().First() + if host == "" { + term.Warningln("Missing required argument: host") + stdcli.Usage(c) + } r := bufio.NewReader(os.Stdin) fmt.Print("Enter your password: ") @@ -38,8 +38,6 @@ func cmdLogin(c *cli.Context) error { stdcli.ExitOnError(err) passwd := strings.TrimSpace(p) - host := c.String("ip") - api, close := client.GrpcClient(host, passwd) defer close() diff --git a/cmd/logs.go b/cmd/logs.go index 9fe072a7..b8cc1002 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -14,7 +14,7 @@ func init() { Usage: "streams logs for an app", Action: cmdAppLogStream, Flags: []cli.Flag{ - appFlag, + &appFlag, &cli.BoolFlag{ Name: "follow, f", Usage: "keep streaming new log output (default)", @@ -33,7 +33,7 @@ func init() { } func cmdAppLogStream(c *cli.Context) error { - _, name, err := getDirApp(".") + name, err := getCurrentApp(c) stdcli.ExitOnError(err) client, close := getApiClient(c) diff --git a/cmd/provider/aws/amazon.go b/cmd/provider/aws/amazon.go index 6a7ac366..3f457ee9 100644 --- a/cmd/provider/aws/amazon.go +++ b/cmd/provider/aws/amazon.go @@ -35,9 +35,11 @@ var ( ) type InitOptions struct { - Name, Region, Zone, Bucket string - APIKey, Version, ArtifactBucket string - MachineType, KeyName string + Name, Region, Zone, Bucket string + APIKey, Version, ArtifactBucket string + ClusterInstanceType, KeyName string + ControllerInstanceType string + DiskSize, NumNodes, ControllerPort int UseSpotInstance, CreateCluster bool } @@ -76,6 +78,10 @@ func InitializeStack(opts *InitOptions, creds *AwsCredentials) (*initResponse, e mkCluster = "false" } + if opts.ControllerInstanceType == "" { + opts.ControllerInstanceType = bastionType + } + req := &cloudformation.CreateStackInput{ Capabilities: []*string{aws.String("CAPABILITY_IAM")}, Parameters: []*cloudformation.Parameter{ @@ -85,11 +91,11 @@ func InitializeStack(opts *InitOptions, creds *AwsCredentials) (*initResponse, e {ParameterKey: aws.String("KeyMaterial"), ParameterValue: resp.KeyMaterial}, {ParameterKey: aws.String("ApiKey"), ParameterValue: aws.String(opts.APIKey)}, {ParameterKey: aws.String("DiskSizeGb"), ParameterValue: aws.String(fmt.Sprintf("%d", opts.DiskSize))}, - {ParameterKey: aws.String("BastionInstanceType"), ParameterValue: aws.String(bastionType)}, + {ParameterKey: aws.String("BastionInstanceType"), ParameterValue: aws.String(opts.ControllerInstanceType)}, {ParameterKey: aws.String("AdminIngressLocation"), ParameterValue: aws.String(adminIngressLoc)}, {ParameterKey: aws.String("NetworkingProvider"), ParameterValue: aws.String(networkProvider)}, {ParameterKey: aws.String("K8sNodeCapacity"), ParameterValue: aws.String(fmt.Sprintf("%d", opts.NumNodes-1))}, - {ParameterKey: aws.String("InstanceType"), ParameterValue: aws.String(opts.MachineType)}, + {ParameterKey: aws.String("InstanceType"), ParameterValue: aws.String(opts.ClusterInstanceType)}, {ParameterKey: aws.String("DatacolVersion"), ParameterValue: aws.String(opts.Version)}, {ParameterKey: aws.String("ArtifactBucket"), ParameterValue: aws.String(opts.ArtifactBucket)}, {ParameterKey: aws.String("SettingBucket"), ParameterValue: aws.String(opts.Bucket)}, diff --git a/cmd/provider/aws/kubernetes-cluster.template b/cmd/provider/aws/kubernetes-cluster.template new file mode 100644 index 00000000..26bbc643 --- /dev/null +++ b/cmd/provider/aws/kubernetes-cluster.template @@ -0,0 +1,1064 @@ +# Copyright 2017 by the contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +AWSTemplateFormatVersion: '2010-09-09' +Description: 'QS(5042) Kubernetes AWS CloudFormation Template: Create a Kubernetes + cluster in an existing VPC. This template is for users who want to add + a Kubernetes cluster to existing AWS infrastructure. The master node is + an auto-recovering Amazon EC2 instance. 1-20 additional EC2 instances in an + AutoScalingGroup join the Kubernetes cluster as nodes. An ELB provides + configurable external access to the Kubernetes API. If you choose a private + subnet, make sure it has a bastion host for SSH access to your cluster. If you + choose a public subnet, you can connect directly to the master node. The stack + is suitable for development and small single-team clusters. **WARNING** This + template creates four Amazon EC2 instances with default settings. You will + be billed for the AWS resources used if you create a stack from this template. + **SUPPORT** Please visit http://jump.heptio.com/aws-qs-help for support. + **NEXT STEPS** Please visit http://jump.heptio.com/aws-qs-next.' + +# The Metadata tells AWS how to display the parameters during stack creation +Metadata: + AWS::CloudFormation::Interface: + ParameterGroups: + - Label: + default: Amazon EC2 Configuration + Parameters: + - VPCID + - AvailabilityZone + - InstanceType + - DiskSizeGb + - ClusterSubnetId + - LoadBalancerSubnetId + - LoadBalancerType + - Label: + default: Access Configuration + Parameters: + - SSHLocation + - ApiLbLocation + - KeyName + - Label: + default: Kubernetes Configuration + Parameters: + - K8sNodeCapacity + - NetworkingProvider + - Label: + default: Advanced + Parameters: + - QSS3BucketName + - QSS3KeyPrefix + - ClusterAssociation + + ParameterLabels: + KeyName: + default: SSH Key + VPCID: + default: VPC + AvailabilityZone: + default: Availability Zone + ClusterSubnetId: + default: Subnet + SSHLocation: + default: SSH Ingress Location + ApiLbLocation: + default: API Ingress Location + InstanceType: + default: Instance Type + DiskSizeGb: + default: Disk Size (GiB) + K8sNodeCapacity: + default: Node Capacity + QSS3BucketName: + default: S3 Bucket + QSS3KeyPrefix: + default: S3 Key Prefix + ClusterAssociation: + default: Cluster Association + NetworkingProvider: + default: Networking Provider + LoadBalancerSubnetId: + default: Load Balancer Subnet + LoadBalancerType: + default: Load Balancer Type + + +# The Parameters allow the user to pass custom settings to the stack before creation +Parameters: + # Required. Calls for the name of an existing EC2 KeyPair, to enable SSH access to the instances + # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html + KeyName: + Description: Existing EC2 KeyPair for SSH access. + Type: AWS::EC2::KeyPair::KeyName + ConstraintDescription: must be the name of an existing EC2 KeyPair. + + VPCID: + Description: Existing VPC to use for this cluster. + Type: AWS::EC2::VPC::Id + + ClusterSubnetId: + Description: Existing subnet to use for this cluster. Must belong to the Availability Zone above. + Type: AWS::EC2::Subnet::Id + + LoadBalancerSubnetId: + Description: Existing subnet to use for load balancing HTTPS access to the Kubernetes API server. + Must be a public subnet. Must belong to the Availability Zone above. + Type: AWS::EC2::Subnet::Id + + LoadBalancerType: + Description: Create Internet-facing (public, external) or Internal-facing Load Balancers + Type: String + Default: internet-facing + AllowedValues: [ "internet-facing", "internal" ] + + ClusterAssociation: + Description: Enter a string, unique within your AWS account, to associate resources in + this Kubernetes cluster with each other. This adds a tag, with the key KubernetesCluster + and the value of this parameter, to resources created as part of this stack. Leave blank + to use this Quick Start Stack name. + Type: String + + # Default is m4.large. EC2 instance type for the cluster + # https://aws.amazon.com/ec2/instance-types/ + InstanceType: + Description: EC2 instance type for the cluster. + Type: String + Default: m4.large + AllowedValues: + - t2.medium + - m5.large + - m5.xlarge + - m5.2xlarge + - m5.4xlarge + - m5.12xlarge + - m5.24xlarge + - c5.large + - c5.xlarge + - c5.2xlarge + - c5.4xlarge + - c5.9xlarge + - c5.18xlarge + - r4.large + - r4.xlarge + - r4.2xlarge + - r4.4xlarge + - r4.8xlarge + - r4.16xlarge + - x1.16xlarge + - x1.32xlarge + - i3.large + - i3.xlarge + - i3.2xlarge + - i3.4xlarge + - i3.8xlarge + - i3.16xlarge + - p2.xlarge + - p2.8xlarge + - p2.16xlarge + - p3.2xlarge + - p3.8xlarge + - p3.16xlarge + - m4.large + - m4.xlarge + - m4.2xlarge + - m4.4xlarge + - m4.10xlarge + - m4.16xlarge + - c4.large + - c4.xlarge + - c4.2xlarge + - c4.4xlarge + - c4.8xlarge + - g3.4xlarge + - g3.8xlarge + - g3.16xlarge + - r3.large + - r3.xlarge + - r3.2xlarge + - r3.4xlarge + - r3.8xlarge + ConstraintDescription: must be a valid Current Generation (non-burstable) EC2 instance type. + + # Specifies the size of the root disk for all EC2 instances, including master + # and nodes. + DiskSizeGb: + Description: 'Size of the root disk for the EC2 instances, in GiB. Default: 40' + Default: 40 + Type: Number + MinValue: 8 + MaxValue: 1024 + + # Required. This is an availability zone from your region + # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html + AvailabilityZone: + Description: The Availability Zone for this cluster. Heptio recommends + that you run one cluster per AZ and use tooling to coordinate across AZs. + Type: AWS::EC2::AvailabilityZone::Name + ConstraintDescription: must be the name of an AWS Availability Zone + + # Specifies the IP range from which you will have SSH access over port 22 + # Used in the allow22 SecurityGroup + SSHLocation: + Description: CIDR block (IP address range) to allow SSH access to the + instances. Use 0.0.0.0/0 to allow access from all locations. + Type: String + MinLength: '9' + MaxLength: '18' + AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" + ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x. + + # Specifies the IP range from which you will have HTTPS access to the Kubernetes API server load balancer + # Used in the ApiLoadBalancerSecGroup SecurityGroup + ApiLbLocation: + Description: CIDR block (IP address range) to allow HTTPS access to + the Kubernetes API. Use 0.0.0.0/0 to allow access from all locations. + Type: String + MinLength: '9' + MaxLength: '18' + AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" + ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x. + + # Default 2. Choose 1-20 initial nodes to run cluster workloads (in addition to the master node instance) + # You can scale up your cluster later and add more nodes + K8sNodeCapacity: + Default: '2' + Description: Initial number of Kubernetes nodes (1-20). + Type: Number + MinValue: '1' + MaxValue: '20' + ConstraintDescription: must be between 1 and 20 EC2 instances. + + # S3 Bucket configuration: allows users to use their own downstream snapshots + # of the quickstart-aws-vpc and quickstart-linux-bastion templates + QSS3BucketName: + AllowedPattern: "^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$" + ConstraintDescription: Quick Start bucket name can include numbers, lowercase + letters, uppercase letters, and hyphens (-). It cannot start or end with a hyphen + (-). + + Default: quickstart-reference + Description: Only change this if you have set up assets, like your own networking + configuration, in an S3 bucket. This and the S3 Key Prefix parameter let you access + scripts from the scripts/ and templates/ directories of your own fork of the Heptio + Quick Start assets, uploaded to S3 and stored at + ${bucketname}.s3.amazonaws.com/${prefix}/scripts/somefile.txt.S3. The bucket name + can include numbers, lowercase letters, uppercase letters, and hyphens (-). + It cannot start or end with a hyphen (-). + Type: String + + QSS3KeyPrefix: + AllowedPattern: "^[0-9a-zA-Z-]+(/[0-9a-zA-Z-]+)*$" + ConstraintDescription: Quick Start key prefix can include numbers, lowercase letters, + uppercase letters, hyphens (-), and forward slash (/). It cannot start or end + with forward slash (/) because they are automatically appended. + Default: heptio/latest + Description: Only change this if you have set up assets in an S3 bucket, as explained + in the S3 Bucket parameter. The S3 key prefix can include numbers, lowercase letters, + uppercase letters, hyphens (-), and forward slashes (/). It cannot start or end with + forward slashes (/) because they are automatically appended. + Type: String + + NetworkingProvider: + AllowedValues: + - calico + - weave + ConstraintDescription: 'Currently supported values are "calico" and "weave"' + Default: calico + Description: Choose the networking provider to use for communication between + pods in the Kubernetes cluster. Supported configurations are calico + (https://docs.projectcalico.org/v2.6/getting-started/kubernetes/installation/hosted/kubeadm/) + and weave (https://github.com/weaveworks/weave/blob/master/site/kubernetes/kube-addon.md). + Type: String + +# http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html +Mappings: + # http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html + RegionMap: + ap-northeast-1: + '64': ami-9adea5fc + ap-northeast-2: + '64': ami-f3dd709d + ap-south-1: + '64': ami-6b421e04 + ap-southeast-1: + '64': ami-93733def + ap-southeast-2: + '64': ami-f39e5991 + ca-central-1: + '64': ami-3aca4d5e + eu-central-1: + '64': ami-65ed8e0a + eu-west-1: + '64': ami-f5e9978c + eu-west-2: + '64': ami-3aed095d + eu-west-3: + '64': ami-cd883eb0 + sa-east-1: + '64': ami-39397155 + us-east-2: + '64': ami-4f6a5d2a + us-west-1: + '64': ami-e1e9e181 + us-west-2: + '64': ami-60880718 + us-east-1: + '64': ami-9ee505e3 +# Helper Conditions which help find the right values for resources +Conditions: + AssociationProvidedCondition: + Fn::Not: + - Fn::Equals: + - !Ref ClusterAssociation + - '' + LoadBalancerSubnetProvidedCondition: + Fn::Not: + - Fn::Equals: + - !Ref LoadBalancerSubnetId + - '' + +# Resources are the AWS services we want to actually create as part of the Stack +Resources: + ClusterInfoBucket: + Type: AWS::S3::Bucket + Properties: + AccessControl: Private + Tags: + - Key: KubernetesCluster + Value: + Fn::If: + - AssociationProvidedCondition + - !Ref ClusterAssociation + - !Ref AWS::StackName + + # Install a CloudWatch logging group for system logs for each instance + KubernetesLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: !Ref AWS::StackName + RetentionInDays: 14 + + # This is an EC2 instance that will serve as our master node + K8sMasterInstance: + Type: AWS::EC2::Instance + DependsOn: ApiLoadBalancer + Metadata: + AWS::CloudFormation::Init: + configSets: + master-setup: master-setup + master-setup: + files: + # Script that will allow for development kubernetes binaries to replace the pre-packaged AMI binaries. + "/tmp/kubernetes-override-binaries.sh": + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/kubernetes-override-binaries.sh.in" + mode: '000755' + context: + BaseBinaryUrl: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/bin/" + + # Configuration file for the cloudwatch agent. The file is a Mustache template, and we're creating it with + # the below context (mainly to substitute in the AWS Stack Name for the logging group.) + "/tmp/kubernetes-awslogs.conf": + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/kubernetes-awslogs.conf" + context: + StackName: !Ref AWS::StackName + + # Installation script for the Cloudwatch agent + "/usr/local/aws/awslogs-agent-setup.py": + source: https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py + mode: '000755' + + # systemd init script for the Cloudwatch logs agent + "/etc/systemd/system/awslogs.service": + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/awslogs.service" + + # setup kubelet hostname + "/tmp/setup-kubelet-hostname.sh": + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/setup-kubelet-hostname.sh" + mode: '000755' + + # Setup script for initializing the Kubernetes master instance. This is where most of the cluster + # initialization happens. See scripts/setup-k8s-master.sh in the Quick Start repo for details. + "/tmp/setup-k8s-master.sh": + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/setup-k8s-master.sh.in" + mode: '000755' + context: + LoadBalancerDns: !GetAtt ApiLoadBalancer.DNSName + LoadBalancerName: !Ref ApiLoadBalancer + ClusterToken: !GetAtt KubeadmToken.Token + NetworkingProvider: !Ref NetworkingProvider + NetworkingProviderUrl: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/${NetworkingProvider}.yaml" + DashboardUrl: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/dashboard.yaml" + StorageClassUrl: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/default.storageclass.yaml" + NetworkPolicyUrl: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/network-policy.yaml" + ClusterInfoBucket: !Ref ClusterInfoBucket + Region: !Ref AWS::Region + + commands: + # Override the AMI binaries with any kubelet/kubeadm/kubectl binaries in the S3 bucket + "00-kubernetes-override-binaries": + command: "/tmp/kubernetes-override-binaries.sh" + # Install the Cloudwatch agent with configuration for the current region and log group name + "01-cloudwatch-agent-setup": + command: !Sub "python /usr/local/aws/awslogs-agent-setup.py -n -r ${AWS::Region} -c /tmp/kubernetes-awslogs.conf" + # Enable the Cloudwatch service and launch it + "02-cloudwatch-service-config": + command: "systemctl enable awslogs.service && systemctl start awslogs.service" + # Setup kubelet hostname + "03-setup-kubelet-hostname": + command: "/tmp/setup-kubelet-hostname.sh" + # Run the master setup + "04-master-setup": + command: "/tmp/setup-k8s-master.sh" + Properties: + # Where the EC2 instance gets deployed geographically + AvailabilityZone: !Ref AvailabilityZone + # Refers to the MasterInstanceProfile resource, which applies the IAM role for the master instance + # The IAM role allows us to create further AWS resources (like an EBS drive) from the cluster + # This is needed for the Kubernetes-AWS cloud-provider integration + IamInstanceProfile: !Ref MasterInstanceProfile + # Type of instance; the default is m3.medium + InstanceType: !Ref InstanceType + # Adds our SSH key to the instance + KeyName: !Ref KeyName + NetworkInterfaces: + - DeleteOnTermination: true + DeviceIndex: 0 + SubnetId: !Ref ClusterSubnetId + # Joins the ClusterSecGroup Security Group for cluster communication and SSH access + # The ClusterSecGroupCrossTalk rules allow all instances in the same stack to communicate internally + # The ClusterSecGroupAllow22 rules allow external communication on port 22 from a chosen CIDR range + # The ClusterSecGroupAllow6443FromLB rules allow HTTPS access to the load balancer on port 6443 + GroupSet: + - !Ref ClusterSecGroup + # Designates a name for this EC2 instance that will appear in the instances list (k8s-master) + # Tags it with KubernetesCluster= or chosen value (needed for cloud-provider's IAM roles) + Tags: + - Key: Name + Value: k8s-master + - Key: KubernetesCluster + Value: + Fn::If: + - AssociationProvidedCondition + - !Ref ClusterAssociation + - !Ref AWS::StackName + # Also tag it with kubernetes.io/cluster/clustername=owned, which is the newer convention for cluster resources + - Key: + Fn::Sub: + - "kubernetes.io/cluster/${ClusterID}" + - ClusterID: + Fn::If: + - AssociationProvidedCondition + - !Ref ClusterAssociation + - !Ref AWS::StackName + Value: 'owned' + # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#cfn-ec2-instance-imageid + ImageId: + Fn::FindInMap: + - RegionMap + - !Ref AWS::Region + - '64' + BlockDeviceMappings: + - DeviceName: '/dev/sda1' + Ebs: + VolumeSize: !Ref DiskSizeGb + VolumeType: gp2 + # The userdata script is launched on startup, but contains only the commands that call out to cfn-init, which runs + # the commands in the metadata above, and cfn-signal, which signals when the initialization is complete. + UserData: + Fn::Base64: + Fn::Sub: | + #!/bin/bash + set -o xtrace + + CFN_INIT=$(which cfn-init) + CFN_SIGNAL=$(which cfn-signal) + + ${!CFN_INIT} \ + --verbose \ + --stack '${AWS::StackName}' \ + --region '${AWS::Region}' \ + --resource K8sMasterInstance \ + --configsets master-setup + + ${!CFN_SIGNAL} \ + --exit-code $? \ + --stack '${AWS::StackName}' \ + --region '${AWS::Region}' \ + --resource K8sMasterInstance + CreationPolicy: + ResourceSignal: + Timeout: PT10M + + # IAM role for Lambda function for generating kubeadm token + LambdaExecutionRole: + Type: "AWS::IAM::Role" + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Principal: + Service: ["lambda.amazonaws.com"] + Action: "sts:AssumeRole" + Path: "/" + Policies: + - PolicyName: "lambda_policy" + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Action: + - "logs:CreateLogGroup" + - "logs:CreateLogStream" + - "logs:PutLogEvents" + Resource: "arn:aws:logs:*:*:*" + + # Lambda Function for generating the kubeadm token + GenKubeadmToken: + Type: "AWS::Lambda::Function" + Properties: + Code: + ZipFile: | + import random + import string + import cfnresponse + def id_generator(size, chars=string.ascii_lowercase + string.digits): + return ''.join(random.choice(chars) for _ in range(size)) + def handler(event, context): + if event['RequestType'] == 'Delete': + cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) + if event['RequestType'] == 'Create': + token = ("%s.%s" % (id_generator(6), id_generator(16))) + responseData = {} + responseData['Token'] = token + cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) + return token + Handler: "index.handler" + Runtime: "python2.7" + Timeout: "5" + Role: !GetAtt LambdaExecutionRole.Arn + + # A Custom resource that uses the lambda function to generate our cluster token + KubeadmToken: + Type: "Custom::GenerateToken" + Version: "1.0" + Properties: + ServiceToken: !GetAtt GenKubeadmToken.Arn + + # This is a CloudWatch alarm https://aws.amazon.com/cloudwatch/ + # If the master node is unresponsive for 5 minutes, AWS will attempt to recover it + # It will preserve the original IP, which is important for Kubernetes networking + # Based on http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/quickref-cloudwatch.html#cloudwatch-sample-recover-instance + RecoveryTestAlarm: + Type: AWS::CloudWatch::Alarm + Properties: + AlarmDescription: Trigger a recovery when instance status check fails for 5 + consecutive minutes. + Namespace: AWS/EC2 + MetricName: StatusCheckFailed_System + Statistic: Minimum + # 60-second periods (1 minute) + Period: '60' + # 5-minute check-ins + EvaluationPeriods: '5' + ComparisonOperator: GreaterThanThreshold + Threshold: '0' + # This is the call that actually tries to recover the instance + AlarmActions: + - !Sub "arn:aws:automate:${AWS::Region}:ec2:recover" + # Applies this alarm to our K8sMasterInstance + Dimensions: + - Name: InstanceId + Value: !Ref K8sMasterInstance + + # This is the Auto Scaling Group that contains EC2 instances that are Kubernetes nodes + # http://docs.aws.amazon.com/autoscaling/latest/userguide/AutoScalingGroup.html + K8sNodeGroup: + Type: AWS::AutoScaling::AutoScalingGroup + DependsOn: K8sMasterInstance + CreationPolicy: + ResourceSignal: + # Ensure at least nodes have signaled success before + # this resource is considered created. + Count: !Ref K8sNodeCapacity + Timeout: PT10M + Properties: + # Where the EC2 instance gets deployed geographically + AvailabilityZones: + - !Ref AvailabilityZone + # Refers to the K8sNodeCapacity parameter, which specifies the number of nodes (1-20) + DesiredCapacity: !Ref K8sNodeCapacity + # Refers to the LaunchConfig, which has specific config details for the EC2 instances + LaunchConfigurationName: !Ref LaunchConfig + # More cluster sizing + MinSize: '1' + MaxSize: '20' + # VPC Zone Identifier is the subnets to put the hosts in + VPCZoneIdentifier: + - !Ref ClusterSubnetId + # Designates names for these EC2 instances that will appear in the instances list (k8s-node) + # Tags each node with KubernetesCluster= or chosen value (needed for cloud-provider's IAM roles) + Tags: + - Key: Name + Value: k8s-node + PropagateAtLaunch: 'true' + - Key: KubernetesCluster + Value: + Fn::If: + - AssociationProvidedCondition + - !Ref ClusterAssociation + - !Ref AWS::StackName + PropagateAtLaunch: 'true' + # Also tag it with kubernetes.io/cluster/clustername=owned, which is the newer convention for cluster resources + - Key: + Fn::Sub: + - "kubernetes.io/cluster/${ClusterID}" + - ClusterID: + Fn::If: + - AssociationProvidedCondition + - !Ref ClusterAssociation + - !Ref AWS::StackName + Value: 'owned' + PropagateAtLaunch: 'true' + # Tells the group how many instances to update at a time, if an update is applied + UpdatePolicy: + AutoScalingRollingUpdate: + MinInstancesInService: '1' + MaxBatchSize: '1' + + # This tells AWS what kinds of servers we want in our Auto Scaling Group + LaunchConfig: + Type: AWS::AutoScaling::LaunchConfiguration + Metadata: + AWS::CloudFormation::Init: + configSets: + node-setup: node-setup + node-setup: + # (See comments in the master instance Metadata for details.) + files: + "/tmp/kubernetes-override-binaries.sh": + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/kubernetes-override-binaries.sh.in" + mode: '000755' + context: + BaseBinaryUrl: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/bin/" + "/tmp/kubernetes-awslogs.conf": + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/kubernetes-awslogs.conf" + context: + StackName: !Ref AWS::StackName + "/usr/local/aws/awslogs-agent-setup.py": + source: https://s3.amazonaws.com/aws-cloudwatch/downloads/latest/awslogs-agent-setup.py + mode: '000755' + "/etc/systemd/system/awslogs.service": + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/awslogs.service" + "/tmp/setup-kubelet-hostname.sh": + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/setup-kubelet-hostname.sh" + mode: '000755' + "/tmp/setup-k8s-node.sh": + mode: '000755' + source: !Sub "https://${QSS3BucketName}.s3.amazonaws.com/${QSS3KeyPrefix}/scripts/setup-k8s-node.sh.in" + context: + K8sMasterPrivateIp: !GetAtt K8sMasterInstance.PrivateIp + ClusterToken: !GetAtt KubeadmToken.Token + ClusterInfoBucket: !Ref ClusterInfoBucket + + commands: + "00-kubernetes-override-binaries": + command: "/tmp/kubernetes-override-binaries.sh" + "01-cloudwatch-agent-setup": + command: !Sub "python /usr/local/aws/awslogs-agent-setup.py -n -r ${AWS::Region} -c /tmp/kubernetes-awslogs.conf" + "02-cloudwatch-service-config": + command: "systemctl enable awslogs.service && systemctl start awslogs.service" + "03-setup-kubelet-hostname": + command: "/tmp/setup-kubelet-hostname.sh" + "04-k8s-setup-node": + command: "/tmp/setup-k8s-node.sh" + Properties: + # Refers to the NodeInstanceProfile resource, which applies the IAM role for the nodes + # The IAM role allows us to create further AWS resources (like an EBS drive) from the cluster + # This is needed for the Kubernetes-AWS cloud-provider integration + IamInstanceProfile: !Ref NodeInstanceProfile + # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#cfn-ec2-instance-imageid + ImageId: + Fn::FindInMap: + - RegionMap + - !Ref AWS::Region + - '64' + BlockDeviceMappings: + - DeviceName: '/dev/sda1' + Ebs: + VolumeSize: !Ref DiskSizeGb + VolumeType: gp2 + # Type of instance; the default is m3.medium + InstanceType: !Ref InstanceType + # Adds our SSH key to the instance + KeyName: !Ref KeyName + # Join the cluster security group so that we can customize the access + # control (See the ClusterSecGroup resource for details) + SecurityGroups: + - !Ref ClusterSecGroup + # The userdata script is launched on startup, but contains only the commands that call out to cfn-init, which runs + # the commands in the metadata above, and cfn-signal, which signals when the initialization is complete. + UserData: + Fn::Base64: + Fn::Sub: | + #!/bin/bash + set -o xtrace + + /usr/local/bin/cfn-init \ + --verbose \ + --stack '${AWS::StackName}' \ + --region '${AWS::Region}' \ + --resource LaunchConfig \ + --configsets node-setup + + /usr/local/bin/cfn-signal \ + --exit-code $? \ + --stack '${AWS::StackName}' \ + --region '${AWS::Region}' \ + --resource K8sNodeGroup + + # Define the (one) security group for all machines in the cluster. Keeping + # just one security group helps with k8s's cloud-provider=aws integration so + # that it knows what security group to manage. + ClusterSecGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Security group for all machines in the cluster + VpcId: !Ref VPCID + # Security Groups must be tagged with KubernetesCluster= so that + # they can coexist in the same VPC + Tags: + - Key: KubernetesCluster + Value: + Fn::If: + - AssociationProvidedCondition + - !Ref ClusterAssociation + - !Ref AWS::StackName + - Key: + Fn::Sub: + - "kubernetes.io/cluster/${ClusterID}" + - ClusterID: + Fn::If: + - AssociationProvidedCondition + - !Ref ClusterAssociation + - !Ref AWS::StackName + Value: 'owned' + - Key: Name + Value: k8s-cluster-security-group + + # Permissions we add to the main security group: + # - Ensure cluster machines can talk to one another + ClusterSecGroupCrossTalk: + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: !Ref ClusterSecGroup + SourceSecurityGroupId: !Ref ClusterSecGroup + IpProtocol: '-1' + FromPort: '0' + ToPort: '65535' + + # - Open up port 22 for SSH into each machine + # The allowed locations are chosen by the user in the SSHLocation parameter + ClusterSecGroupAllow22: + Metadata: + Comment: Open up port 22 for SSH into each machine + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: !Ref ClusterSecGroup + IpProtocol: tcp + FromPort: '22' + ToPort: '22' + CidrIp: !Ref SSHLocation + + # Allow the apiserver load balancer to talk to the cluster on port 6443 + ClusterSecGroupAllow6443FromLB: + Metadata: + Comment: Open up port 6443 for load balancing the API server + Type: AWS::EC2::SecurityGroupIngress + Properties: + GroupId: !Ref ClusterSecGroup + IpProtocol: tcp + FromPort: '6443' + ToPort: '6443' + SourceSecurityGroupId: !Ref ApiLoadBalancerSecGroup + + # IAM role for nodes http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html + NodeRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - sts:AssumeRole + Path: "/" + # IAM policy for nodes that allows specific AWS resource listing and creation + # http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html + Policies: + - PolicyName: node + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - ec2:Describe* + - ecr:GetAuthorizationToken + - ecr:BatchCheckLayerAvailability + - ecr:GetDownloadUrlForLayer + - ecr:GetRepositoryPolicy + - ecr:DescribeRepositories + - ecr:ListImages + - ecr:BatchGetImage + Resource: "*" + + - PolicyName: cwlogs + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - logs:DescribeLogStreams + Resource: !Sub ["${LogGroupArn}:*", LogGroupArn: !GetAtt KubernetesLogGroup.Arn] + + - PolicyName: discoverBucketWrite + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - s3:GetObject + Resource: !Sub "arn:aws:s3:::${ClusterInfoBucket}/cluster-info.yaml" + + # Resource that creates the node IAM role + NodeInstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Path: "/" + Roles: + - !Ref NodeRole + + # IAM role for the master node http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html + MasterRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: + - ec2.amazonaws.com + Action: + - sts:AssumeRole + Path: "/" + # IAM policy for the master node that allows specific AWS resource listing and creation + # More permissive than the node role (it allows load balancer creation) + # http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html + Policies: + - PolicyName: master + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - ec2:* + - elasticloadbalancing:* + - ecr:GetAuthorizationToken + - ecr:BatchCheckLayerAvailability + - ecr:GetDownloadUrlForLayer + - ecr:GetRepositoryPolicy + - ecr:DescribeRepositories + - ecr:ListImages + - ecr:BatchGetImage + - autoscaling:DescribeAutoScalingGroups + - autoscaling:UpdateAutoScalingGroup + Resource: "*" + + - PolicyName: cwlogs + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - logs:DescribeLogStreams + Resource: !Sub ["${LogGroupArn}:*", LogGroupArn: !GetAtt KubernetesLogGroup.Arn] + + - PolicyName: discoverBucketWrite + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - s3:PutObject + Resource: !Sub "arn:aws:s3:::${ClusterInfoBucket}/cluster-info.yaml" + + # Bind the MasterRole to a profile for the VM instance. + MasterInstanceProfile: + Type: AWS::IAM::InstanceProfile + Properties: + Path: "/" + Roles: + - !Ref MasterRole + + # Create a placeholder load balancer for the API server. Backend instances will be added by the master itself on the + # first boot in the startup script above. + ApiLoadBalancer: + Type: AWS::ElasticLoadBalancing::LoadBalancer + Properties: + Scheme: !Ref LoadBalancerType + Listeners: + - Protocol: TCP + InstancePort: 6443 + InstanceProtocol: TCP + LoadBalancerPort: 443 + ConnectionSettings: + IdleTimeout: 3600 + Subnets: + - Fn::If: + - LoadBalancerSubnetProvidedCondition + - !Ref LoadBalancerSubnetId + - !Ref ClusterSubnetId + SecurityGroups: + - !Ref ApiLoadBalancerSecGroup + Tags: + - Key: KubernetesCluster + Value: + Fn::If: + - AssociationProvidedCondition + - !Ref ClusterAssociation + - !Ref AWS::StackName + - Key: + Fn::Sub: + - "kubernetes.io/cluster/${ClusterID}" + - ClusterID: + Fn::If: + - AssociationProvidedCondition + - !Ref ClusterAssociation + - !Ref AWS::StackName + Value: 'owned' + - Key: 'kubernetes.io/service-name' + Value: 'kube-system/apiserver-public' + + # Security group to allow public access to port 443 + ApiLoadBalancerSecGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Security group for API server load balancer + VpcId: !Ref VPCID + SecurityGroupIngress: + CidrIp: !Ref ApiLbLocation + FromPort: 443 + ToPort: 443 + IpProtocol: tcp + Tags: + - Key: Name + Value: apiserver-lb-security-group + + # Give the cleanup lambda function the necessary policy. + CleanupClusterInfoRole: + Type: "AWS::IAM::Role" + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: "Allow" + Principal: + Service: ["lambda.amazonaws.com"] + Action: "sts:AssumeRole" + Path: "/" + Policies: + - PolicyName: s3upload + PolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: arn:aws:logs:*:*:* + - Effect: Allow + Action: ['s3:DeleteObject'] + Resource: [!Sub "arn:aws:s3:::${ClusterInfoBucket}/cluster-info.yaml"] + + # CleanupClusterInfo backs the custom resource defined below. + # When the custom resource gets created, deleted or updated this function will be executed. + CleanupClusterInfo: + Type: "AWS::Lambda::Function" + Properties: + Code: + ZipFile: + Fn::Sub: | + import boto3 + import cfnresponse + + def lambda_handler(event, context): + try: + s3 = boto3.client('s3') + bucket = '${ClusterInfoBucket}' + key = 'cluster-info.yaml' + + if event['RequestType'] == 'Delete': + s3.delete_object(Bucket=bucket, Key=key) + + cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) + return + except Exception as e: + print(e) + cfnresponse.send(event, context, cfnresponse.FAILED, {}) + Handler: "index.lambda_handler" + Runtime: "python3.6" + Timeout: "5" + Role: !GetAtt CleanupClusterInfoRole.Arn + + CleanupClusterInfoOnDelete: + Type: "Custom::CleanupClusterInfo" + Properties: + ServiceToken: !GetAtt CleanupClusterInfo.Arn + + +# Outputs are what AWS will show you after stack creation +# Generally they let you easily access some information about the stack +# like what IP address is assigned to your master node +# Read Descriptions below for more detail +Outputs: + MasterInstanceId: + Description: InstanceId of the master EC2 instance. + Value: !Ref K8sMasterInstance + + MasterPrivateIp: + Description: Private IP address of the master. + Value: !GetAtt K8sMasterInstance.PrivateIp + + NodeGroupInstanceId: + Description: InstanceId of the newly-created NodeGroup. + Value: !Ref K8sNodeGroup + + JoinNodes: + Description: Command to join more nodes to this cluster. + Value: !Sub "aws cp s3://${ClusterInfoBucket}/cluster-info.yaml /tmp/cluster-info.yaml && kubeadm join --node-name=\"$(hostname -f)\" --token=${KubeadmToken.Token} --discovery-file=/tmp/cluster-info.yaml ${K8sMasterInstance.PrivateIp}:6443" + + NextSteps: + Description: Verify your cluster and deploy a test application. Instructions - + http://jump.heptio.com/aws-qs-next + Value: http://jump.heptio.com/aws-qs-next diff --git a/cmd/provider/aws/templates.go b/cmd/provider/aws/templates.go index c5d88e31..fc036238 100644 --- a/cmd/provider/aws/templates.go +++ b/cmd/provider/aws/templates.go @@ -68,7 +68,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _cmdProviderAwsTemplatesFormationYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x3c\x6b\x73\x1b\x37\x92\xdf\xf9\x2b\x3a\x8c\x36\x92\xb3\xe2\xf0\x21\xd9\x2b\xf3\xd6\xa9\xa3\x28\x59\xe6\x49\x96\xb8\xa2\xac\x54\x72\xd9\x73\x81\x33\x20\x89\xd5\x10\x98\x00\x18\x49\x8c\x4e\xf7\xdb\xaf\xf0\x9a\x19\xcc\x0c\xf5\x70\x62\xfb\x6e\x6b\xe3\xaa\x94\x08\x34\x1a\xfd\x42\x77\xa3\x01\x4c\xa3\xd5\x6a\x35\x06\x3f\x4e\x2e\xf0\x32\x89\x91\xc4\x6f\x19\x5f\x22\x79\x89\xb9\x20\x8c\xf6\x61\xb3\xd7\xe9\x76\x5a\x9d\xd7\xad\xce\xeb\xcd\xc6\x01\x16\x21\x27\x89\x34\x3d\xc7\xe9\x14\x73\x8a\x25\x16\x30\xf8\x71\x02\xc3\x98\xa5\x91\x19\x4d\x18\x05\x87\xaf\x0f\x43\x8e\x91\xc4\x80\x20\x1f\xd0\x00\x08\xe3\x54\x48\xcc\x81\x50\x40\x40\xf1\x0d\x5c\x8e\x87\x01\x5c\x2c\x30\x2c\x91\xee\xa0\x2c\xc2\x40\x04\x20\x0a\x28\x95\xac\xc5\x71\xc8\xae\x31\x27\x74\x0e\x83\x25\xfa\x8d\x51\x38\x1c\xf6\x1a\x00\x84\x0a\x89\x68\x88\x03\xe8\xb6\x7a\x1d\x40\x51\x44\x14\x01\x28\x56\xfd\x59\xaf\xd0\x13\x51\x18\xa4\x92\x4d\x42\x14\x13\x3a\x3f\xe2\x2c\x4d\xe0\x1f\x8c\x50\x90\x0b\xdc\x80\x02\x7d\x19\x75\x48\x68\x3a\x44\x00\x03\x0a\x87\x27\xfb\x90\x70\x76\x4d\x22\x05\xc1\xe8\x8c\xcc\x53\x8e\xa6\x31\x06\x7c\x2b\x31\x57\x53\xa2\x30\xc4\x42\xb1\x27\x99\x42\x5a\x44\x39\x18\x8f\x0c\x7f\x96\x59\x20\x34\x8c\x53\x85\x0a\xc1\x14\x09\x2d\xb4\x05\x13\x52\x0d\x9d\x73\x44\x65\x03\x60\x32\x79\x67\x51\x3a\x84\x09\x27\xd7\x4a\x9a\x22\x9d\x52\x2c\x61\xc6\xb8\x6e\xb6\xf4\xaa\x09\x88\x00\x69\x65\x0f\xa1\x16\xbd\xa6\xe7\x86\x81\x90\x28\xbc\x12\x7d\x60\x14\x67\x03\x1d\x31\x88\x46\x5e\x7b\x01\x21\x36\x03\x81\x28\x3c\x22\x25\x52\xb3\xac\x00\x23\x7c\x8d\x63\x96\x2c\x31\x95\x1a\x81\x58\xa2\x38\x06\x41\xe8\x3c\xc6\x2d\x89\xd1\xd2\xa1\x11\x01\x7c\xff\xfd\x8f\x83\xf3\xd3\xd1\xe9\xd1\xf7\xdf\x6b\x22\x15\x4d\x25\x32\x61\xc6\x52\x5e\x50\x6e\x41\x79\x37\x44\x2e\x20\xc2\x33\x94\xc6\x12\x04\x96\x92\xd0\xb9\x08\xe0\x27\x96\xc2\x0d\x89\xe3\x06\xc0\x14\xc3\x94\xc4\x31\x8e\x32\x16\x94\x51\x72\x2c\x58\xca\x15\x86\x54\xe0\x08\xc8\x0c\x56\x2c\xb5\xf3\x01\xb2\x8c\xcd\x38\x5b\x82\x2c\x0a\x2e\xd8\x6c\x34\xbe\xd5\xac\xbf\xc7\x12\x45\x48\x22\x90\x38\x8e\x8d\xa1\x2f\xd8\x8d\xd2\x46\x44\x44\x12\xa3\x95\xd1\x0a\xe2\x68\x89\x15\xa3\x10\xa5\xda\x44\x0d\x66\x3d\x11\x61\xb4\xe1\xd0\xf4\x1b\xa0\x70\xf4\xfb\xfe\x6a\xe9\xf7\x47\x54\x62\x3e\x43\x21\x56\x10\x00\x63\x87\x50\x1b\xa9\x30\x8d\x2d\x38\x41\x53\x1c\x9b\x1f\xea\x3f\x2b\x90\x3e\x9c\xe3\x5f\x53\xc2\x71\x64\x7b\xb2\xd1\xc2\xc1\xb6\xe0\x67\x46\x71\xc7\xfb\xd5\xcd\x7e\x0d\xa2\x25\xa1\x23\x3a\xe7\x58\x88\x13\x16\x1a\x92\x5d\xe7\x31\x5e\x9d\xa2\x25\x7e\x84\x82\x41\x74\xad\x34\xf5\x10\x05\xa7\x58\xde\x30\x7e\x45\xe8\x7c\x6c\x16\x11\xcf\xe7\xd8\x13\xa7\x2c\xc2\x43\x94\xa0\x90\xc8\x55\xd6\x3e\xb2\x06\x70\xb1\x4a\x70\xd6\x78\x40\xc4\xd5\x84\xfc\x86\x8f\xa6\x59\xd3\xbe\x59\x3f\xb5\xe0\x7f\x9b\x4c\x76\xf6\xd3\xf0\x0a\xcb\x8c\x0d\xd7\x7c\x8c\x57\x63\x8e\x67\xe4\xb6\xe1\x0b\x5d\x33\x99\x51\x6e\x05\x50\xc3\xb4\x5a\x9e\xc7\xd8\x91\xab\x25\x5c\x27\x9a\x6b\x44\x62\x34\x25\x31\x91\x2b\x0d\x54\x80\xef\xd6\x61\xc5\x21\xa3\xd1\xda\x61\x75\xca\xaa\x55\xc8\x92\x50\xb0\x70\x50\xd2\x6a\x51\x50\x35\x63\x5d\x37\x14\x04\x99\x4b\xbd\x66\x80\xea\x04\xd5\x0b\x5b\x47\x64\xff\x85\x05\xa8\xd1\xca\x63\x93\xc1\x96\x1d\x04\xef\x98\x90\x0e\x51\xc9\x3c\x6a\x90\xa8\x6e\x28\x99\x8f\xaf\xf8\x3a\x41\xef\x80\xe9\x2f\x0c\xc8\x4c\xa2\x1e\xfe\x18\xaf\xc0\x9a\x8c\xe9\xac\x1a\x75\x1d\x75\x19\x10\x64\xa6\x6f\xfd\x4b\xbe\x50\x00\xc5\xb1\xf2\x2c\x0b\xac\x7c\x15\x57\x2e\x26\x41\x42\x40\x98\x0a\xc9\x96\x99\xd3\x73\x81\xc0\x38\x98\x29\x9e\x31\x8e\x73\x3f\xe3\xaf\x3b\xcf\x72\xbd\xe0\x7d\x78\x4b\x84\x42\xa7\xdd\xac\xe2\x19\x11\xae\x1d\x67\x1e\x71\x02\x3d\x4a\x2b\xcd\xb8\xac\xc3\x61\xaf\xdf\xb7\xb0\xfa\x8f\x6c\x3d\x0d\x19\x15\x92\x23\x42\xa5\x37\xc9\x32\x15\x52\xf9\x65\x1d\x67\xd0\x12\x03\x9b\xa9\x10\x8c\x6b\xe6\x0e\x1a\x86\xdc\xf7\x48\x62\x4e\x50\xfc\x54\x92\x1d\xfc\x03\xb4\x4f\xa4\x72\xc8\x0d\x00\xe5\x7a\x13\x72\x8c\x57\x35\xc8\xc7\x48\x88\x1b\xc6\x4d\xf0\x38\x40\x12\x85\x2c\x56\x6c\x49\xce\xe2\x18\x73\x15\xbc\xa1\x8a\xb3\x01\x0e\xd6\xe5\x4b\x55\xcc\xd7\xa6\x47\xf1\x5e\x8f\xb7\x16\xed\x80\x4b\x32\x43\xa1\x34\xf6\x59\x83\xf6\xd4\xca\x73\xce\xd8\x3c\x56\xd6\xc0\x38\x9a\x63\x98\x6a\x78\x1d\x9e\xd8\x0d\x8d\x19\x8a\x20\xb2\xb3\x4e\x09\x45\x7c\x55\x3b\xdb\xc4\xd8\xd6\xda\xc9\x2c\x56\x25\x1b\x35\x91\x52\x42\xa8\x16\x9c\x8a\xf9\x98\x5e\x13\xce\xa8\xce\x01\xae\x11\x27\x2a\x39\x10\xb5\xb3\x54\xfd\x80\xaf\xdf\x42\xc0\x07\xa9\x7c\x41\x39\x17\xa9\x53\xaa\x42\x62\x57\x99\xec\x05\x4b\x1c\x91\x74\xa9\x9b\x07\x6a\x35\xe1\xe8\x12\xc5\x29\xce\xe2\xa7\xec\x05\x14\x51\x96\xff\x5a\x92\x90\x17\x7e\xea\x04\xa6\xd0\x9b\xa3\xd3\xbf\x63\xc4\xe7\x2e\x10\x2e\x77\xfc\xee\xe5\x4e\xb9\xfb\xb6\xf4\xbb\xe7\x37\xec\x06\xa5\x9f\xe5\xee\x0a\xfc\x6e\xb9\xa1\xdb\xf1\x5a\x42\x9f\x84\xb0\x44\x42\x58\x26\x21\xdc\x29\xa1\x0c\x77\x82\x3d\xbf\xc1\x27\x32\x2c\x11\x19\x96\x89\x0c\xcb\x44\x86\xbb\x25\x8c\xf3\x5e\x69\xc8\xbc\x57\x82\xe0\x3e\x17\xbc\xc4\x05\x2f\x73\xc1\xcb\x5c\xf0\x32\x17\xa4\x17\x94\x7f\xf7\xca\x0d\xbb\xe5\x06\x1f\x45\x54\x42\x11\x95\x51\x44\x65\x14\x91\x8f\xe2\x11\x17\x89\xe0\x1a\xc5\x24\xaa\x2e\x02\xed\x18\xbf\x85\x49\x82\x43\x32\x23\x58\x18\xdf\xaf\x22\x2d\x9b\xe9\xbf\x39\x63\x52\xe5\xa2\x57\x7a\xb9\xa8\xfc\xdb\xcb\x9c\xb7\xed\x36\x43\xad\x59\xb3\xb1\xd2\xf8\xd4\xca\x35\x1b\x9b\x46\x35\xb4\xfb\xdb\xbc\xc9\xda\xc9\x54\x4b\x65\x32\x38\x22\xfb\x41\x61\x55\xee\x76\x36\xfd\x65\xba\xdb\x29\xac\xe3\xd3\x74\x39\xb5\x79\xe0\x7b\x42\xf5\x62\xed\xc3\x9e\xf9\x8d\x6e\xed\xef\x6e\xa7\xb7\xab\xc4\xb0\x36\x9f\x78\xa2\x1f\xf1\x76\x59\x5b\x7a\x43\xa0\xba\x92\x74\x1a\x93\xb0\x10\x3d\x5e\x3c\xc5\xd3\x64\x8e\xe3\x5f\x8e\xe6\x5f\x8e\xe6\x9f\xc5\xd1\x14\xb6\x30\xde\x68\x95\xa9\xba\xdd\x77\x65\x5f\x62\xd7\x17\x11\x26\x27\x0d\xb2\x3c\x87\xe3\x90\x2d\x97\x98\x46\xc2\xa6\xc4\x72\x81\xa4\xde\x80\xf3\x94\xea\x6a\x83\x2b\xb3\x24\x2a\x15\xfa\x59\x7b\xa5\x54\x60\x90\x8c\xc5\xca\x61\x49\x06\x21\x63\x3c\x22\x54\x6f\xd7\x43\xce\x84\x80\xc1\xcf\x6b\x52\xd3\x22\x61\x8a\xae\x7e\xff\x13\x13\x54\xb5\xc9\xaf\xee\xbe\x1a\xde\x8e\xad\x22\x1e\xb1\x66\xd7\x56\x96\xce\x57\x22\x7d\xfd\xae\xd1\x43\x3b\x1c\x1d\x9c\xc3\x34\x66\xe1\x15\x6c\x8d\xc6\x80\xa2\x48\x6f\x1f\x39\xa2\x73\xfc\x42\xe9\xc3\x6c\x52\xaa\x75\x29\xab\x61\xcf\xc5\x2a\x75\xbe\xbb\xb8\x18\x4f\x4a\xb0\xe5\xa2\xd8\x07\x81\xa1\x13\xe8\x7f\x6d\x57\xa2\xc8\xa6\xb2\x43\x75\x8d\x46\x45\xb7\xd8\x12\x5f\x9f\xe3\xeb\x30\x72\x82\xe9\x5c\x2e\xfa\xb0\xf9\x7a\xd3\x45\x92\xac\xa9\xbb\xb7\x59\xf4\xda\x63\x24\x25\xe6\xb4\x0f\xcd\xad\x5f\x7e\x89\xee\xba\xdb\x3b\xf7\x2f\x7e\xf9\x25\x78\xca\x8f\xb6\xfd\xb3\x77\xff\xa2\xf9\x9c\x95\x37\x1a\x1b\x29\x6b\x99\xba\xc0\x3a\x63\x7c\x09\xb7\x81\xfe\xd7\xbe\x35\xbb\xa1\xba\x4d\x6f\x16\x82\x36\x7b\x9b\x55\xed\x8d\x28\x91\x6a\x33\x44\x75\x54\x55\xb8\x0b\xa2\xd6\xe1\x1e\xb6\xba\xad\x5e\xc7\x0b\x70\xb5\x21\x78\xb3\xbb\x59\x0a\xc2\x9b\x3d\x1b\xc8\x1f\x61\x73\x8a\xe5\x0d\xc6\x14\xba\x5a\xfd\xbd\x8e\x9f\x21\xb8\x74\xc6\xed\xbc\xf3\x32\xaa\xc1\xa2\x75\xae\x8b\x75\x5c\x9b\x8b\xf6\x06\x0b\x4c\x38\xb0\x1b\xaa\xf7\x34\x42\x72\x8c\x96\x20\x28\x4a\xc4\x82\x49\xa1\xf1\x59\x29\xfe\x9a\x92\xf0\x4a\x48\xc4\x65\x0b\xdd\x88\xd6\x75\x12\x6a\x22\x0a\xcd\x31\xa1\xe9\x6d\xcb\x59\xa9\x2b\xf7\x29\x24\x75\xd5\x82\x8a\x91\xfc\xd7\x7f\x76\x5a\xaf\x51\xeb\xb7\x41\xeb\xe7\xbf\xff\x79\x2b\xff\xd1\xfa\xfb\xf7\x85\x9e\x17\xdf\x6f\x3c\x68\x11\x7f\x53\xf4\xc0\x44\x11\xe4\xb6\x56\x7a\x09\x87\x88\xba\xa2\xb0\x55\xa1\xd8\x06\x45\x01\x0f\x91\x70\x75\x98\x18\x2b\x62\xc4\x36\xa4\x49\x62\x3a\xf2\x26\xc5\xec\x62\x95\x2c\x30\x15\xb0\xd5\x7a\x11\xc0\x48\x2a\xa4\x94\x49\xd0\xec\x03\xe3\x80\x69\x64\xaa\xa9\xc8\x82\x5a\xbc\x0a\xbe\xe1\xdb\x58\x41\x6e\x1c\xcf\x30\xc7\x34\xc4\x55\xa3\x3b\xa3\xf1\x0a\xc2\x85\xb6\x66\xed\xe7\x6c\x91\x75\x81\xae\x95\x57\x94\x90\x26\x80\x84\xc0\x52\x31\x43\xae\xb0\xea\x34\xea\xa4\x59\x59\xc4\xd2\xe0\x19\xc3\xb6\xad\xd8\x4f\x76\xac\x90\x6c\x71\x5b\x31\xa9\x94\xed\x55\x63\xf2\x0a\xac\x92\x86\x9e\x3f\x2b\xc5\xab\xff\x0c\xbd\xc2\x95\x7a\xb1\x6b\x68\x1b\x74\xce\x0e\xda\x10\x11\x8e\x43\xb5\xc7\xc5\x42\x59\x55\x46\xeb\x8c\xf1\x2b\x67\x66\xef\xb0\x62\xdd\xb9\xbc\xa2\x36\x1d\x9f\x69\xa2\xb6\xde\x38\x52\x26\x3c\xd9\x31\xb5\x71\xc9\x38\x8e\x00\xb9\x62\xd3\xc6\x9d\xe1\x4a\x69\xfe\x3e\x10\x3b\x01\xd2\x75\x6f\x74\x23\x82\x90\x2d\xdb\x1b\x77\x89\x66\xec\xbe\xed\x28\x15\x6c\x89\x67\x24\xc6\x81\xbc\x95\xc1\x64\xc7\x54\xe6\x0b\xe6\xe3\x64\xf8\xa0\x11\x3d\xcb\x7c\x2c\xc6\xa7\x18\x51\x0e\x5f\x2d\xba\xd4\x94\xd5\x1e\x5a\x58\xad\xbf\xff\x79\xab\xed\xfd\x7c\xce\x7a\xba\xc2\x2b\x30\x92\x7b\xaa\x24\x2c\x97\x35\xf2\x28\xc8\xc2\x08\x67\xc6\xf8\x0d\xe2\x11\x88\x18\x89\x05\x6c\xb5\xd7\xae\x30\x8b\x53\x8b\xa8\x32\x08\xa6\x38\x44\xd6\xb1\xad\x00\x71\xac\xcf\xb7\x96\x48\x92\x10\xc5\xf1\x0a\x50\x92\x60\x1a\xe1\x28\xf0\x57\xe3\x42\x5b\x5d\x5b\xdb\xa9\xfc\x3d\xcb\xb0\xbc\xae\xb6\x01\x09\xc0\xb7\x49\x8c\x08\xcd\x0a\xf8\xe6\x50\xac\xe0\xa8\xb3\x05\x66\x0c\x6f\xb2\xf3\x05\x45\xad\xe2\xd6\x7a\x61\x6b\x31\x5b\xd4\x35\xc3\x9e\x27\xee\x72\xd1\x6a\x5d\x7d\xb7\x76\xd7\x17\xa2\x98\x84\x6e\x97\x77\x83\xd1\xf5\x83\x29\xdc\xe6\x30\xe5\x1c\x53\x19\xaf\x40\xa4\x49\xc2\xb8\xc4\x91\xca\x11\x52\x2c\x34\x99\x4d\x83\xae\xa9\xe5\xd1\xd4\xe8\x9a\xa5\xbd\x74\x61\x42\x3f\x8d\x5b\x30\x26\x6c\x72\x98\xd7\x9e\xed\xd9\x25\x77\x81\x55\x65\xa6\x2a\x43\x4f\x29\x31\x39\x95\x0b\xde\x56\x98\x09\x8b\x84\x33\x84\xea\xd9\x68\x00\x93\x8c\x6a\xcf\x71\x1b\xea\x0d\x69\xce\x49\x6e\x2d\xa4\x4c\xfa\xed\x76\xc4\x42\x11\x24\x9c\xfd\x03\x87\xd2\x40\x04\x8c\xcf\xdb\xd7\xbd\xa0\xd3\x9e\x9b\x2a\x64\x4b\xeb\x16\x47\xed\xab\x6c\xca\xb6\xce\x1f\xe2\x58\xa3\x6f\xab\xdc\xd2\x76\xa3\x68\xd9\x76\x87\x04\x4a\x4a\x5a\x48\x66\x32\xd1\x6f\xb7\xe7\x44\x2e\xd2\xa9\xf6\xa7\xba\x47\x09\x42\x98\x3f\xdb\xd3\x98\x4d\xdb\xa6\x22\xd2\x16\x44\x62\x8d\xaf\x85\xa2\x88\xd1\x60\x19\xd5\x39\x33\x73\x74\x37\xd0\x61\x25\xab\x22\xd7\x40\x4c\x70\xc8\xb1\x7c\x04\xce\x1c\x8a\x1f\xef\x89\xa1\x91\x66\x29\xbd\x93\x3c\xad\x09\xb5\xd9\x49\x7a\x2e\x1a\x7b\x0e\xc0\x38\xa8\x65\xb1\xb5\x64\x42\xd9\x53\x56\xdb\x28\x9e\xd0\x26\x29\x4f\x98\xc0\xc1\x8b\x75\x06\xac\x8b\x0f\x6e\x62\xf5\x63\x86\x62\x9b\x77\x94\xc9\xbf\x1c\x0f\x55\x12\x5b\xc3\x9b\xc7\x46\xb3\xeb\xf2\xfa\xee\xab\x66\x95\x9f\xe6\xe5\x78\x58\xd8\x72\x34\x4d\xbc\x98\xe8\xa3\xed\xce\xd3\xf1\x77\x83\x4e\xbb\xb7\x5b\x87\x7f\x6c\x4a\x3b\xf6\xb0\xbc\xe3\xcd\x95\xcd\xd4\x7d\xfa\x4c\xbd\x27\xce\xd4\xad\x9f\x69\x6c\x0e\xef\x9f\xc1\xda\xce\xfa\x09\xfd\x8b\x00\x6b\x78\xb3\x50\xcf\x60\x71\xf7\xa9\x33\x96\x78\x7c\x8f\x92\x84\xd0\xb9\x36\xa5\x73\x3c\x27\x8c\xbe\x47\x89\x99\x12\x25\x2d\xca\xb8\x5c\x60\x24\x64\x2b\x3b\xf1\xdc\x7c\xb5\xbb\xd9\x07\xb4\x24\xad\xee\x1e\x9a\x85\xbb\x7f\x99\x55\x81\x7b\x55\xe0\xd7\x3b\xd1\xab\x4e\x67\x16\x39\x60\xc1\x52\xb9\xa8\xc3\xba\xf7\x12\xcf\x5e\x77\x31\xf2\x00\xd7\x91\x30\xed\xed\xf5\xba\xaf\xa2\x6e\x15\xb8\x86\x04\xfc\x6a\xfa\x72\x0f\xef\xbd\x6c\x98\x7c\xab\x15\x62\x2a\x39\x8a\xeb\xf0\xfe\xa5\xdb\xed\xa1\x4e\xd7\x80\xe2\xf4\x21\xd0\x19\xde\xed\xec\x75\x5e\x77\x1d\xe8\x0d\xae\x27\x35\x44\x7b\x1d\xd4\x99\xbe\xf6\xe0\xea\xa8\x8c\x70\x0f\xef\xed\x19\x38\x81\x5a\xeb\x58\xc7\x9d\xbf\xbc\xc4\xd1\x5e\xa8\x3b\x52\xb1\x16\xee\x75\x14\xce\xa2\xe9\x1e\xf2\xe0\x6a\xe6\x9d\x85\x61\xf7\xf5\xf4\xf5\x6b\x07\xb7\x8e\x8f\x69\xe7\x65\xaf\xb3\x13\x75\x3c\xb8\x1a\x7c\xd3\x5e\xb4\xfb\x6a\x27\xea\x35\x1a\x43\x46\xcd\xdd\x1e\x6d\x64\x1f\xc4\x21\x12\xb2\x9b\x35\x9a\x91\x6f\x69\xbf\x7f\xf8\x6b\x8a\xe2\x2c\x28\x7f\x73\x8e\x67\xa6\xd4\x62\xcc\xd2\x36\x37\x33\x56\x95\xad\x4f\x16\x2c\x8d\xa3\xcc\x29\xf7\x61\x0d\xb2\xcc\x3d\x5a\xbc\x65\x37\xde\x68\x9c\xbb\x1b\x27\x7d\xbd\x33\xcd\x7e\x6a\x77\x6c\x6f\xdb\x18\x07\xda\xaf\x2d\x05\x99\x6e\x80\x31\x67\x09\xe6\x92\xe4\xce\x79\x48\x22\xbe\xaf\x56\x9b\xda\x9e\xe7\x7e\x75\xd3\x76\x1f\x52\x34\x8d\xf1\x01\x15\x36\x2c\xf7\x61\x53\x51\x5b\xe9\x7e\xc7\x84\xde\x74\x88\x12\xc0\x05\x9a\x17\xe2\x80\x0a\x5c\x50\xb8\x30\x01\x60\xeb\x00\xb9\x38\x27\x2a\xf2\x68\x90\x06\xc0\xc1\xbb\xe1\xf8\x2c\xc9\x94\x53\xe5\xab\x00\xb0\x86\xbf\x03\xb6\x44\x84\xfa\x67\xf5\xdf\xe6\x16\x09\x14\xe3\x48\x40\x80\xc3\x5e\x40\xa8\xb9\x6f\xb5\x6d\x4e\x25\xb0\x90\xd9\x09\x85\xd6\xb1\x80\x39\x96\xf0\x57\xf3\xe3\x07\x95\x00\x24\xa9\xc4\xd9\xb0\xa0\x80\x7f\x82\x31\x14\x93\x13\xb5\x01\x33\x5b\x31\x9d\x37\x98\xdb\x48\x97\xe3\xa1\x4d\xbc\xdb\x1f\x04\xe6\x47\x29\x89\x70\xfb\x72\x3c\xfc\xa8\xb8\xfa\x68\xd9\x0a\x16\x72\x19\x67\x98\x95\xed\x8c\x66\x39\x27\xad\x8a\xc1\x16\xba\x9a\x45\xa6\x9a\x85\x8e\x6f\x26\xe9\x14\x9a\x1b\x77\x05\xfb\xbd\xaf\xb0\xd3\xac\x08\x70\x82\xf9\xb5\x77\xf7\xc6\xb0\x61\x53\xd8\xe8\xe0\x74\x62\x83\x78\x41\x2b\x03\x21\x58\x48\x0a\x85\xc1\x1a\xcb\xac\x07\x5f\xa3\xcf\xcb\x24\x1c\x45\xd6\x60\x9c\x55\x03\x1c\x2c\xc2\xc4\xa2\xc8\x7a\x8b\xb6\xa1\x0f\x8a\xa5\xce\x6e\x8e\x90\xc4\x37\x68\x55\x4f\x4d\x09\x68\x0d\x0d\x75\x46\x6d\x12\xe2\xb2\x5d\x9b\x00\x6e\xc5\x62\x91\x0e\xa4\x44\xe1\x42\x25\x4f\x6b\x25\x52\x81\x7c\x96\x30\x4a\x4c\x64\x10\x65\xe6\x94\x2b\xf9\x36\xcb\xb2\x75\x2e\x6f\xf2\x0d\x95\xf4\xfa\x57\x02\x55\xc6\x2e\x48\x84\xe1\x37\x46\x71\x4b\x79\x6b\x1b\xb7\x6d\x5a\x55\xcf\x89\xe9\x7c\x16\xed\x05\x77\xa4\x7b\xaa\x29\x8e\x05\xac\x14\xb7\x0d\x7c\xf1\x42\xda\x13\x7d\x8f\x9f\x81\x3c\x4d\xad\x66\x88\x92\xa0\xd1\xf0\x97\x11\xc3\x67\xe2\xbf\x98\x64\x3e\xc7\xaa\x3d\xd0\x7c\x2b\xe7\x82\x56\x69\x50\x9d\x8b\x37\x00\xef\x51\x62\x30\x8e\x92\x33\x7a\x82\x52\x1a\x2e\xec\x5e\x45\x07\xbb\x8b\x05\x86\xd3\xc1\x05\x8c\xc6\xd9\x99\xab\x6f\x9c\xba\xd4\x20\x30\xa6\xa6\x10\xa7\x36\xee\x76\x7f\x69\xed\xd9\x1d\x98\x64\xc6\x7b\x3a\xb8\x38\x1c\x8d\xb3\x33\x29\xb5\x55\x17\x67\xb4\x5f\xbb\x48\x6b\x55\x7a\x38\x1a\x3f\x18\x6f\xfa\x70\x9d\x84\x3e\xf9\x73\x83\x78\x0d\x0f\x15\xfa\x2c\x1d\xbf\x83\xc6\x53\xf4\x88\x1b\x53\x9b\x35\xb3\x45\xd7\x16\x78\x84\xe5\x40\x4a\x27\x9b\xa0\xd8\x6b\x07\x18\x2b\xcc\xcc\xd5\x33\xfd\x46\xc5\x27\x9c\xb3\x54\xe2\x0b\x95\x1f\xd4\x2f\x8b\xbc\xff\x59\x4b\xe3\x93\x56\xb5\x78\xf6\xb2\xae\xb2\xf2\xe9\x9a\xd0\xc3\xd7\x30\x99\x4b\x21\x97\xeb\x1a\x31\x3a\x0b\xc3\x42\x12\xaa\x35\x53\x70\x13\xe5\xa3\xae\x5c\xfb\x19\xde\x82\x55\x3d\xa4\xad\x47\x43\xb6\x19\x51\x3b\x60\x0d\x93\x65\xc3\xf1\x66\xfe\x04\x41\x94\xfd\xee\x57\x36\x35\x7b\xd5\xe3\x39\x96\x96\xe5\x05\x55\x3e\xbe\x9c\x9d\xd5\x8b\xf0\x59\x66\xf6\x84\x1c\x63\xcd\x34\x5f\xc0\xcc\x3c\xff\xf4\x6c\x31\x7c\x7a\x7e\x34\x2d\xaf\xae\xee\x97\xc9\x8f\xba\x4f\xcb\x0f\xba\x5f\x2b\x3f\xfa\xdc\x62\xf8\x4c\xfc\xff\xbe\xfc\xe8\x6b\x65\x37\xdd\xff\x07\xd9\xcd\xef\xa0\xf1\x77\x65\x37\xdd\x67\x66\x37\xdd\x4a\xbc\xec\xfe\xf3\x64\x37\xdd\xaf\x98\xdd\x74\x3f\x47\x76\xf3\xa0\xb6\xbe\x70\x76\xd3\xfd\x04\x41\x94\xbd\xe6\x3f\x49\x76\xf3\xa5\xed\xac\x5e\x84\x9f\x33\xbb\xf9\xb2\x66\xe6\xf9\xa7\x4f\x10\x43\x03\xc0\x5d\x4e\xaa\x90\x38\xd9\xe9\xf7\x0b\xaf\x98\xaa\xc4\x14\x2e\x35\xd9\x08\x5c\x7c\xed\x61\x63\x03\xba\x72\x51\xab\x70\x66\x8a\x6e\x44\x2b\xbf\x03\xd4\x5e\x77\x8d\x2a\x00\xf8\x11\x43\xc4\xe8\xa6\xd4\xc8\x42\x14\xc7\x40\xa4\xbd\x4a\x13\xaf\x0a\x07\xf0\x48\x16\x2e\x15\xa9\x50\x13\x62\x01\x3d\xef\xd6\xa0\xb0\x07\xb9\xc0\x68\xbc\xd2\xf8\x6e\x10\x95\x60\xde\xbf\xd9\x1b\xe0\xef\x98\x58\x53\x91\x73\x77\xc3\xd7\xc8\x62\xb4\x44\x73\x25\xf0\x6c\x19\xbc\xa5\xfd\xfe\x5b\x42\xa3\x51\x7e\x5a\x65\x16\x4d\x76\x84\xe5\xb5\xcd\xfa\x95\x93\x04\xd3\xb5\xf9\x6a\xd7\xd5\xd1\xbd\xeb\xe9\x46\xe2\xeb\x9f\x27\xda\x65\x99\xbd\xfe\x2c\x96\x6c\xad\x99\x61\x97\x93\x0c\xcc\x65\xcc\x7e\xf1\x8c\x56\x2d\x8f\x18\x4b\x7c\x46\x2f\x30\x5f\xda\x65\x52\x81\xb8\x26\x21\x1e\xd1\x08\xdf\xf6\xa1\x93\x35\x3f\x21\x05\x37\x69\x03\x11\xd9\x3d\x50\x22\x20\x5c\x30\x81\x69\xa6\x53\x96\x66\x17\xe8\x6d\x02\x31\xc5\x73\x42\x05\x20\x09\x85\xc3\xd9\x0c\xa1\x75\xa5\x05\x6e\x36\x2d\xd8\xcb\xcd\x0c\x48\x3f\x7a\x9d\x38\x63\x2f\x08\xdf\x0a\x72\x82\xc3\x94\x13\xb9\xd2\x70\xcf\xf3\x91\xd6\xd4\x5a\xca\xd4\x6c\x9f\x7b\x25\x67\xe4\x50\x7c\xd7\x06\xf0\x41\x60\x7e\x60\xdf\xed\xe6\x16\xb3\x8f\x04\x7e\xb5\x9b\xb7\x99\xd6\x49\x3a\xed\xc3\x7f\x17\x1a\x01\xbe\xfd\xa6\x3d\x25\xb4\x3d\x45\x62\xd1\xf0\x3a\x96\x57\x11\xe1\xd0\x4a\xa0\xcd\x12\xd9\x76\x4f\xb4\xbe\xfb\xce\x03\x02\x08\x53\x1e\x43\xeb\x44\x40\x5b\x2e\x13\x70\xd7\x1a\xec\x63\xaf\xc0\xbc\xfd\x42\x09\x71\x57\xc6\xfc\x57\x63\xf7\x6d\xfd\xe4\x8b\x60\xd1\xde\xb8\xf3\xdf\xa9\xdd\xb7\x51\x42\x42\x19\x07\xbf\x91\x04\x7e\xd0\xd8\x8b\x2d\x15\x3a\x52\xaa\x9a\xcb\x60\xad\xe8\x31\xf2\x17\x4b\x16\xc1\x9f\x6f\x3d\x30\x8b\xc1\x03\xf5\x7e\x84\x48\xc2\x5f\xff\x7a\x78\xf6\x16\x7e\xf8\xc1\x1f\xb9\x71\x67\xb5\x73\x1f\x24\x78\xe9\x0d\xd2\x5d\xee\x35\xe0\xbd\xd7\x75\x78\xf6\xd6\x17\xbe\x21\xeb\x55\xa7\xf3\x64\xec\xe1\x82\xdd\x50\x68\x9d\x43\x3a\x4d\xa9\x4c\xbd\x71\x1e\xa0\x58\x09\x89\x97\xa1\x8c\x21\x42\x78\xc9\x68\x8b\x63\xfd\x08\xef\xbb\xef\x0a\x5d\x58\x1f\xc5\x81\x95\xc3\x03\xbc\x63\x19\xb6\x0b\xef\xeb\x3c\xc8\x83\xc1\xc5\x60\x78\x76\xf2\x71\x7c\x7e\x76\x39\x3a\x38\x3c\x7f\x83\x6e\x44\x2d\xc0\xe4\x62\x30\x3c\x7e\x63\x8f\x90\xb2\x82\xee\x7d\x2d\xec\xe5\xe1\xf9\x64\x74\x76\xfa\xa6\x62\x2f\xb5\xd0\xfb\x1f\x86\xc7\x87\x17\x6f\x36\xee\xbc\x88\x52\x0f\x3b\x18\x8f\x3e\x1e\x1f\xfe\xa4\xe8\xd0\x6f\x30\x7d\xa8\xc1\x8f\x93\x8f\x97\xe3\xe1\xc7\xd1\xc1\x9b\x8d\x3b\xcf\x13\x05\x3a\x23\xaa\xc7\x79\x7c\xf8\xd3\xc7\xd3\xc1\xfb\xc3\x37\xb9\xe6\x2a\x58\x27\x1f\xf6\x4f\x0f\x2f\x26\x65\xb4\xf7\xdb\x7e\x43\x77\xed\xc8\x8f\xe3\xf3\xd1\xe5\xe0\x42\x4d\xe2\x17\xb9\x34\x0a\x2f\x31\xac\xc1\x71\x38\xfc\x70\x3e\xba\xf8\xe9\xe3\xd1\xf9\xd9\x87\xf1\x9b\x8d\xbb\x3a\x07\x16\xe8\xff\x97\x99\x74\x22\x51\x1b\xe5\x37\x1b\x77\x97\xe3\x61\x90\xa5\x3f\x55\xc8\xc1\x70\x78\xf6\xe1\xf4\xc2\x08\x50\x6b\x7a\x10\x86\x2c\xa5\xb2\x0e\xed\xf9\xe1\x91\x51\x72\xf1\x54\xb1\x0e\xe5\xe1\x64\xa2\x85\xec\xb0\x66\x77\x9a\x6a\x39\x3d\x3f\xbc\x28\x0c\x32\x23\x4a\x77\xa1\x1e\x59\x98\xf9\x12\x31\x09\x42\xbe\x42\xbe\x85\xb3\x04\x53\x48\x13\x48\x18\x97\xd0\xeb\x65\x0f\x7e\xeb\x1e\x77\xe5\x99\x82\x27\xe8\x35\xe9\x5d\x25\x98\x54\xf3\x06\xdd\xe7\xbf\x32\x33\x6b\xb8\xf0\x10\xe2\x9a\x20\x47\xdc\x36\x0c\x12\x92\x37\xec\x75\xf6\x3a\x3a\xab\x39\xe2\x49\x98\x37\x77\x3b\x9d\x4e\xe7\xc1\xbc\xdf\x23\xcd\x3e\xde\xc8\x63\xdc\x28\x19\x73\x26\x59\xc8\xe2\x3e\xc8\x30\x4f\x54\xde\x72\xb6\x1c\x9b\x0b\x00\xbd\x5e\x1e\x54\x2f\x58\x4d\xa3\x32\xa9\x51\xe2\x8e\x7c\x1e\xfa\x0c\xc4\x13\x66\x53\x7c\xd6\xcc\xe7\x37\xff\xa1\x33\x6a\x11\xd6\x4c\x59\x6a\x7f\x7c\x4e\x6d\x62\xee\x7a\x5d\x92\x08\x88\x56\x14\x2d\x59\x34\x05\x69\x73\xf0\x03\xdd\x30\x48\x92\xfa\xed\x9d\xe9\x3e\xd8\xef\xf7\x1f\xda\xde\xe9\x3e\x93\x6c\x78\x09\xc5\x7f\x30\x42\x8b\xe9\x44\x0b\x9a\xad\x26\x78\x0d\xad\x07\x8e\xe5\xb2\x41\x8a\x74\x77\x1b\x60\x20\x25\x27\xd3\x54\xe2\x03\x3c\x23\x94\x14\xae\x66\x18\xe0\xac\xdf\xd0\x43\x7d\x74\x59\xaf\xe1\xb1\x39\x69\xe6\xd9\xd2\x24\x5c\xe0\x25\x7a\x00\x57\x53\x21\x6b\x16\xb0\x1d\xe3\x95\xc5\xf3\x6e\x30\x79\xe7\x7a\xf4\x75\x04\x15\x5e\x70\x74\xb1\xe0\x2c\x9d\x2f\x92\xb4\x90\xf3\x9d\x63\x14\xb9\xd7\x2f\x1f\x28\x91\xa2\x0f\xcd\x97\x39\xd6\x1f\x39\x91\xb8\xa6\x5f\xd7\x82\x33\x65\x4e\x53\x12\x47\xeb\xb4\xb9\xaf\x3a\xff\x18\x7d\x7e\x16\x75\x6a\xe2\x3f\x55\xa1\x4d\x12\x35\xff\x30\x85\xfa\xb8\xbe\x9e\x3a\x39\x8e\x31\x12\x78\xed\xfa\x3c\x37\xfd\xff\x87\x75\xea\x38\xf8\x97\x56\x61\x88\xe2\xb8\xf8\x81\x87\xfc\x83\x50\xfa\xc1\x4c\x9a\x24\xf1\x0a\x88\x14\x85\xcf\x2b\x35\xb2\x2d\xa9\xfb\x66\x14\x72\x2f\x2f\xcd\x45\x68\x5d\x63\x70\x7d\x0a\x37\x0a\x65\x8a\xe2\x9a\xfb\xeb\xa6\xb6\x90\xd7\xe8\xf3\xbb\x79\xc7\x7b\x42\xab\xaf\x62\x40\xe5\x8f\x36\x69\x28\x0d\x94\xdf\x42\x2c\x5f\x25\x5c\x67\x65\x96\xd5\x0f\xe7\x27\x7d\x7b\xcb\xcb\x6d\xef\x36\xee\xfc\xb7\x68\xb5\xaf\x83\xbc\x47\x35\xf7\xed\xfc\x05\x53\x7e\x35\xbc\xe5\x2e\xea\xbb\xce\x4c\xa5\x95\x8f\x33\xe9\x4b\x4f\xa3\x83\x4a\x02\xf2\xa4\xcb\x2b\xb5\x25\x8f\x9a\x5a\x87\xf7\xa1\x01\x7b\xf1\xab\xfc\x29\x27\x00\x7b\x27\xe5\x29\xe7\xd0\x4a\x81\x07\xba\xc8\x54\xcc\xc3\x18\x8d\x57\xf9\x3b\x2f\xef\x41\x2a\x91\x02\xc7\xb3\xbc\x02\x32\x79\x97\x3d\x85\xcd\x6e\xda\x15\x4a\x4c\x41\x56\xac\xb8\x6f\xef\xf4\x72\xa3\x1e\x24\xe4\x64\x5a\x18\xf9\x48\x26\xf3\x70\x85\xa1\xfa\xd0\xd3\x42\xd5\x7e\x12\xab\xf2\x4e\xd1\x00\xd7\x7e\xe3\xaa\xfc\xf2\x2a\x07\xcd\xbf\x7b\x55\x92\x7a\xb1\x16\xfa\xa0\x33\xab\x79\x16\x63\x6b\xfb\xeb\x3e\xf2\x05\x70\xc2\x50\xb4\x8f\x62\x65\x15\x15\xf5\xfa\xf7\x53\xce\x52\x99\xa4\xd2\xde\xa0\xb5\x3f\x8c\x4a\x2f\xc7\xc3\xfc\x63\x47\xce\x6a\xf5\x0c\xfe\x43\xd8\x03\x77\x1f\x94\xe2\x9b\x78\xd5\x32\x0e\xc1\xbc\x7a\x57\x3b\x29\x3d\xa2\x78\xdb\xc9\x98\x7c\x76\x6d\x5e\xd4\xe0\x6c\x9e\x10\x73\xcd\xd4\x2b\x77\x59\x3f\x6e\x71\x59\x5e\xab\xf1\xa2\x05\xcd\xed\x42\xb4\xc8\x62\x45\x7d\xd5\x0d\xea\xba\xbb\xf9\xad\x7e\x61\x0d\xf3\x41\x2a\xfd\x73\xa7\xdf\x49\x66\xfd\xf2\x83\xda\xfe\x6e\x03\xfc\x52\xad\x2b\x60\xd6\x29\x6a\x0c\xb6\x10\xe8\x14\xe6\x2d\xd8\xc2\x57\x0a\x1f\xd3\xa2\x3d\x35\xf4\x96\xaf\x9d\xb7\x51\x47\xce\xc1\xe9\xa4\x86\x1e\x7b\x84\x72\x70\x3a\x81\xb7\x7f\x3b\x38\xfd\x6c\x44\x1d\x50\xe1\x6e\x4e\x4f\x26\xef\xc6\x9c\xdd\xae\x86\x6c\xb9\x44\xd4\xd6\xa6\x1f\x89\x27\x1e\xcd\xe7\x29\xd5\xaf\xe2\xe3\x78\x05\x2d\xed\x09\x43\x83\x4a\x7f\x42\x4c\xa1\x76\x0f\xef\xed\x97\x35\x49\xb1\x48\x0e\x20\x4d\x58\xaf\x30\xba\xad\x1f\xe1\x1b\xa7\x9a\xef\x63\xb7\x0a\xc8\xd5\x5c\x3e\xea\xd2\x7b\xf3\x17\xbe\x38\x94\x8b\xfd\xa1\xd5\xc8\xbc\xaf\xae\x13\x34\x13\x24\x17\x6d\xc9\x4a\xf5\xb7\xe6\xbf\x59\x38\x21\xdc\xe3\xbe\x16\x81\x0d\x3b\xca\xb5\x0c\xa0\x75\xa2\xc8\xea\x6b\x01\x28\xaa\xfb\xea\xa7\xeb\x66\x50\x14\xed\x9b\xa6\x10\x0b\x85\xe5\x97\xe6\xc6\xdd\x37\x16\xd3\xfd\x2f\x4d\x5b\xd5\xfb\xf7\x92\xf3\xb7\xd6\x73\x0f\x34\x84\x3f\x2d\xe0\x4f\x89\x8b\x00\x19\xb8\x4b\x14\x02\xeb\xa3\x82\xf7\x5a\x0a\x79\xd8\x50\xfa\x3d\xc2\x52\x49\x65\xa8\x2f\xa7\xfc\x51\x4a\x1e\x8e\x8b\x4a\xce\xbe\xf5\x55\x7e\xa9\x57\x7c\x96\x67\x89\x9f\x11\xfb\xf1\x4e\xa3\x59\xfd\x99\x0d\x9b\xfd\xb8\x34\xec\x9a\x98\x17\x66\xa1\x8c\xb7\xbd\xcf\xb6\x66\x73\xc6\x84\x9a\xaf\x74\xb8\x9b\xf4\xc3\x2c\x1b\x6b\xea\x91\x7a\xe2\xa6\x99\xcc\xe6\x57\xa1\x79\xed\x98\xbd\xb0\x5e\xe9\x87\xa4\x74\xdb\x7c\x91\x13\x51\xe0\xa9\xa3\xb1\x89\x6f\xb5\xc1\x1d\x7f\xd8\x3f\x1c\x9e\x9d\xbe\x1d\x1d\xbd\xd9\xd8\x4a\x6e\xa2\x17\xed\x22\x72\xc9\x00\x53\x91\x72\xec\x88\x85\x54\xe8\xa4\x8f\x94\x38\xd7\x64\x38\x4a\x07\x53\x96\xca\x6c\x44\x2b\x2b\xaa\xe7\x79\x53\x40\x98\x7e\x0a\xd0\x4e\x05\xe6\xad\xb9\xbe\xeb\x9f\x70\xcc\xf1\xaf\xa2\x0d\x7f\x80\x3d\x67\xa5\x8c\x1a\x7b\xfe\xda\x06\xdb\xff\x9f\x82\x88\x21\x28\xfc\x68\xf8\xc1\xd8\xbc\xb3\xc5\x51\x9e\x69\x5d\xed\xe5\xdf\x4f\x6d\x00\x18\xcc\x2e\x0d\x1c\x7d\x82\xc9\xe7\x63\x9d\x1b\xb6\x4e\xa6\xf8\x45\x89\x5a\x7f\xbb\x86\xcd\x1c\x61\x23\x23\x30\x63\xfd\xf9\xf4\xb9\x9b\x1d\x85\xef\xa4\x78\x74\x3e\x87\xb4\x8c\x0c\xfd\xe6\x98\x45\xd8\x96\xfc\xfe\x48\xe9\xf9\xf1\x2a\x9b\xe4\x69\x64\xd6\xd0\xa4\x48\x55\xe9\x83\xea\x12\xcf\x27\x70\x98\xfb\x2f\xfd\x0d\xe6\x25\xe3\xd8\x7e\xa4\x44\xc7\x14\x22\xfc\x0f\xff\x3d\x46\x61\x46\x4a\xe1\xb3\x8c\x6b\xbf\xf7\x38\x48\x88\x7e\xbf\xae\xdf\xc8\xba\xcf\x32\x9e\x8c\x2a\x79\xa1\x41\xf0\xbf\x01\x00\x00\xff\xff\x5d\xc4\x81\x77\x24\x5b\x00\x00") +var _cmdProviderAwsTemplatesFormationYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x7c\x7b\x73\x1b\x37\x92\xf8\xff\xfc\x14\x1d\x46\x1b\xc9\x59\xf1\x29\xd9\x2b\xf3\xb7\x4e\xfd\x28\x4a\x96\x79\x92\x25\xae\x28\x2b\x95\x5c\x72\x2e\x70\xa6\x49\x62\x35\x04\x26\x00\x46\x12\xa3\xd3\x7d\xf6\x2b\xbc\xe6\xc1\x19\xea\xe1\xc4\xf6\xdd\xd6\xc5\x55\x29\x11\x68\x34\xfa\x85\xee\x46\x03\x98\x5a\xa3\xd1\xa8\xf5\x7f\x1c\x5f\xe0\x22\x8e\x88\xc2\xb7\x5c\x2c\x88\xba\x44\x21\x29\x67\x3d\xd8\xec\xb6\x3b\xed\x46\xfb\x75\xa3\xfd\x7a\xb3\x76\x80\x32\x10\x34\x56\xb6\xe7\x38\x99\xa0\x60\xa8\x50\x42\xff\xc7\x31\x0c\x22\x9e\x84\x76\x34\xe5\x0c\x3c\xbe\x1e\x0c\x04\x12\x85\x40\x20\x1b\x50\x03\x08\xa2\x44\x2a\x14\x40\x19\x10\x60\x78\x03\x97\xa3\x41\x13\x2e\xe6\x08\x0b\x62\x3a\x18\x0f\x11\xa8\x04\xc2\x80\x24\x8a\x37\x04\x06\xfc\x1a\x05\x65\x33\xe8\x2f\xc8\xef\x9c\xc1\xe1\xa0\x5b\x03\xa0\x4c\x2a\xc2\x02\x6c\x42\xa7\xd1\x6d\x03\x09\x43\xaa\x09\x20\x91\xee\x4f\x7b\xa5\x99\x88\x41\x3f\x51\x7c\x1c\x90\x88\xb2\xd9\x91\xe0\x49\x0c\xff\xe4\x94\x81\x9a\x63\x0d\x72\xf4\xa5\xd4\x11\x69\xe8\x90\x4d\xe8\x33\x38\x3c\xd9\x87\x58\xf0\x6b\x1a\x6a\x08\xce\xa6\x74\x96\x08\x32\x89\x10\xf0\x56\xa1\xd0\x53\x92\x20\x40\xa9\xd9\x53\x5c\x23\xcd\xa3\xec\x8f\x86\x96\x3f\xc7\x2c\x50\x16\x44\x89\x46\x45\x60\x42\xa4\x11\xda\x9c\x4b\xa5\x87\xce\x04\x61\xaa\x06\x30\x1e\xbf\x73\x28\x3d\xc2\x58\xd0\x6b\x2d\x4d\x99\x4c\x18\x2a\x98\x72\x61\x9a\x1d\xbd\x7a\x02\x2a\x41\x39\xd9\x43\x60\x44\x6f\xe8\xb9\xe1\x20\x15\x09\xae\x64\x0f\x38\xc3\x74\xa0\x27\x86\xb0\xb0\xd0\x9e\x43\x88\x76\x20\x50\x8d\x47\x26\x54\x19\x96\x35\x60\x88\xd7\x18\xf1\x78\x81\x4c\x19\x04\x72\x41\xa2\x08\x24\x65\xb3\x08\x1b\x0a\xc9\xc2\xa3\x91\x4d\xf8\xfe\xfb\x1f\xfb\xe7\xa7\xc3\xd3\xa3\xef\xbf\x37\x44\x6a\x9a\x56\xc8\x84\x29\x4f\x44\x4e\xb9\x39\xe5\xdd\x50\x35\x87\x10\xa7\x24\x89\x14\x48\x54\x8a\xb2\x99\x6c\xc2\x4f\x3c\x81\x1b\x1a\x45\x35\x80\x09\xc2\x84\x46\x11\x86\x29\x0b\xda\x28\x05\x4a\x9e\x08\x8d\x21\x91\x18\x02\x9d\xc2\x92\x27\x6e\x3e\x20\x8e\xb1\xa9\xe0\x0b\x50\x79\xc1\x35\x37\x6b\xb5\x6f\x0d\xeb\xef\x51\x91\x90\x28\x02\x0a\xa3\xc8\x1a\xfa\x9c\xdf\x68\x6d\x84\x54\xc6\x11\x59\x5a\xad\x10\x41\x16\xa8\x19\x85\x30\x31\x26\x6a\x31\x9b\x89\x28\x67\x35\x8f\xa6\x57\x03\x8d\xa3\xd7\x2b\xae\x96\x5e\x6f\xc8\x14\x8a\x29\x09\x50\x43\x00\x8c\x3c\x42\x63\xa4\xd2\x36\x36\xe0\x84\x4c\x30\xb2\x3f\xf4\x7f\x4e\x20\x3d\x38\xc7\xdf\x12\x2a\x30\x74\x3d\xe9\x68\xe9\x61\x1b\xf0\x33\x67\xd8\x2e\xfc\xea\xa4\xbf\xfa\xe1\x82\xb2\x21\x9b\x09\x94\xf2\x84\x07\x96\x64\xdf\x79\x8c\xcb\x53\xb2\xc0\x47\x28\xe8\x87\xd7\x5a\x53\x0f\x51\x70\x8a\xea\x86\x8b\x2b\xca\x66\x23\xbb\x88\x44\x36\xc7\x9e\x3c\xe5\x21\x0e\x48\x4c\x02\xaa\x96\x69\xfb\xd0\x19\xc0\xc5\x32\xc6\xb4\xf1\x80\xca\xab\x31\xfd\x1d\x8f\x26\x69\xd3\xbe\x5d\x3f\x95\xe0\xff\x18\x8f\x77\xf6\x93\xe0\x0a\x55\xca\x86\x6f\x3e\xc6\xe5\x48\xe0\x94\xde\xd6\x8a\x42\x37\x4c\xa6\x94\x3b\x01\x54\x30\xad\x97\xe7\x31\x7a\x72\x8d\x84\xab\x44\x73\x4d\x68\x44\x26\x34\xa2\x6a\x69\x80\x72\xf0\x9d\x2a\xac\x18\x70\x16\xae\x1d\x56\xa5\xac\x4a\x85\x2c\x28\x03\x07\x07\x2b\x5a\xcd\x0b\xaa\x62\xac\xef\x86\x9c\x20\x33\xa9\x57\x0c\xd0\x9d\xa0\x7b\x61\xeb\x88\xee\xbf\x70\x00\x15\x5a\x79\x6c\x32\xd8\x72\x83\xe0\x1d\x97\xca\x23\x5a\x31\x8f\x0a\x24\xba\x1b\x56\xcc\xa7\xa8\xf8\x2a\x41\xef\x80\xed\xcf\x0d\x48\x4d\xa2\x1a\xfe\x18\x97\xe0\x4c\xc6\x76\x96\x8d\xba\x8a\xba\x14\x08\x52\xd3\x77\xfe\x25\x5b\x28\x40\xa2\x48\x7b\x96\x39\x6a\x5f\x25\xb4\x8b\x89\x89\x94\x10\x24\x52\xf1\x45\xea\xf4\x7c\x20\xb0\x0e\x66\x82\x53\x2e\x30\xf3\x33\xc5\x75\x57\xb0\xdc\x42\xf0\x3e\xbc\xa5\x52\xa3\x33\x6e\x56\xf3\x4c\xa8\x30\x8e\x33\x8b\x38\x4d\x33\xca\x28\xcd\xba\xac\xc3\x41\xb7\xd7\x73\xb0\xe6\x8f\x74\x3d\x0d\x38\x93\x4a\x10\xca\x54\x61\x92\x45\x22\x95\xf6\xcb\x26\xce\x90\x05\x02\x9f\xea\x10\x8c\x15\x73\x37\x6b\x96\xdc\xf7\x44\xa1\xa0\x24\x7a\x2a\xc9\x1e\xfe\x01\xda\xc7\x4a\x3b\xe4\x1a\x80\x76\xbd\x31\x3d\xc6\x65\x05\xf2\x11\x91\xf2\x86\x0b\x1b\x3c\x0e\x88\x22\x01\x8f\x34\x5b\x4a\xf0\x28\x42\xa1\x83\x37\x94\x71\xd6\xc0\xc3\xfa\x7c\xa9\x8c\xf9\xda\xf6\x68\xde\xab\xf1\x56\xa2\xed\x0b\x45\xa7\x24\x50\xd6\x3e\x2b\xd0\x9e\x3a\x79\xce\x38\x9f\x45\xda\x1a\xb8\x20\x33\x84\x89\x81\x37\xe1\x89\xdf\xb0\x88\x93\x10\x42\x37\xeb\x84\x32\x22\x96\x95\xb3\x8d\xad\x6d\xad\x9d\xcc\x61\xd5\xb2\xd1\x13\x69\x25\x04\x7a\xc1\xe9\x98\x8f\xec\x9a\x0a\xce\x4c\x0e\x70\x4d\x04\xd5\xc9\x81\xac\x9c\xa5\xec\x07\x8a\xfa\xcd\x05\x7c\x50\xda\x17\xac\xe6\x22\x55\x4a\xd5\x48\xdc\x2a\x5b\xec\x36\x23\x22\x66\xa8\xe7\xfa\x16\xc6\x31\x06\x74\x4a\x51\xda\xd5\xa2\x7d\x13\x9f\x9a\xbf\x05\xe7\x4a\x47\xef\x2b\x33\x81\xce\x58\x0a\xb9\xc6\xb6\x4b\xcc\x34\x97\x36\x15\x35\xf8\x34\xaf\x36\x15\xac\x95\x9d\x61\x31\x31\x1e\xaf\x9d\x4c\xb7\x94\x26\x83\x23\xba\xdf\xcc\xf1\xb1\xdb\xde\x2c\x32\xb6\xdb\xce\x71\x7e\x9a\x2c\x26\x2e\x72\xbe\xa7\xec\x92\x44\x09\xf6\x60\xcf\xfe\x26\xb7\xee\x77\xa7\xdd\xdd\xd5\x62\x58\xeb\x81\x9f\x28\xf9\x42\x5e\xba\x65\x52\x28\xdd\x15\x27\x93\x88\x06\xb9\xf5\xf6\xe2\x31\xdd\xa8\x6e\x73\x41\x03\xc1\x4d\x6b\x5f\x3b\x3a\x0c\x0d\xad\xb9\xfc\x40\x75\x9b\x8c\x30\x9e\xff\x9d\x8d\x71\x0d\x26\xc3\x2c\x40\x60\x48\x93\x45\xbe\xc5\xda\x80\x6f\x58\xec\xac\x82\x2c\x76\xca\x20\xb7\xa5\x96\xee\x6a\xd3\xee\xea\xa8\xdd\xfc\xa8\x47\x3c\x20\x81\x6b\x12\xd1\xb0\x2c\x69\xe3\xf7\x72\x89\x43\x61\xb4\x8e\x0f\x3e\xe7\x2d\x65\x03\x4e\x47\x54\xda\x48\xd0\x4c\xbd\x8b\xde\x27\x2d\x16\xc8\x42\xe9\x68\x55\x73\xa2\x4c\xda\x2b\x12\x66\x72\x7c\xbf\xb9\x89\xb5\x03\xfa\xd9\x58\x76\x22\x11\x14\xe7\x7a\x5b\xa4\x9d\x47\xc0\xb9\x08\x29\x33\x49\x72\x20\xb8\x94\xd0\xff\x79\x4d\x40\xc8\x13\xa6\xe9\xea\xf5\x3e\x31\x2c\xe8\xd4\xba\x9c\xf3\xd4\x0a\x79\x52\x49\x3c\x72\x4d\xae\xb4\x2a\x9d\xaf\x44\xfa\xfa\x5c\xad\x80\x76\x30\x3c\x38\x87\x49\xc4\x83\x2b\xd8\x1a\x8e\xf4\x16\xd6\x24\x6d\x82\xb0\x19\xbe\xd0\xfa\xb0\xa9\x41\x79\x37\xe8\x34\x5c\x58\xa6\x5a\x9d\xef\x2e\x2e\x46\xe3\x15\xd8\xd5\xad\xe8\x07\x89\xd0\x6e\x9a\x7f\x2d\xbf\x31\x48\xa7\x72\x43\xcd\xce\x48\x7b\xc8\xc8\x11\x5f\x1d\x59\x8d\x2b\x3a\x41\x36\x53\xf3\x1e\x6c\xbe\xde\xf4\xde\x28\x6d\xea\xec\x6d\xe6\x57\xfe\x88\x28\xbd\x5f\xee\x41\x7d\xeb\x97\x5f\xc2\xbb\xce\xf6\xce\xfd\x8b\x5f\x7e\x69\x3e\xe5\x47\xcb\xfd\xd9\xbd\x7f\x51\x7f\xce\xca\x1b\x8e\xac\x94\x8d\x4c\xbd\x73\x9e\x72\xb1\x80\xdb\xa6\xf9\xd7\xba\xb5\x39\x48\x55\xaa\x99\xba\xb1\xcd\xee\x66\x59\x7b\x43\x46\x95\x4e\x41\x98\xf1\xcc\x1a\x77\x4e\xd4\x26\x64\xc0\x56\xa7\xd1\x6d\x17\x9c\x64\xa5\x1b\xdf\xec\x6c\xae\x38\xf2\xcd\xae\x0b\x06\x8f\xb0\x39\x41\x75\x83\xc8\xa0\x63\xd4\xdf\x6d\x17\xa3\x4c\xd3\x85\x44\x9f\xef\x66\xc5\x0b\x8b\xc5\xe8\xdc\x6c\x91\x85\x31\x17\xe3\x0d\xe6\x48\x05\xf0\x1b\x66\x32\x09\xa9\x84\xde\xce\x4b\x46\x62\x39\xe7\x4a\x1a\x7c\x4e\x8a\xbf\x25\x34\xb8\x92\x8a\x08\xd5\x20\x37\xb2\x71\x1d\x07\x86\x88\x5c\x73\x44\x59\x72\xdb\xf0\x56\xea\x37\xd9\x1a\x49\x55\x8e\x5e\x32\x92\xff\xf8\xf7\x76\xe3\x35\x69\xfc\xde\x6f\xfc\xfc\xeb\x5f\xb7\xb2\x1f\x8d\x5f\xbf\xcf\xf5\xbc\xf8\x7e\xe3\x41\x8b\xf8\x87\xa6\x07\xc6\x9a\x20\x9f\xd0\x98\x25\x1c\x10\xe6\x4b\x31\x4e\x85\x72\x1b\x34\x05\x22\x20\xd2\xfb\xfb\x08\x35\x31\x72\x1b\x92\x38\xb6\x1d\x59\x93\x66\x76\xbe\x8c\xe7\xc8\x24\x6c\x35\x5e\x34\x61\xa8\x34\x52\xc6\x15\x18\xf6\x81\x0b\x40\x16\xda\x1a\x06\x71\xa0\x0e\xaf\x86\xaf\x15\x6d\x2c\x27\x37\x81\x53\x14\xc8\x02\x2c\x1b\xdd\x19\x8b\x96\x10\xcc\x8d\x35\x1b\x3f\xe7\x4a\x1b\x73\x72\xad\xbd\xa2\x82\x24\x06\x22\x25\x2a\xcd\x0c\xbd\x42\xdd\x69\xd5\xc9\xd2\xcd\x88\xa3\xa1\x60\x0c\xdb\xae\x4e\x36\xde\x71\x42\x72\x25\x25\xcd\xa4\x56\x76\x61\x0f\x94\xd5\x3d\xb4\x34\xcc\xfc\x69\x01\x4c\xff\x67\xe9\x95\xbe\xc0\x82\xbe\xa1\x65\xd1\x79\x3b\x68\x41\x48\x05\x06\x3a\xb3\x44\xa9\xad\x2a\xa5\x75\xca\xc5\x95\x37\xb3\x77\xa8\x59\xf7\x2e\x2f\xaf\x4d\xcf\x67\x12\xeb\x84\x17\x43\x6d\xc2\xe3\x1d\x5b\x91\x52\x5c\x60\x08\xc4\x6f\xf1\x36\xee\x2c\x57\x5a\xf3\xf7\x4d\xb9\xd3\x24\xa6\xda\x44\x6e\x64\x33\xe0\x8b\xd6\xc6\x5d\x6c\x18\xbb\x6f\x79\x4a\x25\x5f\xe0\x94\x46\xd8\x54\xb7\xaa\x39\xde\xb1\xf5\xb0\x9c\xf9\x78\x19\x3e\x68\x44\xcf\x32\x1f\xbf\x45\x7f\x82\x11\x65\xf0\xe5\xad\x4e\xc5\x66\xf6\xa1\x85\xd5\xf8\xf5\xaf\x5b\xad\xc2\xcf\xe7\xac\xa7\x2b\x5c\x82\x95\xdc\x53\x25\xe1\xb8\xac\x90\x47\x4e\x16\x56\x38\x53\x2e\x6e\x88\x08\x41\x46\x44\xce\x61\xab\xb5\x76\x85\x39\x9c\x46\x44\xa5\x41\x30\xc1\x80\x38\xc7\xb6\x04\x22\xd0\x54\x95\x17\x44\xd1\x80\x44\xd1\x12\x48\x1c\x23\x0b\x31\x6c\x16\x57\xe3\xdc\x58\x5d\xcb\xd8\xa9\xfa\x23\xcb\x70\x75\x5d\x6d\x03\x91\x80\xb7\x71\x44\x28\x4b\xcb\x66\xb6\x14\x9d\x73\xd4\xe9\x02\xb3\x86\x37\xde\xf9\x82\xa2\xd6\x71\x6b\xbd\xb0\x8d\x98\x1d\xea\x8a\x61\xcf\x13\xf7\xea\x56\x71\x5d\x55\xa5\x62\xe7\xd0\x80\x80\x44\x34\xe0\xee\xc7\x0d\x92\xeb\x07\x53\xb8\xcd\x41\x22\x04\x32\x15\x2d\x41\x26\x71\xcc\x85\xc2\x50\xe7\x08\x09\x4a\x43\x66\xdd\xa2\xab\x1b\x79\xd4\x0d\xba\xfa\xca\x7e\x2c\x37\x61\x31\x8d\x9b\x73\x2e\x5d\x72\x98\x55\x7c\xdc\x89\x81\xf0\x81\x55\x67\xa6\x3a\x43\x4f\x18\xb5\x39\x95\x0f\xde\x4e\x98\x31\x0f\xa5\x37\x84\xf2\x89\x44\x13\xc6\x29\xd5\x05\xc7\x6d\xa9\xb7\xa4\x79\x27\xb9\x35\x57\x2a\xee\xb5\x5a\x21\x0f\x64\x33\x16\xfc\x9f\x18\x28\x0b\xd1\xe4\x62\xd6\xba\xee\x36\xdb\xad\x99\xdd\xfb\x37\x8c\x6e\x31\x6c\x5d\xa5\x53\xb6\x4c\xfe\x10\x45\x06\x7d\x4b\xe7\x96\xae\x9b\x84\x8b\x96\x2f\xcd\x69\x29\x19\x21\xd9\xc9\x64\xaf\xd5\x9a\x51\x35\x4f\x26\xc6\x9f\x9a\x1e\x2d\x08\x69\xff\x6c\x4d\x22\x3e\x69\xd9\x5d\x75\x4b\x52\x85\x06\x5f\x83\x84\x21\x67\xcd\x45\x58\xe5\xcc\x6c\xc1\xbc\x6f\xc2\x4a\x5a\xbb\xa9\x80\x18\x63\x20\x50\x3d\x02\x67\x8f\xa2\x8e\xf7\xe4\xc0\x4a\x73\x25\xbd\x53\x22\xa9\x08\xb5\xe9\xf9\x55\x26\x1a\x57\x7d\xe3\x02\xf4\xb2\xd8\x5a\x70\xa9\xed\x29\xdd\x1f\xe7\xcf\x45\xe2\x44\xc4\x5c\x62\xf3\xc5\x83\x5b\x5f\x3f\xb1\xfe\x31\x25\x91\xcb\x3b\x56\xc9\xbf\x1c\x0d\x74\x12\x5b\xc1\x5b\x81\x8d\x7a\xc7\xe7\xf5\x9d\x57\xf5\x32\x3f\xf5\xcb\xd1\x20\xb7\xe5\xa8\xdb\x78\x31\x36\x07\x4a\xed\xa7\xe3\xef\x34\xdb\xad\xee\x6e\x15\xfe\x91\x2d\x0f\xb8\x23\xaa\x76\x61\xae\x74\xa6\xce\xd3\x67\xea\x3e\x71\xa6\x4e\xf5\x4c\x23\x7b\x64\xf6\x0c\xd6\x76\xd6\x4f\x58\x3c\x7e\x5b\xc3\x9b\x83\x7a\x06\x8b\xbb\x4f\x9d\x71\x85\xc7\xf7\x24\x8e\x29\x9b\x19\x53\x3a\xc7\x19\xe5\xec\x3d\x89\xed\x94\x24\x6e\x30\x2e\xd4\x1c\x89\x54\x8d\xf4\x9c\x61\xf3\xd5\xee\x66\x0f\xc8\x82\x36\x3a\x7b\x64\x1a\xec\xfe\x6d\x5a\x06\xee\x96\x81\x5f\xef\x84\xaf\xda\xed\x69\xe8\x81\x25\x4f\xd4\xbc\x0a\xeb\xde\x4b\x9c\xbe\xee\x20\x29\x00\xae\x23\x61\xd2\xdd\xeb\x76\x5e\x85\x9d\x32\x70\x05\x09\xf8\x6a\xf2\x72\x0f\xf7\x5e\xd6\x6c\xbe\xd5\x08\x90\x29\x41\xa2\x2a\xbc\x7f\xeb\x74\xba\xa4\xdd\xb1\xa0\x98\x3c\x04\x3a\xc5\xdd\xf6\x5e\xfb\x75\xc7\x83\xde\x60\x35\xa9\x01\xd9\x6b\x93\xf6\xe4\x75\x01\xae\x8a\xca\x10\xbb\xb8\xb7\x67\xe1\x24\x69\xac\x63\x1d\xdb\x7f\x7b\x89\xe1\x5e\x60\x3a\x12\xb9\x16\xee\x75\x18\x4c\xc3\xc9\x1e\x29\xc0\x55\xcc\x3b\x0d\x82\xce\xeb\xc9\xeb\xd7\x1e\x6e\x1d\x1f\x93\xf6\xcb\x6e\x7b\x27\x6c\x17\xe0\x2a\xf0\x4d\xba\xe1\xee\xab\x9d\xb0\x5b\xab\x0d\x38\xb3\x27\xea\xc6\xc8\x3e\xc8\x43\x22\x55\x27\x6d\xb4\x23\xdf\xb2\x5e\xef\xf0\xb7\x84\x44\x69\x50\xfe\xe6\x1c\xa7\xb6\xd4\x62\xcd\xd2\x35\xd7\x53\x56\xb5\xad\x8f\xe7\x3c\x89\xc2\xd4\x29\xf7\x60\x0d\xb2\xd4\x3d\x3a\xbc\xab\x6e\xbc\x56\x3b\xf7\xe7\xbc\x3d\xb3\x33\x4d\x7f\x1a\x77\xec\xce\xb8\xad\x03\xed\x55\x96\x82\x6c\x37\xc0\x48\xf0\x18\x85\xa2\x99\x73\x1e\xd0\x50\xec\xeb\xd5\xa6\xb7\xe7\x99\x5f\xdd\x74\xdd\x87\x8c\x4c\x22\x3c\x60\xd2\x85\xe5\x1e\x6c\x6a\x6a\x4b\xdd\xef\xb8\x34\x9b\x0e\xb9\x02\x70\x41\x66\xb9\x38\xa0\x03\x17\xe4\x8e\x29\x01\x5c\x1d\x20\x13\xe7\x58\x47\x1e\x03\x52\x03\x38\x78\x37\x18\x9d\xc5\xa9\x72\xca\x7c\xe5\x00\xd6\xf0\x77\xc0\x17\x84\xb2\xe2\x09\xd9\xb7\x99\x45\x02\x43\x0c\x25\x34\x31\xe8\x36\x29\xb3\xb7\x1c\xb6\x6d\x65\x1b\xa5\x4a\xab\xdc\x46\xc7\x12\x66\xa8\xe0\xef\xf6\xc7\x0f\x3a\x01\x88\x13\x85\xe9\xb0\x66\x0e\xff\x18\x11\xf2\xc9\x89\xde\x80\xd9\xad\x98\xc9\x1b\xec\x1d\x80\xcb\xd1\xc0\x25\xde\xad\x0f\x12\xc5\x51\x42\x43\x6c\x5d\x8e\x06\x1f\x35\x57\x1f\x1d\x5b\xcd\xb9\x5a\x44\x29\x66\x6d\x3b\xc3\x69\xc6\x49\xa3\x64\xb0\xb9\xae\x7a\x9e\xa9\x7a\xae\xe3\x9b\x71\x32\x81\xfa\xc6\x5d\xce\x7e\xef\x4b\xec\xd4\x4b\x02\x1c\xa3\xb8\x2e\x9c\x78\x5b\x36\x5c\x0a\x1b\x1e\x9c\x8e\x5d\x10\xcf\x69\xa5\x2f\x25\x0f\x68\xae\x30\x58\x61\x99\xd5\xe0\x6b\xf4\x79\x19\x07\xc3\xd0\x19\x8c\xb7\x6a\x80\x83\x79\x10\x3b\x14\x69\x6f\xde\x36\xcc\xf1\x8c\x32\xd9\xcd\x11\x51\x78\x43\x96\xd5\xd4\xac\x00\xad\xa1\xa1\xca\xa8\x6d\x42\xbc\x6a\xd7\x36\x80\x3b\xb1\x38\xa4\x7d\xa5\x48\x30\xd7\xc9\xd3\x5a\x89\x94\x20\x9f\x25\x8c\x15\x26\x52\x88\x55\xe6\xb4\x2b\xf9\x36\xcd\xb2\x4d\x2e\x6f\xf3\x0d\x9d\xf4\x16\x2f\xe2\xe8\x8c\x5d\xd2\x10\xe1\x77\xce\xb0\xa1\xbd\xb5\x8b\xdb\x2e\xad\xaa\xe6\xc4\x76\x3e\x8b\xf6\x9c\x3b\x32\x3d\xe5\x14\xc7\x01\x96\x8a\xdb\x16\x3e\x7f\x0d\xe4\x89\xbe\xa7\x98\x81\x3c\x4d\xad\x76\x88\x96\xa0\xd5\xf0\x97\x11\xc3\x67\xe2\x3f\x9f\x64\x3e\xc7\xaa\x0b\xa0\xd9\x56\xce\x07\xad\x95\x41\x55\x2e\xde\x02\xbc\x27\xb1\xc5\x38\x8c\xcf\xd8\x09\x49\x58\x30\x77\x7b\x15\x13\xec\x2e\xe6\x08\xa7\xfd\x0b\x18\x8e\xd2\x73\xbb\xa2\x71\x9a\x52\x83\x44\x64\xb6\x10\xa7\x37\xee\x6e\x7f\xe9\xec\xd9\x1f\x98\xa4\xc6\x7b\xda\xbf\x38\x1c\x8e\xd2\x33\x29\xbd\x55\x97\x67\xac\x57\xb9\x48\x2b\x55\x7a\x38\x1c\x3d\x18\x6f\x7a\x70\x1d\x07\x45\xf2\x67\x16\xf1\x1a\x1e\x4a\xf4\x39\x3a\xfe\x00\x8d\xa7\xe4\x11\x37\xa6\x37\x6b\x76\x8b\x6e\x2c\xf0\x08\x55\x5f\x29\x2f\x9b\x66\xbe\xd7\x0d\xb0\x56\x98\x9a\x6b\xc1\xf4\x6b\x25\x9f\x70\xce\x13\x85\x17\x3a\x3f\xa8\x5e\x16\x59\xff\xb3\x96\xc6\x27\xad\x6a\xf9\xec\x65\x5d\x66\xe5\xd3\x35\x61\x86\xaf\x61\x32\x93\x42\x26\xd7\x35\x62\xf4\x16\x86\x52\x51\x66\x34\x93\x73\x13\xab\x47\x5d\x99\xf6\x53\xbc\x39\xab\x7a\x48\x5b\x8f\x86\x6c\x3b\xa2\x72\xc0\x1a\x26\x57\x0d\xa7\x30\xf3\x27\x08\x62\xd5\xef\x7e\x65\x53\x73\xd7\x05\x9e\x63\x69\x69\x5e\x50\xe6\xe3\xcb\xd9\x59\xb5\x08\x9f\x65\x66\x4f\xc8\x31\xd6\x4c\xf3\x05\xcc\xac\xe0\x9f\x9e\x2d\x86\x4f\xcf\x8f\x26\xab\xab\xab\xf3\x65\xf2\xa3\xce\xd3\xf2\x83\xce\xd7\xca\x8f\x3e\xb7\x18\x3e\x13\xff\x7f\x2c\x3f\xfa\x5a\xd9\x4d\xe7\x7f\x41\x76\xf3\x07\x68\xfc\x43\xd9\x4d\xe7\x99\xd9\x4d\xa7\x14\x2f\x3b\xff\x3a\xd9\x4d\xe7\x2b\x66\x37\x9d\xcf\x91\xdd\x3c\xa8\xad\x2f\x9c\xdd\x74\x3e\x41\x10\xab\x5e\xf3\x5f\x24\xbb\xf9\xd2\x76\x56\x2d\xc2\xcf\x99\xdd\x7c\x59\x33\x2b\xf8\xa7\x4f\x10\x43\x0d\xc0\x5f\x4e\x2a\x91\x38\xde\xe9\xf5\x72\x6f\x07\xca\xc4\xe4\x2e\x35\xb9\x08\x9c\xbf\x63\xed\x62\x03\xb9\xf2\x51\x2b\x77\x66\x4a\x6e\x64\x23\xbb\x03\xd4\x5a\x77\x8d\xaa\x09\xf0\x23\x42\xc8\xd9\xa6\x32\xc8\x02\x12\x45\x40\x95\xbb\x4a\x13\x2d\x73\x07\xf0\x44\xe5\x2e\x15\xe9\x50\x13\xa0\x84\x6e\xe1\xd6\xa0\x74\x07\xb9\xc0\x59\xb4\x34\xf8\x6e\x08\x53\x60\x5f\x9d\xb8\x5b\xc4\xef\xb8\x5c\x53\x91\xf3\xf7\x8b\xd7\xc8\x62\xb8\x20\x33\x2d\xf0\x74\x19\xbc\x65\xbd\xde\x5b\xca\xc2\x61\x76\x5a\x65\x17\x4d\x7a\x84\x55\x68\x9b\xf6\x4a\x27\x09\xb6\x6b\xf3\xd5\xae\xaf\xa3\x17\xae\x38\x5b\x89\xaf\x7f\x14\xe4\x96\x65\xfa\xe6\x2a\x5f\xb2\x75\x66\x86\x3e\x27\xe9\xdb\xcb\x98\xbd\xfc\x19\xad\x5e\x1e\x11\x2a\x3c\x63\x17\x28\x16\x6e\x99\x94\x20\xae\x69\x80\x43\x16\xe2\x6d\x0f\xda\x69\xf3\x13\x52\x70\x9b\x36\x50\x99\xde\x03\xa5\x12\x82\x39\x97\xc8\x52\x9d\xf2\x24\xbd\x84\xed\x12\x88\x09\xce\x28\x93\x40\x14\xe4\x0e\x67\x53\x84\xce\x95\xe6\xb8\xd9\x74\x60\x2f\x37\x53\x20\xf3\xd4\x6c\xec\x8d\x3d\x27\x7c\x27\xc8\x31\x06\x89\xa0\x6a\x69\xe0\x9e\xe7\x23\x9d\xa9\x35\xb4\xa9\xb9\x3e\xff\x36\xc5\xca\x21\xff\x9a\x04\xe0\x83\x44\x71\xe0\x5e\xcb\x65\x16\xb3\x4f\x24\xbe\xda\xcd\xda\x6c\xeb\x38\x99\xf4\xe0\x3f\x73\x8d\x00\xdf\x7e\xd3\x9a\x50\xd6\x9a\x10\x39\xaf\x15\x3a\x16\x57\x21\x15\xd0\x88\xa1\xc5\x63\xd5\xf2\x0f\x23\xbe\xfb\xae\x00\x04\x10\x24\x22\x82\xc6\x89\x84\x96\x5a\xc4\xe0\xaf\x35\xb8\x27\x16\x4d\xfb\xe2\x82\xc4\xd4\x5f\x19\x2b\xbe\xd5\xb8\x6f\x99\x87\x16\x14\x65\x6b\xe3\xae\xf8\x3a\xe4\xbe\x45\x62\x1a\xa8\xa8\xf9\x3b\x8d\xe1\x07\x83\x3d\xdf\x52\xa2\x23\x61\xba\x79\x15\xac\x11\x3e\x46\xfe\x7c\xc1\x43\xf8\xeb\x6d\x01\xcc\x61\x28\x80\x16\x7e\x04\x44\xc1\xdf\xff\x7e\x78\xf6\x16\x7e\xf8\xa1\x38\x72\xe3\xce\x69\xe7\xbe\x19\xe3\xa2\x30\xc8\x74\xf9\x37\x38\xf7\x85\xae\xc3\xb3\xb7\x45\xe1\x5b\xb2\x5e\xb5\xdb\x4f\xc6\x1e\xcc\xf9\x0d\x83\xc6\x39\x24\x93\x84\xa9\xa4\x30\xae\x00\x28\x97\x52\xe1\x22\x50\x11\x84\x04\x17\x9c\x35\x04\x9a\xa7\x2f\xdf\x7d\x97\xeb\x42\x73\x14\x07\x4e\x0e\x0f\xf0\x8e\x2a\x68\xe5\x5e\xb5\x14\x20\x0f\xfa\x17\xfd\xc1\xd9\xc9\xc7\xd1\xf9\xd9\xe5\xf0\xe0\xf0\xfc\x0d\xb9\x91\x95\x00\xe3\x8b\xfe\xe0\xf8\x8d\x3b\x42\x4a\x0b\xba\xf7\x95\xb0\x97\x87\xe7\xe3\xe1\xd9\xe9\x9b\x92\xbd\x54\x42\xef\x7f\x18\x1c\x1f\x5e\xbc\xd9\xb8\x2b\x44\x94\x6a\xd8\xfe\x68\xf8\xf1\xf8\xf0\x27\x4d\x87\x79\xf9\x54\x84\xea\xff\x38\xfe\x78\x39\x1a\x7c\x1c\x1e\xbc\xd9\xb8\x2b\x78\xa2\xa6\xc9\x88\xaa\x71\x1e\x1f\xfe\xf4\xf1\xb4\xff\xfe\xf0\x4d\xa6\xb9\x12\xd6\xf1\x87\xfd\xd3\xc3\x8b\xf1\x2a\xda\xfb\xed\x62\x43\x67\xed\xc8\x8f\xa3\xf3\xe1\x65\xff\x42\x4f\x52\x2c\x72\x19\x14\x85\xc4\xb0\x02\xc7\xe1\xe0\xc3\xf9\xf0\xe2\xa7\x8f\x47\xe7\x67\x1f\x46\x6f\x36\xee\xaa\x1c\x58\xd3\xfc\x7f\x95\x49\x2f\x12\xbd\x51\x7e\xb3\x71\x77\x39\x1a\x34\xd3\xf4\xa7\x0c\xd9\x1f\x0c\xce\x3e\x9c\x5e\x58\x01\x1a\x4d\xf7\x83\x80\x27\x4c\x55\xa1\x3d\x3f\x3c\xb2\x4a\xce\x9f\x2a\x56\xa1\x3c\x1c\x8f\x8d\x90\x3d\xd6\xf4\x4e\x53\x25\xa7\xe7\x87\x17\xb9\x41\x76\xc4\xca\x5d\xa8\x47\x16\x66\xb6\x44\x6c\x82\x90\xad\x90\x6f\xe1\x2c\x46\x06\x49\x0c\x31\x17\x0a\xba\xdd\xf4\x99\x5d\xd5\x03\xa1\x2c\x53\x28\x08\x7a\x4d\x7a\x57\x0a\x26\xe5\xbc\xc1\xf4\x15\x5f\x2a\xd9\x35\x9c\x7b\x08\x71\x4d\x89\x27\x6e\x1b\xfa\x31\xcd\x1a\xf6\xda\x7b\x6d\x93\xd5\x1c\x89\x38\xc8\x9a\x3b\xed\x76\xbb\xfd\x60\xde\x5f\x20\xcd\x3d\xde\xc8\x62\xdc\x30\x1e\x09\xae\x78\xc0\xa3\x1e\xa8\x20\x4b\x54\xde\x0a\xbe\x18\xd9\x0b\x00\xdd\x6e\x16\x54\x2f\x78\x45\xa3\x36\xa9\x61\xec\x8f\x7c\x1e\x7a\x7c\xfd\x84\xd9\x34\x9f\x15\xf3\x15\x9b\xff\xd4\x19\x8d\x08\x2b\xa6\x5c\x69\x7f\x7c\x4e\x63\x62\xfe\x7a\x5d\x1c\x4b\x08\x97\x8c\x2c\x78\x38\x01\xe5\x72\xf0\x03\xd3\xd0\x8f\xe3\xea\xed\x9d\xed\x3e\xd8\xef\xf5\x1e\xda\xde\x99\x3e\x9b\x6c\x14\x12\x8a\x7f\xe3\x94\xe5\xd3\x89\x06\xd4\x1b\x75\x28\x34\x34\x1e\x38\x96\x4b\x07\x69\xd2\xfd\x6d\x80\xbe\x52\x82\x4e\x12\x85\x07\x38\xa5\x8c\xe6\xae\x66\x58\xe0\xb4\xdf\xd2\xc3\x8a\xe8\xd2\x5e\xcb\x63\x7d\x5c\xcf\xb2\xa5\x71\x30\xc7\x05\x79\x00\x57\x5d\x23\xab\xe7\xb0\x1d\xe3\xd2\xe1\x79\xd7\x1f\xbf\xf3\x3d\xe6\x3a\x82\x0e\x2f\x18\x5e\xcc\x05\x4f\x66\xf3\x38\xc9\xe5\x7c\xe7\x48\x42\xff\xfa\xe5\x03\xa3\x4a\xf6\xa0\xfe\x32\xc3\xfa\xa3\xa0\x0a\x2b\xfa\x4d\x2d\x38\x55\xe6\x24\xa1\x51\xb8\x4e\x9b\xfb\xba\xf3\xcf\xd1\xe7\x67\x51\xa7\x21\xfe\x53\x15\x5a\xa7\x61\xfd\x4f\x53\x68\x11\xd7\xd7\x53\xa7\xc0\x08\x89\xc4\xb5\xeb\xf3\xdc\xf6\xff\x0f\xd6\xa9\xe7\xe0\xff\xb4\x0a\x03\x12\x45\xf9\x67\xd5\xd9\x67\x58\xcc\x83\x99\x24\x8e\xa3\x25\x50\x25\x73\x1f\x35\xa9\xa5\x5b\x52\xff\xa5\x16\xe2\x5f\x5e\xda\x8b\xd0\xa6\xc6\xe0\xfb\x34\x6e\x12\xa8\x84\x44\x15\xf7\xd7\x6d\x6d\x21\xab\xd1\x67\x77\xf3\x8e\xf7\xa4\x51\x5f\xc9\x80\x56\x3f\x95\x62\xa0\x0c\x50\x76\x0b\x71\xf5\x2a\xe1\x3a\x2b\x73\xac\x7e\x38\x3f\xe9\xb9\x5b\x5e\x7e\x7b\xb7\x71\x57\x7c\x8b\x56\xf9\x3a\xa8\xf0\xa8\xe6\xbe\x95\xbd\x60\xca\xae\x86\x37\xfc\x45\x7d\xdf\x99\xaa\xb4\xf4\x49\x14\x73\xe9\x69\x78\x50\x4a\x40\x9e\x74\x79\xa5\xb2\xe4\x51\x51\xeb\x28\x3c\x56\x77\x17\xbf\x56\x3f\xa0\x02\xe0\xee\xa4\x3c\xe5\x1c\x5a\x2b\xf0\xc0\x14\x99\xf2\x79\x18\x67\xd1\x32\x7b\xe7\x55\x78\x90\x4a\x95\xc4\x68\x9a\x55\x40\xc6\xef\xd2\xa7\xb0\xe9\x4d\xbb\x5c\x89\xa9\x99\x16\x2b\xee\x5b\x3b\xdd\xcc\xa8\xfb\x31\x3d\x99\xe4\x46\x3e\x92\xc9\x3c\x5c\x61\x28\x3f\xf4\x74\x50\x95\x1f\xa2\x29\xbd\x53\xb4\xc0\x95\x5f\x96\x59\x7d\x79\x95\x81\x66\x5f\x9b\x59\x91\x7a\xbe\x16\xfa\xa0\x33\xab\x78\x16\xe3\x6a\xfb\xeb\x3e\xad\x03\x70\xc2\x49\xb8\x4f\x22\x6d\x15\x25\xf5\x16\xef\xa7\x9c\x25\x2a\x4e\x94\xbb\x41\xeb\x7e\x58\x95\x5e\x8e\x06\xd9\x27\x46\xbc\xd5\x9a\x19\x8a\x0f\x61\x0f\xfc\x7d\x50\x86\x37\xd1\xb2\x61\x1d\x82\x7d\xf5\xae\x77\x52\x66\x44\xfe\xb6\x93\x35\xf9\xf4\xda\xbc\xac\xc0\x59\x3f\xa1\xf6\x9a\x69\xa1\xdc\xe5\xfc\xb8\xc3\xe5\x78\x2d\xc7\x8b\x06\xd4\xb7\x73\xd1\x22\x8d\x15\xd5\x55\x37\xa8\xea\xee\x64\xb7\xfa\xa5\x33\xcc\x07\xa9\x2c\x9e\x3b\xfd\x41\x32\xab\x97\x1f\x54\xf6\x77\x6a\x50\x2c\xd5\xfa\x02\x66\x95\xa2\x46\xe0\x0a\x81\x5e\x61\x85\x05\x9b\xfb\x36\xd8\x63\x5a\x74\xa7\x86\x85\xe5\xeb\xe6\xad\x55\x91\x73\x70\x3a\xae\xa0\xc7\x1d\xa1\x1c\x9c\x8e\xe1\xed\x3f\x0e\x4e\x3f\x1b\x51\x07\x4c\xfa\x9b\xd3\xe3\xf1\xbb\x91\xe0\xb7\xcb\x01\x5f\x2c\x08\x73\xb5\xe9\x47\xe2\x49\x81\xe6\xf3\x84\x99\x57\xf1\x51\xb4\x84\x86\xf1\x84\x81\x45\x65\x3e\xdc\xa3\x51\xfb\x87\xf7\xee\x7b\x76\x34\x5f\x24\x07\x50\x36\xac\x97\x18\xdd\x36\x8f\xf0\xad\x53\xcd\xf6\xb1\x5b\x39\xe4\x7a\xae\x22\xea\x95\xf7\xe6\x2f\x8a\xe2\xd0\x2e\xf6\x87\x46\x2d\xf5\xbe\xa6\x4e\x50\x8f\x89\x9a\xb7\x14\x5f\xa9\xbf\xd5\xff\x9f\x83\x93\xd2\x3f\xee\x6b\x50\xd8\x70\xa3\x7c\x4b\x1f\x1a\x27\x9a\xac\x9e\x11\x80\xa6\xba\xa7\x7f\xfa\x6e\x0e\x79\xd1\xbe\xa9\x4b\x39\xd7\x58\x7e\xa9\x6f\xdc\x7d\xe3\x30\xdd\xff\x52\x77\x55\xbd\xff\xbf\xe2\xfc\x9d\xf5\xdc\x03\x0b\xe0\x2f\x73\xf8\x4b\xec\x23\x40\x0a\xee\x13\x85\xa6\xf3\x51\xcd\xf7\x46\x0a\x59\xd8\xd0\xfa\x3d\x42\xa5\xa5\x32\x30\x97\x53\xfe\x2c\x25\x0f\x46\x79\x25\xa7\x5f\xd8\x59\x7d\xa9\x97\x7f\x96\xe7\x88\x9f\x52\xf7\xc9\x3c\xab\x59\xf3\x99\x0d\x97\xfd\xf8\x34\xec\x9a\xda\x17\x66\x81\x8a\xb6\x0b\x1f\x4b\x4c\xe7\x8c\x28\xb3\x5f\xe9\xf0\x37\xe9\x07\x69\x36\x56\x37\x23\xcd\xc4\x75\x3b\x99\xcb\xaf\x02\xfb\xda\x31\x7d\x61\xbd\x34\x0f\x49\xd9\xb6\xfd\x0e\x1e\x61\x20\x12\x4f\x63\x1d\x6f\x8d\xc1\x1d\x7f\xd8\x3f\x1c\x9c\x9d\xbe\x1d\x1e\xbd\xd9\xd8\x8a\x6f\xc2\x17\xad\x3c\x72\xc5\x01\x99\x4c\x04\x7a\x62\x21\x91\x26\xe9\xa3\x2b\x9c\x1b\x32\x3c\xa5\xfd\x09\x4f\x54\x3a\xa2\x91\x16\xd5\xb3\xbc\xa9\x49\xb9\x79\x0a\xd0\x4a\x24\x8a\xc6\xcc\xdc\xf5\x8f\x05\x0a\xfc\x4d\xb6\xe0\x4f\xb0\xe7\xb4\x94\x51\x61\xcf\x5f\xdb\x60\x7b\xff\x95\x13\x31\x34\x73\x3f\x6a\xc5\x60\x6c\xdf\xd9\x62\x98\x65\x5a\x57\x7b\xd9\x57\x0b\x6b\x00\x16\xb3\x4f\x03\x87\x9f\x60\xf2\xd9\x58\xef\x86\x9d\x93\xc9\x7f\x51\xa2\xd2\xdf\xae\x61\x33\x43\x58\x4b\x09\x4c\x59\x7f\x3e\x7d\xfe\x66\x47\xee\x3b\x29\x05\x3a\x9f\x43\x5a\x4a\x86\x79\x73\xcc\x43\x74\x25\xbf\x3f\x53\x7a\xc5\x78\x95\x4e\xf2\x34\x32\x2b\x68\xd2\xa4\xea\xf4\x41\x77\xc9\xe7\x13\x38\xc8\xfc\x97\xf9\xf2\xe9\x82\x0b\x74\x1f\x29\x31\x31\x85\xca\xe2\xe7\xb6\x1e\xa3\x30\x25\x25\xf7\x31\xb4\xb5\x5f\x59\xeb\xc7\xd4\xbc\x5f\x37\x6f\x64\xfd\xc7\xd0\x4e\x86\xa5\xbc\xd0\x22\xf8\xef\x00\x00\x00\xff\xff\xd3\x7f\x58\xe9\x9a\x56\x00\x00") func cmdProviderAwsTemplatesFormationYamlBytes() ([]byte, error) { return bindataRead( @@ -83,7 +83,7 @@ func cmdProviderAwsTemplatesFormationYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "cmd/provider/aws/templates/formation.yaml", size: 23332, mode: os.FileMode(436), modTime: time.Unix(1520015640, 0)} + info := bindataFileInfo{name: "cmd/provider/aws/templates/formation.yaml", size: 22170, mode: os.FileMode(436), modTime: time.Unix(1521183917, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/cmd/provider/aws/templates/formation.yaml b/cmd/provider/aws/templates/formation.yaml index d0f22c6f..2358127f 100644 --- a/cmd/provider/aws/templates/formation.yaml +++ b/cmd/provider/aws/templates/formation.yaml @@ -88,48 +88,7 @@ Parameters: InstanceType: Description: EC2 instance type for the cluster. Type: String - Default: t2.medium - AllowedValues: - - t2.nano - - t2.micro - - t2.small - - t2.medium - - t2.large - - m3.medium - - m3.large - - m3.xlarge - - m3.2xlarge - - m4.large - - m4.xlarge - - m4.2xlarge - - m4.4xlarge - - m4.10xlarge - - c3.large - - c3.xlarge - - c3.2xlarge - - c3.4xlarge - - c3.8xlarge - - c4.large - - c4.xlarge - - c4.2xlarge - - c4.4xlarge - - c4.8xlarge - - g2.2xlarge - - g2.8xlarge - - r3.large - - r3.xlarge - - r3.2xlarge - - r3.4xlarge - - r3.8xlarge - - i2.xlarge - - i2.2xlarge - - i2.4xlarge - - i2.8xlarge - - d2.xlarge - - d2.2xlarge - - d2.4xlarge - - d2.8xlarge - ConstraintDescription: must be a valid EC2 instance type. + Default: m4.large # Specifies the size of the root disk for all EC2 instances, including master # and nodes. @@ -145,45 +104,17 @@ Parameters: Type: String Default: t2.micro AllowedValues: - - t2.nano - - t2.micro - - t2.small - - t2.medium - - t2.large - - m3.medium - - m3.large - - m3.xlarge - - m3.2xlarge - - m4.large - - m4.xlarge - - m4.2xlarge - - m4.4xlarge - - m4.10xlarge - - c3.large - - c3.xlarge - - c3.2xlarge - - c3.4xlarge - - c3.8xlarge - - c4.large - - c4.xlarge - - c4.2xlarge - - c4.4xlarge - - c4.8xlarge - - g2.2xlarge - - g2.8xlarge - - r3.large - - r3.xlarge - - r3.2xlarge - - r3.4xlarge - - r3.8xlarge - - i2.xlarge - - i2.2xlarge - - i2.4xlarge - - i2.8xlarge - - d2.xlarge - - d2.2xlarge - - d2.4xlarge - - d2.8xlarge + - t2.nano + - t2.micro + - t2.small + - t2.medium + - t2.large + - m3.medium + - m3.large + - m3.xlarge + - m3.2xlarge + - m4.large + - m4.xlarge ConstraintDescription: must be a valid EC2 instance type. Zone0: diff --git a/cmd/provider/gcp/gcp.go b/cmd/provider/gcp/gcp.go index 55a231e1..179904ed 100644 --- a/cmd/provider/gcp/gcp.go +++ b/cmd/provider/gcp/gcp.go @@ -38,6 +38,9 @@ type InitOptions struct { // SA stands for Service Account SAKeyPath, SAEmail, ClusterVersion string + // Default machine type for controller/api + ControllerMachineType string + DiskSize, NumNodes, ControllerPort int ClusterNotExists, Preemptible bool } diff --git a/cmd/proxy.go b/cmd/proxy.go new file mode 100644 index 00000000..fd78fe40 --- /dev/null +++ b/cmd/proxy.go @@ -0,0 +1,94 @@ +package cmd + +import ( + "fmt" + "net" + "strconv" + "strings" + + "github.com/datacol-io/datacol/client" + "github.com/datacol-io/datacol/cmd/stdcli" + "github.com/urfave/cli" +) + +func init() { + stdcli.AddCommand(cli.Command{ + Name: "proxy", + Description: "proxy local ports into a stack", + Action: cmdStackProxy, + ArgsUsage: "<[port:]host:hostport>", + Flags: []cli.Flag{stackFlag}, + }) +} + +func cmdStackProxy(c *cli.Context) error { + for _, arg := range c.Args() { + parts := strings.SplitN(arg, ":", 3) + + var ( + host string + port, hostport int + ) + + switch len(parts) { + case 2: + host = parts[0] + port = parseInt(parts[1]) + hostport = port + case 3: + port = parseInt(parts[0]) + host = parts[1] + hostport = parseInt(parts[2]) + default: + stdcli.ExitOnError(fmt.Errorf("invalid argument: %s", arg)) + } + + client, close := getApiClient(c) + defer close() + + go proxy("127.0.0.1", port, host, hostport, client) + } + + select {} +} + +func parseInt(str string) int { + p, err := strconv.Atoi(str) + if err != nil { + stdcli.ExitOnError(err) + } + return p +} + +func proxy(localhost string, localport int, remotehost string, remoteport int, client *client.Client) { + fmt.Printf("proxying %s:%d to %s:%d\n", localhost, localport, remotehost, remoteport) + + listener, err := net.Listen("tcp4", fmt.Sprintf("%s:%d", localhost, localport)) + if err != nil { + fmt.Printf("error: %s\n", err) + return + } + + defer listener.Close() + + for { + conn, err := listener.Accept() + if err != nil { + fmt.Printf("error: %s\n", err) + return + } + + defer conn.Close() + + fmt.Printf("connect: %d\n", localport) + + go func() { + err := client.ProxyRemote(remotehost, remoteport, conn) + if err != nil { + fmt.Printf("error: %s\n", err) + conn.Close() + return + } + }() + } +} diff --git a/cmd/ps.go b/cmd/ps.go index 60d2757e..9a431f4f 100644 --- a/cmd/ps.go +++ b/cmd/ps.go @@ -14,6 +14,7 @@ func init() { Name: "ps", Usage: "manage process in an app", Action: cmdAppPS, + Flags: []cli.Flag{&appFlag}, Subcommands: []cli.Command{ { Name: "scale", @@ -47,7 +48,11 @@ func cmdAppPS(c *cli.Context) error { items, err := client.ListProcess(name) stdcli.ExitOnError(err) - term.Println(toJson(items)) + if len(items) > 0 { + term.Println(toJson(items)) + } else { + term.Println("No process running") + } return nil } diff --git a/cmd/run.go b/cmd/run.go index 7ce9d2d5..cb8f7add 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -13,12 +13,13 @@ func init() { Name: "run", Usage: "execute a command in an app", Action: cmdAppRun, + Flags: []cli.Flag{&appFlag}, }) } // follow https://github.com/openshift/origin/search?utf8=%E2%9C%93&q=exec+arrow&type=Issues func cmdAppRun(c *cli.Context) error { - _, name, err := getDirApp(".") + name, err := getCurrentApp(c) stdcli.ExitOnError(err) client, close := getApiClient(c) diff --git a/cmd/stack.go b/cmd/stack.go index ade8a583..428eafc4 100644 --- a/cmd/stack.go +++ b/cmd/stack.go @@ -28,8 +28,11 @@ var ( defaultGcpZone = "asia-east1-a" //Taiwan defaultAWSZone = "ap-southeast-1a" //Singapur - defaultAWSInstanceType = "t2.medium" + defaultAWSInstanceType = "m4.large" defaultGCPInstanceType = "n1-standard-1" + + //Default GKE cluster version if not specified + defaultGKEVersion = "1.7.14-gke.1" ) const ( @@ -39,7 +42,7 @@ const ( func init() { stdcli.AddCommand(cli.Command{ Name: "init", - Usage: "[cloud-provider] [credentials.csv]", + Usage: "[cloud-provider] [aws-credentials.csv OR gcp-service-account.json]", Description: "create new datacol stack", Action: cmdStackCreate, Flags: []cli.Flag{ @@ -74,12 +77,15 @@ func init() { &cli.IntFlag{ Name: "disk-size", Usage: "SSD disk size for cluster in GB", - Value: 10, + Value: 50, }, &cli.StringFlag{ - Name: "machine-type", + Name: "cluster-instance-type", Usage: "type of instance to use for cluster nodes", - Value: "", + }, + &cli.StringFlag{ + Name: "controller-instance-type", + Usage: "type of instance to use for bastion or controller node", }, &cli.BoolFlag{ Name: "preemptible", @@ -95,26 +101,28 @@ func init() { }, &cli.StringFlag{ Name: "cluster-version", - Usage: "The Kubernetes version to use for the master and nodes", - Value: "1.7.11-gke.1", + Usage: "The Kubernetes version to use for the master and nodes (only for GCP)", + Value: defaultGKEVersion, }, &cli.StringFlag{ Name: "key", - Usage: "[Name of ssh-keypair to create for AWS] OR [/path/to/service-account key for GCP]", + Usage: "[Name of ssh-keypair to create for AWS]", }, }, }) stdcli.AddCommand(cli.Command{ - Name: "destroy", - Usage: "destroy the datacol stack from your cloud account", - Action: cmdStackDestroy, + Name: "destroy", + ArgsUsage: "[name]", + Usage: "destroy the datacol stack from your cloud account", + Action: cmdStackDestroy, }) } func cmdStackCreate(c *cli.Context) (err error) { if c.NArg() < 1 { - err = fmt.Errorf("Please provide a cloud provider (aws, gcp, local)") + term.Warningln("Please provide a cloud provider (aws, gcp, local)") + stdcli.Usage(c) } stdcli.ExitOnError(err) @@ -143,18 +151,19 @@ func cmdAWSStackCreate(c *cli.Context) error { stackName := c.String("name") options := &aws.InitOptions{ - Name: stackName, - DiskSize: c.Int("disk-size"), - NumNodes: c.Int("nodes"), - MachineType: c.String("machine-type"), - Zone: c.String("zone"), - Region: c.String("region"), - Bucket: c.String("bucket"), - Version: stdcli.Version, - APIKey: c.String("ApiKey"), - KeyName: c.String("key"), - UseSpotInstance: c.Bool("preemptible"), - CreateCluster: len(c.String("cluster")) == 0, + Name: stackName, + DiskSize: c.Int("disk-size"), + NumNodes: c.Int("nodes"), + Zone: c.String("zone"), + Region: c.String("region"), + Bucket: c.String("bucket"), + Version: stdcli.Version, + APIKey: c.String("ApiKey"), + KeyName: c.String("key"), + UseSpotInstance: c.Bool("preemptible"), + CreateCluster: len(c.String("cluster")) == 0, + ClusterInstanceType: c.String("cluster-instance-type"), + ControllerInstanceType: c.String("controller-instance-type"), } if len(options.APIKey) == 0 { @@ -195,10 +204,17 @@ func cmdGCPStackCreate(c *cli.Context) error { password := c.String("password") cluster := c.String("cluster") - machineType := c.String("machine-type") + machineType := c.String("cluster-instance-type") + apiMachineType := c.String("controller-instance-type") + preemptible := c.Bool("preemptible") diskSize := c.Int("disk-size") + var svaKey string + if c.NArg() > 1 { + svaKey = c.Args().Get(1) + } + options := &gcp.InitOptions{ Name: stackName, ClusterName: cluster, @@ -210,8 +226,11 @@ func cmdGCPStackCreate(c *cli.Context) error { Preemptible: preemptible, Version: stdcli.Version, ApiKey: password, - SAKeyPath: c.String("key"), + SAKeyPath: svaKey, ClusterVersion: c.String("cluster-version"), + + //FIXME: doesn't get applied into deployment spec yet + ControllerMachineType: apiMachineType, } ec := env.FromHost() @@ -266,8 +285,8 @@ func initializeAWS(opts *aws.InitOptions, credentialsFile string) error { opts.Region = getAwsRegionFromZone(opts.Zone) } - if opts.MachineType == "" { - opts.MachineType = defaultAWSInstanceType + if opts.ClusterInstanceType == "" { + opts.ClusterInstanceType = defaultAWSInstanceType } if len(opts.Bucket) == 0 { @@ -386,7 +405,13 @@ func initializeGCP(opts *gcp.InitOptions, nodes int, optout bool) error { } func cmdStackDestroy(c *cli.Context) (err error) { - auth, _ := stdcli.GetAuthOrDie() + stack := c.Args().First() + if stack == "" { + term.Warningln("Missing required argument: name") + stdcli.Usage(c) + } + + auth, _ := stdcli.GetAuthContextOrDie(stack) provider := auth.Provider prompt := fmt.Sprintf("This is destructive action. Do you want to delete %s stack on %s ?", auth.Name, provider) @@ -396,9 +421,9 @@ func cmdStackDestroy(c *cli.Context) (err error) { switch strings.ToLower(provider) { case "gcp": - err = gcpTeardown() + err = gcpTeardown(c) case "aws": - err = awsTeardown() + err = awsTeardown(c) default: err = fmt.Errorf("Invalid cloud provider: %s. Should be either of aws or gcp.", provider) } @@ -408,8 +433,8 @@ func cmdStackDestroy(c *cli.Context) (err error) { return nil } -func awsTeardown() error { - auth, rc := stdcli.GetAuthOrDie() +func awsTeardown(c *cli.Context) error { + auth, rc := stdcli.GetAuthOrDie(c) var credentialsFile string credentialsFile = filepath.Join(pb.ConfigPath, auth.Name, pb.AwsCredentialFile) @@ -434,8 +459,8 @@ func awsTeardown() error { return os.RemoveAll(filepath.Join(pb.ConfigPath, auth.Name)) } -func gcpTeardown() error { - auth, rc := stdcli.GetAuthOrDie() +func gcpTeardown(c *cli.Context) error { + auth, rc := stdcli.GetAuthOrDie(c) if err := gcp.TeardownStack(auth.Name, auth.Project, auth.Bucket); err != nil { return err } diff --git a/cmd/stdcli/apprc.go b/cmd/stdcli/apprc.go index f35201c2..bc9b3b0c 100644 --- a/cmd/stdcli/apprc.go +++ b/cmd/stdcli/apprc.go @@ -6,6 +6,7 @@ import ( "github.com/appscode/go/io" term "github.com/appscode/go/term" pb "github.com/datacol-io/datacol/api/models" + "github.com/urfave/cli" ) var apprcPath string @@ -29,10 +30,13 @@ type Apprc struct { Auths []*Auth `json:"auths,omitempty"` } -func (rc *Apprc) GetAuth() *Auth { - if rc.Context != "" { +func (rc *Apprc) GetAuth(stack string) *Auth { + if stack == "" { + stack = rc.Context + } + if stack != "" { for _, a := range rc.Auths { - if a.Name == rc.Context { + if a.Name == stack { return a } } @@ -89,12 +93,33 @@ func LoadApprc() (*Apprc, error) { } /* Exits if there is any error.*/ -func GetAuthOrDie() (*Auth, *Apprc) { +func GetAuthContextOrDie(stack string) (*Auth, *Apprc) { + rc, err := LoadApprc() + if err != nil { + term.Fatalln("Command requires authentication, please run `datacol login`") + } + + a := rc.GetAuth(stack) + + if a == nil { + term.Fatalln("Command requires authentication, please run `datacol login`") + } + return a, rc +} + +/* Exits if there is any error.*/ +func GetAuthOrDie(c *cli.Context) (*Auth, *Apprc) { rc, err := LoadApprc() if err != nil { term.Fatalln("Command requires authentication, please run `datacol login`") } - a := rc.GetAuth() + + stack := c.String("stack") + if stack == "" { + stack = GetAppSetting("stack") + } + a := rc.GetAuth(stack) + if a == nil { term.Fatalln("Command requires authentication, please run `datacol login`") } @@ -107,7 +132,8 @@ func GetAuthOrAnon() (*Auth, bool) { if err != nil { return NewAnonAUth(), false } - a := rc.GetAuth() + a := rc.GetAuth("") + if a == nil { return NewAnonAUth(), false } diff --git a/cmd/stdcli/cli.go b/cmd/stdcli/cli.go index ec62433f..a12bc162 100644 --- a/cmd/stdcli/cli.go +++ b/cmd/stdcli/cli.go @@ -25,8 +25,8 @@ var ( ) func init() { - Version = "1.0.0-alpha.11" - LocalAppDir = ".dtcol" + Version = "1.0.0-alpha.12" + LocalAppDir = ".datacol" Binary = filepath.Base(os.Args[0]) Commands = []cli.Command{} Stack404 = errors.New("stack not found. To create a new stack run `datacol init` or set `STACK` environment variable.") diff --git a/hack/make.rb b/hack/make.rb index 68bd0950..a36a067e 100644 --- a/hack/make.rb +++ b/hack/make.rb @@ -1,6 +1,6 @@ require 'rubygems' -$version = ENV.fetch('VERSION', "1.0.0-alpha.11") +$version = ENV.fetch('VERSION', "1.0.0-alpha.12") $env = ENV.fetch('DATACOL_ENV') # dev or prod $cgo = ENV.fetch("CGO_ENABLED", "0")