diff --git a/hack/target/serve-plugins/main.go b/hack/target/serve-plugins/main.go index 126ec3d3a..03760114d 100644 --- a/hack/target/serve-plugins/main.go +++ b/hack/target/serve-plugins/main.go @@ -8,7 +8,7 @@ import ( "strconv" "github.com/kubeshop/botkube/pkg/loggerx" - "github.com/kubeshop/botkube/pkg/pluginx" + "github.com/kubeshop/botkube/pkg/plugin" ) func main() { @@ -24,7 +24,7 @@ func main() { loggerx.ExitOnError(err, "while casting server port value") binDir := filepath.Join(dir, *pluginsDir) - indexEndpoint, startServerFn := pluginx.NewStaticPluginServer(pluginx.StaticPluginServerConfig{ + indexEndpoint, startServerFn := plugin.NewStaticPluginServer(plugin.StaticPluginServerConfig{ BinariesDirectory: binDir, Host: *host, Port: portInt, diff --git a/pkg/api/executor/executor.pb.go b/pkg/api/executor/executor.pb.go index 585555d24..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,11 +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"` - IncomingWebhook *IncomingWebhookContext `protobuf:"bytes,5,opt,name=incomingWebhook,proto3" json:"incomingWebhook,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() { @@ -214,6 +229,8 @@ func (x *ExecuteContext) GetIncomingWebhook() *IncomingWebhookContext { 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 @@ -266,10 +283,15 @@ type MessageContext struct { 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"` - ParentActivityId string `protobuf:"bytes,3,opt,name=parentActivityId,proto3" json:"parentActivityId,omitempty"` - User *UserContext `protobuf:"bytes,4,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() { @@ -337,7 +359,9 @@ 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"` } @@ -392,7 +416,16 @@ 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"` } @@ -442,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 @@ -535,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 @@ -592,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 @@ -640,11 +676,16 @@ 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"` } diff --git a/pkg/api/message.go b/pkg/api/message.go index 2a5226c88..f754c993e 100644 --- a/pkg/api/message.go +++ b/pkg/api/message.go @@ -62,7 +62,7 @@ type Message struct { 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" yaml:"parentActivityId"` + ParentActivityID string `json:"parentActivityId,omitempty" yaml:"parentActivityId,omitempty"` } func (msg *Message) IsEmpty() bool { diff --git a/proto/executor.proto b/proto/executor.proto index b072fe9cc..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,34 +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; + // 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; @@ -64,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; @@ -71,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 f16626933..6c2102cd0 100644 --- a/test/e2e/bots_test.go +++ b/test/e2e/bots_test.go @@ -6,7 +6,6 @@ import ( "bytes" "context" "fmt" - "github.com/kubeshop/botkube/pkg/pluginx" "net/http" "regexp" "strconv" @@ -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 pluginx.StaticPluginServerConfig + 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 := pluginx.NewStaticPluginServer(appCfg.Plugins) + endpoint, startServerFn := plugin.NewStaticPluginServer(appCfg.Plugins) indexEndpoint = endpoint go func() { require.NoError(t, startServerFn())