-
Notifications
You must be signed in to change notification settings - Fork 289
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unify commands #867
Unify commands #867
Conversation
Here's what I quickly drafted: package execute
import (
"context"
"github.com/kubeshop/botkube/pkg/bot/interactive"
"github.com/kubeshop/botkube/pkg/config"
)
//
// API
//
var noResourceNames = []string{""} // for resourceless executor
type CommandVerb string
const (
ListCommandVerb = "list"
EnableCommandVerb = "enable"
DisableCommandVerb = "disable"
PingCommandVerb = "ping"
// (...)
)
type CommandExecutor interface {
Commands() map[CommandVerb]CommandFn
ResourceNames() []string
}
type CommandFn func(ctx context.Context, cmdCtx CommandContext) (interactive.Message, error)
type CommandContext struct {
Args []string
ClusterName string
CommGroupName string
BotName string
Conversation Conversation
Platform config.CommPlatformIntegration
notifierHandler NotifierHandler // for notification executor
// etc.
}
//
// Example for resourceful executor
//
type actionExecutor struct{}
func (e *actionExecutor) ResourceNames() []string {
return noResourceNames
}
func (e *actionExecutor) Commands() map[CommandVerb]CommandFn {
return map[CommandVerb]CommandFn{
ListCommandVerb: e.List,
EnableCommandVerb: e.Enable,
DisableCommandVerb: e.Disable,
}
}
func (e *actionExecutor) List(ctx context.Context, cmdCtx CommandContext) (interactive.Message, error) {
// do something
panic("not implemented")
}
func (e *actionExecutor) Enable(ctx context.Context, cmdCtx CommandContext) (interactive.Message, error) {
// do something
panic("not implemented")
}
func (e *actionExecutor) Disable(ctx context.Context, cmdCtx CommandContext) (interactive.Message, error) {
// do something
panic("not implemented")
}
//
// example for Resourceless Executor
//
type pingExecutor struct{}
func (e *actionExecutor) ResourceNames() []string {
return noResourceNames
}
func (e *pingExecutor) Commands() map[CommandVerb]CommandFn {
return map[CommandVerb]CommandFn{
PingCommandVerb: e.Ping,
}
}
func (e *pingExecutor) Ping(ctx context.Context, cmdCtx CommandContext) (interactive.Message, error) {
// do something
panic("not implemented")
}
//
// Default executor
//
type SampleDefaultExecutor struct {
cmdsMapping map[CommandVerb]map[string]CommandFn
}
// In main.go, call this constructor - initialize and pass all sub-executors to it
func NewSampleDefaultExecutor(executors []CommandExecutor) (*SampleDefaultExecutor, error) {
cmdsMapping := make(map[CommandVerb]map[string]CommandFn)
for _, executor := range executors {
cmds := executor.Commands()
resNames := executor.ResourceNames()
for verb, cmdFn := range cmds {
for _, resName := range resNames {
// TODO: Handle conflicts - return error from the constructor
cmdsMapping[verb][resName] = cmdFn
}
}
}
return &SampleDefaultExecutor{cmdsMapping: cmdsMapping}, nil
}
func (s *SampleDefaultExecutor) Execute(ctx context.Context) interactive.Message {
if len(args) == 0 {
// invalid command, do something - ideally rework default executor to return error
}
cmdVerb := args[0]
var cmdRes string
if len(args) > 1 {
cmdRes = args[1]
}
cmdCtx := CommandContext{
// define necessary fields
}
res, err := s.cmdsMapping[cmdVerb][cmdRes](ctx, cmdCtx)
// do something with res, err, first check if it exists
} And here are some general comments:
|
68058ca
to
1b868f9
Compare
pkg/execute/executor.go
Outdated
@@ -220,7 +223,9 @@ func (e *DefaultExecutor) Execute(ctx context.Context) interactive.Message { | |||
cmdVerb := strings.ToLower(args[0]) | |||
var cmdRes string | |||
if len(args) > 1 { | |||
cmdRes = strings.ToLower(args[1]) | |||
if !strings.Contains(args[1], "--cluster-name=") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mszostok any tips on how to support parameters like here?
@botkube help
- args[0]="help", args[1]=""
@botkube help --cluster-name=c1
- args[0]="help", args[1]!=""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, this is tricky. In general, we should get rid of the--cluster-name
flag once we will remove the legacy Slack: #865
However, for now we need to handle that. What's more, you need to handle:
@botkube help --cluster-name=foo
@botkube help --cluster-name foo
// no=
sign@botkube kc get po --cluster-name foo -n default
// whitespace, additional flag after
Currently, we have 2 approaches in our code base:
- Old approach still used in
kubectl
executor, where the--cluster-name
is removed in the for loop:botkube/pkg/execute/kubectl.go
Lines 210 to 243 in 22da62e
// TODO: This code was moved from: // // https://github.com/kubeshop/botkube/blob/0b99ac480c8e7e93ce721b345ffc54d89019a812/pkg/execute/executor.go#L242-L276 // // Further refactoring in needed. For example, the cluster flag should be removed by an upper layer // as it's strictly Botkube related and not executor specific (e.g. kubectl, helm, istio etc.). func (e *Kubectl) getFinalArgs(args []string) []string { // Remove unnecessary flags var finalArgs []string isClusterNameArg := false for _, arg := range args { if isClusterNameArg { isClusterNameArg = false continue } if arg == AbbrFollowFlag.String() || strings.HasPrefix(arg, FollowFlag.String()) { continue } if arg == AbbrWatchFlag.String() || strings.HasPrefix(arg, WatchFlag.String()) { continue } // Remove --cluster-name flag and it's value if strings.HasPrefix(arg, ClusterFlag.String()) { // Check if flag value in current or next argument and compare with config.settings.clusterName if arg == ClusterFlag.String() { isClusterNameArg = true } continue } finalArgs = append(finalArgs, arg) } return finalArgs } - New approach which is more reliable where
cobra
flag parser is used and later flag is removed from a given string:botkube/pkg/execute/executor_filter.go
Lines 105 to 135 in 22da62e
func extractExecutorFilter(cmd string) (executorFilter, error) { var filters []string filters, err := parseAndValidateAnyFilters(cmd) if err != nil { return nil, err } if len(filters) == 0 { return newExecutorEchoFilter(cmd), nil } if len(filters[0]) == 0 { return nil, errors.New(missingCmdFilterValue) } filterVal := filters[0] escapedFilterVal := regexp.QuoteMeta(filterVal) filterFlagRegex, err := regexp.Compile(fmt.Sprintf(`--filter[=|(' ')]*('%s'|"%s"|%s)("|')*`, escapedFilterVal, escapedFilterVal, escapedFilterVal)) if err != nil { return nil, errors.New("could not extract provided filter") } matches := filterFlagRegex.FindStringSubmatch(cmd) if len(matches) == 0 { return nil, fmt.Errorf(filterFlagParseErrorMsg, cmd, "it contains unsupported characters.") } return newExecutorTextFilter(filterVal, strings.ReplaceAll(cmd, fmt.Sprintf(" %s", matches[0]), "")), nil
IMO you can copy and adapt the 2nd approach for cluster-name and simply remove it from the command string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mszostok Just a minor comment about
In general, we should get rid of the--cluster-name flag once we will remove the legacy Slack
I don't think we can do this, if multi-cluster is also supported by Mattermost and Discord. Or, do you mean enforcing users to create a dedicated app per cluster for all comm platforms? If that's not a common use-case, then maybe it makes sense 🤔 to be discussed later, I put it in the #865
FYI: @josefkarasek to make the e2e test pass, you need to also adjust the command name in e2e test. For example here: Line 231 in 22da62e
it shouldn't be |
b41fe28
to
f625bb3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall good job 🚀
I reviewed the code and left some comments. I also tested that locally and found 3 issues:
-
@Botkube status sourcebindings
- doesn't return expect response. Please see the Lacho comment to check desired output.
-
@Botkube status
- doesn't return expect response. Please see the Lacho comment to check desired output.
-
filtering doesn't work globally. It's related to this comment: Unify commands #867 (comment)
@@ -47,6 +33,40 @@ const ( | |||
humanReadableCommandListName = "Available kubectl commands" | |||
|
|||
lineLimitToShowFilter = 16 | |||
|
|||
helpMessageList = `@Botkube list [resource] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's ok-ish approach for now. However, I would suggest adding TODO
comment and addressing that in the follow-up PR where this will be automatically generated. Currently, aliases are defined in two different places and it's error-prone when we will add more or change existing one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. let's do it in a subsequent PR.
pkg/execute/executor.go
Outdated
|
||
fn, found := resources[cmdRes] | ||
if !found { | ||
e.reportCommand(anonymizedInvalidVerb, false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is about verb
, so you should report that in line 272.
Here you should anonymize only resource name, so do sth like:
e.reportCommand(fmt.Sprintf("%s {invalid resource}", cmdVerb), false)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point - it was a mistake on my part. To be consistent with previous implementation, I moved it a few lines above to the CommandVerb parsing.
const enabled = true | ||
cmdVerb, cmdRes := cmdCtx.Args[0], cmdCtx.Args[1] | ||
|
||
defer e.reportCommand(cmdVerb, cmdRes, cmdCtx.Conversation.CommandOrigin, cmdCtx.Platform) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to be careful about reporting our commands. This data is used on our dashboards: https://kubeshop.slack.com/archives/C03MRCX7UE9/p1662727250564769
Please sync with @brampling and inform him about changes that we will introduce because of changed syntax, so it can be handled properly too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is consistent with what we did before this PR. Obviously we should review it, just to be sure, but semantically there's no change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not consistent as the new command is different, e.g. from notifier [start|stop|status]
is now start notifications
etc.
I posted it here, but it was meant to be a generic comment about changed syntax. Not necessary in the scope of "actions".
@@ -16,12 +16,17 @@ import ( | |||
"github.com/kubeshop/botkube/pkg/sliceutil" | |||
) | |||
|
|||
const ( | |||
// KubectlBinary is absolute path of kubectl binary | |||
KubectlBinary = "/usr/local/bin/kubectl" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why it's exported?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm now getting the cluster version in main.go, similarly to the initial help message -
Lines 164 to 168 in bf454cb
k8sVersion, err := findK8sVersion(runner) | |
if err != nil { | |
return reportFatalError("while fetching kubernetes version", err) | |
} | |
botkubeVersion := findBotkubeVersion(k8sVersion) |
6471b42
to
febef2f
Compare
Point 1) and 3) should be addressed by now. I'd prefer to push 2) to a subsequent PR, as it's related to the changes that need to happen to support better help messages, when the user misspell verb.
|
2fa01ef
to
4fd67e3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested and it works awesome 🚀
Let's address last comments and we will be ready for merging this PR!
pkg/execute/config.go
Outdated
|
||
const redactedSecretStr = "*** REDACTED ***" | ||
|
||
// Deprecated: this function doesn't fit in the scope of notifier. It was moved from legacy reasons, but it will be removed in future. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment should be removed as this func is not a part of notifier anymore 👍
const enabled = true | ||
cmdVerb, cmdRes := cmdCtx.Args[0], cmdCtx.Args[1] | ||
|
||
defer e.reportCommand(cmdVerb, cmdRes, cmdCtx.Conversation.CommandOrigin, cmdCtx.Platform) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not consistent as the new command is different, e.g. from notifier [start|stop|status]
is now start notifications
etc.
I posted it here, but it was meant to be a generic comment about changed syntax. Not necessary in the scope of "actions".
|
||
fn, found := resources[cmdRes] | ||
if !found { | ||
e.log.Infof("received unsupported resource: %q", execFilter.FilteredCommand()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please also report that e.reportCommand(fmt.Sprintf("%s {invalid resource}", cmdVerb), false)
as I mentioned here: #867 (comment)
pkg/execute/config.go
Outdated
|
||
// Config returns Config in yaml format | ||
func (e *ConfigExecutor) Config(ctx context.Context, cmdCtx CommandContext) (interactive.Message, error) { | ||
cmdVerb := cmdCtx.Args[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add len check 🙂
pkg/execute/config.go
Outdated
|
||
out, err := e.showControllerConfig() | ||
if err != nil { | ||
return interactive.Message{}, fmt.Errorf("while executing 'showconfig' command: %w", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return interactive.Message{}, fmt.Errorf("while executing 'showconfig' command: %w", err) | |
return interactive.Message{}, fmt.Errorf("while rendering Botkube configuration: %w", err) |
pkg/execute/config.go
Outdated
cmdVerb := cmdCtx.Args[0] | ||
defer e.reportCommand(cmdVerb, cmdCtx.Conversation.CommandOrigin, cmdCtx.Platform) | ||
|
||
out, err := e.showControllerConfig() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
out, err := e.showControllerConfig() | |
out, err := e.renderBotkubeConfiguration() |
the current name is not accurate
pkg/execute/config.go
Outdated
if err != nil { | ||
return interactive.Message{}, fmt.Errorf("while executing 'showconfig' command: %w", err) | ||
} | ||
msg := fmt.Sprintf("Showing config for cluster %q:\n\n%s", cmdCtx.ClusterName, out) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have the cluster name already in the command header. IMO, we can remove this message and return raw configuration only.
pkg/execute/default_runner.go
Outdated
@@ -66,7 +67,9 @@ func newCmdsMapping(executors []CommandExecutor) map[CommandVerb]map[string]Comm | |||
cmdsMapping[verb] = make(map[string]CommandFn) | |||
} | |||
for _, resName := range resNames { | |||
// TODO: Handle conflicts - return error from the constructor | |||
if _, ok := cmdsMapping[verb][resName]; ok { | |||
panic(fmt.Sprintf("Command collision: tried to register '%s %s', but it already exists", verb, resName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot use panic in the production code. Please return an error.
pkg/bot/interactive/help.go
Outdated
h.btnBuilder.ForCommandWithoutDesc("Start notifications", "start notifications"), | ||
h.btnBuilder.ForCommandWithoutDesc("Stop notifications", "stop notifications"), | ||
h.btnBuilder.ForCommandWithoutDesc("Get status", "status notifications"), | ||
h.btnBuilder.ForCommandWithoutDesc("Display configuration", "config"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "Display configuration" button is misleading. It's under the notification section but actually it is about the whole Botkube configuration. For now, let's remove it from this section and discuss that async with @lpetkov and introduce it in a follow-up PR if needed.
pkg/execute/executor.go
Outdated
type NotifierAction string | ||
Available resources: | ||
actions | action | act list available automations | ||
filters | filter | fil list available filters |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
filters | filter | fil list available filters | |
filters | filter | flr list available filters |
First inconsistency 😄 so it proves that this comment is valid: #867 (comment) 😄
`@Botkube [command/verb] [optional secondary command] [--option="string"]` `@Botkube ping [--cluster="string"]` - Ping a cluster `@Botkube help` - Print help menu `@Botkube kc|kcc|kubectl [command] [TYPE] [NAME] [flags]` - Run kubectl commands `@Botkube config` - Show bot configuration `@Botkube status [notifications|notif]` - Show running status of event notifier `@Botkube status [k|kc|kcc|kubectl]` - Show enabled commands/verbs/resource types for `kubectl` for the channel `@Botkube status sourcebindings` - Show enabled source bindings for the channel `@Botkube list [filters|filter|flr]` to list all filters `@Botkube list [commands|cmd]` to list all commands `@Botkube list` - list all `list` options `@Botkube enable [notifications|notif]` to enable notifications in a channel `@Botkube enable [filters|filter|flr] filterName` to enable a filter `@Botkube enable` - list all `enable` options `@Botkube disable [notifications|notif]` to disable notifications in a channel `@Botkube disable [filters|filter|flr] filterName` to disable a filter `@Botkube disable` - list all `disable` options `@Botkube edit sourcebindings` `@Botkube edit` - list all `edit` options
34f54bb
to
37021f9
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
Unify BotKube command names
Changes proposed in this pull request:
Related issue(s)
#703