From 1a1542e159950cabf5a6dd4e335120737d163eda Mon Sep 17 00:00:00 2001 From: John Keyes Date: Tue, 8 Oct 2024 10:42:52 +0000 Subject: [PATCH] feature: making Slack action URL configurable --- docs/guides/alerts.mdx | 6 +++++ internal/alerts.go | 4 +-- internal/config/load_test.go | 49 ++++++++++++++++++++++++++++++++++++ internal/webhook.go | 18 +++++++++++-- internal/webhook_test.go | 19 ++++++++++++++ models/config.go | 1 + 6 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 internal/config/load_test.go create mode 100644 internal/webhook_test.go diff --git a/docs/guides/alerts.mdx b/docs/guides/alerts.mdx index dba2db37e..012bf3f70 100644 --- a/docs/guides/alerts.mdx +++ b/docs/guides/alerts.mdx @@ -46,6 +46,12 @@ https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX [slack] webhook="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" ``` +7. By default the "Open view" action in the Slack alert will attempt to open `http://localhost:{port}/inventory?view={viewId}`. However, you can configure this URL via the `host` field. For example, if komiser is hosted on `https://komiser.local` then set the field accordingly: +``` +[slack] +webhook="https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX" +host="https://komiser.local" +``` ## Custom Webhook integration To integrate a custom webhook, you need a URL that listens to the data posted to it when it is triggered. You don't need to edit the `config.toml` file for this integration. diff --git a/internal/alerts.go b/internal/alerts.go index 1b88794a9..28694ea7e 100644 --- a/internal/alerts.go +++ b/internal/alerts.go @@ -28,7 +28,7 @@ func checkingAlerts(ctx context.Context, cfg models.Config, telemetry bool, port } if alert.IsSlack { log.Info("Sending Slack budget alert for view:", view.Name) - hitSlackWebhook(view.Name, port, int(view.Id), 0, stats.Costs, cfg.Slack.Webhook, alert.Type) + hitSlackWebhook(view.Name, port, int(view.Id), 0, stats.Costs, cfg.Slack.Webhook, cfg.Slack.Host, alert.Type) } else { log.Info("Sending Custom Webhook budget alert for view:", view.Name) hitCustomWebhook(alert.Endpoint, alert.Secret, view.Name, 0, stats.Costs, alert.Type) @@ -42,7 +42,7 @@ func checkingAlerts(ctx context.Context, cfg models.Config, telemetry bool, port } if alert.IsSlack { log.Info("Sending Slack usage alert for view:", view.Name) - hitSlackWebhook(view.Name, port, int(view.Id), stats.Resources, 0, cfg.Slack.Webhook, alert.Type) + hitSlackWebhook(view.Name, port, int(view.Id), stats.Resources, 0, cfg.Slack.Webhook, cfg.Slack.Host, alert.Type) } else { log.Info("Sending Custom Webhook usage alert for view:", view.Name) hitCustomWebhook(alert.Endpoint, alert.Secret, view.Name, stats.Resources, 0, alert.Type) diff --git a/internal/config/load_test.go b/internal/config/load_test.go new file mode 100644 index 000000000..2a707d52c --- /dev/null +++ b/internal/config/load_test.go @@ -0,0 +1,49 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSlackConfig(t *testing.T) { + // test when a value is only specified for webhook + cfgText := ` + [slack] + webhook = "https://example.com" + ` + cfgBytes := []byte(cfgText) + config, _ := loadConfigFromBytes(cfgBytes) + + assert.Equal(t, "https://example.com", config.Slack.Webhook) + assert.Equal(t, false, config.Slack.Reporting) + assert.Equal(t, "", config.Slack.Host) + + // test when a value is specified for reporting + cfgText = ` + [slack] + webhook = "https://example.com" + reporting = true + ` + cfgBytes = []byte(cfgText) + config, _ = loadConfigFromBytes(cfgBytes) + + assert.Equal(t, "https://example.com", config.Slack.Webhook) + assert.Equal(t, true, config.Slack.Reporting) + assert.Equal(t, "", config.Slack.Host) + + // test when a value is specified for host + cfgText = ` + [slack] + webhook = "https://example.com" + reporting = true + host = "https://example.com/komiser" + ` + cfgBytes = []byte(cfgText) + config, _ = loadConfigFromBytes(cfgBytes) + + assert.Equal(t, "https://example.com", config.Slack.Webhook) + assert.Equal(t, true, config.Slack.Reporting) + assert.Equal(t, "https://example.com/komiser", config.Slack.Host) + +} diff --git a/internal/webhook.go b/internal/webhook.go index 46c7b0ce0..879fc59ff 100644 --- a/internal/webhook.go +++ b/internal/webhook.go @@ -64,7 +64,15 @@ func hitCustomWebhook(endpoint string, secret string, viewName string, resources } } -func hitSlackWebhook(viewName string, port int, viewId int, resources int, cost float64, webhookUrl string, alertType string) { +// createSlackAttachment creates a `slack.Attachment`. +// This attachment can then be added to the WebhookMessage for the alert. +func createSlackAttachment(viewName string, port int, viewId int, resources int, cost float64, hostname string, alertType string) slack.Attachment { + // if the hostname is empty i.e. not defined in config.toml + // default to localhost and the runtime port value + if hostname == "" { + hostname = fmt.Sprintf("http://localhost:%d", port) + } + attachment := slack.Attachment{ Color: "danger", AuthorName: "Komiser", @@ -77,7 +85,7 @@ func hitSlackWebhook(viewName string, port int, viewId int, resources int, cost Name: "open", Text: "Open view", Type: "button", - URL: fmt.Sprintf("http://localhost:%d/inventory?view=%d", port, viewId), + URL: fmt.Sprintf("%s/inventory?view=%d", hostname, viewId), }, }, Fields: []slack.AttachmentField{ @@ -103,6 +111,12 @@ func hitSlackWebhook(viewName string, port int, viewId int, resources int, cost Value: fmt.Sprintf("%d", resources), }) } + return attachment +} + +func hitSlackWebhook(viewName string, port int, viewId int, resources int, cost float64, webhookUrl string, hostname string, alertType string) { + + attachment := createSlackAttachment(viewName, port, viewId, resources, cost, hostname, alertType) msg := slack.WebhookMessage{ Attachments: []slack.Attachment{attachment}, diff --git a/internal/webhook_test.go b/internal/webhook_test.go new file mode 100644 index 000000000..6e465470c --- /dev/null +++ b/internal/webhook_test.go @@ -0,0 +1,19 @@ +package internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSlackAttachment(t *testing.T) { + // default - no slack host specified in config + attachment := createSlackAttachment("testViewName", 3000, 101, 99, 99.99, "", "RESOURCES") + expectedActionUrl := "http://localhost:3000/inventory?view=101" + assert.Equal(t, expectedActionUrl, attachment.Actions[0].URL) + + // explicit - slack host specified in config + attachment = createSlackAttachment("testViewName", 3000, 101, 99, 99.99, "https://example.com", "RESOURCES") + expectedActionUrl = "https://example.com/inventory?view=101" + assert.Equal(t, expectedActionUrl, attachment.Actions[0].URL) +} diff --git a/models/config.go b/models/config.go index 34a0ffd6a..c93acad2b 100644 --- a/models/config.go +++ b/models/config.go @@ -106,4 +106,5 @@ type OVHConfig struct { type SlackConfig struct { Webhook string `toml:"webhook"` Reporting bool `toml:"reporting"` + Host string `toml:"host"` }