From 8f7cca40cccd445cef4b9f808f72808982a2c8e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Fri, 29 Oct 2021 13:14:35 +0100 Subject: [PATCH] fix(api): use slices for labels instead of maps --- cmd/karma/alerts.go | 24 +- cmd/karma/api_test.go | 252 ++++++++-------- cmd/karma/views.go | 61 ++-- cmd/karma/views_test.go | 270 +++++++++--------- internal/alertmanager/dedup.go | 5 +- internal/alertmanager/models.go | 8 +- internal/filters/autocomplete_test.go | 12 +- internal/filters/filter_fuzzy.go | 4 +- internal/filters/filter_label.go | 32 +-- internal/filters/filter_test.go | 24 +- internal/mapper/v017/api.go | 14 +- internal/models/__snapshots__/models.snapshot | 58 ++-- internal/models/alert.go | 87 +++++- internal/models/alert_test.go | 111 +++++++ internal/models/alertgroup.go | 18 +- internal/models/alertgroup_test.go | 26 +- internal/models/api.go | 58 ++-- internal/models/api_test.go | 34 +-- internal/transform/strip.go | 14 +- internal/transform/strip_test.go | 140 ++++----- 20 files changed, 746 insertions(+), 506 deletions(-) create mode 100644 internal/models/alert_test.go diff --git a/cmd/karma/alerts.go b/cmd/karma/alerts.go index 94247d1e1..be25d3329 100644 --- a/cmd/karma/alerts.go +++ b/cmd/karma/alerts.go @@ -141,14 +141,14 @@ func resolveLabelValue(name, value string) string { } func getGroupLabel(group *models.APIAlertGroup, label string) string { - if v, found := group.Labels[label]; found { - return resolveLabelValue(label, v) + if v := group.Labels.Get(label); v != nil { + return resolveLabelValue(label, v.Value) } - if v, found := group.Shared.Labels[label]; found { - return resolveLabelValue(label, v) + if v := group.Shared.Labels.Get(label); v != nil { + return resolveLabelValue(label, v.Value) } - if v, found := group.Alerts[0].Labels[label]; found { - return resolveLabelValue(label, v) + if v := group.Alerts[0].Labels.Get(label); v != nil { + return resolveLabelValue(label, v.Value) } return "" } @@ -283,14 +283,14 @@ func autoGridLabel(dedupedAlerts []models.AlertGroup) string { for _, ag := range dedupedAlerts { alertsCount += ag.Alerts.Len() for _, alert := range ag.Alerts { - for key, val := range alert.Labels { - if _, ok := labelToAlertCount[key]; !ok { - labelToAlertCount[key] = map[string]int{} + for _, l := range alert.Labels { + if _, ok := labelToAlertCount[l.Name]; !ok { + labelToAlertCount[l.Name] = map[string]int{} } - if _, ok := labelToAlertCount[key][val]; !ok { - labelToAlertCount[key][val] = 0 + if _, ok := labelToAlertCount[l.Name][l.Value]; !ok { + labelToAlertCount[l.Name][l.Value] = 0 } - labelToAlertCount[key][val]++ + labelToAlertCount[l.Name][l.Value]++ } } } diff --git a/cmd/karma/api_test.go b/cmd/karma/api_test.go index 67a77ef77..b88759079 100644 --- a/cmd/karma/api_test.go +++ b/cmd/karma/api_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "reflect" + "strings" "testing" "time" @@ -18,7 +19,7 @@ import ( ) type groupTest struct { - labels map[string]string + labels models.Labels receiver string alerts []models.Alert id string @@ -28,8 +29,8 @@ type groupTest struct { var groupTests = []groupTest{ { receiver: "by-name", - labels: map[string]string{ - "alertname": "Memory_Usage_Too_High", + labels: models.Labels{ + {Name: "alertname", Value: "Memory_Usage_Too_High"}, }, alerts: []models.Alert{ { @@ -38,10 +39,10 @@ var groupTests = []groupTest{ models.Annotation{Visible: true, Name: "alert", Value: "Memory usage exceeding threshold"}, models.Annotation{Visible: true, Name: "dashboard", Value: "http://localhost/dashboard.html", IsLink: true}, }, - Labels: map[string]string{ - "cluster": "prod", - "instance": "server2", - "job": "node_exporter", + Labels: models.Labels{ + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server2"}, + {Name: "job", Value: "node_exporter"}, }, State: models.AlertStateActive, Alertmanager: []models.AlertmanagerInstance{ @@ -64,9 +65,9 @@ var groupTests = []groupTest{ }, { receiver: "by-cluster-service", - labels: map[string]string{ - "alertname": "Memory_Usage_Too_High", - "cluster": "prod", + labels: models.Labels{ + {Name: "alertname", Value: "Memory_Usage_Too_High"}, + {Name: "cluster", Value: "prod"}, }, alerts: []models.Alert{ { @@ -83,9 +84,9 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "instance": "server2", - "job": "node_exporter", + Labels: models.Labels{ + {Name: "instance", Value: "server2"}, + {Name: "job", Value: "node_exporter"}, }, State: models.AlertStateActive, Receiver: "by-cluster-service", @@ -100,9 +101,9 @@ var groupTests = []groupTest{ }, { receiver: "by-cluster-service", - labels: map[string]string{ - "alertname": "Host_Down", - "cluster": "staging", + labels: models.Labels{ + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "staging"}, }, alerts: []models.Alert{ { @@ -116,9 +117,9 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "instance": "server3", - "ip": "127.0.0.3", + Labels: models.Labels{ + {Name: "instance", Value: "server3"}, + {Name: "ip", Value: "127.0.0.3"}, }, State: models.AlertStateActive, Receiver: "by-cluster-service", @@ -133,9 +134,9 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "instance": "server4", - "ip": "127.0.0.4", + Labels: models.Labels{ + {Name: "instance", Value: "server4"}, + {Name: "ip", Value: "127.0.0.4"}, }, State: models.AlertStateActive, Receiver: "by-cluster-service", @@ -150,9 +151,9 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "instance": "server5", - "ip": "127.0.0.5", + Labels: models.Labels{ + {Name: "instance", Value: "server5"}, + {Name: "ip", Value: "127.0.0.5"}, }, State: models.AlertStateActive, Receiver: "by-cluster-service", @@ -167,9 +168,9 @@ var groupTests = []groupTest{ }, { receiver: "by-cluster-service", - labels: map[string]string{ - "alertname": "Host_Down", - "cluster": "dev", + labels: models.Labels{ + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "dev"}, }, alerts: []models.Alert{ { @@ -183,9 +184,9 @@ var groupTests = []groupTest{ SilencedBy: []string{"168f139d-77e4-41d6-afb5-8fe2cfd0cc9d"}, }, }, - Labels: map[string]string{ - "instance": "server6", - "ip": "127.0.0.6", + Labels: models.Labels{ + {Name: "instance", Value: "server6"}, + {Name: "ip", Value: "127.0.0.6"}, }, State: models.AlertStateSuppressed, Receiver: "by-cluster-service", @@ -201,9 +202,9 @@ var groupTests = []groupTest{ SilencedBy: []string{"168f139d-77e4-41d6-afb5-8fe2cfd0cc9d", "378eaa69-097d-41c4-a8c2-fe6568c3abfc"}, }, }, - Labels: map[string]string{ - "instance": "server7", - "ip": "127.0.0.7", + Labels: models.Labels{ + {Name: "instance", Value: "server7"}, + {Name: "ip", Value: "127.0.0.7"}, }, State: models.AlertStateSuppressed, Receiver: "by-cluster-service", @@ -219,9 +220,9 @@ var groupTests = []groupTest{ SilencedBy: []string{"168f139d-77e4-41d6-afb5-8fe2cfd0cc9d"}, }, }, - Labels: map[string]string{ - "instance": "server8", - "ip": "127.0.0.8", + Labels: models.Labels{ + {Name: "instance", Value: "server8"}, + {Name: "ip", Value: "127.0.0.8"}, }, State: models.AlertStateSuppressed, Receiver: "by-cluster-service", @@ -236,8 +237,8 @@ var groupTests = []groupTest{ }, { receiver: "by-name", - labels: map[string]string{ - "alertname": "Host_Down", + labels: models.Labels{ + {Name: "alertname", Value: "Host_Down"}, }, alerts: []models.Alert{ { @@ -245,10 +246,10 @@ var groupTests = []groupTest{ Annotations: models.Annotations{ models.Annotation{Visible: true, Name: "url", Value: "http://localhost/example.html", IsLink: true}, }, - Labels: map[string]string{ - "cluster": "prod", - "instance": "server1", - "ip": "127.0.0.1", + Labels: models.Labels{ + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server1"}, + {Name: "ip", Value: "127.0.0.1"}, }, State: models.AlertStateActive, Alertmanager: []models.AlertmanagerInstance{ @@ -264,10 +265,10 @@ var groupTests = []groupTest{ { StartsAt: time.Date(2019, time.January, 1, 0, 1, 0, 0, time.UTC), Annotations: models.Annotations{}, - Labels: map[string]string{ - "cluster": "prod", - "instance": "server2", - "ip": "127.0.0.2", + Labels: models.Labels{ + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server2"}, + {Name: "ip", Value: "127.0.0.2"}, }, State: models.AlertStateActive, Alertmanager: []models.AlertmanagerInstance{ @@ -283,10 +284,10 @@ var groupTests = []groupTest{ { StartsAt: time.Date(2019, time.January, 1, 0, 1, 0, 1, time.UTC), Annotations: models.Annotations{}, - Labels: map[string]string{ - "cluster": "staging", - "instance": "server3", - "ip": "127.0.0.3", + Labels: models.Labels{ + {Name: "cluster", Value: "staging"}, + {Name: "instance", Value: "server3"}, + {Name: "ip", Value: "127.0.0.3"}, }, State: models.AlertStateActive, Alertmanager: []models.AlertmanagerInstance{ @@ -302,10 +303,10 @@ var groupTests = []groupTest{ { StartsAt: time.Date(2019, time.January, 1, 0, 0, 59, 0, time.UTC), Annotations: models.Annotations{}, - Labels: map[string]string{ - "cluster": "staging", - "instance": "server4", - "ip": "127.0.0.4", + Labels: models.Labels{ + {Name: "cluster", Value: "staging"}, + {Name: "instance", Value: "server4"}, + {Name: "ip", Value: "127.0.0.4"}, }, State: models.AlertStateActive, Alertmanager: []models.AlertmanagerInstance{ @@ -321,10 +322,10 @@ var groupTests = []groupTest{ { StartsAt: time.Date(2019, time.January, 10, 0, 0, 0, 0, time.UTC), Annotations: models.Annotations{}, - Labels: map[string]string{ - "cluster": "staging", - "instance": "server5", - "ip": "127.0.0.5", + Labels: models.Labels{ + {Name: "cluster", Value: "staging"}, + {Name: "instance", Value: "server5"}, + {Name: "ip", Value: "127.0.0.5"}, }, State: models.AlertStateActive, Alertmanager: []models.AlertmanagerInstance{ @@ -340,10 +341,10 @@ var groupTests = []groupTest{ { StartsAt: time.Date(2019, time.January, 10, 1, 0, 0, 0, time.UTC), Annotations: models.Annotations{}, - Labels: map[string]string{ - "cluster": "dev", - "instance": "server6", - "ip": "127.0.0.6", + Labels: models.Labels{ + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "server6"}, + {Name: "ip", Value: "127.0.0.6"}, }, State: models.AlertStateSuppressed, Alertmanager: []models.AlertmanagerInstance{ @@ -359,10 +360,10 @@ var groupTests = []groupTest{ { StartsAt: time.Date(2019, time.January, 10, 0, 20, 0, 0, time.UTC), Annotations: models.Annotations{}, - Labels: map[string]string{ - "cluster": "dev", - "instance": "server7", - "ip": "127.0.0.7", + Labels: models.Labels{ + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "server7"}, + {Name: "ip", Value: "127.0.0.7"}, }, State: models.AlertStateSuppressed, Alertmanager: []models.AlertmanagerInstance{ @@ -378,10 +379,10 @@ var groupTests = []groupTest{ { StartsAt: time.Date(2019, time.January, 10, 0, 21, 0, 0, time.UTC), Annotations: models.Annotations{}, - Labels: map[string]string{ - "cluster": "dev", - "instance": "server8", - "ip": "127.0.0.8", + Labels: models.Labels{ + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "server8"}, + {Name: "ip", Value: "127.0.0.8"}, }, State: models.AlertStateSuppressed, Alertmanager: []models.AlertmanagerInstance{ @@ -404,9 +405,9 @@ var groupTests = []groupTest{ }, { receiver: "by-cluster-service", - labels: map[string]string{ - "alertname": "Free_Disk_Space_Too_Low", - "cluster": "staging", + labels: models.Labels{ + {Name: "alertname", Value: "Free_Disk_Space_Too_Low"}, + {Name: "cluster", Value: "staging"}, }, alerts: []models.Alert{ { @@ -423,10 +424,10 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "instance": "server5", - "job": "node_exporter", - "disk": "sda", + Labels: models.Labels{ + {Name: "disk", Value: "sda"}, + {Name: "instance", Value: "server5"}, + {Name: "job", Value: "node_exporter"}, }, State: models.AlertStateActive, Receiver: "by-cluster-service", @@ -441,9 +442,9 @@ var groupTests = []groupTest{ }, { receiver: "by-cluster-service", - labels: map[string]string{ - "alertname": "Host_Down", - "cluster": "prod", + labels: models.Labels{ + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "prod"}, }, alerts: []models.Alert{ { @@ -459,9 +460,9 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "instance": "server1", - "ip": "127.0.0.1", + Labels: models.Labels{ + {Name: "instance", Value: "server1"}, + {Name: "ip", Value: "127.0.0.1"}, }, State: models.AlertStateActive, Receiver: "by-cluster-service", @@ -476,9 +477,9 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "instance": "server2", - "ip": "127.0.0.2", + Labels: models.Labels{ + {Name: "instance", Value: "server2"}, + {Name: "ip", Value: "127.0.0.2"}, }, State: models.AlertStateActive, Receiver: "by-cluster-service", @@ -493,8 +494,8 @@ var groupTests = []groupTest{ }, { receiver: "by-name", - labels: map[string]string{ - "alertname": "HTTP_Probe_Failed", + labels: models.Labels{ + {Name: "alertname", Value: "HTTP_Probe_Failed"}, }, alerts: []models.Alert{ { @@ -510,8 +511,8 @@ var groupTests = []groupTest{ Source: "http://localhost/prometheus", }, }, - Labels: map[string]string{ - "instance": "web1", + Labels: models.Labels{ + {Name: "instance", Value: "web1"}, }, State: models.AlertStateSuppressed, Receiver: "by-name", @@ -527,8 +528,8 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "instance": "web2", + Labels: models.Labels{ + {Name: "instance", Value: "web2"}, }, State: models.AlertStateActive, Receiver: "by-name", @@ -543,8 +544,8 @@ var groupTests = []groupTest{ }, { receiver: "by-name", - labels: map[string]string{ - "alertname": "Free_Disk_Space_Too_Low", + labels: models.Labels{ + {Name: "alertname", Value: "Free_Disk_Space_Too_Low"}, }, alerts: []models.Alert{ { @@ -561,11 +562,11 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "cluster": "staging", - "instance": "server5", - "job": "node_exporter", - "disk": "sda", + Labels: models.Labels{ + {Name: "cluster", Value: "staging"}, + {Name: "disk", Value: "sda"}, + {Name: "instance", Value: "server5"}, + {Name: "job", Value: "node_exporter"}, }, State: models.AlertStateActive, Receiver: "by-name", @@ -580,9 +581,9 @@ var groupTests = []groupTest{ }, { receiver: "by-cluster-service", - labels: map[string]string{ - "alertname": "HTTP_Probe_Failed", - "cluster": "dev", + labels: models.Labels{ + {Name: "alertname", Value: "HTTP_Probe_Failed"}, + {Name: "cluster", Value: "dev"}, }, alerts: []models.Alert{ { @@ -599,8 +600,8 @@ var groupTests = []groupTest{ SilencedBy: []string{"0804764c-6163-4c64-b0a9-08feebe2db4b"}, }, }, - Labels: map[string]string{ - "instance": "web1", + Labels: models.Labels{ + {Name: "instance", Value: "web1"}, }, State: models.AlertStateSuppressed, Receiver: "by-cluster-service", @@ -616,8 +617,8 @@ var groupTests = []groupTest{ SilencedBy: []string{}, }, }, - Labels: map[string]string{ - "instance": "web2", + Labels: models.Labels{ + {Name: "instance", Value: "web2"}, }, State: models.AlertStateActive, Receiver: "by-cluster-service", @@ -641,12 +642,12 @@ func compareAlertGroups(testCase groupTest, group models.APIAlertGroup) bool { if len(testCase.labels) != len(group.Labels) { return false } - for key, val := range testCase.labels { - v, found := group.Labels[key] - if !found { + for _, l := range testCase.labels { + v := group.Labels.Get(l.Name) + if v == nil { return false } - if val != v { + if l.Value != v.Value { return false } } @@ -660,12 +661,12 @@ func compareAlerts(expectedAlert, gotAlert models.Alert) bool { if len(gotAlert.Labels) != len(expectedAlert.Labels) { return false } - for key, val := range expectedAlert.Labels { - v, found := gotAlert.Labels[key] - if !found { + for _, l := range expectedAlert.Labels { + v := gotAlert.Labels.Get(l.Name) + if v == nil { return false } - if val != v { + if l.Value != v.Value { return false } } @@ -1000,22 +1001,23 @@ func TestSortOrder(t *testing.T) { } else { values := []string{} for _, ag := range ur.Grids[0].AlertGroups { - v := ag.Labels[testCase.expectedLabel] + v := ag.Labels.GetValue(testCase.expectedLabel) if v == "" { - v = ag.Shared.Labels[testCase.expectedLabel] + v = ag.Shared.Labels.GetValue(testCase.expectedLabel) } if v != "" { values = append(values, v) } else { for _, alert := range ag.Alerts { - v = alert.Labels[testCase.expectedLabel] + v = alert.Labels.GetValue(testCase.expectedLabel) values = append(values, v) } } } if diff := cmp.Diff(testCase.expectedValues, values); diff != "" { - t.Errorf("Incorrectly sorted values (-want +got):\n%s", diff) + t.Errorf("Incorrectly sorted values for filter=%q order=%s label=%q reverse=%v (-want +got):\n%s", + strings.Join(testCase.filter, " "), testCase.sortOrder, testCase.sortLabel, testCase.sortReverse, diff) } } } @@ -1023,22 +1025,22 @@ func TestSortOrder(t *testing.T) { } } -func verifyStrippedLabels(t *testing.T, labels map[string]string, keep, strip []string) { +func verifyStrippedLabels(t *testing.T, labels models.Labels, keep, strip []string) { for _, l := range strip { - if val, ok := labels[l]; ok { - t.Errorf("Found stripped label %s=%s on %v", l, val, labels) + if val := labels.Get(l); val != nil { + t.Errorf("Found stripped label %s=%s on %v", val.Name, val.Value, labels) } } if len(keep) > 0 && len(strip) == 0 { - for k, v := range labels { + for _, ll := range labels { ok := false for _, l := range keep { - if k == l { + if ll.Name == l { ok = true } } if !ok { - t.Errorf("Found label %s=%s that's not on the keep list: %v", k, v, keep) + t.Errorf("Found label %s=%s that's not on the keep list: %v", ll.Name, ll.Value, keep) } } } diff --git a/cmd/karma/views.go b/cmd/karma/views.go index b27ba9256..3795b2284 100644 --- a/cmd/karma/views.go +++ b/cmd/karma/views.go @@ -224,7 +224,6 @@ func alerts(w http.ResponseWriter, r *http.Request) { return } - // use full URI (including query args) as cache key cacheKey := fmt.Sprintf("%x", structhash.Sha1(request, 1)) data, found := apiCache.Get(cacheKey) @@ -265,14 +264,14 @@ func alerts(w http.ResponseWriter, r *http.Request) { for _, ag := range filtered { perGridAlertGroup := map[string]*models.AlertGroup{} - for k := range ag.Labels { - labelMap[k] = struct{}{} + for _, l := range ag.Labels { + labelMap[l.Name] = struct{}{} } for _, alert := range ag.Alerts { alert := alert // scopelint pin - for k := range alert.Labels { - labelMap[k] = struct{}{} + for _, l := range alert.Labels { + labelMap[l.Name] = struct{}{} } allReceivers[alert.Receiver] = true @@ -292,7 +291,7 @@ func alerts(w http.ResponseWriter, r *http.Request) { alertGridLabelValues[am.Cluster] = true } default: - alertGridLabelValues[alert.Labels[gridLabel]] = true + alertGridLabelValues[alert.Labels.GetValue(gridLabel)] = true } for alertGridLabelValue := range alertGridLabelValues { @@ -390,13 +389,13 @@ func alerts(w http.ResponseWriter, r *http.Request) { } } - for key, value := range alert.Labels { - if keyMap, foundKey := dedupedColors[key]; foundKey { - if color, foundColor := keyMap[value]; foundColor { - if _, found := colors[key]; !found { - colors[key] = map[string]models.LabelColors{} + for _, l := range alert.Labels { + if keyMap, foundKey := dedupedColors[l.Name]; foundKey { + if color, foundColor := keyMap[l.Value]; foundColor { + if _, found := colors[l.Name]; !found { + colors[l.Name] = map[string]models.LabelColors{} } - colors[key][value] = color + colors[l.Name][l.Value] = color } } } @@ -684,7 +683,7 @@ func silences(w http.ResponseWriter, r *http.Request) { } type AlertList struct { - Alerts []map[string]string `json:"alerts"` + Alerts []models.Labels `json:"alerts"` } func alertList(w http.ResponseWriter, r *http.Request) { @@ -711,12 +710,9 @@ func alertList(w http.ResponseWriter, r *http.Request) { labelMap := map[string]map[string]string{} for _, ag := range filtered { for _, alert := range ag.Alerts { - labels := map[string]string{} - for k, v := range ag.Labels { - labels[k] = v - } - for k, v := range alert.Labels { - labels[k] = v + labels := ag.Labels.Map() + for _, l := range alert.Labels { + labels[l.Name] = l.Value } h := fmt.Sprintf("%x", structhash.Sha1(labels, 1)) labelMap[h] = labels @@ -736,10 +732,15 @@ func alertList(w http.ResponseWriter, r *http.Request) { sort.Strings(sortKeys) al := AlertList{ - Alerts: []map[string]string{}, + Alerts: []models.Labels{}, } for _, labels := range labelMap { - al.Alerts = append(al.Alerts, labels) + alert := models.Labels{} + for k, v := range labels { + alert = alert.Set(k, v) + } + sort.Sort(alert) + al.Alerts = append(al.Alerts, alert) } sortSliceOfLabels(al.Alerts, sortKeys, "alertname") @@ -751,20 +752,22 @@ func alertList(w http.ResponseWriter, r *http.Request) { _, _ = w.Write(data) } -func sortSliceOfLabels(labels []map[string]string, sortKeys []string, fallback string) { +func sortSliceOfLabels(labels []models.Labels, sortKeys []string, fallback string) { sort.SliceStable(labels, func(i, j int) bool { for _, k := range sortKeys { - if labels[i][k] != "" && labels[j][k] == "" { + vi := labels[i].GetValue(k) + vj := labels[j].GetValue(k) + if vi != "" && vj == "" { return true } - if labels[i][k] == "" && labels[j][k] != "" { + if vi == "" && vj != "" { return false } - if labels[i][k] != labels[j][k] { - return sortorder.NaturalLess(labels[i][k], labels[j][k]) + if vi != vj { + return sortorder.NaturalLess(vi, vj) } } - return sortorder.NaturalLess(labels[i][fallback], labels[j][fallback]) + return sortorder.NaturalLess(labels[i].GetValue(fallback), labels[j].GetValue(fallback)) }) } @@ -814,8 +817,8 @@ func counters(w http.ResponseWriter, r *http.Request) { } countLabel(counters, "@state", alert.State) countLabel(counters, "@receiver", alert.Receiver) - for key, value := range alert.Labels { - countLabel(counters, key, value) + for _, l := range alert.Labels { + countLabel(counters, l.Name, l.Value) } } } diff --git a/cmd/karma/views_test.go b/cmd/karma/views_test.go index b5581ba18..728d9971c 100644 --- a/cmd/karma/views_test.go +++ b/cmd/karma/views_test.go @@ -3474,87 +3474,87 @@ func TestAlertList(t *testing.T) { { args: "", alerts: AlertList{ - Alerts: []map[string]string{ + Alerts: []models.Labels{ { - "alertname": "Free_Disk_Space_Too_Low", - "cluster": "staging", - "disk": "sda", - "instance": "server5", - "job": "node_exporter", + {Name: "alertname", Value: "Free_Disk_Space_Too_Low"}, + {Name: "cluster", Value: "staging"}, + {Name: "disk", Value: "sda"}, + {Name: "instance", Value: "server5"}, + {Name: "job", Value: "node_exporter"}, }, { - "alertname": "HTTP_Probe_Failed", - "cluster": "dev", - "instance": "web1", - "job": "node_exporter", + {Name: "alertname", Value: "HTTP_Probe_Failed"}, + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "web1"}, + {Name: "job", Value: "node_exporter"}, }, { - "alertname": "HTTP_Probe_Failed", - "cluster": "dev", - "instance": "web2", - "job": "node_exporter", + {Name: "alertname", Value: "HTTP_Probe_Failed"}, + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "web2"}, + {Name: "job", Value: "node_exporter"}, }, { - "alertname": "Host_Down", - "cluster": "dev", - "instance": "server6", - "ip": "127.0.0.6", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "server6"}, + {Name: "ip", Value: "127.0.0.6"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Host_Down", - "cluster": "dev", - "instance": "server7", - "ip": "127.0.0.7", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "server7"}, + {Name: "ip", Value: "127.0.0.7"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Host_Down", - "cluster": "dev", - "instance": "server8", - "ip": "127.0.0.8", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "server8"}, + {Name: "ip", Value: "127.0.0.8"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Host_Down", - "cluster": "prod", - "instance": "server1", - "ip": "127.0.0.1", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server1"}, + {Name: "ip", Value: "127.0.0.1"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Host_Down", - "cluster": "prod", - "instance": "server2", - "ip": "127.0.0.2", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server2"}, + {Name: "ip", Value: "127.0.0.2"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Host_Down", - "cluster": "staging", - "instance": "server3", - "ip": "127.0.0.3", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "staging"}, + {Name: "instance", Value: "server3"}, + {Name: "ip", Value: "127.0.0.3"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Host_Down", - "cluster": "staging", - "instance": "server4", - "ip": "127.0.0.4", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "staging"}, + {Name: "instance", Value: "server4"}, + {Name: "ip", Value: "127.0.0.4"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Host_Down", - "cluster": "staging", - "instance": "server5", - "ip": "127.0.0.5", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "staging"}, + {Name: "instance", Value: "server5"}, + {Name: "ip", Value: "127.0.0.5"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Memory_Usage_Too_High", - "cluster": "prod", - "instance": "server2", - "job": "node_exporter", + {Name: "alertname", Value: "Memory_Usage_Too_High"}, + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server2"}, + {Name: "job", Value: "node_exporter"}, }, }, }, @@ -3562,13 +3562,13 @@ func TestAlertList(t *testing.T) { { args: "q=alertname=Free_Disk_Space_Too_Low", alerts: AlertList{ - Alerts: []map[string]string{ + Alerts: []models.Labels{ { - "alertname": "Free_Disk_Space_Too_Low", - "cluster": "staging", - "disk": "sda", - "instance": "server5", - "job": "node_exporter", + {Name: "alertname", Value: "Free_Disk_Space_Too_Low"}, + {Name: "cluster", Value: "staging"}, + {Name: "disk", Value: "sda"}, + {Name: "instance", Value: "server5"}, + {Name: "job", Value: "node_exporter"}, }, }, }, @@ -3576,18 +3576,18 @@ func TestAlertList(t *testing.T) { { args: "q=alertname=HTTP_Probe_Failed", alerts: AlertList{ - Alerts: []map[string]string{ + Alerts: []models.Labels{ { - "alertname": "HTTP_Probe_Failed", - "cluster": "dev", - "instance": "web1", - "job": "node_exporter", + {Name: "alertname", Value: "HTTP_Probe_Failed"}, + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "web1"}, + {Name: "job", Value: "node_exporter"}, }, { - "alertname": "HTTP_Probe_Failed", - "cluster": "dev", - "instance": "web2", - "job": "node_exporter", + {Name: "alertname", Value: "HTTP_Probe_Failed"}, + {Name: "cluster", Value: "dev"}, + {Name: "instance", Value: "web2"}, + {Name: "job", Value: "node_exporter"}, }, }, }, @@ -3595,19 +3595,19 @@ func TestAlertList(t *testing.T) { { args: "q=instance=server2", alerts: AlertList{ - Alerts: []map[string]string{ + Alerts: []models.Labels{ { - "alertname": "Host_Down", - "cluster": "prod", - "instance": "server2", - "ip": "127.0.0.2", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server2"}, + {Name: "ip", Value: "127.0.0.2"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Memory_Usage_Too_High", - "cluster": "prod", - "instance": "server2", - "job": "node_exporter", + {Name: "alertname", Value: "Memory_Usage_Too_High"}, + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server2"}, + {Name: "job", Value: "node_exporter"}, }, }, }, @@ -3615,20 +3615,20 @@ func TestAlertList(t *testing.T) { { args: "q=alertname=Host_Down&q=cluster=prod", alerts: AlertList{ - Alerts: []map[string]string{ + Alerts: []models.Labels{ { - "alertname": "Host_Down", - "cluster": "prod", - "instance": "server1", - "ip": "127.0.0.1", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server1"}, + {Name: "ip", Value: "127.0.0.1"}, + {Name: "job", Value: "node_ping"}, }, { - "alertname": "Host_Down", - "cluster": "prod", - "instance": "server2", - "ip": "127.0.0.2", - "job": "node_ping", + {Name: "alertname", Value: "Host_Down"}, + {Name: "cluster", Value: "prod"}, + {Name: "instance", Value: "server2"}, + {Name: "ip", Value: "127.0.0.2"}, + {Name: "job", Value: "node_ping"}, }, }, }, @@ -3636,7 +3636,7 @@ func TestAlertList(t *testing.T) { { args: "q=foo=bar", alerts: AlertList{ - Alerts: []map[string]string{}, + Alerts: []models.Labels{}, }, }, } @@ -3675,85 +3675,85 @@ func TestAlertList(t *testing.T) { func TestSortSliceOfLabels(t *testing.T) { type testCaseT struct { - labels []map[string]string + labels []models.Labels sortKeys []string fallback string - output []map[string]string + output []models.Labels } testCases := []testCaseT{ { - labels: []map[string]string{ - {"alertname": "alert2"}, - {"alertname": "alert1"}, + labels: []models.Labels{ + {{Name: "alertname", Value: "alert2"}}, + {{Name: "alertname", Value: "alert1"}}, }, sortKeys: []string{}, fallback: "", - output: []map[string]string{ - {"alertname": "alert2"}, - {"alertname": "alert1"}, + output: []models.Labels{ + {{Name: "alertname", Value: "alert2"}}, + {{Name: "alertname", Value: "alert1"}}, }, }, { - labels: []map[string]string{ - {"alertname": "alert2"}, - {"alertname": "alert1"}, + labels: []models.Labels{ + {{Name: "alertname", Value: "alert2"}}, + {{Name: "alertname", Value: "alert1"}}, }, sortKeys: []string{"alertname"}, fallback: "alertname", - output: []map[string]string{ - {"alertname": "alert1"}, - {"alertname": "alert2"}, + output: []models.Labels{ + {{Name: "alertname", Value: "alert1"}}, + {{Name: "alertname", Value: "alert2"}}, }, }, { - labels: []map[string]string{ - {"alertname": "alert2"}, - {"alertname": "alert1"}, + labels: []models.Labels{ + {{Name: "alertname", Value: "alert2"}}, + {{Name: "alertname", Value: "alert1"}}, }, sortKeys: []string{}, fallback: "alertname", - output: []map[string]string{ - {"alertname": "alert1"}, - {"alertname": "alert2"}, + output: []models.Labels{ + {{Name: "alertname", Value: "alert1"}}, + {{Name: "alertname", Value: "alert2"}}, }, }, { - labels: []map[string]string{ - {"alertname": "alert2"}, - {"alertname": "alert1"}, + labels: []models.Labels{ + {{Name: "alertname", Value: "alert2"}}, + {{Name: "alertname", Value: "alert1"}}, }, sortKeys: []string{"foo"}, fallback: "alertname", - output: []map[string]string{ - {"alertname": "alert1"}, - {"alertname": "alert2"}, + output: []models.Labels{ + {{Name: "alertname", Value: "alert1"}}, + {{Name: "alertname", Value: "alert2"}}, }, }, { - labels: []map[string]string{ - {"alertname": "alert1"}, - {"alertname": "alert1"}, + labels: []models.Labels{ + {{Name: "alertname", Value: "alert1"}}, + {{Name: "alertname", Value: "alert1"}}, }, sortKeys: []string{"alertname"}, fallback: "alertname", - output: []map[string]string{ - {"alertname": "alert1"}, - {"alertname": "alert1"}, + output: []models.Labels{ + {{Name: "alertname", Value: "alert1"}}, + {{Name: "alertname", Value: "alert1"}}, }, }, { - labels: []map[string]string{ - {"alertname": "alert2", "job": "a"}, - {"alertname": "alert1"}, - {"alertname": "alert3", "job": "b"}, + labels: []models.Labels{ + {{Name: "alertname", Value: "alert2"}, {Name: "job", Value: "a"}}, + {{Name: "alertname", Value: "alert1"}}, + {{Name: "alertname", Value: "alert3"}, {Name: "job", Value: "b"}}, }, sortKeys: []string{"job"}, fallback: "alertname", - output: []map[string]string{ - {"alertname": "alert2", "job": "a"}, - {"alertname": "alert3", "job": "b"}, - {"alertname": "alert1"}, + output: []models.Labels{ + {{Name: "alertname", Value: "alert2"}, {Name: "job", Value: "a"}}, + {{Name: "alertname", Value: "alert3"}, {Name: "job", Value: "b"}}, + {{Name: "alertname", Value: "alert1"}}, }, }, } diff --git a/internal/alertmanager/dedup.go b/internal/alertmanager/dedup.go index 3bc545985..998f91167 100644 --- a/internal/alertmanager/dedup.go +++ b/internal/alertmanager/dedup.go @@ -236,8 +236,9 @@ func DedupKnownLabelValues(name string) []string { for _, am := range upstreams { for _, ag := range am.Alerts() { for _, alert := range ag.Alerts { - if val, found := alert.Labels[name]; found { - dedupedValues[val] = true + if v := alert.Labels.Get(name); v != nil { + dedupedValues[v.Value] = true + } } } diff --git a/internal/alertmanager/models.go b/internal/alertmanager/models.go index 8be03ba06..fe03f0f2a 100644 --- a/internal/alertmanager/models.go +++ b/internal/alertmanager/models.go @@ -253,8 +253,8 @@ func (am *Alertmanager) pullAlerts(version string) error { if _, found := uniqueAlerts[agID][alertCFP]; !found { uniqueAlerts[agID][alertCFP] = alert } - for key := range alert.Labels { - knownLabelsMap[key] = true + for _, l := range alert.Labels { + knownLabelsMap[l.Name] = true } if name, hc := am.IsHealthCheckAlert(&alert); hc != nil { @@ -305,8 +305,8 @@ func (am *Alertmanager) pullAlerts(version string) error { transform.ColorLabel(colors, "@alertmanager", am.Name) transform.ColorLabel(colors, "@cluster", am.Cluster) } - for k, v := range alert.Labels { - transform.ColorLabel(colors, k, v) + for _, l := range alert.Labels { + transform.ColorLabel(colors, l.Name, l.Value) } alert.UpdateFingerprints() diff --git a/internal/filters/autocomplete_test.go b/internal/filters/autocomplete_test.go index 42ffe87c0..b295413fa 100644 --- a/internal/filters/autocomplete_test.go +++ b/internal/filters/autocomplete_test.go @@ -31,9 +31,9 @@ var acTests = []acTest{ Alerts: []models.Alert{ { State: models.AlertStateActive, - Labels: map[string]string{ - "foo": "bar", - "number": "1", + Labels: models.Labels{ + {Name: "foo", Value: "bar"}, + {Name: "number", Value: "1"}, }, Receiver: "default", Alertmanager: []models.AlertmanagerInstance{ @@ -43,9 +43,9 @@ var acTests = []acTest{ }, { State: models.AlertStateSuppressed, - Labels: map[string]string{ - "foo": "bar baz", - "number": "5", + Labels: models.Labels{ + {Name: "foo", Value: "bar baz"}, + {Name: "number", Value: "5"}, }, Receiver: "not default", Alertmanager: []models.AlertmanagerInstance{ diff --git a/internal/filters/filter_fuzzy.go b/internal/filters/filter_fuzzy.go index a09a7ab3a..c7bba8631 100644 --- a/internal/filters/filter_fuzzy.go +++ b/internal/filters/filter_fuzzy.go @@ -33,8 +33,8 @@ func (filter *fuzzyFilter) Match(alert *models.Alert, matches int) bool { } } - for _, val := range alert.Labels { - if filter.Matcher.Compare(val, filter.Value) { + for _, l := range alert.Labels { + if filter.Matcher.Compare(l.Value, filter.Value) { filter.Hits++ return true } diff --git a/internal/filters/filter_label.go b/internal/filters/filter_label.go index d0429431d..2ea4067ed 100644 --- a/internal/filters/filter_label.go +++ b/internal/filters/filter_label.go @@ -14,7 +14,7 @@ type labelFilter struct { func (filter *labelFilter) Match(alert *models.Alert, matches int) bool { if filter.IsValid { - isMatch := filter.Matcher.Compare(alert.Labels[filter.Matched], filter.Value) + isMatch := filter.Matcher.Compare(alert.Labels.GetValue(filter.Matched), filter.Value) if isMatch { filter.Hits++ } @@ -32,44 +32,44 @@ func newLabelFilter() FilterT { func labelAutocomplete(name string, operators []string, alerts []models.Alert) []models.Autocomplete { tokens := map[string]models.Autocomplete{} for _, alert := range alerts { - for key, value := range alert.Labels { + for _, l := range alert.Labels { for _, operator := range operators { switch operator { case equalOperator, notEqualOperator: - token := fmt.Sprintf("%s%s%s", key, operator, value) + token := fmt.Sprintf("%s%s%s", l.Name, operator, l.Value) tokens[token] = makeAC( token, []string{ - key, - fmt.Sprintf("%s%s", key, operator), - value, + l.Name, + fmt.Sprintf("%s%s", l.Name, operator), + l.Value, }, ) case regexpOperator, negativeRegexOperator: - substrings := strings.Split(value, " ") + substrings := strings.Split(l.Value, " ") if len(substrings) > 1 { for _, substring := range substrings { - token := fmt.Sprintf("%s%s%s", key, operator, substring) + token := fmt.Sprintf("%s%s%s", l.Name, operator, substring) tokens[token] = makeAC( token, []string{ - key, - fmt.Sprintf("%s%s", key, operator), - value, + l.Name, + fmt.Sprintf("%s%s", l.Name, operator), + l.Value, substring, }, ) } } case moreThanOperator, lessThanOperator: - if _, err := strconv.Atoi(value); err == nil { - token := fmt.Sprintf("%s%s%s", key, operator, value) + if _, err := strconv.Atoi(l.Value); err == nil { + token := fmt.Sprintf("%s%s%s", l.Name, operator, l.Value) tokens[token] = makeAC( token, []string{ - key, - fmt.Sprintf("%s%s", key, operator), - value, + l.Name, + fmt.Sprintf("%s%s", l.Name, operator), + l.Value, }, ) } diff --git a/internal/filters/filter_test.go b/internal/filters/filter_test.go index 850b3f395..94ee55a46 100644 --- a/internal/filters/filter_test.go +++ b/internal/filters/filter_test.go @@ -496,7 +496,7 @@ var tests = []filterTest{ { Expression: "node=vps1", IsValid: true, - Alert: models.Alert{Labels: map[string]string{"node": "vps1"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}}, IsMatch: true, }, { @@ -508,68 +508,68 @@ var tests = []filterTest{ { Expression: "node!=vps1", IsValid: true, - Alert: models.Alert{Labels: map[string]string{"node": "vps1"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}}, IsMatch: false, }, { Expression: "node!=vps1", IsValid: true, - Alert: models.Alert{Labels: map[string]string{"node": "vps2"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps2"}}}, IsMatch: true, }, { Expression: "node=~vps", IsValid: true, - Alert: models.Alert{Labels: map[string]string{"node": "vps1"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}}, IsMatch: true, }, { Expression: "node!~vps", IsValid: true, - Alert: models.Alert{Labels: map[string]string{"node": "vps1"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}}, IsMatch: false, }, { Expression: "node!~abc", IsValid: true, - Alert: models.Alert{Labels: map[string]string{"node": "vps1"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}}, IsMatch: true, }, { Expression: "node!~", IsValid: false, - Alert: models.Alert{Labels: map[string]string{"node": "vps1"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}}, IsMatch: false, }, { Expression: "node=", IsValid: false, - Alert: models.Alert{Labels: map[string]string{"node": "vps1"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}}, IsMatch: false, }, { Expression: "node===", IsValid: false, - Alert: models.Alert{Labels: map[string]string{"node": "vps1"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "node", Value: "vps1"}}}, IsMatch: false, }, { Expression: "abc", IsValid: true, - Alert: models.Alert{Labels: map[string]string{"key": "abc"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "key", Value: "abc"}}}, IsMatch: true, }, { Expression: "abc", IsValid: true, - Alert: models.Alert{Labels: map[string]string{"key": "XXXabcx"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "key", Value: "XXXabcx"}}}, IsMatch: true, }, { Expression: "abc", IsValid: true, - Alert: models.Alert{Labels: map[string]string{"abc": "xxxab"}}, + Alert: models.Alert{Labels: models.Labels{{Name: "abc", Value: "xxxab"}}}, IsMatch: false, }, { diff --git a/internal/mapper/v017/api.go b/internal/mapper/v017/api.go index 590155298..77fa8e1a4 100644 --- a/internal/mapper/v017/api.go +++ b/internal/mapper/v017/api.go @@ -49,17 +49,27 @@ func groups(c *client.AlertmanagerAPI, timeout time.Duration) ([]models.AlertGro ret := make([]models.AlertGroup, 0, len(groups.Payload)) for _, group := range groups.Payload { + ls := models.Labels{} + for k, v := range group.Labels { + ls = ls.Set(k, v) + } + sort.Sort(ls) g := models.AlertGroup{ Receiver: *group.Receiver.Name, - Labels: group.Labels, + Labels: ls, Alerts: make(models.AlertList, 0, len(group.Alerts)), } for _, alert := range group.Alerts { + ls := models.Labels{} + for k, v := range alert.Labels { + ls = ls.Set(k, v) + } + sort.Sort(ls) a := models.Alert{ Fingerprint: *alert.Fingerprint, Receiver: *group.Receiver.Name, Annotations: models.AnnotationsFromMap(alert.Annotations), - Labels: alert.Labels, + Labels: ls, StartsAt: time.Time(*alert.StartsAt), GeneratorURL: alert.GeneratorURL.String(), State: *alert.Status.State, diff --git a/internal/models/__snapshots__/models.snapshot b/internal/models/__snapshots__/models.snapshot index 8976e5a02..92a59128f 100644 --- a/internal/models/__snapshots__/models.snapshot +++ b/internal/models/__snapshots__/models.snapshot @@ -1,9 +1,12 @@ /* snapshot: SharedMaps */ { "receiver": "", - "labels": { - "alertname": "FakeAlert" - }, + "labels": [ + { + "name": "alertname", + "value": "FakeAlert" + } + ], "alerts": [ { "annotations": [ @@ -15,10 +18,16 @@ "isAction": false } ], - "labels": { - "instance": "1", - "job": "node_exporter" - }, + "labels": [ + { + "name": "job", + "value": "node_exporter" + }, + { + "name": "instance", + "value": "1" + } + ], "startsAt": "0001-01-01T00:00:00Z", "state": "suppressed", "alertmanager": [ @@ -54,10 +63,16 @@ }, { "annotations": [], - "labels": { - "instance": "2", - "job": "node_exporter" - }, + "labels": [ + { + "name": "job", + "value": "node_exporter" + }, + { + "name": "instance", + "value": "2" + } + ], "startsAt": "0001-01-01T00:00:00Z", "state": "active", "alertmanager": [ @@ -93,11 +108,20 @@ }, { "annotations": [], - "labels": { - "extra": "ignore", - "instance": "3", - "job": "blackbox" - }, + "labels": [ + { + "name": "job", + "value": "blackbox" + }, + { + "name": "instance", + "value": "3" + }, + { + "name": "extra", + "value": "ignore" + } + ], "startsAt": "0001-01-01T00:00:00Z", "state": "suppressed", "alertmanager": [ @@ -146,7 +170,7 @@ "isAction": false } ], - "labels": {}, + "labels": [], "silences": { "fakeCluster": [ "fakeSilence1", diff --git a/internal/models/alert.go b/internal/models/alert.go index 9d18092b3..196b50ce9 100644 --- a/internal/models/alert.go +++ b/internal/models/alert.go @@ -2,9 +2,11 @@ package models import ( "fmt" + "strings" "time" "github.com/cnf/structhash" + "github.com/fvbommel/sortorder" ) // AlertStateUnprocessed means that Alertmanager notify didn't yet process it @@ -24,16 +26,91 @@ var AlertStateList = []string{ AlertStateSuppressed, } +type Label struct { + Name string `json:"name"` + Value string `json:"value"` +} + +func (l Label) String() string { + return fmt.Sprintf("%s=\"%s\"", l.Name, l.Value) +} + +type Labels []Label + +func (ls Labels) String() string { + var s []string + for _, l := range ls { + s = append(s, l.String()) + } + return strings.Join(s, ",") +} + +func (ls Labels) Map() map[string]string { + m := make(map[string]string, len(ls)) + for _, l := range ls { + m[l.Name] = l.Value + } + return m +} + +func (ls Labels) Len() int { + return len(ls) +} + +func (ls Labels) Swap(i, j int) { + ls[i], ls[j] = ls[j], ls[i] +} + +func (ls Labels) Less(i, j int) bool { + if ls[i].Name == ls[j].Name { + return sortorder.NaturalLess(ls[i].Value, ls[j].Value) + } + return sortorder.NaturalLess(ls[i].Name, ls[j].Name) +} + +func (ls Labels) Get(name string) *Label { + for _, l := range ls { + l := l + if l.Name == name { + return &l + } + } + return nil +} + +func (ls Labels) GetValue(name string) string { + for _, l := range ls { + if l.Name == name { + return l.Value + } + } + return "" +} + +func (ls Labels) Add(l Label) Labels { + if ls.Get(l.Name) != nil { + return ls + } + return append(ls, l) +} + +func (ls Labels) Set(name, value string) Labels { + if ls.Get(name) != nil { + return ls + } + return append(ls, Label{Name: name, Value: value}) +} + // Alert is vanilla alert + some additional attributes // karma extends an alert object with: // * Links map, it's generated from annotations if annotation value is an url // it's pulled out of annotation map and returned under links field, // karma UI used this to show links differently than other annotations type Alert struct { - Annotations Annotations `json:"annotations"` - Labels map[string]string `json:"labels"` - StartsAt time.Time `json:"startsAt"` - State string `json:"state"` + Annotations Annotations `json:"annotations"` + Labels Labels `json:"labels"` + StartsAt time.Time `json:"startsAt"` + State string `json:"state"` // those are not exposed in JSON, Alertmanager specific value will be in kept // in the Alertmanager slice // skip those when generating alert fingerprint too @@ -52,7 +129,7 @@ type Alert struct { // UpdateFingerprints will generate a new set of fingerprints for this alert // it should be called after modifying any field that isn't tagged with hash:"-" func (a *Alert) UpdateFingerprints() { - a.LabelsFP = fmt.Sprintf("%x", structhash.Sha1(a.Labels, 1)) + a.LabelsFP = fmt.Sprintf("%x", structhash.Sha1(a.Labels.Map(), 1)) a.contentFP = fmt.Sprintf("%x", structhash.Sha1(a, 1)) } diff --git a/internal/models/alert_test.go b/internal/models/alert_test.go new file mode 100644 index 000000000..6e1cf1f7c --- /dev/null +++ b/internal/models/alert_test.go @@ -0,0 +1,111 @@ +package models_test + +import ( + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/prymitive/karma/internal/models" +) + +func TestLabelsSet(t *testing.T) { + l := models.Labels{} + if l.String() != "" { + t.Errorf("Invalid labels: %s", l) + } + + l = l.Set("foo", "bar") + if l.String() != `foo="bar"` { + t.Errorf("Invalid labels: %s", l) + } + + l = l.Set("foo", "bar") + if l.String() != `foo="bar"` { + t.Errorf("Invalid labels: %s", l) + } + + l = l.Set("bar", "foo") + if l.String() != `foo="bar",bar="foo"` { + t.Errorf("Invalid labels: %s", l) + } + + l = l.Set("bar", "foo") + if l.String() != `foo="bar",bar="foo"` { + t.Errorf("Invalid labels: %s", l) + } + + l = l.Set("foo", "bar") + if l.String() != `foo="bar",bar="foo"` { + t.Errorf("Invalid labels: %s", l) + } +} + +type sortLabelsTestCase struct { + in models.Labels + out models.Labels +} + +func TestSortLabels(t *testing.T) { + testCases := []sortLabelsTestCase{ + { + in: models.Labels{ + {Name: "foo", Value: "bar"}, + }, + out: models.Labels{ + {Name: "foo", Value: "bar"}, + }, + }, + { + in: models.Labels{ + {Name: "foo", Value: "bar"}, + {Name: "bar", Value: "foo"}, + }, + out: models.Labels{ + {Name: "bar", Value: "foo"}, + {Name: "foo", Value: "bar"}, + }, + }, + { + in: models.Labels{ + {Name: "bar", Value: "foo"}, + {Name: "foo", Value: "bar"}, + }, + out: models.Labels{ + {Name: "bar", Value: "foo"}, + {Name: "foo", Value: "bar"}, + }, + }, + { + in: models.Labels{ + {Name: "foo", Value: "foo"}, + {Name: "bar", Value: "foo"}, + {Name: "foo", Value: "bar"}, + }, + out: models.Labels{ + {Name: "bar", Value: "foo"}, + {Name: "foo", Value: "bar"}, + {Name: "foo", Value: "foo"}, + }, + }, + { + in: models.Labels{ + {Name: "1", Value: "a12"}, + {Name: "1", Value: "1"}, + {Name: "1", Value: "a2"}, + }, + out: models.Labels{ + {Name: "1", Value: "1"}, + {Name: "1", Value: "a2"}, + {Name: "1", Value: "a12"}, + }, + }, + } + + for _, testCase := range testCases { + sort.Sort(testCase.in) + if diff := cmp.Diff(testCase.in, testCase.out); diff != "" { + t.Errorf("Incorrectly sorted labels (-want +got):\n%s", diff) + break + } + } +} diff --git a/internal/models/alertgroup.go b/internal/models/alertgroup.go index 364e9ad58..fe581d310 100644 --- a/internal/models/alertgroup.go +++ b/internal/models/alertgroup.go @@ -35,14 +35,14 @@ func (a AlertList) Less(i, j int) bool { // There is a hash computed from all alerts, it's used by UI to quickly tell // if there was any change in a group and it needs to refresh it type AlertGroup struct { - Receiver string `json:"receiver"` - Labels map[string]string `json:"labels"` - Alerts AlertList `json:"alerts"` - ID string `json:"id"` - Hash string `json:"-"` - AlertmanagerCount map[string]int `json:"alertmanagerCount"` - StateCount map[string]int `json:"stateCount"` - LatestStartsAt time.Time `json:"-"` + Receiver string `json:"receiver"` + Labels Labels `json:"labels"` + Alerts AlertList `json:"alerts"` + ID string `json:"id"` + Hash string `json:"-"` + AlertmanagerCount map[string]int `json:"alertmanagerCount"` + StateCount map[string]int `json:"stateCount"` + LatestStartsAt time.Time `json:"-"` } // LabelsFingerprint is a checksum of this AlertGroup labels and the receiver @@ -50,7 +50,7 @@ type AlertGroup struct { func (ag AlertGroup) LabelsFingerprint() string { agIDHasher := sha1.New() _, _ = io.WriteString(agIDHasher, ag.Receiver) - _, _ = io.WriteString(agIDHasher, fmt.Sprintf("%x", structhash.Sha1(ag.Labels, 1))) + _, _ = io.WriteString(agIDHasher, fmt.Sprintf("%x", structhash.Sha1(ag.Labels.Map(), 1))) return fmt.Sprintf("%x", agIDHasher.Sum(nil)) } diff --git a/internal/models/alertgroup_test.go b/internal/models/alertgroup_test.go index 5dac84f37..730398a78 100644 --- a/internal/models/alertgroup_test.go +++ b/internal/models/alertgroup_test.go @@ -97,8 +97,10 @@ var agFPTests = []agFPTest{ { name: "different Labels shouldn't change content fingerprint", ag: models.AlertGroup{ - Receiver: "default", - Labels: map[string]string{"foo": "bar"}, + Receiver: "default", + Labels: models.Labels{ + {Name: "foo", Value: "bar"}, + }, StateCount: map[string]int{"default": 0}, }, fpChange: false, @@ -107,10 +109,12 @@ var agFPTests = []agFPTest{ name: "different set of alerts should change content fingerprint", ag: models.AlertGroup{ Receiver: "default", - Labels: map[string]string{"foo": "bar"}, + Labels: models.Labels{{Name: "foo", Value: "bar"}}, Alerts: models.AlertList{ models.Alert{ - Labels: map[string]string{"foo1": "bar"}, + Labels: models.Labels{ + {Name: "foo1", Value: "bar"}, + }, }, }, StateCount: map[string]int{"default": 0}, @@ -121,10 +125,12 @@ var agFPTests = []agFPTest{ name: "another different set of alerts should change content fingerprint", ag: models.AlertGroup{ Receiver: "default", - Labels: map[string]string{"bar": "foo"}, + Labels: models.Labels{{Name: "bar", Value: "foo"}}, Alerts: models.AlertList{ models.Alert{ - Labels: map[string]string{"bar": "foo"}, + Labels: models.Labels{ + {Name: "bar", Value: "foo"}, + }, }, }, StateCount: map[string]int{"default": 0}, @@ -135,10 +141,14 @@ var agFPTests = []agFPTest{ name: "repeating last set of alerts shouldn't change content fingerprint", ag: models.AlertGroup{ Receiver: "default", - Labels: map[string]string{"bar": "foo"}, + Labels: models.Labels{ + {Name: "bar", Value: "foo"}, + }, Alerts: models.AlertList{ models.Alert{ - Labels: map[string]string{"bar": "foo"}, + Labels: models.Labels{ + {Name: "bar", Value: "foo"}, + }, }, }, StateCount: map[string]int{"default": 0}, diff --git a/internal/models/api.go b/internal/models/api.go index be48fe37f..cd13e9989 100644 --- a/internal/models/api.go +++ b/internal/models/api.go @@ -94,7 +94,7 @@ func (lnsl LabelNameStatsList) Less(i, j int) bool { // APIAlertGroupSharedMaps defines shared part of APIAlertGroup type APIAlertGroupSharedMaps struct { Annotations Annotations `json:"annotations"` - Labels map[string]string `json:"labels"` + Labels Labels `json:"labels"` Silences map[string][]string `json:"silences"` Sources []string `json:"sources"` Clusters []string `json:"clusters"` @@ -117,8 +117,8 @@ func (ag *APIAlertGroup) dedupLabels() { labelCounts := make(map[string]int, len(ag.Alerts)) for _, alert := range ag.Alerts { - for name, val := range alert.Labels { - key := fmt.Sprintf("%s\n%s", name, val) + for _, l := range alert.Labels { + key := fmt.Sprintf("%s\n%s", l.Name, l.Value) _, found := labelCounts[key] if found { labelCounts[key]++ @@ -128,16 +128,16 @@ func (ag *APIAlertGroup) dedupLabels() { } } - sharedLabels := map[string]string{} + sharedLabels := Labels{} for i, alert := range ag.Alerts { - newAlertLabels := map[string]string{} - for name, val := range alert.Labels { - key := fmt.Sprintf("%s\n%s", name, val) + newAlertLabels := Labels{} + for _, l := range alert.Labels { + key := fmt.Sprintf("%s\n%s", l.Name, l.Value) if labelCounts[key] == totalAlerts { - sharedLabels[name] = val + sharedLabels = sharedLabels.Add(l) } else { - newAlertLabels[name] = val + newAlertLabels = newAlertLabels.Add(l) } } ag.Alerts[i].Labels = newAlertLabels @@ -148,27 +148,27 @@ func (ag *APIAlertGroup) dedupLabels() { } func (ag *APIAlertGroup) removeGroupingLabels(dropNames []string) { - newGroupLabels := map[string]string{} - for name, val := range ag.Labels { - if slices.StringInSlice(dropNames, name) { + newGroupLabels := Labels{} + for _, l := range ag.Labels { + if slices.StringInSlice(dropNames, l.Name) { continue } - newGroupLabels[name] = val + newGroupLabels = newGroupLabels.Add(l) } ag.Labels = newGroupLabels for i, alert := range ag.Alerts { - newAlertLabels := map[string]string{} - for name, val := range alert.Labels { - if slices.StringInSlice(dropNames, name) { + newAlertLabels := Labels{} + for _, l := range alert.Labels { + if slices.StringInSlice(dropNames, l.Name) { // skip all labels from the drop list continue } - if _, found := ag.Labels[name]; found { + if v := ag.Labels.Get(l.Name); v != nil { // skip all labels that are used for grouping continue } - newAlertLabels[name] = val + newAlertLabels = newAlertLabels.Add(l) } ag.Alerts[i].Labels = newAlertLabels } @@ -322,11 +322,11 @@ func (ag *APIAlertGroup) populateAllLabels() { labels := map[string]int{} for _, alert := range ag.Alerts { - for k := range alert.Labels { - if _, ok := labels[k]; !ok { - labels[k] = 0 + for _, l := range alert.Labels { + if _, ok := labels[l.Name]; !ok { + labels[l.Name] = 0 } - labels[k]++ + labels[l.Name]++ } } @@ -339,15 +339,15 @@ func (ag *APIAlertGroup) populateAllLabels() { } for _, alert := range ag.Alerts { - for k, v := range alert.Labels { - if _, ok := labelNames[k]; !ok { + for _, l := range alert.Labels { + if _, ok := labelNames[l.Name]; !ok { continue } - if _, ok := ag.AllLabels[alert.State][k]; !ok { - ag.AllLabels[alert.State][k] = []string{} + if _, ok := ag.AllLabels[alert.State][l.Name]; !ok { + ag.AllLabels[alert.State][l.Name] = []string{} } - if !slices.StringInSlice(ag.AllLabels[alert.State][k], v) { - ag.AllLabels[alert.State][k] = append(ag.AllLabels[alert.State][k], v) + if !slices.StringInSlice(ag.AllLabels[alert.State][l.Name], l.Value) { + ag.AllLabels[alert.State][l.Name] = append(ag.AllLabels[alert.State][l.Name], l.Value) } } } @@ -372,7 +372,7 @@ func (ag *APIAlertGroup) DedupSharedMaps(ignoredLabels []string) { ag.dedupClusters() } else { ag.Shared = APIAlertGroupSharedMaps{ - Labels: map[string]string{}, + Labels: Labels{}, Annotations: Annotations{}, Silences: map[string][]string{}, Clusters: []string{}, diff --git a/internal/models/api_test.go b/internal/models/api_test.go index 93dc4408b..79831c958 100644 --- a/internal/models/api_test.go +++ b/internal/models/api_test.go @@ -14,8 +14,8 @@ import ( func TestDedupSharedMaps(t *testing.T) { ag := models.APIAlertGroup{ AlertGroup: models.AlertGroup{ - Labels: map[string]string{ - "alertname": "FakeAlert", + Labels: models.Labels{ + {"alertname", "FakeAlert"}, }, Alerts: models.AlertList{ models.Alert{ @@ -30,10 +30,10 @@ func TestDedupSharedMaps(t *testing.T) { Value: "bar", }, }, - Labels: map[string]string{ - "alertname": "FakeAlert", - "job": "node_exporter", - "instance": "1", + Labels: models.Labels{ + {"alertname", "FakeAlert"}, + {"job", "node_exporter"}, + {"instance", "1"}, }, Alertmanager: []models.AlertmanagerInstance{ { @@ -60,10 +60,10 @@ func TestDedupSharedMaps(t *testing.T) { Value: "this is summary", }, }, - Labels: map[string]string{ - "alertname": "FakeAlert", - "job": "node_exporter", - "instance": "2", + Labels: models.Labels{ + {"alertname", "FakeAlert"}, + {"job", "node_exporter"}, + {"instance", "2"}, }, Alertmanager: []models.AlertmanagerInstance{ { @@ -90,11 +90,11 @@ func TestDedupSharedMaps(t *testing.T) { Value: "this is summary", }, }, - Labels: map[string]string{ - "alertname": "FakeAlert", - "job": "blackbox", - "instance": "3", - "extra": "ignore", + Labels: models.Labels{ + {"alertname", "FakeAlert"}, + {"job", "blackbox"}, + {"instance", "3"}, + {"extra", "ignore"}, }, Alertmanager: []models.AlertmanagerInstance{ { @@ -126,8 +126,8 @@ func TestDedupSharedMapsSingleGroup(t *testing.T) { ag := models.APIAlertGroup{ AlertGroup: models.AlertGroup{ Alerts: models.AlertList{ - models.Alert{State: models.AlertStateActive, Labels: map[string]string{"foo": "bar"}}, - models.Alert{State: models.AlertStateUnprocessed, Labels: map[string]string{"foo": "bar"}}, + models.Alert{State: models.AlertStateActive, Labels: models.Labels{{"foo", "bar"}}}, + models.Alert{State: models.AlertStateUnprocessed, Labels: models.Labels{{"foo", "bar"}}}, }, }, } diff --git a/internal/transform/strip.go b/internal/transform/strip.go index 13b05a392..cde0f8767 100644 --- a/internal/transform/strip.go +++ b/internal/transform/strip.go @@ -2,6 +2,7 @@ package transform import ( "regexp" + "sort" "strings" "github.com/prymitive/karma/internal/models" @@ -12,21 +13,22 @@ import ( // it takes the list of label keys to ignore and alert label map // it will return label map without labels found on the ignore list func StripLables(keptLabels, ignoredLabels []string, keptLabelsRegex, ignoredLabelsRegex []*regexp.Regexp, - sourceLabels map[string]string) map[string]string { + sourceLabels models.Labels) models.Labels { // empty keep lists means keep everything by default keepAll := len(keptLabels) == 0 && len(keptLabelsRegex) == 0 - labels := map[string]string{} - for label, value := range sourceLabels { + labels := models.Labels{} + for _, label := range sourceLabels { // is explicitly marked to be kept - inKeep := slices.StringInSlice(keptLabels, label) || matchesAnyRegex(label, keptLabelsRegex) + inKeep := slices.StringInSlice(keptLabels, label.Name) || matchesAnyRegex(label.Name, keptLabelsRegex) // is explicitly marked to be stripped - inStrip := slices.StringInSlice(ignoredLabels, label) || matchesAnyRegex(label, ignoredLabelsRegex) + inStrip := slices.StringInSlice(ignoredLabels, label.Name) || matchesAnyRegex(label.Name, ignoredLabelsRegex) if (keepAll || inKeep) && !inStrip { // strip leading and trailing space in label value // this is to normalize values in case space is added by Alertmanager rules - labels[label] = strings.TrimSpace(value) + labels = labels.Set(label.Name, strings.TrimSpace(label.Value)) } } + sort.Sort(labels) return labels } diff --git a/internal/transform/strip_test.go b/internal/transform/strip_test.go index 6c57567e2..2c4d03edb 100644 --- a/internal/transform/strip_test.go +++ b/internal/transform/strip_test.go @@ -1,12 +1,12 @@ package transform_test import ( - "github.com/prymitive/karma/internal/regex" "reflect" "regexp" "testing" "github.com/prymitive/karma/internal/models" + "github.com/prymitive/karma/internal/regex" "github.com/prymitive/karma/internal/transform" ) @@ -15,8 +15,8 @@ type stripLabelTest struct { keep []string stripRegex []string keepRegex []string - before map[string]string - after map[string]string + before models.Labels + after models.Labels } var stripLabelTests = []stripLabelTest{ @@ -25,14 +25,14 @@ var stripLabelTests = []stripLabelTest{ keep: []string{}, stripRegex: []string{}, keepRegex: []string{}, - before: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + before: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "env", Value: "production"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{ - "host": "localhost", - "level": "info", + after: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "level", Value: "info"}, }, }, { @@ -40,15 +40,15 @@ var stripLabelTests = []stripLabelTest{ keep: []string{}, stripRegex: []string{}, keepRegex: []string{}, - before: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + before: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "env", Value: "production"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + after: models.Labels{ + {Name: "env", Value: "production"}, + {Name: "host", Value: "localhost"}, + {Name: "level", Value: "info"}, }, }, { @@ -56,15 +56,15 @@ var stripLabelTests = []stripLabelTest{ keep: []string{}, stripRegex: []string{}, keepRegex: []string{}, - before: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + before: models.Labels{ + {Name: "env", Value: "production"}, + {Name: "host", Value: "localhost"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + after: models.Labels{ + {Name: "env", Value: "production"}, + {Name: "host", Value: "localhost"}, + {Name: "level", Value: "info"}, }, }, { @@ -72,23 +72,23 @@ var stripLabelTests = []stripLabelTest{ keep: []string{}, stripRegex: []string{}, keepRegex: []string{}, - before: map[string]string{ - "host": "localhost", + before: models.Labels{ + {Name: "host", Value: "localhost"}, }, - after: map[string]string{}, + after: models.Labels{}, }, { strip: []string{}, keep: []string{"env"}, stripRegex: []string{}, keepRegex: []string{}, - before: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + before: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "env", Value: "production"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{ - "env": "production", + after: models.Labels{ + {Name: "env", Value: "production"}, }, }, { @@ -96,13 +96,13 @@ var stripLabelTests = []stripLabelTest{ keep: []string{"host"}, stripRegex: []string{}, keepRegex: []string{}, - before: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + before: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "env", Value: "production"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{ - "host": "localhost", + after: models.Labels{ + {Name: "host", Value: "localhost"}, }, }, { @@ -110,24 +110,24 @@ var stripLabelTests = []stripLabelTest{ keep: []string{"env"}, stripRegex: []string{}, keepRegex: []string{}, - before: map[string]string{ - "host": "localhost", - "level": "info", + before: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{}, + after: models.Labels{}, }, { strip: []string{}, keep: []string{}, stripRegex: []string{".*e.*"}, keepRegex: []string{}, - before: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + before: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "env", Value: "production"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{ - "host": "localhost", + after: models.Labels{ + {Name: "host", Value: "localhost"}, }, }, { @@ -135,14 +135,14 @@ var stripLabelTests = []stripLabelTest{ keep: []string{}, stripRegex: []string{}, keepRegex: []string{".*e.*"}, - before: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + before: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "env", Value: "production"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{ - "env": "production", - "level": "info", + after: models.Labels{ + {Name: "env", Value: "production"}, + {Name: "level", Value: "info"}, }, }, { @@ -150,13 +150,13 @@ var stripLabelTests = []stripLabelTest{ keep: []string{"env", "level"}, stripRegex: []string{".*el"}, keepRegex: []string{}, - before: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + before: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "env", Value: "production"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{ - "env": "production", + after: models.Labels{ + {Name: "env", Value: "production"}, }, }, { @@ -164,13 +164,13 @@ var stripLabelTests = []stripLabelTest{ keep: []string{}, stripRegex: []string{}, keepRegex: []string{".*e.*"}, - before: map[string]string{ - "host": "localhost", - "env": "production", - "level": "info", + before: models.Labels{ + {Name: "host", Value: "localhost"}, + {Name: "env", Value: "production"}, + {Name: "level", Value: "info"}, }, - after: map[string]string{ - "env": "production", + after: models.Labels{ + {Name: "env", Value: "production"}, }, }, }