diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 50b70fa5a..4397097de 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -162,7 +162,7 @@ For faster development, you can also build and run Botkube outside K8s cluster. 1. Start fake plugins server to serve binaries from [`dist`](dist) folder: ```bash - go run test/helpers/plugin_server.go + go run hack/target/serve-plugins/main.go ``` > **Note** diff --git a/hack/target/serve-plugins/main.go b/hack/target/serve-plugins/main.go new file mode 100644 index 000000000..03760114d --- /dev/null +++ b/hack/target/serve-plugins/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "log" + "os" + "path/filepath" + "strconv" + + "github.com/kubeshop/botkube/pkg/loggerx" + "github.com/kubeshop/botkube/pkg/plugin" +) + +func main() { + pluginsDir := flag.String("plugins-dir", getEnv("PLUGINS_DIR", "plugin-dist"), "Plugins directory") + host := flag.String("host", getEnv("PLUGIN_SERVER_HOST", "http://localhost"), "Local server host") + port := flag.String("port", getEnv("PLUGIN_SERVER_PORT", "3010"), "Local server port") + flag.Parse() + + dir, err := os.Getwd() + loggerx.ExitOnError(err, "while getting current directory") + + portInt, err := strconv.Atoi(*port) + loggerx.ExitOnError(err, "while casting server port value") + + binDir := filepath.Join(dir, *pluginsDir) + indexEndpoint, startServerFn := plugin.NewStaticPluginServer(plugin.StaticPluginServerConfig{ + BinariesDirectory: binDir, + Host: *host, + Port: portInt, + }) + + log.Printf("Service plugin binaries from %s\n", binDir) + log.Printf("Botkube repository index URL: %s", indexEndpoint) + err = startServerFn() + loggerx.ExitOnError(err, "while starting server") +} + +func getEnv(key, defaultValue string) string { + value, exists := os.LookupEnv(key) + if !exists { + return defaultValue + } + return value +} diff --git a/pkg/api/executor/executor.pb.go b/pkg/api/executor/executor.pb.go index 1f3d3ce78..efee3097c 100644 --- a/pkg/api/executor/executor.pb.go +++ b/pkg/api/executor/executor.pb.go @@ -21,12 +21,15 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// Config holds the Executor configuration. type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields // rawYAML contains the Executor configuration in YAML definitions. + // Configuration data is unique per executor. + // Botkube related configuration details are stored in ExecuteContext instead. RawYAML []byte `protobuf:"bytes,1,opt,name=rawYAML,proto3" json:"rawYAML,omitempty"` } @@ -140,10 +143,23 @@ type ExecuteContext struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - IsInteractivitySupported bool `protobuf:"varint,1,opt,name=isInteractivitySupported,proto3" json:"isInteractivitySupported,omitempty"` - SlackState []byte `protobuf:"bytes,2,opt,name=slackState,proto3" json:"slackState,omitempty"` - KubeConfig []byte `protobuf:"bytes,3,opt,name=kubeConfig,proto3" json:"kubeConfig,omitempty"` - Message *MessageContext `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + // isInteractivitySupported is set to true only if communication platform supports interactive Messages + // with buttons, select menus, etc. If set to false, you should send only text based messages. + IsInteractivitySupported bool `protobuf:"varint,1,opt,name=isInteractivitySupported,proto3" json:"isInteractivitySupported,omitempty"` + // slackState represents modal state. It's available only if: + // - IsInteractivitySupported is set to true, + // - and interactive actions were used in the response Message. + // + // This is an alpha feature and may change in the future. + // Most likely, it will be generalized to support all communication platforms. + SlackState []byte `protobuf:"bytes,2,opt,name=slackState,proto3" json:"slackState,omitempty"` + // kubeConfig is the slice of byte representation of kubeconfig file content. + // it is available only if context.rbac is configured for a given plugins. Otherwise, it is empty. + KubeConfig []byte `protobuf:"bytes,3,opt,name=kubeConfig,proto3" json:"kubeConfig,omitempty"` + // message holds message details that triggered a given Executor. + Message *MessageContext `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"` + // incomingWebhook holds details about Botkube built-in incoming webhook configuration. + IncomingWebhook *IncomingWebhookContext `protobuf:"bytes,5,opt,name=incomingWebhook,proto3" json:"incomingWebhook,omitempty"` } func (x *ExecuteContext) Reset() { @@ -206,20 +222,82 @@ func (x *ExecuteContext) GetMessage() *MessageContext { return nil } +func (x *ExecuteContext) GetIncomingWebhook() *IncomingWebhookContext { + if x != nil { + return x.IncomingWebhook + } + return nil +} + +// IncomingWebhookContext holds information about the built-in incoming webhook that +// allows triggering HandleExternalRequest on a given source. +type IncomingWebhookContext struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + BaseSourceURL string `protobuf:"bytes,1,opt,name=baseSourceURL,proto3" json:"baseSourceURL,omitempty"` +} + +func (x *IncomingWebhookContext) Reset() { + *x = IncomingWebhookContext{} + if protoimpl.UnsafeEnabled { + mi := &file_executor_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IncomingWebhookContext) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IncomingWebhookContext) ProtoMessage() {} + +func (x *IncomingWebhookContext) ProtoReflect() protoreflect.Message { + mi := &file_executor_proto_msgTypes[3] + 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 IncomingWebhookContext.ProtoReflect.Descriptor instead. +func (*IncomingWebhookContext) Descriptor() ([]byte, []int) { + return file_executor_proto_rawDescGZIP(), []int{3} +} + +func (x *IncomingWebhookContext) GetBaseSourceURL() string { + if x != nil { + return x.BaseSourceURL + } + return "" +} + type MessageContext struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` - Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` - User *UserContext `protobuf:"bytes,3,opt,name=user,proto3" json:"user,omitempty"` + // text is the text of the message in the raw format. + Text string `protobuf:"bytes,1,opt,name=text,proto3" json:"text,omitempty"` + // url is the URL of the message. Can be used to open the message in a browser. + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + // parentActivityId is the ID of the parent activity. If user follows with messages in a thread, this ID represents the originating message that started that thread. + // Otherwise, it's the ID of the initial message. + ParentActivityId string `protobuf:"bytes,3,opt,name=parentActivityId,proto3" json:"parentActivityId,omitempty"` + // user holds user details that wrote a given message. + User *UserContext `protobuf:"bytes,4,opt,name=user,proto3" json:"user,omitempty"` } func (x *MessageContext) Reset() { *x = MessageContext{} if protoimpl.UnsafeEnabled { - mi := &file_executor_proto_msgTypes[3] + mi := &file_executor_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -232,7 +310,7 @@ func (x *MessageContext) String() string { func (*MessageContext) ProtoMessage() {} func (x *MessageContext) ProtoReflect() protoreflect.Message { - mi := &file_executor_proto_msgTypes[3] + mi := &file_executor_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -245,7 +323,7 @@ func (x *MessageContext) ProtoReflect() protoreflect.Message { // Deprecated: Use MessageContext.ProtoReflect.Descriptor instead. func (*MessageContext) Descriptor() ([]byte, []int) { - return file_executor_proto_rawDescGZIP(), []int{3} + return file_executor_proto_rawDescGZIP(), []int{4} } func (x *MessageContext) GetText() string { @@ -262,6 +340,13 @@ func (x *MessageContext) GetUrl() string { return "" } +func (x *MessageContext) GetParentActivityId() string { + if x != nil { + return x.ParentActivityId + } + return "" +} + func (x *MessageContext) GetUser() *UserContext { if x != nil { return x.User @@ -274,14 +359,16 @@ type UserContext struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Mention string `protobuf:"bytes,1,opt,name=mention,proto3" json:"mention,omitempty"` + // mention represents a user platforms specific mention of the user. + Mention string `protobuf:"bytes,1,opt,name=mention,proto3" json:"mention,omitempty"` + // displayName represents user display name. It can be empty. DisplayName string `protobuf:"bytes,2,opt,name=displayName,proto3" json:"displayName,omitempty"` } func (x *UserContext) Reset() { *x = UserContext{} if protoimpl.UnsafeEnabled { - mi := &file_executor_proto_msgTypes[4] + mi := &file_executor_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -294,7 +381,7 @@ func (x *UserContext) String() string { func (*UserContext) ProtoMessage() {} func (x *UserContext) ProtoReflect() protoreflect.Message { - mi := &file_executor_proto_msgTypes[4] + mi := &file_executor_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -307,7 +394,7 @@ func (x *UserContext) ProtoReflect() protoreflect.Message { // Deprecated: Use UserContext.ProtoReflect.Descriptor instead. func (*UserContext) Descriptor() ([]byte, []int) { - return file_executor_proto_rawDescGZIP(), []int{4} + return file_executor_proto_rawDescGZIP(), []int{5} } func (x *UserContext) GetMention() string { @@ -329,14 +416,23 @@ type ExecuteResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Message []byte `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + // message represents the output of processing a given input command. + // You can construct a complex message or just use one of our helper functions: + // - api.NewCodeBlockMessage("body", true) + // - api.NewPlaintextMessage("body", true) + Message []byte `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + // messages holds a collection of messages that should be dispatched to the user in the context of a given command execution. + // To avoid spamming, you can specify max 15 messages. + // Limitations: + // - It's available only for SocketSlack. In the future, it may be adopted across other platforms. + // - Interactive message filtering is not available. (https://docs.botkube.io/usage/interactive-output-filtering) Messages [][]byte `protobuf:"bytes,2,rep,name=messages,proto3" json:"messages,omitempty"` } func (x *ExecuteResponse) Reset() { *x = ExecuteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_executor_proto_msgTypes[5] + mi := &file_executor_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -349,7 +445,7 @@ func (x *ExecuteResponse) String() string { func (*ExecuteResponse) ProtoMessage() {} func (x *ExecuteResponse) ProtoReflect() protoreflect.Message { - mi := &file_executor_proto_msgTypes[5] + mi := &file_executor_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -362,7 +458,7 @@ func (x *ExecuteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteResponse.ProtoReflect.Descriptor instead. func (*ExecuteResponse) Descriptor() ([]byte, []int) { - return file_executor_proto_rawDescGZIP(), []int{5} + return file_executor_proto_rawDescGZIP(), []int{6} } func (x *ExecuteResponse) GetMessage() []byte { @@ -379,6 +475,7 @@ func (x *ExecuteResponse) GetMessages() [][]byte { return nil } +// MetadataResponse represents metadata of a given plugin. Data is used to generate a plugin index file. type MetadataResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -401,7 +498,7 @@ type MetadataResponse struct { func (x *MetadataResponse) Reset() { *x = MetadataResponse{} if protoimpl.UnsafeEnabled { - mi := &file_executor_proto_msgTypes[6] + mi := &file_executor_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -414,7 +511,7 @@ func (x *MetadataResponse) String() string { func (*MetadataResponse) ProtoMessage() {} func (x *MetadataResponse) ProtoReflect() protoreflect.Message { - mi := &file_executor_proto_msgTypes[6] + mi := &file_executor_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -427,7 +524,7 @@ func (x *MetadataResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MetadataResponse.ProtoReflect.Descriptor instead. func (*MetadataResponse) Descriptor() ([]byte, []int) { - return file_executor_proto_rawDescGZIP(), []int{6} + return file_executor_proto_rawDescGZIP(), []int{7} } func (x *MetadataResponse) GetVersion() string { @@ -472,6 +569,7 @@ func (x *MetadataResponse) GetRecommended() bool { return false } +// JSONSchema represents a JSON schema of a given plugin configuration. type JSONSchema struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -486,7 +584,7 @@ type JSONSchema struct { func (x *JSONSchema) Reset() { *x = JSONSchema{} if protoimpl.UnsafeEnabled { - mi := &file_executor_proto_msgTypes[7] + mi := &file_executor_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -499,7 +597,7 @@ func (x *JSONSchema) String() string { func (*JSONSchema) ProtoMessage() {} func (x *JSONSchema) ProtoReflect() protoreflect.Message { - mi := &file_executor_proto_msgTypes[7] + mi := &file_executor_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -512,7 +610,7 @@ func (x *JSONSchema) ProtoReflect() protoreflect.Message { // Deprecated: Use JSONSchema.ProtoReflect.Descriptor instead. func (*JSONSchema) Descriptor() ([]byte, []int) { - return file_executor_proto_rawDescGZIP(), []int{7} + return file_executor_proto_rawDescGZIP(), []int{8} } func (x *JSONSchema) GetValue() string { @@ -529,6 +627,7 @@ func (x *JSONSchema) GetRefUrl() string { return "" } +// Dependency represents a dependency of a given plugin. All binaries are downloaded before the plugin is started. type Dependency struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -541,7 +640,7 @@ type Dependency struct { func (x *Dependency) Reset() { *x = Dependency{} if protoimpl.UnsafeEnabled { - mi := &file_executor_proto_msgTypes[8] + mi := &file_executor_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -554,7 +653,7 @@ func (x *Dependency) String() string { func (*Dependency) ProtoMessage() {} func (x *Dependency) ProtoReflect() protoreflect.Message { - mi := &file_executor_proto_msgTypes[8] + mi := &file_executor_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -567,7 +666,7 @@ func (x *Dependency) ProtoReflect() protoreflect.Message { // Deprecated: Use Dependency.ProtoReflect.Descriptor instead. func (*Dependency) Descriptor() ([]byte, []int) { - return file_executor_proto_rawDescGZIP(), []int{8} + return file_executor_proto_rawDescGZIP(), []int{9} } func (x *Dependency) GetUrls() map[string]string { @@ -577,18 +676,23 @@ func (x *Dependency) GetUrls() map[string]string { return nil } +// HelpResponse represents help of a given plugin. type HelpResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + // help is the help of a given plugin. + // You can construct a complex message with buttons etc, or just use one of our helper functions: + // - api.NewCodeBlockMessage("body", true) + // - api.NewPlaintextMessage("body", true) Help []byte `protobuf:"bytes,1,opt,name=help,proto3" json:"help,omitempty"` } func (x *HelpResponse) Reset() { *x = HelpResponse{} if protoimpl.UnsafeEnabled { - mi := &file_executor_proto_msgTypes[9] + mi := &file_executor_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -601,7 +705,7 @@ func (x *HelpResponse) String() string { func (*HelpResponse) ProtoMessage() {} func (x *HelpResponse) ProtoReflect() protoreflect.Message { - mi := &file_executor_proto_msgTypes[9] + mi := &file_executor_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -614,7 +718,7 @@ func (x *HelpResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HelpResponse.ProtoReflect.Descriptor instead. func (*HelpResponse) Descriptor() ([]byte, []int) { - return file_executor_proto_rawDescGZIP(), []int{9} + return file_executor_proto_rawDescGZIP(), []int{10} } func (x *HelpResponse) GetHelp() []byte { @@ -641,7 +745,7 @@ var file_executor_proto_rawDesc = []byte{ 0x66, 0x69, 0x67, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, - 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0xc0, 0x01, 0x0a, 0x0e, 0x45, 0x78, 0x65, + 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x22, 0x8c, 0x02, 0x0a, 0x0e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x3a, 0x0a, 0x18, 0x69, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x61, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x69, @@ -653,75 +757,86 @@ var file_executor_proto_rawDesc = []byte{ 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, - 0x78, 0x74, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x61, 0x0a, 0x0e, 0x4d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, - 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x75, 0x72, 0x6c, 0x12, 0x29, 0x0a, 0x04, 0x75, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x55, 0x73, 0x65, - 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x49, - 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x18, 0x0a, - 0x07, 0x6d, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6d, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, - 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x47, 0x0a, 0x0f, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, - 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x73, 0x22, 0xfd, 0x02, 0x0a, 0x10, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x0b, 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, - 0x6d, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, - 0x74, 0x6f, 0x72, 0x2e, 0x4a, 0x53, 0x4f, 0x4e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x0a, - 0x6a, 0x73, 0x6f, 0x6e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x50, 0x0a, 0x0c, 0x64, 0x65, - 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x2c, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x65, 0x70, - 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, - 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, - 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, - 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, - 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x63, - 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, - 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x1a, 0x55, 0x0a, 0x11, 0x44, - 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x44, 0x65, 0x70, - 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x22, 0x3b, 0x0a, 0x0a, 0x4a, 0x53, 0x4f, 0x4e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x5f, 0x75, 0x72, - 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x66, 0x55, 0x72, 0x6c, 0x22, - 0x79, 0x0a, 0x0a, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x32, 0x0a, - 0x04, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x65, 0x78, - 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, - 0x79, 0x2e, 0x55, 0x72, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x75, 0x72, 0x6c, - 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x55, 0x72, 0x6c, 0x73, 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, 0x22, 0x0a, 0x0c, 0x48, 0x65, - 0x6c, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, - 0x6c, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x32, 0xc8, - 0x01, 0x0a, 0x08, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x12, 0x40, 0x0a, 0x07, 0x45, - 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, 0x18, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, - 0x72, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x19, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x45, 0x78, 0x65, 0x63, - 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, - 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x1a, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, - 0x38, 0x0a, 0x04, 0x48, 0x65, 0x6c, 0x70, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x16, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x70, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x12, 0x5a, 0x10, 0x70, 0x6b, 0x67, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x78, 0x74, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x4a, 0x0a, 0x0f, 0x69, + 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x49, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, + 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x22, 0x3e, 0x0a, 0x16, 0x49, 0x6e, 0x63, 0x6f, 0x6d, + 0x69, 0x6e, 0x67, 0x57, 0x65, 0x62, 0x68, 0x6f, 0x6f, 0x6b, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, + 0x52, 0x4c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x55, 0x52, 0x4c, 0x22, 0x8d, 0x01, 0x0a, 0x0e, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, + 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x12, 0x2a, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, + 0x74, 0x79, 0x49, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x41, 0x63, 0x74, 0x69, 0x76, 0x69, 0x74, 0x79, 0x49, 0x64, 0x12, 0x29, 0x0a, 0x04, + 0x75, 0x73, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, + 0x74, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x22, 0x49, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x43, + 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x6e, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, + 0x6d, 0x65, 0x22, 0x47, 0x0a, 0x0f, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, + 0x1a, 0x0a, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x08, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x22, 0xfd, 0x02, 0x0a, 0x10, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x35, 0x0a, 0x0b, + 0x6a, 0x73, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x14, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x4a, 0x53, 0x4f, + 0x4e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x0a, 0x6a, 0x73, 0x6f, 0x6e, 0x53, 0x63, 0x68, + 0x65, 0x6d, 0x61, 0x12, 0x50, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, + 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, + 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, + 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x10, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, + 0x72, 0x6c, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x64, 0x65, + 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x6d, 0x65, + 0x6e, 0x64, 0x65, 0x64, 0x1a, 0x55, 0x0a, 0x11, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, + 0x63, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, 0x0a, 0x0a, 0x4a, + 0x53, 0x4f, 0x4e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x72, 0x65, 0x66, 0x55, 0x72, 0x6c, 0x22, 0x79, 0x0a, 0x0a, 0x44, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x32, 0x0a, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, + 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x2e, 0x55, 0x72, 0x6c, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x75, 0x72, 0x6c, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x55, 0x72, + 0x6c, 0x73, 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, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x68, 0x65, 0x6c, 0x70, 0x32, 0xc8, 0x01, 0x0a, 0x08, 0x45, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x12, 0x40, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x12, + 0x18, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x6f, 0x72, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x04, 0x48, 0x65, 0x6c, 0x70, + 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x65, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x65, 0x6c, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0x12, 0x5a, 0x10, 0x70, 0x6b, 0x67, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x6f, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -736,42 +851,44 @@ func file_executor_proto_rawDescGZIP() []byte { return file_executor_proto_rawDescData } -var file_executor_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_executor_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_executor_proto_goTypes = []interface{}{ - (*Config)(nil), // 0: executor.Config - (*ExecuteRequest)(nil), // 1: executor.ExecuteRequest - (*ExecuteContext)(nil), // 2: executor.ExecuteContext - (*MessageContext)(nil), // 3: executor.MessageContext - (*UserContext)(nil), // 4: executor.UserContext - (*ExecuteResponse)(nil), // 5: executor.ExecuteResponse - (*MetadataResponse)(nil), // 6: executor.MetadataResponse - (*JSONSchema)(nil), // 7: executor.JSONSchema - (*Dependency)(nil), // 8: executor.Dependency - (*HelpResponse)(nil), // 9: executor.HelpResponse - nil, // 10: executor.MetadataResponse.DependenciesEntry - nil, // 11: executor.Dependency.UrlsEntry - (*emptypb.Empty)(nil), // 12: google.protobuf.Empty + (*Config)(nil), // 0: executor.Config + (*ExecuteRequest)(nil), // 1: executor.ExecuteRequest + (*ExecuteContext)(nil), // 2: executor.ExecuteContext + (*IncomingWebhookContext)(nil), // 3: executor.IncomingWebhookContext + (*MessageContext)(nil), // 4: executor.MessageContext + (*UserContext)(nil), // 5: executor.UserContext + (*ExecuteResponse)(nil), // 6: executor.ExecuteResponse + (*MetadataResponse)(nil), // 7: executor.MetadataResponse + (*JSONSchema)(nil), // 8: executor.JSONSchema + (*Dependency)(nil), // 9: executor.Dependency + (*HelpResponse)(nil), // 10: executor.HelpResponse + nil, // 11: executor.MetadataResponse.DependenciesEntry + nil, // 12: executor.Dependency.UrlsEntry + (*emptypb.Empty)(nil), // 13: google.protobuf.Empty } var file_executor_proto_depIdxs = []int32{ 0, // 0: executor.ExecuteRequest.configs:type_name -> executor.Config 2, // 1: executor.ExecuteRequest.context:type_name -> executor.ExecuteContext - 3, // 2: executor.ExecuteContext.message:type_name -> executor.MessageContext - 4, // 3: executor.MessageContext.user:type_name -> executor.UserContext - 7, // 4: executor.MetadataResponse.json_schema:type_name -> executor.JSONSchema - 10, // 5: executor.MetadataResponse.dependencies:type_name -> executor.MetadataResponse.DependenciesEntry - 11, // 6: executor.Dependency.urls:type_name -> executor.Dependency.UrlsEntry - 8, // 7: executor.MetadataResponse.DependenciesEntry.value:type_name -> executor.Dependency - 1, // 8: executor.Executor.Execute:input_type -> executor.ExecuteRequest - 12, // 9: executor.Executor.Metadata:input_type -> google.protobuf.Empty - 12, // 10: executor.Executor.Help:input_type -> google.protobuf.Empty - 5, // 11: executor.Executor.Execute:output_type -> executor.ExecuteResponse - 6, // 12: executor.Executor.Metadata:output_type -> executor.MetadataResponse - 9, // 13: executor.Executor.Help:output_type -> executor.HelpResponse - 11, // [11:14] is the sub-list for method output_type - 8, // [8:11] is the sub-list for method input_type - 8, // [8:8] is the sub-list for extension type_name - 8, // [8:8] is the sub-list for extension extendee - 0, // [0:8] is the sub-list for field type_name + 4, // 2: executor.ExecuteContext.message:type_name -> executor.MessageContext + 3, // 3: executor.ExecuteContext.incomingWebhook:type_name -> executor.IncomingWebhookContext + 5, // 4: executor.MessageContext.user:type_name -> executor.UserContext + 8, // 5: executor.MetadataResponse.json_schema:type_name -> executor.JSONSchema + 11, // 6: executor.MetadataResponse.dependencies:type_name -> executor.MetadataResponse.DependenciesEntry + 12, // 7: executor.Dependency.urls:type_name -> executor.Dependency.UrlsEntry + 9, // 8: executor.MetadataResponse.DependenciesEntry.value:type_name -> executor.Dependency + 1, // 9: executor.Executor.Execute:input_type -> executor.ExecuteRequest + 13, // 10: executor.Executor.Metadata:input_type -> google.protobuf.Empty + 13, // 11: executor.Executor.Help:input_type -> google.protobuf.Empty + 6, // 12: executor.Executor.Execute:output_type -> executor.ExecuteResponse + 7, // 13: executor.Executor.Metadata:output_type -> executor.MetadataResponse + 10, // 14: executor.Executor.Help:output_type -> executor.HelpResponse + 12, // [12:15] is the sub-list for method output_type + 9, // [9:12] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_executor_proto_init() } @@ -817,7 +934,7 @@ func file_executor_proto_init() { } } file_executor_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MessageContext); i { + switch v := v.(*IncomingWebhookContext); i { case 0: return &v.state case 1: @@ -829,7 +946,7 @@ func file_executor_proto_init() { } } file_executor_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UserContext); i { + switch v := v.(*MessageContext); i { case 0: return &v.state case 1: @@ -841,7 +958,7 @@ func file_executor_proto_init() { } } file_executor_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExecuteResponse); i { + switch v := v.(*UserContext); i { case 0: return &v.state case 1: @@ -853,7 +970,7 @@ func file_executor_proto_init() { } } file_executor_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*MetadataResponse); i { + switch v := v.(*ExecuteResponse); i { case 0: return &v.state case 1: @@ -865,7 +982,7 @@ func file_executor_proto_init() { } } file_executor_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JSONSchema); i { + switch v := v.(*MetadataResponse); i { case 0: return &v.state case 1: @@ -877,7 +994,7 @@ func file_executor_proto_init() { } } file_executor_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Dependency); i { + switch v := v.(*JSONSchema); i { case 0: return &v.state case 1: @@ -889,6 +1006,18 @@ func file_executor_proto_init() { } } file_executor_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Dependency); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_executor_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*HelpResponse); i { case 0: return &v.state @@ -907,7 +1036,7 @@ func file_executor_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_executor_proto_rawDesc, NumEnums: 0, - NumMessages: 12, + NumMessages: 13, NumExtensions: 0, NumServices: 1, }, diff --git a/pkg/api/executor/grpc_adapter.go b/pkg/api/executor/grpc_adapter.go index c8b8a5fd0..5bbc7527c 100644 --- a/pkg/api/executor/grpc_adapter.go +++ b/pkg/api/executor/grpc_adapter.go @@ -53,6 +53,13 @@ type ( // Limitations: // - It's available only for SocketSlack. In the future, it may be adopted across other platforms. Message Message + + IncomingWebhook IncomingWebhookDetailsContext + } + + // IncomingWebhookDetailsContext holds source incoming webhook context. + IncomingWebhookDetailsContext struct { + BaseSourceURL string } // Message holds information about the message that triggered a given Executor. @@ -60,6 +67,10 @@ type ( Text string URL string User User + + // ParentActivityID is the ID of the parent activity. If user follows with messages in a thread, this ID represents the originating message that started that thread. + // Otherwise, it's the ID of the initial message. + ParentActivityID string } // User represents the user that sent a message. @@ -93,7 +104,7 @@ type ( // // NOTE: In the future we can consider using VersionedPlugins. These can be used to negotiate // a compatible version between client and server. If this is set, Handshake.ProtocolVersion is not required. -const ProtocolVersion = 2 +const ProtocolVersion = 3 var _ plugin.GRPCPlugin = &Plugin{} @@ -133,13 +144,17 @@ func (p *grpcClient) Execute(ctx context.Context, in ExecuteInput) (ExecuteOutpu IsInteractivitySupported: in.Context.IsInteractivitySupported, KubeConfig: in.Context.KubeConfig, Message: &MessageContext{ - Text: in.Context.Message.Text, - Url: in.Context.Message.URL, + Text: in.Context.Message.Text, + Url: in.Context.Message.URL, + ParentActivityId: in.Context.Message.ParentActivityID, User: &UserContext{ Mention: in.Context.Message.User.Mention, DisplayName: in.Context.Message.User.DisplayName, }, }, + IncomingWebhook: &IncomingWebhookContext{ + BaseSourceURL: in.Context.IncomingWebhook.BaseSourceURL, + }, }, } @@ -241,6 +256,9 @@ func (p *grpcServer) Execute(ctx context.Context, request *ExecuteRequest) (*Exe IsInteractivitySupported: request.Context.IsInteractivitySupported, KubeConfig: request.Context.KubeConfig, Message: p.toMessageIfPresent(request.Context.Message), + IncomingWebhook: IncomingWebhookDetailsContext{ + BaseSourceURL: request.Context.IncomingWebhook.BaseSourceURL, + }, }, }) if err != nil { @@ -281,9 +299,10 @@ func (*grpcServer) toMessageIfPresent(msg *MessageContext) Message { } return Message{ - Text: msg.Text, - URL: msg.Url, - User: user, + Text: msg.Text, + URL: msg.Url, + ParentActivityID: msg.ParentActivityId, + User: user, } } diff --git a/pkg/api/message.go b/pkg/api/message.go index 685aaee83..f754c993e 100644 --- a/pkg/api/message.go +++ b/pkg/api/message.go @@ -60,6 +60,9 @@ type Message struct { OnlyVisibleForYou bool `json:"onlyVisibleForYou,omitempty" yaml:"onlyVisibleForYou"` ReplaceOriginal bool `json:"replaceOriginal,omitempty" yaml:"replaceOriginal"` UserHandle string `json:"userHandle,omitempty" yaml:"userHandle"` + + // ParentActivityID represents the originating message that started a thread. If set, message will be sent in that thread instead of the default one. + ParentActivityID string `json:"parentActivityId,omitempty" yaml:"parentActivityId,omitempty"` } func (msg *Message) IsEmpty() bool { diff --git a/pkg/api/source/grpc_adapter.go b/pkg/api/source/grpc_adapter.go index 2ac629b68..05585392a 100644 --- a/pkg/api/source/grpc_adapter.go +++ b/pkg/api/source/grpc_adapter.go @@ -108,7 +108,7 @@ type ( // // NOTE: In the future we can consider using VersionedPlugins. These can be used to negotiate // a compatible version between client and server. If this is set, Handshake.ProtocolVersion is not required. -const ProtocolVersion = 2 +const ProtocolVersion = 3 var _ plugin.GRPCPlugin = &Plugin{} diff --git a/pkg/bot/slack_socket.go b/pkg/bot/slack_socket.go index affa2f825..29dcbe7aa 100644 --- a/pkg/bot/slack_socket.go +++ b/pkg/bot/slack_socket.go @@ -442,6 +442,7 @@ func (b *SocketSlack) handleMessage(ctx context.Context, event slackMessage) err SlackState: event.State, URL: permalink, Text: event.Text, + ParentActivityID: event.GetTimestamp(), }, Message: request, User: execute.UserInput{ @@ -568,6 +569,11 @@ func (b *SocketSlack) send(ctx context.Context, event slackMessage, in interacti if resp.Message.UserHandle != "" { id = resp.Message.UserHandle } + + if resp.Message.ParentActivityID != "" { + options = append(options, slack.MsgOptionTS(resp.Message.ParentActivityID)) + } + _, _, err = b.client.PostMessageContext(ctx, id, options...) if err != nil { return fmt.Errorf("while posting Slack message: %w", slackError(err, event.Channel)) diff --git a/pkg/execute/factory.go b/pkg/execute/factory.go index 432837b78..a2d3d6711 100644 --- a/pkg/execute/factory.go +++ b/pkg/execute/factory.go @@ -172,6 +172,7 @@ type Conversation struct { SlackState *slack.BlockActionStates URL string Text string + ParentActivityID string } // NewDefaultInput an input for NewDefault diff --git a/pkg/execute/plugin_executor.go b/pkg/execute/plugin_executor.go index aac91054c..da214c588 100644 --- a/pkg/execute/plugin_executor.go +++ b/pkg/execute/plugin_executor.go @@ -119,6 +119,10 @@ func (e *PluginExecutor) Execute(ctx context.Context, bindings []string, slackSt Mention: cmdCtx.User.Mention, DisplayName: cmdCtx.User.DisplayName, }, + ParentActivityID: cmdCtx.Conversation.ParentActivityID, + }, + IncomingWebhook: executor.IncomingWebhookDetailsContext{ + BaseSourceURL: e.cfg.Plugins.IncomingWebhook.InClusterBaseURL + "/sources/v1", }, }, }) diff --git a/test/fake/plugin_server.go b/pkg/plugin/plugin_server.go similarity index 66% rename from test/fake/plugin_server.go rename to pkg/plugin/plugin_server.go index 1c8a113a6..9f374b4c5 100644 --- a/test/fake/plugin_server.go +++ b/pkg/plugin/plugin_server.go @@ -1,8 +1,7 @@ -package fake +package plugin import ( "fmt" - "github.com/kubeshop/botkube/pkg/plugin" "log" "net/http" "os" @@ -16,26 +15,21 @@ import ( const indexFileEndpoint = "/botkube.yaml" type ( - // PluginConfig holds configuration for fake plugin server. - PluginConfig struct { + // StaticPluginServerConfig holds configuration for fake plugin server. + StaticPluginServerConfig struct { BinariesDirectory string - Server PluginServer - } - - // PluginServer holds configuration for HTTP plugin server. - PluginServer struct { - Host string `envconfig:"default=http://host.k3d.internal"` - Port int `envconfig:"default=3000"` + Host string `envconfig:"default=http://host.k3d.internal"` + Port int `envconfig:"default=3000"` } ) -// NewPluginServer return function to start the fake plugin HTTP server. -func NewPluginServer(cfg PluginConfig) (string, func() error) { +// NewStaticPluginServer return function to start the static plugin HTTP server suitable for local development or e2e tests. +func NewStaticPluginServer(cfg StaticPluginServerConfig) (string, func() error) { fs := http.FileServer(http.Dir(cfg.BinariesDirectory)) http.Handle("/static/", http.StripPrefix("/static/", fs)) - basePath := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) - builder := plugin.NewIndexBuilder(loggerx.NewNoop()) + basePath := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) + builder := NewIndexBuilder(loggerx.NewNoop()) http.HandleFunc(indexFileEndpoint, func(w http.ResponseWriter, _ *http.Request) { isArchive := os.Getenv("OUTPUT_MODE") == "archive" @@ -57,7 +51,7 @@ func NewPluginServer(cfg PluginConfig) (string, func() error) { } }) - addr := fmt.Sprintf(":%d", cfg.Server.Port) + addr := fmt.Sprintf(":%d", cfg.Port) log.Printf("Listening on %s...", addr) server := &http.Server{ diff --git a/proto/executor.proto b/proto/executor.proto index 125ad6ee3..997da55dc 100644 --- a/proto/executor.proto +++ b/proto/executor.proto @@ -6,8 +6,11 @@ option go_package = "pkg/api/executor"; package executor; +// Config holds the Executor configuration. message Config { // rawYAML contains the Executor configuration in YAML definitions. + // Configuration data is unique per executor. + // Botkube related configuration details are stored in ExecuteContext instead. bytes rawYAML = 1; } @@ -21,28 +24,64 @@ message ExecuteRequest { } message ExecuteContext { + // isInteractivitySupported is set to true only if communication platform supports interactive Messages + // with buttons, select menus, etc. If set to false, you should send only text based messages. bool isInteractivitySupported = 1; + // slackState represents modal state. It's available only if: + // - IsInteractivitySupported is set to true, + // - and interactive actions were used in the response Message. + // This is an alpha feature and may change in the future. + // Most likely, it will be generalized to support all communication platforms. bytes slackState = 2; + // kubeConfig is the slice of byte representation of kubeconfig file content. + // it is available only if context.rbac is configured for a given plugins. Otherwise, it is empty. bytes kubeConfig = 3; + // message holds message details that triggered a given Executor. MessageContext message = 4; + // incomingWebhook holds details about Botkube built-in incoming webhook configuration. + IncomingWebhookContext incomingWebhook = 5; +} + +// IncomingWebhookContext holds information about the built-in incoming webhook that +// allows triggering HandleExternalRequest on a given source. +message IncomingWebhookContext { + string baseSourceURL = 1; } message MessageContext { + // text is the text of the message in the raw format. string text = 1; + // url is the URL of the message. Can be used to open the message in a browser. string url = 2; - UserContext user = 3; + // parentActivityId is the ID of the parent activity. If user follows with messages in a thread, this ID represents the originating message that started that thread. + // Otherwise, it's the ID of the initial message. + string parentActivityId = 3; + // user holds user details that wrote a given message. + UserContext user = 4; } message UserContext { + // mention represents a user platforms specific mention of the user. string mention = 1; + // displayName represents user display name. It can be empty. string displayName = 2; } message ExecuteResponse { + // message represents the output of processing a given input command. + // You can construct a complex message or just use one of our helper functions: + // - api.NewCodeBlockMessage("body", true) + // - api.NewPlaintextMessage("body", true) bytes message = 1; + // messages holds a collection of messages that should be dispatched to the user in the context of a given command execution. + // To avoid spamming, you can specify max 15 messages. + // Limitations: + // - It's available only for SocketSlack. In the future, it may be adopted across other platforms. + // - Interactive message filtering is not available. (https://docs.botkube.io/usage/interactive-output-filtering) repeated bytes messages = 2; } +// MetadataResponse represents metadata of a given plugin. Data is used to generate a plugin index file. message MetadataResponse { // version is a version of a given plugin. It should follow the SemVer syntax. string version = 1; @@ -58,6 +97,7 @@ message MetadataResponse { bool recommended = 6; } +// JSONSchema represents a JSON schema of a given plugin configuration. message JSONSchema { // value is the string value of the JSON schema. string value = 1; @@ -65,12 +105,18 @@ message JSONSchema { string ref_url = 2; } +// Dependency represents a dependency of a given plugin. All binaries are downloaded before the plugin is started. message Dependency { // urls is the map of URL of the dependency. The key is in format of "os/arch", such as "linux/amd64". map urls = 1; } +// HelpResponse represents help of a given plugin. message HelpResponse { + // help is the help of a given plugin. + // You can construct a complex message with buttons etc, or just use one of our helper functions: + // - api.NewCodeBlockMessage("body", true) + // - api.NewPlaintextMessage("body", true) bytes help = 1; } diff --git a/test/e2e/bots_test.go b/test/e2e/bots_test.go index 1ac88ba1d..6c2102cd0 100644 --- a/test/e2e/bots_test.go +++ b/test/e2e/bots_test.go @@ -17,7 +17,6 @@ import ( "botkube.io/botube/test/botkubex" "botkube.io/botube/test/commplatform" "botkube.io/botube/test/diff" - "botkube.io/botube/test/fake" "github.com/MakeNowJust/heredoc" "github.com/anthhub/forwarder" "github.com/hasura/go-graphql-client" @@ -40,6 +39,7 @@ import ( "github.com/kubeshop/botkube/pkg/bot/interactive" "github.com/kubeshop/botkube/pkg/config" "github.com/kubeshop/botkube/pkg/httpx" + "github.com/kubeshop/botkube/pkg/plugin" "github.com/kubeshop/botkube/pkg/ptr" ) @@ -87,7 +87,7 @@ type Config struct { Port int `envconfig:"default=2115"` LocalPort int `envconfig:"default=2115"` } - Plugins fake.PluginConfig + Plugins plugin.StaticPluginServerConfig ConfigMap struct { Namespace string `envconfig:"default=botkube"` } @@ -215,7 +215,7 @@ func runBotTest(t *testing.T, var indexEndpoint string if botDriver.Type() == commplatform.DiscordBot { t.Log("Starting plugin server...") - endpoint, startServerFn := fake.NewPluginServer(appCfg.Plugins) + endpoint, startServerFn := plugin.NewStaticPluginServer(appCfg.Plugins) indexEndpoint = endpoint go func() { require.NoError(t, startServerFn()) diff --git a/test/helpers/plugin_server.go b/test/helpers/plugin_server.go deleted file mode 100644 index 0d77509e5..000000000 --- a/test/helpers/plugin_server.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "log" - "os" - "path/filepath" - "strconv" - - "botkube.io/botube/test/fake" - - "github.com/kubeshop/botkube/pkg/loggerx" -) - -func main() { - dir, err := os.Getwd() - loggerx.ExitOnError(err, "while getting current directory") - - host := os.Getenv("PLUGIN_SERVER_HOST") - port := os.Getenv("PLUGIN_SERVER_PORT") - if host == "" { - host = "http://localhost" - } - if port == "" { - port = "3010" - } - portInt, err := strconv.Atoi(port) - loggerx.ExitOnError(err, "while starting server") - - binDir := filepath.Join(dir, "../plugin-dist") - indexEndpoint, startServerFn := fake.NewPluginServer(fake.PluginConfig{ - BinariesDirectory: binDir, - Server: fake.PluginServer{ - Host: host, - Port: portInt, - }, - }) - - log.Printf("Service plugin binaries from %s\n", binDir) - log.Printf("Botkube repository index URL: %s", indexEndpoint) - err = startServerFn() - loggerx.ExitOnError(err, "while starting server") -}