From 568bc2c11c0ce8bdbdb9128edc05e818ecf0ef87 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Wed, 28 Aug 2024 19:10:58 +0200 Subject: [PATCH] MQTT receiver: Add a setting to append group key to the topic name --- receivers/mqtt/config.go | 21 +++++++++++---------- receivers/mqtt/config_test.go | 25 +++++++++++++++++++++++++ receivers/mqtt/mqtt.go | 13 ++++++++++++- receivers/mqtt/mqtt_test.go | 35 +++++++++++++++++++++++++++-------- receivers/mqtt/testing.go | 3 ++- 5 files changed, 77 insertions(+), 20 deletions(-) diff --git a/receivers/mqtt/config.go b/receivers/mqtt/config.go index b2eff868..baca3806 100644 --- a/receivers/mqtt/config.go +++ b/receivers/mqtt/config.go @@ -17,16 +17,17 @@ const ( ) type Config struct { - BrokerURL string `json:"brokerUrl,omitempty" yaml:"brokerUrl,omitempty"` - ClientID string `json:"clientId,omitempty" yaml:"clientId,omitempty"` - Topic string `json:"topic,omitempty" yaml:"topic,omitempty"` - Message string `json:"message,omitempty" yaml:"message,omitempty"` - MessageFormat string `json:"messageFormat,omitempty" yaml:"messageFormat,omitempty"` - Username string `json:"username,omitempty" yaml:"username,omitempty"` - Password string `json:"password,omitempty" yaml:"password,omitempty"` - QoS receivers.OptionalNumber `json:"qos,omitempty" yaml:"qos,omitempty"` - Retain bool `json:"retain,omitempty" yaml:"retain,omitempty"` - TLSConfig *receivers.TLSConfig `json:"tlsConfig,omitempty" yaml:"tlsConfig,omitempty"` + BrokerURL string `json:"brokerUrl,omitempty" yaml:"brokerUrl,omitempty"` + ClientID string `json:"clientId,omitempty" yaml:"clientId,omitempty"` + Topic string `json:"topic,omitempty" yaml:"topic,omitempty"` + Message string `json:"message,omitempty" yaml:"message,omitempty"` + MessageFormat string `json:"messageFormat,omitempty" yaml:"messageFormat,omitempty"` + Username string `json:"username,omitempty" yaml:"username,omitempty"` + Password string `json:"password,omitempty" yaml:"password,omitempty"` + QoS receivers.OptionalNumber `json:"qos,omitempty" yaml:"qos,omitempty"` + Retain bool `json:"retain,omitempty" yaml:"retain,omitempty"` + TLSConfig *receivers.TLSConfig `json:"tlsConfig,omitempty" yaml:"tlsConfig,omitempty"` + AddGroupKeyToTopic bool `json:"addGroupKeyToTopic,omitempty" yaml:"addGroupKeyToTopic,omitempty"` } func NewConfig(jsonData json.RawMessage, decryptFn receivers.DecryptFunc) (Config, error) { diff --git a/receivers/mqtt/config_test.go b/receivers/mqtt/config_test.go index f3b48c5a..c05c1d77 100644 --- a/receivers/mqtt/config_test.go +++ b/receivers/mqtt/config_test.go @@ -120,6 +120,31 @@ func TestNewConfig(t *testing.T) { }, }, }, + { + name: "Full valid configuration", + settings: FullValidConfigForTesting, + secureSettings: map[string][]byte{ + "password": []byte("test-password"), + }, + expectedConfig: Config{ + Message: templates.DefaultMessageEmbed, + BrokerURL: "tcp://localhost:1883", + Topic: "grafana/alerts", + MessageFormat: MessageFormatJSON, + ClientID: "grafana-test-client-id", + Username: "test-username", + Password: "test-password", + QoS: "0", + TLSConfig: &receivers.TLSConfig{ + InsecureSkipVerify: false, + ServerName: "localhost", + CACertificate: "test-tls-ca-certificate", + ClientKey: "test-tls-client-key", + ClientCertificate: "test-tls-client-certificate", + }, + AddGroupKeyToTopic: true, + }, + }, } for _, c := range cases { diff --git a/receivers/mqtt/mqtt.go b/receivers/mqtt/mqtt.go index 29a21823..61520a8c 100644 --- a/receivers/mqtt/mqtt.go +++ b/receivers/mqtt/mqtt.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "path" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/types" @@ -96,10 +97,20 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) return false, fmt.Errorf("Failed to parse QoS: %s", err.Error()) } + topic := n.settings.Topic + if n.settings.AddGroupKeyToTopic { + groupKey, err := notify.ExtractGroupKey(ctx) + if err != nil { + n.log.Error("Failed to extract group key", "error", err.Error()) + return false, fmt.Errorf("Failed to extract group key: %s", err.Error()) + } + topic = path.Join(n.settings.Topic, string(groupKey)) + } + err = n.client.Publish( ctx, message{ - topic: n.settings.Topic, + topic: topic, payload: []byte(msg), retain: n.settings.Retain, qos: int(qos), diff --git a/receivers/mqtt/mqtt_test.go b/receivers/mqtt/mqtt_test.go index 921c62bf..457c36ff 100644 --- a/receivers/mqtt/mqtt_test.go +++ b/receivers/mqtt/mqtt_test.go @@ -92,6 +92,15 @@ func TestNotify(t *testing.T) { require.NoError(t, err) tmpl.ExternalURL = externalURL + defaultAlert := &types.Alert{ + Alert: model.Alert{ + Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, + Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"}, + GeneratorURL: "a URL", + }, + } + defaultAlertRenderedJSON := "{\"receiver\":\"\",\"status\":\"firing\",\"alerts\":[{\"status\":\"firing\",\"labels\":{\"alertname\":\"alert1\",\"lbl1\":\"val1\"},\"annotations\":{\"ann1\":\"annv1\"},\"startsAt\":\"0001-01-01T00:00:00Z\",\"endsAt\":\"0001-01-01T00:00:00Z\",\"generatorURL\":\"a URL\",\"fingerprint\":\"fac0861a85de433a\",\"silenceURL\":\"http://localhost/base/alerting/silence/new?alertmanager=grafana\\u0026matcher=alertname%3Dalert1\\u0026matcher=lbl1%3Dval1\",\"dashboardURL\":\"http://localhost/base/d/abcd\",\"panelURL\":\"http://localhost/base/d/abcd?viewPanel=efgh\",\"values\":null,\"valueString\":\"\"}],\"groupLabels\":{\"alertname\":\"\"},\"commonLabels\":{\"alertname\":\"alert1\",\"lbl1\":\"val1\"},\"commonAnnotations\":{\"ann1\":\"annv1\"},\"externalURL\":\"http://localhost/base\",\"version\":\"1\",\"groupKey\":\"alertname\",\"message\":\"**Firing**\\n\\nValue: [no value]\\nLabels:\\n - alertname = alert1\\n - lbl1 = val1\\nAnnotations:\\n - ann1 = annv1\\nSource: a URL\\nSilence: http://localhost/base/alerting/silence/new?alertmanager=grafana\\u0026matcher=alertname%3Dalert1\\u0026matcher=lbl1%3Dval1\\nDashboard: http://localhost/base/d/abcd\\nPanel: http://localhost/base/d/abcd?viewPanel=efgh\\n\"}" + cases := []struct { name string settings Config @@ -110,17 +119,11 @@ func TestNotify(t *testing.T) { MessageFormat: MessageFormatJSON, }, alerts: []*types.Alert{ - { - Alert: model.Alert{ - Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"}, - Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"}, - GeneratorURL: "a URL", - }, - }, + defaultAlert, }, expMessage: message{ topic: "alert1", - payload: []byte("{\"receiver\":\"\",\"status\":\"firing\",\"alerts\":[{\"status\":\"firing\",\"labels\":{\"alertname\":\"alert1\",\"lbl1\":\"val1\"},\"annotations\":{\"ann1\":\"annv1\"},\"startsAt\":\"0001-01-01T00:00:00Z\",\"endsAt\":\"0001-01-01T00:00:00Z\",\"generatorURL\":\"a URL\",\"fingerprint\":\"fac0861a85de433a\",\"silenceURL\":\"http://localhost/base/alerting/silence/new?alertmanager=grafana\\u0026matcher=alertname%3Dalert1\\u0026matcher=lbl1%3Dval1\",\"dashboardURL\":\"http://localhost/base/d/abcd\",\"panelURL\":\"http://localhost/base/d/abcd?viewPanel=efgh\",\"values\":null,\"valueString\":\"\"}],\"groupLabels\":{\"alertname\":\"\"},\"commonLabels\":{\"alertname\":\"alert1\",\"lbl1\":\"val1\"},\"commonAnnotations\":{\"ann1\":\"annv1\"},\"externalURL\":\"http://localhost/base\",\"version\":\"1\",\"groupKey\":\"alertname\",\"message\":\"**Firing**\\n\\nValue: [no value]\\nLabels:\\n - alertname = alert1\\n - lbl1 = val1\\nAnnotations:\\n - ann1 = annv1\\nSource: a URL\\nSilence: http://localhost/base/alerting/silence/new?alertmanager=grafana\\u0026matcher=alertname%3Dalert1\\u0026matcher=lbl1%3Dval1\\nDashboard: http://localhost/base/d/abcd\\nPanel: http://localhost/base/d/abcd?viewPanel=efgh\\n\"}"), + payload: []byte(defaultAlertRenderedJSON), retain: false, qos: 0, }, @@ -150,6 +153,22 @@ func TestNotify(t *testing.T) { retain: true, qos: 1, }, + }, + { + name: "A single alert with the default template in JSON and group-key added to the topic", + settings: Config{ + Topic: "alert1", + Message: templates.DefaultMessageEmbed, + MessageFormat: MessageFormatJSON, + AddGroupKeyToTopic: true, + }, + alerts: []*types.Alert{ + defaultAlert, + }, + expMessage: message{ + topic: "alert1/alertname", + payload: []byte(defaultAlertRenderedJSON), + }, expError: nil, }, { diff --git a/receivers/mqtt/testing.go b/receivers/mqtt/testing.go index a035723f..0dd98d94 100644 --- a/receivers/mqtt/testing.go +++ b/receivers/mqtt/testing.go @@ -15,7 +15,8 @@ const FullValidConfigForTesting = `{ "caCertificate": "test-tls-ca-certificate", "clientCertificate": "test-tls-client-certificate", "clientKey": "test-tls-client-key" - } + }, + "addGroupKeyToTopic": true }` // FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets