From d0b955aadd1caaece82f301aafd42868e8908256 Mon Sep 17 00:00:00 2001 From: Pavel Cizinsky Date: Tue, 6 Jan 2026 12:18:00 +0100 Subject: [PATCH] feat(slack): Add environment variable fallback for config Allow Slack alertmanager to read token, channel, and webhook from SLACK_TOKEN, SLACK_CHANNEL, and SLACK_WEBHOOK environment variables when not specified in config. Config values take precedence over environment variables when both are present. --- alertmanager/slack/slack.go | 12 ++++++ alertmanager/slack/slack_test.go | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/alertmanager/slack/slack.go b/alertmanager/slack/slack.go index 3e82508..1f45c94 100644 --- a/alertmanager/slack/slack.go +++ b/alertmanager/slack/slack.go @@ -2,6 +2,7 @@ package slack import ( "fmt" + "os" "strings" "github.com/abahmed/kwatch/config" @@ -44,6 +45,17 @@ func NewSlack(config map[string]interface{}, appCfg *config.App) *Slack { title, _ := config["title"].(string) text, _ := config["text"].(string) + // Fall back to environment variables if config values are not set + if len(token) == 0 { + token = os.Getenv("SLACK_TOKEN") + } + if len(channel) == 0 { + channel = os.Getenv("SLACK_CHANNEL") + } + if len(webhook) == 0 { + webhook = os.Getenv("SLACK_WEBHOOK") + } + // Token takes precedence over webhook if len(token) > 0 { if len(channel) == 0 { diff --git a/alertmanager/slack/slack_test.go b/alertmanager/slack/slack_test.go index f03b2c8..baabc60 100644 --- a/alertmanager/slack/slack_test.go +++ b/alertmanager/slack/slack_test.go @@ -1,6 +1,7 @@ package slack import ( + "os" "testing" "github.com/abahmed/kwatch/config" @@ -140,3 +141,68 @@ func TestSendEvent(t *testing.T) { } assert.Nil(s.SendEvent(&ev)) } + +func TestSlackEnvVarToken(t *testing.T) { + assert := assert.New(t) + + // Set environment variables + os.Setenv("SLACK_TOKEN", "xoxb-env-token") + os.Setenv("SLACK_CHANNEL", "#env-channel") + defer func() { + os.Unsetenv("SLACK_TOKEN") + os.Unsetenv("SLACK_CHANNEL") + }() + + // Empty config should use env vars + s := NewSlack(map[string]interface{}{}, &config.App{ClusterName: "dev"}) + assert.NotNil(s) + assert.Equal("xoxb-env-token", s.token) + assert.Equal("#env-channel", s.channel) + assert.NotNil(s.client) +} + +func TestSlackEnvVarWebhook(t *testing.T) { + assert := assert.New(t) + + // Set environment variable + os.Setenv("SLACK_WEBHOOK", "https://hooks.slack.com/env-webhook") + defer os.Unsetenv("SLACK_WEBHOOK") + + // Empty config should use env var + s := NewSlack(map[string]interface{}{}, &config.App{ClusterName: "dev"}) + assert.NotNil(s) + assert.Equal("https://hooks.slack.com/env-webhook", s.webhook) +} + +func TestSlackConfigOverridesEnvVar(t *testing.T) { + assert := assert.New(t) + + // Set environment variables + os.Setenv("SLACK_TOKEN", "xoxb-env-token") + os.Setenv("SLACK_CHANNEL", "#env-channel") + defer func() { + os.Unsetenv("SLACK_TOKEN") + os.Unsetenv("SLACK_CHANNEL") + }() + + // Config values should override env vars + s := NewSlack(map[string]interface{}{ + "token": "xoxb-config-token", + "channel": "#config-channel", + }, &config.App{ClusterName: "dev"}) + assert.NotNil(s) + assert.Equal("xoxb-config-token", s.token) + assert.Equal("#config-channel", s.channel) +} + +func TestSlackEnvVarTokenWithoutChannel(t *testing.T) { + assert := assert.New(t) + + // Set only token env var (no channel) + os.Setenv("SLACK_TOKEN", "xoxb-env-token") + defer os.Unsetenv("SLACK_TOKEN") + + // Should fail because channel is required with token + s := NewSlack(map[string]interface{}{}, &config.App{ClusterName: "dev"}) + assert.Nil(s) +}