From ef5e903418d4c07c021626d29e035bc9a47cbee3 Mon Sep 17 00:00:00 2001 From: Mathieu Parent Date: Thu, 22 Feb 2024 15:29:05 +0100 Subject: [PATCH 01/18] Support default SMTP TLS config Fixes: #3709 Signed-off-by: Mathieu Parent --- config/config.go | 58 +++++++++++++++++++++----------------- config/config_test.go | 12 ++++++-- config/notifiers.go | 28 +++++++++--------- docs/configuration.md | 4 ++- notify/email/email.go | 4 +-- notify/email/email_test.go | 2 +- 6 files changed, 61 insertions(+), 47 deletions(-) diff --git a/config/config.go b/config/config.go index 35cd06dd13..3588a305c4 100644 --- a/config/config.go +++ b/config/config.go @@ -365,6 +365,9 @@ func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { } } for _, ec := range rcv.EmailConfigs { + if ec.TLSConfig == nil { + ec.TLSConfig = c.Global.SMTPTLSConfig + } if ec.Smarthost.String() == "" { if c.Global.SMTPSmarthost.String() == "" { return fmt.Errorf("no global SMTP smarthost set") @@ -629,12 +632,14 @@ func checkTimeInterval(r *Route, timeIntervals map[string]struct{}) error { // DefaultGlobalConfig returns GlobalConfig with default values. func DefaultGlobalConfig() GlobalConfig { defaultHTTPConfig := commoncfg.DefaultHTTPClientConfig - return GlobalConfig{ - ResolveTimeout: model.Duration(5 * time.Minute), - HTTPConfig: &defaultHTTPConfig, + defaultSMTPTLSConfig := commoncfg.TLSConfig{} + return GlobalConfig{ + ResolveTimeout: model.Duration(5 * time.Minute), + HTTPConfig: &defaultHTTPConfig, SMTPHello: "localhost", SMTPRequireTLS: true, + SMTPTLSConfig: &defaultSMTPTLSConfig, PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"), OpsGenieAPIURL: mustParseURL("https://api.opsgenie.com/"), WeChatAPIURL: mustParseURL("https://qyapi.weixin.qq.com/cgi-bin/"), @@ -742,29 +747,30 @@ type GlobalConfig struct { HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` - SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"` - SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"` - SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"` - SMTPAuthUsername string `yaml:"smtp_auth_username,omitempty" json:"smtp_auth_username,omitempty"` - SMTPAuthPassword Secret `yaml:"smtp_auth_password,omitempty" json:"smtp_auth_password,omitempty"` - SMTPAuthPasswordFile string `yaml:"smtp_auth_password_file,omitempty" json:"smtp_auth_password_file,omitempty"` - SMTPAuthSecret Secret `yaml:"smtp_auth_secret,omitempty" json:"smtp_auth_secret,omitempty"` - SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,omitempty"` - SMTPRequireTLS bool `yaml:"smtp_require_tls" json:"smtp_require_tls,omitempty"` - SlackAPIURL *SecretURL `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"` - SlackAPIURLFile string `yaml:"slack_api_url_file,omitempty" json:"slack_api_url_file,omitempty"` - PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"` - OpsGenieAPIURL *URL `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"` - OpsGenieAPIKey Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,omitempty"` - OpsGenieAPIKeyFile string `yaml:"opsgenie_api_key_file,omitempty" json:"opsgenie_api_key_file,omitempty"` - WeChatAPIURL *URL `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"` - WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"` - WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"` - VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"` - VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"` - VictorOpsAPIKeyFile string `yaml:"victorops_api_key_file,omitempty" json:"victorops_api_key_file,omitempty"` - TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"` - WebexAPIURL *URL `yaml:"webex_api_url,omitempty" json:"webex_api_url,omitempty"` + SMTPFrom string `yaml:"smtp_from,omitempty" json:"smtp_from,omitempty"` + SMTPHello string `yaml:"smtp_hello,omitempty" json:"smtp_hello,omitempty"` + SMTPSmarthost HostPort `yaml:"smtp_smarthost,omitempty" json:"smtp_smarthost,omitempty"` + SMTPAuthUsername string `yaml:"smtp_auth_username,omitempty" json:"smtp_auth_username,omitempty"` + SMTPAuthPassword Secret `yaml:"smtp_auth_password,omitempty" json:"smtp_auth_password,omitempty"` + SMTPAuthPasswordFile string `yaml:"smtp_auth_password_file,omitempty" json:"smtp_auth_password_file,omitempty"` + SMTPAuthSecret Secret `yaml:"smtp_auth_secret,omitempty" json:"smtp_auth_secret,omitempty"` + SMTPAuthIdentity string `yaml:"smtp_auth_identity,omitempty" json:"smtp_auth_identity,omitempty"` + SMTPRequireTLS bool `yaml:"smtp_require_tls" json:"smtp_require_tls,omitempty"` + SMTPTLSConfig *commoncfg.TLSConfig `yaml:"smtp_tls_config,omitempty" json:"smtp_tls_config,omitempty"` + SlackAPIURL *SecretURL `yaml:"slack_api_url,omitempty" json:"slack_api_url,omitempty"` + SlackAPIURLFile string `yaml:"slack_api_url_file,omitempty" json:"slack_api_url_file,omitempty"` + PagerdutyURL *URL `yaml:"pagerduty_url,omitempty" json:"pagerduty_url,omitempty"` + OpsGenieAPIURL *URL `yaml:"opsgenie_api_url,omitempty" json:"opsgenie_api_url,omitempty"` + OpsGenieAPIKey Secret `yaml:"opsgenie_api_key,omitempty" json:"opsgenie_api_key,omitempty"` + OpsGenieAPIKeyFile string `yaml:"opsgenie_api_key_file,omitempty" json:"opsgenie_api_key_file,omitempty"` + WeChatAPIURL *URL `yaml:"wechat_api_url,omitempty" json:"wechat_api_url,omitempty"` + WeChatAPISecret Secret `yaml:"wechat_api_secret,omitempty" json:"wechat_api_secret,omitempty"` + WeChatAPICorpID string `yaml:"wechat_api_corp_id,omitempty" json:"wechat_api_corp_id,omitempty"` + VictorOpsAPIURL *URL `yaml:"victorops_api_url,omitempty" json:"victorops_api_url,omitempty"` + VictorOpsAPIKey Secret `yaml:"victorops_api_key,omitempty" json:"victorops_api_key,omitempty"` + VictorOpsAPIKeyFile string `yaml:"victorops_api_key_file,omitempty" json:"victorops_api_key_file,omitempty"` + TelegramAPIUrl *URL `yaml:"telegram_api_url,omitempty" json:"telegram_api_url,omitempty"` + WebexAPIURL *URL `yaml:"webex_api_url,omitempty" json:"webex_api_url,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for GlobalConfig. diff --git a/config/config_test.go b/config/config_test.go index 7631d37cb7..2551132797 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -857,9 +857,12 @@ func TestEmptyFieldsAndRegex(t *testing.T) { FollowRedirects: true, EnableHTTP2: true, }, - ResolveTimeout: model.Duration(5 * time.Minute), - SMTPSmarthost: HostPort{Host: "localhost", Port: "25"}, - SMTPFrom: "alertmanager@example.org", + ResolveTimeout: model.Duration(5 * time.Minute), + SMTPSmarthost: HostPort{Host: "localhost", Port: "25"}, + SMTPFrom: "alertmanager@example.org", + SMTPTLSConfig: &commoncfg.TLSConfig{ + InsecureSkipVerify: false, + }, SlackAPIURL: (*SecretURL)(mustParseURL("http://slack.example.com/")), SMTPRequireTLS: true, PagerdutyURL: mustParseURL("https://events.pagerduty.com/v2/enqueue"), @@ -905,6 +908,9 @@ func TestEmptyFieldsAndRegex(t *testing.T) { Smarthost: HostPort{Host: "localhost", Port: "25"}, HTML: "{{ template \"email.default.html\" . }}", RequireTLS: &boolFoo, + TLSConfig: &commoncfg.TLSConfig{ + InsecureSkipVerify: false, + }, }, }, }, diff --git a/config/notifiers.go b/config/notifiers.go index d79c8b5057..7a601bb908 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -248,20 +248,20 @@ type EmailConfig struct { NotifierConfig `yaml:",inline" json:",inline"` // Email address to notify. - To string `yaml:"to,omitempty" json:"to,omitempty"` - From string `yaml:"from,omitempty" json:"from,omitempty"` - Hello string `yaml:"hello,omitempty" json:"hello,omitempty"` - Smarthost HostPort `yaml:"smarthost,omitempty" json:"smarthost,omitempty"` - AuthUsername string `yaml:"auth_username,omitempty" json:"auth_username,omitempty"` - AuthPassword Secret `yaml:"auth_password,omitempty" json:"auth_password,omitempty"` - AuthPasswordFile string `yaml:"auth_password_file,omitempty" json:"auth_password_file,omitempty"` - AuthSecret Secret `yaml:"auth_secret,omitempty" json:"auth_secret,omitempty"` - AuthIdentity string `yaml:"auth_identity,omitempty" json:"auth_identity,omitempty"` - Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` - HTML string `yaml:"html,omitempty" json:"html,omitempty"` - Text string `yaml:"text,omitempty" json:"text,omitempty"` - RequireTLS *bool `yaml:"require_tls,omitempty" json:"require_tls,omitempty"` - TLSConfig commoncfg.TLSConfig `yaml:"tls_config,omitempty" json:"tls_config,omitempty"` + To string `yaml:"to,omitempty" json:"to,omitempty"` + From string `yaml:"from,omitempty" json:"from,omitempty"` + Hello string `yaml:"hello,omitempty" json:"hello,omitempty"` + Smarthost HostPort `yaml:"smarthost,omitempty" json:"smarthost,omitempty"` + AuthUsername string `yaml:"auth_username,omitempty" json:"auth_username,omitempty"` + AuthPassword Secret `yaml:"auth_password,omitempty" json:"auth_password,omitempty"` + AuthPasswordFile string `yaml:"auth_password_file,omitempty" json:"auth_password_file,omitempty"` + AuthSecret Secret `yaml:"auth_secret,omitempty" json:"auth_secret,omitempty"` + AuthIdentity string `yaml:"auth_identity,omitempty" json:"auth_identity,omitempty"` + Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"` + HTML string `yaml:"html,omitempty" json:"html,omitempty"` + Text string `yaml:"text,omitempty" json:"text,omitempty"` + RequireTLS *bool `yaml:"require_tls,omitempty" json:"require_tls,omitempty"` + TLSConfig *commoncfg.TLSConfig `yaml:"tls_config,omitempty" json:"tls_config,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface. diff --git a/docs/configuration.md b/docs/configuration.md index b4b421d0d0..bd94aec5e2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -83,6 +83,8 @@ global: # The default SMTP TLS requirement. # Note that Go does not support unencrypted connections to remote SMTP endpoints. [ smtp_require_tls: | default = true ] + # The default TLS configuration for SMTP receivers + [ smtp_tls_config: ] # The API URL to use for Slack notifications. [ slack_api_url: ] @@ -884,7 +886,7 @@ to: # TLS configuration. tls_config: - [ ] + [ | default = global.smtp_tls_config ] # The HTML body of the email notification. [ html: | default = '{{ template "email.default.html" . }}' ] diff --git a/notify/email/email.go b/notify/email/email.go index d7e5be7bdc..457b80ab39 100644 --- a/notify/email/email.go +++ b/notify/email/email.go @@ -131,7 +131,7 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { success = false ) if n.conf.Smarthost.Port == "465" { - tlsConfig, err := commoncfg.NewTLSConfig(&n.conf.TLSConfig) + tlsConfig, err := commoncfg.NewTLSConfig(n.conf.TLSConfig) if err != nil { return false, fmt.Errorf("parse TLS configuration: %w", err) } @@ -178,7 +178,7 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { return true, fmt.Errorf("'require_tls' is true (default) but %q does not advertise the STARTTLS extension", n.conf.Smarthost) } - tlsConf, err := commoncfg.NewTLSConfig(&n.conf.TLSConfig) + tlsConf, err := commoncfg.NewTLSConfig(n.conf.TLSConfig) if err != nil { return false, fmt.Errorf("parse TLS configuration: %w", err) } diff --git a/notify/email/email_test.go b/notify/email/email_test.go index 6303b85d72..970c117749 100644 --- a/notify/email/email_test.go +++ b/notify/email/email_test.go @@ -407,7 +407,7 @@ func TestEmailNotifyWithSTARTTLS(t *testing.T) { Text: "Text body", RequireTLS: &trueVar, // MailDev embeds a self-signed certificate which can't be retrieved. - TLSConfig: commoncfg.TLSConfig{InsecureSkipVerify: true}, + TLSConfig: &commoncfg.TLSConfig{InsecureSkipVerify: true}, }, c.Server, ) From 85d1e06d242e28e70b785d53a050de88b74eb204 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:58:59 +0000 Subject: [PATCH 02/18] Bump webpack from 5.75.0 to 5.76.0 in /ui/react-app Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.76.0) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- ui/react-app/package-lock.json | 14 +++++++------- ui/react-app/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/react-app/package-lock.json b/ui/react-app/package-lock.json index 1585dc53e5..08d1a26626 100644 --- a/ui/react-app/package-lock.json +++ b/ui/react-app/package-lock.json @@ -40,7 +40,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.1", "typescript": "^4.7.4", - "webpack": "^5.75.0", + "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.7.0", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.11.1", @@ -7944,9 +7944,9 @@ } }, "node_modules/webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.76.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", + "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -14165,9 +14165,9 @@ } }, "webpack": { - "version": "5.75.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", - "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "version": "5.76.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", + "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", diff --git a/ui/react-app/package.json b/ui/react-app/package.json index e1ff99ceab..ba563aa6d7 100644 --- a/ui/react-app/package.json +++ b/ui/react-app/package.json @@ -42,7 +42,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.1", "typescript": "^4.7.4", - "webpack": "^5.75.0", + "webpack": "^5.76.0", "webpack-bundle-analyzer": "^4.7.0", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.11.1", From 688d6490b423b24576c8b618a6157982c1e71d47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 15:38:38 +0000 Subject: [PATCH 03/18] Bump use-query-params from 2.2.0 to 2.2.1 in /ui/react-app Bumps [use-query-params](https://github.com/pbeshai/use-query-params) from 2.2.0 to 2.2.1. - [Release notes](https://github.com/pbeshai/use-query-params/releases) - [Commits](https://github.com/pbeshai/use-query-params/compare/use-query-params@2.2.0...use-query-params@2.2.1) --- updated-dependencies: - dependency-name: use-query-params dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- ui/react-app/package-lock.json | 26 ++++++++++++++++++-------- ui/react-app/package.json | 2 +- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/ui/react-app/package-lock.json b/ui/react-app/package-lock.json index 1585dc53e5..df8198a633 100644 --- a/ui/react-app/package-lock.json +++ b/ui/react-app/package-lock.json @@ -16,7 +16,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.3.0", - "use-query-params": "^2.1.1" + "use-query-params": "^2.2.1" }, "devDependencies": { "@types/react": "^18.2.51", @@ -7857,15 +7857,25 @@ } }, "node_modules/use-query-params": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/use-query-params/-/use-query-params-2.2.0.tgz", - "integrity": "sha512-MPBwXVZYzFeJEdjv0YgPNFsafUOM8WTpwBEZfNEMlyzbTsf2c+ZpOBkdM95/w4rxzk4eVO3E4DW7v33+VDbiQw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/use-query-params/-/use-query-params-2.2.1.tgz", + "integrity": "sha512-i6alcyLB8w9i3ZK3caNftdb+UnbfBRNPDnc89CNQWkGRmDrm/gfydHvMBfVsQJRq3NoHOM2dt/ceBWG2397v1Q==", "dependencies": { "serialize-query-params": "^2.0.2" }, "peerDependencies": { + "@reach/router": "^1.2.1", "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "react-dom": ">=16.8.0", + "react-router-dom": ">=5" + }, + "peerDependenciesMeta": { + "@reach/router": { + "optional": true + }, + "react-router-dom": { + "optional": true + } } }, "node_modules/use-sync-external-store": { @@ -14096,9 +14106,9 @@ } }, "use-query-params": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/use-query-params/-/use-query-params-2.2.0.tgz", - "integrity": "sha512-MPBwXVZYzFeJEdjv0YgPNFsafUOM8WTpwBEZfNEMlyzbTsf2c+ZpOBkdM95/w4rxzk4eVO3E4DW7v33+VDbiQw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/use-query-params/-/use-query-params-2.2.1.tgz", + "integrity": "sha512-i6alcyLB8w9i3ZK3caNftdb+UnbfBRNPDnc89CNQWkGRmDrm/gfydHvMBfVsQJRq3NoHOM2dt/ceBWG2397v1Q==", "requires": { "serialize-query-params": "^2.0.2" } diff --git a/ui/react-app/package.json b/ui/react-app/package.json index e1ff99ceab..0c4764c53a 100644 --- a/ui/react-app/package.json +++ b/ui/react-app/package.json @@ -18,7 +18,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.3.0", - "use-query-params": "^2.1.1" + "use-query-params": "^2.2.1" }, "devDependencies": { "@types/react": "^18.2.51", From 6e27339d679aff64b462b33c8b02e37c9be3988b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:09:34 +0000 Subject: [PATCH 04/18] Bump github.com/cespare/xxhash/v2 from 2.2.0 to 2.3.0 Bumps [github.com/cespare/xxhash/v2](https://github.com/cespare/xxhash) from 2.2.0 to 2.3.0. - [Commits](https://github.com/cespare/xxhash/compare/v2.2.0...v2.3.0) --- updated-dependencies: - dependency-name: github.com/cespare/xxhash/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6f17483518..e4a698cd87 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/aws/aws-sdk-go v1.53.14 github.com/benbjohnson/clock v1.3.5 github.com/cenkalti/backoff/v4 v4.3.0 - github.com/cespare/xxhash/v2 v2.2.0 + github.com/cespare/xxhash/v2 v2.3.0 github.com/go-kit/log v0.2.1 github.com/go-openapi/analysis v0.23.0 github.com/go-openapi/errors v0.22.0 diff --git a/go.sum b/go.sum index 281fdc8bba..44dcd78b08 100644 --- a/go.sum +++ b/go.sum @@ -91,8 +91,8 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= From db32fab6128f00da2905d6cda076147b0e241831 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Thu, 20 Jun 2024 12:02:05 +0100 Subject: [PATCH 05/18] Replace incorrect use of fmt.Errorf (#3883) --- silence/silence.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/silence/silence.go b/silence/silence.go index 6b67b2ca38..c837e6fa99 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -44,10 +44,10 @@ import ( ) // ErrNotFound is returned if a silence was not found. -var ErrNotFound = fmt.Errorf("silence not found") +var ErrNotFound = errors.New("silence not found") // ErrInvalidState is returned if the state isn't valid. -var ErrInvalidState = fmt.Errorf("invalid state") +var ErrInvalidState = errors.New("invalid state") type matcherCache map[*pb.Silence]labels.Matchers @@ -338,7 +338,7 @@ type Options struct { func (o *Options) validate() error { if o.SnapshotFile != "" && o.SnapshotReader != nil { - return fmt.Errorf("only one of SnapshotFile and SnapshotReader must be set") + return errors.New("only one of SnapshotFile and SnapshotReader must be set") } return nil } From 124da3462d28d5a87605cc55222017cf5b90b472 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Thu, 20 Jun 2024 14:50:53 +0100 Subject: [PATCH 06/18] Silence limits as functions (#3885) * Silence limits as functions This commit changes silence limits from a struct of ints to a struct of functions that return individual limits. This allows limits to be lazy-loaded and updated without having to call silences.New(). Signed-off-by: George Robinson * Add explicit test for no limits Signed-off-by: George Robinson * Fix run() Signed-off-by: George Robinson --------- Signed-off-by: George Robinson --- cmd/alertmanager/main.go | 4 ++-- silence/silence.go | 17 ++++++++++------- silence/silence_test.go | 22 ++++++++++++++++++++-- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index 7566fba8d5..f353adb364 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -262,8 +262,8 @@ func run() int { SnapshotFile: filepath.Join(*dataDir, "silences"), Retention: *retention, Limits: silence.Limits{ - MaxSilences: *maxSilences, - MaxPerSilenceBytes: *maxPerSilenceBytes, + MaxSilences: func() int { return *maxSilences }, + MaxPerSilenceBytes: func() int { return *maxPerSilenceBytes }, }, Logger: log.With(logger, "component", "silences"), Metrics: prometheus.DefaultRegisterer, diff --git a/silence/silence.go b/silence/silence.go index c837e6fa99..31ec705d51 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -206,10 +206,10 @@ type Silences struct { type Limits struct { // MaxSilences limits the maximum number of silences, including expired // silences. - MaxSilences int + MaxSilences func() int // MaxPerSilenceBytes is the maximum size of an individual silence as // stored on disk. - MaxPerSilenceBytes int + MaxPerSilenceBytes func() int } // MaintenanceFunc represents the function to run as part of the periodic maintenance for silences. @@ -585,8 +585,11 @@ func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) // Check the limit unless the silence has been expired. This is to avoid // situations where silences cannot be expired after the limit has been // reduced. - if n := msil.Size(); s.limits.MaxPerSilenceBytes > 0 && n > s.limits.MaxPerSilenceBytes && sil.EndsAt.After(now) { - return fmt.Errorf("silence exceeded maximum size: %d bytes (limit: %d bytes)", n, s.limits.MaxPerSilenceBytes) + if s.limits.MaxPerSilenceBytes != nil { + n := msil.Size() + if m := s.limits.MaxPerSilenceBytes(); m > 0 && n > m && sil.EndsAt.After(now) { + return fmt.Errorf("silence exceeded maximum size: %d bytes (limit: %d bytes)", n, m) + } } if s.st.merge(msil, now) { @@ -622,9 +625,9 @@ func (s *Silences) Set(sil *pb.Silence) (string, error) { } // If we got here it's either a new silence or a replacing one. - if s.limits.MaxSilences > 0 { - if len(s.st)+1 > s.limits.MaxSilences { - return "", fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(s.st), s.limits.MaxSilences) + if s.limits.MaxSilences != nil { + if m := s.limits.MaxSilences(); m > 0 && len(s.st)+1 > m { + return "", fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(s.st), m) } } diff --git a/silence/silence_test.go b/silence/silence_test.go index a956d756b3..11b5fb13ec 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -462,8 +462,8 @@ func TestSilenceSet(t *testing.T) { func TestSilenceLimits(t *testing.T) { s, err := New(Options{ Limits: Limits{ - MaxSilences: 1, - MaxPerSilenceBytes: 2 << 11, // 4KB + MaxSilences: func() int { return 1 }, + MaxPerSilenceBytes: func() int { return 2 << 11 }, // 4KB }, }) require.NoError(t, err) @@ -535,6 +535,24 @@ func TestSilenceLimits(t *testing.T) { require.Equal(t, "", id3) } +func TestSilenceNoLimits(t *testing.T) { + s, err := New(Options{ + Limits: Limits{}, + }) + require.NoError(t, err) + + // Insert sil should succeed without error. + sil := &pb.Silence{ + Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, + StartsAt: time.Now(), + EndsAt: time.Now().Add(5 * time.Minute), + Comment: strings.Repeat("c", 2<<9), + } + id, err := s.Set(sil) + require.NoError(t, err) + require.NotEqual(t, "", id) +} + func TestSetActiveSilence(t *testing.T) { s, err := New(Options{ Retention: time.Hour, From e690fbe250b96c8c963f9145f1812dc22e0128ae Mon Sep 17 00:00:00 2001 From: George Robinson Date: Thu, 20 Jun 2024 15:20:52 +0100 Subject: [PATCH 07/18] Rename silence limit to max-silence-size-bytes (#3886) * Rename silence limit to max-silence-size-bytes This commit renames an existing (unreleased) limit from max-per-silence-bytes to max-silence-size-bytes. Signed-off-by: George Robinson * Update help Signed-off-by: George Robinson --------- Signed-off-by: George Robinson --- cmd/alertmanager/main.go | 6 +++--- silence/silence.go | 8 ++++---- silence/silence_test.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index f353adb364..49b515c135 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -147,7 +147,7 @@ func run() int { retention = kingpin.Flag("data.retention", "How long to keep data for.").Default("120h").Duration() maintenanceInterval = kingpin.Flag("data.maintenance-interval", "Interval between garbage collection and snapshotting to disk of the silences and the notification logs.").Default("15m").Duration() maxSilences = kingpin.Flag("silences.max-silences", "Maximum number of silences, including expired silences. If negative or zero, no limit is set.").Default("0").Int() - maxPerSilenceBytes = kingpin.Flag("silences.max-per-silence-bytes", "Maximum per silence size in bytes. If negative or zero, no limit is set.").Default("0").Int() + maxSilenceSizeBytes = kingpin.Flag("silences.max-silence-size-bytes", "Maximum silence size in bytes. If negative or zero, no limit is set.").Default("0").Int() alertGCInterval = kingpin.Flag("alerts.gc-interval", "Interval between alert GC.").Default("30m").Duration() webConfig = webflag.AddFlags(kingpin.CommandLine, ":9093") @@ -262,8 +262,8 @@ func run() int { SnapshotFile: filepath.Join(*dataDir, "silences"), Retention: *retention, Limits: silence.Limits{ - MaxSilences: func() int { return *maxSilences }, - MaxPerSilenceBytes: func() int { return *maxPerSilenceBytes }, + MaxSilences: func() int { return *maxSilences }, + MaxSilenceSizeBytes: func() int { return *maxSilenceSizeBytes }, }, Logger: log.With(logger, "component", "silences"), Metrics: prometheus.DefaultRegisterer, diff --git a/silence/silence.go b/silence/silence.go index 31ec705d51..91c1142969 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -207,9 +207,9 @@ type Limits struct { // MaxSilences limits the maximum number of silences, including expired // silences. MaxSilences func() int - // MaxPerSilenceBytes is the maximum size of an individual silence as + // MaxSilenceSizeBytes is the maximum size of an individual silence as // stored on disk. - MaxPerSilenceBytes func() int + MaxSilenceSizeBytes func() int } // MaintenanceFunc represents the function to run as part of the periodic maintenance for silences. @@ -585,9 +585,9 @@ func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) // Check the limit unless the silence has been expired. This is to avoid // situations where silences cannot be expired after the limit has been // reduced. - if s.limits.MaxPerSilenceBytes != nil { + if s.limits.MaxSilenceSizeBytes != nil { n := msil.Size() - if m := s.limits.MaxPerSilenceBytes(); m > 0 && n > m && sil.EndsAt.After(now) { + if m := s.limits.MaxSilenceSizeBytes(); m > 0 && n > m && sil.EndsAt.After(now) { return fmt.Errorf("silence exceeded maximum size: %d bytes (limit: %d bytes)", n, m) } } diff --git a/silence/silence_test.go b/silence/silence_test.go index 11b5fb13ec..4cc67f8c9b 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -462,8 +462,8 @@ func TestSilenceSet(t *testing.T) { func TestSilenceLimits(t *testing.T) { s, err := New(Options{ Limits: Limits{ - MaxSilences: func() int { return 1 }, - MaxPerSilenceBytes: func() int { return 2 << 11 }, // 4KB + MaxSilences: func() int { return 1 }, + MaxSilenceSizeBytes: func() int { return 2 << 11 }, // 4KB }, }) require.NoError(t, err) From cc6de9c666a179beda15460d692e4c55c7c8ac32 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Mon, 17 Jun 2024 11:45:31 +0100 Subject: [PATCH 08/18] Remove Id return from silences.Set(*pb.Silence) This commit removes the Id from the method silences.Set(*pb.Silence) as it is redundant. The Id is still set even when creating a silence fails. This will be fixed in a later change. Signed-off-by: George Robinson --- api/v2/api.go | 5 +- api/v2/api_test.go | 24 +++---- notify/notify_test.go | 8 +-- silence/silence.go | 18 ++--- silence/silence_bench_test.go | 8 +-- silence/silence_test.go | 129 ++++++++++++++++------------------ 6 files changed, 86 insertions(+), 106 deletions(-) diff --git a/api/v2/api.go b/api/v2/api.go index 6f034d2596..b279b619d5 100644 --- a/api/v2/api.go +++ b/api/v2/api.go @@ -660,8 +660,7 @@ func (api *API) postSilencesHandler(params silence_ops.PostSilencesParams) middl return silence_ops.NewPostSilencesBadRequest().WithPayload(msg) } - sid, err := api.silences.Set(sil) - if err != nil { + if err = api.silences.Set(sil); err != nil { level.Error(logger).Log("msg", "Failed to create silence", "err", err) if errors.Is(err, silence.ErrNotFound) { return silence_ops.NewPostSilencesNotFound().WithPayload(err.Error()) @@ -670,7 +669,7 @@ func (api *API) postSilencesHandler(params silence_ops.PostSilencesParams) middl } return silence_ops.NewPostSilencesOK().WithPayload(&silence_ops.PostSilencesOKBody{ - SilenceID: sid, + SilenceID: sil.Id, }) } diff --git a/api/v2/api_test.go b/api/v2/api_test.go index d520dbde13..5cabf2e0c7 100644 --- a/api/v2/api_test.go +++ b/api/v2/api_test.go @@ -159,8 +159,7 @@ func TestDeleteSilenceHandler(t *testing.T) { EndsAt: now.Add(time.Hour), UpdatedAt: now, } - unexpiredSid, err := silences.Set(unexpiredSil) - require.NoError(t, err) + require.NoError(t, silences.Set(unexpiredSil)) expiredSil := &silencepb.Silence{ Matchers: []*silencepb.Matcher{m}, @@ -168,9 +167,8 @@ func TestDeleteSilenceHandler(t *testing.T) { EndsAt: now.Add(time.Hour), UpdatedAt: now, } - expiredSid, err := silences.Set(expiredSil) - require.NoError(t, err) - require.NoError(t, silences.Expire(expiredSid)) + require.NoError(t, silences.Set(expiredSil)) + require.NoError(t, silences.Expire(expiredSil.Id)) for i, tc := range []struct { sid string @@ -181,11 +179,11 @@ func TestDeleteSilenceHandler(t *testing.T) { 404, }, { - unexpiredSid, + unexpiredSil.Id, 200, }, { - expiredSid, + expiredSil.Id, 200, }, } { @@ -223,8 +221,7 @@ func TestPostSilencesHandler(t *testing.T) { EndsAt: now.Add(time.Hour), UpdatedAt: now, } - unexpiredSid, err := silences.Set(unexpiredSil) - require.NoError(t, err) + require.NoError(t, silences.Set(unexpiredSil)) expiredSil := &silencepb.Silence{ Matchers: []*silencepb.Matcher{m}, @@ -232,9 +229,8 @@ func TestPostSilencesHandler(t *testing.T) { EndsAt: now.Add(time.Hour), UpdatedAt: now, } - expiredSid, err := silences.Set(expiredSil) - require.NoError(t, err) - require.NoError(t, silences.Expire(expiredSid)) + require.NoError(t, silences.Set(expiredSil)) + require.NoError(t, silences.Expire(expiredSil.Id)) t.Run("Silences CRUD", func(t *testing.T) { for i, tc := range []struct { @@ -259,14 +255,14 @@ func TestPostSilencesHandler(t *testing.T) { }, { "with an active silence ID - it extends the silence", - unexpiredSid, + unexpiredSil.Id, now.Add(time.Hour), now.Add(time.Hour * 2), 200, }, { "with an expired silence ID - it re-creates the silence", - expiredSid, + expiredSil.Id, now.Add(time.Hour), now.Add(time.Hour * 2), 200, diff --git a/notify/notify_test.go b/notify/notify_test.go index 5c2297e993..eb14c58b76 100644 --- a/notify/notify_test.go +++ b/notify/notify_test.go @@ -718,11 +718,11 @@ func TestMuteStageWithSilences(t *testing.T) { if err != nil { t.Fatal(err) } - silID, err := silences.Set(&silencepb.Silence{ + sil := &silencepb.Silence{ EndsAt: utcNow().Add(time.Hour), Matchers: []*silencepb.Matcher{{Name: "mute", Pattern: "me"}}, - }) - if err != nil { + } + if err = silences.Set(sil); err != nil { t.Fatal(err) } @@ -799,7 +799,7 @@ func TestMuteStageWithSilences(t *testing.T) { } // Expire the silence and verify that no alerts are silenced now. - if err := silences.Expire(silID); err != nil { + if err := silences.Expire(sil.Id); err != nil { t.Fatal(err) } diff --git a/silence/silence.go b/silence/silence.go index 91c1142969..ecd3f840f3 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -602,24 +602,24 @@ func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) // Set the specified silence. If a silence with the ID already exists and the modification // modifies history, the old silence gets expired and a new one is created. -func (s *Silences) Set(sil *pb.Silence) (string, error) { +func (s *Silences) Set(sil *pb.Silence) error { s.mtx.Lock() defer s.mtx.Unlock() now := s.nowUTC() prev, ok := s.getSilence(sil.Id) if sil.Id != "" && !ok { - return "", ErrNotFound + return ErrNotFound } if ok { if canUpdate(prev, sil, now) { - return sil.Id, s.setSilence(sil, now, false) + return s.setSilence(sil, now, false) } if getState(prev, s.nowUTC()) != types.SilenceStateExpired { // We cannot update the silence, expire the old one. if err := s.expire(prev.Id); err != nil { - return "", fmt.Errorf("expire previous silence: %w", err) + return fmt.Errorf("expire previous silence: %w", err) } } } @@ -627,13 +627,13 @@ func (s *Silences) Set(sil *pb.Silence) (string, error) { // If we got here it's either a new silence or a replacing one. if s.limits.MaxSilences != nil { if m := s.limits.MaxSilences(); m > 0 && len(s.st)+1 > m { - return "", fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(s.st), m) + return fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(s.st), m) } } uid, err := uuid.NewV4() if err != nil { - return "", fmt.Errorf("generate uuid: %w", err) + return fmt.Errorf("generate uuid: %w", err) } sil.Id = uid.String() @@ -641,11 +641,7 @@ func (s *Silences) Set(sil *pb.Silence) (string, error) { sil.StartsAt = now } - if err = s.setSilence(sil, now, false); err != nil { - return "", err - } - - return sil.Id, nil + return s.setSilence(sil, now, false) } // canUpdate returns true if silence a can be updated to b without diff --git a/silence/silence_bench_test.go b/silence/silence_bench_test.go index 146316e3f3..e1d39a9418 100644 --- a/silence/silence_bench_test.go +++ b/silence/silence_bench_test.go @@ -58,8 +58,7 @@ func benchmarkMutes(b *testing.B, n int) { var silenceIDs []string for i := 0; i < n; i++ { - var silenceID string - silenceID, err = silences.Set(&silencepb.Silence{ + s := &silencepb.Silence{ Matchers: []*silencepb.Matcher{{ Type: silencepb.Matcher_EQUAL, Name: "foo", @@ -67,9 +66,10 @@ func benchmarkMutes(b *testing.B, n int) { }}, StartsAt: now, EndsAt: now.Add(time.Minute), - }) + } + require.NoError(b, silences.Set(s)) require.NoError(b, err) - silenceIDs = append(silenceIDs, silenceID) + silenceIDs = append(silenceIDs, s.Id) } require.Len(b, silenceIDs, n) diff --git a/silence/silence_test.go b/silence/silence_test.go index 4cc67f8c9b..ca425afe4b 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -321,14 +321,13 @@ func TestSilenceSet(t *testing.T) { StartsAt: start1.Add(2 * time.Minute), EndsAt: start1.Add(5 * time.Minute), } - id1, err := s.Set(sil1) - require.NoError(t, err) - require.NotEqual(t, "", id1) + require.NoError(t, s.Set(sil1)) + require.NotEqual(t, "", sil1.Id) want := state{ - id1: &pb.MeshSilence{ + sil1.Id: &pb.MeshSilence{ Silence: &pb.Silence{ - Id: id1, + Id: sil1.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, StartsAt: start1.Add(2 * time.Minute), EndsAt: start1.Add(5 * time.Minute), @@ -347,15 +346,14 @@ func TestSilenceSet(t *testing.T) { Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, EndsAt: start2.Add(1 * time.Minute), } - id2, err := s.Set(sil2) - require.NoError(t, err) - require.NotEqual(t, "", id2) + require.NoError(t, s.Set(sil2)) + require.NotEqual(t, "", sil2.Id) want = state{ - id1: want[id1], - id2: &pb.MeshSilence{ + sil1.Id: want[sil1.Id], + sil2.Id: &pb.MeshSilence{ Silence: &pb.Silence{ - Id: id2, + Id: sil2.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, StartsAt: start2, EndsAt: start2.Add(1 * time.Minute), @@ -373,15 +371,14 @@ func TestSilenceSet(t *testing.T) { sil3 := cloneSilence(sil2) sil3.EndsAt = start3.Add(100 * time.Minute) - id3, err := s.Set(sil3) - require.NoError(t, err) - require.Equal(t, id2, id3) + require.NoError(t, s.Set(sil3)) + require.Equal(t, sil2.Id, sil3.Id) want = state{ - id1: want[id1], - id2: &pb.MeshSilence{ + sil1.Id: want[sil1.Id], + sil2.Id: &pb.MeshSilence{ Silence: &pb.Silence{ - Id: id2, + Id: sil2.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, StartsAt: start2, EndsAt: start3.Add(100 * time.Minute), @@ -399,16 +396,15 @@ func TestSilenceSet(t *testing.T) { sil4 := cloneSilence(sil3) sil4.Matchers = []*pb.Matcher{{Name: "a", Pattern: "c"}} - id4, err := s.Set(sil4) - require.NoError(t, err) + require.NoError(t, s.Set(sil4)) // This new silence gets a new id. - require.NotEqual(t, id2, id4) + require.NotEqual(t, sil2.Id, sil4.Id) want = state{ - id1: want[id1], - id2: &pb.MeshSilence{ + sil1.Id: want[sil1.Id], + sil2.Id: &pb.MeshSilence{ Silence: &pb.Silence{ - Id: id2, + Id: sil2.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, StartsAt: start2, EndsAt: start4, // Expired @@ -416,9 +412,9 @@ func TestSilenceSet(t *testing.T) { }, ExpiresAt: start4.Add(s.retention), }, - id4: &pb.MeshSilence{ + sil4.Id: &pb.MeshSilence{ Silence: &pb.Silence{ - Id: id4, + Id: sil4.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "c"}}, StartsAt: start4, EndsAt: start3.Add(100 * time.Minute), @@ -437,17 +433,16 @@ func TestSilenceSet(t *testing.T) { sil5.StartsAt = start1 sil5.EndsAt = start1.Add(5 * time.Minute) - id5, err := s.Set(sil5) - require.NoError(t, err) - require.NotEqual(t, id2, id4) + require.NoError(t, s.Set(sil5)) + require.NotEqual(t, sil2.Id, sil5.Id) want = state{ - id1: want[id1], - id2: want[id2], - id4: want[id4], - id5: &pb.MeshSilence{ + sil1.Id: want[sil1.Id], + sil2.Id: want[sil2.Id], + sil4.Id: want[sil4.Id], + sil5.Id: &pb.MeshSilence{ Silence: &pb.Silence{ - Id: id5, + Id: sil5.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, StartsAt: start5, // New silences have their start time set to "now" when created. EndsAt: start1.Add(5 * time.Minute), @@ -474,9 +469,8 @@ func TestSilenceLimits(t *testing.T) { StartsAt: time.Now(), EndsAt: time.Now().Add(5 * time.Minute), } - id1, err := s.Set(sil1) - require.NoError(t, err) - require.NotEqual(t, "", id1) + require.NoError(t, s.Set(sil1)) + require.NotEqual(t, "", sil1.Id) // Insert sil2 should fail because maximum number of silences // has been exceeded. @@ -485,27 +479,24 @@ func TestSilenceLimits(t *testing.T) { StartsAt: time.Now(), EndsAt: time.Now().Add(5 * time.Minute), } - id2, err := s.Set(sil2) - require.EqualError(t, err, "exceeded maximum number of silences: 1 (limit: 1)") - require.Equal(t, "", id2) + require.EqualError(t, s.Set(sil2), "exceeded maximum number of silences: 1 (limit: 1)") + require.Equal(t, "", sil2.Id) // Expire sil1 and run the GC. This should allow sil2 to be // inserted. - require.NoError(t, s.Expire(id1)) + require.NoError(t, s.Expire(sil1.Id)) n, err := s.GC() require.NoError(t, err) require.Equal(t, 1, n) - id2, err = s.Set(sil2) - require.NoError(t, err) - require.NotEqual(t, "", id2) + require.NoError(t, s.Set(sil2)) + require.NotEqual(t, "", sil2.Id) // Should be able to update sil2 without hitting the limit. - _, err = s.Set(sil2) - require.NoError(t, err) + require.NoError(t, s.Set(sil2)) // Expire sil2. - require.NoError(t, s.Expire(id2)) + require.NoError(t, s.Expire(sil2.Id)) n, err = s.GC() require.NoError(t, err) require.Equal(t, 1, n) @@ -527,12 +518,13 @@ func TestSilenceLimits(t *testing.T) { StartsAt: time.Now(), EndsAt: time.Now().Add(5 * time.Minute), } - id3, err := s.Set(sil3) + err = s.Set(sil3) require.Error(t, err) // Do not check the exact size as it can change between consecutive runs // due to padding. require.Contains(t, err.Error(), "silence exceeded maximum size") - require.Equal(t, "", id3) + // TODO: Once we fix https://github.com/prometheus/alertmanager/issues/3878 this should be require.Equal. + require.NotEqual(t, "", sil3.Id) } func TestSilenceNoLimits(t *testing.T) { @@ -548,9 +540,8 @@ func TestSilenceNoLimits(t *testing.T) { EndsAt: time.Now().Add(5 * time.Minute), Comment: strings.Repeat("c", 2<<9), } - id, err := s.Set(sil) - require.NoError(t, err) - require.NotEqual(t, "", id) + require.NoError(t, s.Set(sil)) + require.NotEqual(t, "", sil.Id) } func TestSetActiveSilence(t *testing.T) { @@ -571,7 +562,7 @@ func TestSetActiveSilence(t *testing.T) { StartsAt: startsAt, EndsAt: endsAt, } - id1, _ := s.Set(sil1) + require.NoError(t, s.Set(sil1)) // Update silence with 2 extra nanoseconds so the "seconds" part should not change @@ -579,20 +570,19 @@ func TestSetActiveSilence(t *testing.T) { newEndsAt := endsAt.Add(2 * time.Minute) sil2 := cloneSilence(sil1) - sil2.Id = id1 + sil2.Id = sil1.Id sil2.StartsAt = newStartsAt sil2.EndsAt = newEndsAt clock.Add(time.Minute) now = s.nowUTC() - id2, err := s.Set(sil2) - require.NoError(t, err) - require.Equal(t, id1, id2) + require.NoError(t, s.Set(sil2)) + require.Equal(t, sil1.Id, sil2.Id) want := state{ - id2: &pb.MeshSilence{ + sil2.Id: &pb.MeshSilence{ Silence: &pb.Silence{ - Id: id1, + Id: sil1.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, StartsAt: newStartsAt, EndsAt: newEndsAt, @@ -624,8 +614,7 @@ func TestSilencesSetFail(t *testing.T) { }, } for _, c := range cases { - _, err := s.Set(c.s) - checkErr(t, c.err, err) + checkErr(t, c.err, s.Set(c.s)) } } @@ -1190,22 +1179,22 @@ func TestSilencer(t *testing.T) { require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced without any silences") - _, err = ss.Set(&pb.Silence{ + sil1 := &pb.Silence{ Matchers: []*pb.Matcher{{Name: "foo", Pattern: "baz"}}, StartsAt: now.Add(-time.Hour), EndsAt: now.Add(5 * time.Minute), - }) - require.NoError(t, err) + } + require.NoError(t, ss.Set(sil1)) require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by non-matching silence") - id, err := ss.Set(&pb.Silence{ + sil2 := &pb.Silence{ Matchers: []*pb.Matcher{{Name: "foo", Pattern: "bar"}}, StartsAt: now.Add(-time.Hour), EndsAt: now.Add(5 * time.Minute), - }) - require.NoError(t, err) - require.NotEmpty(t, id) + } + require.NoError(t, ss.Set(sil2)) + require.NotEmpty(t, sil2.Id) require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by matching silence") @@ -1216,8 +1205,8 @@ func TestSilencer(t *testing.T) { require.False(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert not silenced by expired silence") // Update silence to start in the future. - _, err = ss.Set(&pb.Silence{ - Id: id, + err = ss.Set(&pb.Silence{ + Id: sil2.Id, Matchers: []*pb.Matcher{{Name: "foo", Pattern: "bar"}}, StartsAt: now.Add(time.Hour), EndsAt: now.Add(3 * time.Hour), @@ -1233,7 +1222,7 @@ func TestSilencer(t *testing.T) { // Exposes issue #2426. require.True(t, s.Mutes(model.LabelSet{"foo": "bar"}), "expected alert silenced by activated silence") - _, err = ss.Set(&pb.Silence{ + err = ss.Set(&pb.Silence{ Matchers: []*pb.Matcher{{Name: "foo", Pattern: "b..", Type: pb.Matcher_REGEXP}}, StartsAt: now.Add(time.Hour), EndsAt: now.Add(3 * time.Hour), From 24be2dc3e387912b99000a752e6a1c4ff0c94c8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:55:14 +0000 Subject: [PATCH 09/18] Bump fork-ts-checker-webpack-plugin from 7.3.0 to 9.0.2 in /ui/react-app Bumps [fork-ts-checker-webpack-plugin](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin) from 7.3.0 to 9.0.2. - [Release notes](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/releases) - [Changelog](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/blob/main/CHANGELOG.md) - [Commits](https://github.com/TypeStrong/fork-ts-checker-webpack-plugin/compare/v7.3.0...v9.0.2) --- updated-dependencies: - dependency-name: fork-ts-checker-webpack-plugin dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- ui/react-app/package-lock.json | 54 +++++++++++++++++++++++++++------- ui/react-app/package.json | 2 +- 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/ui/react-app/package-lock.json b/ui/react-app/package-lock.json index 1585dc53e5..0b93e861c5 100644 --- a/ui/react-app/package-lock.json +++ b/ui/react-app/package-lock.json @@ -34,7 +34,7 @@ "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-webpack-plugin": "^4.0.1", - "fork-ts-checker-webpack-plugin": "^7.3.0", + "fork-ts-checker-webpack-plugin": "^9.0.2", "html-webpack-plugin": "^5.5.0", "style-loader": "^3.3.4", "ts-loader": "^9.5.1", @@ -4155,15 +4155,15 @@ } }, "node_modules/fork-ts-checker-webpack-plugin": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.3.0.tgz", - "integrity": "sha512-IN+XTzusCjR5VgntYFgxbxVx3WraPRnKehBFrf00cMSrtUuW9MsG9dhL6MWpY6MkjC3wVwoujfCDgZZCQwbswA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", + "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==", "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", "chalk": "^4.1.2", "chokidar": "^3.5.3", - "cosmiconfig": "^7.0.1", + "cosmiconfig": "^8.2.0", "deepmerge": "^4.2.2", "fs-extra": "^10.0.0", "memfs": "^3.4.1", @@ -4179,11 +4179,31 @@ }, "peerDependencies": { "typescript": ">3.6.0", - "vue-template-compiler": "*", "webpack": "^5.11.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" }, "peerDependenciesMeta": { - "vue-template-compiler": { + "typescript": { "optional": true } } @@ -11412,15 +11432,15 @@ } }, "fork-ts-checker-webpack-plugin": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.3.0.tgz", - "integrity": "sha512-IN+XTzusCjR5VgntYFgxbxVx3WraPRnKehBFrf00cMSrtUuW9MsG9dhL6MWpY6MkjC3wVwoujfCDgZZCQwbswA==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", + "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==", "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "chalk": "^4.1.2", "chokidar": "^3.5.3", - "cosmiconfig": "^7.0.1", + "cosmiconfig": "^8.2.0", "deepmerge": "^4.2.2", "fs-extra": "^10.0.0", "memfs": "^3.4.1", @@ -11431,6 +11451,18 @@ "tapable": "^2.2.1" }, "dependencies": { + "cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "requires": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + } + }, "schema-utils": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", diff --git a/ui/react-app/package.json b/ui/react-app/package.json index e1ff99ceab..89e3c8321f 100644 --- a/ui/react-app/package.json +++ b/ui/react-app/package.json @@ -36,7 +36,7 @@ "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-webpack-plugin": "^4.0.1", - "fork-ts-checker-webpack-plugin": "^7.3.0", + "fork-ts-checker-webpack-plugin": "^9.0.2", "html-webpack-plugin": "^5.5.0", "style-loader": "^3.3.4", "ts-loader": "^9.5.1", From 04174d7d7b24f26170aee498f005901e6d0956df Mon Sep 17 00:00:00 2001 From: PrometheusBot Date: Fri, 21 Jun 2024 16:14:46 +0200 Subject: [PATCH 10/18] Synchronize common files from prometheus/prometheus (#3848) * Update common Prometheus files Signed-off-by: prombot * chore: fix test Signed-off-by: Simon Pasquier --------- Signed-off-by: prombot Signed-off-by: Simon Pasquier Co-authored-by: Simon Pasquier --- .github/workflows/golangci-lint.yml | 9 +++++---- Makefile.common | 2 +- cluster/tls_transport_test.go | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index b36f71c3c1..5ceb590991 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -24,8 +24,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - name: install Go + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - name: Install Go uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: 1.22.x @@ -33,6 +33,7 @@ jobs: run: sudo apt-get update && sudo apt-get -y install libsnmp-dev if: github.repository == 'prometheus/snmp_exporter' - name: Lint - uses: golangci/golangci-lint-action@9d1e0624a798bb64f6c3cea93db47765312263dc # v5.1.0 + uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 with: - version: v1.56.2 + args: --verbose + version: v1.59.0 diff --git a/Makefile.common b/Makefile.common index 0e9ace29b4..1617292350 100644 --- a/Makefile.common +++ b/Makefile.common @@ -61,7 +61,7 @@ PROMU_URL := https://github.com/prometheus/promu/releases/download/v$(PROMU_ SKIP_GOLANGCI_LINT := GOLANGCI_LINT := GOLANGCI_LINT_OPTS ?= -GOLANGCI_LINT_VERSION ?= v1.56.2 +GOLANGCI_LINT_VERSION ?= v1.59.0 # golangci-lint only supports linux, darwin and windows platforms on i386/amd64/arm64. # windows isn't included here because of the path separator being different. ifeq ($(GOHOSTOS),$(filter $(GOHOSTOS),linux darwin)) diff --git a/cluster/tls_transport_test.go b/cluster/tls_transport_test.go index ac8d1c95e2..5b9e3d5149 100644 --- a/cluster/tls_transport_test.go +++ b/cluster/tls_transport_test.go @@ -217,7 +217,7 @@ func TestDialTimeout(t *testing.T) { sent := []byte(("test stream")) m, err := from.Write(sent) require.NoError(t, err) - require.Greater(t, m, 0) + require.Positive(t, m) wg.Wait() From 52eb1fc4aa84d24fab9cb61cc4af57aeb773b6f9 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Fri, 21 Jun 2024 15:17:27 +0100 Subject: [PATCH 11/18] Rename matchers package to matcher singular (#3777) * Rename matchers package to matcher singular I realized that we had named the package plural "matchers" when its idiomatic in Go to use singular package names. --------- Signed-off-by: George Robinson --- api/v2/api.go | 2 +- cli/alert_add.go | 2 +- cli/alert_query.go | 2 +- cli/root.go | 2 +- cli/silence_add.go | 2 +- cli/silence_query.go | 2 +- cli/test_routing.go | 2 +- cmd/alertmanager/main.go | 2 +- config/config.go | 2 +- {matchers => matcher}/compat/parse.go | 16 ++++++++-------- {matchers => matcher}/compat/parse_test.go | 0 .../compliance/compliance_test.go | 2 +- {matchers => matcher}/parse/bench_test.go | 0 {matchers => matcher}/parse/fuzz_test.go | 0 {matchers => matcher}/parse/lexer.go | 0 {matchers => matcher}/parse/lexer_test.go | 0 {matchers => matcher}/parse/parse.go | 0 {matchers => matcher}/parse/parse_test.go | 0 {matchers => matcher}/parse/token.go | 0 pkg/labels/matcher.go | 2 +- silence/silence.go | 2 +- silence/silence_test.go | 2 +- types/types.go | 2 +- types/types_test.go | 2 +- 24 files changed, 23 insertions(+), 23 deletions(-) rename {matchers => matcher}/compat/parse.go (95%) rename {matchers => matcher}/compat/parse_test.go (100%) rename {matchers => matcher}/compliance/compliance_test.go (99%) rename {matchers => matcher}/parse/bench_test.go (100%) rename {matchers => matcher}/parse/fuzz_test.go (100%) rename {matchers => matcher}/parse/lexer.go (100%) rename {matchers => matcher}/parse/lexer_test.go (100%) rename {matchers => matcher}/parse/parse.go (100%) rename {matchers => matcher}/parse/parse_test.go (100%) rename {matchers => matcher}/parse/token.go (100%) diff --git a/api/v2/api.go b/api/v2/api.go index b279b619d5..aa6c8a72ab 100644 --- a/api/v2/api.go +++ b/api/v2/api.go @@ -45,7 +45,7 @@ import ( "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/dispatch" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/provider" "github.com/prometheus/alertmanager/silence" diff --git a/cli/alert_add.go b/cli/alert_add.go index 4e7eae7ed0..2890946c24 100644 --- a/cli/alert_add.go +++ b/cli/alert_add.go @@ -25,7 +25,7 @@ import ( "github.com/prometheus/alertmanager/api/v2/client/alert" "github.com/prometheus/alertmanager/api/v2/models" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" "github.com/prometheus/alertmanager/pkg/labels" ) diff --git a/cli/alert_query.go b/cli/alert_query.go index e4bddaa651..de42ed0ff7 100644 --- a/cli/alert_query.go +++ b/cli/alert_query.go @@ -23,7 +23,7 @@ import ( "github.com/prometheus/alertmanager/api/v2/client/alert" "github.com/prometheus/alertmanager/cli/format" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" ) type alertQueryCmd struct { diff --git a/cli/root.go b/cli/root.go index fe02fb82f3..1e63cfe88e 100644 --- a/cli/root.go +++ b/cli/root.go @@ -34,7 +34,7 @@ import ( "github.com/prometheus/alertmanager/cli/config" "github.com/prometheus/alertmanager/cli/format" "github.com/prometheus/alertmanager/featurecontrol" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" ) var ( diff --git a/cli/silence_add.go b/cli/silence_add.go index 4456ddec9a..e23d1fcc86 100644 --- a/cli/silence_add.go +++ b/cli/silence_add.go @@ -27,7 +27,7 @@ import ( "github.com/prometheus/alertmanager/api/v2/client/silence" "github.com/prometheus/alertmanager/api/v2/models" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" "github.com/prometheus/alertmanager/pkg/labels" ) diff --git a/cli/silence_query.go b/cli/silence_query.go index 5eb33a27d7..ba5f01e15d 100644 --- a/cli/silence_query.go +++ b/cli/silence_query.go @@ -25,7 +25,7 @@ import ( "github.com/prometheus/alertmanager/api/v2/client/silence" "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/cli/format" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" ) type silenceQueryCmd struct { diff --git a/cli/test_routing.go b/cli/test_routing.go index 589a2e8cf8..163dce391b 100644 --- a/cli/test_routing.go +++ b/cli/test_routing.go @@ -24,7 +24,7 @@ import ( "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/dispatch" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" "github.com/prometheus/alertmanager/pkg/labels" ) diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index 49b515c135..d44d58a8d1 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -50,7 +50,7 @@ import ( "github.com/prometheus/alertmanager/dispatch" "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/alertmanager/inhibit" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" "github.com/prometheus/alertmanager/nflog" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/provider/mem" diff --git a/config/config.go b/config/config.go index b1760ea64b..cdc83e3771 100644 --- a/config/config.go +++ b/config/config.go @@ -30,7 +30,7 @@ import ( "github.com/prometheus/common/model" "gopkg.in/yaml.v2" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/timeinterval" ) diff --git a/matchers/compat/parse.go b/matcher/compat/parse.go similarity index 95% rename from matchers/compat/parse.go rename to matcher/compat/parse.go index 0c0dfffb1f..14aeb5a2ae 100644 --- a/matchers/compat/parse.go +++ b/matcher/compat/parse.go @@ -24,7 +24,7 @@ import ( "github.com/prometheus/common/model" "github.com/prometheus/alertmanager/featurecontrol" - "github.com/prometheus/alertmanager/matchers/parse" + "github.com/prometheus/alertmanager/matcher/parse" "github.com/prometheus/alertmanager/pkg/labels" ) @@ -90,7 +90,7 @@ func ClassicMatchersParser(l log.Logger) ParseMatchers { } } -// UTF8MatcherParser uses the new matchers/parse parser to parse the matcher +// UTF8MatcherParser uses the new matcher/parse parser to parse the matcher // in the input string. If this fails it does not revert to the pkg/labels parser. func UTF8MatcherParser(l log.Logger) ParseMatcher { return func(input, origin string) (matcher *labels.Matcher, err error) { @@ -102,7 +102,7 @@ func UTF8MatcherParser(l log.Logger) ParseMatcher { } } -// UTF8MatchersParser uses the new matchers/parse parser to parse zero or more +// UTF8MatchersParser uses the new matcher/parse parser to parse zero or more // matchers in the input string. If this fails it does not revert to the // pkg/labels parser. func UTF8MatchersParser(l log.Logger) ParseMatchers { @@ -112,7 +112,7 @@ func UTF8MatchersParser(l log.Logger) ParseMatchers { } } -// FallbackMatcherParser uses the new matchers/parse parser to parse zero or more +// FallbackMatcherParser uses the new matcher/parse parser to parse zero or more // matchers in the string. If this fails it reverts to the pkg/labels parser and // emits a warning log line. func FallbackMatcherParser(l log.Logger) ParseMatcher { @@ -130,7 +130,7 @@ func FallbackMatcherParser(l log.Logger) ParseMatcher { if cErr != nil { return nil, cErr } - // The input is valid in the pkg/labels parser, but not the matchers/parse + // The input is valid in the pkg/labels parser, but not the matcher/parse // parser. This means the input is not forwards compatible. suggestion := cMatcher.String() level.Warn(l).Log("msg", "Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion) @@ -146,7 +146,7 @@ func FallbackMatcherParser(l log.Logger) ParseMatcher { } } -// FallbackMatchersParser uses the new matchers/parse parser to parse the +// FallbackMatchersParser uses the new matcher/parse parser to parse the // matcher in the input string. If this fails it falls back to the pkg/labels // parser and emits a warning log line. func FallbackMatchersParser(l log.Logger) ParseMatchers { @@ -161,7 +161,7 @@ func FallbackMatchersParser(l log.Logger) ParseMatchers { if cErr != nil { return nil, cErr } - // The input is valid in the pkg/labels parser, but not the matchers/parse + // The input is valid in the pkg/labels parser, but not the matcher/parse // parser. This means the input is not forwards compatible. var sb strings.Builder for i, n := range cMatchers { @@ -172,7 +172,7 @@ func FallbackMatchersParser(l log.Logger) ParseMatchers { } suggestion := sb.String() // The input is valid in the pkg/labels parser, but not the - // new matchers/parse parser. + // new matcher/parse parser. level.Warn(l).Log("msg", "Alertmanager is moving to a new parser for labels and matchers, and this input is incompatible. Alertmanager has instead parsed the input using the classic matchers parser as a fallback. To make this input compatible with the UTF-8 matchers parser please make sure all regular expressions and values are double-quoted. If you are still seeing this message please open an issue.", "input", input, "origin", origin, "err", nErr, "suggestion", suggestion) return cMatchers, nil } diff --git a/matchers/compat/parse_test.go b/matcher/compat/parse_test.go similarity index 100% rename from matchers/compat/parse_test.go rename to matcher/compat/parse_test.go diff --git a/matchers/compliance/compliance_test.go b/matcher/compliance/compliance_test.go similarity index 99% rename from matchers/compliance/compliance_test.go rename to matcher/compliance/compliance_test.go index 844f80a542..2fd39ec68d 100644 --- a/matchers/compliance/compliance_test.go +++ b/matcher/compliance/compliance_test.go @@ -17,7 +17,7 @@ import ( "reflect" "testing" - "github.com/prometheus/alertmanager/matchers/parse" + "github.com/prometheus/alertmanager/matcher/parse" "github.com/prometheus/alertmanager/pkg/labels" ) diff --git a/matchers/parse/bench_test.go b/matcher/parse/bench_test.go similarity index 100% rename from matchers/parse/bench_test.go rename to matcher/parse/bench_test.go diff --git a/matchers/parse/fuzz_test.go b/matcher/parse/fuzz_test.go similarity index 100% rename from matchers/parse/fuzz_test.go rename to matcher/parse/fuzz_test.go diff --git a/matchers/parse/lexer.go b/matcher/parse/lexer.go similarity index 100% rename from matchers/parse/lexer.go rename to matcher/parse/lexer.go diff --git a/matchers/parse/lexer_test.go b/matcher/parse/lexer_test.go similarity index 100% rename from matchers/parse/lexer_test.go rename to matcher/parse/lexer_test.go diff --git a/matchers/parse/parse.go b/matcher/parse/parse.go similarity index 100% rename from matchers/parse/parse.go rename to matcher/parse/parse.go diff --git a/matchers/parse/parse_test.go b/matcher/parse/parse_test.go similarity index 100% rename from matchers/parse/parse_test.go rename to matcher/parse/parse_test.go diff --git a/matchers/parse/token.go b/matcher/parse/token.go similarity index 100% rename from matchers/parse/token.go rename to matcher/parse/token.go diff --git a/pkg/labels/matcher.go b/pkg/labels/matcher.go index f37fcb2173..eba6b4ca60 100644 --- a/pkg/labels/matcher.go +++ b/pkg/labels/matcher.go @@ -205,7 +205,7 @@ func (ms Matchers) String() string { return buf.String() } -// This is copied from matchers/parse/lexer.go. It will be removed when +// This is copied from matcher/parse/lexer.go. It will be removed when // the transition window from classic matchers to UTF-8 matchers is complete, // as then we can use double quotes when printing the label name for all // matchers. Until then, the classic parser does not understand double quotes diff --git a/silence/silence.go b/silence/silence.go index ecd3f840f3..828c282c21 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -37,7 +37,7 @@ import ( "github.com/prometheus/common/model" "github.com/prometheus/alertmanager/cluster" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" "github.com/prometheus/alertmanager/pkg/labels" pb "github.com/prometheus/alertmanager/silence/silencepb" "github.com/prometheus/alertmanager/types" diff --git a/silence/silence_test.go b/silence/silence_test.go index ca425afe4b..98e70cda59 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -33,7 +33,7 @@ import ( "go.uber.org/atomic" "github.com/prometheus/alertmanager/featurecontrol" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" pb "github.com/prometheus/alertmanager/silence/silencepb" "github.com/prometheus/alertmanager/types" ) diff --git a/types/types.go b/types/types.go index 85391ce91f..727ac320e3 100644 --- a/types/types.go +++ b/types/types.go @@ -22,7 +22,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" "github.com/prometheus/alertmanager/pkg/labels" ) diff --git a/types/types_test.go b/types/types_test.go index 198804862b..c5ca4b58cf 100644 --- a/types/types_test.go +++ b/types/types_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/require" "github.com/prometheus/alertmanager/featurecontrol" - "github.com/prometheus/alertmanager/matchers/compat" + "github.com/prometheus/alertmanager/matcher/compat" ) func TestMemMarker_Muted(t *testing.T) { From 25def02a7f57cb4af3d924bc9a26b76c72cef8c3 Mon Sep 17 00:00:00 2001 From: Kapil Ramwani <50957624+codeknight03@users.noreply.github.com> Date: Fri, 21 Jun 2024 19:49:06 +0530 Subject: [PATCH 12/18] Fix: Hide config.SecretURL when the URL is incorrect. (#3887) * fix: Hide config.SecretURL when the URL is incorrect. Updated the config.go to redact the URL. Added test cases to check URL stays hidden. Signed-off-by: Kapil Ramwani(kanishkramwani6@gmail.com) --------- Signed-off-by: Simon Pasquier Co-authored-by: Simon Pasquier --- config/config.go | 7 ++++++- config/config_test.go | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index cdc83e3771..820d71c725 100644 --- a/config/config.go +++ b/config/config.go @@ -165,7 +165,12 @@ func (s *SecretURL) UnmarshalJSON(data []byte) error { s.URL = &url.URL{} return nil } - return json.Unmarshal(data, (*URL)(s)) + // Redact the secret URL in case of errors + if err := json.Unmarshal(data, (*URL)(s)); err != nil { + return errors.New(strings.ReplaceAll(err.Error(), string(data), "[REDACTED]")) + } + + return nil } // Load parses the YAML input s into a Config. diff --git a/config/config_test.go b/config/config_test.go index 7aba475f72..605054e523 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -602,6 +602,15 @@ func TestUnmarshalSecretURL(t *testing.T) { require.Equal(t, "http://example.com/se%20cret", u.String(), "SecretURL not properly unmarshaled in YAML.") } +func TestHideSecretURL(t *testing.T) { + b := []byte(`"://wrongurl/"`) + var u SecretURL + + err := json.Unmarshal(b, &u) + require.Error(t, err) + require.NotContains(t, err.Error(), "wrongurl") +} + func TestMarshalURL(t *testing.T) { for name, tc := range map[string]struct { input *URL From b4443817e8974a8838bdf3559d02606947729381 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Mon, 24 Jun 2024 15:11:10 +0100 Subject: [PATCH 13/18] Fix MaxSilences limit causes incomplete updates of existing silences (#3877) * Fix MaxSilences limit causes incomplete updates of existing silences This commit fixes a bug where the MaxSilences limit can cause an incomplete update of existing silences, where the old silence can be expired but the new silence cannot be created because it would exceeded the maximum number of silences. Signed-off-by: George Robinson --------- Signed-off-by: George Robinson --- silence/silence.go | 36 +++++++++++--------- silence/silence_test.go | 74 ++++++++++++++++++++++++++--------------- 2 files changed, 69 insertions(+), 41 deletions(-) diff --git a/silence/silence.go b/silence/silence.go index 828c282c21..37eadc60c7 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -564,6 +564,13 @@ func (s *Silences) getSilence(id string) (*pb.Silence, bool) { return msil.Silence, true } +func (s *Silences) toMeshSilence(sil *pb.Silence) *pb.MeshSilence { + return &pb.MeshSilence{ + Silence: sil, + ExpiresAt: sil.EndsAt.Add(s.retention), + } +} + func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) error { sil.UpdatedAt = now @@ -573,10 +580,7 @@ func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) } } - msil := &pb.MeshSilence{ - Silence: sil, - ExpiresAt: sil.EndsAt.Add(s.retention), - } + msil := s.toMeshSilence(sil) b, err := marshalMeshSilence(msil) if err != nil { return err @@ -612,25 +616,27 @@ func (s *Silences) Set(sil *pb.Silence) error { return ErrNotFound } - if ok { - if canUpdate(prev, sil, now) { - return s.setSilence(sil, now, false) - } - if getState(prev, s.nowUTC()) != types.SilenceStateExpired { - // We cannot update the silence, expire the old one. - if err := s.expire(prev.Id); err != nil { - return fmt.Errorf("expire previous silence: %w", err) - } - } + if ok && canUpdate(prev, sil, now) { + return s.setSilence(sil, now, false) } - // If we got here it's either a new silence or a replacing one. + // If we got here it's either a new silence or a replacing one (which would + // also create a new silence) so we need to make sure we have capacity for + // the new silence. if s.limits.MaxSilences != nil { if m := s.limits.MaxSilences(); m > 0 && len(s.st)+1 > m { return fmt.Errorf("exceeded maximum number of silences: %d (limit: %d)", len(s.st), m) } } + if ok && getState(prev, s.nowUTC()) != types.SilenceStateExpired { + // We cannot update the silence, expire the old one to leave a history of + // the silence before modification. + if err := s.expire(prev.Id); err != nil { + return fmt.Errorf("expire previous silence: %w", err) + } + } + uid, err := uuid.NewV4() if err != nil { return fmt.Errorf("generate uuid: %w", err) diff --git a/silence/silence_test.go b/silence/silence_test.go index 98e70cda59..155a154454 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -15,6 +15,7 @@ package silence import ( "bytes" + "fmt" "os" "runtime" "sort" @@ -470,32 +471,24 @@ func TestSilenceLimits(t *testing.T) { EndsAt: time.Now().Add(5 * time.Minute), } require.NoError(t, s.Set(sil1)) - require.NotEqual(t, "", sil1.Id) - // Insert sil2 should fail because maximum number of silences - // has been exceeded. + // Insert sil2 should fail because maximum number of silences has been + // exceeded. sil2 := &pb.Silence{ - Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, + Matchers: []*pb.Matcher{{Name: "c", Pattern: "d"}}, StartsAt: time.Now(), EndsAt: time.Now().Add(5 * time.Minute), } require.EqualError(t, s.Set(sil2), "exceeded maximum number of silences: 1 (limit: 1)") - require.Equal(t, "", sil2.Id) - // Expire sil1 and run the GC. This should allow sil2 to be - // inserted. + // Expire sil1 and run the GC. This should allow sil2 to be inserted. require.NoError(t, s.Expire(sil1.Id)) n, err := s.GC() require.NoError(t, err) require.Equal(t, 1, n) - require.NoError(t, s.Set(sil2)) - require.NotEqual(t, "", sil2.Id) - // Should be able to update sil2 without hitting the limit. - require.NoError(t, s.Set(sil2)) - - // Expire sil2. + // Expire sil2 and run the GC. require.NoError(t, s.Expire(sil2.Id)) n, err = s.GC() require.NoError(t, err) @@ -505,26 +498,55 @@ func TestSilenceLimits(t *testing.T) { sil3 := &pb.Silence{ Matchers: []*pb.Matcher{ { - Name: strings.Repeat("a", 2<<9), - Pattern: strings.Repeat("b", 2<<9), + Name: strings.Repeat("e", 2<<9), + Pattern: strings.Repeat("f", 2<<9), }, { - Name: strings.Repeat("c", 2<<9), - Pattern: strings.Repeat("d", 2<<9), + Name: strings.Repeat("g", 2<<9), + Pattern: strings.Repeat("h", 2<<9), }, }, - CreatedBy: strings.Repeat("e", 2<<9), - Comment: strings.Repeat("f", 2<<9), + CreatedBy: strings.Repeat("i", 2<<9), + Comment: strings.Repeat("j", 2<<9), StartsAt: time.Now(), EndsAt: time.Now().Add(5 * time.Minute), } - err = s.Set(sil3) - require.Error(t, err) - // Do not check the exact size as it can change between consecutive runs - // due to padding. - require.Contains(t, err.Error(), "silence exceeded maximum size") - // TODO: Once we fix https://github.com/prometheus/alertmanager/issues/3878 this should be require.Equal. - require.NotEqual(t, "", sil3.Id) + require.EqualError(t, s.Set(sil3), fmt.Sprintf("silence exceeded maximum size: %d bytes (limit: 4096 bytes)", s.toMeshSilence(sil3).Size())) + + // Should be able to insert sil4. + sil4 := &pb.Silence{ + Matchers: []*pb.Matcher{{Name: "k", Pattern: "l"}}, + StartsAt: time.Now(), + EndsAt: time.Now().Add(5 * time.Minute), + } + require.NoError(t, s.Set(sil4)) + + // Should be able to update sil4 without modifications. It is expected to + // keep the same ID. + sil5 := cloneSilence(sil4) + require.NoError(t, s.Set(sil5)) + require.Equal(t, sil4.Id, sil5.Id) + + // Should be able to update the comment. It is also expected to keep the + // same ID. + sil6 := cloneSilence(sil5) + sil6.Comment = "m" + require.NoError(t, s.Set(sil6)) + require.Equal(t, sil5.Id, sil6.Id) + + // Should not be able to update the start and end time as this requires + // sil6 to be expired and a new silence to be created. However, this would + // exceed the maximum number of silences, which counts both active and + // expired silences. + sil7 := cloneSilence(sil6) + sil7.StartsAt = time.Now().Add(5 * time.Minute) + sil7.EndsAt = time.Now().Add(10 * time.Minute) + require.EqualError(t, s.Set(sil7), "exceeded maximum number of silences: 1 (limit: 1)") + + // sil6 should not be expired because the update failed. + sil6, err = s.QueryOne(QIDs(sil6.Id)) + require.NoError(t, err) + require.Equal(t, types.SilenceStateActive, getState(sil6, s.nowUTC())) } func TestSilenceNoLimits(t *testing.T) { From ffd7681fb42ccb9f1e7189ce32f5cda6ec90977a Mon Sep 17 00:00:00 2001 From: George Robinson Date: Mon, 24 Jun 2024 15:32:16 +0100 Subject: [PATCH 14/18] Improve test coverage for silences (#3896) This commit improves the existing test coverage for silences to cover a number of additional cases, and also improve the comments of existing cases. Signed-off-by: George Robinson --- silence/silence_test.go | 99 ++++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/silence/silence_test.go b/silence/silence_test.go index 155a154454..b77eca83c2 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -365,16 +365,27 @@ func TestSilenceSet(t *testing.T) { } require.Equal(t, want, s.st, "unexpected state after silence creation") - // Overwrite silence 2 with new end time. - clock.Add(time.Minute) - start3 := s.nowUTC() - + // Should be able to update silence without modifications. It is expected to + // keep the same ID. sil3 := cloneSilence(sil2) - sil3.EndsAt = start3.Add(100 * time.Minute) - require.NoError(t, s.Set(sil3)) require.Equal(t, sil2.Id, sil3.Id) + // Should be able to update silence with comment. It is also expected to + // keep the same ID. + sil4 := cloneSilence(sil3) + sil4.Comment = "c" + require.NoError(t, s.Set(sil4)) + require.Equal(t, sil3.Id, sil4.Id) + + // Extend sil4 to expire at a later time. This should not expire the + // existing silence, and so should also keep the same ID. + clock.Add(time.Minute) + start5 := s.nowUTC() + sil5 := cloneSilence(sil4) + sil5.EndsAt = start5.Add(100 * time.Minute) + require.NoError(t, s.Set(sil5)) + require.Equal(t, sil4.Id, sil5.Id) want = state{ sil1.Id: want[sil1.Id], sil2.Id: &pb.MeshSilence{ @@ -382,25 +393,27 @@ func TestSilenceSet(t *testing.T) { Id: sil2.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, StartsAt: start2, - EndsAt: start3.Add(100 * time.Minute), - UpdatedAt: start3, + EndsAt: start5.Add(100 * time.Minute), + UpdatedAt: start5, + Comment: "c", }, - ExpiresAt: start3.Add(100*time.Minute + s.retention), + ExpiresAt: start5.Add(100*time.Minute + s.retention), }, } require.Equal(t, want, s.st, "unexpected state after silence creation") - // Update this silence again with new matcher. This expires it and creates a new one. + // Replace the silence sil5 with another silence with different matchers. + // Unlike previous updates, changing the matchers for an existing silence + // will expire the existing silence and create a new silence. The new + // silence is expected to have a different ID to preserve the history of + // the previous silence. clock.Add(time.Minute) - start4 := s.nowUTC() - - sil4 := cloneSilence(sil3) - sil4.Matchers = []*pb.Matcher{{Name: "a", Pattern: "c"}} - - require.NoError(t, s.Set(sil4)) - // This new silence gets a new id. - require.NotEqual(t, sil2.Id, sil4.Id) + start6 := s.nowUTC() + sil6 := cloneSilence(sil5) + sil6.Matchers = []*pb.Matcher{{Name: "a", Pattern: "c"}} + require.NoError(t, s.Set(sil6)) + require.NotEqual(t, sil5.Id, sil6.Id) want = state{ sil1.Id: want[sil1.Id], sil2.Id: &pb.MeshSilence{ @@ -408,46 +421,48 @@ func TestSilenceSet(t *testing.T) { Id: sil2.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, StartsAt: start2, - EndsAt: start4, // Expired - UpdatedAt: start4, + EndsAt: start6, // Expired + UpdatedAt: start6, + Comment: "c", }, - ExpiresAt: start4.Add(s.retention), + ExpiresAt: start6.Add(s.retention), }, - sil4.Id: &pb.MeshSilence{ + sil6.Id: &pb.MeshSilence{ Silence: &pb.Silence{ - Id: sil4.Id, + Id: sil6.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "c"}}, - StartsAt: start4, - EndsAt: start3.Add(100 * time.Minute), - UpdatedAt: start4, + StartsAt: start6, + EndsAt: start5.Add(100 * time.Minute), + UpdatedAt: start6, + Comment: "c", }, - ExpiresAt: start3.Add(100*time.Minute + s.retention), + ExpiresAt: start5.Add(100*time.Minute + s.retention), }, } require.Equal(t, want, s.st, "unexpected state after silence creation") - // Re-create the silence that just expired. + // Re-create the silence that we just replaced. Changing the start time, + // just like changing the matchers, creates a new silence with a different + // ID. This is again to preserve the history of the original silence. clock.Add(time.Minute) - start5 := s.nowUTC() - - sil5 := cloneSilence(sil3) - sil5.StartsAt = start1 - sil5.EndsAt = start1.Add(5 * time.Minute) - - require.NoError(t, s.Set(sil5)) - require.NotEqual(t, sil2.Id, sil5.Id) - + start7 := s.nowUTC() + sil7 := cloneSilence(sil5) + sil7.StartsAt = start1 + sil7.EndsAt = start1.Add(5 * time.Minute) + require.NoError(t, s.Set(sil7)) + require.NotEqual(t, sil2.Id, sil7.Id) want = state{ sil1.Id: want[sil1.Id], sil2.Id: want[sil2.Id], - sil4.Id: want[sil4.Id], - sil5.Id: &pb.MeshSilence{ + sil6.Id: want[sil6.Id], + sil7.Id: &pb.MeshSilence{ Silence: &pb.Silence{ - Id: sil5.Id, + Id: sil7.Id, Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, - StartsAt: start5, // New silences have their start time set to "now" when created. + StartsAt: start7, // New silences have their start time set to "now" when created. EndsAt: start1.Add(5 * time.Minute), - UpdatedAt: start5, + UpdatedAt: start7, + Comment: "c", }, ExpiresAt: start1.Add(5*time.Minute + s.retention), }, From b676fc4d2ea7a533e7280b2868a211ac829c6114 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Tue, 25 Jun 2024 11:58:45 +0100 Subject: [PATCH 15/18] Add tests for create and then update silences (#3899) This commit adds a unit test for the postSilencesHandler to create and then update a silence. It shows that changing the ID of an existing silence returns 404 Not Found, and removing the ID of an existing silence re-creates that silence with a different ID. Signed-off-by: George Robinson --- api/v2/api_test.go | 102 +++++++++++++++++++++++++++++++++++++++------ api/v2/testing.go | 9 +--- 2 files changed, 92 insertions(+), 19 deletions(-) diff --git a/api/v2/api_test.go b/api/v2/api_test.go index 5cabf2e0c7..438e6f44d4 100644 --- a/api/v2/api_test.go +++ b/api/v2/api_test.go @@ -15,6 +15,7 @@ package v2 import ( "bytes" + "encoding/json" "fmt" "io" "net/http" @@ -24,6 +25,7 @@ import ( "time" "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" "github.com/go-openapi/strfmt" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" @@ -269,32 +271,108 @@ func TestPostSilencesHandler(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - silence, silenceBytes := createSilence(t, tc.sid, "silenceCreator", tc.start, tc.end) - api := API{ uptime: time.Now(), silences: silences, logger: log.NewNopLogger(), } - r, err := http.NewRequest("POST", "/api/v2/silence/${tc.sid}", bytes.NewReader(silenceBytes)) - require.NoError(t, err) - + sil := createSilence(t, tc.sid, "silenceCreator", tc.start, tc.end) w := httptest.NewRecorder() - p := runtime.TextProducer() - responder := api.postSilencesHandler(silence_ops.PostSilencesParams{ - HTTPRequest: r, - Silence: &silence, - }) - responder.WriteResponse(w, p) + postSilences(t, w, api.postSilencesHandler, sil) body, _ := io.ReadAll(w.Result().Body) - require.Equal(t, tc.expectedCode, w.Code, fmt.Sprintf("test case: %d, response: %s", i, string(body))) }) } }) } +func TestPostSilencesHandlerMissingIdCreatesSilence(t *testing.T) { + now := time.Now() + silences := newSilences(t) + api := API{ + uptime: time.Now(), + silences: silences, + logger: log.NewNopLogger(), + } + + // Create a new silence. It should be assigned a random UUID. + sil := createSilence(t, "", "silenceCreator", now.Add(time.Hour), now.Add(time.Hour*2)) + w := httptest.NewRecorder() + postSilences(t, w, api.postSilencesHandler, sil) + require.Equal(t, http.StatusOK, w.Code) + + // Get the silences from the API. + w = httptest.NewRecorder() + getSilences(t, w, api.getSilencesHandler) + require.Equal(t, http.StatusOK, w.Code) + var resp []open_api_models.GettableSilence + require.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) + require.Len(t, resp, 1) + + // Change the ID. It should return 404 Not Found. + sil = open_api_models.PostableSilence{ + ID: "unknownID", + Silence: resp[0].Silence, + } + w = httptest.NewRecorder() + postSilences(t, w, api.postSilencesHandler, sil) + require.Equal(t, http.StatusNotFound, w.Code) + + // Remove the ID. It should duplicate the silence with a different UUID. + sil = open_api_models.PostableSilence{ + ID: "", + Silence: resp[0].Silence, + } + w = httptest.NewRecorder() + postSilences(t, w, api.postSilencesHandler, sil) + require.Equal(t, http.StatusOK, w.Code) + + // Get the silences from the API. There should now be 2 silences. + w = httptest.NewRecorder() + getSilences(t, w, api.getSilencesHandler) + require.Equal(t, http.StatusOK, w.Code) + require.NoError(t, json.NewDecoder(w.Body).Decode(&resp)) + require.Len(t, resp, 2) + require.NotEqual(t, resp[0].ID, resp[1].ID) +} + +func getSilences( + t *testing.T, + w *httptest.ResponseRecorder, + handlerFunc func(params silence_ops.GetSilencesParams) middleware.Responder, +) { + r, err := http.NewRequest("GET", "/api/v2/silences", nil) + require.NoError(t, err) + + p := runtime.TextProducer() + responder := handlerFunc(silence_ops.GetSilencesParams{ + HTTPRequest: r, + Filter: nil, + }) + responder.WriteResponse(w, p) +} + +func postSilences( + t *testing.T, + w *httptest.ResponseRecorder, + handlerFunc func(params silence_ops.PostSilencesParams) middleware.Responder, + sil open_api_models.PostableSilence, +) { + b, err := json.Marshal(sil) + require.NoError(t, err) + + r, err := http.NewRequest("POST", "/api/v2/silences", bytes.NewReader(b)) + require.NoError(t, err) + + p := runtime.TextProducer() + responder := handlerFunc(silence_ops.PostSilencesParams{ + HTTPRequest: r, + Silence: &sil, + }) + responder.WriteResponse(w, p) +} + func TestCheckSilenceMatchesFilterLabels(t *testing.T) { type test struct { silenceMatchers []*silencepb.Matcher diff --git a/api/v2/testing.go b/api/v2/testing.go index c813d350d3..7665a19f67 100644 --- a/api/v2/testing.go +++ b/api/v2/testing.go @@ -14,19 +14,17 @@ package v2 import ( - "encoding/json" "testing" "time" "github.com/go-openapi/strfmt" - "github.com/stretchr/testify/require" open_api_models "github.com/prometheus/alertmanager/api/v2/models" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/silence/silencepb" ) -func createSilence(t *testing.T, ID, creator string, start, ends time.Time) (open_api_models.PostableSilence, []byte) { +func createSilence(t *testing.T, ID, creator string, start, ends time.Time) open_api_models.PostableSilence { t.Helper() comment := "test" @@ -46,10 +44,7 @@ func createSilence(t *testing.T, ID, creator string, start, ends time.Time) (ope Comment: &comment, }, } - b, err := json.Marshal(&sil) - require.NoError(t, err) - - return sil, b + return sil } func createSilenceMatcher(t *testing.T, name, pattern string, matcherType silencepb.Matcher_Type) *silencepb.Matcher { From 58dc6f8d3358c12dffb8eb287c800ba15b6cde96 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Tue, 25 Jun 2024 12:38:33 +0100 Subject: [PATCH 16/18] Fix invalid silence causes incomplete updates (#3898) This commit fixes a bug where an invalid silence causes incomplete updates of existing silences. This is fixed moving validation out of the setSilence method and putting it at the start of the Set method instead. Signed-off-by: George Robinson --- silence/silence.go | 31 ++++++---------- silence/silence_test.go | 47 ++++++++++-------------- test/with_api_v2/acceptance/utf8_test.go | 2 +- 3 files changed, 33 insertions(+), 47 deletions(-) diff --git a/silence/silence.go b/silence/silence.go index 37eadc60c7..06a27cf61c 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -518,9 +518,6 @@ func matchesEmpty(m *pb.Matcher) bool { } func validateSilence(s *pb.Silence) error { - if s.Id == "" { - return errors.New("ID missing") - } if len(s.Matchers) == 0 { return errors.New("at least one matcher required") } @@ -544,9 +541,6 @@ func validateSilence(s *pb.Silence) error { if s.EndsAt.Before(s.StartsAt) { return errors.New("end time must not be before start time") } - if s.UpdatedAt.IsZero() { - return errors.New("invalid zero update timestamp") - } return nil } @@ -571,15 +565,9 @@ func (s *Silences) toMeshSilence(sil *pb.Silence) *pb.MeshSilence { } } -func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) error { +func (s *Silences) setSilence(sil *pb.Silence, now time.Time) error { sil.UpdatedAt = now - if !skipValidate { - if err := validateSilence(sil); err != nil { - return fmt.Errorf("silence invalid: %w", err) - } - } - msil := s.toMeshSilence(sil) b, err := marshalMeshSilence(msil) if err != nil { @@ -611,13 +599,21 @@ func (s *Silences) Set(sil *pb.Silence) error { defer s.mtx.Unlock() now := s.nowUTC() + if sil.StartsAt.IsZero() { + sil.StartsAt = now + } + + if err := validateSilence(sil); err != nil { + return fmt.Errorf("invalid silence: %w", err) + } + prev, ok := s.getSilence(sil.Id) if sil.Id != "" && !ok { return ErrNotFound } if ok && canUpdate(prev, sil, now) { - return s.setSilence(sil, now, false) + return s.setSilence(sil, now) } // If we got here it's either a new silence or a replacing one (which would @@ -647,7 +643,7 @@ func (s *Silences) Set(sil *pb.Silence) error { sil.StartsAt = now } - return s.setSilence(sil, now, false) + return s.setSilence(sil, now) } // canUpdate returns true if silence a can be updated to b without @@ -705,10 +701,7 @@ func (s *Silences) expire(id string) error { sil.StartsAt = now sil.EndsAt = now } - - // Skip validation of the silence when expiring it. Without this, silences created - // with valid UTF-8 matchers cannot be expired when Alertmanager is run in classic mode. - return s.setSilence(sil, now, true) + return s.setSilence(sil, now) } // QueryParam expresses parameters along which silences are queried. diff --git a/silence/silence_test.go b/silence/silence_test.go index b77eca83c2..c9a635f476 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -295,7 +295,7 @@ func TestSilencesSetSilence(t *testing.T) { func() { s.mtx.Lock() defer s.mtx.Unlock() - require.NoError(t, s.setSilence(sil, nowpb, false)) + require.NoError(t, s.setSilence(sil, nowpb)) }() // Ensure broadcast was called. @@ -468,6 +468,19 @@ func TestSilenceSet(t *testing.T) { }, } require.Equal(t, want, s.st, "unexpected state after silence creation") + + // Updating an existing silence with an invalid silence should not expire + // the original silence. + clock.Add(time.Millisecond) + sil8 := cloneSilence(sil7) + sil8.EndsAt = time.Time{} + require.EqualError(t, s.Set(sil8), "invalid silence: invalid zero end timestamp") + + // sil7 should not be expired because the update failed. + clock.Add(time.Millisecond) + sil7, err = s.QueryOne(QIDs(sil7.Id)) + require.NoError(t, err) + require.Equal(t, types.SilenceStateActive, getState(sil7, s.nowUTC())) } func TestSilenceLimits(t *testing.T) { @@ -643,11 +656,15 @@ func TestSilencesSetFail(t *testing.T) { err string }{ { - s: &pb.Silence{Id: "some_id"}, + s: &pb.Silence{ + Id: "some_id", + Matchers: []*pb.Matcher{{Name: "a", Pattern: "b"}}, + EndsAt: clock.Now().Add(5 * time.Minute), + }, err: ErrNotFound.Error(), }, { s: &pb.Silence{}, // Silence without matcher. - err: "silence invalid", + err: "invalid silence", }, } for _, c := range cases { @@ -1488,18 +1505,6 @@ func TestValidateSilence(t *testing.T) { }, err: "", }, - { - s: &pb.Silence{ - Id: "", - Matchers: []*pb.Matcher{ - {Name: "a", Pattern: "b"}, - }, - StartsAt: validTimestamp, - EndsAt: validTimestamp, - UpdatedAt: validTimestamp, - }, - err: "ID missing", - }, { s: &pb.Silence{ Id: "some_id", @@ -1572,18 +1577,6 @@ func TestValidateSilence(t *testing.T) { }, err: "invalid zero end timestamp", }, - { - s: &pb.Silence{ - Id: "some_id", - Matchers: []*pb.Matcher{ - {Name: "a", Pattern: "b"}, - }, - StartsAt: validTimestamp, - EndsAt: validTimestamp, - UpdatedAt: zeroTimestamp, - }, - err: "invalid zero update timestamp", - }, } for _, c := range cases { checkErr(t, c.err, validateSilence(c.s)) diff --git a/test/with_api_v2/acceptance/utf8_test.go b/test/with_api_v2/acceptance/utf8_test.go index 6c12b2f762..c0c1dce256 100644 --- a/test/with_api_v2/acceptance/utf8_test.go +++ b/test/with_api_v2/acceptance/utf8_test.go @@ -267,7 +267,7 @@ receivers: _, err := am.Client().Silence.PostSilences(silenceParams) require.Error(t, err) - require.True(t, strings.Contains(err.Error(), "silence invalid: invalid label matcher")) + require.True(t, strings.Contains(err.Error(), "invalid silence: invalid label matcher")) } func TestSendAlertsToUTF8Route(t *testing.T) { From 94ac36b3e04525431d812319229bcf95758b2c93 Mon Sep 17 00:00:00 2001 From: George Robinson Date: Tue, 25 Jun 2024 13:23:09 +0100 Subject: [PATCH 17/18] Fix MaxSilenceSizeBytes limit causes incomplete updates of existing silences (#3897) This commit fixes a bug where the MaxSilenceSizeBytes limit can cause an incomplete update of existing silences, where the old silence can be expired but the new silence cannot be created because it would exceed the maximum size limit. Signed-off-by: George Robinson --- silence/silence.go | 61 +++++++++++++++++++++++------------------ silence/silence_test.go | 29 +++++++++++++++++++- 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/silence/silence.go b/silence/silence.go index 06a27cf61c..ee97551908 100644 --- a/silence/silence.go +++ b/silence/silence.go @@ -550,6 +550,16 @@ func cloneSilence(sil *pb.Silence) *pb.Silence { return &s } +func (s *Silences) checkSizeLimits(msil *pb.MeshSilence) error { + if s.limits.MaxSilenceSizeBytes != nil { + n := msil.Size() + if m := s.limits.MaxSilenceSizeBytes(); m > 0 && n > m { + return fmt.Errorf("silence exceeded maximum size: %d bytes (limit: %d bytes)", n, m) + } + } + return nil +} + func (s *Silences) getSilence(id string) (*pb.Silence, bool) { msil, ok := s.st[id] if !ok { @@ -565,30 +575,15 @@ func (s *Silences) toMeshSilence(sil *pb.Silence) *pb.MeshSilence { } } -func (s *Silences) setSilence(sil *pb.Silence, now time.Time) error { - sil.UpdatedAt = now - - msil := s.toMeshSilence(sil) +func (s *Silences) setSilence(msil *pb.MeshSilence, now time.Time) error { b, err := marshalMeshSilence(msil) if err != nil { return err } - - // Check the limit unless the silence has been expired. This is to avoid - // situations where silences cannot be expired after the limit has been - // reduced. - if s.limits.MaxSilenceSizeBytes != nil { - n := msil.Size() - if m := s.limits.MaxSilenceSizeBytes(); m > 0 && n > m && sil.EndsAt.After(now) { - return fmt.Errorf("silence exceeded maximum size: %d bytes (limit: %d bytes)", n, m) - } - } - if s.st.merge(msil, now) { s.version++ } s.broadcast(b) - return nil } @@ -613,7 +608,12 @@ func (s *Silences) Set(sil *pb.Silence) error { } if ok && canUpdate(prev, sil, now) { - return s.setSilence(sil, now) + sil.UpdatedAt = now + msil := s.toMeshSilence(sil) + if err := s.checkSizeLimits(msil); err != nil { + return err + } + return s.setSilence(msil, now) } // If we got here it's either a new silence or a replacing one (which would @@ -625,14 +625,6 @@ func (s *Silences) Set(sil *pb.Silence) error { } } - if ok && getState(prev, s.nowUTC()) != types.SilenceStateExpired { - // We cannot update the silence, expire the old one to leave a history of - // the silence before modification. - if err := s.expire(prev.Id); err != nil { - return fmt.Errorf("expire previous silence: %w", err) - } - } - uid, err := uuid.NewV4() if err != nil { return fmt.Errorf("generate uuid: %w", err) @@ -642,8 +634,22 @@ func (s *Silences) Set(sil *pb.Silence) error { if sil.StartsAt.Before(now) { sil.StartsAt = now } + sil.UpdatedAt = now - return s.setSilence(sil, now) + msil := s.toMeshSilence(sil) + if err := s.checkSizeLimits(msil); err != nil { + return err + } + + if ok && getState(prev, s.nowUTC()) != types.SilenceStateExpired { + // We cannot update the silence, expire the old one to leave a history of + // the silence before modification. + if err := s.expire(prev.Id); err != nil { + return fmt.Errorf("expire previous silence: %w", err) + } + } + + return s.setSilence(msil, now) } // canUpdate returns true if silence a can be updated to b without @@ -701,7 +707,8 @@ func (s *Silences) expire(id string) error { sil.StartsAt = now sil.EndsAt = now } - return s.setSilence(sil, now) + sil.UpdatedAt = now + return s.setSilence(s.toMeshSilence(sil), now) } // QueryParam expresses parameters along which silences are queried. diff --git a/silence/silence_test.go b/silence/silence_test.go index c9a635f476..6fe536e557 100644 --- a/silence/silence_test.go +++ b/silence/silence_test.go @@ -295,7 +295,7 @@ func TestSilencesSetSilence(t *testing.T) { func() { s.mtx.Lock() defer s.mtx.Unlock() - require.NoError(t, s.setSilence(sil, nowpb)) + require.NoError(t, s.setSilence(s.toMeshSilence(sil), nowpb)) }() // Ensure broadcast was called. @@ -575,6 +575,33 @@ func TestSilenceLimits(t *testing.T) { sil6, err = s.QueryOne(QIDs(sil6.Id)) require.NoError(t, err) require.Equal(t, types.SilenceStateActive, getState(sil6, s.nowUTC())) + + // Should not be able to update with a comment that exceeds maximum size. + // Need to increase the maximum number of silences to test this. + s.limits.MaxSilences = func() int { return 2 } + sil8 := cloneSilence(sil6) + sil8.Comment = strings.Repeat("m", 2<<11) + require.EqualError(t, s.Set(sil8), fmt.Sprintf("silence exceeded maximum size: %d bytes (limit: 4096 bytes)", s.toMeshSilence(sil8).Size())) + + // sil6 should not be expired because the update failed. + sil6, err = s.QueryOne(QIDs(sil6.Id)) + require.NoError(t, err) + require.Equal(t, types.SilenceStateActive, getState(sil6, s.nowUTC())) + + // Should not be able to replace with a silence that exceeds maximum size. + // This is different from the previous assertion as unlike when adding or + // updating a comment, changing the matchers for a silence should expire + // the existing silence, unless the silence that is replacing it exceeds + // limits, in which case the operation should fail and the existing silence + // should still be active. + sil9 := cloneSilence(sil8) + sil9.Matchers = []*pb.Matcher{{Name: "n", Pattern: "o"}} + require.EqualError(t, s.Set(sil9), fmt.Sprintf("silence exceeded maximum size: %d bytes (limit: 4096 bytes)", s.toMeshSilence(sil9).Size())) + + // sil6 should not be expired because the update failed. + sil6, err = s.QueryOne(QIDs(sil6.Id)) + require.NoError(t, err) + require.Equal(t, types.SilenceStateActive, getState(sil6, s.nowUTC())) } func TestSilenceNoLimits(t *testing.T) { From 7d615b778f5c71a1d1625d4296b1a669e8f02d6d Mon Sep 17 00:00:00 2001 From: dongjiang Date: Tue, 25 Jun 2024 20:29:08 +0800 Subject: [PATCH 18/18] feat(env): add auto go maxprocs (#3837) * add auto go maxprocs --------- Signed-off-by: dongjiang1989 Signed-off-by: dongjiang --- cmd/alertmanager/main.go | 10 ++++++++++ featurecontrol/featurecontrol.go | 19 +++++++++++++++++++ go.mod | 1 + go.sum | 4 ++++ 4 files changed, 34 insertions(+) diff --git a/cmd/alertmanager/main.go b/cmd/alertmanager/main.go index d44d58a8d1..06d697a055 100644 --- a/cmd/alertmanager/main.go +++ b/cmd/alertmanager/main.go @@ -42,6 +42,7 @@ import ( "github.com/prometheus/common/version" "github.com/prometheus/exporter-toolkit/web" webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag" + "go.uber.org/automaxprocs/maxprocs" "github.com/prometheus/alertmanager/api" "github.com/prometheus/alertmanager/cluster" @@ -194,6 +195,15 @@ func run() int { } compat.InitFromFlags(logger, ff) + if ff.EnableAutoGOMAXPROCS() { + l := func(format string, a ...interface{}) { + level.Info(logger).Log("component", "automaxprocs", "msg", fmt.Sprintf(strings.TrimPrefix(format, "maxprocs: "), a...)) + } + if _, err := maxprocs.Set(maxprocs.Logger(l)); err != nil { + level.Warn(logger).Log("msg", "Failed to set GOMAXPROCS automatically", "err", err) + } + } + err = os.MkdirAll(*dataDir, 0o777) if err != nil { level.Error(logger).Log("msg", "Unable to create data directory", "err", err) diff --git a/featurecontrol/featurecontrol.go b/featurecontrol/featurecontrol.go index 9ff7a2d8fd..4616a057fb 100644 --- a/featurecontrol/featurecontrol.go +++ b/featurecontrol/featurecontrol.go @@ -26,18 +26,21 @@ const ( FeatureReceiverNameInMetrics = "receiver-name-in-metrics" FeatureClassicMode = "classic-mode" FeatureUTF8StrictMode = "utf8-strict-mode" + FeatureAutoGOMAXPROCS = "auto-gomaxprocs" ) var AllowedFlags = []string{ FeatureReceiverNameInMetrics, FeatureClassicMode, FeatureUTF8StrictMode, + FeatureAutoGOMAXPROCS, } type Flagger interface { EnableReceiverNamesInMetrics() bool ClassicMode() bool UTF8StrictMode() bool + EnableAutoGOMAXPROCS() bool } type Flags struct { @@ -45,6 +48,7 @@ type Flags struct { enableReceiverNamesInMetrics bool classicMode bool utf8StrictMode bool + enableAutoGOMAXPROCS bool } func (f *Flags) EnableReceiverNamesInMetrics() bool { @@ -59,6 +63,10 @@ func (f *Flags) UTF8StrictMode() bool { return f.utf8StrictMode } +func (f *Flags) EnableAutoGOMAXPROCS() bool { + return f.enableAutoGOMAXPROCS +} + type flagOption func(flags *Flags) func enableReceiverNameInMetrics() flagOption { @@ -79,6 +87,12 @@ func enableUTF8StrictMode() flagOption { } } +func enableAutoGOMAXPROCS() flagOption { + return func(configs *Flags) { + configs.enableAutoGOMAXPROCS = true + } +} + func NewFlags(logger log.Logger, features string) (Flagger, error) { fc := &Flags{logger: logger} opts := []flagOption{} @@ -98,6 +112,9 @@ func NewFlags(logger log.Logger, features string) (Flagger, error) { case FeatureUTF8StrictMode: opts = append(opts, enableUTF8StrictMode()) level.Warn(logger).Log("msg", "UTF-8 strict mode enabled") + case FeatureAutoGOMAXPROCS: + opts = append(opts, enableAutoGOMAXPROCS()) + level.Warn(logger).Log("msg", "Automatically set GOMAXPROCS to match Linux container CPU quota") default: return nil, fmt.Errorf("Unknown option '%s' for --enable-feature", feature) } @@ -121,3 +138,5 @@ func (n NoopFlags) EnableReceiverNamesInMetrics() bool { return false } func (n NoopFlags) ClassicMode() bool { return false } func (n NoopFlags) UTF8StrictMode() bool { return false } + +func (n NoopFlags) EnableAutoGOMAXPROCS() bool { return false } diff --git a/go.mod b/go.mod index ac487a46b7..71051b8560 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/stretchr/testify v1.9.0 github.com/xlab/treeprint v1.2.0 go.uber.org/atomic v1.11.0 + go.uber.org/automaxprocs v1.5.3 golang.org/x/mod v0.18.0 golang.org/x/net v0.26.0 golang.org/x/text v0.16.0 diff --git a/go.sum b/go.sum index 9c87947bb0..0d8f4f45e2 100644 --- a/go.sum +++ b/go.sum @@ -411,6 +411,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= @@ -519,6 +521,8 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=