From c10fb46ede2f70cd31ed6db0cdc58e33b30d736a Mon Sep 17 00:00:00 2001 From: Sven Efftinge Date: Sat, 12 Jun 2021 11:27:13 +0000 Subject: [PATCH 1/2] initialize workspaces with additional file content Introduces an AdditionalContentContext and the corresponding Initializer. --- .../content-service-api/go/initializer.pb.go | 557 ++++++++++---- .../content-service-api/initializer.proto | 26 + .../typescript/src/initializer_pb.d.ts | 89 +++ .../typescript/src/initializer_pb.js | 716 +++++++++++++++++- .../pkg/initializer/download.go | 110 +++ .../pkg/initializer/initializer.go | 43 ++ components/gitpod-protocol/src/protocol.ts | 29 +- components/gitpod-protocol/src/wsready.ts | 2 +- components/image-builder/cmd/bob-init-base.go | 6 +- components/image-builder/go.mod | 1 + .../image-builder/pkg/builder/docker.go | 23 +- components/server/src/container-module.ts | 4 +- ...dditional-content-prefix-context-parser.ts | 45 ++ .../server/src/workspace/config-provider.ts | 108 +-- .../src/workspace/image-source-provider.ts | 23 +- .../server/src/workspace/workspace-starter.ts | 104 ++- .../typescript-rest/lib/control.dt.ts | 13 + .../typescript-rest/lib/control.swagger.json | 54 ++ .../typescript-rest/lib/info.dt.ts | 81 ++ .../typescript-rest/lib/info.swagger.json | 145 ++++ .../typescript-rest/lib/notification.dt.ts | 58 ++ .../lib/notification.swagger.json | 181 +++++ .../typescript-rest/lib/port.dt.ts | 57 ++ .../typescript-rest/lib/port.swagger.json | 211 ++++++ .../typescript-rest/lib/status.dt.ts | 194 +++++ .../typescript-rest/lib/status.swagger.json | 591 +++++++++++++++ .../typescript-rest/lib/terminal.dt.ts | 89 +++ .../typescript-rest/lib/terminal.swagger.json | 316 ++++++++ .../typescript-rest/lib/token.dt.ts | 105 +++ .../typescript-rest/lib/token.swagger.json | 348 +++++++++ yarn.lock | 19 +- 31 files changed, 4093 insertions(+), 255 deletions(-) create mode 100644 components/content-service/pkg/initializer/download.go create mode 100644 components/server/src/workspace/additional-content-prefix-context-parser.ts create mode 100644 components/supervisor-api/typescript-rest/lib/control.dt.ts create mode 100644 components/supervisor-api/typescript-rest/lib/control.swagger.json create mode 100644 components/supervisor-api/typescript-rest/lib/info.dt.ts create mode 100644 components/supervisor-api/typescript-rest/lib/info.swagger.json create mode 100644 components/supervisor-api/typescript-rest/lib/notification.dt.ts create mode 100644 components/supervisor-api/typescript-rest/lib/notification.swagger.json create mode 100644 components/supervisor-api/typescript-rest/lib/port.dt.ts create mode 100644 components/supervisor-api/typescript-rest/lib/port.swagger.json create mode 100644 components/supervisor-api/typescript-rest/lib/status.dt.ts create mode 100644 components/supervisor-api/typescript-rest/lib/status.swagger.json create mode 100644 components/supervisor-api/typescript-rest/lib/terminal.dt.ts create mode 100644 components/supervisor-api/typescript-rest/lib/terminal.swagger.json create mode 100644 components/supervisor-api/typescript-rest/lib/token.dt.ts create mode 100644 components/supervisor-api/typescript-rest/lib/token.swagger.json diff --git a/components/content-service-api/go/initializer.pb.go b/components/content-service-api/go/initializer.pb.go index e48c71b78f5a7b..a05ec98e55844a 100644 --- a/components/content-service-api/go/initializer.pb.go +++ b/components/content-service-api/go/initializer.pb.go @@ -146,6 +146,8 @@ type WorkspaceInitializer struct { // *WorkspaceInitializer_Git // *WorkspaceInitializer_Snapshot // *WorkspaceInitializer_Prebuild + // *WorkspaceInitializer_Composite + // *WorkspaceInitializer_Download Spec isWorkspaceInitializer_Spec `protobuf_oneof:"spec"` } @@ -216,6 +218,20 @@ func (x *WorkspaceInitializer) GetPrebuild() *PrebuildInitializer { return nil } +func (x *WorkspaceInitializer) GetComposite() *CompositeInitializer { + if x, ok := x.GetSpec().(*WorkspaceInitializer_Composite); ok { + return x.Composite + } + return nil +} + +func (x *WorkspaceInitializer) GetDownload() *FileDownloadInitializer { + if x, ok := x.GetSpec().(*WorkspaceInitializer_Download); ok { + return x.Download + } + return nil +} + type isWorkspaceInitializer_Spec interface { isWorkspaceInitializer_Spec() } @@ -236,6 +252,14 @@ type WorkspaceInitializer_Prebuild struct { Prebuild *PrebuildInitializer `protobuf:"bytes,4,opt,name=prebuild,proto3,oneof"` } +type WorkspaceInitializer_Composite struct { + Composite *CompositeInitializer `protobuf:"bytes,5,opt,name=composite,proto3,oneof"` +} + +type WorkspaceInitializer_Download struct { + Download *FileDownloadInitializer `protobuf:"bytes,6,opt,name=download,proto3,oneof"` +} + func (*WorkspaceInitializer_Empty) isWorkspaceInitializer_Spec() {} func (*WorkspaceInitializer_Git) isWorkspaceInitializer_Spec() {} @@ -244,6 +268,115 @@ func (*WorkspaceInitializer_Snapshot) isWorkspaceInitializer_Spec() {} func (*WorkspaceInitializer_Prebuild) isWorkspaceInitializer_Spec() {} +func (*WorkspaceInitializer_Composite) isWorkspaceInitializer_Spec() {} + +func (*WorkspaceInitializer_Download) isWorkspaceInitializer_Spec() {} + +// CompositeInitializer uses a collection of initializer to produce workspace content. +// All initializer are executed in the order they're provided. +type CompositeInitializer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Initializer []*WorkspaceInitializer `protobuf:"bytes,1,rep,name=initializer,proto3" json:"initializer,omitempty"` +} + +func (x *CompositeInitializer) Reset() { + *x = CompositeInitializer{} + if protoimpl.UnsafeEnabled { + mi := &file_initializer_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CompositeInitializer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CompositeInitializer) ProtoMessage() {} + +func (x *CompositeInitializer) ProtoReflect() protoreflect.Message { + mi := &file_initializer_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CompositeInitializer.ProtoReflect.Descriptor instead. +func (*CompositeInitializer) Descriptor() ([]byte, []int) { + return file_initializer_proto_rawDescGZIP(), []int{1} +} + +func (x *CompositeInitializer) GetInitializer() []*WorkspaceInitializer { + if x != nil { + return x.Initializer + } + return nil +} + +// FileDownloadInitializer downloads files and uses them as workspace content. +type FileDownloadInitializer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Files []*FileDownloadInitializer_FileInfo `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"` + TargetLocation string `protobuf:"bytes,2,opt,name=target_location,json=targetLocation,proto3" json:"target_location,omitempty"` +} + +func (x *FileDownloadInitializer) Reset() { + *x = FileDownloadInitializer{} + if protoimpl.UnsafeEnabled { + mi := &file_initializer_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FileDownloadInitializer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FileDownloadInitializer) ProtoMessage() {} + +func (x *FileDownloadInitializer) ProtoReflect() protoreflect.Message { + mi := &file_initializer_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FileDownloadInitializer.ProtoReflect.Descriptor instead. +func (*FileDownloadInitializer) Descriptor() ([]byte, []int) { + return file_initializer_proto_rawDescGZIP(), []int{2} +} + +func (x *FileDownloadInitializer) GetFiles() []*FileDownloadInitializer_FileInfo { + if x != nil { + return x.Files + } + return nil +} + +func (x *FileDownloadInitializer) GetTargetLocation() string { + if x != nil { + return x.TargetLocation + } + return "" +} + type EmptyInitializer struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -253,7 +386,7 @@ type EmptyInitializer struct { func (x *EmptyInitializer) Reset() { *x = EmptyInitializer{} if protoimpl.UnsafeEnabled { - mi := &file_initializer_proto_msgTypes[1] + mi := &file_initializer_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -266,7 +399,7 @@ func (x *EmptyInitializer) String() string { func (*EmptyInitializer) ProtoMessage() {} func (x *EmptyInitializer) ProtoReflect() protoreflect.Message { - mi := &file_initializer_proto_msgTypes[1] + mi := &file_initializer_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -279,7 +412,7 @@ func (x *EmptyInitializer) ProtoReflect() protoreflect.Message { // Deprecated: Use EmptyInitializer.ProtoReflect.Descriptor instead. func (*EmptyInitializer) Descriptor() ([]byte, []int) { - return file_initializer_proto_rawDescGZIP(), []int{1} + return file_initializer_proto_rawDescGZIP(), []int{3} } type GitInitializer struct { @@ -304,7 +437,7 @@ type GitInitializer struct { func (x *GitInitializer) Reset() { *x = GitInitializer{} if protoimpl.UnsafeEnabled { - mi := &file_initializer_proto_msgTypes[2] + mi := &file_initializer_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -317,7 +450,7 @@ func (x *GitInitializer) String() string { func (*GitInitializer) ProtoMessage() {} func (x *GitInitializer) ProtoReflect() protoreflect.Message { - mi := &file_initializer_proto_msgTypes[2] + mi := &file_initializer_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -330,7 +463,7 @@ func (x *GitInitializer) ProtoReflect() protoreflect.Message { // Deprecated: Use GitInitializer.ProtoReflect.Descriptor instead. func (*GitInitializer) Descriptor() ([]byte, []int) { - return file_initializer_proto_rawDescGZIP(), []int{2} + return file_initializer_proto_rawDescGZIP(), []int{4} } func (x *GitInitializer) GetRemoteUri() string { @@ -396,7 +529,7 @@ type GitConfig struct { func (x *GitConfig) Reset() { *x = GitConfig{} if protoimpl.UnsafeEnabled { - mi := &file_initializer_proto_msgTypes[3] + mi := &file_initializer_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -409,7 +542,7 @@ func (x *GitConfig) String() string { func (*GitConfig) ProtoMessage() {} func (x *GitConfig) ProtoReflect() protoreflect.Message { - mi := &file_initializer_proto_msgTypes[3] + mi := &file_initializer_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -422,7 +555,7 @@ func (x *GitConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use GitConfig.ProtoReflect.Descriptor instead. func (*GitConfig) Descriptor() ([]byte, []int) { - return file_initializer_proto_rawDescGZIP(), []int{3} + return file_initializer_proto_rawDescGZIP(), []int{5} } func (x *GitConfig) GetCustomConfig() map[string]string { @@ -472,7 +605,7 @@ type SnapshotInitializer struct { func (x *SnapshotInitializer) Reset() { *x = SnapshotInitializer{} if protoimpl.UnsafeEnabled { - mi := &file_initializer_proto_msgTypes[4] + mi := &file_initializer_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -485,7 +618,7 @@ func (x *SnapshotInitializer) String() string { func (*SnapshotInitializer) ProtoMessage() {} func (x *SnapshotInitializer) ProtoReflect() protoreflect.Message { - mi := &file_initializer_proto_msgTypes[4] + mi := &file_initializer_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -498,7 +631,7 @@ func (x *SnapshotInitializer) ProtoReflect() protoreflect.Message { // Deprecated: Use SnapshotInitializer.ProtoReflect.Descriptor instead. func (*SnapshotInitializer) Descriptor() ([]byte, []int) { - return file_initializer_proto_rawDescGZIP(), []int{4} + return file_initializer_proto_rawDescGZIP(), []int{6} } func (x *SnapshotInitializer) GetSnapshot() string { @@ -522,7 +655,7 @@ type PrebuildInitializer struct { func (x *PrebuildInitializer) Reset() { *x = PrebuildInitializer{} if protoimpl.UnsafeEnabled { - mi := &file_initializer_proto_msgTypes[5] + mi := &file_initializer_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -535,7 +668,7 @@ func (x *PrebuildInitializer) String() string { func (*PrebuildInitializer) ProtoMessage() {} func (x *PrebuildInitializer) ProtoReflect() protoreflect.Message { - mi := &file_initializer_proto_msgTypes[5] + mi := &file_initializer_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -548,7 +681,7 @@ func (x *PrebuildInitializer) ProtoReflect() protoreflect.Message { // Deprecated: Use PrebuildInitializer.ProtoReflect.Descriptor instead. func (*PrebuildInitializer) Descriptor() ([]byte, []int) { - return file_initializer_proto_rawDescGZIP(), []int{5} + return file_initializer_proto_rawDescGZIP(), []int{7} } func (x *PrebuildInitializer) GetPrebuild() *SnapshotInitializer { @@ -592,7 +725,7 @@ type GitStatus struct { func (x *GitStatus) Reset() { *x = GitStatus{} if protoimpl.UnsafeEnabled { - mi := &file_initializer_proto_msgTypes[6] + mi := &file_initializer_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -605,7 +738,7 @@ func (x *GitStatus) String() string { func (*GitStatus) ProtoMessage() {} func (x *GitStatus) ProtoReflect() protoreflect.Message { - mi := &file_initializer_proto_msgTypes[6] + mi := &file_initializer_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -618,7 +751,7 @@ func (x *GitStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use GitStatus.ProtoReflect.Descriptor instead. func (*GitStatus) Descriptor() ([]byte, []int) { - return file_initializer_proto_rawDescGZIP(), []int{6} + return file_initializer_proto_rawDescGZIP(), []int{8} } func (x *GitStatus) GetBranch() string { @@ -677,12 +810,82 @@ func (x *GitStatus) GetTotalUnpushedCommits() int64 { return 0 } +type FileDownloadInitializer_FileInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + // file_path is relative to the target_location, e.g. if target_location is in `/workspace/myrepo` + // a file_path of `foobar/file` would produce a file in `/workspace/myrepo/foobar/file`. + // file_path must include the filename. The FileDownloadInitializer will create any parent directories + // necessary to place the file. + FilePath string `protobuf:"bytes,2,opt,name=file_path,json=filePath,proto3" json:"file_path,omitempty"` + // digest is a hash of the file content in the OCI digest format (see https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests). + // This information is used to compute subsequent + // content versions, and to validate the file content was downloaded correctly. + Digest string `protobuf:"bytes,3,opt,name=digest,proto3" json:"digest,omitempty"` +} + +func (x *FileDownloadInitializer_FileInfo) Reset() { + *x = FileDownloadInitializer_FileInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_initializer_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *FileDownloadInitializer_FileInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*FileDownloadInitializer_FileInfo) ProtoMessage() {} + +func (x *FileDownloadInitializer_FileInfo) ProtoReflect() protoreflect.Message { + mi := &file_initializer_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use FileDownloadInitializer_FileInfo.ProtoReflect.Descriptor instead. +func (*FileDownloadInitializer_FileInfo) Descriptor() ([]byte, []int) { + return file_initializer_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *FileDownloadInitializer_FileInfo) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *FileDownloadInitializer_FileInfo) GetFilePath() string { + if x != nil { + return x.FilePath + } + return "" +} + +func (x *FileDownloadInitializer_FileInfo) GetDigest() string { + if x != nil { + return x.Digest + } + return "" +} + var File_initializer_proto protoreflect.FileDescriptor var file_initializer_proto_rawDesc = []byte{ 0x0a, 0x11, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x22, 0x92, 0x02, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x69, 0x63, 0x65, 0x22, 0x9f, 0x03, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x05, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x45, 0x6d, 0x70, @@ -699,95 +902,124 @@ var file_initializer_proto_rawDesc = []byte{ 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x50, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x48, 0x00, 0x52, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, - 0x42, 0x06, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x12, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x22, 0xa2, 0x02, 0x0a, - 0x0e, 0x47, 0x69, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, - 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x55, 0x72, 0x69, 0x12, 0x2e, - 0x0a, 0x13, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x52, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x70, 0x73, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x55, 0x72, 0x69, 0x12, 0x40, - 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x6c, 0x6f, 0x6e, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x65, - 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x65, 0x74, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x54, 0x61, 0x67, 0x65, - 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x5f, 0x6c, 0x6f, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x68, - 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, - 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, - 0x47, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x22, 0xc2, 0x02, 0x0a, 0x09, 0x47, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x50, 0x0a, 0x0d, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x45, 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, - 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, - 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x74, - 0x68, 0x55, 0x73, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x61, - 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, - 0x74, 0x68, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x75, - 0x74, 0x68, 0x5f, 0x6f, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x75, - 0x74, 0x68, 0x4f, 0x74, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x31, 0x0a, 0x13, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, - 0x6f, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x1a, 0x0a, - 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x22, 0x88, 0x01, 0x0a, 0x13, 0x50, 0x72, - 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, - 0x72, 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x2e, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x6e, 0x69, - 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x52, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, - 0x6c, 0x64, 0x12, 0x30, 0x0a, 0x03, 0x67, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x2e, 0x47, 0x69, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x52, - 0x03, 0x67, 0x69, 0x74, 0x22, 0xe7, 0x02, 0x0a, 0x09, 0x47, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x61, - 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, - 0x29, 0x0a, 0x10, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, - 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6d, - 0x6d, 0x69, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, - 0x74, 0x61, 0x6c, 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x55, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, - 0x12, 0x27, 0x0a, 0x0f, 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x66, 0x69, - 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x6e, 0x74, 0x72, 0x61, - 0x63, 0x6b, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, - 0x61, 0x6c, 0x5f, 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, - 0x65, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, - 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x29, 0x0a, - 0x10, 0x75, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, - 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, - 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, - 0x6c, 0x5f, 0x75, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, - 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, - 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x2a, 0x5a, - 0x0a, 0x0f, 0x43, 0x6c, 0x6f, 0x6e, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4d, 0x6f, 0x64, - 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x48, 0x45, 0x41, 0x44, - 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4d, - 0x4d, 0x49, 0x54, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, - 0x42, 0x52, 0x41, 0x4e, 0x43, 0x48, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x4f, 0x43, 0x41, - 0x4c, 0x5f, 0x42, 0x52, 0x41, 0x4e, 0x43, 0x48, 0x10, 0x03, 0x2a, 0x40, 0x0a, 0x0d, 0x47, 0x69, - 0x74, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x4e, - 0x4f, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x41, 0x53, 0x49, - 0x43, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x42, 0x41, 0x53, 0x49, - 0x43, 0x5f, 0x41, 0x55, 0x54, 0x48, 0x5f, 0x4f, 0x54, 0x53, 0x10, 0x02, 0x42, 0x31, 0x5a, 0x2f, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, - 0x64, 0x2d, 0x69, 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, - 0x65, 0x6e, 0x74, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x44, 0x0a, 0x09, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x49, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x48, 0x00, 0x52, 0x09, 0x63, 0x6f, 0x6d, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x12, 0x45, 0x0a, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, + 0x61, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, + 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x72, 0x48, 0x00, 0x52, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x06, 0x0a, + 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x5e, 0x0a, 0x14, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x65, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x46, 0x0a, + 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x69, + 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x52, 0x0b, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x22, 0xdd, 0x01, 0x0a, 0x17, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, + 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x72, 0x12, 0x46, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x49, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x1a, 0x51, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, + 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, + 0x69, 0x67, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x49, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x22, 0xa2, 0x02, 0x0a, 0x0e, 0x47, 0x69, + 0x74, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x55, 0x72, 0x69, 0x12, 0x2e, 0x0a, 0x13, 0x75, + 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x75, + 0x72, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x55, 0x72, 0x69, 0x12, 0x40, 0x0a, 0x0b, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x43, 0x6c, 0x6f, 0x6e, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4d, 0x6f, 0x64, + 0x65, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6c, 0x6f, 0x6e, 0x65, 0x54, 0x61, 0x67, 0x65, 0x74, 0x12, 0x2b, + 0x0a, 0x11, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x6f, 0x75, 0x74, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x6f, 0x75, 0x74, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x31, 0x0a, 0x06, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x69, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xc2, + 0x02, 0x0a, 0x09, 0x47, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x50, 0x0a, 0x0d, + 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x45, + 0x0a, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x4d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0e, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x75, 0x73, + 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x61, 0x75, 0x74, 0x68, 0x55, 0x73, + 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x75, 0x74, 0x68, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x61, 0x75, 0x74, 0x68, 0x5f, + 0x6f, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x75, 0x74, 0x68, 0x4f, + 0x74, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x31, 0x0a, 0x13, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, + 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x22, 0x88, 0x01, 0x0a, 0x13, 0x50, 0x72, 0x65, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x12, 0x3f, + 0x0a, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x2e, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x52, 0x08, 0x70, 0x72, 0x65, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x12, + 0x30, 0x0a, 0x03, 0x67, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x47, 0x69, + 0x74, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x52, 0x03, 0x67, 0x69, + 0x74, 0x22, 0xe7, 0x02, 0x0a, 0x09, 0x47, 0x69, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x62, 0x72, 0x61, 0x6e, 0x63, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x6c, 0x61, 0x74, 0x65, 0x73, + 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, + 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x12, 0x29, 0x0a, 0x10, + 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, + 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, 0x6e, + 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x27, 0x0a, + 0x0f, 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, + 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, + 0x75, 0x6e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, 0x6e, 0x74, 0x72, + 0x61, 0x63, 0x6b, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x29, 0x0a, 0x10, 0x75, 0x6e, + 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x75, 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x43, 0x6f, + 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x75, + 0x6e, 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x03, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x55, 0x6e, 0x70, 0x75, + 0x73, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x73, 0x2a, 0x5a, 0x0a, 0x0f, 0x43, + 0x6c, 0x6f, 0x6e, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0f, + 0x0a, 0x0b, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x48, 0x45, 0x41, 0x44, 0x10, 0x00, 0x12, + 0x11, 0x0a, 0x0d, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, + 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x45, 0x4d, 0x4f, 0x54, 0x45, 0x5f, 0x42, 0x52, 0x41, + 0x4e, 0x43, 0x48, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x4f, 0x43, 0x41, 0x4c, 0x5f, 0x42, + 0x52, 0x41, 0x4e, 0x43, 0x48, 0x10, 0x03, 0x2a, 0x40, 0x0a, 0x0d, 0x47, 0x69, 0x74, 0x41, 0x75, + 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x4e, 0x4f, 0x5f, 0x41, + 0x55, 0x54, 0x48, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x42, 0x41, 0x53, 0x49, 0x43, 0x5f, 0x41, + 0x55, 0x54, 0x48, 0x10, 0x01, 0x12, 0x12, 0x0a, 0x0e, 0x42, 0x41, 0x53, 0x49, 0x43, 0x5f, 0x41, + 0x55, 0x54, 0x48, 0x5f, 0x4f, 0x54, 0x53, 0x10, 0x02, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2d, 0x69, + 0x6f, 0x2f, 0x67, 0x69, 0x74, 0x70, 0x6f, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -803,35 +1035,42 @@ func file_initializer_proto_rawDescGZIP() []byte { } var file_initializer_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_initializer_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_initializer_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_initializer_proto_goTypes = []interface{}{ - (CloneTargetMode)(0), // 0: contentservice.CloneTargetMode - (GitAuthMethod)(0), // 1: contentservice.GitAuthMethod - (*WorkspaceInitializer)(nil), // 2: contentservice.WorkspaceInitializer - (*EmptyInitializer)(nil), // 3: contentservice.EmptyInitializer - (*GitInitializer)(nil), // 4: contentservice.GitInitializer - (*GitConfig)(nil), // 5: contentservice.GitConfig - (*SnapshotInitializer)(nil), // 6: contentservice.SnapshotInitializer - (*PrebuildInitializer)(nil), // 7: contentservice.PrebuildInitializer - (*GitStatus)(nil), // 8: contentservice.GitStatus - nil, // 9: contentservice.GitConfig.CustomConfigEntry + (CloneTargetMode)(0), // 0: contentservice.CloneTargetMode + (GitAuthMethod)(0), // 1: contentservice.GitAuthMethod + (*WorkspaceInitializer)(nil), // 2: contentservice.WorkspaceInitializer + (*CompositeInitializer)(nil), // 3: contentservice.CompositeInitializer + (*FileDownloadInitializer)(nil), // 4: contentservice.FileDownloadInitializer + (*EmptyInitializer)(nil), // 5: contentservice.EmptyInitializer + (*GitInitializer)(nil), // 6: contentservice.GitInitializer + (*GitConfig)(nil), // 7: contentservice.GitConfig + (*SnapshotInitializer)(nil), // 8: contentservice.SnapshotInitializer + (*PrebuildInitializer)(nil), // 9: contentservice.PrebuildInitializer + (*GitStatus)(nil), // 10: contentservice.GitStatus + (*FileDownloadInitializer_FileInfo)(nil), // 11: contentservice.FileDownloadInitializer.FileInfo + nil, // 12: contentservice.GitConfig.CustomConfigEntry } var file_initializer_proto_depIdxs = []int32{ - 3, // 0: contentservice.WorkspaceInitializer.empty:type_name -> contentservice.EmptyInitializer - 4, // 1: contentservice.WorkspaceInitializer.git:type_name -> contentservice.GitInitializer - 6, // 2: contentservice.WorkspaceInitializer.snapshot:type_name -> contentservice.SnapshotInitializer - 7, // 3: contentservice.WorkspaceInitializer.prebuild:type_name -> contentservice.PrebuildInitializer - 0, // 4: contentservice.GitInitializer.target_mode:type_name -> contentservice.CloneTargetMode - 5, // 5: contentservice.GitInitializer.config:type_name -> contentservice.GitConfig - 9, // 6: contentservice.GitConfig.custom_config:type_name -> contentservice.GitConfig.CustomConfigEntry - 1, // 7: contentservice.GitConfig.authentication:type_name -> contentservice.GitAuthMethod - 6, // 8: contentservice.PrebuildInitializer.prebuild:type_name -> contentservice.SnapshotInitializer - 4, // 9: contentservice.PrebuildInitializer.git:type_name -> contentservice.GitInitializer - 10, // [10:10] is the sub-list for method output_type - 10, // [10:10] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 5, // 0: contentservice.WorkspaceInitializer.empty:type_name -> contentservice.EmptyInitializer + 6, // 1: contentservice.WorkspaceInitializer.git:type_name -> contentservice.GitInitializer + 8, // 2: contentservice.WorkspaceInitializer.snapshot:type_name -> contentservice.SnapshotInitializer + 9, // 3: contentservice.WorkspaceInitializer.prebuild:type_name -> contentservice.PrebuildInitializer + 3, // 4: contentservice.WorkspaceInitializer.composite:type_name -> contentservice.CompositeInitializer + 4, // 5: contentservice.WorkspaceInitializer.download:type_name -> contentservice.FileDownloadInitializer + 2, // 6: contentservice.CompositeInitializer.initializer:type_name -> contentservice.WorkspaceInitializer + 11, // 7: contentservice.FileDownloadInitializer.files:type_name -> contentservice.FileDownloadInitializer.FileInfo + 0, // 8: contentservice.GitInitializer.target_mode:type_name -> contentservice.CloneTargetMode + 7, // 9: contentservice.GitInitializer.config:type_name -> contentservice.GitConfig + 12, // 10: contentservice.GitConfig.custom_config:type_name -> contentservice.GitConfig.CustomConfigEntry + 1, // 11: contentservice.GitConfig.authentication:type_name -> contentservice.GitAuthMethod + 8, // 12: contentservice.PrebuildInitializer.prebuild:type_name -> contentservice.SnapshotInitializer + 6, // 13: contentservice.PrebuildInitializer.git:type_name -> contentservice.GitInitializer + 14, // [14:14] is the sub-list for method output_type + 14, // [14:14] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_initializer_proto_init() } @@ -853,7 +1092,7 @@ func file_initializer_proto_init() { } } file_initializer_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EmptyInitializer); i { + switch v := v.(*CompositeInitializer); i { case 0: return &v.state case 1: @@ -865,7 +1104,7 @@ func file_initializer_proto_init() { } } file_initializer_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GitInitializer); i { + switch v := v.(*FileDownloadInitializer); i { case 0: return &v.state case 1: @@ -877,7 +1116,7 @@ func file_initializer_proto_init() { } } file_initializer_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GitConfig); i { + switch v := v.(*EmptyInitializer); i { case 0: return &v.state case 1: @@ -889,7 +1128,7 @@ func file_initializer_proto_init() { } } file_initializer_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SnapshotInitializer); i { + switch v := v.(*GitInitializer); i { case 0: return &v.state case 1: @@ -901,7 +1140,7 @@ func file_initializer_proto_init() { } } file_initializer_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PrebuildInitializer); i { + switch v := v.(*GitConfig); i { case 0: return &v.state case 1: @@ -913,6 +1152,30 @@ func file_initializer_proto_init() { } } file_initializer_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SnapshotInitializer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_initializer_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PrebuildInitializer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_initializer_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GitStatus); i { case 0: return &v.state @@ -924,12 +1187,26 @@ func file_initializer_proto_init() { return nil } } + file_initializer_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*FileDownloadInitializer_FileInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } file_initializer_proto_msgTypes[0].OneofWrappers = []interface{}{ (*WorkspaceInitializer_Empty)(nil), (*WorkspaceInitializer_Git)(nil), (*WorkspaceInitializer_Snapshot)(nil), (*WorkspaceInitializer_Prebuild)(nil), + (*WorkspaceInitializer_Composite)(nil), + (*WorkspaceInitializer_Download)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -937,7 +1214,7 @@ func file_initializer_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_initializer_proto_rawDesc, NumEnums: 2, - NumMessages: 8, + NumMessages: 11, NumExtensions: 0, NumServices: 0, }, diff --git a/components/content-service-api/initializer.proto b/components/content-service-api/initializer.proto index 17126e5da7bb3c..bccec0eb0c6ba5 100644 --- a/components/content-service-api/initializer.proto +++ b/components/content-service-api/initializer.proto @@ -11,9 +11,35 @@ message WorkspaceInitializer { GitInitializer git = 2; SnapshotInitializer snapshot = 3; PrebuildInitializer prebuild = 4; + CompositeInitializer composite = 5; + FileDownloadInitializer download = 6; } } +// CompositeInitializer uses a collection of initializer to produce workspace content. +// All initializer are executed in the order they're provided. +message CompositeInitializer { + repeated WorkspaceInitializer initializer = 1; +} + +// FileDownloadInitializer downloads files and uses them as workspace content. +message FileDownloadInitializer { + message FileInfo { + string url = 1; + // file_path is relative to the target_location, e.g. if target_location is in `/workspace/myrepo` + // a file_path of `foobar/file` would produce a file in `/workspace/myrepo/foobar/file`. + // file_path must include the filename. The FileDownloadInitializer will create any parent directories + // necessary to place the file. + string file_path = 2; + // digest is a hash of the file content in the OCI digest format (see https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests). + // This information is used to compute subsequent + // content versions, and to validate the file content was downloaded correctly. + string digest = 3; + } + repeated FileInfo files = 1; + string target_location = 2; +} + message EmptyInitializer { } message GitInitializer { diff --git a/components/content-service-api/typescript/src/initializer_pb.d.ts b/components/content-service-api/typescript/src/initializer_pb.d.ts index 0e2e170b8a14bd..b288cf107d1395 100644 --- a/components/content-service-api/typescript/src/initializer_pb.d.ts +++ b/components/content-service-api/typescript/src/initializer_pb.d.ts @@ -34,6 +34,16 @@ export class WorkspaceInitializer extends jspb.Message { getPrebuild(): PrebuildInitializer | undefined; setPrebuild(value?: PrebuildInitializer): WorkspaceInitializer; + hasComposite(): boolean; + clearComposite(): void; + getComposite(): CompositeInitializer | undefined; + setComposite(value?: CompositeInitializer): WorkspaceInitializer; + + hasDownload(): boolean; + clearDownload(): void; + getDownload(): FileDownloadInitializer | undefined; + setDownload(value?: FileDownloadInitializer): WorkspaceInitializer; + getSpecCase(): WorkspaceInitializer.SpecCase; serializeBinary(): Uint8Array; @@ -52,6 +62,8 @@ export namespace WorkspaceInitializer { git?: GitInitializer.AsObject, snapshot?: SnapshotInitializer.AsObject, prebuild?: PrebuildInitializer.AsObject, + composite?: CompositeInitializer.AsObject, + download?: FileDownloadInitializer.AsObject, } export enum SpecCase { @@ -60,6 +72,83 @@ export namespace WorkspaceInitializer { GIT = 2, SNAPSHOT = 3, PREBUILD = 4, + COMPOSITE = 5, + DOWNLOAD = 6, + } + +} + +export class CompositeInitializer extends jspb.Message { + clearInitializerList(): void; + getInitializerList(): Array; + setInitializerList(value: Array): CompositeInitializer; + addInitializer(value?: WorkspaceInitializer, index?: number): WorkspaceInitializer; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): CompositeInitializer.AsObject; + static toObject(includeInstance: boolean, msg: CompositeInitializer): CompositeInitializer.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: CompositeInitializer, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): CompositeInitializer; + static deserializeBinaryFromReader(message: CompositeInitializer, reader: jspb.BinaryReader): CompositeInitializer; +} + +export namespace CompositeInitializer { + export type AsObject = { + initializerList: Array, + } +} + +export class FileDownloadInitializer extends jspb.Message { + clearFilesList(): void; + getFilesList(): Array; + setFilesList(value: Array): FileDownloadInitializer; + addFiles(value?: FileDownloadInitializer.FileInfo, index?: number): FileDownloadInitializer.FileInfo; + getTargetLocation(): string; + setTargetLocation(value: string): FileDownloadInitializer; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): FileDownloadInitializer.AsObject; + static toObject(includeInstance: boolean, msg: FileDownloadInitializer): FileDownloadInitializer.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: FileDownloadInitializer, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): FileDownloadInitializer; + static deserializeBinaryFromReader(message: FileDownloadInitializer, reader: jspb.BinaryReader): FileDownloadInitializer; +} + +export namespace FileDownloadInitializer { + export type AsObject = { + filesList: Array, + targetLocation: string, + } + + + export class FileInfo extends jspb.Message { + getUrl(): string; + setUrl(value: string): FileInfo; + getFilePath(): string; + setFilePath(value: string): FileInfo; + getDigest(): string; + setDigest(value: string): FileInfo; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): FileInfo.AsObject; + static toObject(includeInstance: boolean, msg: FileInfo): FileInfo.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: FileInfo, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): FileInfo; + static deserializeBinaryFromReader(message: FileInfo, reader: jspb.BinaryReader): FileInfo; + } + + export namespace FileInfo { + export type AsObject = { + url: string, + filePath: string, + digest: string, + } } } diff --git a/components/content-service-api/typescript/src/initializer_pb.js b/components/content-service-api/typescript/src/initializer_pb.js index 778f9a7011342d..66349247d237e7 100644 --- a/components/content-service-api/typescript/src/initializer_pb.js +++ b/components/content-service-api/typescript/src/initializer_pb.js @@ -22,7 +22,10 @@ var goog = jspb; var global = Function('return this')(); goog.exportSymbol('proto.contentservice.CloneTargetMode', null, global); +goog.exportSymbol('proto.contentservice.CompositeInitializer', null, global); goog.exportSymbol('proto.contentservice.EmptyInitializer', null, global); +goog.exportSymbol('proto.contentservice.FileDownloadInitializer', null, global); +goog.exportSymbol('proto.contentservice.FileDownloadInitializer.FileInfo', null, global); goog.exportSymbol('proto.contentservice.GitAuthMethod', null, global); goog.exportSymbol('proto.contentservice.GitConfig', null, global); goog.exportSymbol('proto.contentservice.GitInitializer', null, global); @@ -52,6 +55,69 @@ if (goog.DEBUG && !COMPILED) { */ proto.contentservice.WorkspaceInitializer.displayName = 'proto.contentservice.WorkspaceInitializer'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.contentservice.CompositeInitializer = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.contentservice.CompositeInitializer.repeatedFields_, null); +}; +goog.inherits(proto.contentservice.CompositeInitializer, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.contentservice.CompositeInitializer.displayName = 'proto.contentservice.CompositeInitializer'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.contentservice.FileDownloadInitializer = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, proto.contentservice.FileDownloadInitializer.repeatedFields_, null); +}; +goog.inherits(proto.contentservice.FileDownloadInitializer, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.contentservice.FileDownloadInitializer.displayName = 'proto.contentservice.FileDownloadInitializer'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.contentservice.FileDownloadInitializer.FileInfo = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.contentservice.FileDownloadInitializer.FileInfo, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.contentservice.FileDownloadInitializer.FileInfo.displayName = 'proto.contentservice.FileDownloadInitializer.FileInfo'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -187,7 +253,7 @@ if (goog.DEBUG && !COMPILED) { * @private {!Array>} * @const */ -proto.contentservice.WorkspaceInitializer.oneofGroups_ = [[1,2,3,4]]; +proto.contentservice.WorkspaceInitializer.oneofGroups_ = [[1,2,3,4,5,6]]; /** * @enum {number} @@ -197,7 +263,9 @@ proto.contentservice.WorkspaceInitializer.SpecCase = { EMPTY: 1, GIT: 2, SNAPSHOT: 3, - PREBUILD: 4 + PREBUILD: 4, + COMPOSITE: 5, + DOWNLOAD: 6 }; /** @@ -241,7 +309,9 @@ proto.contentservice.WorkspaceInitializer.toObject = function(includeInstance, m empty: (f = msg.getEmpty()) && proto.contentservice.EmptyInitializer.toObject(includeInstance, f), git: (f = msg.getGit()) && proto.contentservice.GitInitializer.toObject(includeInstance, f), snapshot: (f = msg.getSnapshot()) && proto.contentservice.SnapshotInitializer.toObject(includeInstance, f), - prebuild: (f = msg.getPrebuild()) && proto.contentservice.PrebuildInitializer.toObject(includeInstance, f) + prebuild: (f = msg.getPrebuild()) && proto.contentservice.PrebuildInitializer.toObject(includeInstance, f), + composite: (f = msg.getComposite()) && proto.contentservice.CompositeInitializer.toObject(includeInstance, f), + download: (f = msg.getDownload()) && proto.contentservice.FileDownloadInitializer.toObject(includeInstance, f) }; if (includeInstance) { @@ -298,6 +368,16 @@ proto.contentservice.WorkspaceInitializer.deserializeBinaryFromReader = function reader.readMessage(value,proto.contentservice.PrebuildInitializer.deserializeBinaryFromReader); msg.setPrebuild(value); break; + case 5: + var value = new proto.contentservice.CompositeInitializer; + reader.readMessage(value,proto.contentservice.CompositeInitializer.deserializeBinaryFromReader); + msg.setComposite(value); + break; + case 6: + var value = new proto.contentservice.FileDownloadInitializer; + reader.readMessage(value,proto.contentservice.FileDownloadInitializer.deserializeBinaryFromReader); + msg.setDownload(value); + break; default: reader.skipField(); break; @@ -359,6 +439,22 @@ proto.contentservice.WorkspaceInitializer.serializeBinaryToWriter = function(mes proto.contentservice.PrebuildInitializer.serializeBinaryToWriter ); } + f = message.getComposite(); + if (f != null) { + writer.writeMessage( + 5, + f, + proto.contentservice.CompositeInitializer.serializeBinaryToWriter + ); + } + f = message.getDownload(); + if (f != null) { + writer.writeMessage( + 6, + f, + proto.contentservice.FileDownloadInitializer.serializeBinaryToWriter + ); + } }; @@ -510,6 +606,620 @@ proto.contentservice.WorkspaceInitializer.prototype.hasPrebuild = function() { }; +/** + * optional CompositeInitializer composite = 5; + * @return {?proto.contentservice.CompositeInitializer} + */ +proto.contentservice.WorkspaceInitializer.prototype.getComposite = function() { + return /** @type{?proto.contentservice.CompositeInitializer} */ ( + jspb.Message.getWrapperField(this, proto.contentservice.CompositeInitializer, 5)); +}; + + +/** + * @param {?proto.contentservice.CompositeInitializer|undefined} value + * @return {!proto.contentservice.WorkspaceInitializer} returns this +*/ +proto.contentservice.WorkspaceInitializer.prototype.setComposite = function(value) { + return jspb.Message.setOneofWrapperField(this, 5, proto.contentservice.WorkspaceInitializer.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.contentservice.WorkspaceInitializer} returns this + */ +proto.contentservice.WorkspaceInitializer.prototype.clearComposite = function() { + return this.setComposite(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.contentservice.WorkspaceInitializer.prototype.hasComposite = function() { + return jspb.Message.getField(this, 5) != null; +}; + + +/** + * optional FileDownloadInitializer download = 6; + * @return {?proto.contentservice.FileDownloadInitializer} + */ +proto.contentservice.WorkspaceInitializer.prototype.getDownload = function() { + return /** @type{?proto.contentservice.FileDownloadInitializer} */ ( + jspb.Message.getWrapperField(this, proto.contentservice.FileDownloadInitializer, 6)); +}; + + +/** + * @param {?proto.contentservice.FileDownloadInitializer|undefined} value + * @return {!proto.contentservice.WorkspaceInitializer} returns this +*/ +proto.contentservice.WorkspaceInitializer.prototype.setDownload = function(value) { + return jspb.Message.setOneofWrapperField(this, 6, proto.contentservice.WorkspaceInitializer.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.contentservice.WorkspaceInitializer} returns this + */ +proto.contentservice.WorkspaceInitializer.prototype.clearDownload = function() { + return this.setDownload(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.contentservice.WorkspaceInitializer.prototype.hasDownload = function() { + return jspb.Message.getField(this, 6) != null; +}; + + + +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.contentservice.CompositeInitializer.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.contentservice.CompositeInitializer.prototype.toObject = function(opt_includeInstance) { + return proto.contentservice.CompositeInitializer.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.contentservice.CompositeInitializer} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.contentservice.CompositeInitializer.toObject = function(includeInstance, msg) { + var f, obj = { + initializerList: jspb.Message.toObjectList(msg.getInitializerList(), + proto.contentservice.WorkspaceInitializer.toObject, includeInstance) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.contentservice.CompositeInitializer} + */ +proto.contentservice.CompositeInitializer.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.contentservice.CompositeInitializer; + return proto.contentservice.CompositeInitializer.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.contentservice.CompositeInitializer} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.contentservice.CompositeInitializer} + */ +proto.contentservice.CompositeInitializer.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.contentservice.WorkspaceInitializer; + reader.readMessage(value,proto.contentservice.WorkspaceInitializer.deserializeBinaryFromReader); + msg.addInitializer(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.contentservice.CompositeInitializer.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.contentservice.CompositeInitializer.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.contentservice.CompositeInitializer} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.contentservice.CompositeInitializer.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getInitializerList(); + if (f.length > 0) { + writer.writeRepeatedMessage( + 1, + f, + proto.contentservice.WorkspaceInitializer.serializeBinaryToWriter + ); + } +}; + + +/** + * repeated WorkspaceInitializer initializer = 1; + * @return {!Array} + */ +proto.contentservice.CompositeInitializer.prototype.getInitializerList = function() { + return /** @type{!Array} */ ( + jspb.Message.getRepeatedWrapperField(this, proto.contentservice.WorkspaceInitializer, 1)); +}; + + +/** + * @param {!Array} value + * @return {!proto.contentservice.CompositeInitializer} returns this +*/ +proto.contentservice.CompositeInitializer.prototype.setInitializerList = function(value) { + return jspb.Message.setRepeatedWrapperField(this, 1, value); +}; + + +/** + * @param {!proto.contentservice.WorkspaceInitializer=} opt_value + * @param {number=} opt_index + * @return {!proto.contentservice.WorkspaceInitializer} + */ +proto.contentservice.CompositeInitializer.prototype.addInitializer = function(opt_value, opt_index) { + return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.contentservice.WorkspaceInitializer, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.contentservice.CompositeInitializer} returns this + */ +proto.contentservice.CompositeInitializer.prototype.clearInitializerList = function() { + return this.setInitializerList([]); +}; + + + +/** + * List of repeated fields within this message type. + * @private {!Array} + * @const + */ +proto.contentservice.FileDownloadInitializer.repeatedFields_ = [1]; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.contentservice.FileDownloadInitializer.prototype.toObject = function(opt_includeInstance) { + return proto.contentservice.FileDownloadInitializer.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.contentservice.FileDownloadInitializer} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.contentservice.FileDownloadInitializer.toObject = function(includeInstance, msg) { + var f, obj = { + filesList: jspb.Message.toObjectList(msg.getFilesList(), + proto.contentservice.FileDownloadInitializer.FileInfo.toObject, includeInstance), + targetLocation: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.contentservice.FileDownloadInitializer} + */ +proto.contentservice.FileDownloadInitializer.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.contentservice.FileDownloadInitializer; + return proto.contentservice.FileDownloadInitializer.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.contentservice.FileDownloadInitializer} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.contentservice.FileDownloadInitializer} + */ +proto.contentservice.FileDownloadInitializer.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.contentservice.FileDownloadInitializer.FileInfo; + reader.readMessage(value,proto.contentservice.FileDownloadInitializer.FileInfo.deserializeBinaryFromReader); + msg.addFiles(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setTargetLocation(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.contentservice.FileDownloadInitializer.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.contentservice.FileDownloadInitializer.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.contentservice.FileDownloadInitializer} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.contentservice.FileDownloadInitializer.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getFilesList(); + if (f.length > 0) { + writer.writeRepeatedMessage( + 1, + f, + proto.contentservice.FileDownloadInitializer.FileInfo.serializeBinaryToWriter + ); + } + f = message.getTargetLocation(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.contentservice.FileDownloadInitializer.FileInfo.prototype.toObject = function(opt_includeInstance) { + return proto.contentservice.FileDownloadInitializer.FileInfo.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.contentservice.FileDownloadInitializer.FileInfo} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.contentservice.FileDownloadInitializer.FileInfo.toObject = function(includeInstance, msg) { + var f, obj = { + url: jspb.Message.getFieldWithDefault(msg, 1, ""), + filePath: jspb.Message.getFieldWithDefault(msg, 2, ""), + digest: jspb.Message.getFieldWithDefault(msg, 3, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.contentservice.FileDownloadInitializer.FileInfo} + */ +proto.contentservice.FileDownloadInitializer.FileInfo.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.contentservice.FileDownloadInitializer.FileInfo; + return proto.contentservice.FileDownloadInitializer.FileInfo.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.contentservice.FileDownloadInitializer.FileInfo} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.contentservice.FileDownloadInitializer.FileInfo} + */ +proto.contentservice.FileDownloadInitializer.FileInfo.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setUrl(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setFilePath(value); + break; + case 3: + var value = /** @type {string} */ (reader.readString()); + msg.setDigest(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.contentservice.FileDownloadInitializer.FileInfo.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.contentservice.FileDownloadInitializer.FileInfo.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.contentservice.FileDownloadInitializer.FileInfo} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.contentservice.FileDownloadInitializer.FileInfo.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getUrl(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getFilePath(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getDigest(); + if (f.length > 0) { + writer.writeString( + 3, + f + ); + } +}; + + +/** + * optional string url = 1; + * @return {string} + */ +proto.contentservice.FileDownloadInitializer.FileInfo.prototype.getUrl = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.contentservice.FileDownloadInitializer.FileInfo} returns this + */ +proto.contentservice.FileDownloadInitializer.FileInfo.prototype.setUrl = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string file_path = 2; + * @return {string} + */ +proto.contentservice.FileDownloadInitializer.FileInfo.prototype.getFilePath = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.contentservice.FileDownloadInitializer.FileInfo} returns this + */ +proto.contentservice.FileDownloadInitializer.FileInfo.prototype.setFilePath = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional string digest = 3; + * @return {string} + */ +proto.contentservice.FileDownloadInitializer.FileInfo.prototype.getDigest = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 3, "")); +}; + + +/** + * @param {string} value + * @return {!proto.contentservice.FileDownloadInitializer.FileInfo} returns this + */ +proto.contentservice.FileDownloadInitializer.FileInfo.prototype.setDigest = function(value) { + return jspb.Message.setProto3StringField(this, 3, value); +}; + + +/** + * repeated FileInfo files = 1; + * @return {!Array} + */ +proto.contentservice.FileDownloadInitializer.prototype.getFilesList = function() { + return /** @type{!Array} */ ( + jspb.Message.getRepeatedWrapperField(this, proto.contentservice.FileDownloadInitializer.FileInfo, 1)); +}; + + +/** + * @param {!Array} value + * @return {!proto.contentservice.FileDownloadInitializer} returns this +*/ +proto.contentservice.FileDownloadInitializer.prototype.setFilesList = function(value) { + return jspb.Message.setRepeatedWrapperField(this, 1, value); +}; + + +/** + * @param {!proto.contentservice.FileDownloadInitializer.FileInfo=} opt_value + * @param {number=} opt_index + * @return {!proto.contentservice.FileDownloadInitializer.FileInfo} + */ +proto.contentservice.FileDownloadInitializer.prototype.addFiles = function(opt_value, opt_index) { + return jspb.Message.addToRepeatedWrapperField(this, 1, opt_value, proto.contentservice.FileDownloadInitializer.FileInfo, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.contentservice.FileDownloadInitializer} returns this + */ +proto.contentservice.FileDownloadInitializer.prototype.clearFilesList = function() { + return this.setFilesList([]); +}; + + +/** + * optional string target_location = 2; + * @return {string} + */ +proto.contentservice.FileDownloadInitializer.prototype.getTargetLocation = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.contentservice.FileDownloadInitializer} returns this + */ +proto.contentservice.FileDownloadInitializer.prototype.setTargetLocation = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + diff --git a/components/content-service/pkg/initializer/download.go b/components/content-service/pkg/initializer/download.go new file mode 100644 index 00000000000000..4bf3d7580097d8 --- /dev/null +++ b/components/content-service/pkg/initializer/download.go @@ -0,0 +1,110 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package initializer + +import ( + "context" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "time" + + "github.com/gitpod-io/gitpod/common-go/log" + "github.com/gitpod-io/gitpod/common-go/tracing" + csapi "github.com/gitpod-io/gitpod/content-service/api" + "github.com/gitpod-io/gitpod/content-service/pkg/archive" + "github.com/opentracing/opentracing-go" + "golang.org/x/xerrors" +) + +type FileInfo struct { + url string + // filePath is relative to the FileDownloadInitializer's TargetLocation, e.g. if TargetLocation is in `/workspace/myrepo` + // a filePath of `foobar/file` would produce a file in `/workspace/myrepo/foobar/file`. + // filePath must include the filename. The FileDownloadInitializer will create any parent directories + // necessary to place the file. + filePath string + // digest is a hash of the file content in the OCI digest format (see https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests). + // This information is used to compute subsequent + // content versions, and to validate the file content was downloaded correctly. + digest string +} + +type FileDownloadInitializer struct { + FilesInfos []FileInfo + TargetLocation string +} + +// Run initializes the workspace +func (ws *FileDownloadInitializer) Run(ctx context.Context, mappings []archive.IDMapping) (src csapi.WorkspaceInitSource, err error) { + span, ctx := opentracing.StartSpanFromContext(ctx, "FileDownloadInitializer.Run") + defer tracing.FinishSpan(span, &err) + + for _, info := range ws.FilesInfos { + contents, err := downloadFile(ctx, info.url) + if err != nil { + tracing.LogError(span, xerrors.Errorf("cannot download file '%s' from '%s': %w", info.filePath, info.url, err)) + } + + fullPath := filepath.Join(ws.TargetLocation, info.filePath) + err = os.MkdirAll(filepath.Dir(fullPath), 0755) + if err != nil { + tracing.LogError(span, xerrors.Errorf("cannot mkdir %s: %w", filepath.Dir(fullPath), err)) + } + err = ioutil.WriteFile(fullPath, contents, 0755) + if err != nil { + tracing.LogError(span, xerrors.Errorf("cannot write %s: %w", fullPath, err)) + } + } + return src, nil +} + +func downloadFile(ctx context.Context, url string) (content []byte, err error) { + //nolint:ineffassign + span, ctx := opentracing.StartSpanFromContext(ctx, "downloadFile") + defer tracing.FinishSpan(span, &err) + span.LogKV("url", url) + + dl := func() (content []byte, err error) { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, err + } + _ = opentracing.GlobalTracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, xerrors.Errorf("non-OK OTS response: %s", resp.Status) + } + + return io.ReadAll(resp.Body) + } + for i := 0; i < otsDownloadAttempts; i++ { + span.LogKV("attempt", i) + if i > 0 { + time.Sleep(time.Second) + } + + content, err = dl() + if err == context.Canceled || err == context.DeadlineExceeded { + return + } + if err == nil { + break + } + log.WithError(err).WithField("attempt", i).Warn("cannot download additional content files") + } + if err != nil { + return nil, err + } + + return content, nil +} diff --git a/components/content-service/pkg/initializer/initializer.go b/components/content-service/pkg/initializer/initializer.go index c4cc3949061ef3..f1fae384c65b16 100644 --- a/components/content-service/pkg/initializer/initializer.go +++ b/components/content-service/pkg/initializer/initializer.go @@ -55,6 +55,20 @@ func (e *EmptyInitializer) Run(ctx context.Context, mappings []archive.IDMapping return csapi.WorkspaceInitFromOther, nil } +// CompositeInitializer does nothing +type CompositeInitializer struct { + Initializer []Initializer +} + +// Run calls run on all child initializers +func (e *CompositeInitializer) Run(ctx context.Context, mappings []archive.IDMapping) (csapi.WorkspaceInitSource, error) { + _, ctx = opentracing.StartSpanFromContext(ctx, "CompositeInitializer.Run") + for _, init := range e.Initializer { + init.Run(ctx, mappings) + } + return csapi.WorkspaceInitFromOther, nil +} + // NewFromRequestOpts configures the initializer produced from a content init request type NewFromRequestOpts struct { // ForceGitpodUserForGit forces gitpod:gitpod ownership on all files produced by the Git initializer. @@ -76,6 +90,17 @@ func NewFromRequest(ctx context.Context, loc string, rs storage.DirectDownloader var initializer Initializer if _, ok := spec.(*csapi.WorkspaceInitializer_Empty); ok { initializer = &EmptyInitializer{} + } else if ir, ok := spec.(*csapi.WorkspaceInitializer_Composite); ok { + initializers := make([]Initializer, len(ir.Composite.Initializer)) + for i, init := range ir.Composite.Initializer { + initializers[i], err = NewFromRequest(ctx, loc, rs, init, opts) + if err != nil { + return nil, err + } + } + initializer = &CompositeInitializer{ + Initializer: initializers, + } } else if ir, ok := spec.(*csapi.WorkspaceInitializer_Git); ok { if ir.Git == nil { return nil, status.Error(codes.InvalidArgument, "missing Git initializer spec") @@ -108,13 +133,31 @@ func NewFromRequest(ctx context.Context, loc string, rs storage.DirectDownloader } } else if ir, ok := spec.(*csapi.WorkspaceInitializer_Snapshot); ok { initializer, err = newSnapshotInitializer(loc, rs, ir.Snapshot) + } else if ir, ok := spec.(*csapi.WorkspaceInitializer_Download); ok { + initializer, err = newFileDownloadInitializer(loc, ir.Download) } else { initializer = &EmptyInitializer{} } if err != nil { return nil, status.Error(codes.Internal, fmt.Sprintf("cannot initialize workspace: %v", err)) } + return initializer, nil +} +// newFileDownloadInitializer creates a download initializer for a request +func newFileDownloadInitializer(loc string, req *csapi.FileDownloadInitializer) (*FileDownloadInitializer, error) { + fileInfos := make([]FileInfo, len(req.Files)) + for i, f := range req.Files { + fileInfos[i] = FileInfo{ + url: f.Url, + filePath: f.FilePath, + digest: f.Digest, + } + } + initializer := &FileDownloadInitializer{ + FilesInfos: fileInfos, + TargetLocation: filepath.Join(loc, req.TargetLocation), + } return initializer, nil } diff --git a/components/gitpod-protocol/src/protocol.ts b/components/gitpod-protocol/src/protocol.ts index 05c5364158be25..a46659a42d5bf6 100644 --- a/components/gitpod-protocol/src/protocol.ts +++ b/components/gitpod-protocol/src/protocol.ts @@ -561,9 +561,10 @@ export interface WorkspaceConfig { * repo - from the repository * definitly-gp - from github.com/gitpod-io/definitely-gp * derived - computed based on analyzing the repository + * additional-content - config comes from additional content, usually provided through the project's configuration * default - our static catch-all default config */ - _origin?: 'repo' | 'definitely-gp' | 'derived' | 'default'; + _origin?: 'repo' | 'definitely-gp' | 'derived' | 'additional-content' | 'default'; /** * Set of automatically infered feature flags. That's not something the user can set, but @@ -593,16 +594,13 @@ export namespace GithubAppPrebuildConfig { export type WorkspaceImageSource = WorkspaceImageSourceDocker | WorkspaceImageSourceReference; export interface WorkspaceImageSourceDocker { - // TODO: clean this up. We should have the commit and an ImageSource in here, not duplicate the whole thing again. - // We have a ton of those objects in the database, thus cleaning this up means lengthy DB migrations. Yuck. - dockerFileHash: string - dockerFileSource: Commit dockerFilePath: string + dockerFileHash: string + dockerFileSource?: Commit } export namespace WorkspaceImageSourceDocker { export function is(obj: object): obj is WorkspaceImageSourceDocker { return 'dockerFileHash' in obj - && 'dockerFileSource' in obj && 'dockerFilePath' in obj; } } @@ -884,6 +882,25 @@ export interface Commit { refType?: RefType } +export interface AdditionalContentContext { + + /** + * utf-8 encoded contents that will be copied on top of the workspace's filesystem + */ + additionalFiles: {[filePath: string]: string}; + +} + +export namespace AdditionalContentContext { + export function is(ctx: any): ctx is AdditionalContentContext { + return 'additionalFiles' in ctx; + } + + export function hasDockerConfig(ctx: any, config: WorkspaceConfig): boolean { + return is(ctx) && ImageConfigFile.is(config.image) && !!ctx.additionalFiles[config.image.file]; + } +} + export interface CommitContext extends WorkspaceContext, Commit { /** @deprecated Moved to .repository.cloneUrl, left here for backwards-compatibility for old workspace contextes in the DB */ cloneUrl?: string diff --git a/components/gitpod-protocol/src/wsready.ts b/components/gitpod-protocol/src/wsready.ts index d821b799513529..b235301e828cdc 100644 --- a/components/gitpod-protocol/src/wsready.ts +++ b/components/gitpod-protocol/src/wsready.ts @@ -4,7 +4,7 @@ * See License-AGPL.txt in the project root for license information. */ -// generated using github.com/32leaves/bel on 2021-04-20 10:08:58.0197329 +0000 UTC m=+0.008313436 +// generated using github.com/32leaves/bel on 2021-06-11 12:57:53.834879928 +0000 UTC m=+0.008335102 // DO NOT MODIFY export enum WorkspaceInitSource { diff --git a/components/image-builder/cmd/bob-init-base.go b/components/image-builder/cmd/bob-init-base.go index 8a7574c385bb8d..212a0624b6f229 100644 --- a/components/image-builder/cmd/bob-init-base.go +++ b/components/image-builder/cmd/bob-init-base.go @@ -73,11 +73,11 @@ var bobInitBase = &cobra.Command{ log.WithError(err).Fatal("init failed") } if stat, err := os.Stat(initctx); os.IsNotExist(err) { - log.Fatalf("Context directory \"%s\" does not exist", src.ContextPath) + log.Fatalf("Context directory \"%s\" does not exist", initctx) } else if err != nil { log.Fatalf("Context directory error: %v", err) } else if !stat.IsDir() { - log.Fatalf("Context path \"%s\" is not a directory", src.ContextPath) + log.Fatalf("Context path \"%s\" is not a directory", initctx) } initdf, err := securejoin.SecureJoin(initwd, src.DockerfilePath) @@ -85,7 +85,7 @@ var bobInitBase = &cobra.Command{ log.WithError(err).Fatal("init failed") } if stat, err := os.Stat(initdf); os.IsNotExist(err) { - log.Fatalf("Dockerfile \"%s\" does not exist", src.DockerfilePath) + log.Fatalf("Dockerfile \"%s\" does not exist", initdf) } else if err != nil { log.Fatalf("Dockerfile error: %v", err) } else if stat.IsDir() { diff --git a/components/image-builder/go.mod b/components/image-builder/go.mod index cb69559243efdc..4725aae318c7da 100644 --- a/components/image-builder/go.mod +++ b/components/image-builder/go.mod @@ -18,6 +18,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/gitpod-io/gitpod/common-go v0.0.0-00010101000000-000000000000 github.com/gitpod-io/gitpod/content-service v0.0.0-00010101000000-000000000000 + github.com/gitpod-io/gitpod/content-service/api v0.0.0-00010101000000-000000000000 github.com/gitpod-io/gitpod/image-builder/api v0.0.0-00010101000000-000000000000 github.com/golang/mock v1.5.0 github.com/gorilla/mux v1.8.0 // indirect diff --git a/components/image-builder/pkg/builder/docker.go b/components/image-builder/pkg/builder/docker.go index 6cf98e551d2a31..dd42d7f44b0f84 100644 --- a/components/image-builder/pkg/builder/docker.go +++ b/components/image-builder/pkg/builder/docker.go @@ -19,6 +19,7 @@ import ( "github.com/docker/docker/api/types/mount" docker "github.com/docker/docker/client" "github.com/docker/docker/pkg/jsonmessage" + csapi "github.com/gitpod-io/gitpod/content-service/api" "github.com/opentracing/opentracing-go" "golang.org/x/xerrors" "google.golang.org/grpc/codes" @@ -214,6 +215,19 @@ func (b *DockerBuilder) getAbsoluteImageRef(ctx context.Context, ref string, all return b.Resolver.Resolve(ctx, ref, resolve.WithAuthentication(auth)) } +func findGitInit(init *csapi.WorkspaceInitializer) *csapi.GitInitializer { + if init.GetGit() != nil { + return init.GetGit() + } + for _, current := range init.GetComposite().GetInitializer() { + nestedGitInit := findGitInit(current) + if nestedGitInit != nil { + return nestedGitInit + } + } + return nil +} + func (b *DockerBuilder) getBaseImageRef(ctx context.Context, bs *api.BuildSource, allowedAuth allowedAuthFor) (res string, err error) { //nolint:ineffassign span, ctx := opentracing.StartSpanFromContext(ctx, "getBaseImageRef") @@ -229,13 +243,14 @@ func (b *DockerBuilder) getBaseImageRef(ctx context.Context, bs *api.BuildSource "DockerfileVersion": src.File.DockerfileVersion, "ContextPath": src.File.ContextPath, } + + gitInit := findGitInit(src.File.Source) // workspace starter will only ever send us Git sources. Should that ever change, we'll need to add // manifest support for the other initializer types. - if src.File.Source.GetGit() != nil { - fsrc := src.File.Source.GetGit() + if gitInit != nil { manifest["Source"] = "git" - manifest["CloneTarget"] = fsrc.CloneTaget - manifest["RemoteURI"] = fsrc.RemoteUri + manifest["CloneTarget"] = gitInit.CloneTaget + manifest["RemoteURI"] = gitInit.RemoteUri } else { return "", xerrors.Errorf("unsupported context initializer") } diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts index e7688bfca4bf0f..3bd9146cd85bc6 100644 --- a/components/server/src/container-module.ts +++ b/components/server/src/container-module.ts @@ -74,6 +74,7 @@ import { GitTokenValidator } from './workspace/git-token-validator'; import { newAnalyticsWriterFromEnv, IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/util/analytics'; import { OAuthController } from './oauth-server/oauth-controller'; import { ImageBuildPrefixContextParser } from './workspace/imagebuild-prefix-context-parser'; +import { AdditionalContentPrefixContextParser } from './workspace/additional-content-prefix-context-parser'; export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => { bind(Env).toSelf().inSingletonScope(); @@ -134,6 +135,7 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(IContextParser).to(SnapshotContextParser).inSingletonScope(); bind(IPrefixContextParser).to(EnvvarPrefixParser).inSingletonScope(); bind(IPrefixContextParser).to(ImageBuildPrefixContextParser).inSingletonScope(); + bind(IPrefixContextParser).to(AdditionalContentPrefixContextParser).inSingletonScope(); bind(GitTokenScopeGuesser).toSelf().inSingletonScope(); bind(GitTokenValidator).toSelf().inSingletonScope(); @@ -188,6 +190,6 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(CodeSyncService).toSelf().inSingletonScope(); bind(IAnalyticsWriter).toDynamicValue(newAnalyticsWriterFromEnv).inSingletonScope(); - + bind(OAuthController).toSelf().inSingletonScope(); }); diff --git a/components/server/src/workspace/additional-content-prefix-context-parser.ts b/components/server/src/workspace/additional-content-prefix-context-parser.ts new file mode 100644 index 00000000000000..62cc545cd38283 --- /dev/null +++ b/components/server/src/workspace/additional-content-prefix-context-parser.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2021 Gitpod GmbH. All rights reserved. + * Licensed under the GNU Affero General Public License (AGPL). + * See License-AGPL.txt in the project root for license information. + */ + +import { AdditionalContentContext, User, WorkspaceContext } from "@gitpod/gitpod-protocol"; +import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; +import { base64decode } from "@jmondi/oauth2-server"; +import { inject, injectable } from "inversify"; +import { Env } from "../env"; +import { IPrefixContextParser } from "./context-parser"; + + +/** + * mostly for testing purpose + */ +@injectable() +export class AdditionalContentPrefixContextParser implements IPrefixContextParser { + @inject(Env) protected readonly env: Env; + static PREFIX = /^\/?additionalcontent\/([^\/]*)\//; + + findPrefix(user: User, context: string): string | undefined { + if (this.env.hostUrl.url.host !== 'gitpod.io') { + const result = AdditionalContentPrefixContextParser.PREFIX.exec(context); + if (result) { + return result[0]; + } + } + log.error("Someone tried additionalcontent URL prefix.", {context}); + return undefined; + } + + public async handle(user: User, prefix: string, context: WorkspaceContext): Promise { + const match = AdditionalContentPrefixContextParser.PREFIX.exec(prefix); + if (!match) { + log.error('Could not parse prefix ' + prefix); + return context; + } + const text = base64decode(decodeURIComponent(match[1])); + const files = JSON.parse(text); + (context as any as AdditionalContentContext).additionalFiles = files + return context; + } +} diff --git a/components/server/src/workspace/config-provider.ts b/components/server/src/workspace/config-provider.ts index 47dac3213aeb74..eb01dbf3b8f962 100644 --- a/components/server/src/workspace/config-provider.ts +++ b/components/server/src/workspace/config-provider.ts @@ -9,7 +9,7 @@ import fetch from 'node-fetch'; import * as path from 'path'; import { log, LogContext } from '@gitpod/gitpod-protocol/lib/util/logging'; -import { User, WorkspaceConfig, CommitContext, Repository, ImageConfigString, ExternalImageConfigFile, ImageConfigFile, Commit, NamedWorkspaceFeatureFlag } from "@gitpod/gitpod-protocol"; +import { User, WorkspaceConfig, CommitContext, Repository, ImageConfigString, ExternalImageConfigFile, ImageConfigFile, Commit, NamedWorkspaceFeatureFlag, AdditionalContentContext } from "@gitpod/gitpod-protocol"; import { GitpodFileParser } from "@gitpod/gitpod-protocol/lib/gitpod-file-parser"; import { MaybeContent } from "../repohost/file-provider"; @@ -43,53 +43,64 @@ export class ConfigProvider { span.addTags({ commit }) - + const logContext: LogContext = { userId: user.id }; + let configBasePath = ''; try { - // try and find config file in the context repo or remote in - const host = commit.repository.host; - const hostContext = this.hostContextProvider.get(host); - if (!hostContext || !hostContext.services) { - throw new Error(`Cannot fetch config for host: ${host}`); - } - const services = hostContext.services; - const contextRepoConfig = services.fileProvider.getGitpodFileContent(commit, user); - const definitelyGpConfig = this.fetchExternalGitpodFileContent({ span }, commit.repository); - const inferredConfig = this.inferingConfigProvider.infer(user, commit); - - let customConfigString = await contextRepoConfig; - let configBasePath = ''; - let fromDefinitelyGp = false; - if (!customConfigString) { - /* We haven't found a Gitpod configuration file in the context repo - check definitely-gp. - * - * In case we had found a config file here, we'd still be checking the definitely GP repo, just to save some time. - * While all those checks will be in vain, they should not leak memory either as they'll simply - * be resolved and garbage collected. - */ - const { content, basePath } = await definitelyGpConfig; - customConfigString = content; - // We do not only care about the config itself but also where we got it from - configBasePath = basePath; - fromDefinitelyGp = true; - } - let customConfig: WorkspaceConfig | undefined; - const logContext: LogContext = { userId: user.id }; - if (customConfigString) { + if (AdditionalContentContext.is(commit) && commit.additionalFiles['.gitpod.yml']) { + const customConfigString = commit.additionalFiles['.gitpod.yml']; const parseResult = this.gitpodParser.parse(customConfigString); - customConfig = parseResult.config - await this.fillInDefaultLocations(customConfig, inferredConfig); + customConfig = parseResult.config; + customConfig._origin = 'additional-content'; if (parseResult.validationErrors) { - log.error(logContext, `Skipping invalid config. Errors are ${parseResult.validationErrors.join(',')}`, { repoCloneUrl: commit.repository.cloneUrl, revision: commit.revision, customConfigString }); + log.error(logContext, `Invalid config. Errors are ${parseResult.validationErrors.join(',')}`, { repoCloneUrl: commit.repository.cloneUrl, revision: commit.revision, customConfigString }); } - customConfig._origin = fromDefinitelyGp ? 'definitely-gp' : 'repo'; - } else { - /* There is no configuration for this repository. Before we fall back to the default config, - * let's see if there is language specific configuration we might want to apply. - */ - customConfig = await inferredConfig; - if (customConfig) { - customConfig._origin = 'derived'; + } + if (!customConfig) { + + // try and find config file in the context repo or remote in + const host = commit.repository.host; + const hostContext = this.hostContextProvider.get(host); + if (!hostContext || !hostContext.services) { + throw new Error(`Cannot fetch config for host: ${host}`); + } + const services = hostContext.services; + const contextRepoConfig = services.fileProvider.getGitpodFileContent(commit, user); + const definitelyGpConfig = this.fetchExternalGitpodFileContent({ span }, commit.repository); + const inferredConfig = this.inferingConfigProvider.infer(user, commit); + + let customConfigString = await contextRepoConfig; + let fromDefinitelyGp = false; + if (!customConfigString) { + /* We haven't found a Gitpod configuration file in the context repo - check definitely-gp. + * + * In case we had found a config file here, we'd still be checking the definitely GP repo, just to save some time. + * While all those checks will be in vain, they should not leak memory either as they'll simply + * be resolved and garbage collected. + */ + const { content, basePath } = await definitelyGpConfig; + customConfigString = content; + // We do not only care about the config itself but also where we got it from + configBasePath = basePath; + fromDefinitelyGp = true; + } + + if (customConfigString) { + const parseResult = this.gitpodParser.parse(customConfigString); + customConfig = parseResult.config + await this.fillInDefaultLocations(customConfig, inferredConfig); + if (parseResult.validationErrors) { + log.error(logContext, `Skipping invalid config. Errors are ${parseResult.validationErrors.join(',')}`, { repoCloneUrl: commit.repository.cloneUrl, revision: commit.revision, customConfigString }); + } + customConfig._origin = fromDefinitelyGp ? 'definitely-gp' : 'repo'; + } else { + /* There is no configuration for this repository. Before we fall back to the default config, + * let's see if there is language specific configuration we might want to apply. + */ + customConfig = await inferredConfig; + if (customConfig) { + customConfig._origin = 'derived'; + } } } @@ -112,15 +123,16 @@ export class ConfigProvider { let rev = commit.revision; let image = config.image!; - if (fromDefinitelyGp) { + if (config._origin === 'definitely-gp') { repo = ConfigProvider.DEFINITELY_GP_REPO; rev = 'master'; image.file = dockerfilePath; } - - config.image = { - ...image, - externalSource: await this.fetchWorkspaceImageSourceDocker({ span }, repo, rev, user, dockerfilePath) + if (!(AdditionalContentContext.is(commit) && commit.additionalFiles[dockerfilePath])) { + config.image = { + ...image, + externalSource: await this.fetchWorkspaceImageSourceDocker({ span }, repo, rev, user, dockerfilePath) + } } } diff --git a/components/server/src/workspace/image-source-provider.ts b/components/server/src/workspace/image-source-provider.ts index 4caa6f600f83ea..64d588e2f51321 100644 --- a/components/server/src/workspace/image-source-provider.ts +++ b/components/server/src/workspace/image-source-provider.ts @@ -8,7 +8,8 @@ import { injectable, inject } from "inversify"; import { ImageBuilderClientProvider, ResolveBaseImageRequest, BuildRegistryAuthTotal, BuildRegistryAuth } from "@gitpod/image-builder/lib"; import { HostContextProvider } from "../auth/host-context-provider"; import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; -import { CommitContext, WorkspaceImageSource, WorkspaceConfig, WorkspaceImageSourceReference, WorkspaceImageSourceDocker, ImageConfigFile, ExternalImageConfigFile, User } from "@gitpod/gitpod-protocol"; +import { CommitContext, WorkspaceImageSource, WorkspaceConfig, WorkspaceImageSourceReference, WorkspaceImageSourceDocker, ImageConfigFile, ExternalImageConfigFile, User, AdditionalContentContext } from "@gitpod/gitpod-protocol"; +import { createHmac } from 'crypto'; @injectable() export class ImageSourceProvider { @@ -36,6 +37,14 @@ export class ImageSourceProvider { dockerFileHash: lastDockerFileSha } } else if (ImageConfigFile.is(imgcfg)) { + // if a dockerfile sits in the additional content we use its contents sha + if (AdditionalContentContext.is(context) && ImageConfigFile.is(config.image) && context.additionalFiles[config.image.file]) { + return { + dockerFilePath: config.image.file, + dockerFileHash: this.getContentSHA(context.additionalFiles[config.image.file]), + dockerFileSource: CommitContext.is(context) ? context : undefined + } + } // There are no special instructions as to where to get the Dockerfile from, hence we use the context of the current workspace. const hostContext = this.hostContextProvider.get(context.repository.host); if (!hostContext || !hostContext.services) { @@ -47,7 +56,7 @@ export class ImageSourceProvider { dockerFileSource: context, dockerFileHash: lastDockerFileSha, } - } else if (typeof(imgcfg) === "string") { + } else if (typeof (imgcfg) === "string") { // We resolve this request allowing all configured auth because at this poing we don't have access to the user or permission service. // If anyone feels like changing this and properly use the REGISTRY_ACCESS permission, be my guest. // @@ -65,7 +74,7 @@ export class ImageSourceProvider { req.setAuth(auth); const client = this.imagebuilderClientProvider.getDefault(); - const res = await client.resolveBaseImage({span}, req); + const res = await client.resolveBaseImage({ span }, req); result = { baseImageResolved: res.getRef() @@ -76,12 +85,18 @@ export class ImageSourceProvider { return result; } catch (e) { - TraceContext.logError({span}, e); + TraceContext.logError({ span }, e); throw e; } finally { span.finish(); } } + protected getContentSHA(contents: string): string { + return createHmac('sha256', '') + .update(contents) + .digest('hex'); + } + } \ No newline at end of file diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index b1a8f222148695..5de7268cd45f0c 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -4,9 +4,10 @@ * See License-AGPL.txt in the project root for license information. */ -import { CloneTargetMode, GitAuthMethod, GitConfig, GitInitializer, PrebuildInitializer, SnapshotInitializer, WorkspaceInitializer } from "@gitpod/content-service/lib"; +import { CloneTargetMode, FileDownloadInitializer, GitAuthMethod, GitConfig, GitInitializer, PrebuildInitializer, SnapshotInitializer, WorkspaceInitializer } from "@gitpod/content-service/lib"; +import { CompositeInitializer } from "@gitpod/content-service/lib/initializer_pb"; import { DBUser, DBWithTracing, TracedUserDB, TracedWorkspaceDB, UserDB, WorkspaceDB } from '@gitpod/gitpod-db/lib'; -import { CommitContext, Disposable, GitpodToken, GitpodTokenType, ImageConfigFile, IssueContext, NamedWorkspaceFeatureFlag, PullRequestContext, RefType, SnapshotContext, StartWorkspaceResult, User, UserEnvVar, UserEnvVarValue, WithEnvvarsContext, WithPrebuild, Workspace, WorkspaceContext, WorkspaceImageSource, WorkspaceImageSourceDocker, WorkspaceImageSourceReference, WorkspaceInstance, WorkspaceInstanceConfiguration, WorkspaceInstanceStatus, WorkspaceProbeContext, Permission, HeadlessLogEvent, HeadlessWorkspaceEventType } from "@gitpod/gitpod-protocol"; +import { CommitContext, Disposable, GitpodToken, GitpodTokenType, IssueContext, NamedWorkspaceFeatureFlag, PullRequestContext, RefType, SnapshotContext, StartWorkspaceResult, User, UserEnvVar, UserEnvVarValue, WithEnvvarsContext, WithPrebuild, Workspace, WorkspaceContext, WorkspaceImageSource, WorkspaceImageSourceDocker, WorkspaceImageSourceReference, WorkspaceInstance, WorkspaceInstanceConfiguration, WorkspaceInstanceStatus, WorkspaceProbeContext, Permission, HeadlessLogEvent, HeadlessWorkspaceEventType, DisposableCollection, AdditionalContentContext } from "@gitpod/gitpod-protocol"; import { IAnalyticsWriter } from '@gitpod/gitpod-protocol/lib/util/analytics'; import { log } from '@gitpod/gitpod-protocol/lib/util/logging'; import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; @@ -176,9 +177,9 @@ export class WorkspaceStarter { const resp = (await manager.startWorkspace({ span }, startRequest)).toObject(); span.log({ "resp": resp }); - this.analytics.track({ - userId: user.id, - event: "workspace-started", + this.analytics.track({ + userId: user.id, + event: "workspace-started", properties: { workspaceId: workspace.id, instanceId: instance.id, @@ -345,23 +346,32 @@ export class WorkspaceStarter { auth.setSelective(selectiveAuth); } if (WorkspaceImageSourceDocker.is(imgsrc)) { - // We only build Docker images from Git initializer. To ensure that createGitInitializer relies on the - // unset "ref". To double down on this, we do modify the initializer request. We're just using createGitInitializer - // for the authentication handling. - const { git, disposable } = await this.createGitInitializer({span}, workspace, { - ...imgsrc.dockerFileSource, - title: "irrelevant", - ref: undefined, - }, user); - git.setCloneTaget(imgsrc.dockerFileSource.revision); - git.setTargetMode(CloneTargetMode.REMOTE_COMMIT); - git.setCheckoutLocation(".") - const source = new WorkspaceInitializer(); - source.setGit(git); - - // Dockerfile and context path are both relative to the checkout location - let contextPath = (workspace.config.image as ImageConfigFile).context || "."; - let dockerFilePath = imgsrc.dockerFilePath; + let source: WorkspaceInitializer; + const disp = new DisposableCollection(); + let checkoutLocation = this.getCheckoutLocation(workspace); + if (!AdditionalContentContext.hasDockerConfig(workspace.context, workspace.config) && imgsrc.dockerFileSource) { + // TODO(se): we cannot change this initializer structure now because it is part of how baserefs are computed in image-builder. + // Image builds should however just use the initialization if the workspace they are running for (i.e. the one from above). + const { git, disposable } = await this.createGitInitializer({span}, workspace, { + ...imgsrc.dockerFileSource, + title: "irrelevant", + ref: undefined, + }, user); + disp.push(disposable); + git.setCloneTaget(imgsrc.dockerFileSource.revision); + git.setTargetMode(CloneTargetMode.REMOTE_COMMIT); + checkoutLocation = "." + git.setCheckoutLocation(checkoutLocation); + source = new WorkspaceInitializer(); + source.setGit(git); + } else { + const {initializer, disposable} = await this.createInitializer({span}, workspace, workspace.context, user); + source = initializer; + disp.push(disposable); + } + + let contextPath = checkoutLocation; + let dockerFilePath = checkoutLocation + '/' + imgsrc.dockerFilePath; const file = new BuildSourceDockerfile(); file.setContextPath(contextPath); @@ -371,7 +381,7 @@ export class WorkspaceStarter { const src = new BuildSource(); src.setFile(file); - return {src, auth, disposable}; + return {src, auth, disposable: disp}; } if (WorkspaceImageSourceReference.is(imgsrc)) { const ref = new BuildSourceReference(); @@ -634,7 +644,7 @@ export class WorkspaceStarter { spec.setEnvvarsList(envvars); spec.setGit(this.createGitSpec(workspace, user)); spec.setPortsList(ports); - spec.setInitializer(await initializerPromise); + spec.setInitializer((await initializerPromise).initializer); spec.setIdeImage(ideImage); spec.setWorkspaceImage(instance.workspaceImage); spec.setWorkspaceLocation(workspace.config.workspaceLocation || spec.getCheckoutLocation()); @@ -716,8 +726,9 @@ export class WorkspaceStarter { return gitSpec; } - protected async createInitializer(traceCtx: TraceContext, workspace: Workspace, context: WorkspaceContext, user: User): Promise { + protected async createInitializer(traceCtx: TraceContext, workspace: Workspace, context: WorkspaceContext, user: User): Promise<{initializer: WorkspaceInitializer, disposable: Disposable}> { let result = new WorkspaceInitializer(); + const disp = new DisposableCollection(); if (SnapshotContext.is(context)) { const snapshot = new SnapshotInitializer(); @@ -738,13 +749,46 @@ export class WorkspaceStarter { } else if (WorkspaceProbeContext.is(context)) { // workspace probes have no workspace initializer as they need no content } else if (CommitContext.is(context)) { - const { git } = await this.createGitInitializer(traceCtx, workspace, context, user); + const { git, disposable } = await this.createGitInitializer(traceCtx, workspace, context, user); + disp.push(disposable); result.setGit(git); } else { throw new Error("cannot create initializer for unkown context type"); } + if (AdditionalContentContext.is(context)) { + const additionalInit = new FileDownloadInitializer(); - return result; + const getDigest = (contents: string) => { + return 'sha256:'+crypto.createHmac('sha256', '') + .update(contents) + .digest('hex'); + } + + const tokenExpirationTime = new Date(); + tokenExpirationTime.setMinutes(tokenExpirationTime.getMinutes() + 30); + const fileInfos = await Promise.all(Object.entries(context.additionalFiles).map(async ([filePath, content]) => { + const url = await this.otsServer.serve(traceCtx, content, tokenExpirationTime); + const finfo = new FileDownloadInitializer.FileInfo(); + finfo.setUrl(url.token); + finfo.setFilePath(filePath); + finfo.setDigest(getDigest(content)) + return finfo; + })); + + additionalInit.setFilesList(fileInfos); + additionalInit.setTargetLocation(this.getCheckoutLocation(workspace)); + + // wire the protobuf structure + const newRoot = new WorkspaceInitializer(); + const composite = new CompositeInitializer(); + newRoot.setComposite(composite); + composite.addInitializer(result); + const wsInitializerForDownload = new WorkspaceInitializer(); + wsInitializerForDownload.setDownload(additionalInit); + composite.addInitializer(wsInitializerForDownload); + result = newRoot; + } + return {initializer: result, disposable: disp}; } protected async createGitInitializer(traceCtx: TraceContext, workspace: Workspace, context: CommitContext, user: User): Promise<{git: GitInitializer, disposable: Disposable}> { @@ -819,7 +863,7 @@ export class WorkspaceStarter { const result = new GitInitializer(); result.setConfig(gitConfig); - result.setCheckoutLocation(workspace.config.checkoutLocation || context.repository.name); + result.setCheckoutLocation(this.getCheckoutLocation(workspace)); if (!!cloneTarget) { result.setCloneTaget(cloneTarget); } @@ -835,6 +879,10 @@ export class WorkspaceStarter { }; } + protected getCheckoutLocation(workspace: Workspace) { + return workspace.config.checkoutLocation || CommitContext.is(workspace.context) && workspace.context.repository.name || '.'; + } + protected buildUpstreamCloneUrl(context: CommitContext): string | undefined { let upstreamCloneUrl: string | undefined = undefined; if (PullRequestContext.is(context) && context.base) { diff --git a/components/supervisor-api/typescript-rest/lib/control.dt.ts b/components/supervisor-api/typescript-rest/lib/control.dt.ts new file mode 100644 index 00000000000000..08ee1f5a3471dd --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/control.dt.ts @@ -0,0 +1,13 @@ +declare namespace Definitions { + export interface ProtobufAny { + typeUrl?: string; + value?: string; // byte + } + export interface RpcStatus { + code?: number; // int32 + message?: string; + details?: ProtobufAny[]; + } + export interface SupervisorExposePortResponse { + } +} diff --git a/components/supervisor-api/typescript-rest/lib/control.swagger.json b/components/supervisor-api/typescript-rest/lib/control.swagger.json new file mode 100644 index 00000000000000..faabf4ecc8efde --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/control.swagger.json @@ -0,0 +1,54 @@ +{ + "swagger": "2.0", + "info": { + "title": "control.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "ControlService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": {}, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "typeUrl": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "supervisorExposePortResponse": { + "type": "object" + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/info.dt.ts b/components/supervisor-api/typescript-rest/lib/info.dt.ts new file mode 100644 index 00000000000000..c1ad581305fdbc --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/info.dt.ts @@ -0,0 +1,81 @@ +declare namespace Definitions { + export interface ProtobufAny { + typeUrl?: string; + value?: string; // byte + } + export interface RpcStatus { + code?: number; // int32 + message?: string; + details?: ProtobufAny[]; + } + export interface SupervisorWorkspaceInfoResponse { + /** + * workspace_id is the workspace ID of this workspace. + */ + workspaceId?: string; + /** + * instance_id is the instance ID of this workspace. + */ + instanceId?: string; + /** + * checkout_location is the path where we initialized the workspace content + */ + checkoutLocation?: string; + /** + * file means the workspace root is a file describing the workspace layout. + */ + workspaceLocationFile?: string; + /** + * folder means the workspace root is a simple folder. + */ + workspaceLocationFolder?: string; + /** + * user_home is the path to the user's home. + */ + userHome?: string; + /** + * GitpodAPI provides information to reach the Gitpod server API. + */ + gitpodApi?: WorkspaceInfoResponseGitpodAPI; + /** + * gitpod_host provides Gitpod host URL. + */ + gitpodHost?: string; + /** + * workspace_context_url is an URL for which the workspace was created. + */ + workspaceContextUrl?: string; + /** + * repository is a repository from which this workspace was created + */ + repository?: WorkspaceInfoResponseRepository; + } + export interface WorkspaceInfoResponseGitpodAPI { + /** + * endpoint is the websocket URL on which the token-accessible Gitpod API is served on + */ + endpoint?: string; + /** + * host is the host of the endpoint. Use this host to ask supervisor a token. + */ + host?: string; + } + export interface WorkspaceInfoResponseRepository { + /** + * owner is the repository owner + */ + owner?: string; + /** + * name is the repository name + */ + name?: string; + } +} +declare namespace Paths { + namespace InfoServiceWorkspaceInfo { + namespace Responses { + export type $200 = Definitions.SupervisorWorkspaceInfoResponse; + export type Default = Definitions.RpcStatus; + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/info.swagger.json b/components/supervisor-api/typescript-rest/lib/info.swagger.json new file mode 100644 index 00000000000000..72b68098daf04e --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/info.swagger.json @@ -0,0 +1,145 @@ +{ + "swagger": "2.0", + "info": { + "title": "info.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "InfoService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/info/workspace": { + "get": { + "operationId": "InfoService_WorkspaceInfo", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorWorkspaceInfoResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "InfoService" + ] + } + } + }, + "definitions": { + "WorkspaceInfoResponseGitpodAPI": { + "type": "object", + "properties": { + "endpoint": { + "type": "string", + "title": "endpoint is the websocket URL on which the token-accessible Gitpod API is served on" + }, + "host": { + "type": "string", + "description": "host is the host of the endpoint. Use this host to ask supervisor a token." + } + } + }, + "WorkspaceInfoResponseRepository": { + "type": "object", + "properties": { + "owner": { + "type": "string", + "title": "owner is the repository owner" + }, + "name": { + "type": "string", + "title": "name is the repository name" + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "typeUrl": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "supervisorWorkspaceInfoResponse": { + "type": "object", + "properties": { + "workspaceId": { + "type": "string", + "description": "workspace_id is the workspace ID of this workspace." + }, + "instanceId": { + "type": "string", + "description": "instance_id is the instance ID of this workspace." + }, + "checkoutLocation": { + "type": "string", + "title": "checkout_location is the path where we initialized the workspace content" + }, + "workspaceLocationFile": { + "type": "string", + "description": "file means the workspace root is a file describing the workspace layout." + }, + "workspaceLocationFolder": { + "type": "string", + "description": "folder means the workspace root is a simple folder." + }, + "userHome": { + "type": "string", + "description": "user_home is the path to the user's home." + }, + "gitpodApi": { + "$ref": "#/definitions/WorkspaceInfoResponseGitpodAPI", + "description": "GitpodAPI provides information to reach the Gitpod server API." + }, + "gitpodHost": { + "type": "string", + "description": "gitpod_host provides Gitpod host URL." + }, + "workspaceContextUrl": { + "type": "string", + "description": "workspace_context_url is an URL for which the workspace was created." + }, + "repository": { + "$ref": "#/definitions/WorkspaceInfoResponseRepository", + "title": "repository is a repository from which this workspace was created" + } + } + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/notification.dt.ts b/components/supervisor-api/typescript-rest/lib/notification.dt.ts new file mode 100644 index 00000000000000..e74315a257042a --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/notification.dt.ts @@ -0,0 +1,58 @@ +declare namespace Definitions { + export type NotifyRequestLevel = "ERROR" | "WARNING" | "INFO"; + export interface ProtobufAny { + typeUrl?: string; + value?: string; // byte + } + export interface RpcStatus { + code?: number; // int32 + message?: string; + details?: ProtobufAny[]; + } + export interface SupervisorNotifyRequest { + level?: NotifyRequestLevel; + message?: string; + /** + * if actions are empty, Notify will return immediately + */ + actions?: string[]; + } + export interface SupervisorNotifyResponse { + /** + * action chosen by the user or empty string if cancelled + */ + action?: string; + } + export interface SupervisorRespondResponse { + } + export interface SupervisorSubscribeResponse { + requestId?: string; // uint64 + request?: SupervisorNotifyRequest; + } +} +declare namespace Paths { + namespace NotificationServiceNotify { + namespace Responses { + export type $200 = Definitions.SupervisorNotifyResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace NotificationServiceRespond { + namespace Responses { + export type $200 = Definitions.SupervisorRespondResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace NotificationServiceSubscribe { + namespace Responses { + /** + * Stream result of supervisorSubscribeResponse + */ + export interface $200 { + result?: Definitions.SupervisorSubscribeResponse; + error?: Definitions.RpcStatus; + } + export type Default = Definitions.RpcStatus; + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/notification.swagger.json b/components/supervisor-api/typescript-rest/lib/notification.swagger.json new file mode 100644 index 00000000000000..cda6960205d071 --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/notification.swagger.json @@ -0,0 +1,181 @@ +{ + "swagger": "2.0", + "info": { + "title": "notification.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "NotificationService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/notification/notify": { + "post": { + "summary": "Prompts the user and asks for a decision. Typically called by some external process.\nIf the list of actions is empty this service returns immediately,\notherwise it blocks until the user has made their choice.", + "operationId": "NotificationService_Notify", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorNotifyResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "NotificationService" + ] + } + }, + "/v1/notification/respond": { + "post": { + "summary": "Report a user's choice as a response to a notification. Typically called by the IDE.", + "operationId": "NotificationService_Respond", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorRespondResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "NotificationService" + ] + } + }, + "/v1/notification/subscribe": { + "get": { + "summary": "Subscribe to notifications. Typically called by the IDE.", + "operationId": "NotificationService_Subscribe", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/supervisorSubscribeResponse" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of supervisorSubscribeResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "NotificationService" + ] + } + } + }, + "definitions": { + "NotifyRequestLevel": { + "type": "string", + "enum": [ + "ERROR", + "WARNING", + "INFO" + ], + "default": "ERROR" + }, + "protobufAny": { + "type": "object", + "properties": { + "typeUrl": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "supervisorNotifyRequest": { + "type": "object", + "properties": { + "level": { + "$ref": "#/definitions/NotifyRequestLevel" + }, + "message": { + "type": "string" + }, + "actions": { + "type": "array", + "items": { + "type": "string" + }, + "title": "if actions are empty, Notify will return immediately" + } + } + }, + "supervisorNotifyResponse": { + "type": "object", + "properties": { + "action": { + "type": "string", + "title": "action chosen by the user or empty string if cancelled" + } + } + }, + "supervisorRespondResponse": { + "type": "object" + }, + "supervisorSubscribeResponse": { + "type": "object", + "properties": { + "requestId": { + "type": "string", + "format": "uint64" + }, + "request": { + "$ref": "#/definitions/supervisorNotifyRequest" + } + } + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/port.dt.ts b/components/supervisor-api/typescript-rest/lib/port.dt.ts new file mode 100644 index 00000000000000..c2cba7a2b2b0e8 --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/port.dt.ts @@ -0,0 +1,57 @@ +declare namespace Definitions { + export interface ProtobufAny { + typeUrl?: string; + value?: string; // byte + } + export interface RpcStatus { + code?: number; // int32 + message?: string; + details?: ProtobufAny[]; + } + export interface SupervisorAutoTunnelResponse { + } + export interface SupervisorCloseTunnelResponse { + } + export interface SupervisorEstablishTunnelResponse { + data?: string; // byte + } + export interface SupervisorTunnelPortRequest { + port?: number; // int64 + targetPort?: number; // int64 + visibility?: SupervisorTunnelVisiblity; + clientId?: string; + } + export interface SupervisorTunnelPortResponse { + } + export type SupervisorTunnelVisiblity = "none" | "host" | "network"; +} +declare namespace Paths { + namespace PortServiceAutoTunnel { + namespace Responses { + export type $200 = Definitions.SupervisorAutoTunnelResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace PortServiceCloseTunnel { + namespace Responses { + export type $200 = Definitions.SupervisorCloseTunnelResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace PortServiceTunnel { + export interface BodyParameters { + body: Parameters.Body; + } + namespace Parameters { + export interface Body { + targetPort?: number; // int64 + visibility?: Definitions.SupervisorTunnelVisiblity; + clientId?: string; + } + } + namespace Responses { + export type $200 = Definitions.SupervisorTunnelPortResponse; + export type Default = Definitions.RpcStatus; + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/port.swagger.json b/components/supervisor-api/typescript-rest/lib/port.swagger.json new file mode 100644 index 00000000000000..03c4de5fdc8195 --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/port.swagger.json @@ -0,0 +1,211 @@ +{ + "swagger": "2.0", + "info": { + "title": "port.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "PortService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/port/tunnel/auto/{enabled}": { + "post": { + "summary": "AutoTunnel controls enablement of auto tunneling", + "operationId": "PortService_AutoTunnel", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorAutoTunnelResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "enabled", + "in": "path", + "required": true, + "type": "boolean" + } + ], + "tags": [ + "PortService" + ] + } + }, + "/v1/port/tunnel/{port}": { + "delete": { + "summary": "CloseTunnel notifies clients to remove listeners on remote machines.", + "operationId": "PortService_CloseTunnel", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorCloseTunnelResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "port", + "in": "path", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "tags": [ + "PortService" + ] + }, + "post": { + "summary": "Tunnel notifies clients to install listeners on remote machines.\nAfter that such clients should call EstablishTunnel to forward incoming connections.", + "operationId": "PortService_Tunnel", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorTunnelPortResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "port", + "in": "path", + "required": true, + "type": "integer", + "format": "int64" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "targetPort": { + "type": "integer", + "format": "int64" + }, + "visibility": { + "$ref": "#/definitions/supervisorTunnelVisiblity" + }, + "clientId": { + "type": "string" + } + } + } + } + ], + "tags": [ + "PortService" + ] + } + } + }, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "typeUrl": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "supervisorAutoTunnelResponse": { + "type": "object" + }, + "supervisorCloseTunnelResponse": { + "type": "object" + }, + "supervisorEstablishTunnelResponse": { + "type": "object", + "properties": { + "data": { + "type": "string", + "format": "byte" + } + } + }, + "supervisorTunnelPortRequest": { + "type": "object", + "properties": { + "port": { + "type": "integer", + "format": "int64" + }, + "targetPort": { + "type": "integer", + "format": "int64" + }, + "visibility": { + "$ref": "#/definitions/supervisorTunnelVisiblity" + }, + "clientId": { + "type": "string" + } + } + }, + "supervisorTunnelPortResponse": { + "type": "object" + }, + "supervisorTunnelVisiblity": { + "type": "string", + "enum": [ + "none", + "host", + "network" + ], + "default": "none" + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/status.dt.ts b/components/supervisor-api/typescript-rest/lib/status.dt.ts new file mode 100644 index 00000000000000..9fb84ae8aeb934 --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/status.dt.ts @@ -0,0 +1,194 @@ +declare namespace Definitions { + export interface ProtobufAny { + typeUrl?: string; + value?: string; // byte + } + export interface RpcStatus { + code?: number; // int32 + message?: string; + details?: ProtobufAny[]; + } + export interface SupervisorBackupStatusResponse { + canaryAvailable?: boolean; + } + export type SupervisorContentSource = "from_other" | "from_backup" | "from_prebuild"; + export interface SupervisorContentStatusResponse { + /** + * true if the workspace content is available + */ + available?: boolean; + /** + * source indicates where the workspace content came from + */ + source?: SupervisorContentSource; + } + export interface SupervisorExposedPortInfo { + /** + * public determines if the port is available without authentication or not + */ + visibility?: SupervisorPortVisibility; + /** + * url is the URL at which the port is available + */ + url?: string; + /** + * action hint on expose + */ + onExposed?: SupervisorOnPortExposedAction; + } + export interface SupervisorIDEStatusResponse { + ok?: boolean; + } + export type SupervisorOnPortExposedAction = "ignore" | "open_browser" | "open_preview" | "notify" | "notify_private"; + export type SupervisorPortVisibility = "private" | "public"; + export interface SupervisorPortsStatus { + /** + * local_port is the port a service actually bound to. Some services bind + * to localhost:, in which case they cannot be made accessible from + * outside the container. To help with this, supervisor then starts a proxy + * that forwards traffic to this local port. In those cases, global_port + * contains the port where the proxy is listening on. + */ + localPort?: number; // int64 + globalPort?: number; // int64 + /** + * served is true if there is a process in the workspace that serves this port. + */ + served?: boolean; + /** + * Exposed provides information when a port is exposed. If this field isn't set, + * the port is not available from outside the workspace (i.e. the internet). + */ + exposed?: SupervisorExposedPortInfo; + /** + * Tunneled provides information when a port is tunneled. If not present then + * the port is not tunneled. + */ + tunneled?: SupervisorTunneledPortInfo; + } + export interface SupervisorPortsStatusResponse { + ports?: SupervisorPortsStatus[]; + } + export interface SupervisorSupervisorStatusResponse { + ok?: boolean; + } + export interface SupervisorTaskPresentation { + name?: string; + openIn?: string; + openMode?: string; + } + export type SupervisorTaskState = "opening" | "running" | "closed"; + export interface SupervisorTaskStatus { + id?: string; + state?: SupervisorTaskState; + terminal?: string; + presentation?: SupervisorTaskPresentation; + } + export interface SupervisorTasksStatusResponse { + tasks?: SupervisorTaskStatus[]; + } + export type SupervisorTunnelVisiblity = "none" | "host" | "network"; + export interface SupervisorTunneledPortInfo { + /** + * target port is the desired port on the remote machine + */ + targetPort?: number; // int64 + /** + * visibility determines if the listener on remote machine should accept connections from localhost or network + * visibility none means that the port should not be tunneled + */ + visibility?: SupervisorTunnelVisiblity; + /** + * map of remote clients indicates on which remote port each client is listening to + */ + clients?: { + [name: string]: number; // int64 + }; + } +} +declare namespace Paths { + namespace StatusServiceBackupStatus { + namespace Responses { + export type $200 = Definitions.SupervisorBackupStatusResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace StatusServiceContentStatus { + namespace Responses { + export type $200 = Definitions.SupervisorContentStatusResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace StatusServiceContentStatus2 { + namespace Responses { + export type $200 = Definitions.SupervisorContentStatusResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace StatusServiceIDEStatus { + namespace Responses { + export type $200 = Definitions.SupervisorIDEStatusResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace StatusServiceIDEStatus2 { + namespace Responses { + export type $200 = Definitions.SupervisorIDEStatusResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace StatusServicePortsStatus { + namespace Responses { + /** + * Stream result of supervisorPortsStatusResponse + */ + export interface $200 { + result?: Definitions.SupervisorPortsStatusResponse; + error?: Definitions.RpcStatus; + } + export type Default = Definitions.RpcStatus; + } + } + namespace StatusServicePortsStatus2 { + namespace Responses { + /** + * Stream result of supervisorPortsStatusResponse + */ + export interface $200 { + result?: Definitions.SupervisorPortsStatusResponse; + error?: Definitions.RpcStatus; + } + export type Default = Definitions.RpcStatus; + } + } + namespace StatusServiceSupervisorStatus { + namespace Responses { + export type $200 = Definitions.SupervisorSupervisorStatusResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace StatusServiceTasksStatus { + namespace Responses { + /** + * Stream result of supervisorTasksStatusResponse + */ + export interface $200 { + result?: Definitions.SupervisorTasksStatusResponse; + error?: Definitions.RpcStatus; + } + export type Default = Definitions.RpcStatus; + } + } + namespace StatusServiceTasksStatus2 { + namespace Responses { + /** + * Stream result of supervisorTasksStatusResponse + */ + export interface $200 { + result?: Definitions.SupervisorTasksStatusResponse; + error?: Definitions.RpcStatus; + } + export type Default = Definitions.RpcStatus; + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/status.swagger.json b/components/supervisor-api/typescript-rest/lib/status.swagger.json new file mode 100644 index 00000000000000..cd5ac233ad81ac --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/status.swagger.json @@ -0,0 +1,591 @@ +{ + "swagger": "2.0", + "info": { + "title": "status.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "StatusService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/status/backup": { + "get": { + "summary": "BackupStatus offers feedback on the workspace backup status. This status information can\nbe relayed to the user to provide transparency as to how \"safe\" their files/content\ndata are w.r.t. to being lost.", + "operationId": "StatusService_BackupStatus", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorBackupStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "StatusService" + ] + } + }, + "/v1/status/content": { + "get": { + "summary": "ContentStatus returns the status of the workspace content. When used with `wait`, the call\nreturns when the content has become available.", + "operationId": "StatusService_ContentStatus", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorContentStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "wait", + "description": "if true this request will return either when it times out or when the workspace content\nhas become available.", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "StatusService" + ] + } + }, + "/v1/status/content/wait/{wait}": { + "get": { + "summary": "ContentStatus returns the status of the workspace content. When used with `wait`, the call\nreturns when the content has become available.", + "operationId": "StatusService_ContentStatus2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorContentStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "wait", + "description": "if true this request will return either when it times out or when the workspace content\nhas become available.", + "in": "path", + "required": true, + "type": "boolean" + } + ], + "tags": [ + "StatusService" + ] + } + }, + "/v1/status/ide": { + "get": { + "summary": "IDEStatus returns OK if the IDE can serve requests.", + "operationId": "StatusService_IDEStatus", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorIDEStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "wait", + "description": "if true this request will return either when it times out or when the workspace IDE\nhas become available.", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "StatusService" + ] + } + }, + "/v1/status/ide/wait/{wait}": { + "get": { + "summary": "IDEStatus returns OK if the IDE can serve requests.", + "operationId": "StatusService_IDEStatus2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorIDEStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "wait", + "description": "if true this request will return either when it times out or when the workspace IDE\nhas become available.", + "in": "path", + "required": true, + "type": "boolean" + } + ], + "tags": [ + "StatusService" + ] + } + }, + "/v1/status/ports": { + "get": { + "summary": "PortsStatus provides feedback about the network ports currently in use.", + "operationId": "StatusService_PortsStatus", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/supervisorPortsStatusResponse" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of supervisorPortsStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "observe", + "description": "if observe is true, we'll return a stream of changes rather than just the\ncurrent state of affairs.", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "StatusService" + ] + } + }, + "/v1/status/ports/observe/{observe}": { + "get": { + "summary": "PortsStatus provides feedback about the network ports currently in use.", + "operationId": "StatusService_PortsStatus2", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/supervisorPortsStatusResponse" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of supervisorPortsStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "observe", + "description": "if observe is true, we'll return a stream of changes rather than just the\ncurrent state of affairs.", + "in": "path", + "required": true, + "type": "boolean" + } + ], + "tags": [ + "StatusService" + ] + } + }, + "/v1/status/supervisor": { + "get": { + "summary": "SupervisorStatus returns once supervisor is running.", + "operationId": "StatusService_SupervisorStatus", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorSupervisorStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "StatusService" + ] + } + }, + "/v1/status/tasks": { + "get": { + "summary": "TasksStatus provides tasks status information.", + "operationId": "StatusService_TasksStatus", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/supervisorTasksStatusResponse" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of supervisorTasksStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "observe", + "description": "if observe is true, we'll return a stream of changes rather than just the\ncurrent state of affairs.", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "StatusService" + ] + } + }, + "/v1/status/tasks/observe/{observe}": { + "get": { + "summary": "TasksStatus provides tasks status information.", + "operationId": "StatusService_TasksStatus2", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/supervisorTasksStatusResponse" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of supervisorTasksStatusResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "observe", + "description": "if observe is true, we'll return a stream of changes rather than just the\ncurrent state of affairs.", + "in": "path", + "required": true, + "type": "boolean" + } + ], + "tags": [ + "StatusService" + ] + } + } + }, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "typeUrl": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "supervisorBackupStatusResponse": { + "type": "object", + "properties": { + "canaryAvailable": { + "type": "boolean" + } + } + }, + "supervisorContentSource": { + "type": "string", + "enum": [ + "from_other", + "from_backup", + "from_prebuild" + ], + "default": "from_other" + }, + "supervisorContentStatusResponse": { + "type": "object", + "properties": { + "available": { + "type": "boolean", + "title": "true if the workspace content is available" + }, + "source": { + "$ref": "#/definitions/supervisorContentSource", + "title": "source indicates where the workspace content came from" + } + } + }, + "supervisorExposedPortInfo": { + "type": "object", + "properties": { + "visibility": { + "$ref": "#/definitions/supervisorPortVisibility", + "title": "public determines if the port is available without authentication or not" + }, + "url": { + "type": "string", + "title": "url is the URL at which the port is available" + }, + "onExposed": { + "$ref": "#/definitions/supervisorOnPortExposedAction", + "title": "action hint on expose" + } + } + }, + "supervisorIDEStatusResponse": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + } + } + }, + "supervisorOnPortExposedAction": { + "type": "string", + "enum": [ + "ignore", + "open_browser", + "open_preview", + "notify", + "notify_private" + ], + "default": "ignore" + }, + "supervisorPortVisibility": { + "type": "string", + "enum": [ + "private", + "public" + ], + "default": "private" + }, + "supervisorPortsStatus": { + "type": "object", + "properties": { + "localPort": { + "type": "integer", + "format": "int64", + "description": "local_port is the port a service actually bound to. Some services bind\nto localhost:\u003cport\u003e, in which case they cannot be made accessible from\noutside the container. To help with this, supervisor then starts a proxy\nthat forwards traffic to this local port. In those cases, global_port\ncontains the port where the proxy is listening on." + }, + "globalPort": { + "type": "integer", + "format": "int64" + }, + "served": { + "type": "boolean", + "description": "served is true if there is a process in the workspace that serves this port." + }, + "exposed": { + "$ref": "#/definitions/supervisorExposedPortInfo", + "description": "Exposed provides information when a port is exposed. If this field isn't set,\nthe port is not available from outside the workspace (i.e. the internet)." + }, + "tunneled": { + "$ref": "#/definitions/supervisorTunneledPortInfo", + "description": "Tunneled provides information when a port is tunneled. If not present then\nthe port is not tunneled." + } + } + }, + "supervisorPortsStatusResponse": { + "type": "object", + "properties": { + "ports": { + "type": "array", + "items": { + "$ref": "#/definitions/supervisorPortsStatus" + } + } + } + }, + "supervisorSupervisorStatusResponse": { + "type": "object", + "properties": { + "ok": { + "type": "boolean" + } + } + }, + "supervisorTaskPresentation": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "openIn": { + "type": "string" + }, + "openMode": { + "type": "string" + } + } + }, + "supervisorTaskState": { + "type": "string", + "enum": [ + "opening", + "running", + "closed" + ], + "default": "opening" + }, + "supervisorTaskStatus": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "state": { + "$ref": "#/definitions/supervisorTaskState" + }, + "terminal": { + "type": "string" + }, + "presentation": { + "$ref": "#/definitions/supervisorTaskPresentation" + } + } + }, + "supervisorTasksStatusResponse": { + "type": "object", + "properties": { + "tasks": { + "type": "array", + "items": { + "$ref": "#/definitions/supervisorTaskStatus" + } + } + } + }, + "supervisorTunnelVisiblity": { + "type": "string", + "enum": [ + "none", + "host", + "network" + ], + "default": "none" + }, + "supervisorTunneledPortInfo": { + "type": "object", + "properties": { + "targetPort": { + "type": "integer", + "format": "int64", + "title": "target port is the desired port on the remote machine" + }, + "visibility": { + "$ref": "#/definitions/supervisorTunnelVisiblity", + "title": "visibility determines if the listener on remote machine should accept connections from localhost or network\nvisibility none means that the port should not be tunneled" + }, + "clients": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int64" + }, + "title": "map of remote clients indicates on which remote port each client is listening to" + } + } + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/terminal.dt.ts b/components/supervisor-api/typescript-rest/lib/terminal.dt.ts new file mode 100644 index 00000000000000..bdacdbb6c8eb9c --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/terminal.dt.ts @@ -0,0 +1,89 @@ +declare namespace Definitions { + export interface ProtobufAny { + typeUrl?: string; + value?: string; // byte + } + export interface RpcStatus { + code?: number; // int32 + message?: string; + details?: ProtobufAny[]; + } + export interface SupervisorListTerminalsResponse { + terminals?: SupervisorTerminal[]; + } + export interface SupervisorListenTerminalResponse { + data?: string; // byte + exitCode?: number; // int32 + title?: string; + } + export interface SupervisorOpenTerminalResponse { + terminal?: SupervisorTerminal; + /** + * starter_token can be used to change the terminal size if there are + * multiple listerns, without having to force your way in. + */ + starterToken?: string; + } + export interface SupervisorSetTerminalSizeResponse { + } + export interface SupervisorShutdownTerminalResponse { + } + export interface SupervisorTerminal { + alias?: string; + command?: string[]; + title?: string; + pid?: string; // int64 + initialWorkdir?: string; + currentWorkdir?: string; + annotations?: { + [name: string]: string; + }; + } + export interface SupervisorTerminalSize { + rows?: number; // int64 + cols?: number; // int64 + widthPx?: number; // int64 + heightPx?: number; // int64 + } + export interface SupervisorWriteTerminalResponse { + bytesWritten?: number; // int64 + } +} +declare namespace Paths { + namespace TerminalServiceGet { + namespace Responses { + export type $200 = Definitions.SupervisorTerminal; + export type Default = Definitions.RpcStatus; + } + } + namespace TerminalServiceList { + namespace Responses { + export type $200 = Definitions.SupervisorListTerminalsResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace TerminalServiceListen { + namespace Responses { + /** + * Stream result of supervisorListenTerminalResponse + */ + export interface $200 { + result?: Definitions.SupervisorListenTerminalResponse; + error?: Definitions.RpcStatus; + } + export type Default = Definitions.RpcStatus; + } + } + namespace TerminalServiceShutdown { + namespace Responses { + export type $200 = Definitions.SupervisorShutdownTerminalResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace TerminalServiceWrite { + namespace Responses { + export type $200 = Definitions.SupervisorWriteTerminalResponse; + export type Default = Definitions.RpcStatus; + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/terminal.swagger.json b/components/supervisor-api/typescript-rest/lib/terminal.swagger.json new file mode 100644 index 00000000000000..c6437eaa2a3de2 --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/terminal.swagger.json @@ -0,0 +1,316 @@ +{ + "swagger": "2.0", + "info": { + "title": "terminal.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "TerminalService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/terminal/get/{alias}": { + "get": { + "summary": "Get returns an opened terminal info", + "operationId": "TerminalService_Get", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorTerminal" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "alias", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "TerminalService" + ] + } + }, + "/v1/terminal/list": { + "get": { + "summary": "List lists all open terminals", + "operationId": "TerminalService_List", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorListTerminalsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "tags": [ + "TerminalService" + ] + } + }, + "/v1/terminal/listen/{alias}": { + "get": { + "summary": "Listen listens to a terminal", + "operationId": "TerminalService_Listen", + "responses": { + "200": { + "description": "A successful response.(streaming responses)", + "schema": { + "type": "object", + "properties": { + "result": { + "$ref": "#/definitions/supervisorListenTerminalResponse" + }, + "error": { + "$ref": "#/definitions/rpcStatus" + } + }, + "title": "Stream result of supervisorListenTerminalResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "alias", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "TerminalService" + ] + } + }, + "/v1/terminal/shutdown/{alias}": { + "get": { + "summary": "Shutdown closes a terminal for the given alias, SIGKILL'ing all child processes\nbefore closing the pseudo-terminal.", + "operationId": "TerminalService_Shutdown", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorShutdownTerminalResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "alias", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "TerminalService" + ] + } + }, + "/v1/terminal/write/{alias}": { + "post": { + "summary": "Write writes to a terminal", + "operationId": "TerminalService_Write", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorWriteTerminalResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "alias", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "TerminalService" + ] + } + } + }, + "definitions": { + "protobufAny": { + "type": "object", + "properties": { + "typeUrl": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "supervisorListTerminalsResponse": { + "type": "object", + "properties": { + "terminals": { + "type": "array", + "items": { + "$ref": "#/definitions/supervisorTerminal" + } + } + } + }, + "supervisorListenTerminalResponse": { + "type": "object", + "properties": { + "data": { + "type": "string", + "format": "byte" + }, + "exitCode": { + "type": "integer", + "format": "int32" + }, + "title": { + "type": "string" + } + } + }, + "supervisorOpenTerminalResponse": { + "type": "object", + "properties": { + "terminal": { + "$ref": "#/definitions/supervisorTerminal" + }, + "starterToken": { + "type": "string", + "description": "starter_token can be used to change the terminal size if there are\nmultiple listerns, without having to force your way in." + } + } + }, + "supervisorSetTerminalSizeResponse": { + "type": "object" + }, + "supervisorShutdownTerminalResponse": { + "type": "object" + }, + "supervisorTerminal": { + "type": "object", + "properties": { + "alias": { + "type": "string" + }, + "command": { + "type": "array", + "items": { + "type": "string" + } + }, + "title": { + "type": "string" + }, + "pid": { + "type": "string", + "format": "int64" + }, + "initialWorkdir": { + "type": "string" + }, + "currentWorkdir": { + "type": "string" + }, + "annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "supervisorTerminalSize": { + "type": "object", + "properties": { + "rows": { + "type": "integer", + "format": "int64" + }, + "cols": { + "type": "integer", + "format": "int64" + }, + "widthPx": { + "type": "integer", + "format": "int64" + }, + "heightPx": { + "type": "integer", + "format": "int64" + } + } + }, + "supervisorWriteTerminalResponse": { + "type": "object", + "properties": { + "bytesWritten": { + "type": "integer", + "format": "int64" + } + } + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/token.dt.ts b/components/supervisor-api/typescript-rest/lib/token.dt.ts new file mode 100644 index 00000000000000..ac551d98fb56d0 --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/token.dt.ts @@ -0,0 +1,105 @@ +declare namespace Definitions { + export interface ProtobufAny { + typeUrl?: string; + value?: string; // byte + } + export interface ProvideTokenRequestRegisterProvider { + kind?: string; + } + export interface RpcStatus { + code?: number; // int32 + message?: string; + details?: ProtobufAny[]; + } + export interface SupervisorClearTokenResponse { + } + export interface SupervisorGetTokenRequest { + host?: string; + scope?: string[]; + description?: string; + kind?: string; + } + export interface SupervisorGetTokenResponse { + token?: string; + /** + * * The username of the account associated with the token. + */ + user?: string; + scope?: string[]; + } + export interface SupervisorProvideTokenResponse { + request?: SupervisorGetTokenRequest; + } + export interface SupervisorSetTokenRequest { + host?: string; + scope?: string[]; + token?: string; + expiryDate?: string; // date-time + reuse?: /** + * - REUSE_NEVER: REUSE_NEVER means the token can never be re-used. + * This mode only makes sense when providing a token in response to a request. + * - REUSE_EXACTLY: REUSE_EXACTLY means the token can only be reused when the requested scopes + * exactly match those of the token. + * - REUSE_WHEN_POSSIBLE: REUSE_WHEN_POSSIBLE means the token can be reused when the requested scopes + * are a subset of the token's scopes. + */ + SupervisorTokenReuse; + kind?: string; + } + export interface SupervisorSetTokenResponse { + } + /** + * - REUSE_NEVER: REUSE_NEVER means the token can never be re-used. + * This mode only makes sense when providing a token in response to a request. + * - REUSE_EXACTLY: REUSE_EXACTLY means the token can only be reused when the requested scopes + * exactly match those of the token. + * - REUSE_WHEN_POSSIBLE: REUSE_WHEN_POSSIBLE means the token can be reused when the requested scopes + * are a subset of the token's scopes. + */ + export type SupervisorTokenReuse = "REUSE_NEVER" | "REUSE_EXACTLY" | "REUSE_WHEN_POSSIBLE"; +} +declare namespace Paths { + namespace TokenServiceClearToken { + namespace Responses { + export type $200 = Definitions.SupervisorClearTokenResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace TokenServiceClearToken2 { + namespace Responses { + export type $200 = Definitions.SupervisorClearTokenResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace TokenServiceGetToken { + namespace Responses { + export type $200 = Definitions.SupervisorGetTokenResponse; + export type Default = Definitions.RpcStatus; + } + } + namespace TokenServiceSetToken { + export interface BodyParameters { + body: Parameters.Body; + } + namespace Parameters { + export interface Body { + scope?: string[]; + token?: string; + expiryDate?: string; // date-time + reuse?: /** + * - REUSE_NEVER: REUSE_NEVER means the token can never be re-used. + * This mode only makes sense when providing a token in response to a request. + * - REUSE_EXACTLY: REUSE_EXACTLY means the token can only be reused when the requested scopes + * exactly match those of the token. + * - REUSE_WHEN_POSSIBLE: REUSE_WHEN_POSSIBLE means the token can be reused when the requested scopes + * are a subset of the token's scopes. + */ + Definitions.SupervisorTokenReuse; + } + } + namespace Responses { + export type $200 = Definitions.SupervisorSetTokenResponse; + export type Default = Definitions.RpcStatus; + } + } +} diff --git a/components/supervisor-api/typescript-rest/lib/token.swagger.json b/components/supervisor-api/typescript-rest/lib/token.swagger.json new file mode 100644 index 00000000000000..c810d3893efe05 --- /dev/null +++ b/components/supervisor-api/typescript-rest/lib/token.swagger.json @@ -0,0 +1,348 @@ +{ + "swagger": "2.0", + "info": { + "title": "token.proto", + "version": "version not set" + }, + "tags": [ + { + "name": "TokenService" + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/v1/token/{kind}/clear/all/{all}": { + "delete": { + "operationId": "TokenService_ClearToken2", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorClearTokenResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "kind", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "all", + "in": "path", + "required": true, + "type": "boolean" + }, + { + "name": "value", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "TokenService" + ] + } + }, + "/v1/token/{kind}/{host}": { + "post": { + "operationId": "TokenService_SetToken", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorSetTokenResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "kind", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "host", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "token": { + "type": "string" + }, + "expiryDate": { + "type": "string", + "format": "date-time" + }, + "reuse": { + "$ref": "#/definitions/supervisorTokenReuse" + } + } + } + } + ], + "tags": [ + "TokenService" + ] + } + }, + "/v1/token/{kind}/{host}/{scope}": { + "get": { + "operationId": "TokenService_GetToken", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorGetTokenResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "kind", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "host", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "scope", + "in": "path", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "csv", + "minItems": 1 + }, + { + "name": "description", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "TokenService" + ] + } + }, + "/v1/token/{kind}/{value}": { + "delete": { + "operationId": "TokenService_ClearToken", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/supervisorClearTokenResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "kind", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "value", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "all", + "in": "query", + "required": false, + "type": "boolean" + } + ], + "tags": [ + "TokenService" + ] + } + } + }, + "definitions": { + "ProvideTokenRequestRegisterProvider": { + "type": "object", + "properties": { + "kind": { + "type": "string" + } + } + }, + "protobufAny": { + "type": "object", + "properties": { + "typeUrl": { + "type": "string" + }, + "value": { + "type": "string", + "format": "byte" + } + } + }, + "rpcStatus": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "$ref": "#/definitions/protobufAny" + } + } + } + }, + "supervisorClearTokenResponse": { + "type": "object" + }, + "supervisorGetTokenRequest": { + "type": "object", + "properties": { + "host": { + "type": "string" + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "description": { + "type": "string" + }, + "kind": { + "type": "string" + } + } + }, + "supervisorGetTokenResponse": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "user": { + "type": "string", + "description": "* The username of the account associated with the token." + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "supervisorProvideTokenResponse": { + "type": "object", + "properties": { + "request": { + "$ref": "#/definitions/supervisorGetTokenRequest" + } + } + }, + "supervisorSetTokenRequest": { + "type": "object", + "properties": { + "host": { + "type": "string" + }, + "scope": { + "type": "array", + "items": { + "type": "string" + } + }, + "token": { + "type": "string" + }, + "expiryDate": { + "type": "string", + "format": "date-time" + }, + "reuse": { + "$ref": "#/definitions/supervisorTokenReuse" + }, + "kind": { + "type": "string" + } + } + }, + "supervisorSetTokenResponse": { + "type": "object" + }, + "supervisorTokenReuse": { + "type": "string", + "enum": [ + "REUSE_NEVER", + "REUSE_EXACTLY", + "REUSE_WHEN_POSSIBLE" + ], + "default": "REUSE_NEVER", + "description": " - REUSE_NEVER: REUSE_NEVER means the token can never be re-used.\nThis mode only makes sense when providing a token in response to a request.\n - REUSE_EXACTLY: REUSE_EXACTLY means the token can only be reused when the requested scopes\nexactly match those of the token.\n - REUSE_WHEN_POSSIBLE: REUSE_WHEN_POSSIBLE means the token can be reused when the requested scopes\nare a subset of the token's scopes." + } + } +} diff --git a/yarn.lock b/yarn.lock index bc664e01ca7c61..41af0d14d15994 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8274,7 +8274,7 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-fetch@3.0.6, cross-fetch@^3.0.4, cross-fetch@^3.0.5, cross-fetch@^3.0.6: +cross-fetch@3.0.6, cross-fetch@^3.0.4, cross-fetch@^3.0.6: version "3.0.6" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== @@ -9220,21 +9220,6 @@ dotenv@^4.0.0: version "4.0.0" resolved "http://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" -dtsgenerator@~3.3.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/dtsgenerator/-/dtsgenerator-3.3.1.tgz#00ab61551e646569043794137f709cc068f3347c" - integrity sha512-agBZATxyr+iUs/THNqJUxVzSrc4VK6c+U4kI5/MhSUL+vcvUi1eiZWpOHQz5QVz4GgQshlRf2HqBn/FzX3sShA== - dependencies: - commander "^6.0.0" - cross-fetch "^3.0.5" - debug "^4.1.1" - glob "^7.1.6" - https-proxy-agent "^5.0.0" - js-yaml "^3.14.0" - mkdirp "^1.0.4" - tslib "^2.0.0" - typescript "^3.9.7" - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -20821,7 +20806,7 @@ typescript-parser@^2.6.1: tslib "^1.9.3" typescript "^3.0.3" -typescript@^3.0.3, typescript@^3.9.7: +typescript@^3.0.3: version "3.9.7" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== From 5436968778c8d0a3897002a883d5d45e8e8753f3 Mon Sep 17 00:00:00 2001 From: Christian Weichel Date: Tue, 15 Jun 2021 08:02:59 +0000 Subject: [PATCH 2/2] [initializer] Add digest support and unit tests to the new FileDownloadInitializer --- components/content-service/go.mod | 1 + components/content-service/go.sum | 1 + .../pkg/initializer/download.go | 103 +++++++----- .../pkg/initializer/download_test.go | 147 ++++++++++++++++++ .../pkg/initializer/initializer.go | 21 ++- .../src/workspace/image-source-provider.ts | 6 +- .../server/src/workspace/workspace-starter.ts | 4 +- 7 files changed, 231 insertions(+), 52 deletions(-) create mode 100644 components/content-service/pkg/initializer/download_test.go diff --git a/components/content-service/go.mod b/components/content-service/go.mod index 46d93b687ac0b4..5ecb1b814fa505 100644 --- a/components/content-service/go.mod +++ b/components/content-service/go.mod @@ -23,6 +23,7 @@ require ( github.com/prometheus/client_golang v1.9.0 github.com/spf13/cobra v1.1.1 golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 google.golang.org/api v0.46.0 google.golang.org/grpc v1.37.0 diff --git a/components/content-service/go.sum b/components/content-service/go.sum index dcb4cd87dc02e0..b907352712920f 100644 --- a/components/content-service/go.sum +++ b/components/content-service/go.sum @@ -899,6 +899,7 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/components/content-service/pkg/initializer/download.go b/components/content-service/pkg/initializer/download.go index 4bf3d7580097d8..3fb649390abb82 100644 --- a/components/content-service/pkg/initializer/download.go +++ b/components/content-service/pkg/initializer/download.go @@ -7,7 +7,6 @@ package initializer import ( "context" "io" - "io/ioutil" "net/http" "os" "path/filepath" @@ -17,83 +16,111 @@ import ( "github.com/gitpod-io/gitpod/common-go/tracing" csapi "github.com/gitpod-io/gitpod/content-service/api" "github.com/gitpod-io/gitpod/content-service/pkg/archive" + "github.com/opencontainers/go-digest" "github.com/opentracing/opentracing-go" + "golang.org/x/sync/errgroup" "golang.org/x/xerrors" ) -type FileInfo struct { - url string - // filePath is relative to the FileDownloadInitializer's TargetLocation, e.g. if TargetLocation is in `/workspace/myrepo` - // a filePath of `foobar/file` would produce a file in `/workspace/myrepo/foobar/file`. - // filePath must include the filename. The FileDownloadInitializer will create any parent directories +type fileInfo struct { + URL string + + // Path is relative to the FileDownloadInitializer's TargetLocation, e.g. if TargetLocation is in `/workspace/myrepo` + // a Path of `foobar/file` would produce a file in `/workspace/myrepo/foobar/file`. + // Path must include the filename. The FileDownloadInitializer will create any parent directories // necessary to place the file. - filePath string - // digest is a hash of the file content in the OCI digest format (see https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests). + Path string + + // Digest is a hash of the file content in the OCI Digest format (see https://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests). // This information is used to compute subsequent // content versions, and to validate the file content was downloaded correctly. - digest string + Digest digest.Digest } -type FileDownloadInitializer struct { - FilesInfos []FileInfo +type fileDownloadInitializer struct { + FilesInfos []fileInfo TargetLocation string + HTTPClient *http.Client + RetryTimeout time.Duration } // Run initializes the workspace -func (ws *FileDownloadInitializer) Run(ctx context.Context, mappings []archive.IDMapping) (src csapi.WorkspaceInitSource, err error) { +func (ws *fileDownloadInitializer) Run(ctx context.Context, mappings []archive.IDMapping) (src csapi.WorkspaceInitSource, err error) { span, ctx := opentracing.StartSpanFromContext(ctx, "FileDownloadInitializer.Run") defer tracing.FinishSpan(span, &err) for _, info := range ws.FilesInfos { - contents, err := downloadFile(ctx, info.url) - if err != nil { - tracing.LogError(span, xerrors.Errorf("cannot download file '%s' from '%s': %w", info.filePath, info.url, err)) - } - - fullPath := filepath.Join(ws.TargetLocation, info.filePath) - err = os.MkdirAll(filepath.Dir(fullPath), 0755) - if err != nil { - tracing.LogError(span, xerrors.Errorf("cannot mkdir %s: %w", filepath.Dir(fullPath), err)) - } - err = ioutil.WriteFile(fullPath, contents, 0755) + err := ws.downloadFile(ctx, info) if err != nil { - tracing.LogError(span, xerrors.Errorf("cannot write %s: %w", fullPath, err)) + tracing.LogError(span, xerrors.Errorf("cannot download file '%s' from '%s': %w", info.Path, info.URL, err)) + return src, err } } - return src, nil + return csapi.WorkspaceInitFromOther, nil } -func downloadFile(ctx context.Context, url string) (content []byte, err error) { +func (ws *fileDownloadInitializer) downloadFile(ctx context.Context, info fileInfo) (err error) { //nolint:ineffassign span, ctx := opentracing.StartSpanFromContext(ctx, "downloadFile") defer tracing.FinishSpan(span, &err) - span.LogKV("url", url) + span.LogKV("url", info.URL) - dl := func() (content []byte, err error) { - req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + fn := filepath.Join(ws.TargetLocation, info.Path) + err = os.MkdirAll(filepath.Dir(fn), 0755) + if err != nil { + tracing.LogError(span, xerrors.Errorf("cannot mkdir %s: %w", filepath.Dir(fn), err)) + } + + fd, err := os.OpenFile(fn, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return err + } + + dl := func() (err error) { + req, err := http.NewRequestWithContext(ctx, "GET", info.URL, nil) if err != nil { - return nil, err + return err } _ = opentracing.GlobalTracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header)) - resp, err := http.DefaultClient.Do(req) + resp, err := ws.HTTPClient.Do(req) if err != nil { - return nil, err + return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, xerrors.Errorf("non-OK OTS response: %s", resp.Status) + return xerrors.Errorf("non-OK download response: %s", resp.Status) } - return io.ReadAll(resp.Body) + pr, pw := io.Pipe() + body := io.TeeReader(resp.Body, pw) + + eg, _ := errgroup.WithContext(ctx) + eg.Go(func() error { + _, err = io.Copy(fd, body) + pw.Close() + return err + }) + eg.Go(func() error { + dgst, err := digest.FromReader(pr) + if err != nil { + return err + } + if dgst != info.Digest { + return xerrors.Errorf("digest mismatch") + } + return nil + }) + + return eg.Wait() } for i := 0; i < otsDownloadAttempts; i++ { span.LogKV("attempt", i) if i > 0 { - time.Sleep(time.Second) + time.Sleep(ws.RetryTimeout) } - content, err = dl() + err = dl() if err == context.Canceled || err == context.DeadlineExceeded { return } @@ -103,8 +130,8 @@ func downloadFile(ctx context.Context, url string) (content []byte, err error) { log.WithError(err).WithField("attempt", i).Warn("cannot download additional content files") } if err != nil { - return nil, err + return err } - return content, nil + return nil } diff --git a/components/content-service/pkg/initializer/download_test.go b/components/content-service/pkg/initializer/download_test.go new file mode 100644 index 00000000000000..ff8404a17a4414 --- /dev/null +++ b/components/content-service/pkg/initializer/download_test.go @@ -0,0 +1,147 @@ +// Copyright (c) 2021 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package initializer + +import ( + "bytes" + "context" + "io" + "net/http" + "os" + "testing" + + "github.com/gitpod-io/gitpod/content-service/api" + "github.com/opencontainers/go-digest" +) + +// RoundTripFunc . +type RoundTripFunc func(req *http.Request) *http.Response + +// RoundTrip . +func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req), nil +} + +func TestFileDownloadInitializer(t *testing.T) { + const defaultContent = "hello world" + + type serverSideFile struct { + Path string + Content string + } + tests := []struct { + Name string + Files []fileInfo + ServerSide []serverSideFile + ExpectedError string + }{ + { + Name: "happy path", + Files: []fileInfo{ + { + URL: "/file1", + Path: "/level/file1", + Digest: digest.FromString(defaultContent), + }, + // duplication is intentional + { + URL: "/file1", + Path: "/level/file1", + Digest: digest.FromString(defaultContent), + }, + { + URL: "/file2", + Path: "/level/file2", + Digest: digest.FromString(defaultContent), + }, + }, + ServerSide: []serverSideFile{ + {Path: "/file1", Content: defaultContent}, + {Path: "/file2", Content: defaultContent}, + }, + }, + { + Name: "digest mismatch", + Files: []fileInfo{ + { + URL: "/file1", + Path: "/level/file1", + Digest: digest.FromString(defaultContent + "foobar"), + }, + }, + ServerSide: []serverSideFile{ + {Path: "/file1", Content: defaultContent}, + }, + ExpectedError: "digest mismatch", + }, + { + Name: "file not found", + Files: []fileInfo{ + { + URL: "/file1", + Path: "/level/file1", + Digest: digest.FromString(defaultContent + "foobar"), + }, + }, + ExpectedError: "non-OK download response: Not Found", + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + tmpdir, err := os.MkdirTemp("", "TestFileDownloadInitializer*") + if err != nil { + t.Fatal("cannot create tempdir", err) + } + defer os.RemoveAll(tmpdir) + + client := &http.Client{ + Transport: RoundTripFunc(func(req *http.Request) *http.Response { + for _, f := range test.ServerSide { + if f.Path != req.URL.Path { + continue + } + + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader([]byte(f.Content))), + Header: make(http.Header), + } + } + + return &http.Response{ + Status: http.StatusText(http.StatusNotFound), + StatusCode: http.StatusNotFound, + Header: make(http.Header), + } + }), + } + + req := &api.FileDownloadInitializer{} + for _, f := range test.Files { + req.Files = append(req.Files, &api.FileDownloadInitializer_FileInfo{ + Url: "http://foobar" + f.URL, + FilePath: f.Path, + Digest: string(f.Digest), + }) + } + + initializer, err := newFileDownloadInitializer(tmpdir, req) + if err != nil { + t.Fatal(err) + } + initializer.HTTPClient = client + initializer.RetryTimeout = 0 + + src, err := initializer.Run(context.Background(), nil) + if err == nil && src != api.WorkspaceInitFromOther { + t.Error("initializer returned wrong content init source") + } + if err != nil && err.Error() != test.ExpectedError { + t.Fatalf("unexpected error: %v", err) + } + }) + } +} diff --git a/components/content-service/pkg/initializer/initializer.go b/components/content-service/pkg/initializer/initializer.go index f1fae384c65b16..168d79264d62ff 100644 --- a/components/content-service/pkg/initializer/initializer.go +++ b/components/content-service/pkg/initializer/initializer.go @@ -15,6 +15,7 @@ import ( "strings" "time" + "github.com/opencontainers/go-digest" "github.com/opentracing/opentracing-go" "golang.org/x/xerrors" "google.golang.org/grpc/codes" @@ -145,18 +146,24 @@ func NewFromRequest(ctx context.Context, loc string, rs storage.DirectDownloader } // newFileDownloadInitializer creates a download initializer for a request -func newFileDownloadInitializer(loc string, req *csapi.FileDownloadInitializer) (*FileDownloadInitializer, error) { - fileInfos := make([]FileInfo, len(req.Files)) +func newFileDownloadInitializer(loc string, req *csapi.FileDownloadInitializer) (*fileDownloadInitializer, error) { + fileInfos := make([]fileInfo, len(req.Files)) for i, f := range req.Files { - fileInfos[i] = FileInfo{ - url: f.Url, - filePath: f.FilePath, - digest: f.Digest, + dgst, err := digest.Parse(f.Digest) + if err != nil { + return nil, xerrors.Errorf("invalid digest %s: %w", f.Digest, err) + } + fileInfos[i] = fileInfo{ + URL: f.Url, + Path: f.FilePath, + Digest: dgst, } } - initializer := &FileDownloadInitializer{ + initializer := &fileDownloadInitializer{ FilesInfos: fileInfos, TargetLocation: filepath.Join(loc, req.TargetLocation), + HTTPClient: http.DefaultClient, + RetryTimeout: 1 * time.Second, } return initializer, nil } diff --git a/components/server/src/workspace/image-source-provider.ts b/components/server/src/workspace/image-source-provider.ts index 64d588e2f51321..d310786f849e53 100644 --- a/components/server/src/workspace/image-source-provider.ts +++ b/components/server/src/workspace/image-source-provider.ts @@ -9,7 +9,7 @@ import { ImageBuilderClientProvider, ResolveBaseImageRequest, BuildRegistryAuthT import { HostContextProvider } from "../auth/host-context-provider"; import { TraceContext } from "@gitpod/gitpod-protocol/lib/util/tracing"; import { CommitContext, WorkspaceImageSource, WorkspaceConfig, WorkspaceImageSourceReference, WorkspaceImageSourceDocker, ImageConfigFile, ExternalImageConfigFile, User, AdditionalContentContext } from "@gitpod/gitpod-protocol"; -import { createHmac } from 'crypto'; +import { createHash } from 'crypto'; @injectable() export class ImageSourceProvider { @@ -93,9 +93,7 @@ export class ImageSourceProvider { } protected getContentSHA(contents: string): string { - return createHmac('sha256', '') - .update(contents) - .digest('hex'); + return createHash('sha256').update(contents).digest('hex'); } diff --git a/components/server/src/workspace/workspace-starter.ts b/components/server/src/workspace/workspace-starter.ts index 5de7268cd45f0c..a071ee12f7f108 100644 --- a/components/server/src/workspace/workspace-starter.ts +++ b/components/server/src/workspace/workspace-starter.ts @@ -759,9 +759,7 @@ export class WorkspaceStarter { const additionalInit = new FileDownloadInitializer(); const getDigest = (contents: string) => { - return 'sha256:'+crypto.createHmac('sha256', '') - .update(contents) - .digest('hex'); + return 'sha256:'+crypto.createHash('sha256').update(contents).digest('hex'); } const tokenExpirationTime = new Date();