diff --git a/cmd/botkube/main.go b/cmd/botkube/main.go
index eaf975613..ea4fc2c52 100644
--- a/cmd/botkube/main.go
+++ b/cmd/botkube/main.go
@@ -16,6 +16,7 @@ import (
"github.com/spf13/pflag"
"golang.org/x/sync/errgroup"
"k8s.io/apimachinery/pkg/api/meta"
+ "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/discovery"
cacheddiscovery "k8s.io/client-go/discovery/cached"
"k8s.io/client-go/dynamic"
@@ -27,6 +28,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
"github.com/kubeshop/botkube/internal/analytics"
+ "github.com/kubeshop/botkube/internal/lifecycle"
"github.com/kubeshop/botkube/internal/storage"
"github.com/kubeshop/botkube/pkg/bot"
"github.com/kubeshop/botkube/pkg/bot/interactive"
@@ -36,6 +38,7 @@ import (
"github.com/kubeshop/botkube/pkg/execute/kubectl"
"github.com/kubeshop/botkube/pkg/filterengine"
"github.com/kubeshop/botkube/pkg/httpsrv"
+ "github.com/kubeshop/botkube/pkg/notifier"
"github.com/kubeshop/botkube/pkg/recommendation"
"github.com/kubeshop/botkube/pkg/sink"
"github.com/kubeshop/botkube/pkg/sources"
@@ -159,7 +162,7 @@ func run() error {
commCfg := conf.Communications
var (
- notifiers []controller.Notifier
+ notifiers []notifier.Notifier
bots = map[string]bot.Bot{}
)
@@ -241,6 +244,39 @@ func run() error {
}
}
+ // Lifecycle server
+ if conf.Settings.LifecycleServer.Enabled {
+ lifecycleSrv := lifecycle.NewServer(
+ logger.WithField(componentLogFieldKey, "Lifecycle server"),
+ k8sCli,
+ conf.Settings.LifecycleServer,
+ conf.Settings.ClusterName,
+ func(msg string) error {
+ return notifier.SendPlaintextMessage(ctx, notifiers, msg)
+ },
+ )
+ errGroup.Go(func() error {
+ defer analytics.ReportPanicIfOccurs(logger, reporter)
+ return lifecycleSrv.Serve(ctx)
+ })
+ }
+
+ if conf.ConfigWatcher.Enabled {
+ err := config.WaitForWatcherSync(
+ ctx,
+ logger.WithField(componentLogFieldKey, "Config Watcher Sync"),
+ conf.ConfigWatcher,
+ )
+ if err != nil {
+ if err != wait.ErrWaitTimeout {
+ return reportFatalError("while waiting for Config Watcher sync", err)
+ }
+
+ // non-blocking error, move forward
+ logger.Warn("Config Watcher is still not synchronized. Read the logs of the sidecar container to see the cause. Continuing running BotKube...")
+ }
+ }
+
// Send help message
helpDB := storage.NewForHelp(conf.Settings.SystemConfigMap.Namespace, conf.Settings.SystemConfigMap.Name, k8sCli)
err = sendHelp(ctx, helpDB, conf.Settings.ClusterName, bots)
@@ -264,20 +300,6 @@ func run() error {
})
}
- // Start Config Watcher
- if conf.Settings.ConfigWatcher {
- cfgWatcher := controller.NewConfigWatcher(
- logger.WithField(componentLogFieldKey, "Config Watcher"),
- confDetails.CfgFilesToWatch,
- conf.Settings.ClusterName,
- notifiers,
- )
- errGroup.Go(func() error {
- defer analytics.ReportPanicIfOccurs(logger, reporter)
- return cfgWatcher.Do(ctx, cancelCtxFn)
- })
- }
-
recommFactory := recommendation.NewFactory(logger.WithField(componentLogFieldKey, "Recommendations"), dynamicCli)
// Create and start controller
diff --git a/global_config.yaml.tpl b/global_config.yaml.tpl
index 9614ad82d..465ab3f2e 100644
--- a/global_config.yaml.tpl
+++ b/global_config.yaml.tpl
@@ -238,10 +238,19 @@ settings:
# Cluster name to differentiate incoming messages
clusterName: not-configured
# Set true to enable config watcher
- configWatcher: true
+ # Server configuration which exposes functionality related to the app lifecycle.
+ lifecycleServer:
+ deployment:
+ name: botkube
+ namespace: botkube
+ port: "2113"
# Set false to disable upgrade notification
upgradeNotifier: true
+# Parameters for the config watcher container.
+configWatcher:
+ enabled: false # Used only on Kubernetes
+
# Map of enabled executors. The `executors` property name is an alias for a given configuration.
# It's used as a binding reference.
#
diff --git a/go.mod b/go.mod
index 92b966b53..fd87d976d 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,6 @@ require (
github.com/aws/aws-sdk-go v1.44.20
github.com/bwmarrin/discordgo v0.25.0
github.com/dustin/go-humanize v1.0.0
- github.com/fsnotify/fsnotify v1.5.4
github.com/go-playground/locales v0.14.0
github.com/go-playground/universal-translator v0.18.0
github.com/go-playground/validator/v10 v10.11.0
@@ -54,6 +53,7 @@ require (
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/francoispqt/gojay v1.2.13 // indirect
+ github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/fvbommel/sortorder v1.0.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.3 // indirect
github.com/go-errors/errors v1.0.1 // indirect
diff --git a/hack/goreleaser.sh b/hack/goreleaser.sh
index 7534ac39a..a23ee87a5 100755
--- a/hack/goreleaser.sh
+++ b/hack/goreleaser.sh
@@ -123,15 +123,15 @@ build() {
}
build_single() {
- export GORELEASER_CURRENT_TAG=v9.99.9-dev
+ export IMAGE_TAG=v9.99.9-dev
docker run --rm --privileged \
-v "$PWD":/go/src/github.com/kubeshop/botkube \
-v /var/run/docker.sock:/var/run/docker.sock \
-w /go/src/github.com/kubeshop/botkube \
- -e GORELEASER_CURRENT_TAG=${GORELEASER_CURRENT_TAG} \
+ -e IMAGE_TAG=${IMAGE_TAG} \
-e ANALYTICS_API_KEY="${ANALYTICS_API_KEY}" \
goreleaser/goreleaser build --single-target --rm-dist --snapshot --id botkube -o "./botkube"
- docker build -f "$PWD/build/Dockerfile" --platform "${IMAGE_PLATFORM}" -t "${IMAGE_REGISTRY}/${IMAGE_REPOSITORY}:${GORELEASER_CURRENT_TAG}" .
+ docker build -f "$PWD/build/Dockerfile" --platform "${IMAGE_PLATFORM}" -t "${IMAGE_REGISTRY}/${IMAGE_REPOSITORY}:${IMAGE_TAG}" .
rm "$PWD/botkube"
}
diff --git a/helm/botkube/README.md b/helm/botkube/README.md
index 395f2048f..bad3b217f 100644
--- a/helm/botkube/README.md
+++ b/helm/botkube/README.md
@@ -58,114 +58,121 @@ Controller for the BotKube Slack app which helps you monitor your Kubernetes clu
| [executors.kubectl-read-only.kubectl.commands.resources](./values.yaml#L262) | list | `["deployments","pods","namespaces","daemonsets","statefulsets","storageclasses","nodes","configmaps"]` | Configures which K8s resource are allowed. |
| [executors.kubectl-read-only.kubectl.defaultNamespace](./values.yaml#L264) | string | `"default"` | Configures the default Namespace for executing BotKube `kubectl` commands. If not set, uses the 'default'. |
| [executors.kubectl-read-only.kubectl.restrictAccess](./values.yaml#L266) | bool | `false` | If true, enables commands execution from configured channel only. |
-| [existingCommunicationsSecretName](./values.yaml#L276) | string | `""` | Configures existing Secret with communication settings. It MUST be in the `botkube` Namespace. |
-| [communications](./values.yaml#L283) | object | See the `values.yaml` file for full object. | Map of communication groups. Communication group contains settings for multiple communication platforms. The property name under `communications` object is an alias for a given configuration group. You can define multiple communication groups with different names. |
-| [communications.default-group.slack.enabled](./values.yaml#L288) | bool | `false` | If true, enables Slack bot. |
-| [communications.default-group.slack.channels](./values.yaml#L292) | object | `{"default":{"bindings":{"executors":["kubectl-read-only"],"sources":["k8s-err-events","k8s-recommendation-events"]},"name":"SLACK_CHANNEL","notification":{"disabled":false}}}` | Map of configured channels. The property name under `channels` object is an alias for a given configuration. |
-| [communications.default-group.slack.channels.default.name](./values.yaml#L295) | string | `"SLACK_CHANNEL"` | Slack channel name without '#' prefix where you have added BotKube and want to receive notifications in. |
-| [communications.default-group.slack.channels.default.notification.disabled](./values.yaml#L298) | bool | `false` | If true, the notifications are not sent to the channel. They can be enabled with `@BotKube` command anytime. |
-| [communications.default-group.slack.channels.default.bindings.executors](./values.yaml#L301) | list | `["kubectl-read-only"]` | Executors configuration for a given channel. |
-| [communications.default-group.slack.channels.default.bindings.sources](./values.yaml#L304) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given channel. |
-| [communications.default-group.slack.token](./values.yaml#L308) | string | `""` | Slack token. |
-| [communications.default-group.slack.notification.type](./values.yaml#L311) | string | `"short"` | Configures notification type that are sent. Possible values: `short`, `long`. |
-| [communications.default-group.socketSlack.enabled](./values.yaml#L316) | bool | `false` | If true, enables Slack bot. |
-| [communications.default-group.socketSlack.channels](./values.yaml#L320) | object | `{"default":{"bindings":{"executors":["kubectl-read-only"],"sources":["k8s-err-events","k8s-recommendation-events"]},"name":"SLACK_CHANNEL"}}` | Map of configured channels. The property name under `channels` object is an alias for a given configuration. |
-| [communications.default-group.socketSlack.channels.default.name](./values.yaml#L323) | string | `"SLACK_CHANNEL"` | Slack channel name without '#' prefix where you have added BotKube and want to receive notifications in. |
-| [communications.default-group.socketSlack.channels.default.bindings.executors](./values.yaml#L326) | list | `["kubectl-read-only"]` | Executors configuration for a given channel. |
-| [communications.default-group.socketSlack.channels.default.bindings.sources](./values.yaml#L329) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given channel. |
-| [communications.default-group.socketSlack.botToken](./values.yaml#L334) | string | `""` | Slack bot token for your own Slack app. [Ref doc](https://api.slack.com/authentication/token-types). |
-| [communications.default-group.socketSlack.appToken](./values.yaml#L337) | string | `""` | Slack app-level token for your own Slack app. [Ref doc](https://api.slack.com/authentication/token-types). |
-| [communications.default-group.socketSlack.notification.type](./values.yaml#L340) | string | `"short"` | Configures notification type that are sent. Possible values: `short`, `long`. |
-| [communications.default-group.mattermost.enabled](./values.yaml#L344) | bool | `false` | If true, enables Mattermost bot. |
-| [communications.default-group.mattermost.botName](./values.yaml#L346) | string | `"BotKube"` | User in Mattermost which belongs the specified Personal Access token. |
-| [communications.default-group.mattermost.url](./values.yaml#L348) | string | `"MATTERMOST_SERVER_URL"` | The URL (including http/https schema) where Mattermost is running. e.g https://example.com:9243 |
-| [communications.default-group.mattermost.token](./values.yaml#L350) | string | `"MATTERMOST_TOKEN"` | Personal Access token generated by BotKube user. |
-| [communications.default-group.mattermost.team](./values.yaml#L352) | string | `"MATTERMOST_TEAM"` | The Mattermost Team name where BotKube is added. |
-| [communications.default-group.mattermost.channels](./values.yaml#L356) | object | `{"default":{"bindings":{"executors":["kubectl-read-only"],"sources":["k8s-err-events","k8s-recommendation-events"]},"name":"MATTERMOST_CHANNEL","notification":{"disabled":false}}}` | Map of configured channels. The property name under `channels` object is an alias for a given configuration. |
-| [communications.default-group.mattermost.channels.default.name](./values.yaml#L360) | string | `"MATTERMOST_CHANNEL"` | The Mattermost channel name for receiving BotKube alerts. The BotKube user needs to be added to it. |
-| [communications.default-group.mattermost.channels.default.notification.disabled](./values.yaml#L363) | bool | `false` | If true, the notifications are not sent to the channel. They can be enabled with `@BotKube` command anytime. |
-| [communications.default-group.mattermost.channels.default.bindings.executors](./values.yaml#L366) | list | `["kubectl-read-only"]` | Executors configuration for a given channel. |
-| [communications.default-group.mattermost.channels.default.bindings.sources](./values.yaml#L369) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given channel. |
-| [communications.default-group.mattermost.notification.type](./values.yaml#L374) | string | `"short"` | Configures notification type that are sent. Possible values: `short`, `long`. |
-| [communications.default-group.teams.enabled](./values.yaml#L379) | bool | `false` | If true, enables MS Teams bot. |
-| [communications.default-group.teams.botName](./values.yaml#L381) | string | `"BotKube"` | The Bot name set while registering Bot to MS Teams. |
-| [communications.default-group.teams.appID](./values.yaml#L383) | string | `"APPLICATION_ID"` | The BotKube application ID generated while registering Bot to MS Teams. |
-| [communications.default-group.teams.appPassword](./values.yaml#L385) | string | `"APPLICATION_PASSWORD"` | The BotKube application password generated while registering Bot to MS Teams. |
-| [communications.default-group.teams.bindings.executors](./values.yaml#L388) | list | `["kubectl-read-only"]` | Executor bindings apply to all MS Teams channels where BotKube has access to. |
-| [communications.default-group.teams.bindings.sources](./values.yaml#L391) | list | `["k8s-err-events","k8s-recommendation-events"]` | Source bindings apply to all channels which have notification turned on with `@BotKube notifier start` command. |
-| [communications.default-group.teams.messagePath](./values.yaml#L395) | string | `"/bots/teams"` | The path in endpoint URL provided while registering BotKube to MS Teams. |
-| [communications.default-group.teams.port](./values.yaml#L397) | int | `3978` | The Service port for bot endpoint on BotKube container. |
-| [communications.default-group.discord.enabled](./values.yaml#L402) | bool | `false` | If true, enables Discord bot. |
-| [communications.default-group.discord.token](./values.yaml#L404) | string | `"DISCORD_TOKEN"` | BotKube Bot Token. |
-| [communications.default-group.discord.botID](./values.yaml#L406) | string | `"DISCORD_BOT_ID"` | BotKube Application Client ID. |
-| [communications.default-group.discord.channels](./values.yaml#L410) | object | `{"default":{"bindings":{"executors":["kubectl-read-only"],"sources":["k8s-err-events","k8s-recommendation-events"]},"id":"DISCORD_CHANNEL_ID","notification":{"disabled":false}}}` | Map of configured channels. The property name under `channels` object is an alias for a given configuration. |
-| [communications.default-group.discord.channels.default.id](./values.yaml#L414) | string | `"DISCORD_CHANNEL_ID"` | Discord channel ID for receiving BotKube alerts. The BotKube user needs to be added to it. |
-| [communications.default-group.discord.channels.default.notification.disabled](./values.yaml#L417) | bool | `false` | If true, the notifications are not sent to the channel. They can be enabled with `@BotKube` command anytime. |
-| [communications.default-group.discord.channels.default.bindings.executors](./values.yaml#L420) | list | `["kubectl-read-only"]` | Executors configuration for a given channel. |
-| [communications.default-group.discord.channels.default.bindings.sources](./values.yaml#L423) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given channel. |
-| [communications.default-group.discord.notification.type](./values.yaml#L428) | string | `"short"` | Configures notification type that are sent. Possible values: `short`, `long`. |
-| [communications.default-group.elasticsearch.enabled](./values.yaml#L433) | bool | `false` | If true, enables Elasticsearch. |
-| [communications.default-group.elasticsearch.awsSigning.enabled](./values.yaml#L437) | bool | `false` | If true, enables awsSigning using IAM for Elasticsearch hosted on AWS. Make sure AWS environment variables are set. [Ref doc](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html). |
-| [communications.default-group.elasticsearch.awsSigning.awsRegion](./values.yaml#L439) | string | `"us-east-1"` | AWS region where Elasticsearch is deployed. |
-| [communications.default-group.elasticsearch.awsSigning.roleArn](./values.yaml#L441) | string | `""` | AWS IAM Role arn to assume for credentials, use this only if you don't want to use the EC2 instance role or not running on AWS instance. |
-| [communications.default-group.elasticsearch.server](./values.yaml#L443) | string | `"ELASTICSEARCH_ADDRESS"` | The server URL, e.g https://example.com:9243 |
-| [communications.default-group.elasticsearch.username](./values.yaml#L445) | string | `"ELASTICSEARCH_USERNAME"` | Basic Auth username. |
-| [communications.default-group.elasticsearch.password](./values.yaml#L447) | string | `"ELASTICSEARCH_PASSWORD"` | Basic Auth password. |
-| [communications.default-group.elasticsearch.skipTLSVerify](./values.yaml#L450) | bool | `false` | If true, skips the verification of TLS certificate of the Elastic nodes. It's useful for clusters with self-signed certificates. |
-| [communications.default-group.elasticsearch.indices](./values.yaml#L454) | object | `{"default":{"bindings":{"sources":["k8s-err-events","k8s-recommendation-events"]},"name":"botkube","replicas":0,"shards":1,"type":"botkube-event"}}` | Map of configured indices. The `indices` property name is an alias for a given configuration. |
-| [communications.default-group.elasticsearch.indices.default.name](./values.yaml#L457) | string | `"botkube"` | Configures Elasticsearch index settings. |
-| [communications.default-group.elasticsearch.indices.default.bindings.sources](./values.yaml#L463) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given index. |
-| [communications.default-group.webhook.enabled](./values.yaml#L470) | bool | `false` | If true, enables Webhook. |
-| [communications.default-group.webhook.url](./values.yaml#L472) | string | `"WEBHOOK_URL"` | The Webhook URL, e.g.: https://example.com:80 |
-| [communications.default-group.webhook.bindings.sources](./values.yaml#L475) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for the webhook. |
-| [settings.clusterName](./values.yaml#L482) | string | `"not-configured"` | Cluster name to differentiate incoming messages. |
-| [settings.configWatcher](./values.yaml#L484) | bool | `true` | If true, restarts the BotKube Pod on config changes. |
-| [settings.upgradeNotifier](./values.yaml#L486) | bool | `true` | If true, notifies about new BotKube releases. |
-| [settings.log.level](./values.yaml#L490) | string | `"info"` | Sets one of the log levels. Allowed values: `info`, `warn`, `debug`, `error`, `fatal`, `panic`. |
-| [settings.log.disableColors](./values.yaml#L492) | bool | `false` | If true, disable ANSI colors in logging. |
-| [settings.systemConfigMap](./values.yaml#L495) | object | `{"name":"botkube-system"}` | BotKube's system ConfigMap where internal data is stored. |
-| [settings.persistentConfig](./values.yaml#L500) | 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#L515) | bool | `false` | If true, specify cert path in `config.ssl.cert` property or K8s Secret in `config.ssl.existingSecretName`. |
-| [ssl.existingSecretName](./values.yaml#L521) | string | `""` | Using existing SSL Secret. It MUST be in `botkube` Namespace. |
-| [ssl.cert](./values.yaml#L524) | string | `""` | SSL Certificate file e.g certs/my-cert.crt. |
-| [service](./values.yaml#L527) | object | `{"name":"metrics","port":2112,"targetPort":2112}` | Configures Service settings for ServiceMonitor CR. |
-| [ingress](./values.yaml#L534) | 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#L545) | 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#L555) | object | `{}` | Extra annotations to pass to the BotKube Deployment. |
-| [extraAnnotations](./values.yaml#L562) | object | `{}` | Extra annotations to pass to the BotKube Pod. |
-| [extraLabels](./values.yaml#L564) | object | `{}` | Extra labels to pass to the BotKube Pod. |
-| [priorityClassName](./values.yaml#L566) | string | `""` | Priority class name for the BotKube Pod. |
-| [nameOverride](./values.yaml#L569) | string | `""` | Fully override "botkube.name" template. |
-| [fullnameOverride](./values.yaml#L571) | string | `""` | Fully override "botkube.fullname" template. |
-| [resources](./values.yaml#L577) | 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#L589) | list | `[]` | 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#L601) | 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#L616) | 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#L634) | object | `{}` | Node labels for BotKube Pod assignment. [Ref doc](https://kubernetes.io/docs/user-guide/node-selection/). |
-| [tolerations](./values.yaml#L638) | list | `[]` | Tolerations for BotKube Pod assignment. [Ref doc](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/). |
-| [affinity](./values.yaml#L642) | object | `{}` | Affinity for BotKube Pod assignment. [Ref doc](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). |
-| [rbac](./values.yaml#L646) | object | `{"create":true,"rules":[{"apiGroups":["*"],"resources":["*"],"verbs":["get","watch","list"]}]}` | Role Based Access for BotKube Pod. [Ref doc](https://kubernetes.io/docs/admin/authorization/rbac/). |
-| [serviceAccount.create](./values.yaml#L655) | bool | `true` | If true, a ServiceAccount is automatically created. |
-| [serviceAccount.name](./values.yaml#L658) | string | `""` | The name of the service account to use. If not set, a name is generated using the fullname template. |
-| [serviceAccount.annotations](./values.yaml#L660) | object | `{}` | Extra annotations for the ServiceAccount. |
-| [extraObjects](./values.yaml#L663) | list | `[]` | Extra Kubernetes resources to create. Helm templating is allowed as it is evaluated before creating the resources. |
-| [analytics.disable](./values.yaml#L691) | bool | `false` | If true, sending anonymous analytics is disabled. To learn what date we collect, see [Privacy Policy](https://botkube.io/privacy#privacy-policy). |
-| [e2eTest.image.registry](./values.yaml#L697) | string | `"ghcr.io"` | Test runner image registry. |
-| [e2eTest.image.repository](./values.yaml#L699) | string | `"kubeshop/botkube-test"` | Test runner image repository. |
-| [e2eTest.image.pullPolicy](./values.yaml#L701) | string | `"IfNotPresent"` | Test runner image pull policy. |
-| [e2eTest.image.tag](./values.yaml#L703) | string | `"v9.99.9-dev"` | Test runner image tag. Default tag is `appVersion` from Chart.yaml. |
-| [e2eTest.deployment](./values.yaml#L705) | object | `{"waitTimeout":"3m"}` | Configures BotKube Deployment related data. |
-| [e2eTest.slack.botName](./values.yaml#L710) | string | `"botkube"` | Name of the BotKube bot to interact with during the e2e tests. |
-| [e2eTest.slack.testerName](./values.yaml#L712) | string | `"botkube_tester"` | Name of the BotKube Tester bot that sends messages during the e2e tests. |
-| [e2eTest.slack.testerAppToken](./values.yaml#L714) | string | `""` | Slack tester application token that interacts with BotKube bot. |
-| [e2eTest.slack.additionalContextMessage](./values.yaml#L716) | string | `""` | Additional message that is sent by Tester. You can pass e.g. pull request number or source link where these tests are run from. |
-| [e2eTest.slack.messageWaitTimeout](./values.yaml#L718) | string | `"1m"` | Message wait timeout. It defines how long we wait to ensure that notification were not sent when disabled. |
-| [e2eTest.discord.botName](./values.yaml#L721) | string | `"botkube"` | Name of the BotKube bot to interact with during the e2e tests. |
-| [e2eTest.discord.testerName](./values.yaml#L723) | string | `"botkube_tester"` | Name of the BotKube Tester bot that sends messages during the e2e tests. |
-| [e2eTest.discord.guildID](./values.yaml#L725) | string | `""` | Discord Guild ID (discord server ID) used to run e2e tests |
-| [e2eTest.discord.testerAppToken](./values.yaml#L727) | string | `""` | Discord tester application token that interacts with BotKube bot. |
-| [e2eTest.discord.additionalContextMessage](./values.yaml#L729) | string | `""` | Additional message that is sent by Tester. You can pass e.g. pull request number or source link where these tests are run from. |
-| [e2eTest.discord.messageWaitTimeout](./values.yaml#L731) | string | `"1m"` | Message wait timeout. It defines how long we wait to ensure that notification were not sent when disabled. |
+| [existingCommunicationsSecretName](./values.yaml#L277) | string | `""` | Configures existing Secret with communication settings. It MUST be in the `botkube` Namespace. To reload BotKube once it changes, add label `botkube.io/config-watch: "true"`. |
+| [communications](./values.yaml#L284) | object | See the `values.yaml` file for full object. | Map of communication groups. Communication group contains settings for multiple communication platforms. The property name under `communications` object is an alias for a given configuration group. You can define multiple communication groups with different names. |
+| [communications.default-group.slack.enabled](./values.yaml#L289) | bool | `false` | If true, enables Slack bot. |
+| [communications.default-group.slack.channels](./values.yaml#L293) | object | `{"default":{"bindings":{"executors":["kubectl-read-only"],"sources":["k8s-err-events","k8s-recommendation-events"]},"name":"SLACK_CHANNEL","notification":{"disabled":false}}}` | Map of configured channels. The property name under `channels` object is an alias for a given configuration. |
+| [communications.default-group.slack.channels.default.name](./values.yaml#L296) | string | `"SLACK_CHANNEL"` | Slack channel name without '#' prefix where you have added BotKube and want to receive notifications in. |
+| [communications.default-group.slack.channels.default.notification.disabled](./values.yaml#L299) | bool | `false` | If true, the notifications are not sent to the channel. They can be enabled with `@BotKube` command anytime. |
+| [communications.default-group.slack.channels.default.bindings.executors](./values.yaml#L302) | list | `["kubectl-read-only"]` | Executors configuration for a given channel. |
+| [communications.default-group.slack.channels.default.bindings.sources](./values.yaml#L305) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given channel. |
+| [communications.default-group.slack.token](./values.yaml#L309) | string | `""` | Slack token. |
+| [communications.default-group.slack.notification.type](./values.yaml#L312) | string | `"short"` | Configures notification type that are sent. Possible values: `short`, `long`. |
+| [communications.default-group.socketSlack.enabled](./values.yaml#L317) | bool | `false` | If true, enables Slack bot. |
+| [communications.default-group.socketSlack.channels](./values.yaml#L321) | object | `{"default":{"bindings":{"executors":["kubectl-read-only"],"sources":["k8s-err-events","k8s-recommendation-events"]},"name":"SLACK_CHANNEL"}}` | Map of configured channels. The property name under `channels` object is an alias for a given configuration. |
+| [communications.default-group.socketSlack.channels.default.name](./values.yaml#L324) | string | `"SLACK_CHANNEL"` | Slack channel name without '#' prefix where you have added BotKube and want to receive notifications in. |
+| [communications.default-group.socketSlack.channels.default.bindings.executors](./values.yaml#L327) | list | `["kubectl-read-only"]` | Executors configuration for a given channel. |
+| [communications.default-group.socketSlack.channels.default.bindings.sources](./values.yaml#L330) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given channel. |
+| [communications.default-group.socketSlack.botToken](./values.yaml#L335) | string | `""` | Slack bot token for your own Slack app. [Ref doc](https://api.slack.com/authentication/token-types). |
+| [communications.default-group.socketSlack.appToken](./values.yaml#L338) | string | `""` | Slack app-level token for your own Slack app. [Ref doc](https://api.slack.com/authentication/token-types). |
+| [communications.default-group.socketSlack.notification.type](./values.yaml#L341) | string | `"short"` | Configures notification type that are sent. Possible values: `short`, `long`. |
+| [communications.default-group.mattermost.enabled](./values.yaml#L345) | bool | `false` | If true, enables Mattermost bot. |
+| [communications.default-group.mattermost.botName](./values.yaml#L347) | string | `"BotKube"` | User in Mattermost which belongs the specified Personal Access token. |
+| [communications.default-group.mattermost.url](./values.yaml#L349) | string | `"MATTERMOST_SERVER_URL"` | The URL (including http/https schema) where Mattermost is running. e.g https://example.com:9243 |
+| [communications.default-group.mattermost.token](./values.yaml#L351) | string | `"MATTERMOST_TOKEN"` | Personal Access token generated by BotKube user. |
+| [communications.default-group.mattermost.team](./values.yaml#L353) | string | `"MATTERMOST_TEAM"` | The Mattermost Team name where BotKube is added. |
+| [communications.default-group.mattermost.channels](./values.yaml#L357) | object | `{"default":{"bindings":{"executors":["kubectl-read-only"],"sources":["k8s-err-events","k8s-recommendation-events"]},"name":"MATTERMOST_CHANNEL","notification":{"disabled":false}}}` | Map of configured channels. The property name under `channels` object is an alias for a given configuration. |
+| [communications.default-group.mattermost.channels.default.name](./values.yaml#L361) | string | `"MATTERMOST_CHANNEL"` | The Mattermost channel name for receiving BotKube alerts. The BotKube user needs to be added to it. |
+| [communications.default-group.mattermost.channels.default.notification.disabled](./values.yaml#L364) | bool | `false` | If true, the notifications are not sent to the channel. They can be enabled with `@BotKube` command anytime. |
+| [communications.default-group.mattermost.channels.default.bindings.executors](./values.yaml#L367) | list | `["kubectl-read-only"]` | Executors configuration for a given channel. |
+| [communications.default-group.mattermost.channels.default.bindings.sources](./values.yaml#L370) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given channel. |
+| [communications.default-group.mattermost.notification.type](./values.yaml#L375) | string | `"short"` | Configures notification type that are sent. Possible values: `short`, `long`. |
+| [communications.default-group.teams.enabled](./values.yaml#L380) | bool | `false` | If true, enables MS Teams bot. |
+| [communications.default-group.teams.botName](./values.yaml#L382) | string | `"BotKube"` | The Bot name set while registering Bot to MS Teams. |
+| [communications.default-group.teams.appID](./values.yaml#L384) | string | `"APPLICATION_ID"` | The BotKube application ID generated while registering Bot to MS Teams. |
+| [communications.default-group.teams.appPassword](./values.yaml#L386) | string | `"APPLICATION_PASSWORD"` | The BotKube application password generated while registering Bot to MS Teams. |
+| [communications.default-group.teams.bindings.executors](./values.yaml#L389) | list | `["kubectl-read-only"]` | Executor bindings apply to all MS Teams channels where BotKube has access to. |
+| [communications.default-group.teams.bindings.sources](./values.yaml#L392) | list | `["k8s-err-events","k8s-recommendation-events"]` | Source bindings apply to all channels which have notification turned on with `@BotKube notifier start` command. |
+| [communications.default-group.teams.messagePath](./values.yaml#L396) | string | `"/bots/teams"` | The path in endpoint URL provided while registering BotKube to MS Teams. |
+| [communications.default-group.teams.port](./values.yaml#L398) | int | `3978` | The Service port for bot endpoint on BotKube container. |
+| [communications.default-group.discord.enabled](./values.yaml#L403) | bool | `false` | If true, enables Discord bot. |
+| [communications.default-group.discord.token](./values.yaml#L405) | string | `"DISCORD_TOKEN"` | BotKube Bot Token. |
+| [communications.default-group.discord.botID](./values.yaml#L407) | string | `"DISCORD_BOT_ID"` | BotKube Application Client ID. |
+| [communications.default-group.discord.channels](./values.yaml#L411) | object | `{"default":{"bindings":{"executors":["kubectl-read-only"],"sources":["k8s-err-events","k8s-recommendation-events"]},"id":"DISCORD_CHANNEL_ID","notification":{"disabled":false}}}` | Map of configured channels. The property name under `channels` object is an alias for a given configuration. |
+| [communications.default-group.discord.channels.default.id](./values.yaml#L415) | string | `"DISCORD_CHANNEL_ID"` | Discord channel ID for receiving BotKube alerts. The BotKube user needs to be added to it. |
+| [communications.default-group.discord.channels.default.notification.disabled](./values.yaml#L418) | bool | `false` | If true, the notifications are not sent to the channel. They can be enabled with `@BotKube` command anytime. |
+| [communications.default-group.discord.channels.default.bindings.executors](./values.yaml#L421) | list | `["kubectl-read-only"]` | Executors configuration for a given channel. |
+| [communications.default-group.discord.channels.default.bindings.sources](./values.yaml#L424) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given channel. |
+| [communications.default-group.discord.notification.type](./values.yaml#L429) | string | `"short"` | Configures notification type that are sent. Possible values: `short`, `long`. |
+| [communications.default-group.elasticsearch.enabled](./values.yaml#L434) | bool | `false` | If true, enables Elasticsearch. |
+| [communications.default-group.elasticsearch.awsSigning.enabled](./values.yaml#L438) | bool | `false` | If true, enables awsSigning using IAM for Elasticsearch hosted on AWS. Make sure AWS environment variables are set. [Ref doc](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html). |
+| [communications.default-group.elasticsearch.awsSigning.awsRegion](./values.yaml#L440) | string | `"us-east-1"` | AWS region where Elasticsearch is deployed. |
+| [communications.default-group.elasticsearch.awsSigning.roleArn](./values.yaml#L442) | string | `""` | AWS IAM Role arn to assume for credentials, use this only if you don't want to use the EC2 instance role or not running on AWS instance. |
+| [communications.default-group.elasticsearch.server](./values.yaml#L444) | string | `"ELASTICSEARCH_ADDRESS"` | The server URL, e.g https://example.com:9243 |
+| [communications.default-group.elasticsearch.username](./values.yaml#L446) | string | `"ELASTICSEARCH_USERNAME"` | Basic Auth username. |
+| [communications.default-group.elasticsearch.password](./values.yaml#L448) | string | `"ELASTICSEARCH_PASSWORD"` | Basic Auth password. |
+| [communications.default-group.elasticsearch.skipTLSVerify](./values.yaml#L451) | bool | `false` | If true, skips the verification of TLS certificate of the Elastic nodes. It's useful for clusters with self-signed certificates. |
+| [communications.default-group.elasticsearch.indices](./values.yaml#L455) | object | `{"default":{"bindings":{"sources":["k8s-err-events","k8s-recommendation-events"]},"name":"botkube","replicas":0,"shards":1,"type":"botkube-event"}}` | Map of configured indices. The `indices` property name is an alias for a given configuration. |
+| [communications.default-group.elasticsearch.indices.default.name](./values.yaml#L458) | string | `"botkube"` | Configures Elasticsearch index settings. |
+| [communications.default-group.elasticsearch.indices.default.bindings.sources](./values.yaml#L464) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for a given index. |
+| [communications.default-group.webhook.enabled](./values.yaml#L471) | bool | `false` | If true, enables Webhook. |
+| [communications.default-group.webhook.url](./values.yaml#L473) | string | `"WEBHOOK_URL"` | The Webhook URL, e.g.: https://example.com:80 |
+| [communications.default-group.webhook.bindings.sources](./values.yaml#L476) | list | `["k8s-err-events","k8s-recommendation-events"]` | Notification sources configuration for the webhook. |
+| [settings.clusterName](./values.yaml#L483) | string | `"not-configured"` | Cluster name to differentiate incoming messages. |
+| [settings.lifecycleServer](./values.yaml#L486) | object | `{"enabled":true,"port":2113}` | Server configuration which exposes functionality related to the app lifecycle. |
+| [settings.upgradeNotifier](./values.yaml#L490) | bool | `true` | If true, notifies about new BotKube releases. |
+| [settings.log.level](./values.yaml#L494) | string | `"info"` | Sets one of the log levels. Allowed values: `info`, `warn`, `debug`, `error`, `fatal`, `panic`. |
+| [settings.log.disableColors](./values.yaml#L496) | bool | `false` | If true, disable ANSI colors in logging. |
+| [settings.systemConfigMap](./values.yaml#L499) | object | `{"name":"botkube-system"}` | BotKube's system ConfigMap where internal data is stored. |
+| [settings.persistentConfig](./values.yaml#L504) | 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#L519) | bool | `false` | If true, specify cert path in `config.ssl.cert` property or K8s Secret in `config.ssl.existingSecretName`. |
+| [ssl.existingSecretName](./values.yaml#L525) | string | `""` | Using existing SSL Secret. It MUST be in `botkube` Namespace. |
+| [ssl.cert](./values.yaml#L528) | string | `""` | SSL Certificate file e.g certs/my-cert.crt. |
+| [service](./values.yaml#L531) | object | `{"name":"metrics","port":2112,"targetPort":2112}` | Configures Service settings for ServiceMonitor CR. |
+| [ingress](./values.yaml#L538) | 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#L549) | 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#L559) | object | `{}` | Extra annotations to pass to the BotKube Deployment. |
+| [extraAnnotations](./values.yaml#L566) | object | `{}` | Extra annotations to pass to the BotKube Pod. |
+| [extraLabels](./values.yaml#L568) | object | `{}` | Extra labels to pass to the BotKube Pod. |
+| [priorityClassName](./values.yaml#L570) | string | `""` | Priority class name for the BotKube Pod. |
+| [nameOverride](./values.yaml#L573) | string | `""` | Fully override "botkube.name" template. |
+| [fullnameOverride](./values.yaml#L575) | string | `""` | Fully override "botkube.fullname" template. |
+| [resources](./values.yaml#L581) | 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#L593) | list | `[]` | 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#L605) | 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#L620) | 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#L638) | object | `{}` | Node labels for BotKube Pod assignment. [Ref doc](https://kubernetes.io/docs/user-guide/node-selection/). |
+| [tolerations](./values.yaml#L642) | list | `[]` | Tolerations for BotKube Pod assignment. [Ref doc](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/). |
+| [affinity](./values.yaml#L646) | object | `{}` | Affinity for BotKube Pod assignment. [Ref doc](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). |
+| [rbac](./values.yaml#L650) | object | `{"create":true,"rules":[{"apiGroups":["*"],"resources":["*"],"verbs":["get","watch","list"]}]}` | Role Based Access for BotKube Pod. [Ref doc](https://kubernetes.io/docs/admin/authorization/rbac/). |
+| [serviceAccount.create](./values.yaml#L659) | bool | `true` | If true, a ServiceAccount is automatically created. |
+| [serviceAccount.name](./values.yaml#L662) | string | `""` | The name of the service account to use. If not set, a name is generated using the fullname template. |
+| [serviceAccount.annotations](./values.yaml#L664) | object | `{}` | Extra annotations for the ServiceAccount. |
+| [extraObjects](./values.yaml#L667) | list | `[]` | Extra Kubernetes resources to create. Helm templating is allowed as it is evaluated before creating the resources. |
+| [analytics.disable](./values.yaml#L695) | bool | `false` | If true, sending anonymous analytics is disabled. To learn what date we collect, see [Privacy Policy](https://botkube.io/privacy#privacy-policy). |
+| [configWatcher.enabled](./values.yaml#L700) | bool | `true` | If true, restarts the BotKube Pod on config changes. |
+| [configWatcher.tmpDir](./values.yaml#L702) | string | `"/tmp/watched-cfg/"` | Directory, where watched configuration resources are stored. |
+| [configWatcher.initialSyncTimeout](./values.yaml#L705) | 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#L708) | string | `"ghcr.io"` | Config watcher image registry. |
+| [configWatcher.image.repository](./values.yaml#L710) | string | `"kubeshop/k8s-sidecar"` | Config watcher image repository. |
+| [configWatcher.image.tag](./values.yaml#L712) | string | `"ignore-initial-events"` | Config watcher image tag. |
+| [configWatcher.image.pullPolicy](./values.yaml#L714) | string | `"IfNotPresent"` | Config watcher image pull policy. |
+| [e2eTest.image.registry](./values.yaml#L720) | string | `"ghcr.io"` | Test runner image registry. |
+| [e2eTest.image.repository](./values.yaml#L722) | string | `"kubeshop/botkube-test"` | Test runner image repository. |
+| [e2eTest.image.pullPolicy](./values.yaml#L724) | string | `"IfNotPresent"` | Test runner image pull policy. |
+| [e2eTest.image.tag](./values.yaml#L726) | string | `"v9.99.9-dev"` | Test runner image tag. Default tag is `appVersion` from Chart.yaml. |
+| [e2eTest.deployment](./values.yaml#L728) | object | `{"waitTimeout":"3m"}` | Configures BotKube Deployment related data. |
+| [e2eTest.slack.botName](./values.yaml#L733) | string | `"botkube"` | Name of the BotKube bot to interact with during the e2e tests. |
+| [e2eTest.slack.testerName](./values.yaml#L735) | string | `"botkube_tester"` | Name of the BotKube Tester bot that sends messages during the e2e tests. |
+| [e2eTest.slack.testerAppToken](./values.yaml#L737) | string | `""` | Slack tester application token that interacts with BotKube bot. |
+| [e2eTest.slack.additionalContextMessage](./values.yaml#L739) | string | `""` | Additional message that is sent by Tester. You can pass e.g. pull request number or source link where these tests are run from. |
+| [e2eTest.slack.messageWaitTimeout](./values.yaml#L741) | string | `"1m"` | Message wait timeout. It defines how long we wait to ensure that notification were not sent when disabled. |
+| [e2eTest.discord.botName](./values.yaml#L744) | string | `"botkube"` | Name of the BotKube bot to interact with during the e2e tests. |
+| [e2eTest.discord.testerName](./values.yaml#L746) | string | `"botkube_tester"` | Name of the BotKube Tester bot that sends messages during the e2e tests. |
+| [e2eTest.discord.guildID](./values.yaml#L748) | string | `""` | Discord Guild ID (discord server ID) used to run e2e tests |
+| [e2eTest.discord.testerAppToken](./values.yaml#L750) | string | `""` | Discord tester application token that interacts with BotKube bot. |
+| [e2eTest.discord.additionalContextMessage](./values.yaml#L752) | string | `""` | Additional message that is sent by Tester. You can pass e.g. pull request number or source link where these tests are run from. |
+| [e2eTest.discord.messageWaitTimeout](./values.yaml#L754) | string | `"1m"` | Message wait timeout. It defines how long we wait to ensure that notification were not sent when disabled. |
### AWS IRSA on EKS support
diff --git a/helm/botkube/templates/communicationsecret.yaml b/helm/botkube/templates/communicationsecret.yaml
index 0a8028a82..d34c604cf 100644
--- a/helm/botkube/templates/communicationsecret.yaml
+++ b/helm/botkube/templates/communicationsecret.yaml
@@ -8,6 +8,7 @@ metadata:
helm.sh/chart: {{ include "botkube.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
+ botkube.io/config-watch: "true"
stringData:
comm_config.yaml: |
# Communication settings
diff --git a/helm/botkube/templates/deployment.yaml b/helm/botkube/templates/deployment.yaml
index 170c62f0e..4c0196ac6 100644
--- a/helm/botkube/templates/deployment.yaml
+++ b/helm/botkube/templates/deployment.yaml
@@ -15,6 +15,8 @@ metadata:
{{- end }}
spec:
replicas: {{ .Values.replicaCount }}
+ strategy:
+ type: Recreate # RollingUpdate doesn't work with SocketSlack integration as it requires a single connection to Slack API.
selector:
matchLabels:
component: controller
@@ -70,6 +72,8 @@ spec:
{{ end }}
- name: cache
mountPath: "/.kube/cache"
+ - name: cfg-watcher-tmp
+ mountPath: {{ .Values.configWatcher.tmpDir }}
env:
- name: BOTKUBE_CONFIG_PATHS
value: "/config/global_config.yaml,/config/comm_config.yaml,/config/{{ .Values.settings.persistentConfig.runtime.fileName}},/startup-config/{{ .Values.settings.persistentConfig.startup.fileName}}"
@@ -85,6 +89,10 @@ spec:
value: "{{.Release.Namespace}}"
- name: BOTKUBE_SETTINGS_PERSISTENT__CONFIG_STARTUP_CONFIG__MAP_NAMESPACE
value: "{{.Release.Namespace}}"
+ - name: BOTKUBE_SETTINGS_LIFECYCLE__SERVER_DEPLOYMENT_NAMESPACE
+ value: "{{.Release.Namespace}}"
+ - name: BOTKUBE_SETTINGS_LIFECYCLE__SERVER_DEPLOYMENT_NAME
+ value: "{{ include "botkube.fullname" . }}"
{{- with .Values.extraEnv }}
{{ toYaml . | nindent 12 }}
{{- end }}
@@ -92,7 +100,38 @@ spec:
resources:
{{ toYaml .Values.resources | indent 12 }}
{{- end }}
+ {{- if .Values.configWatcher.enabled }}
+ - name: cfg-watcher
+ image: "{{ .Values.configWatcher.image.registry }}/{{ .Values.configWatcher.image.repository }}:{{ .Values.configWatcher.image.tag }}"
+ imagePullPolicy: "{{ .Values.configWatcher.image.pullPolicy }}"
+ env:
+ - name: FOLDER
+ value: {{ .Values.configWatcher.tmpDir }}
+ - name: RESOURCE
+ value: "both"
+ - name: LOG_LEVEL
+ value: "DEBUG"
+ - name: NAMESPACE
+ value: "{{.Release.Namespace}}"
+ - name: LABEL
+ value: "botkube.io/config-watch"
+ - name: LABEL_VALUE
+ value: "true"
+ - name: REQ_URL
+ value: "http://{{ include "botkube.fullname" . }}:{{.Values.settings.lifecycleServer.port}}/reload"
+ - name: REQ_IGNORE_INITIAL_EVENT
+ value: "true"
+ - name: REQ_METHOD
+ value: "POST"
+ - name: IGNORE_ALREADY_PROCESSED
+ value: "true"
+ volumeMounts:
+ - name: cfg-watcher-tmp
+ mountPath: {{ .Values.configWatcher.tmpDir }}
+ {{- end }}
volumes:
+ - name: cfg-watcher-tmp
+ emptyDir: {}
- name: config-volume
projected:
sources:
diff --git a/helm/botkube/templates/global-config.yaml b/helm/botkube/templates/global-config.yaml
index 9bec78dd6..9eac11d87 100644
--- a/helm/botkube/templates/global-config.yaml
+++ b/helm/botkube/templates/global-config.yaml
@@ -7,6 +7,7 @@ metadata:
helm.sh/chart: {{ include "botkube.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
+ botkube.io/config-watch: "true"
data:
global_config.yaml: |
executors:
@@ -21,5 +22,8 @@ data:
filters:
{{- .Values.filters | toYaml | nindent 6 }}
+ configWatcher:
+ {{- .Values.configWatcher | toYaml | nindent 6 }}
+
analytics:
disable: {{ .Values.analytics.disable }}
diff --git a/helm/botkube/templates/persistent-config.yaml b/helm/botkube/templates/persistent-config.yaml
index 3d883fdd4..33e1b8cc6 100644
--- a/helm/botkube/templates/persistent-config.yaml
+++ b/helm/botkube/templates/persistent-config.yaml
@@ -12,6 +12,7 @@ metadata:
helm.sh/chart: {{ include "botkube.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
+ botkube.io/config-watch: "true"
data:
{{- $prevRuntimeCfgMap := lookup "v1" "ConfigMap" .Release.Namespace $runtimeStateCfgMap | default dict }}
{{- $prevRuntimeFile := index ( $prevRuntimeCfgMap.data | default dict ) .Values.settings.persistentConfig.runtime.fileName | default "" | fromYaml -}}
@@ -66,14 +67,13 @@ metadata:
helm.sh/chart: {{ include "botkube.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
+ botkube.io/config-watch: "false" # Explicitly don't watch this ConfigMap
data:
{{- $prevStartupCfgMap := lookup "v1" "ConfigMap" .Release.Namespace $startupStateCfgMap | default dict }}
{{- $prevStartupFile := index ( $prevStartupCfgMap.data | default dict ) .Values.settings.persistentConfig.startup.fileName | default "" | fromYaml -}}
{{- $mergedStartupCommunications := mustMergeOverwrite (mustDeepCopy (default (dict) $prevStartupFile.communications )) (mustDeepCopy .Values.communications) }}
{{- $mergedStartupFilters := mustMergeOverwrite (mustDeepCopy (default (dict) $prevStartupFile.filters )) (mustDeepCopy (default (dict) .Values.filters)) }}
- # This file has a special prefix to:
- # - load it as the last config file during BotKube startup,
- # - ignore it by Config Watcher.
+ # This file has a special prefix to load it as the last config file during BotKube startup.
{{ .Values.settings.persistentConfig.startup.fileName }}: |
communications:
{{- range $commGroupName,$commGroup := $mergedStartupCommunications }}
diff --git a/helm/botkube/templates/service.yaml b/helm/botkube/templates/service.yaml
index 4a70b64ce..cb81eb717 100644
--- a/helm/botkube/templates/service.yaml
+++ b/helm/botkube/templates/service.yaml
@@ -1,4 +1,4 @@
-{{- if or .Values.serviceMonitor.enabled (include "botkube.communication.team.enabled" $) }}
+{{- if or .Values.serviceMonitor.enabled (include "botkube.communication.team.enabled" $) (.Values.settings.lifecycleServer.enabled ) }}
apiVersion: v1
kind: Service
metadata:
@@ -12,6 +12,11 @@ metadata:
spec:
type: ClusterIP
ports:
+ {{- if .Values.settings.lifecycleServer.enabled }}
+ - name: "lifecycle"
+ port: {{ .Values.settings.lifecycleServer.port }}
+ targetPort: {{ .Values.settings.lifecycleServer.port }}
+ {{- end }}
{{- if .Values.serviceMonitor.enabled }}
- name: {{ .Values.service.name }}
port: {{ .Values.service.port }}
diff --git a/helm/botkube/templates/botkube-cm.yaml b/helm/botkube/templates/system-config.yaml
similarity index 80%
rename from helm/botkube/templates/botkube-cm.yaml
rename to helm/botkube/templates/system-config.yaml
index 23cd3e24d..f72c7ad42 100644
--- a/helm/botkube/templates/botkube-cm.yaml
+++ b/helm/botkube/templates/system-config.yaml
@@ -7,3 +7,4 @@ metadata:
helm.sh/chart: {{ include "botkube.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
+ botkube.io/config-watch: "false" # Explicitly don't watch this ConfigMap
diff --git a/helm/botkube/templates/systemroles.yaml b/helm/botkube/templates/systemroles.yaml
index 165e76a49..46961a308 100644
--- a/helm/botkube/templates/systemroles.yaml
+++ b/helm/botkube/templates/systemroles.yaml
@@ -12,6 +12,14 @@ rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["update", "get", "create"]
+ - apiGroups: [""]
+ resources: ["configmaps", "secrets"]
+ verbs: ["get", "watch", "list"]
+{{- if .Values.settings.lifecycleServer.enabled }}
+ - apiGroups: ["apps"]
+ resources: ["deployments"]
+ verbs: ["patch"]
+{{ end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
diff --git a/helm/botkube/values.yaml b/helm/botkube/values.yaml
index 40cef9cb8..08bc3f87c 100644
--- a/helm/botkube/values.yaml
+++ b/helm/botkube/values.yaml
@@ -267,6 +267,7 @@ executors:
# -- Configures existing Secret with communication settings. It MUST be in the `botkube` Namespace.
+# To reload BotKube once it changes, add label `botkube.io/config-watch: "true"`.
## Secret format:
## stringData:
## comm_config.yaml: |
@@ -480,8 +481,11 @@ communications:
settings:
# -- Cluster name to differentiate incoming messages.
clusterName: not-configured
- # -- If true, restarts the BotKube Pod on config changes.
- configWatcher: true
+
+ # -- Server configuration which exposes functionality related to the app lifecycle.
+ lifecycleServer:
+ enabled: true
+ port: 2113
# -- If true, notifies about new BotKube releases.
upgradeNotifier: true
## BotKube logging settings.
@@ -502,7 +506,7 @@ settings:
configMap:
name: botkube-startup-config
annotations: {}
- fileName: "__startup_state.yaml"
+ fileName: "_startup_state.yaml"
runtime:
configMap:
name: botkube-runtime-config
@@ -690,6 +694,25 @@ analytics:
# see [Privacy Policy](https://botkube.io/privacy#privacy-policy).
disable: false
+## Parameters for the config watcher container.
+configWatcher:
+ # -- If true, restarts the BotKube Pod on config changes.
+ enabled: true
+ # -- Directory, where watched configuration resources are stored.
+ tmpDir: "/tmp/watched-cfg/"
+ # -- 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.
+ initialSyncTimeout: 0
+ image:
+ # -- Config watcher image registry.
+ registry: ghcr.io
+ # -- Config watcher image repository.
+ repository: kubeshop/k8s-sidecar # kiwigrid/k8s-sidecar:1.19.5 - see https://github.com/kubeshop/k8s-sidecar/pull/1
+ # -- Config watcher image tag.
+ tag: ignore-initial-events
+ # -- Config watcher image pull policy.
+ pullPolicy: IfNotPresent
+
## Parameters for the test container with E2E tests.
e2eTest:
image:
diff --git a/internal/lifecycle/server.go b/internal/lifecycle/server.go
new file mode 100644
index 000000000..6ae0e29ba
--- /dev/null
+++ b/internal/lifecycle/server.go
@@ -0,0 +1,69 @@
+package lifecycle
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/gorilla/mux"
+ "github.com/sirupsen/logrus"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/client-go/kubernetes"
+
+ "github.com/kubeshop/botkube/pkg/config"
+ "github.com/kubeshop/botkube/pkg/httpsrv"
+)
+
+const (
+ k8sDeploymentRestartPatchFmt = `{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":"%s"}}}}}`
+ reloadMsgFmt = ":arrows_counterclockwise: Configuration reload requested for cluster '%s'. Hold on a sec..."
+)
+
+// SendMessageFn defines a function which sends a given message.
+type SendMessageFn func(msg string) error
+
+// NewServer creates a new httpsrv.Server that exposes lifecycle methods as HTTP endpoints.
+func NewServer(log logrus.FieldLogger, k8sCli kubernetes.Interface, cfg config.LifecycleServer, clusterName string, sendMsgFn SendMessageFn) *httpsrv.Server {
+ addr := fmt.Sprintf(":%d", cfg.Port)
+ router := mux.NewRouter()
+ reloadHandler := newReloadHandler(log, k8sCli, cfg.Deployment, clusterName, sendMsgFn)
+ router.HandleFunc("/reload", reloadHandler)
+ return httpsrv.New(log, addr, router)
+}
+
+func newReloadHandler(log logrus.FieldLogger, k8sCli kubernetes.Interface, deploy config.K8sResourceRef, clusterName string, sendMsgFn SendMessageFn) http.HandlerFunc {
+ return func(writer http.ResponseWriter, request *http.Request) {
+ log.Info("Reload requested. Sending last message before exit...")
+ err := sendMsgFn(fmt.Sprintf(reloadMsgFmt, clusterName))
+ if err != nil {
+ errMsg := fmt.Sprintf("while sending last message: %s", err.Error())
+ log.Errorf(errMsg)
+
+ // continue anyway, this is a non-blocking error
+ }
+
+ log.Infof(`Reloading te the deployment "%s/%s"...`, deploy.Namespace, deploy.Name)
+ // This is what `kubectl rollout restart` does.
+ restartData := fmt.Sprintf(k8sDeploymentRestartPatchFmt, time.Now().String())
+ ctx := request.Context()
+ _, err = k8sCli.AppsV1().Deployments(deploy.Namespace).Patch(
+ ctx,
+ deploy.Name,
+ types.StrategicMergePatchType,
+ []byte(restartData),
+ metav1.PatchOptions{FieldManager: "kubectl-rollout"},
+ )
+ if err != nil {
+ errMsg := fmt.Sprintf("while restarting the Deployment: %s", err.Error())
+ log.Error(errMsg)
+ http.Error(writer, errMsg, http.StatusInternalServerError)
+ }
+
+ writer.WriteHeader(http.StatusOK)
+ _, err = writer.Write([]byte(fmt.Sprintf(`Deployment "%s/%s" restarted successfully.`, deploy.Namespace, deploy.Name)))
+ if err != nil {
+ log.Errorf("while writing success response: %s", err.Error())
+ }
+ }
+}
diff --git a/internal/lifecycle/server_test.go b/internal/lifecycle/server_test.go
new file mode 100644
index 000000000..beef08d13
--- /dev/null
+++ b/internal/lifecycle/server_test.go
@@ -0,0 +1,67 @@
+package lifecycle
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ logtest "github.com/sirupsen/logrus/hooks/test"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ appsv1 "k8s.io/api/apps/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes/fake"
+
+ "github.com/kubeshop/botkube/pkg/config"
+)
+
+func TestNewReloadHandler_HappyPath(t *testing.T) {
+ // given
+ clusterName := "foo"
+
+ expectedMsg := fmt.Sprintf(":arrows_counterclockwise: Configuration reload requested for cluster '%s'. Hold on a sec...", clusterName)
+ expectedResponse := `Deployment "namespace/name" restarted successfully.`
+
+ expectedStatusCode := http.StatusOK
+ deployCfg := config.K8sResourceRef{
+ Name: "name",
+ Namespace: "namespace",
+ }
+ inputDeploy := &appsv1.Deployment{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: deployCfg.Name,
+ Namespace: deployCfg.Namespace,
+ },
+ }
+ sendMsgFn := SendMessageFn(func(msg string) error {
+ assert.Equal(t, expectedMsg, msg)
+ return nil
+ })
+ logger, _ := logtest.NewNullLogger()
+ k8sCli := fake.NewSimpleClientset(inputDeploy)
+
+ req := httptest.NewRequest(http.MethodPost, "/reload", nil)
+ writer := httptest.NewRecorder()
+ handler := newReloadHandler(logger, k8sCli, deployCfg, clusterName, sendMsgFn)
+
+ // when
+ handler(writer, req)
+
+ res := writer.Result()
+ defer res.Body.Close()
+ data, err := io.ReadAll(res.Body)
+ require.NoError(t, err)
+
+ // then
+ assert.Equal(t, expectedStatusCode, res.StatusCode)
+ assert.Equal(t, expectedResponse, string(data))
+
+ actualDeploy, err := k8sCli.AppsV1().Deployments(deployCfg.Namespace).Get(context.Background(), deployCfg.Name, metav1.GetOptions{})
+ require.NoError(t, err)
+
+ _, exists := actualDeploy.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"]
+ assert.True(t, exists)
+}
diff --git a/pkg/bot/bot.go b/pkg/bot/bot.go
index 3292017bf..87beb26f5 100644
--- a/pkg/bot/bot.go
+++ b/pkg/bot/bot.go
@@ -4,15 +4,15 @@ import (
"context"
"github.com/kubeshop/botkube/pkg/config"
- "github.com/kubeshop/botkube/pkg/controller"
"github.com/kubeshop/botkube/pkg/execute"
+ "github.com/kubeshop/botkube/pkg/notifier"
)
// Bot connects to communication channels and reads/sends messages. It is a two-way integration.
type Bot interface {
Start(ctx context.Context) error
BotName() string
- controller.Notifier
+ notifier.Notifier
}
// ExecutorFactory facilitates creation of execute.Executor instances.
diff --git a/pkg/bot/teams.go b/pkg/bot/teams.go
index 3d569e354..c8c8650d6 100644
--- a/pkg/bot/teams.go
+++ b/pkg/bot/teams.go
@@ -71,7 +71,8 @@ type Teams struct {
conversations map[string]conversation
notifyMutex sync.Mutex
botMentionRegex *regexp.Regexp
- mdFormatter interactive.MDFormatter
+ longFormatter interactive.MDFormatter
+ shortFormatter interactive.MDFormatter
botName string
AppID string
@@ -102,7 +103,9 @@ func NewTeams(log logrus.FieldLogger, commGroupName string, cfg config.Teams, cl
if msgPath == "" {
msgPath = "/"
}
- mdFormatter := interactive.NewMDFormatter(mdLineFormatter, interactive.DefaultMDHeaderFormatter)
+ longFormatter := interactive.NewMDFormatter(longLineFormatter, interactive.DefaultMDHeaderFormatter)
+ shortFormatter := interactive.NewMDFormatter(shortLineFormatter, interactive.DefaultMDHeaderFormatter)
+
return &Teams{
log: log,
executorFactory: executorFactory,
@@ -118,7 +121,8 @@ func NewTeams(log logrus.FieldLogger, commGroupName string, cfg config.Teams, cl
Port: port,
conversations: make(map[string]conversation),
botMentionRegex: botMentionRegex,
- mdFormatter: mdFormatter,
+ longFormatter: longFormatter,
+ shortFormatter: shortFormatter,
}, nil
}
@@ -299,9 +303,9 @@ func (b *Teams) convertInteractiveMessage(in interactive.Message) string {
if in.HasSections() {
// MS Teams doesn't respect multiple new lines, so it needs to be rendered
// with `
` tags instead Β―\_(γ)_/Β―
- return interactive.MessageToMarkdown(b.mdFormatter, in)
+ return interactive.MessageToMarkdown(b.longFormatter, in)
}
- return interactive.MessageToMarkdown(b.mdFormatter, in)
+ return interactive.MessageToMarkdown(b.shortFormatter, in)
}
func (b *Teams) putRequest(u string, data []byte) (err error) {
@@ -536,14 +540,20 @@ func teamsBotMentionRegex(botName string) (*regexp.Regexp, error) {
return botMentionRegex, nil
}
-// MSTeamsLineFmt represents new line formatting for MS Teams.
+// longLineFormatter represents new line formatting for MS Teams where message has multiple sections.
// Unfortunately, it's different from all others integrations.
-func mdLineFormatter(msg string) string {
+func longLineFormatter(msg string) string {
// e.g. `:rocket:` is not supported by MS Teams, so we need to replace it with actual emoji
msg = replaceEmojiTagsWithActualOne(msg)
return fmt.Sprintf("%s
", msg)
}
+func shortLineFormatter(msg string) string {
+ // e.g. `:rocket:` is not supported by MS Teams, so we need to replace it with actual emoji
+ msg = replaceEmojiTagsWithActualOne(msg)
+ return fmt.Sprintf("%s\n", msg)
+}
+
// replaceEmojiTagsWithActualOne replaces the emoji tag with actual emoji.
func replaceEmojiTagsWithActualOne(content string) string {
return mdEmojiTag.ReplaceAllStringFunc(content, func(s string) string {
@@ -553,5 +563,9 @@ func replaceEmojiTagsWithActualOne(content string) string {
// emojiMapping holds mapping between emoji tags and actual ones.
var emojiMapping = map[string]string{
- ":rocket:": "π",
+ ":rocket:": "π",
+ ":white_check_mark:": "β
",
+ ":arrows_counterclockwise:": "π",
+ ":crossed_fingers:": "π€",
+ ":exclamation:": "β",
}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 4c857695e..806a9ac77 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -25,12 +25,11 @@ var defaultConfiguration []byte
var configPathsFlag []string
const (
- configEnvVariablePrefix = "BOTKUBE_"
- configDelimiter = "."
- camelCaseDelimiter = "__"
- nestedFieldDelimiter = "_"
- specialConfigFileNamePrefix = "_"
- specialIgnoredConfigFileNamePrefix = "__"
+ configEnvVariablePrefix = "BOTKUBE_"
+ configDelimiter = "."
+ camelCaseDelimiter = "__"
+ nestedFieldDelimiter = "_"
+ specialConfigFileNamePrefix = "_"
)
const (
@@ -130,8 +129,9 @@ type Config struct {
Communications map[string]Communications `yaml:"communications" validate:"required,min=1,dive"`
Filters Filters `yaml:"filters"`
- Analytics Analytics `yaml:"analytics"`
- Settings Settings `yaml:"settings"`
+ Analytics Analytics `yaml:"analytics"`
+ Settings Settings `yaml:"settings"`
+ ConfigWatcher CfgWatcher `yaml:"configWatcher"`
}
// ChannelBindingsByName contains configuration bindings per channel.
@@ -497,14 +497,21 @@ type Commands struct {
Resources []string `yaml:"resources"`
}
+// CfgWatcher describes configuration for watching the configuration.
+type CfgWatcher struct {
+ Enabled bool `yaml:"enabled"`
+ InitialSyncTimeout time.Duration `yaml:"initialSyncTimeout"`
+ TmpDir string `yaml:"tmpDir"`
+}
+
// Settings contains BotKube's related configuration.
type Settings struct {
ClusterName string `yaml:"clusterName"`
- ConfigWatcher bool `yaml:"configWatcher"`
UpgradeNotifier bool `yaml:"upgradeNotifier"`
- SystemConfigMap K8sConfigMapRef `yaml:"systemConfigMap"`
+ SystemConfigMap K8sResourceRef `yaml:"systemConfigMap"`
PersistentConfig PersistentConfig `yaml:"persistentConfig"`
MetricsPort string `yaml:"metricsPort"`
+ LifecycleServer LifecycleServer `yaml:"lifecycleServer"`
Log struct {
Level string `yaml:"level"`
DisableColors bool `yaml:"disableColors"`
@@ -513,6 +520,13 @@ type Settings struct {
Kubeconfig string `yaml:"kubeconfig"`
}
+// LifecycleServer contains configuration for the server with app lifecycle methods.
+type LifecycleServer struct {
+ Enabled bool `yaml:"enabled"`
+ Port int `yaml:"port"` // String for consistency
+ Deployment K8sResourceRef `yaml:"deployment"`
+}
+
// PersistentConfig contains configuration for persistent storage.
type PersistentConfig struct {
Startup PartialPersistentConfig `yaml:"startup"`
@@ -521,12 +535,12 @@ type PersistentConfig struct {
// PartialPersistentConfig contains configuration for persistent storage of a given type.
type PartialPersistentConfig struct {
- FileName string `yaml:"fileName"`
- ConfigMap K8sConfigMapRef `yaml:"configMap"`
+ FileName string `yaml:"fileName"`
+ ConfigMap K8sResourceRef `yaml:"configMap"`
}
-// K8sConfigMapRef holds the configuration for a ConfigMap.
-type K8sConfigMapRef struct {
+// K8sResourceRef holds the configuration for a Kubernetes resource.
+type K8sResourceRef struct {
Name string `yaml:"name,omitempty"`
Namespace string `yaml:"namespace,omitempty"`
}
@@ -540,7 +554,6 @@ type PathsGetter func() []string
// LoadWithDefaultsDetails holds the LoadWithDefaults function details.
type LoadWithDefaultsDetails struct {
- CfgFilesToWatch []string
ValidateWarnings error
}
@@ -587,7 +600,6 @@ func LoadWithDefaults(getCfgPaths PathsGetter) (*Config, LoadWithDefaultsDetails
}
return &cfg, LoadWithDefaultsDetails{
- CfgFilesToWatch: getCfgFilesToWatch(configPaths),
ValidateWarnings: result.Warnings.ErrorOrNil(),
}, nil
}
@@ -643,22 +655,6 @@ func sortCfgFiles(paths []string) []string {
return append(ordinaryCfgFiles, specialCfgFiles...)
}
-// getCfgFilesToWatch excludes the files that has specialIgnoredConfigFileNamePrefix from the paths.
-func getCfgFilesToWatch(paths []string) []string {
- var filesToWatch []string
- for _, path := range paths {
- _, filename := filepath.Split(path)
-
- if strings.HasPrefix(filename, specialIgnoredConfigFileNamePrefix) {
- continue
- }
-
- filesToWatch = append(filesToWatch, path)
- }
-
- return filesToWatch
-}
-
// IdentifiableMap provides an option to construct an indexable map for identifiable items.
type IdentifiableMap[T Identifiable] map[string]T
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 8ba7c7e5f..10117e5de 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -270,42 +270,18 @@ func TestIsNamespaceAllowed(t *testing.T) {
}
}
-func TestGetCfgFilesToWatch(t *testing.T) {
- tests := map[string]struct {
- input []string
- expected []string
- }{
- "No special files": {
- input: []string{"config.yaml", ".bar.yaml", "/_foo/bar.yaml", "/__bar/baz.yaml"},
- expected: []string{"config.yaml", ".bar.yaml", "/_foo/bar.yaml", "/__bar/baz.yaml"},
- },
- "Special files should be ignored": {
- input: []string{"_test.yaml", "config.yaml", "__foo.yaml", ".bar.yaml", "/bar/__baz.yaml", "/baz/_qux.yaml"},
- expected: []string{"_test.yaml", "config.yaml", ".bar.yaml", "/baz/_qux.yaml"},
- },
- }
-
- for name, test := range tests {
- name, test := name, test
- t.Run(name, func(t *testing.T) {
- actual := config.GetCfgFilesToWatch(test.input)
- assert.Equal(t, test.expected, actual)
- })
- }
-}
-
func TestSortCfgFiles(t *testing.T) {
tests := map[string]struct {
input []string
expected []string
}{
"No special files": {
- input: []string{"config.yaml", ".bar.yaml", "/_foo/bar.yaml", "/__bar/baz.yaml"},
- expected: []string{"config.yaml", ".bar.yaml", "/_foo/bar.yaml", "/__bar/baz.yaml"},
+ input: []string{"config.yaml", ".bar.yaml", "/_foo/bar.yaml", "/_bar/baz.yaml"},
+ expected: []string{"config.yaml", ".bar.yaml", "/_foo/bar.yaml", "/_bar/baz.yaml"},
},
"Special files": {
- input: []string{"_test.yaml", "config.yaml", "_foo.yaml", "__foo.yaml", ".bar.yaml", "/bar/__baz.yaml", "/bar/_baz.yaml"},
- expected: []string{"config.yaml", ".bar.yaml", "_test.yaml", "_foo.yaml", "__foo.yaml", "/bar/__baz.yaml", "/bar/_baz.yaml"},
+ input: []string{"_test.yaml", "config.yaml", "_foo.yaml", ".bar.yaml", "/bar/_baz.yaml"},
+ expected: []string{"config.yaml", ".bar.yaml", "_test.yaml", "_foo.yaml", "/bar/_baz.yaml"},
},
}
diff --git a/pkg/config/export_test.go b/pkg/config/export_test.go
index 6204e1a8c..738e32814 100644
--- a/pkg/config/export_test.go
+++ b/pkg/config/export_test.go
@@ -4,10 +4,6 @@ func NormalizeConfigEnvName(name string) string {
return normalizeConfigEnvName(name)
}
-func GetCfgFilesToWatch(paths []string) []string {
- return getCfgFilesToWatch(paths)
-}
-
func SortCfgFiles(paths []string) []string {
return sortCfgFiles(paths)
}
diff --git a/pkg/config/manager_test.go b/pkg/config/manager_test.go
index 4b3f97f73..71449faaa 100644
--- a/pkg/config/manager_test.go
+++ b/pkg/config/manager_test.go
@@ -19,7 +19,7 @@ func TestPersistenceManager_PersistSourceBindings(t *testing.T) {
// given
commGroupName := "default-group"
cfg := config.PartialPersistentConfig{
- ConfigMap: config.K8sConfigMapRef{
+ ConfigMap: config.K8sResourceRef{
Name: "foo",
Namespace: "ns",
},
@@ -275,7 +275,7 @@ func TestPersistenceManager_PersistNotificationsEnabled(t *testing.T) {
// given
commGroupName := "default-group"
cfg := config.PartialPersistentConfig{
- ConfigMap: config.K8sConfigMapRef{
+ ConfigMap: config.K8sResourceRef{
Name: "foo",
Namespace: "ns",
},
@@ -428,7 +428,7 @@ func TestPersistenceManager_PersistNotificationsEnabled(t *testing.T) {
func TestPersistenceManager_PersistFilterEnabled(t *testing.T) {
// given
cfg := config.PartialPersistentConfig{
- ConfigMap: config.K8sConfigMapRef{
+ ConfigMap: config.K8sResourceRef{
Name: "foo",
Namespace: "ns",
},
diff --git a/pkg/config/testdata/TestLoadConfigSuccess/config.golden.yaml b/pkg/config/testdata/TestLoadConfigSuccess/config.golden.yaml
index 20461dfa7..709ee4cd0 100644
--- a/pkg/config/testdata/TestLoadConfigSuccess/config.golden.yaml
+++ b/pkg/config/testdata/TestLoadConfigSuccess/config.golden.yaml
@@ -389,7 +389,6 @@ analytics:
disable: true
settings:
clusterName: cluster-name-from-env
- configWatcher: true
upgradeNotifier: true
systemConfigMap:
name: botkube-system
@@ -404,8 +403,16 @@ settings:
configMap:
name: runtime-config
metricsPort: "1313"
+ lifecycleServer:
+ enabled: false
+ port: 0
+ deployment: {}
log:
level: error
disableColors: false
informersResyncPeriod: 30m0s
kubeconfig: kubeconfig-from-env
+configWatcher:
+ enabled: false
+ initialSyncTimeout: 0s
+ tmpDir: ""
diff --git a/pkg/config/watcher_sync.go b/pkg/config/watcher_sync.go
new file mode 100644
index 000000000..3af58045c
--- /dev/null
+++ b/pkg/config/watcher_sync.go
@@ -0,0 +1,44 @@
+package config
+
+import (
+ "context"
+ "os"
+ "time"
+
+ "github.com/sirupsen/logrus"
+ "k8s.io/apimachinery/pkg/util/wait"
+)
+
+const (
+ watcherPollInterval = 200 * time.Millisecond
+)
+
+// WaitForWatcherSync delays startup until ConfigWatcher synchronizes at least one configuration file
+func WaitForWatcherSync(ctx context.Context, log logrus.FieldLogger, cfg CfgWatcher) error {
+ if cfg.InitialSyncTimeout.Milliseconds() == 0 {
+ log.Info("Skipping waiting for Config Watcher sync...")
+ return nil
+ }
+
+ log.Infof("Waiting for synchronized files in directory %q with timeout %s...", cfg.TmpDir, cfg.InitialSyncTimeout)
+ err := wait.PollWithContext(ctx, watcherPollInterval, cfg.InitialSyncTimeout, func(ctx context.Context) (done bool, err error) {
+ files, err := os.ReadDir(cfg.TmpDir)
+ if err != nil {
+ return false, err
+ }
+
+ for _, file := range files {
+ if file.IsDir() {
+ // skip subdirectories
+ continue
+ }
+
+ log.Infof("File %q detected. Finishing polling...", file.Name())
+ return true, nil
+ }
+
+ return false, nil
+ })
+
+ return err
+}
diff --git a/pkg/controller/config_watcher.go b/pkg/controller/config_watcher.go
deleted file mode 100644
index 2d06cf0c1..000000000
--- a/pkg/controller/config_watcher.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package controller
-
-import (
- "context"
- "fmt"
-
- "github.com/fsnotify/fsnotify"
- "github.com/sirupsen/logrus"
- "golang.org/x/sync/errgroup"
-
- "github.com/kubeshop/botkube/pkg/multierror"
-)
-
-// ConfigWatcher watches for the config file changes and exits the app.
-// TODO: It keeps the previous behavior for now, but it should hot-reload the configuration files without needing a restart.
-type ConfigWatcher struct {
- log logrus.FieldLogger
- configPaths []string
- clusterName string
- notifiers []Notifier
-}
-
-// NewConfigWatcher returns new ConfigWatcher instance.
-func NewConfigWatcher(log logrus.FieldLogger, configPaths []string, clusterName string, notifiers []Notifier) *ConfigWatcher {
- return &ConfigWatcher{
- log: log,
- configPaths: configPaths,
- clusterName: clusterName,
- notifiers: notifiers,
- }
-}
-
-// Do starts watching the configuration file
-func (w *ConfigWatcher) Do(ctx context.Context, cancelFunc context.CancelFunc) (err error) {
- watcher, err := fsnotify.NewWatcher()
- if err != nil {
- return fmt.Errorf("while creating file watcher: %w", err)
- }
- defer func() {
- deferredErr := watcher.Close()
- if deferredErr != nil {
- err = multierror.Append(err, deferredErr)
- }
- }()
-
- ctx, cancelFn := context.WithCancel(ctx)
- defer cancelFn()
-
- log := w.log.WithField("configPaths", w.configPaths)
-
- errGroup, _ := errgroup.WithContext(ctx)
- errGroup.Go(func() error {
- for {
- select {
- case <-ctx.Done():
- log.Info("Shutdown requested. Finishing...")
- return nil
- case ev, ok := <-watcher.Events:
- if !ok {
- return fmt.Errorf("unexpected file watch end")
- }
-
- currentLogg := log.WithField("event", ev.String())
-
- currentLogg.Info("Config updated. Sending last message before exit...")
- err := sendMessageToNotifiers(ctx, w.notifiers, fmt.Sprintf(configUpdateMsg, w.clusterName))
- if err != nil {
- wrappedErr := fmt.Errorf("while sending message to notifiers: %w", err)
- //do not exit yet, cancel the context first
- cancelFunc()
- return wrappedErr
- }
-
- currentLogg.Infof("Cancelling the context...")
- cancelFunc()
- return nil
- case err, ok := <-watcher.Errors:
- if !ok {
- return fmt.Errorf("unexpected file watch end")
- }
- return fmt.Errorf("while reading events for config files: %w", err)
- }
- }
- })
-
- log.Infof("Registering watcher on config files")
- for _, path := range w.configPaths {
- err = watcher.Add(path)
- if err != nil {
- return fmt.Errorf("while registering watch on config file %q: %w", path, err)
- }
- }
- return errGroup.Wait()
-}
diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go
index 4eece8886..6623a3d43 100644
--- a/pkg/controller/controller.go
+++ b/pkg/controller/controller.go
@@ -18,15 +18,15 @@ import (
"github.com/kubeshop/botkube/pkg/events"
"github.com/kubeshop/botkube/pkg/filterengine"
"github.com/kubeshop/botkube/pkg/multierror"
+ "github.com/kubeshop/botkube/pkg/notifier"
"github.com/kubeshop/botkube/pkg/recommendation"
"github.com/kubeshop/botkube/pkg/sources"
"github.com/kubeshop/botkube/pkg/utils"
)
const (
- controllerStartMsg = "...and now my watch begins for cluster '%s'! :crossed_swords:"
- controllerStopMsg = "My watch has ended for cluster '%s'. Hope will be back online soon! :crossed_fingers:"
- configUpdateMsg = "Looks like the configuration is updated for cluster '%s'. I shall halt my watch till I read it."
+ controllerStartMsg = "My watch begins for cluster '%s'! :crossed_swords:"
+ controllerStopMsg = "My watch has ended for cluster '%s'. See you soon! :crossed_fingers:"
finalMessageTimeout = 20 * time.Second
)
@@ -57,7 +57,7 @@ type Controller struct {
reporter AnalyticsReporter
startTime time.Time
conf *config.Config
- notifiers []Notifier
+ notifiers []notifier.Notifier
recommFactory RecommendationFactory
filterEngine filterengine.FilterEngine
informersResyncPeriod time.Duration
@@ -72,7 +72,7 @@ type Controller struct {
// New create a new Controller instance.
func New(log logrus.FieldLogger,
conf *config.Config,
- notifiers []Notifier,
+ notifiers []notifier.Notifier,
recommFactory RecommendationFactory,
filterEngine filterengine.FilterEngine,
dynamicCli dynamic.Interface,
@@ -97,6 +97,7 @@ func New(log logrus.FieldLogger,
// Start creates new informer controllers to watch k8s resources
func (c *Controller) Start(ctx context.Context) error {
+ c.log.Info("Starting controller...")
c.dynamicKubeInformerFactory = dynamicinformer.NewDynamicSharedInformerFactory(c.dynamicCli, c.informersResyncPeriod)
err := c.sourcesRouter.RegisterInformers([]config.EventType{
@@ -197,8 +198,8 @@ func (c *Controller) Start(ctx context.Context) error {
}
})
- c.log.Info("Starting controller")
- err = sendMessageToNotifiers(ctx, c.notifiers, fmt.Sprintf(controllerStartMsg, c.conf.Settings.ClusterName))
+ c.log.Info("Sending welcome message...")
+ err = notifier.SendPlaintextMessage(ctx, c.notifiers, fmt.Sprintf(controllerStartMsg, c.conf.Settings.ClusterName))
if err != nil {
return fmt.Errorf("while sending first message: %w", err)
}
@@ -206,14 +207,14 @@ func (c *Controller) Start(ctx context.Context) error {
c.startTime = time.Now()
stopCh := ctx.Done()
-
c.dynamicKubeInformerFactory.Start(stopCh)
+
<-stopCh
c.log.Info("Shutdown requested. Sending final message...")
finalMsgCtx, cancelFn := context.WithTimeout(context.Background(), finalMessageTimeout)
defer cancelFn()
- err = sendMessageToNotifiers(finalMsgCtx, c.notifiers, fmt.Sprintf(controllerStopMsg, c.conf.Settings.ClusterName))
+ err = notifier.SendPlaintextMessage(finalMsgCtx, c.notifiers, fmt.Sprintf(controllerStopMsg, c.conf.Settings.ClusterName))
if err != nil {
return fmt.Errorf("while sending final message: %w", err)
}
@@ -286,7 +287,7 @@ func (c *Controller) sendEvent(ctx context.Context, obj interface{}, resource st
// Send event over notifiers
anonymousEvent := analytics.AnonymizedEventDetailsFrom(event)
for _, n := range c.notifiers {
- go func(n Notifier) {
+ go func(n notifier.Notifier) {
defer analytics.ReportPanicIfOccurs(c.log, c.reporter)
err := n.SendEvent(ctx, event, sources)
diff --git a/pkg/controller/upgrade.go b/pkg/controller/upgrade.go
index 018bf0017..4a7c09c2a 100644
--- a/pkg/controller/upgrade.go
+++ b/pkg/controller/upgrade.go
@@ -9,6 +9,7 @@ import (
"github.com/google/go-github/v44/github"
"github.com/sirupsen/logrus"
+ "github.com/kubeshop/botkube/pkg/notifier"
"github.com/kubeshop/botkube/pkg/version"
)
@@ -27,12 +28,12 @@ type GitHubRepoClient interface {
// UpgradeChecker checks for new BotKube releases.
type UpgradeChecker struct {
log logrus.FieldLogger
- notifiers []Notifier
+ notifiers []notifier.Notifier
ghRepoCli GitHubRepoClient
}
// NewUpgradeChecker creates a new instance of the Upgrade Checker.
-func NewUpgradeChecker(log logrus.FieldLogger, notifiers []Notifier, ghCli GitHubRepoClient) *UpgradeChecker {
+func NewUpgradeChecker(log logrus.FieldLogger, notifiers []notifier.Notifier, ghCli GitHubRepoClient) *UpgradeChecker {
return &UpgradeChecker{log: log, notifiers: notifiers, ghRepoCli: ghCli}
}
@@ -90,7 +91,7 @@ func (c *UpgradeChecker) notifyAboutUpgradeIfShould(ctx context.Context) (bool,
return false, nil
}
- err = sendMessageToNotifiers(ctx, c.notifiers, fmt.Sprintf(upgradeMsgFmt, *release.TagName))
+ err = notifier.SendPlaintextMessage(ctx, c.notifiers, fmt.Sprintf(upgradeMsgFmt, *release.TagName))
if err != nil {
return false, fmt.Errorf("while sending message about new release: %w", err)
}
diff --git a/pkg/execute/edit.go b/pkg/execute/edit.go
index 382e1dea8..af5fc3c47 100644
--- a/pkg/execute/edit.go
+++ b/pkg/execute/edit.go
@@ -17,8 +17,9 @@ import (
)
const (
- editedSourcesMsgFmt = ":white_check_mark: %s adjusted the BotKube notifications settings to %s messages. Expect BotKube restart soon..."
- unknownSourcesMsgFmt = ":exclamation: The %s %s not found in configuration. To learn how to add custom source, visit https://botkube.io/docs/configuration/source."
+ editedSourcesMsgFmt = ":white_check_mark: %s adjusted the BotKube notifications settings to %s messages. Expect BotKube reload in a few seconds..."
+ editedSourcesMsgWithoutReloadFmt = ":white_check_mark: %s adjusted the BotKube notifications settings to %s messages.\nAs the Config Watcher is disabled, you need to restart BotKube manually to apply the changes."
+ unknownSourcesMsgFmt = ":exclamation: The %s %s not found in configuration. To learn how to add custom source, visit https://botkube.io/docs/configuration/source."
)
// EditResource defines the name of editable resource
@@ -150,13 +151,22 @@ func (e *EditExecutor) editSourceBindingHandler(cmdArgs []string, commGroupName
if userID == "" {
userID = "Anonymous"
}
+
return interactive.Message{
Base: interactive.Base{
- Description: fmt.Sprintf(editedSourcesMsgFmt, userID, sourceList),
+ Description: e.getEditedSourceBindingsMsg(userID, sourceList),
},
}, nil
}
+func (e *EditExecutor) getEditedSourceBindingsMsg(userID, sourceList string) string {
+ if !e.cfg.ConfigWatcher.Enabled {
+ return fmt.Sprintf(editedSourcesMsgWithoutReloadFmt, userID, sourceList)
+ }
+
+ return fmt.Sprintf(editedSourcesMsgFmt, userID, sourceList)
+}
+
func (e *EditExecutor) generateUnknownMessage(unknown []string) interactive.Message {
list := english.OxfordWordSeries(e.quoteEachItem(unknown), "and")
word := english.PluralWord(len(unknown), "source was", "sources were")
diff --git a/pkg/execute/edit_test.go b/pkg/execute/edit_test.go
index 519de4dc4..8778ef76d 100644
--- a/pkg/execute/edit_test.go
+++ b/pkg/execute/edit_test.go
@@ -43,11 +43,16 @@ func TestSourceBindingsHappyPath(t *testing.T) {
DisplayName: "BAZ",
},
},
+ ConfigWatcher: config.CfgWatcher{
+ Enabled: true,
+ },
}
+ cfgWithCfgWatcherDisabled := config.Config{Sources: cfg.Sources}
tests := []struct {
name string
command string
+ config config.Config
message string
sourceBindings []string
@@ -55,52 +60,67 @@ func TestSourceBindingsHappyPath(t *testing.T) {
{
name: "Should resolve quoted list which is separated by comma",
command: `edit SourceBindings "bar,xyz"`,
+ config: cfg,
- message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR and XYZ messages. Expect BotKube restart soon...",
+ message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR and XYZ messages. Expect BotKube reload in a few seconds...",
sourceBindings: []string{"bar", "xyz"},
},
{
name: "Should resolve quoted and code items separated by comma",
command: "edit sourcebindings β`bar`,xyz β",
+ config: cfg,
- message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR and XYZ messages. Expect BotKube restart soon...",
+ message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR and XYZ messages. Expect BotKube reload in a few seconds...",
sourceBindings: []string{"bar", "xyz"},
},
{
name: "Should resolve list which is separated by comma and ends with whitespace",
command: `edit sourceBindings bar,xyz `,
+ config: cfg,
- message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR and XYZ messages. Expect BotKube restart soon...",
+ message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR and XYZ messages. Expect BotKube reload in a few seconds...",
sourceBindings: []string{"bar", "xyz"},
},
{
name: "Should resolve list which is separated by comma but has a lot of whitespaces",
command: `edit sourcebindings bar, xyz, `,
+ config: cfg,
- message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR and XYZ messages. Expect BotKube restart soon...",
+ message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR and XYZ messages. Expect BotKube reload in a few seconds...",
sourceBindings: []string{"bar", "xyz"},
},
{
name: "Should resolve list which is separated by comma, has a lot of whitespaces and some items are quoted",
command: `edit SourceBindings bar xyz, "baz"`,
+ config: cfg,
- message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR, XYZ, and BAZ messages. Expect BotKube restart soon...",
+ message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR, XYZ, and BAZ messages. Expect BotKube reload in a few seconds...",
sourceBindings: []string{"bar", "xyz", "baz"},
},
{
name: "Should resolve list with unicode quotes",
command: `edit SourceBindings βfoo,barβ`,
+ config: cfg,
- message: ":white_check_mark: Joe adjusted the BotKube notifications settings to FOO and BAR messages. Expect BotKube restart soon...",
+ message: ":white_check_mark: Joe adjusted the BotKube notifications settings to FOO and BAR messages. Expect BotKube reload in a few seconds...",
sourceBindings: []string{"foo", "bar"},
},
{
name: "Should resolve list which has mixed formatting for different items, all at once",
command: `edit SourceBindings foo baz "bar,xyz" "fiz"`,
+ config: cfg,
- message: ":white_check_mark: Joe adjusted the BotKube notifications settings to FOO, BAZ, BAR, XYZ, and FIZ messages. Expect BotKube restart soon...",
+ message: ":white_check_mark: Joe adjusted the BotKube notifications settings to FOO, BAZ, BAR, XYZ, and FIZ messages. Expect BotKube reload in a few seconds...",
sourceBindings: []string{"foo", "baz", "bar", "xyz", "fiz"},
},
+ {
+ name: "Should mention manual app restart",
+ command: `edit SourceBindings "bar,xyz"`,
+ config: cfgWithCfgWatcherDisabled,
+
+ message: ":white_check_mark: Joe adjusted the BotKube notifications settings to BAR and XYZ messages.\nAs the Config Watcher is disabled, you need to restart BotKube manually to apply the changes.",
+ sourceBindings: []string{"bar", "xyz"},
+ },
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
@@ -109,7 +129,7 @@ func TestSourceBindingsHappyPath(t *testing.T) {
fakeStorage := &fakeBindingsStorage{}
args := strings.Fields(strings.TrimSpace(tc.command))
- executor := NewEditExecutor(log, &fakeAnalyticsReporter{}, fakeStorage, cfg)
+ executor := NewEditExecutor(log, &fakeAnalyticsReporter{}, fakeStorage, tc.config)
expMessage := interactive.Message{
Base: interactive.Base{
diff --git a/pkg/execute/notifier_test.go b/pkg/execute/notifier_test.go
index 9d57fb8fa..b6e760486 100644
--- a/pkg/execute/notifier_test.go
+++ b/pkg/execute/notifier_test.go
@@ -94,7 +94,6 @@ func TestNotifierExecutor_Do_Success(t *testing.T) {
disable: false
settings:
clusterName: foo
- configWatcher: false
upgradeNotifier: false
systemConfigMap: {}
persistentConfig:
@@ -105,11 +104,19 @@ func TestNotifierExecutor_Do_Success(t *testing.T) {
fileName: ""
configMap: {}
metricsPort: ""
+ lifecycleServer:
+ enabled: false
+ port: 0
+ deployment: {}
log:
level: ""
disableColors: false
informersResyncPeriod: 0s
kubeconfig: ""
+ configWatcher:
+ enabled: false
+ initialSyncTimeout: 0s
+ tmpDir: ""
`),
ExpectedStatusAfter: `Notifications from cluster 'cluster-name' are disabled here.`,
},
diff --git a/pkg/controller/notifier.go b/pkg/notifier/notifier.go
similarity index 85%
rename from pkg/controller/notifier.go
rename to pkg/notifier/notifier.go
index 4268067c2..65aeba568 100644
--- a/pkg/controller/notifier.go
+++ b/pkg/notifier/notifier.go
@@ -1,4 +1,4 @@
-package controller
+package notifier
import (
"context"
@@ -17,7 +17,7 @@ type Notifier interface {
// SendMessage is used for notifying about BotKube start/stop listening, possible BotKube upgrades and other events.
// Some integrations may decide to ignore such messages and have SendMessage method no-op.
- // TODO: Consider option per channel to turn on/off "announcements" (BotKube start/stop/upgrade notify/config change.
+ // TODO: Consider option per channel to turn on/off "announcements" (BotKube start/stop/upgrade, notify/config change).
SendMessage(context.Context, interactive.Message) error
// IntegrationName returns a name of a given communication platform.
@@ -27,7 +27,8 @@ type Notifier interface {
Type() config.IntegrationType
}
-func sendMessageToNotifiers(ctx context.Context, notifiers []Notifier, msg string) error {
+// SendPlaintextMessage sends a plaintext message to specified providers.
+func SendPlaintextMessage(ctx context.Context, notifiers []Notifier, msg string) error {
if msg == "" {
return fmt.Errorf("message cannot be empty")
}
diff --git a/pkg/sink/types.go b/pkg/sink/types.go
index 124127e1d..ce694c275 100644
--- a/pkg/sink/types.go
+++ b/pkg/sink/types.go
@@ -2,12 +2,12 @@ package sink
import (
"github.com/kubeshop/botkube/pkg/config"
- "github.com/kubeshop/botkube/pkg/controller"
+ "github.com/kubeshop/botkube/pkg/notifier"
)
// Sink sends messages to communication channels. It is a one-way integration.
type Sink interface {
- controller.Notifier
+ notifier.Notifier
}
// AnalyticsReporter defines a reporter that collects analytics data for sinks.
diff --git a/test/e2e/bots_test.go b/test/e2e/bots_test.go
index 2897c407d..dea335882 100644
--- a/test/e2e/bots_test.go
+++ b/test/e2e/bots_test.go
@@ -176,7 +176,7 @@ func runBotTest(t *testing.T,
interactive.Help(config.CommPlatformIntegration(botDriver.Type()), appCfg.ClusterName, botDriver.BotName()),
)
require.NoError(t, err)
- err = botDriver.WaitForMessagePostedRecentlyEqual(botDriver.BotUserID(), botDriver.Channel().ID(), fmt.Sprintf("...and now my watch begins for cluster '%s'! :crossed_swords:", appCfg.ClusterName))
+ err = botDriver.WaitForMessagePostedRecentlyEqual(botDriver.BotUserID(), botDriver.Channel().ID(), fmt.Sprintf("My watch begins for cluster '%s'! :crossed_swords:", appCfg.ClusterName))
require.NoError(t, err)
t.Log("Running actual test cases")