From 784bd3ee87201477fffc0b4659578c2422ab60f3 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 18:15:15 +0000 Subject: [PATCH] [release-11.6.8] Alerting: Fix unmarshalling of GettableStatus to include time intervals (#112731) * Alerting: Fix unmarshalling of GettableStatus to include time intervals (#112602) * move test files into test-data * add test for the bug * populate time-intervals of gettableStatus config (cherry picked from commit 5f9a51418c5bbf460d54741bcab0f00ae3a4e616) * change test data to match the version of Alertmanager --------- Co-authored-by: Yuri Tseretyan --- .../api/tooling/definitions/alertmanager.go | 10 +- .../tooling/definitions/alertmanager_test.go | 36 +- .../alertmanager_test_artifact.json | 0 .../alertmanager_test_artifact.yaml | 0 .../test-data/gettable-status-expected.json | 318 ++++++++++++++++++ .../test-data/gettable-status.json | 24 ++ 6 files changed, 380 insertions(+), 8 deletions(-) rename pkg/services/ngalert/api/tooling/definitions/{ => test-data}/alertmanager_test_artifact.json (100%) rename pkg/services/ngalert/api/tooling/definitions/{ => test-data}/alertmanager_test_artifact.yaml (100%) create mode 100644 pkg/services/ngalert/api/tooling/definitions/test-data/gettable-status-expected.json create mode 100644 pkg/services/ngalert/api/tooling/definitions/test-data/gettable-status.json diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index 5a9c6a5b868..103beb9d84c 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -462,10 +462,12 @@ func (s *GettableStatus) UnmarshalJSON(b []byte) error { s.Cluster = amStatus.Cluster s.Config = &PostableApiAlertingConfig{Config: Config{ - Global: c.Global, - Route: AsGrafanaRoute(c.Route), - InhibitRules: c.InhibitRules, - Templates: c.Templates, + Global: c.Global, + Route: AsGrafanaRoute(c.Route), + InhibitRules: c.InhibitRules, + Templates: c.Templates, + MuteTimeIntervals: c.MuteTimeIntervals, + TimeIntervals: c.TimeIntervals, }} s.Uptime = amStatus.Uptime s.VersionInfo = amStatus.VersionInfo diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go index 55821b9ba58..a7c0a3edecc 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager_test.go @@ -1,8 +1,10 @@ package definitions import ( + "embed" "encoding/json" - "os" + "path" + "reflect" "strings" "testing" @@ -13,6 +15,32 @@ import ( "gopkg.in/yaml.v3" ) +//go:embed test-data/*.* +var testData embed.FS + +func Test_GettableStatusUnmarshalJSON(t *testing.T) { + incoming, err := testData.ReadFile(path.Join("test-data", "gettable-status.json")) + require.Nil(t, err) + + var actual GettableStatus + require.NoError(t, json.Unmarshal(incoming, &actual)) + + actualJson, err := json.Marshal(actual) + require.NoError(t, err) + + expected, err := testData.ReadFile(path.Join("test-data", "gettable-status-expected.json")) + require.NoError(t, err) + assert.JSONEq(t, string(expected), string(actualJson)) + + v := reflect.ValueOf(actual.Config.Config) + ty := v.Type() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + fieldName := ty.Field(i).Name + assert.False(t, field.IsZero(), "Field %s should not be zero value", fieldName) + } +} + func Test_GettableUserConfigUnmarshaling(t *testing.T) { for _, tc := range []struct { desc, input string @@ -138,10 +166,10 @@ alertmanager_config: | func Test_GettableUserConfigRoundtrip(t *testing.T) { // raw contains secret fields. We'll unmarshal, re-marshal, and ensure // the fields are not redacted. - yamlEncoded, err := os.ReadFile("alertmanager_test_artifact.yaml") + yamlEncoded, err := testData.ReadFile(path.Join("test-data", "alertmanager_test_artifact.yaml")) require.Nil(t, err) - jsonEncoded, err := os.ReadFile("alertmanager_test_artifact.json") + jsonEncoded, err := testData.ReadFile(path.Join("test-data", "alertmanager_test_artifact.json")) require.Nil(t, err) // test GettableUserConfig (yamlDecode -> jsonEncode) @@ -160,7 +188,7 @@ func Test_GettableUserConfigRoundtrip(t *testing.T) { } func Test_Marshaling_Validation(t *testing.T) { - jsonEncoded, err := os.ReadFile("alertmanager_test_artifact.json") + jsonEncoded, err := testData.ReadFile(path.Join("test-data", "alertmanager_test_artifact.json")) require.Nil(t, err) var tmp GettableUserConfig diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test_artifact.json b/pkg/services/ngalert/api/tooling/definitions/test-data/alertmanager_test_artifact.json similarity index 100% rename from pkg/services/ngalert/api/tooling/definitions/alertmanager_test_artifact.json rename to pkg/services/ngalert/api/tooling/definitions/test-data/alertmanager_test_artifact.json diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager_test_artifact.yaml b/pkg/services/ngalert/api/tooling/definitions/test-data/alertmanager_test_artifact.yaml similarity index 100% rename from pkg/services/ngalert/api/tooling/definitions/alertmanager_test_artifact.yaml rename to pkg/services/ngalert/api/tooling/definitions/test-data/alertmanager_test_artifact.yaml diff --git a/pkg/services/ngalert/api/tooling/definitions/test-data/gettable-status-expected.json b/pkg/services/ngalert/api/tooling/definitions/test-data/gettable-status-expected.json new file mode 100644 index 00000000000..869719e5199 --- /dev/null +++ b/pkg/services/ngalert/api/tooling/definitions/test-data/gettable-status-expected.json @@ -0,0 +1,318 @@ +{ + "cluster": { + "name": "01K7SGS3KSRG8FT5RZQPFN72NB", + "peers": [ + { + "address": "172.18.0.5:9094", + "name": "01K7SGS3KSRG8FT5RZQPFN72NB" + } + ], + "status": "ready" + }, + "config": { + "global": { + "resolve_timeout": "5m", + "http_config": { + "tls_config": { + "insecure_skip_verify": false + }, + "follow_redirects": true, + "enable_http2": true, + "proxy_url": null + }, + "smtp_from": "alertmanager@example.org", + "smtp_hello": "localhost", + "smtp_smarthost": "localhost:25", + "smtp_require_tls": true, + "pagerduty_url": "https://events.pagerduty.com/v2/enqueue", + "opsgenie_api_url": "https://api.opsgenie.com/", + "wechat_api_url": "https://qyapi.weixin.qq.com/cgi-bin/", + "victorops_api_url": "https://alert.victorops.com/integrations/generic/20131114/alert/", + "telegram_api_url": "https://api.telegram.org", + "webex_api_url": "https://webexapis.com/v1/messages" + }, + "route": { + "receiver": "team-X-mails", + "group_by": [ + "alertname", + "cluster" + ], + "routes": [ + { + "receiver": "team-X-mails", + "matchers": [ + "service=~\"^(foo1|foo2|baz)$\"" + ], + "routes": [ + { + "receiver": "team-X-pager", + "matchers": [ + "severity=\"critical\"" + ] + } + ] + }, + { + "receiver": "team-Y-mails", + "matchers": [ + "service=\"files\"" + ], + "routes": [ + { + "receiver": "team-Y-pager", + "matchers": [ + "severity=\"critical\"" + ], + "mute_time_intervals": [ + "nightly-quiet-time-deprecated" + ] + } + ] + }, + { + "receiver": "team-DB-pager", + "group_by": [ + "alertname", + "cluster", + "database" + ], + "matchers": [ + "service=\"database\"" + ], + "routes": [ + { + "receiver": "team-X-pager", + "matchers": [ + "owner=\"team-X\"" + ], + "mute_time_intervals": [ + "nightly-quiet-time" + ] + }, + { + "receiver": "team-Y-pager", + "matchers": [ + "owner=\"team-Y\"" + ] + } + ] + } + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "3h" + }, + "inhibit_rules": [ + { + "source_matchers": [ + "severity=\"critical\"" + ], + "target_matchers": [ + "severity=\"warning\"" + ], + "equal": [ + "alertname" + ] + } + ], + "mute_time_intervals": [ + { + "name": "nightly-quiet-time-deprecated", + "time_intervals": [ + { + "times": [ + { + "start_time": "18:00", + "end_time": "23:59" + }, + { + "start_time": "00:00", + "end_time": "07:00" + } + ] + } + ] + } + ], + "time_intervals": [ + { + "name": "nightly-quiet-time", + "time_intervals": [ + { + "times": [ + { + "start_time": "18:00", + "end_time": "23:59" + }, + { + "start_time": "00:00", + "end_time": "07:00" + } + ] + } + ] + } + ], + "templates": [ + "/etc/alertmanager/templates/*.tmpl" + ], + "receivers": [ + { + "name": "team-X-mails", + "email_configs": [ + { + "send_resolved": false, + "to": "team-X+alerts@example.org, team-Y+alerts@example.org", + "from": "alertmanager@example.org", + "hello": "localhost", + "smarthost": "localhost:25", + "headers": { + "From": "alertmanager@example.org", + "Subject": "{{ template \"email.default.subject\" . }}", + "To": "team-X+alerts@example.org, team-Y+alerts@example.org" + }, + "html": "{{ template \"email.default.html\" . }}", + "require_tls": true, + "tls_config": { + "insecure_skip_verify": false + } + } + ] + }, + { + "name": "team-X-pager", + "email_configs": [ + { + "send_resolved": false, + "to": "team-X+alerts-critical@example.org", + "from": "alertmanager@example.org", + "hello": "localhost", + "smarthost": "localhost:25", + "headers": { + "From": "alertmanager@example.org", + "Subject": "{{ template \"email.default.subject\" . }}", + "To": "team-X+alerts-critical@example.org" + }, + "html": "{{ template \"email.default.html\" . }}", + "require_tls": true, + "tls_config": { + "insecure_skip_verify": false + } + } + ], + "pagerduty_configs": [ + { + "send_resolved": true, + "http_config": { + "tls_config": { + "insecure_skip_verify": false + }, + "follow_redirects": true, + "enable_http2": true, + "proxy_url": null + }, + "routing_key": "", + "url": "https://events.pagerduty.com/v2/enqueue", + "client": "{{ template \"pagerduty.default.client\" . }}", + "client_url": "{{ template \"pagerduty.default.clientURL\" . }}", + "description": "{{ template \"pagerduty.default.description\" .}}", + "details": { + "firing": "{{ template \"pagerduty.default.instances\" .Alerts.Firing }}", + "num_firing": "{{ .Alerts.Firing | len }}", + "num_resolved": "{{ .Alerts.Resolved | len }}", + "resolved": "{{ template \"pagerduty.default.instances\" .Alerts.Resolved }}" + }, + "source": "{{ template \"pagerduty.default.client\" . }}" + } + ] + }, + { + "name": "team-Y-mails", + "email_configs": [ + { + "send_resolved": false, + "to": "team-Y+alerts@example.org", + "from": "alertmanager@example.org", + "hello": "localhost", + "smarthost": "localhost:25", + "headers": { + "From": "alertmanager@example.org", + "Subject": "{{ template \"email.default.subject\" . }}", + "To": "team-Y+alerts@example.org" + }, + "html": "{{ template \"email.default.html\" . }}", + "require_tls": true, + "tls_config": { + "insecure_skip_verify": false + } + } + ] + }, + { + "name": "team-Y-pager", + "pagerduty_configs": [ + { + "send_resolved": true, + "http_config": { + "tls_config": { + "insecure_skip_verify": false + }, + "follow_redirects": true, + "enable_http2": true, + "proxy_url": null + }, + "routing_key": "", + "url": "https://events.pagerduty.com/v2/enqueue", + "client": "{{ template \"pagerduty.default.client\" . }}", + "client_url": "{{ template \"pagerduty.default.clientURL\" . }}", + "description": "{{ template \"pagerduty.default.description\" .}}", + "details": { + "firing": "{{ template \"pagerduty.default.instances\" .Alerts.Firing }}", + "num_firing": "{{ .Alerts.Firing | len }}", + "num_resolved": "{{ .Alerts.Resolved | len }}", + "resolved": "{{ template \"pagerduty.default.instances\" .Alerts.Resolved }}" + }, + "source": "{{ template \"pagerduty.default.client\" . }}" + } + ] + }, + { + "name": "team-DB-pager", + "pagerduty_configs": [ + { + "send_resolved": true, + "http_config": { + "tls_config": { + "insecure_skip_verify": false + }, + "follow_redirects": true, + "enable_http2": true, + "proxy_url": null + }, + "routing_key": "", + "url": "https://events.pagerduty.com/v2/enqueue", + "client": "{{ template \"pagerduty.default.client\" . }}", + "client_url": "{{ template \"pagerduty.default.clientURL\" . }}", + "description": "{{ template \"pagerduty.default.description\" .}}", + "details": { + "firing": "{{ template \"pagerduty.default.instances\" .Alerts.Firing }}", + "num_firing": "{{ .Alerts.Firing | len }}", + "num_resolved": "{{ .Alerts.Resolved | len }}", + "resolved": "{{ template \"pagerduty.default.instances\" .Alerts.Resolved }}" + }, + "source": "{{ template \"pagerduty.default.client\" . }}" + } + ] + } + ] + }, + "uptime": "2025-10-17T16:41:50.500Z", + "versionInfo": { + "branch": "HEAD", + "buildDate": "20250115-14:22:34", + "buildUser": "root@40be7f318ba7", + "goVersion": "go1.23.4", + "revision": "4ce04fb010bd626fca35928dcfe82f6f2da52ced", + "version": "0.28.0" + } +} \ No newline at end of file diff --git a/pkg/services/ngalert/api/tooling/definitions/test-data/gettable-status.json b/pkg/services/ngalert/api/tooling/definitions/test-data/gettable-status.json new file mode 100644 index 00000000000..79c69b37080 --- /dev/null +++ b/pkg/services/ngalert/api/tooling/definitions/test-data/gettable-status.json @@ -0,0 +1,24 @@ +{ + "cluster": { + "name": "01K7SGS3KSRG8FT5RZQPFN72NB", + "peers": [ + { + "address": "172.18.0.5:9094", + "name": "01K7SGS3KSRG8FT5RZQPFN72NB" + } + ], + "status": "ready" + }, + "config": { + "original": "global:\n resolve_timeout: 5m\n http_config:\n follow_redirects: true\n enable_http2: true\n smtp_from: alertmanager@example.org\n smtp_hello: localhost\n smtp_smarthost: localhost:25\n smtp_require_tls: true\n pagerduty_url: https://events.pagerduty.com/v2/enqueue\n opsgenie_api_url: https://api.opsgenie.com/\n wechat_api_url: https://qyapi.weixin.qq.com/cgi-bin/\n victorops_api_url: https://alert.victorops.com/integrations/generic/20131114/alert/\n telegram_api_url: https://api.telegram.org\n webex_api_url: https://webexapis.com/v1/messages\n rocketchat_api_url: https://open.rocket.chat/\nroute:\n receiver: team-X-mails\n group_by:\n - alertname\n - cluster\n continue: false\n routes:\n - receiver: team-X-mails\n matchers:\n - service=~\"^(foo1|foo2|baz)$\"\n continue: false\n routes:\n - receiver: team-X-pager\n matchers:\n - severity=\"critical\"\n continue: false\n - receiver: team-Y-mails\n matchers:\n - service=\"files\"\n continue: false\n routes:\n - receiver: team-Y-pager\n matchers:\n - severity=\"critical\"\n mute_time_intervals:\n - nightly-quiet-time-deprecated\n continue: false\n - receiver: team-DB-pager\n group_by:\n - alertname\n - cluster\n - database\n matchers:\n - service=\"database\"\n continue: false\n routes:\n - receiver: team-X-pager\n matchers:\n - owner=\"team-X\"\n mute_time_intervals:\n - nightly-quiet-time\n continue: false\n - receiver: team-Y-pager\n matchers:\n - owner=\"team-Y\"\n continue: false\n group_wait: 30s\n group_interval: 5m\n repeat_interval: 3h\ninhibit_rules:\n- source_matchers:\n - severity=\"critical\"\n target_matchers:\n - severity=\"warning\"\n equal:\n - alertname\nreceivers:\n- name: team-X-mails\n email_configs:\n - send_resolved: false\n to: team-X+alerts@example.org, team-Y+alerts@example.org\n from: alertmanager@example.org\n hello: localhost\n smarthost: localhost:25\n headers:\n From: alertmanager@example.org\n Subject: '{{ template \"email.default.subject\" . }}'\n To: team-X+alerts@example.org, team-Y+alerts@example.org\n html: '{{ template \"email.default.html\" . }}'\n require_tls: true\n tls_config:\n insecure_skip_verify: false\n- name: team-X-pager\n email_configs:\n - send_resolved: false\n to: team-X+alerts-critical@example.org\n from: alertmanager@example.org\n hello: localhost\n smarthost: localhost:25\n headers:\n From: alertmanager@example.org\n Subject: '{{ template \"email.default.subject\" . }}'\n To: team-X+alerts-critical@example.org\n html: '{{ template \"email.default.html\" . }}'\n require_tls: true\n tls_config:\n insecure_skip_verify: false\n pagerduty_configs:\n - send_resolved: true\n http_config:\n follow_redirects: true\n enable_http2: true\n routing_key: \n url: https://events.pagerduty.com/v2/enqueue\n client: '{{ template \"pagerduty.default.client\" . }}'\n client_url: '{{ template \"pagerduty.default.clientURL\" . }}'\n description: '{{ template \"pagerduty.default.description\" .}}'\n details:\n firing: '{{ template \"pagerduty.default.instances\" .Alerts.Firing }}'\n num_firing: '{{ .Alerts.Firing | len }}'\n num_resolved: '{{ .Alerts.Resolved | len }}'\n resolved: '{{ template \"pagerduty.default.instances\" .Alerts.Resolved }}'\n source: '{{ template \"pagerduty.default.client\" . }}'\n- name: team-Y-mails\n email_configs:\n - send_resolved: false\n to: team-Y+alerts@example.org\n from: alertmanager@example.org\n hello: localhost\n smarthost: localhost:25\n headers:\n From: alertmanager@example.org\n Subject: '{{ template \"email.default.subject\" . }}'\n To: team-Y+alerts@example.org\n html: '{{ template \"email.default.html\" . }}'\n require_tls: true\n tls_config:\n insecure_skip_verify: false\n- name: team-Y-pager\n pagerduty_configs:\n - send_resolved: true\n http_config:\n follow_redirects: true\n enable_http2: true\n routing_key: \n url: https://events.pagerduty.com/v2/enqueue\n client: '{{ template \"pagerduty.default.client\" . }}'\n client_url: '{{ template \"pagerduty.default.clientURL\" . }}'\n description: '{{ template \"pagerduty.default.description\" .}}'\n details:\n firing: '{{ template \"pagerduty.default.instances\" .Alerts.Firing }}'\n num_firing: '{{ .Alerts.Firing | len }}'\n num_resolved: '{{ .Alerts.Resolved | len }}'\n resolved: '{{ template \"pagerduty.default.instances\" .Alerts.Resolved }}'\n source: '{{ template \"pagerduty.default.client\" . }}'\n- name: team-DB-pager\n pagerduty_configs:\n - send_resolved: true\n http_config:\n follow_redirects: true\n enable_http2: true\n routing_key: \n url: https://events.pagerduty.com/v2/enqueue\n client: '{{ template \"pagerduty.default.client\" . }}'\n client_url: '{{ template \"pagerduty.default.clientURL\" . }}'\n description: '{{ template \"pagerduty.default.description\" .}}'\n details:\n firing: '{{ template \"pagerduty.default.instances\" .Alerts.Firing }}'\n num_firing: '{{ .Alerts.Firing | len }}'\n num_resolved: '{{ .Alerts.Resolved | len }}'\n resolved: '{{ template \"pagerduty.default.instances\" .Alerts.Resolved }}'\n source: '{{ template \"pagerduty.default.client\" . }}'\ntemplates:\n- /etc/alertmanager/templates/*.tmpl\nmute_time_intervals:\n- name: nightly-quiet-time-deprecated\n time_intervals:\n - times:\n - start_time: \"18:00\"\n end_time: \"23:59\"\n - start_time: \"00:00\"\n end_time: \"07:00\"\ntime_intervals:\n- name: nightly-quiet-time\n time_intervals:\n - times:\n - start_time: \"18:00\"\n end_time: \"23:59\"\n - start_time: \"00:00\"\n end_time: \"07:00\"\n" + }, + "uptime": "2025-10-17T16:41:50.500Z", + "versionInfo": { + "branch": "HEAD", + "buildDate": "20250115-14:22:34", + "buildUser": "root@40be7f318ba7", + "goVersion": "go1.23.4", + "revision": "4ce04fb010bd626fca35928dcfe82f6f2da52ced", + "version": "0.28.0" + } +} \ No newline at end of file