diff --git a/cmd/botkube-agent/main.go b/cmd/botkube-agent/main.go index 72107636f..2e4efcbe7 100644 --- a/cmd/botkube-agent/main.go +++ b/cmd/botkube-agent/main.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "log" "net/http" "time" @@ -67,9 +66,7 @@ func main() { ctx, cancelCtxFn := context.WithCancel(ctx) defer cancelCtxFn() - if err := run(ctx); err != nil { - log.Fatal(err) - } + loggerx.ExitOnError(run(ctx), "while running application") } // run wraps the main logic of the app to be able to properly clean up resources via deferred calls. diff --git a/cmd/cli/cmd/install.go b/cmd/cli/cmd/install.go index f4e5ca0a4..5ebb8d944 100644 --- a/cmd/cli/cmd/install.go +++ b/cmd/cli/cmd/install.go @@ -47,21 +47,20 @@ func NewInstall() *cobra.Command { flags := installCmd.Flags() kubex.RegisterKubeconfigFlag(flags) + flags.DurationVar(&opts.Timeout, "timeout", 10*time.Minute, `Maximum time during which the Botkube installation is being watched, where "0" means "infinite". Valid time units are "ns", "us" (or "ยตs"), "ms", "s", "m", "h".`) + flags.BoolVarP(&opts.Watch, "watch", "w", true, "Watches the status of the Botkube installation until it finish or the defined `--timeout` occurs.") // common params for install and upgrade operation flags.StringVar(&opts.HelmParams.Version, "version", install.LatestVersionTag, "Botkube version. Possible values @latest, 1.2.0, ...") flags.StringVar(&opts.HelmParams.Namespace, "namespace", install.Namespace, "Botkube installation namespace.") flags.StringVar(&opts.HelmParams.ReleaseName, "release-name", install.ReleaseName, "Botkube Helm chart release name.") - flags.StringVar(&opts.HelmParams.ChartName, "chart-name", "botkube", "Botkube Helm chart name.") + flags.StringVar(&opts.HelmParams.ChartName, "chart-name", install.HelmChartName, "Botkube Helm chart name.") flags.StringVar(&opts.HelmParams.RepoLocation, "repo", install.HelmRepoStable, fmt.Sprintf("Botkube Helm chart repository location. It can be relative path to current working directory or URL. Use %s tag to select repository which holds the stable Helm chart versions.", install.StableVersionTag)) flags.BoolVar(&opts.HelmParams.DryRun, "dry-run", false, "Simulate an install") flags.BoolVar(&opts.HelmParams.Force, "force", false, "Force resource updates through a replacement strategy") flags.BoolVar(&opts.HelmParams.DisableHooks, "no-hooks", false, "Disable pre/post install/upgrade hooks") flags.BoolVar(&opts.HelmParams.DisableOpenAPIValidation, "disable-openapi-validation", false, "If set, it will not validate rendered templates against the Kubernetes OpenAPI Schema") flags.BoolVar(&opts.HelmParams.SkipCRDs, "skip-crds", false, "If set, no CRDs will be installed.") - flags.DurationVar(&opts.HelmParams.Timeout, "timeout", 5*time.Minute, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") - flags.BoolVar(&opts.HelmParams.Wait, "wait", true, "If set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") - flags.BoolVar(&opts.HelmParams.WaitForJobs, "wait-for-jobs", true, "If set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") flags.BoolVar(&opts.HelmParams.Atomic, "atomic", false, "If set, process rolls back changes made in case of failed install/upgrade. The --wait flag will be set automatically if --atomic is used") flags.BoolVar(&opts.HelmParams.SubNotes, "render-subchart-notes", false, "If set, render subchart notes along with the parent") flags.StringVar(&opts.HelmParams.Description, "description", "", "add a custom description") diff --git a/cmd/cli/docs/botkube_install.md b/cmd/cli/docs/botkube_install.md index 2a1850e56..61aa677a5 100644 --- a/cmd/cli/docs/botkube_install.md +++ b/cmd/cli/docs/botkube_install.md @@ -52,11 +52,10 @@ botkube install --repo @local --set-literal stringArray Set a literal STRING value on the command line --set-string stringArray Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) --skip-crds If set, no CRDs will be installed. - --timeout duration time to wait for any individual Kubernetes operation (like Jobs for hooks) (default 5m0s) + --timeout duration Maximum time during which the Botkube installation is being watched, where "0" means "infinite". Valid time units are "ns", "us" (or "ยตs"), "ms", "s", "m", "h". (default 10m0s) -f, --values strings Specify values in a YAML file or a URL (can specify multiple) --version string Botkube version. Possible values @latest, 1.2.0, ... (default "@latest") - --wait If set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout (default true) - --wait-for-jobs If set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default true) + -w, --watch --timeout Watches the status of the Botkube installation until it finish or the defined --timeout occurs. (default true) ``` ### Options inherited from parent commands diff --git a/go.mod b/go.mod index bfb8f3e39..fc3ec49a3 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/aws/aws-sdk-go v1.44.122 github.com/briandowns/spinner v1.23.0 github.com/bwmarrin/discordgo v0.25.0 + github.com/charmbracelet/log v0.2.2 github.com/dustin/go-humanize v1.0.1 github.com/fatih/color v1.13.0 github.com/go-playground/locales v0.14.0 @@ -34,6 +35,7 @@ require ( github.com/mattermost/mattermost-server/v6 v6.7.2 github.com/mattn/go-isatty v0.0.19 github.com/mattn/go-shellwords v1.0.12 + github.com/morikuni/aec v1.0.0 github.com/muesli/reflow v0.3.0 github.com/olivere/elastic v6.2.37+incompatible github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 @@ -94,6 +96,7 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/charmbracelet/lipgloss v0.7.1 // indirect github.com/containerd/containerd v1.7.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect @@ -117,6 +120,7 @@ require ( github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.0.5 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.2.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -191,7 +195,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/muesli/termenv v0.15.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/run v1.1.0 // indirect diff --git a/go.sum b/go.sum index 185215136..613b7ae66 100644 --- a/go.sum +++ b/go.sum @@ -469,6 +469,10 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= +github.com/charmbracelet/log v0.2.2 h1:CaXgos+ikGn5tcws5Cw3paQuk9e/8bIwuYGhnkqQFjo= +github.com/charmbracelet/log v0.2.2/go.mod h1:Zs11hKpb8l+UyX4y1srwZIGW+MPCXJHIty3MB9l/sno= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= @@ -813,6 +817,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= diff --git a/helm/botkube/README.md b/helm/botkube/README.md index 9254462b1..475952a0f 100644 --- a/helm/botkube/README.md +++ b/helm/botkube/README.md @@ -47,46 +47,46 @@ Controller for the Botkube Slack app which helps you monitor your Kubernetes clu | [actions.show-logs-on-error.bindings.executors](./values.yaml#L109) | list | `["k8s-default-tools"]` | Executors configuration used to execute a configured command. | | [sources](./values.yaml#L118) | object | See the `values.yaml` file for full object. | Map of sources. Source contains configuration for Kubernetes events and sending recommendations. The property name under `sources` object is an alias for a given configuration. You can define multiple sources configuration with different names. Key name is used as a binding reference. | | [sources.k8s-recommendation-events.botkube/kubernetes](./values.yaml#L123) | object | See the `values.yaml` file for full object. | Describes Kubernetes source configuration. | -| [sources.k8s-err-events-with-ai-support.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | -| [sources.k8s-recommendation-events.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | -| [sources.k8s-err-with-logs-events.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | | [sources.k8s-all-events.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | -| [sources.k8s-err-events.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | -| [sources.k8s-create-events.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | -| [executors.bins-management.botkube/exec.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | -| [executors.k8s-default-tools.botkube/helm.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | | [executors.ai.botkube/doctor.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | +| [executors.bins-management.botkube/exec.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | +| [sources.k8s-recommendation-events.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | | [executors.k8s-default-tools.botkube/kubectl.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | +| [executors.k8s-default-tools.botkube/helm.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | +| [sources.k8s-err-events-with-ai-support.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | +| [sources.k8s-create-events.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | +| [sources.k8s-err-with-logs-events.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | +| [sources.k8s-err-events.botkube/kubernetes.context.rbac](./values.yaml#L126) | object | `{"group":{"prefix":"","static":{"values":["botkube-plugins-default"]},"type":"Static"}}` | RBAC configuration for this plugin. | +| [executors.k8s-default-tools.botkube/helm.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | | [sources.k8s-create-events.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | -| [executors.bins-management.botkube/exec.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | -| [sources.k8s-err-events.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | +| [sources.k8s-err-with-logs-events.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | | [sources.k8s-all-events.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | -| [sources.k8s-err-events-with-ai-support.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | -| [sources.k8s-recommendation-events.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | -| [executors.k8s-default-tools.botkube/helm.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | | [executors.k8s-default-tools.botkube/kubectl.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | +| [sources.k8s-err-events-with-ai-support.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | +| [sources.k8s-err-events.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | | [executors.ai.botkube/doctor.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | -| [sources.k8s-err-with-logs-events.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | -| [sources.k8s-err-with-logs-events.botkube/kubernetes.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | -| [executors.k8s-default-tools.botkube/helm.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | -| [sources.k8s-recommendation-events.botkube/kubernetes.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | -| [sources.k8s-err-events.botkube/kubernetes.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | -| [executors.ai.botkube/doctor.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | +| [sources.k8s-recommendation-events.botkube/kubernetes.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | +| [executors.bins-management.botkube/exec.context.rbac.group.type](./values.yaml#L129) | string | `"Static"` | Static impersonation for a given username and groups. | +| [executors.bins-management.botkube/exec.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | | [sources.k8s-err-events-with-ai-support.botkube/kubernetes.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | +| [executors.ai.botkube/doctor.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | +| [sources.k8s-recommendation-events.botkube/kubernetes.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | +| [executors.k8s-default-tools.botkube/helm.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | +| [executors.k8s-default-tools.botkube/kubectl.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | | [sources.k8s-all-events.botkube/kubernetes.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | -| [executors.bins-management.botkube/exec.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | +| [sources.k8s-err-events.botkube/kubernetes.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | +| [sources.k8s-err-with-logs-events.botkube/kubernetes.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | | [sources.k8s-create-events.botkube/kubernetes.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | -| [executors.k8s-default-tools.botkube/kubectl.context.rbac.group.prefix](./values.yaml#L131) | string | `""` | Prefix that will be applied to .static.value[*]. | -| [sources.k8s-create-events.botkube/kubernetes.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | -| [executors.bins-management.botkube/exec.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | +| [executors.k8s-default-tools.botkube/kubectl.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | | [sources.k8s-err-events-with-ai-support.botkube/kubernetes.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | -| [executors.ai.botkube/doctor.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | +| [executors.bins-management.botkube/exec.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | | [sources.k8s-recommendation-events.botkube/kubernetes.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | | [sources.k8s-all-events.botkube/kubernetes.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | +| [executors.ai.botkube/doctor.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | | [sources.k8s-err-events.botkube/kubernetes.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | +| [sources.k8s-create-events.botkube/kubernetes.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | | [executors.k8s-default-tools.botkube/helm.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | | [sources.k8s-err-with-logs-events.botkube/kubernetes.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | -| [executors.k8s-default-tools.botkube/kubectl.context.rbac.group.static.values](./values.yaml#L134) | list | `["botkube-plugins-default"]` | Name of group.rbac.authorization.k8s.io the plugin will be bound to. | | [sources.k8s-recommendation-events.botkube/kubernetes.config.recommendations](./values.yaml#L148) | object | `{"ingress":{"backendServiceValid":true,"tlsSecretValid":true},"pod":{"labelsSet":true,"noLatestImageTag":true}}` | Describes configuration for various recommendation insights. | | [sources.k8s-recommendation-events.botkube/kubernetes.config.recommendations.pod](./values.yaml#L150) | object | `{"labelsSet":true,"noLatestImageTag":true}` | Recommendations for Pod Kubernetes resource. | | [sources.k8s-recommendation-events.botkube/kubernetes.config.recommendations.pod.noLatestImageTag](./values.yaml#L152) | bool | `true` | If true, notifies about Pod containers that use `latest` tag for images. | @@ -99,11 +99,11 @@ Controller for the Botkube Slack app which helps you monitor your Kubernetes clu | [sources.k8s-all-events.botkube/kubernetes.config.filters.objectAnnotationChecker](./values.yaml#L174) | bool | `true` | If true, enables support for `botkube.io/disable` resource annotation. | | [sources.k8s-all-events.botkube/kubernetes.config.filters.nodeEventsChecker](./values.yaml#L176) | bool | `true` | If true, filters out Node-related events that are not important. | | [sources.k8s-all-events.botkube/kubernetes.config.namespaces](./values.yaml#L180) | object | `{"include":[".*"]}` | Describes namespaces for every Kubernetes resources you want to watch or exclude. These namespaces are applied to every resource specified in the resources list. However, every specified resource can override this by using its own namespaces object. | -| [sources.k8s-err-with-logs-events.botkube/kubernetes.config.namespaces.include](./values.yaml#L184) | list | `[".*"]` | Include contains a list of allowed Namespaces. It can also contain regex expressions: `- ".*"` - to specify all Namespaces. | | [sources.k8s-create-events.botkube/kubernetes.config.namespaces.include](./values.yaml#L184) | list | `[".*"]` | Include contains a list of allowed Namespaces. It can also contain regex expressions: `- ".*"` - to specify all Namespaces. | +| [sources.k8s-err-with-logs-events.botkube/kubernetes.config.namespaces.include](./values.yaml#L184) | list | `[".*"]` | Include contains a list of allowed Namespaces. It can also contain regex expressions: `- ".*"` - to specify all Namespaces. | | [sources.k8s-err-events.botkube/kubernetes.config.namespaces.include](./values.yaml#L184) | list | `[".*"]` | Include contains a list of allowed Namespaces. It can also contain regex expressions: `- ".*"` - to specify all Namespaces. | -| [sources.k8s-all-events.botkube/kubernetes.config.namespaces.include](./values.yaml#L184) | list | `[".*"]` | Include contains a list of allowed Namespaces. It can also contain regex expressions: `- ".*"` - to specify all Namespaces. | | [sources.k8s-err-events-with-ai-support.botkube/kubernetes.config.namespaces.include](./values.yaml#L184) | list | `[".*"]` | Include contains a list of allowed Namespaces. It can also contain regex expressions: `- ".*"` - to specify all Namespaces. | +| [sources.k8s-all-events.botkube/kubernetes.config.namespaces.include](./values.yaml#L184) | list | `[".*"]` | Include contains a list of allowed Namespaces. It can also contain regex expressions: `- ".*"` - to specify all Namespaces. | | [sources.k8s-all-events.botkube/kubernetes.config.event](./values.yaml#L194) | object | `{"message":{"exclude":[],"include":[]},"reason":{"exclude":[],"include":[]},"types":["create","delete","error"]}` | Describes event constraints for Kubernetes resources. These constraints are applied for every resource specified in the `resources` list, unless they are overridden by the resource's own `events` object. | | [sources.k8s-all-events.botkube/kubernetes.config.event.types](./values.yaml#L196) | list | `["create","delete","error"]` | Lists all event types to be watched. | | [sources.k8s-all-events.botkube/kubernetes.config.event.reason](./values.yaml#L202) | object | `{"exclude":[],"include":[]}` | Optional list of exact values or regex patterns to filter events by event reason. Skipped, if both include/exclude lists are empty. | @@ -204,61 +204,62 @@ Controller for the Botkube Slack app which helps you monitor your Kubernetes clu | [settings.healthPort](./values.yaml#L824) | int | `2114` | | | [settings.upgradeNotifier](./values.yaml#L826) | bool | `true` | If true, notifies about new Botkube releases. | | [settings.log.level](./values.yaml#L830) | string | `"info"` | Sets one of the log levels. Allowed values: `info`, `warn`, `debug`, `error`, `fatal`, `panic`. | -| [settings.log.disableColors](./values.yaml#L832) | bool | `false` | If true, disable ANSI colors in logging. | -| [settings.systemConfigMap](./values.yaml#L835) | object | `{"name":"botkube-system"}` | Botkube's system ConfigMap where internal data is stored. | -| [settings.persistentConfig](./values.yaml#L840) | object | `{"runtime":{"configMap":{"annotations":{},"name":"botkube-runtime-config"},"fileName":"_runtime_state.yaml"},"startup":{"configMap":{"annotations":{},"name":"botkube-startup-config"},"fileName":"_startup_state.yaml"}}` | Persistent config contains ConfigMap where persisted configuration is stored. The persistent configuration is evaluated from both chart upgrade and Botkube commands used in runtime. | -| [ssl.enabled](./values.yaml#L855) | bool | `false` | If true, specify cert path in `config.ssl.cert` property or K8s Secret in `config.ssl.existingSecretName`. | -| [ssl.existingSecretName](./values.yaml#L861) | string | `""` | Using existing SSL Secret. It MUST be in `botkube` Namespace. | -| [ssl.cert](./values.yaml#L864) | string | `""` | SSL Certificate file e.g certs/my-cert.crt. | -| [service](./values.yaml#L867) | object | `{"name":"metrics","port":2112,"targetPort":2112}` | Configures Service settings for ServiceMonitor CR. | -| [ingress](./values.yaml#L874) | object | `{"annotations":{"kubernetes.io/ingress.class":"nginx"},"create":false,"host":"HOST","tls":{"enabled":false,"secretName":""}}` | Configures Ingress settings that exposes MS Teams endpoint. [Ref doc](https://kubernetes.io/docs/concepts/services-networking/ingress/#the-ingress-resource). | -| [serviceMonitor](./values.yaml#L885) | object | `{"enabled":false,"interval":"10s","labels":{},"path":"/metrics","port":"metrics"}` | Configures ServiceMonitor settings. [Ref doc](https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#servicemonitor). | -| [deployment.annotations](./values.yaml#L895) | object | `{}` | Extra annotations to pass to the Botkube Deployment. | -| [deployment.livenessProbe](./values.yaml#L897) | object | `{"failureThreshold":5,"initialDelaySeconds":1,"periodSeconds":15,"successThreshold":1,"timeoutSeconds":1}` | Liveness probe. | -| [deployment.livenessProbe.initialDelaySeconds](./values.yaml#L899) | int | `1` | The liveness probe initial delay seconds. | -| [deployment.livenessProbe.periodSeconds](./values.yaml#L901) | int | `15` | The liveness probe period seconds. | -| [deployment.livenessProbe.timeoutSeconds](./values.yaml#L903) | int | `1` | The liveness probe timeout seconds. | -| [deployment.livenessProbe.failureThreshold](./values.yaml#L905) | int | `5` | The liveness probe failure threshold. | -| [deployment.livenessProbe.successThreshold](./values.yaml#L907) | int | `1` | The liveness probe success threshold. | -| [deployment.readinessProbe](./values.yaml#L910) | object | `{"failureThreshold":5,"initialDelaySeconds":1,"periodSeconds":15,"successThreshold":1,"timeoutSeconds":1}` | Readiness probe. | -| [deployment.readinessProbe.initialDelaySeconds](./values.yaml#L912) | int | `1` | The readiness probe initial delay seconds. | -| [deployment.readinessProbe.periodSeconds](./values.yaml#L914) | int | `15` | The readiness probe period seconds. | -| [deployment.readinessProbe.timeoutSeconds](./values.yaml#L916) | int | `1` | The readiness probe timeout seconds. | -| [deployment.readinessProbe.failureThreshold](./values.yaml#L918) | int | `5` | The readiness probe failure threshold. | -| [deployment.readinessProbe.successThreshold](./values.yaml#L920) | int | `1` | The readiness probe success threshold. | -| [extraAnnotations](./values.yaml#L927) | object | `{}` | Extra annotations to pass to the Botkube Pod. | -| [extraLabels](./values.yaml#L929) | object | `{}` | Extra labels to pass to the Botkube Pod. | -| [priorityClassName](./values.yaml#L931) | string | `""` | Priority class name for the Botkube Pod. | -| [nameOverride](./values.yaml#L934) | string | `""` | Fully override "botkube.name" template. | -| [fullnameOverride](./values.yaml#L936) | string | `""` | Fully override "botkube.fullname" template. | -| [resources](./values.yaml#L942) | object | `{}` | The Botkube Pod resource request and limits. We usually recommend not to specify default resources and to leave this as a conscious choice for the user. This also increases chances charts run on environments with little resources, such as Minikube. [Ref docs](https://kubernetes.io/docs/user-guide/compute-resources/) | -| [extraEnv](./values.yaml#L954) | list | `[{"name":"LOG_LEVEL_SOURCE_BOTKUBE_KUBERNETES","value":"debug"}]` | Extra environment variables to pass to the Botkube container. [Ref docs](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables). | -| [extraVolumes](./values.yaml#L968) | list | `[]` | Extra volumes to pass to the Botkube container. Mount it later with extraVolumeMounts. [Ref docs](https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/volume/#Volume). | -| [extraVolumeMounts](./values.yaml#L983) | list | `[]` | Extra volume mounts to pass to the Botkube container. [Ref docs](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#volumes-1). | -| [nodeSelector](./values.yaml#L1001) | object | `{}` | Node labels for Botkube Pod assignment. [Ref doc](https://kubernetes.io/docs/user-guide/node-selection/). | -| [tolerations](./values.yaml#L1005) | list | `[]` | Tolerations for Botkube Pod assignment. [Ref doc](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/). | -| [affinity](./values.yaml#L1009) | object | `{}` | Affinity for Botkube Pod assignment. [Ref doc](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). | -| [serviceAccount.create](./values.yaml#L1013) | bool | `true` | If true, a ServiceAccount is automatically created. | -| [serviceAccount.name](./values.yaml#L1016) | string | `""` | The name of the service account to use. If not set, a name is generated using the fullname template. | -| [serviceAccount.annotations](./values.yaml#L1018) | object | `{}` | Extra annotations for the ServiceAccount. | -| [extraObjects](./values.yaml#L1021) | list | `[]` | Extra Kubernetes resources to create. Helm templating is allowed as it is evaluated before creating the resources. | -| [analytics.disable](./values.yaml#L1049) | bool | `false` | If true, sending anonymous analytics is disabled. To learn what date we collect, see [Privacy Policy](https://docs.botkube.io/privacy#privacy-policy). | -| [configWatcher.enabled](./values.yaml#L1054) | bool | `true` | If true, restarts the Botkube Pod on config changes. | -| [configWatcher.tmpDir](./values.yaml#L1056) | string | `"/tmp/watched-cfg/"` | Directory, where watched configuration resources are stored. | -| [configWatcher.initialSyncTimeout](./values.yaml#L1059) | int | `0` | Timeout for the initial Config Watcher sync. If set to 0, waiting for Config Watcher sync will be skipped. In a result, configuration changes may not reload Botkube app during the first few seconds after Botkube startup. | -| [configWatcher.image.registry](./values.yaml#L1062) | string | `"ghcr.io"` | Config watcher image registry. | -| [configWatcher.image.repository](./values.yaml#L1064) | string | `"kubeshop/k8s-sidecar"` | Config watcher image repository. | -| [configWatcher.image.tag](./values.yaml#L1066) | string | `"in-cluster-config"` | Config watcher image tag. | -| [configWatcher.image.pullPolicy](./values.yaml#L1068) | string | `"IfNotPresent"` | Config watcher image pull policy. | -| [plugins](./values.yaml#L1071) | object | `{"cacheDir":"/tmp","repositories":{"botkube":{"url":"https://storage.googleapis.com/botkube-plugins-latest/plugins-index.yaml"}}}` | Configuration for Botkube executors and sources plugins. | -| [plugins.cacheDir](./values.yaml#L1073) | string | `"/tmp"` | Directory, where downloaded plugins are cached. | -| [plugins.repositories](./values.yaml#L1075) | object | `{"botkube":{"url":"https://storage.googleapis.com/botkube-plugins-latest/plugins-index.yaml"}}` | List of plugins repositories. | -| [plugins.repositories.botkube](./values.yaml#L1077) | object | `{"url":"https://storage.googleapis.com/botkube-plugins-latest/plugins-index.yaml"}` | This repository serves officially supported Botkube plugins. | -| [config](./values.yaml#L1081) | object | `{"provider":{"apiKey":"","endpoint":"https://api.botkube.io/graphql","identifier":""}}` | Configuration for synchronizing Botkube configuration. | -| [config.provider](./values.yaml#L1083) | object | `{"apiKey":"","endpoint":"https://api.botkube.io/graphql","identifier":""}` | Base provider definition. | -| [config.provider.identifier](./values.yaml#L1086) | string | `""` | Unique identifier for remote Botkube settings. If set to an empty string, Botkube won't fetch remote configuration. | -| [config.provider.endpoint](./values.yaml#L1088) | string | `"https://api.botkube.io/graphql"` | Endpoint to fetch Botkube settings from. | -| [config.provider.apiKey](./values.yaml#L1090) | string | `""` | Key passed as a `X-API-Key` header to the provider's endpoint. | +| [settings.log.disableColors](./values.yaml#L832) | bool | `false` | If true, disable ANSI colors in logging. Ignored when `json` formatter is used. | +| [settings.log.formatter](./values.yaml#L834) | string | `"json"` | Configures log format. Allowed values: `text`, `json`. | +| [settings.systemConfigMap](./values.yaml#L837) | object | `{"name":"botkube-system"}` | Botkube's system ConfigMap where internal data is stored. | +| [settings.persistentConfig](./values.yaml#L842) | object | `{"runtime":{"configMap":{"annotations":{},"name":"botkube-runtime-config"},"fileName":"_runtime_state.yaml"},"startup":{"configMap":{"annotations":{},"name":"botkube-startup-config"},"fileName":"_startup_state.yaml"}}` | Persistent config contains ConfigMap where persisted configuration is stored. The persistent configuration is evaluated from both chart upgrade and Botkube commands used in runtime. | +| [ssl.enabled](./values.yaml#L857) | bool | `false` | If true, specify cert path in `config.ssl.cert` property or K8s Secret in `config.ssl.existingSecretName`. | +| [ssl.existingSecretName](./values.yaml#L863) | string | `""` | Using existing SSL Secret. It MUST be in `botkube` Namespace. | +| [ssl.cert](./values.yaml#L866) | string | `""` | SSL Certificate file e.g certs/my-cert.crt. | +| [service](./values.yaml#L869) | object | `{"name":"metrics","port":2112,"targetPort":2112}` | Configures Service settings for ServiceMonitor CR. | +| [ingress](./values.yaml#L876) | object | `{"annotations":{"kubernetes.io/ingress.class":"nginx"},"create":false,"host":"HOST","tls":{"enabled":false,"secretName":""}}` | Configures Ingress settings that exposes MS Teams endpoint. [Ref doc](https://kubernetes.io/docs/concepts/services-networking/ingress/#the-ingress-resource). | +| [serviceMonitor](./values.yaml#L887) | object | `{"enabled":false,"interval":"10s","labels":{},"path":"/metrics","port":"metrics"}` | Configures ServiceMonitor settings. [Ref doc](https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#servicemonitor). | +| [deployment.annotations](./values.yaml#L897) | object | `{}` | Extra annotations to pass to the Botkube Deployment. | +| [deployment.livenessProbe](./values.yaml#L899) | object | `{"failureThreshold":35,"initialDelaySeconds":1,"periodSeconds":2,"successThreshold":1,"timeoutSeconds":1}` | Liveness probe. | +| [deployment.livenessProbe.initialDelaySeconds](./values.yaml#L901) | int | `1` | The liveness probe initial delay seconds. | +| [deployment.livenessProbe.periodSeconds](./values.yaml#L903) | int | `2` | The liveness probe period seconds. | +| [deployment.livenessProbe.timeoutSeconds](./values.yaml#L905) | int | `1` | The liveness probe timeout seconds. | +| [deployment.livenessProbe.failureThreshold](./values.yaml#L907) | int | `35` | The liveness probe failure threshold. | +| [deployment.livenessProbe.successThreshold](./values.yaml#L909) | int | `1` | The liveness probe success threshold. | +| [deployment.readinessProbe](./values.yaml#L912) | object | `{"failureThreshold":35,"initialDelaySeconds":1,"periodSeconds":2,"successThreshold":1,"timeoutSeconds":1}` | Readiness probe. | +| [deployment.readinessProbe.initialDelaySeconds](./values.yaml#L914) | int | `1` | The readiness probe initial delay seconds. | +| [deployment.readinessProbe.periodSeconds](./values.yaml#L916) | int | `2` | The readiness probe period seconds. | +| [deployment.readinessProbe.timeoutSeconds](./values.yaml#L918) | int | `1` | The readiness probe timeout seconds. | +| [deployment.readinessProbe.failureThreshold](./values.yaml#L920) | int | `35` | The readiness probe failure threshold. | +| [deployment.readinessProbe.successThreshold](./values.yaml#L922) | int | `1` | The readiness probe success threshold. | +| [extraAnnotations](./values.yaml#L929) | object | `{}` | Extra annotations to pass to the Botkube Pod. | +| [extraLabels](./values.yaml#L931) | object | `{}` | Extra labels to pass to the Botkube Pod. | +| [priorityClassName](./values.yaml#L933) | string | `""` | Priority class name for the Botkube Pod. | +| [nameOverride](./values.yaml#L936) | string | `""` | Fully override "botkube.name" template. | +| [fullnameOverride](./values.yaml#L938) | string | `""` | Fully override "botkube.fullname" template. | +| [resources](./values.yaml#L944) | object | `{}` | The Botkube Pod resource request and limits. We usually recommend not to specify default resources and to leave this as a conscious choice for the user. This also increases chances charts run on environments with little resources, such as Minikube. [Ref docs](https://kubernetes.io/docs/user-guide/compute-resources/) | +| [extraEnv](./values.yaml#L956) | list | `[{"name":"LOG_LEVEL_SOURCE_BOTKUBE_KUBERNETES","value":"debug"}]` | Extra environment variables to pass to the Botkube container. [Ref docs](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#environment-variables). | +| [extraVolumes](./values.yaml#L970) | list | `[]` | Extra volumes to pass to the Botkube container. Mount it later with extraVolumeMounts. [Ref docs](https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/volume/#Volume). | +| [extraVolumeMounts](./values.yaml#L985) | list | `[]` | Extra volume mounts to pass to the Botkube container. [Ref docs](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#volumes-1). | +| [nodeSelector](./values.yaml#L1003) | object | `{}` | Node labels for Botkube Pod assignment. [Ref doc](https://kubernetes.io/docs/user-guide/node-selection/). | +| [tolerations](./values.yaml#L1007) | list | `[]` | Tolerations for Botkube Pod assignment. [Ref doc](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/). | +| [affinity](./values.yaml#L1011) | object | `{}` | Affinity for Botkube Pod assignment. [Ref doc](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). | +| [serviceAccount.create](./values.yaml#L1015) | bool | `true` | If true, a ServiceAccount is automatically created. | +| [serviceAccount.name](./values.yaml#L1018) | string | `""` | The name of the service account to use. If not set, a name is generated using the fullname template. | +| [serviceAccount.annotations](./values.yaml#L1020) | object | `{}` | Extra annotations for the ServiceAccount. | +| [extraObjects](./values.yaml#L1023) | list | `[]` | Extra Kubernetes resources to create. Helm templating is allowed as it is evaluated before creating the resources. | +| [analytics.disable](./values.yaml#L1051) | bool | `false` | If true, sending anonymous analytics is disabled. To learn what date we collect, see [Privacy Policy](https://docs.botkube.io/privacy#privacy-policy). | +| [configWatcher.enabled](./values.yaml#L1056) | bool | `true` | If true, restarts the Botkube Pod on config changes. | +| [configWatcher.tmpDir](./values.yaml#L1058) | string | `"/tmp/watched-cfg/"` | Directory, where watched configuration resources are stored. | +| [configWatcher.initialSyncTimeout](./values.yaml#L1061) | int | `0` | Timeout for the initial Config Watcher sync. If set to 0, waiting for Config Watcher sync will be skipped. In a result, configuration changes may not reload Botkube app during the first few seconds after Botkube startup. | +| [configWatcher.image.registry](./values.yaml#L1064) | string | `"ghcr.io"` | Config watcher image registry. | +| [configWatcher.image.repository](./values.yaml#L1066) | string | `"kubeshop/k8s-sidecar"` | Config watcher image repository. | +| [configWatcher.image.tag](./values.yaml#L1068) | string | `"in-cluster-config"` | Config watcher image tag. | +| [configWatcher.image.pullPolicy](./values.yaml#L1070) | string | `"IfNotPresent"` | Config watcher image pull policy. | +| [plugins](./values.yaml#L1073) | object | `{"cacheDir":"/tmp","repositories":{"botkube":{"url":"https://storage.googleapis.com/botkube-plugins-latest/plugins-index.yaml"}}}` | Configuration for Botkube executors and sources plugins. | +| [plugins.cacheDir](./values.yaml#L1075) | string | `"/tmp"` | Directory, where downloaded plugins are cached. | +| [plugins.repositories](./values.yaml#L1077) | object | `{"botkube":{"url":"https://storage.googleapis.com/botkube-plugins-latest/plugins-index.yaml"}}` | List of plugins repositories. | +| [plugins.repositories.botkube](./values.yaml#L1079) | object | `{"url":"https://storage.googleapis.com/botkube-plugins-latest/plugins-index.yaml"}` | This repository serves officially supported Botkube plugins. | +| [config](./values.yaml#L1083) | object | `{"provider":{"apiKey":"","endpoint":"https://api.botkube.io/graphql","identifier":""}}` | Configuration for synchronizing Botkube configuration. | +| [config.provider](./values.yaml#L1085) | object | `{"apiKey":"","endpoint":"https://api.botkube.io/graphql","identifier":""}` | Base provider definition. | +| [config.provider.identifier](./values.yaml#L1088) | string | `""` | Unique identifier for remote Botkube settings. If set to an empty string, Botkube won't fetch remote configuration. | +| [config.provider.endpoint](./values.yaml#L1090) | string | `"https://api.botkube.io/graphql"` | Endpoint to fetch Botkube settings from. | +| [config.provider.apiKey](./values.yaml#L1092) | string | `""` | Key passed as a `X-API-Key` header to the provider's endpoint. | ### AWS IRSA on EKS support diff --git a/helm/botkube/values.yaml b/helm/botkube/values.yaml index 36fa176ae..8ffbdd368 100644 --- a/helm/botkube/values.yaml +++ b/helm/botkube/values.yaml @@ -828,8 +828,10 @@ settings: log: # -- Sets one of the log levels. Allowed values: `info`, `warn`, `debug`, `error`, `fatal`, `panic`. level: info - # -- If true, disable ANSI colors in logging. + # -- If true, disable ANSI colors in logging. Ignored when `json` formatter is used. disableColors: false + # -- Configures log format. Allowed values: `text`, `json`. + formatter: json # -- Botkube's system ConfigMap where internal data is stored. systemConfigMap: @@ -898,11 +900,11 @@ deployment: # -- The liveness probe initial delay seconds. initialDelaySeconds: 1 # -- The liveness probe period seconds. - periodSeconds: 15 + periodSeconds: 2 # -- The liveness probe timeout seconds. timeoutSeconds: 1 # -- The liveness probe failure threshold. - failureThreshold: 5 + failureThreshold: 35 # -- The liveness probe success threshold. successThreshold: 1 @@ -911,11 +913,11 @@ deployment: # -- The readiness probe initial delay seconds. initialDelaySeconds: 1 # -- The readiness probe period seconds. - periodSeconds: 15 + periodSeconds: 2 # -- The readiness probe timeout seconds. timeoutSeconds: 1 # -- The readiness probe failure threshold. - failureThreshold: 5 + failureThreshold: 35 # -- The readiness probe success threshold. successThreshold: 1 diff --git a/internal/cli/install/config.go b/internal/cli/install/config.go index c0fd81b92..addf52670 100644 --- a/internal/cli/install/config.go +++ b/internal/cli/install/config.go @@ -1,6 +1,8 @@ package install import ( + "time" + "github.com/kubeshop/botkube/internal/cli/install/helm" ) @@ -17,12 +19,16 @@ const ( ReleaseName = "botkube" // HelmRepoStable URL of the stable Botkube Helm charts repository. HelmRepoStable = "https://charts.botkube.io/" + // HelmChartName represents Botkube Helm chart name in a given Helm repository. + HelmChartName = "botkube" // LocalChartsPath path to Helm charts in botkube repository. - LocalChartsPath = "./helm/botkube/" + LocalChartsPath = "./helm/" ) // Config holds parameters for Botkube installation on cluster. type Config struct { Kubeconfig string HelmParams helm.Config + Watch bool + Timeout time.Duration } diff --git a/internal/cli/install/helm/config.go b/internal/cli/install/helm/config.go index 979b10d70..fca646094 100644 --- a/internal/cli/install/helm/config.go +++ b/internal/cli/install/helm/config.go @@ -1,8 +1,6 @@ package helm import ( - "time" - "helm.sh/helm/v3/pkg/cli/values" ) @@ -20,9 +18,6 @@ type Config struct { Namespace string SkipCRDs bool - Timeout time.Duration - Wait bool - WaitForJobs bool DisableHooks bool DryRun bool Force bool diff --git a/internal/cli/install/helm/install.go b/internal/cli/install/helm/install.go index d3f278f67..7eae546a3 100644 --- a/internal/cli/install/helm/install.go +++ b/internal/cli/install/helm/install.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "os" - "path" + "strings" "time" "github.com/AlecAivazis/survey/v2" @@ -20,6 +20,7 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/rest" + "github.com/kubeshop/botkube/internal/cli" "github.com/kubeshop/botkube/internal/cli/install/iox" "github.com/kubeshop/botkube/internal/cli/printer" "github.com/kubeshop/botkube/internal/ptr" @@ -45,13 +46,20 @@ func NewHelm(k8sCfg *rest.Config, forNamespace string) (*Helm, error) { // Install installs a given Helm chart. func (c *Helm) Install(ctx context.Context, status *printer.StatusPrinter, opts Config) (*release.Release, error) { histClient := action.NewHistory(c.helmCfg) - histClient.Max = 1 - _, err := histClient.Run(opts.ReleaseName) + rels, err := histClient.Run(opts.ReleaseName) var runFn Run switch { case err == nil: + if len(rels) > 0 { // it shouldn't happen, because there is not found error in such cases, however it better to be on the safe side. + if err := PrintReleaseStatus("Detected existing Botkube installation:", status, rels[len(rels)-1]); err != nil { + return nil, err + } + } else { + status.Infof("Detected existing Botkube installation") + } + prompt := &survey.Confirm{ - Message: "Detected existing Botkube installation. Do you want to upgrade it?", + Message: "Do you want to upgrade existing installation?", Default: true, } @@ -86,11 +94,12 @@ func (c *Helm) Install(ctx context.Context, status *printer.StatusPrinter, opts return nil, err } - status.Step("Installing %s Helm chart", opts.ChartName) + status.Step("Scheduling %s Helm chart", opts.ChartName) + status.End(true) // We may run into in issue temporary network issues. var rel *release.Release err = retry.Do(func() error { - rel, err = runFn(ctx, opts.ReleaseName, loadedChart, vals) // fixme values + rel, err = runFn(ctx, opts.ReleaseName, loadedChart, vals) return err }, retry.Attempts(3), retry.Delay(time.Second)) if err != nil { @@ -108,7 +117,8 @@ func (c *Helm) getChart(repoLocation string, chartName string, version string) ( } if isLocalDir(repoLocation) { - location = path.Join(repoLocation, chartName) + repoLocation = strings.TrimSuffix(repoLocation, "/") + location = fmt.Sprintf("%s/%s", repoLocation, chartName) chartOptions.RepoURL = "" } @@ -132,9 +142,8 @@ func (c *Helm) installAction(opts Config) Run { installCli.Namespace = opts.Namespace installCli.SkipCRDs = opts.SkipCRDs - installCli.Timeout = opts.Timeout - installCli.Wait = opts.Wait - installCli.WaitForJobs = opts.WaitForJobs + installCli.Wait = false // botkube CLI has a custom logic to do that + installCli.WaitForJobs = false installCli.DisableHooks = opts.DisableHooks installCli.DryRun = opts.DryRun installCli.Force = opts.Force @@ -156,9 +165,8 @@ func (c *Helm) upgradeAction(opts Config) Run { upgradeAction.Namespace = opts.Namespace upgradeAction.SkipCRDs = opts.SkipCRDs - upgradeAction.Timeout = opts.Timeout - upgradeAction.Wait = opts.Wait - upgradeAction.WaitForJobs = opts.WaitForJobs + upgradeAction.Wait = false // botkube CLI has a custom logic to do that + upgradeAction.WaitForJobs = false upgradeAction.DisableHooks = opts.DisableHooks upgradeAction.DryRun = opts.DryRun upgradeAction.Force = opts.Force @@ -186,7 +194,11 @@ func getConfiguration(k8sCfg *rest.Config, forNamespace string) (*action.Configu } debugLog := func(format string, v ...interface{}) { - // noop + if cli.VerboseMode.IsTracing() { + fmt.Print(" Helm log: ") // if enabled, we need to nest that under Helm step which was already printed with 2 spaces. + fmt.Printf(format, v...) + fmt.Println() + } } err := actionConfig.Init(helmCfg, forNamespace, helmDriver, debugLog) diff --git a/internal/cli/install/helm/status.go b/internal/cli/install/helm/status.go index b360921b1..df07b9c11 100644 --- a/internal/cli/install/helm/status.go +++ b/internal/cli/install/helm/status.go @@ -14,14 +14,14 @@ import ( var releaseGoTpl = ` {{ Key "Name" }} {{ .Name | Val }} {{ Key "Namespace" }} {{ .Namespace | Val }} + {{ Key "Version" }} {{ .Version | Val }} {{ Key "Last Deployed" }} {{ .LastDeployed | FmtDate | Val }} {{ Key "Revision" }} {{ .Revision | Val }} - {{ Key "Description" }} {{ .Description | Val }} ` // PrintReleaseStatus returns release description similar to what Helm does, // based on https://github.com/helm/helm/blob/f31d4fb3aacabf6102b3ec9214b3433a3dbf1812/cmd/helm/status.go#L126C1-L138C3 -func PrintReleaseStatus(status *printer.StatusPrinter, r *release.Release) error { +func PrintReleaseStatus(header string, status *printer.StatusPrinter, r *release.Release) error { if r == nil { return nil } @@ -30,19 +30,26 @@ func PrintReleaseStatus(status *printer.StatusPrinter, r *release.Release) error properties := make(map[string]string) properties["Name"] = r.Name - if !r.Info.LastDeployed.IsZero() { - properties["LastDeployed"] = r.Info.LastDeployed.Format(time.ANSIC) - } properties["Namespace"] = r.Namespace - properties["Status"] = r.Info.Status.String() properties["Revision"] = fmt.Sprintf("%d", r.Version) - properties["Description"] = r.Info.Description + + if r.Info != nil { + if !r.Info.LastDeployed.IsZero() { + properties["LastDeployed"] = r.Info.LastDeployed.Format(time.ANSIC) + } + properties["Status"] = r.Info.Status.String() + properties["Description"] = r.Info.Description + } + + if r.Chart != nil { + properties["Version"] = r.Chart.AppVersion() + } desc, err := renderer.Render(properties, true) if err != nil { return err } - status.InfoWithBody("Release details:", indent.String(desc, 4)) + status.InfoWithBody(header, indent.String(desc, 2)) return nil } diff --git a/internal/cli/install/install.go b/internal/cli/install/install.go index 3e243cfd7..ebf33b15e 100644 --- a/internal/cli/install/install.go +++ b/internal/cli/install/install.go @@ -4,26 +4,38 @@ import ( "context" "fmt" "io" + "time" + "github.com/morikuni/aec" "github.com/muesli/reflow/indent" "go.szostok.io/version/style" + "golang.org/x/sync/errgroup" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" "github.com/kubeshop/botkube/internal/cli" "github.com/kubeshop/botkube/internal/cli/install/helm" + "github.com/kubeshop/botkube/internal/cli/install/logs" "github.com/kubeshop/botkube/internal/cli/printer" "github.com/kubeshop/botkube/internal/kubex" ) +const messageInitialBufferSize = 100 + // Install installs Botkube Helm chart into cluster. func Install(ctx context.Context, w io.Writer, k8sCfg *kubex.ConfigWithMeta, opts Config) (err error) { + ctxWithTimeout, cancel := context.WithCancel(ctx) + if opts.Timeout > 0 { + ctxWithTimeout, cancel = context.WithTimeout(ctxWithTimeout, opts.Timeout) + } + defer cancel() + status := printer.NewStatus(w, "Installing Botkube on cluster...") defer func() { status.End(err == nil) + fmt.Println(aec.Show) }() switch opts.HelmParams.RepoLocation { @@ -55,19 +67,102 @@ func Install(ctx context.Context, w io.Writer, k8sCfg *kubex.ConfigWithMeta, opt } status.Step("Creating namespace %s", opts.HelmParams.Namespace) - err = ensureNamespaceCreated(ctx, k8sCfg.K8s, opts.HelmParams.Namespace) - status.End(err == nil) + clientset, err := kubernetes.NewForConfig(k8sCfg.K8s) if err != nil { return err } - rel, err := helmInstaller.Install(ctx, status, opts.HelmParams) + err = ensureNamespaceCreated(ctxWithTimeout, clientset, opts.HelmParams.Namespace) status.End(err == nil) if err != nil { return err } - if err := helm.PrintReleaseStatus(status, rel); err != nil { + timeBeforeInstall := time.Now() + parallel, _ := errgroup.WithContext(ctxWithTimeout) + + podScheduledIndicator := make(chan string) + podWaitResult := make(chan error, 1) + parallel.Go(func() error { + err := kubex.WaitForPod(ctxWithTimeout, clientset, opts.HelmParams.Namespace, opts.HelmParams.ReleaseName, kubex.PodReady(podScheduledIndicator, timeBeforeInstall)) + podWaitResult <- err + return nil + }) + rel, err := helmInstaller.Install(ctxWithTimeout, status, opts.HelmParams) + if err != nil { + return err + } + + if !opts.Watch { + status.Infof("Watching Botkube installation is disabled") + if err := helm.PrintReleaseStatus("Release details:", status, rel); err != nil { + return err + } + + return printSuccessInstallMessage(opts.HelmParams.Version, w) + } + + status.Step("Waiting until Botkube Pod is running") + var podName string + select { + case podName = <-podScheduledIndicator: + status.End(true) + case <-ctxWithTimeout.Done(): + return fmt.Errorf("Timed out waiting for Pod") + } + + messages := make(chan []byte, messageInitialBufferSize) + streamLogCtx, cancelStreamLogs := context.WithCancel(context.Background()) + defer cancelStreamLogs() + parallel.Go(func() error { + defer close(messages) + return logs.StartsLogsStreaming(streamLogCtx, clientset, opts.HelmParams.Namespace, podName, messages) + }) + + logsPrinter := logs.NewPrinter( + podName, + ) + + parallel.Go(func() error { + for { + select { + case <-ctxWithTimeout.Done(): // it's canceled on OS signals or if function passed to 'Go' method returns a non-nil error + return ctxWithTimeout.Err() + case err := <-podWaitResult: + time.Sleep(time.Second) + cancelStreamLogs() + return err + } + } + }) + + status.InfoWithBody("Streaming logs...", indent.String(fmt.Sprintf("Pod: %s\n", podName), 4)) + + parallel.Go(func() error { + for { + select { + case <-ctxWithTimeout.Done(): // it's canceled on OS signals or if function passed to 'Go' method returns a non-nil error + return ctxWithTimeout.Err() + case entry, ok := <-messages: + if !ok { + status.Infof("Finished logs streaming") + return nil + } + logsPrinter.PrintLine(string(entry)) + } + } + }) + + err = parallel.Wait() + if err != nil { + printErr := printFailedInstallMessage(opts.HelmParams.Version, opts.HelmParams.Namespace, podName, w) + if printErr != nil { + return fmt.Errorf("%s: %v", printErr, err) + } + return err + } + + if err := helm.PrintReleaseStatus("Release details:", status, rel); err != nil { return err } @@ -76,7 +171,7 @@ func Install(ctx context.Context, w io.Writer, k8sCfg *kubex.ConfigWithMeta, opt var successInstallGoTpl = ` - โ”‚ Botkube {{ .Version | Bold }} installed successfully! + โ”‚ Botkube {{ .Version | Bold }} installed successfully! ๐Ÿš€ โ”‚ To read more how to use CLI, check out the documentation on {{ .DocsURL | Underline | Blue }} ` @@ -93,7 +188,42 @@ func printSuccessInstallMessage(version string, w io.Writer) error { return fmt.Errorf("while rendering message: %v", err) } - _, err = fmt.Fprintln(w, out) + _, err = fmt.Fprint(w, out) + if err != nil { + return err + } + + return nil +} + +var failedInstallGoTpl = ` + โ”‚ {{ printf "Botkube %s installation failed ๐Ÿ˜ฟ" .Version | Bold | Red }} + โ”‚ To get all Botkube logs, run: + โ”‚ + โ”‚ kubectl logs -n {{ .Namespace }} pod/{{ .PodName }} + + โ”‚ To resolve the issue, see Botkube documentation on {{ .DocsURL | Underline | Blue }}. + | If that doesn't help, join our Slack community at {{ .SlackURL | Underline | Blue }}. + โ”‚ We'll be glad to get your Botkube up and running! +` + +func printFailedInstallMessage(version string, namespace string, name string, w io.Writer) error { + renderer := style.NewGoTemplateRender(style.DefaultConfig(failedInstallGoTpl)) + + props := map[string]string{ + "SlackURL": "https://join.botkube.io", + "DocsURL": "https://docs.botkube.io", + "Version": version, + "Namespace": namespace, + "PodName": name, + } + + out, err := renderer.Render(props, cli.IsSmartTerminal(w)) + if err != nil { + return err + } + + _, err = fmt.Fprint(w, out) if err != nil { return fmt.Errorf("while printing message: %v", err) } @@ -133,19 +263,14 @@ func printInstallationDetails(cfg *kubex.ConfigWithMeta, opts Config, status *pr } // ensureNamespaceCreated creates a k8s namespaces. If it already exists it does nothing. -func ensureNamespaceCreated(ctx context.Context, config *rest.Config, namespace string) error { - clientset, err := kubernetes.NewForConfig(config) - if err != nil { - return err - } - +func ensureNamespaceCreated(ctx context.Context, clientset *kubernetes.Clientset, namespace string) error { nsName := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: namespace, }, } - _, err = clientset.CoreV1().Namespaces().Create(ctx, nsName, metav1.CreateOptions{}) + _, err := clientset.CoreV1().Namespaces().Create(ctx, nsName, metav1.CreateOptions{}) if apierrors.IsAlreadyExists(err) { return nil } diff --git a/internal/cli/install/logs/json_parser.go b/internal/cli/install/logs/json_parser.go new file mode 100644 index 000000000..df88b08e3 --- /dev/null +++ b/internal/cli/install/logs/json_parser.go @@ -0,0 +1,54 @@ +package logs + +import ( + "encoding/json" + "fmt" + "sort" + + charmlog "github.com/charmbracelet/log" + "golang.org/x/exp/maps" + + "github.com/kubeshop/botkube/internal/cli" +) + +// JSONParser knows how to parse JSON formatted logs. +type JSONParser struct{} + +// ParseLineIntoCharm returns parsed log line with charm logger support. +func (j *JSONParser) ParseLineIntoCharm(line string) ([]any, charmlog.Level) { + result := j.parseLine(line) + if result == nil { + return nil, 0 + } + + var fields []any + + lvl := charmlog.ParseLevel(fmt.Sprint(result["level"])) + fields = append(fields, charmlog.LevelKey, lvl) + fields = append(fields, charmlog.MessageKey, result["msg"]) + + keys := maps.Keys(result) + sort.Strings(keys) + for _, k := range keys { + switch k { + case "level", "msg", "time": // already processed + continue + case "component", "url": + if !cli.VerboseMode.IsEnabled() { + continue // ignore those fields if verbose is not enabled + } + } + fields = append(fields, k, result[k]) + } + + return fields, lvl +} + +func (*JSONParser) parseLine(line string) map[string]any { + var out map[string]any + err := json.Unmarshal([]byte(line), &out) + if err != nil { + return nil + } + return out +} diff --git a/internal/cli/install/logs/k8s.go b/internal/cli/install/logs/k8s.go new file mode 100644 index 000000000..5f16fa023 --- /dev/null +++ b/internal/cli/install/logs/k8s.go @@ -0,0 +1,56 @@ +package logs + +import ( + "bufio" + "context" + "fmt" + "io" + "time" + + "github.com/avast/retry-go/v4" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + containerName = "botkube" +) + +// StartsLogsStreaming reads the data from request and writes into +// the out channel. It buffers data from requests until the newline or io.EOF +// occurs in the data, so it doesn't interleave logs sub-line +// when running concurrently. +// +// A successful read returns err == nil, not err == io.EOF. +// Because the function is defined to read from request until io.EOF, it does +// not treat an io.EOF as an error to be reported. +func StartsLogsStreaming(ctx context.Context, clientset *kubernetes.Clientset, namespace, name string, out chan<- []byte) error { + return retry.Do(func() error { + req := clientset.CoreV1().Pods(namespace).GetLogs(name, &v1.PodLogOptions{ + Container: containerName, + Follow: true, + Timestamps: false, + }) + podLogs, err := req.Stream(ctx) + if err != nil { + return fmt.Errorf("while opening log stream: %v", err) + } + defer podLogs.Close() + + r := bufio.NewReader(podLogs) + for { + bytes, readErr := r.ReadBytes('\n') + // write first, as there might be some chars already loaded + out <- bytes + switch readErr { + case nil: + case io.EOF: + return nil + case ctx.Err(): + return nil + default: + return fmt.Errorf("while reading log stream: %v", readErr) + } + } + }, retry.Attempts(3), retry.Delay(time.Second)) +} diff --git a/internal/cli/install/logs/printer.go b/internal/cli/install/logs/printer.go new file mode 100644 index 000000000..f9855c6ae --- /dev/null +++ b/internal/cli/install/logs/printer.go @@ -0,0 +1,55 @@ +package logs + +import ( + "fmt" + "os" + + "github.com/charmbracelet/log" + charmlog "github.com/charmbracelet/log" + "github.com/morikuni/aec" + + "github.com/kubeshop/botkube/internal/cli" +) + +// Printer knows how to print Botkube logs. +type Printer struct { + podName string + newLog chan string + stop chan struct{} + parser JSONParser + logger *log.Logger +} + +// NewPrinter creates a new Printer instance. +func NewPrinter(podName string) *Printer { + return &Printer{ + newLog: make(chan string, 10), + stop: make(chan struct{}), + logger: log.NewWithOptions(os.Stdout, log.Options{ + Formatter: log.TextFormatter, + }), + podName: podName, + parser: JSONParser{}, + } +} + +func (f *Printer) PrintLine(line string) { + fields, lvl := f.parser.ParseLineIntoCharm(line) + if fields == nil { // it was not recognized as JSON log entry, so let's print it as plain text. + f.printLogLine(line) + return + } + if lvl == charmlog.DebugLevel && !cli.VerboseMode.IsEnabled() { + return + } + + fmt.Print(aec.EraseLine(aec.EraseModes.Tail)) + fmt.Print(aec.Column(6)) + f.logger.With(fields...).Print(nil) +} + +func (f *Printer) printLogLine(line string) { + fmt.Print(aec.EraseLine(aec.EraseModes.Tail)) + fmt.Print(aec.Column(6)) + fmt.Print(line) +} diff --git a/internal/cli/printer/status.go b/internal/cli/printer/status.go index bb4085d3d..e240982ed 100644 --- a/internal/cli/printer/status.go +++ b/internal/cli/printer/status.go @@ -3,9 +3,11 @@ package printer import ( "fmt" "io" + "sync" "time" "github.com/fatih/color" + "github.com/morikuni/aec" "k8s.io/apimachinery/pkg/util/duration" "github.com/kubeshop/botkube/internal/cli" @@ -36,6 +38,8 @@ type StatusPrinter struct { timeStarted time.Time stage string + + sync.Mutex } // NewStatus returns a new Status instance. @@ -73,6 +77,8 @@ func (s *StatusPrinter) Step(stageFmt string, args ...interface{}) { // End marks started step as completed. func (s *StatusPrinter) End(success bool) { + s.Lock() + defer s.Unlock() if !s.spinner.Active() { return } @@ -100,6 +106,7 @@ func (s *StatusPrinter) Infof(format string, a ...interface{}) { // Ensure that previously started step is finished. Without that we will mess up our output. s.End(true) + fmt.Fprint(s.w, aec.Column(0)) fmt.Fprintf(s.w, " โ€ข %s\n", fmt.Sprintf(format, a...)) } @@ -113,6 +120,7 @@ func (s *StatusPrinter) Debugf(format string, a ...interface{}) { // Ensure that previously started step is finished. Without that we will mess up our output. s.End(true) + fmt.Fprint(s.w, aec.Column(0)) fmt.Fprintf(s.w, " โ€ข %s\n", fmt.Sprintf(format, a...)) } @@ -121,5 +129,6 @@ func (s *StatusPrinter) InfoWithBody(header, body string) { // Ensure that previously started step is finished. Without that we will mess up our output. s.End(true) + fmt.Fprint(s.w, aec.Column(0)) fmt.Fprintf(s.w, " โ€ข %s\n%s", header, body) } diff --git a/internal/kubex/wait.go b/internal/kubex/wait.go new file mode 100644 index 000000000..477b75ef6 --- /dev/null +++ b/internal/kubex/wait.go @@ -0,0 +1,85 @@ +package kubex + +import ( + "context" + "errors" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + watchtools "k8s.io/client-go/tools/watch" +) + +// WaitForPod watches a given Pod until the exitCondition is true. +func WaitForPod(ctx context.Context, clientset *kubernetes.Clientset, namespace string, name string, exitCondition watchtools.ConditionFunc) error { + selector := labels.SelectorFromSet(map[string]string{"app": name}).String() + lw := &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + options.LabelSelector = selector + return clientset.CoreV1().Pods(namespace).List(ctx, options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + options.LabelSelector = selector + return clientset.CoreV1().Pods(namespace).Watch(ctx, options) + }, + } + + _, err := watchtools.UntilWithSync(ctx, lw, &corev1.Pod{}, nil, exitCondition) + return err +} + +var ( + errPodRestartedWithError = errors.New("pod restarted with non zero exit code") +) + +// PodReady returns true if the Pod is ready. +func PodReady(podScheduledIndicator chan string, since time.Time) func(event watch.Event) (bool, error) { + informed := false + sinceK8sTime := metav1.NewTime(since) + return func(event watch.Event) (bool, error) { + switch t := event.Type; t { + case watch.Added, watch.Modified: + switch pod := event.Object.(type) { + case *corev1.Pod: + + createdAt := pod.GetObjectMeta().GetCreationTimestamp() + // we don't care about previously created pods, for example when user do some upgrades, we watch for a new Pod instance only. + if createdAt.Before(&sinceK8sTime) { + return false, nil + } + + if pod.Status.Phase == corev1.PodRunning && !informed { + informed = true + podScheduledIndicator <- pod.Name + close(podScheduledIndicator) + } + + for _, cond := range pod.Status.ContainerStatuses { + if !cond.Ready && cond.RestartCount > 0 { + // pod was already restarted because of the problem, we restart botkube on permanent errors mostly, so let's stop watching + return true, errPodRestartedWithError + } + } + + return isPodReady(pod), nil + } + } + + return false, nil + } +} + +// isPodReady returns true if a pod is ready; false otherwise. +func isPodReady(pod *corev1.Pod) bool { + for _, c := range pod.Status.Conditions { + if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue { + return true + } + } + return false +} diff --git a/internal/loggerx/logger.go b/internal/loggerx/logger.go index 474d5b1ee..2f109613f 100644 --- a/internal/loggerx/logger.go +++ b/internal/loggerx/logger.go @@ -21,7 +21,28 @@ func New(cfg config.Logger) logrus.FieldLogger { logLevel = logrus.InfoLevel } logger.SetLevel(logLevel) - logger.Formatter = &logrus.TextFormatter{FullTimestamp: true, DisableColors: cfg.DisableColors} + if cfg.Formatter == config.FormatterJson { + logger.Formatter = &logrus.JSONFormatter{} + } else { + logger.Formatter = &logrus.TextFormatter{FullTimestamp: true, DisableColors: cfg.DisableColors, ForceColors: true} + } return logger } + +// ExitOnError exits an app with a given error. +func ExitOnError(err error, context string) { + if err == nil { + return + } + log := &logrus.Logger{ + Out: os.Stdout, + Formatter: &logrus.JSONFormatter{}, + Hooks: make(logrus.LevelHooks), + Level: logrus.InfoLevel, + ExitFunc: os.Exit, + ReportCaller: false, + } + + log.Fatalf("%s: %s", context, err) +} diff --git a/pkg/config/config.go b/pkg/config/config.go index b7965c9dc..74b5e9659 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -520,10 +520,22 @@ type Settings struct { SACredentialsPathPrefix string `yaml:"saCredentialsPathPrefix"` } +// Formatter log formatter +type Formatter string + +const ( + // FormatterText text formatter for logging + FormatterText Formatter = "text" + + // FormatterJson json formatter for logging + FormatterJson Formatter = "json" +) + // Logger holds logger configuration parameters. type Logger struct { - Level string `yaml:"level"` - DisableColors bool `yaml:"disableColors"` + Level string `yaml:"level"` + DisableColors bool `yaml:"disableColors"` + Formatter Formatter `yaml:"formatter"` } // LifecycleServer contains configuration for the server with app lifecycle methods. diff --git a/pkg/config/testdata/TestLoadConfigSuccess/config.golden.yaml b/pkg/config/testdata/TestLoadConfigSuccess/config.golden.yaml index 709e917c5..717a7f331 100644 --- a/pkg/config/testdata/TestLoadConfigSuccess/config.golden.yaml +++ b/pkg/config/testdata/TestLoadConfigSuccess/config.golden.yaml @@ -156,6 +156,7 @@ settings: log: level: error disableColors: false + formatter: "" informersResyncPeriod: 30m0s kubeconfig: kubeconfig-from-env saCredentialsPathPrefix: "" diff --git a/pkg/execute/config_test.go b/pkg/execute/config_test.go index b3eee52a9..bd036ff2e 100644 --- a/pkg/execute/config_test.go +++ b/pkg/execute/config_test.go @@ -64,6 +64,7 @@ func TestConfigExecutorShowConfig(t *testing.T) { log: level: "" disableColors: false + formatter: "" informersResyncPeriod: 0s kubeconfig: "" saCredentialsPathPrefix: ""