diff --git a/go.mod b/go.mod index 4d6ec8c81f2..323328a2c2c 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,7 @@ require ( github.com/opentracing-contrib/go-stdlib v1.0.0 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b github.com/pkg/errors v0.9.1 - github.com/prometheus/alertmanager v0.26.0 + github.com/prometheus/alertmanager v0.26.1-0.20231117200754-ca5089d33eab github.com/prometheus/client_golang v1.17.0 github.com/prometheus/client_model v0.5.0 github.com/prometheus/common v0.45.0 @@ -209,7 +209,7 @@ require ( github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97 // indirect github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect - github.com/rs/cors v1.9.0 // indirect + github.com/rs/cors v1.10.1 // indirect github.com/rs/xid v1.5.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/sercand/kuberesolver/v5 v5.1.1 // indirect diff --git a/go.sum b/go.sum index 92a453bf6cb..6278fb04240 100644 --- a/go.sum +++ b/go.sum @@ -829,8 +829,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/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/prometheus/alertmanager v0.26.0 h1:uOMJWfIwJguc3NaM3appWNbbrh6G/OjvaHMk22aBBYc= -github.com/prometheus/alertmanager v0.26.0/go.mod h1:rVcnARltVjavgVaNnmevxK7kOn7IZavyf0KNgHkbEpU= +github.com/prometheus/alertmanager v0.26.1-0.20231117200754-ca5089d33eab h1:639j+0he3vXZWIfy8RgphuZC3iCKGRe4qM23fymWrkE= +github.com/prometheus/alertmanager v0.26.1-0.20231117200754-ca5089d33eab/go.mod h1:d9ELeFBjQ7xxGYb+0Xh+ygxMUBwM6579tojeH6xb0qE= 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= @@ -872,8 +872,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= -github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= +github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= diff --git a/pkg/alertmanager/alertmanager.go b/pkg/alertmanager/alertmanager.go index f4752a0b160..67023522916 100644 --- a/pkg/alertmanager/alertmanager.go +++ b/pkg/alertmanager/alertmanager.go @@ -27,6 +27,7 @@ import ( "github.com/prometheus/alertmanager/cluster/clusterpb" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/dispatch" + "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/alertmanager/inhibit" "github.com/prometheus/alertmanager/nflog" "github.com/prometheus/alertmanager/notify" @@ -237,7 +238,15 @@ func New(cfg *Config, reg *prometheus.Registry) (*Alertmanager, error) { return nil, errors.Wrap(err, "failed to start state persister service") } - am.pipelineBuilder = notify.NewPipelineBuilder(am.registry) + // Alertmanager defines two flags for the classic vs new matcher parsing. + // The default behavior with no flags set, is to actually use the new matcher parsing that's still under development. + // Therefore, we must pass in a flag here to force usage of the default/stable matcher parser. + // In the future, when the new matcher parsing stabilizes and we feel ready to adopt it, we can remove this flag to enable it. + features, err := featurecontrol.NewFlags(am.logger, featurecontrol.FeatureClassicMode) + if err != nil { + return nil, fmt.Errorf("invalid alertmanager featuresset: %v", err) + } + am.pipelineBuilder = notify.NewPipelineBuilder(am.registry, features) // Run the silences maintenance in a dedicated goroutine. am.wg.Add(1) @@ -378,13 +387,14 @@ func (am *Alertmanager) ApplyConfig(userID string, conf *config.Config, rawCfg s for _, ti := range conf.TimeIntervals { timeIntervals[ti.Name] = ti.TimeIntervals } + intervener := timeinterval.NewIntervener(timeIntervals) pipeline := am.pipelineBuilder.New( integrationsMap, waitFunc, am.inhibitor, silence.NewSilencer(am.silences, am.marker, am.logger), - timeIntervals, + intervener, am.nflog, am.state, ) @@ -474,7 +484,7 @@ func buildReceiverIntegrations(nc config.Receiver, tmpl *template.Template, fire return } n = wrapper(name, n) - integrations = append(integrations, notify.NewIntegration(n, rs, name, i)) + integrations = append(integrations, notify.NewIntegration(n, rs, name, i, nc.Name)) } ) diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/alert_group.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/alert_group.go index c943e683303..fbb5e283b14 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/alert_group.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/alert_group.go @@ -163,6 +163,11 @@ func (m *AlertGroup) contextValidateAlerts(ctx context.Context, formats strfmt.R for i := 0; i < len(m.Alerts); i++ { if m.Alerts[i] != nil { + + if swag.IsZero(m.Alerts[i]) { // not required + return nil + } + if err := m.Alerts[i].ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("alerts" + "." + strconv.Itoa(i)) @@ -195,6 +200,7 @@ func (m *AlertGroup) contextValidateLabels(ctx context.Context, formats strfmt.R func (m *AlertGroup) contextValidateReceiver(ctx context.Context, formats strfmt.Registry) error { if m.Receiver != nil { + if err := m.Receiver.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("receiver") diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/alert_groups.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/alert_groups.go index 31ccb2172b9..338b22127a7 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/alert_groups.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/alert_groups.go @@ -68,6 +68,11 @@ func (m AlertGroups) ContextValidate(ctx context.Context, formats strfmt.Registr for i := 0; i < len(m); i++ { if m[i] != nil { + + if swag.IsZero(m[i]) { // not required + return nil + } + if err := m[i].ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName(strconv.Itoa(i)) diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/alertmanager_status.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/alertmanager_status.go index 0d5370edfb3..2ab11ec4617 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/alertmanager_status.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/alertmanager_status.go @@ -175,6 +175,7 @@ func (m *AlertmanagerStatus) ContextValidate(ctx context.Context, formats strfmt func (m *AlertmanagerStatus) contextValidateCluster(ctx context.Context, formats strfmt.Registry) error { if m.Cluster != nil { + if err := m.Cluster.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("cluster") @@ -191,6 +192,7 @@ func (m *AlertmanagerStatus) contextValidateCluster(ctx context.Context, formats func (m *AlertmanagerStatus) contextValidateConfig(ctx context.Context, formats strfmt.Registry) error { if m.Config != nil { + if err := m.Config.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("config") @@ -207,6 +209,7 @@ func (m *AlertmanagerStatus) contextValidateConfig(ctx context.Context, formats func (m *AlertmanagerStatus) contextValidateVersionInfo(ctx context.Context, formats strfmt.Registry) error { if m.VersionInfo != nil { + if err := m.VersionInfo.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("versionInfo") diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/cluster_status.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/cluster_status.go index 0078320f15c..f470bc010f2 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/cluster_status.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/cluster_status.go @@ -156,6 +156,11 @@ func (m *ClusterStatus) contextValidatePeers(ctx context.Context, formats strfmt for i := 0; i < len(m.Peers); i++ { if m.Peers[i] != nil { + + if swag.IsZero(m.Peers[i]) { // not required + return nil + } + if err := m.Peers[i].ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("peers" + "." + strconv.Itoa(i)) diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_alert.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_alert.go index f7db3321c1f..195bb53764e 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_alert.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_alert.go @@ -366,6 +366,11 @@ func (m *GettableAlert) contextValidateReceivers(ctx context.Context, formats st for i := 0; i < len(m.Receivers); i++ { if m.Receivers[i] != nil { + + if swag.IsZero(m.Receivers[i]) { // not required + return nil + } + if err := m.Receivers[i].ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("receivers" + "." + strconv.Itoa(i)) @@ -384,6 +389,7 @@ func (m *GettableAlert) contextValidateReceivers(ctx context.Context, formats st func (m *GettableAlert) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { if m.Status != nil { + if err := m.Status.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("status") diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_alerts.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_alerts.go index 4efe8cd5ec8..db78dcc4716 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_alerts.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_alerts.go @@ -68,6 +68,11 @@ func (m GettableAlerts) ContextValidate(ctx context.Context, formats strfmt.Regi for i := 0; i < len(m); i++ { if m[i] != nil { + + if swag.IsZero(m[i]) { // not required + return nil + } + if err := m[i].ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName(strconv.Itoa(i)) diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_silence.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_silence.go index fe9d178d7f7..9d60f6cad0f 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_silence.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_silence.go @@ -202,6 +202,7 @@ func (m *GettableSilence) ContextValidate(ctx context.Context, formats strfmt.Re func (m *GettableSilence) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { if m.Status != nil { + if err := m.Status.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("status") diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_silences.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_silences.go index cda5ef6497a..fed9d0b8868 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_silences.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/gettable_silences.go @@ -68,6 +68,11 @@ func (m GettableSilences) ContextValidate(ctx context.Context, formats strfmt.Re for i := 0; i < len(m); i++ { if m[i] != nil { + + if swag.IsZero(m[i]) { // not required + return nil + } + if err := m[i].ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName(strconv.Itoa(i)) diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/matchers.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/matchers.go index 4e2061872e4..fbff9875eb1 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/matchers.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/matchers.go @@ -75,6 +75,11 @@ func (m Matchers) ContextValidate(ctx context.Context, formats strfmt.Registry) for i := 0; i < len(m); i++ { if m[i] != nil { + + if swag.IsZero(m[i]) { // not required + return nil + } + if err := m[i].ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName(strconv.Itoa(i)) diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/postable_alert.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/postable_alert.go index dcec7f0a19e..105b8b30cd8 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/postable_alert.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/postable_alert.go @@ -203,6 +203,10 @@ func (m *PostableAlert) ContextValidate(ctx context.Context, formats strfmt.Regi func (m *PostableAlert) contextValidateAnnotations(ctx context.Context, formats strfmt.Registry) error { + if swag.IsZero(m.Annotations) { // not required + return nil + } + if err := m.Annotations.ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName("annotations") diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/models/postable_alerts.go b/vendor/github.com/prometheus/alertmanager/api/v2/models/postable_alerts.go index ed4d7fb9bab..3df968820db 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/models/postable_alerts.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/models/postable_alerts.go @@ -68,6 +68,11 @@ func (m PostableAlerts) ContextValidate(ctx context.Context, formats strfmt.Regi for i := 0; i < len(m); i++ { if m[i] != nil { + + if swag.IsZero(m[i]) { // not required + return nil + } + if err := m[i].ContextValidate(ctx, formats); err != nil { if ve, ok := err.(*errors.Validation); ok { return ve.ValidateName(strconv.Itoa(i)) diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/restapi/operations/alertmanager_api.go b/vendor/github.com/prometheus/alertmanager/api/v2/restapi/operations/alertmanager_api.go index 8cbd9a6efb0..e28c76b32ef 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/restapi/operations/alertmanager_api.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/restapi/operations/alertmanager_api.go @@ -412,6 +412,6 @@ func (o *AlertmanagerAPI) AddMiddlewareFor(method, path string, builder middlewa } o.Init() if h, ok := o.handlers[um][path]; ok { - o.handlers[method][path] = builder(h) + o.handlers[um][path] = builder(h) } } diff --git a/vendor/github.com/prometheus/alertmanager/config/config.go b/vendor/github.com/prometheus/alertmanager/config/config.go index ae5f786ee8d..bf236aefe03 100644 --- a/vendor/github.com/prometheus/alertmanager/config/config.go +++ b/vendor/github.com/prometheus/alertmanager/config/config.go @@ -907,7 +907,7 @@ type Receiver struct { SNSConfigs []*SNSConfig `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"` TelegramConfigs []*TelegramConfig `yaml:"telegram_configs,omitempty" json:"telegram_configs,omitempty"` WebexConfigs []*WebexConfig `yaml:"webex_configs,omitempty" json:"webex_configs,omitempty"` - MSTeamsConfigs []*MSTeamsConfig `yaml:"msteams_configs,omitempty" json:"teams_configs,omitempty"` + MSTeamsConfigs []*MSTeamsConfig `yaml:"msteams_configs,omitempty" json:"msteams_configs,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for Receiver. diff --git a/vendor/github.com/prometheus/alertmanager/config/notifiers.go b/vendor/github.com/prometheus/alertmanager/config/notifiers.go index 0da9e27ba22..2650db5f3b5 100644 --- a/vendor/github.com/prometheus/alertmanager/config/notifiers.go +++ b/vendor/github.com/prometheus/alertmanager/config/notifiers.go @@ -503,11 +503,6 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { if c.URL != nil && c.URLFile != "" { return fmt.Errorf("at most one of url & url_file must be configured") } - if c.URL != nil { - if c.URL.Scheme != "https" && c.URL.Scheme != "http" { - return fmt.Errorf("scheme required for webhook url") - } - } return nil } @@ -694,6 +689,7 @@ type PushoverConfig struct { Priority string `yaml:"priority,omitempty" json:"priority,omitempty"` Retry duration `yaml:"retry,omitempty" json:"retry,omitempty"` Expire duration `yaml:"expire,omitempty" json:"expire,omitempty"` + TTL duration `yaml:"ttl,omitempty" json:"ttl,omitempty"` HTML bool `yaml:"html" json:"html,omitempty"` } diff --git a/vendor/github.com/prometheus/alertmanager/dispatch/route.go b/vendor/github.com/prometheus/alertmanager/dispatch/route.go index 4b3673c5310..5ada178dabd 100644 --- a/vendor/github.com/prometheus/alertmanager/dispatch/route.go +++ b/vendor/github.com/prometheus/alertmanager/dispatch/route.go @@ -180,6 +180,29 @@ func (r *Route) Key() string { return b.String() } +// ID returns a unique identifier for the route. +func (r *Route) ID() string { + b := strings.Builder{} + + position := -1 + if r.parent != nil { + // Find the position in the same level leaf. + for i, cr := range r.parent.Routes { + if cr == r { + position = i + break + } + } + } + b.WriteString(r.Key()) + + if position > -1 { + b.WriteRune('/') + b.WriteString(fmt.Sprint(position)) + } + return b.String() +} + // Walk traverses the route tree in depth-first order. func (r *Route) Walk(visit func(*Route)) { visit(r) diff --git a/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go b/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go new file mode 100644 index 00000000000..d48af09ad5f --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go @@ -0,0 +1,123 @@ +// Copyright 2023 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package featurecontrol + +import ( + "errors" + "fmt" + "strings" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" +) + +const ( + FeatureReceiverNameInMetrics = "receiver-name-in-metrics" + FeatureClassicMode = "classic-mode" + FeatureUTF8Mode = "utf8-mode" +) + +var AllowedFlags = []string{ + FeatureReceiverNameInMetrics, + FeatureClassicMode, + FeatureUTF8Mode, +} + +type Flagger interface { + EnableReceiverNamesInMetrics() bool + ClassicMode() bool + UTF8Mode() bool +} + +type Flags struct { + logger log.Logger + enableReceiverNamesInMetrics bool + classicMode bool + utf8Mode bool +} + +func (f *Flags) EnableReceiverNamesInMetrics() bool { + return f.enableReceiverNamesInMetrics +} + +func (f *Flags) ClassicMode() bool { + return f.classicMode +} + +func (f *Flags) UTF8Mode() bool { + return f.utf8Mode +} + +type flagOption func(flags *Flags) + +func enableReceiverNameInMetrics() flagOption { + return func(configs *Flags) { + configs.enableReceiverNamesInMetrics = true + } +} + +func enableClassicMode() flagOption { + return func(configs *Flags) { + configs.classicMode = true + } +} + +func enableUTF8Mode() flagOption { + return func(configs *Flags) { + configs.utf8Mode = true + } +} + +func NewFlags(logger log.Logger, features string) (Flagger, error) { + fc := &Flags{logger: logger} + opts := []flagOption{} + + if len(features) == 0 { + return NoopFlags{}, nil + } + + for _, feature := range strings.Split(features, ",") { + switch feature { + case FeatureReceiverNameInMetrics: + opts = append(opts, enableReceiverNameInMetrics()) + level.Warn(logger).Log("msg", "Experimental receiver name in metrics enabled") + case FeatureClassicMode: + opts = append(opts, enableClassicMode()) + level.Warn(logger).Log("msg", "Classic mode enabled") + case FeatureUTF8Mode: + opts = append(opts, enableUTF8Mode()) + level.Warn(logger).Log("msg", "UTF-8 mode enabled") + default: + return nil, fmt.Errorf("Unknown option '%s' for --enable-feature", feature) + } + } + + for _, opt := range opts { + opt(fc) + } + + if fc.classicMode && fc.utf8Mode { + return nil, errors.New("cannot have both classic and UTF-8 modes enabled") + } + + return fc, nil +} + +type NoopFlags struct{} + +func (n NoopFlags) EnableReceiverNamesInMetrics() bool { return false } + +func (n NoopFlags) ClassicMode() bool { return false } + +func (n NoopFlags) UTF8Mode() bool { return false } diff --git a/vendor/github.com/prometheus/alertmanager/notify/discord/discord.go b/vendor/github.com/prometheus/alertmanager/notify/discord/discord.go index 31a0a7cfe77..2fad87a9383 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/discord/discord.go +++ b/vendor/github.com/prometheus/alertmanager/notify/discord/discord.go @@ -30,6 +30,13 @@ import ( "github.com/prometheus/alertmanager/types" ) +const ( + // https://discord.com/developers/docs/resources/channel#embed-object-embed-limits - 256 characters or runes. + maxTitleLenRunes = 256 + // https://discord.com/developers/docs/resources/channel#embed-object-embed-limits - 4096 characters or runes. + maxDescriptionLenRunes = 4096 +) + const ( colorRed = 0x992D22 colorGreen = 0x2ECC71 @@ -90,14 +97,20 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) return false, err } - title := tmpl(n.conf.Title) + title, truncated := notify.TruncateInRunes(tmpl(n.conf.Title), maxTitleLenRunes) if err != nil { return false, err } - description := tmpl(n.conf.Message) + if truncated { + level.Warn(n.logger).Log("msg", "Truncated title", "key", key, "max_runes", maxTitleLenRunes) + } + description, truncated := notify.TruncateInRunes(tmpl(n.conf.Message), maxDescriptionLenRunes) if err != nil { return false, err } + if truncated { + level.Warn(n.logger).Log("msg", "Truncated message", "key", key, "max_runes", maxDescriptionLenRunes) + } color := colorGrey if alerts.Status() == model.AlertFiring { diff --git a/vendor/github.com/prometheus/alertmanager/notify/notify.go b/vendor/github.com/prometheus/alertmanager/notify/notify.go index 0364a049095..33d499af30c 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/notify.go +++ b/vendor/github.com/prometheus/alertmanager/notify/notify.go @@ -28,6 +28,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" + "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/alertmanager/inhibit" "github.com/prometheus/alertmanager/nflog" "github.com/prometheus/alertmanager/nflog/nflogpb" @@ -61,19 +62,21 @@ type Notifier interface { // Integration wraps a notifier and its configuration to be uniquely identified // by name and index from its origin in the configuration. type Integration struct { - notifier Notifier - rs ResolvedSender - name string - idx int + notifier Notifier + rs ResolvedSender + name string + idx int + receiverName string } // NewIntegration returns a new integration. -func NewIntegration(notifier Notifier, rs ResolvedSender, name string, idx int) Integration { +func NewIntegration(notifier Notifier, rs ResolvedSender, name string, idx int, receiverName string) Integration { return Integration{ - notifier: notifier, - rs: rs, - name: name, - idx: idx, + notifier: notifier, + rs: rs, + name: name, + idx: idx, + receiverName: receiverName, } } @@ -249,40 +252,86 @@ type Metrics struct { numNotificationRequestsTotal *prometheus.CounterVec numNotificationRequestsFailedTotal *prometheus.CounterVec notificationLatencySeconds *prometheus.HistogramVec + + ff featurecontrol.Flagger } -func NewMetrics(r prometheus.Registerer) *Metrics { +func NewMetrics(r prometheus.Registerer, ff featurecontrol.Flagger) *Metrics { + labels := []string{"integration"} + + if ff.EnableReceiverNamesInMetrics() { + labels = append(labels, "receiver_name") + } + m := &Metrics{ numNotifications: prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "alertmanager", Name: "notifications_total", Help: "The total number of attempted notifications.", - }, []string{"integration"}), + }, labels), numTotalFailedNotifications: prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "alertmanager", Name: "notifications_failed_total", Help: "The total number of failed notifications.", - }, []string{"integration", "reason"}), + }, append(labels, "reason")), numNotificationRequestsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "alertmanager", Name: "notification_requests_total", Help: "The total number of attempted notification requests.", - }, []string{"integration"}), + }, labels), numNotificationRequestsFailedTotal: prometheus.NewCounterVec(prometheus.CounterOpts{ Namespace: "alertmanager", Name: "notification_requests_failed_total", Help: "The total number of failed notification requests.", - }, []string{"integration"}), + }, labels), notificationLatencySeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{ Namespace: "alertmanager", Name: "notification_latency_seconds", Help: "The latency of notifications in seconds.", Buckets: []float64{1, 5, 10, 15, 20}, - }, []string{"integration"}), + }, labels), + ff: ff, + } + + r.MustRegister( + m.numNotifications, m.numTotalFailedNotifications, + m.numNotificationRequestsTotal, m.numNotificationRequestsFailedTotal, + m.notificationLatencySeconds, + ) + + return m +} + +func (m *Metrics) InitializeFor(receiver map[string][]Integration) { + if m.ff.EnableReceiverNamesInMetrics() { + + // Reset the vectors to take into account receiver names changing after hot reloads. + m.numNotifications.Reset() + m.numNotificationRequestsTotal.Reset() + m.numNotificationRequestsFailedTotal.Reset() + m.notificationLatencySeconds.Reset() + m.numTotalFailedNotifications.Reset() + + for name, integrations := range receiver { + for _, integration := range integrations { + + m.numNotifications.WithLabelValues(integration.Name(), name) + m.numNotificationRequestsTotal.WithLabelValues(integration.Name(), name) + m.numNotificationRequestsFailedTotal.WithLabelValues(integration.Name(), name) + m.notificationLatencySeconds.WithLabelValues(integration.Name(), name) + + for _, reason := range possibleFailureReasonCategory { + m.numTotalFailedNotifications.WithLabelValues(integration.Name(), name, reason) + } + } + } + + return } + + // When the feature flag is not enabled, we just carry on registering _all_ the integrations. for _, integration := range []string{ "email", - "msteams", "pagerduty", "wechat", "pushover", @@ -292,6 +341,9 @@ func NewMetrics(r prometheus.Registerer) *Metrics { "victorops", "sns", "telegram", + "discord", + "webex", + "msteams", } { m.numNotifications.WithLabelValues(integration) m.numNotificationRequestsTotal.WithLabelValues(integration) @@ -302,21 +354,17 @@ func NewMetrics(r prometheus.Registerer) *Metrics { m.numTotalFailedNotifications.WithLabelValues(integration, reason) } } - r.MustRegister( - m.numNotifications, m.numTotalFailedNotifications, - m.numNotificationRequestsTotal, m.numNotificationRequestsFailedTotal, - m.notificationLatencySeconds, - ) - return m } type PipelineBuilder struct { metrics *Metrics + ff featurecontrol.Flagger } -func NewPipelineBuilder(r prometheus.Registerer) *PipelineBuilder { +func NewPipelineBuilder(r prometheus.Registerer, ff featurecontrol.Flagger) *PipelineBuilder { return &PipelineBuilder{ - metrics: NewMetrics(r), + metrics: NewMetrics(r, ff), + ff: ff, } } @@ -326,7 +374,7 @@ func (pb *PipelineBuilder) New( wait func() time.Duration, inhibitor *inhibit.Inhibitor, silencer *silence.Silencer, - times map[string][]timeinterval.TimeInterval, + intervener *timeinterval.Intervener, notificationLog NotificationLog, peer Peer, ) RoutingStage { @@ -334,14 +382,17 @@ func (pb *PipelineBuilder) New( ms := NewGossipSettleStage(peer) is := NewMuteStage(inhibitor) - tas := NewTimeActiveStage(times) - tms := NewTimeMuteStage(times) + tas := NewTimeActiveStage(intervener) + tms := NewTimeMuteStage(intervener) ss := NewMuteStage(silencer) for name := range receivers { st := createReceiverStage(name, receivers[name], wait, notificationLog, pb.metrics) rs[name] = MultiStage{ms, is, tas, tms, ss, st} } + + pb.metrics.InitializeFor(receivers) + return rs } @@ -467,16 +518,24 @@ func NewMuteStage(m types.Muter) *MuteStage { } // Exec implements the Stage interface. -func (n *MuteStage) Exec(ctx context.Context, _ log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { - var filtered []*types.Alert +func (n *MuteStage) Exec(ctx context.Context, logger log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { + var ( + filtered []*types.Alert + muted []*types.Alert + ) for _, a := range alerts { // TODO(fabxc): increment total alerts counter. // Do not send the alert if muted. - if !n.muter.Mutes(a.Labels) { + if n.muter.Mutes(a.Labels) { + muted = append(muted, a) + } else { filtered = append(filtered, a) } // TODO(fabxc): increment muted alerts counter if muted. } + if len(muted) > 0 { + level.Debug(logger).Log("msg", "Notifications will not be sent for muted alerts", "alerts", fmt.Sprintf("%v", muted)) + } return ctx, filtered, nil } @@ -652,19 +711,27 @@ type RetryStage struct { integration Integration groupName string metrics *Metrics + labelValues []string } // NewRetryStage returns a new instance of a RetryStage. func NewRetryStage(i Integration, groupName string, metrics *Metrics) *RetryStage { + labelValues := []string{i.Name()} + + if metrics.ff.EnableReceiverNamesInMetrics() { + labelValues = append(labelValues, i.receiverName) + } + return &RetryStage{ integration: i, groupName: groupName, metrics: metrics, + labelValues: labelValues, } } func (r RetryStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) { - r.metrics.numNotifications.WithLabelValues(r.integration.Name()).Inc() + r.metrics.numNotifications.WithLabelValues(r.labelValues...).Inc() ctx, alerts, err := r.exec(ctx, l, alerts...) failureReason := DefaultReason.String() @@ -672,7 +739,7 @@ func (r RetryStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Ale if e, ok := errors.Cause(err).(*ErrorWithReason); ok { failureReason = e.Reason.String() } - r.metrics.numTotalFailedNotifications.WithLabelValues(r.integration.Name(), failureReason).Inc() + r.metrics.numTotalFailedNotifications.WithLabelValues(append(r.labelValues, failureReason)...).Inc() } return ctx, alerts, err } @@ -733,10 +800,11 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale case <-tick.C: now := time.Now() retry, err := r.integration.Notify(ctx, sent...) - r.metrics.notificationLatencySeconds.WithLabelValues(r.integration.Name()).Observe(time.Since(now).Seconds()) - r.metrics.numNotificationRequestsTotal.WithLabelValues(r.integration.Name()).Inc() + dur := time.Since(now) + r.metrics.notificationLatencySeconds.WithLabelValues(r.labelValues...).Observe(dur.Seconds()) + r.metrics.numNotificationRequestsTotal.WithLabelValues(r.labelValues...).Inc() if err != nil { - r.metrics.numNotificationRequestsFailedTotal.WithLabelValues(r.integration.Name()).Inc() + r.metrics.numNotificationRequestsFailedTotal.WithLabelValues(r.labelValues...).Inc() if !retry { return ctx, alerts, errors.Wrapf(err, "%s/%s: notify retry canceled due to unrecoverable error after %d attempts", r.groupName, r.integration.String(), i) } @@ -754,7 +822,7 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale lvl = level.Debug(log.With(l, "alerts", fmt.Sprintf("%v", alerts))) } - lvl.Log("msg", "Notify success", "attempts", i) + lvl.Log("msg", "Notify success", "attempts", i, "duration", dur) return ctx, alerts, nil } case <-ctx.Done(): @@ -809,13 +877,13 @@ func (n SetNotifiesStage) Exec(ctx context.Context, l log.Logger, alerts ...*typ } type timeStage struct { - Times map[string][]timeinterval.TimeInterval + muter types.TimeMuter } type TimeMuteStage timeStage -func NewTimeMuteStage(ti map[string][]timeinterval.TimeInterval) *TimeMuteStage { - return &TimeMuteStage{ti} +func NewTimeMuteStage(m types.TimeMuter) *TimeMuteStage { + return &TimeMuteStage{m} } // Exec implements the stage interface for TimeMuteStage. @@ -830,7 +898,12 @@ func (tms TimeMuteStage) Exec(ctx context.Context, l log.Logger, alerts ...*type return ctx, alerts, errors.New("missing now timestamp") } - muted, err := inTimeIntervals(now, tms.Times, muteTimeIntervalNames) + // Skip this stage if there are no mute timings. + if len(muteTimeIntervalNames) == 0 { + return ctx, alerts, nil + } + + muted, err := tms.muter.Mutes(muteTimeIntervalNames, now) if err != nil { return ctx, alerts, err } @@ -845,8 +918,8 @@ func (tms TimeMuteStage) Exec(ctx context.Context, l log.Logger, alerts ...*type type TimeActiveStage timeStage -func NewTimeActiveStage(ti map[string][]timeinterval.TimeInterval) *TimeActiveStage { - return &TimeActiveStage{ti} +func NewTimeActiveStage(m types.TimeMuter) *TimeActiveStage { + return &TimeActiveStage{m} } // Exec implements the stage interface for TimeActiveStage. @@ -867,32 +940,16 @@ func (tas TimeActiveStage) Exec(ctx context.Context, l log.Logger, alerts ...*ty return ctx, alerts, errors.New("missing now timestamp") } - active, err := inTimeIntervals(now, tas.Times, activeTimeIntervalNames) + muted, err := tas.muter.Mutes(activeTimeIntervalNames, now) if err != nil { return ctx, alerts, err } // If the current time is not inside an active time, all alerts are removed from the pipeline - if !active { + if !muted { level.Debug(l).Log("msg", "Notifications not sent, route is not within active time") return ctx, nil, nil } return ctx, alerts, nil } - -// inTimeIntervals returns true if the current time is contained in one of the given time intervals. -func inTimeIntervals(now time.Time, intervals map[string][]timeinterval.TimeInterval, intervalNames []string) (bool, error) { - for _, name := range intervalNames { - interval, ok := intervals[name] - if !ok { - return false, errors.Errorf("time interval %s doesn't exist in config", name) - } - for _, ti := range interval { - if ti.ContainsTime(now.UTC()) { - return true, nil - } - } - } - return false, nil -} diff --git a/vendor/github.com/prometheus/alertmanager/notify/pushover/pushover.go b/vendor/github.com/prometheus/alertmanager/notify/pushover/pushover.go index 9964270a5e0..a8f4644f65b 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/pushover/pushover.go +++ b/vendor/github.com/prometheus/alertmanager/notify/pushover/pushover.go @@ -147,6 +147,12 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) parameters.Add("expire", fmt.Sprintf("%d", int64(time.Duration(n.conf.Expire).Seconds()))) parameters.Add("device", tmpl(n.conf.Device)) parameters.Add("sound", tmpl(n.conf.Sound)) + + newttl := int64(time.Duration(n.conf.TTL).Seconds()) + if newttl > 0 { + parameters.Add("ttl", fmt.Sprintf("%d", newttl)) + } + if err != nil { return false, err } diff --git a/vendor/github.com/prometheus/alertmanager/pkg/labels/matcher.go b/vendor/github.com/prometheus/alertmanager/pkg/labels/matcher.go index 445c9056515..f37fcb2173f 100644 --- a/vendor/github.com/prometheus/alertmanager/pkg/labels/matcher.go +++ b/vendor/github.com/prometheus/alertmanager/pkg/labels/matcher.go @@ -18,7 +18,9 @@ import ( "encoding/json" "fmt" "regexp" + "strconv" "strings" + "unicode" "github.com/prometheus/common/model" ) @@ -74,6 +76,9 @@ func NewMatcher(t MatchType, n, v string) (*Matcher, error) { } func (m *Matcher) String() string { + if strings.ContainsFunc(m.Name, isReserved) { + return fmt.Sprintf(`%s%s%s`, strconv.Quote(m.Name), m.Type, strconv.Quote(m.Value)) + } return fmt.Sprintf(`%s%s"%s"`, m.Name, m.Type, openMetricsEscape(m.Value)) } @@ -199,3 +204,13 @@ func (ms Matchers) String() string { return buf.String() } + +// This is copied from matchers/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 +// around the label name, so we use this function as a heuristic to tell if +// the matcher was parsed with the UTF-8 parser or the classic parser. +func isReserved(r rune) bool { + return unicode.IsSpace(r) || strings.ContainsRune("{}!=~,\\\"'`", r) +} diff --git a/vendor/github.com/prometheus/alertmanager/timeinterval/timeinterval.go b/vendor/github.com/prometheus/alertmanager/timeinterval/timeinterval.go index a5018aaef97..fe8c97d7294 100644 --- a/vendor/github.com/prometheus/alertmanager/timeinterval/timeinterval.go +++ b/vendor/github.com/prometheus/alertmanager/timeinterval/timeinterval.go @@ -27,6 +27,35 @@ import ( "gopkg.in/yaml.v2" ) +// Intervener determines whether a given time and active route time interval should mute outgoing notifications. +// It implements the TimeMuter interface. +type Intervener struct { + intervals map[string][]TimeInterval +} + +func (i *Intervener) Mutes(names []string, now time.Time) (bool, error) { + for _, name := range names { + interval, ok := i.intervals[name] + if !ok { + return false, fmt.Errorf("time interval %s doesn't exist in config", name) + } + + for _, ti := range interval { + if ti.ContainsTime(now.UTC()) { + return true, nil + } + } + } + + return false, nil +} + +func NewIntervener(ti map[string][]TimeInterval) *Intervener { + return &Intervener{ + intervals: ti, + } +} + // TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained // within the interval. type TimeInterval struct { @@ -436,9 +465,6 @@ func (ir InclusiveRange) MarshalYAML() (interface{}, error) { return string(bytes), err } -// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals. -const TimeLayout = "15:04" - var ( validTime = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)" validTimeRE = regexp.MustCompile(validTime) diff --git a/vendor/github.com/prometheus/alertmanager/types/types.go b/vendor/github.com/prometheus/alertmanager/types/types.go index f3101f8234c..b427a3d1d3f 100644 --- a/vendor/github.com/prometheus/alertmanager/types/types.go +++ b/vendor/github.com/prometheus/alertmanager/types/types.go @@ -381,6 +381,11 @@ type Muter interface { Mutes(model.LabelSet) bool } +// TimeMuter determines if alerts should be muted based on the specified current time and active time interval on the route. +type TimeMuter interface { + Mutes(timeIntervalName []string, now time.Time) (bool, error) +} + // A MuteFunc is a function that implements the Muter interface. type MuteFunc func(model.LabelSet) bool diff --git a/vendor/github.com/prometheus/alertmanager/ui/web.go b/vendor/github.com/prometheus/alertmanager/ui/web.go index 4d41a307329..38206259258 100644 --- a/vendor/github.com/prometheus/alertmanager/ui/web.go +++ b/vendor/github.com/prometheus/alertmanager/ui/web.go @@ -62,7 +62,7 @@ func Register(r *route.Router, reloadCh chan<- chan error, logger log.Logger) { fs.ServeHTTP(w, req) }) - r.Post("/-/reload", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + r.Post("/-/reload", func(w http.ResponseWriter, req *http.Request) { errc := make(chan error) defer close(errc) @@ -70,22 +70,22 @@ func Register(r *route.Router, reloadCh chan<- chan error, logger log.Logger) { if err := <-errc; err != nil { http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError) } - })) + }) - r.Get("/-/healthy", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + r.Get("/-/healthy", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "OK") - })) - r.Head("/-/healthy", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + }) + r.Head("/-/healthy", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - })) - r.Get("/-/ready", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + }) + r.Get("/-/ready", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "OK") - })) - r.Head("/-/ready", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + }) + r.Head("/-/ready", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - })) + }) r.Get("/debug/*subpath", http.DefaultServeMux.ServeHTTP) r.Post("/debug/*subpath", http.DefaultServeMux.ServeHTTP) diff --git a/vendor/github.com/rs/cors/README.md b/vendor/github.com/rs/cors/README.md index 0ad3e94e1ba..fe12fa671d1 100644 --- a/vendor/github.com/rs/cors/README.md +++ b/vendor/github.com/rs/cors/README.md @@ -1,4 +1,4 @@ -# Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![build](https://img.shields.io/travis/rs/cors.svg?style=flat)](https://travis-ci.org/rs/cors) [![Coverage](http://gocover.io/_badge/github.com/rs/cors)](http://gocover.io/github.com/rs/cors) +# Go CORS handler [![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/cors) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/cors/master/LICENSE) [![Go Coverage](https://github.com/rs/cors/wiki/coverage.svg)](https://raw.githack.com/wiki/rs/cors/coverage.html) CORS is a `net/http` handler implementing [Cross Origin Resource Sharing W3 specification](http://www.w3.org/TR/cors/) in Golang. @@ -102,15 +102,18 @@ See [API documentation](http://godoc.org/github.com/rs/cors) for more info. ## Benchmarks - BenchmarkWithout 20000000 64.6 ns/op 8 B/op 1 allocs/op - BenchmarkDefault 3000000 469 ns/op 114 B/op 2 allocs/op - BenchmarkAllowedOrigin 3000000 608 ns/op 114 B/op 2 allocs/op - BenchmarkPreflight 20000000 73.2 ns/op 0 B/op 0 allocs/op - BenchmarkPreflightHeader 20000000 73.6 ns/op 0 B/op 0 allocs/op - BenchmarkParseHeaderList 2000000 847 ns/op 184 B/op 6 allocs/op - BenchmarkParse…Single 5000000 290 ns/op 32 B/op 3 allocs/op - BenchmarkParse…Normalized 2000000 776 ns/op 160 B/op 6 allocs/op - +goos: darwin +goarch: arm64 +pkg: github.com/rs/cors +BenchmarkWithout-10 352671961 3.317 ns/op 0 B/op 0 allocs/op +BenchmarkDefault-10 18261723 65.63 ns/op 0 B/op 0 allocs/op +BenchmarkAllowedOrigin-10 13309591 90.21 ns/op 0 B/op 0 allocs/op +BenchmarkPreflight-10 7247728 166.9 ns/op 0 B/op 0 allocs/op +BenchmarkPreflightHeader-10 5915437 202.9 ns/op 0 B/op 0 allocs/op +BenchmarkWildcard/match-10 250336476 4.784 ns/op 0 B/op 0 allocs/op +BenchmarkWildcard/too_short-10 1000000000 0.6354 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/rs/cors 9.613s ## Licenses All source code is licensed under the [MIT License](https://raw.github.com/rs/cors/master/LICENSE). diff --git a/vendor/github.com/rs/cors/cors.go b/vendor/github.com/rs/cors/cors.go index 5669a67f891..69f89d8f94c 100644 --- a/vendor/github.com/rs/cors/cors.go +++ b/vendor/github.com/rs/cors/cors.go @@ -4,15 +4,15 @@ as defined by http://www.w3.org/TR/cors/ You can configure it by passing an option struct to cors.New: - c := cors.New(cors.Options{ - AllowedOrigins: []string{"foo.com"}, - AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, - AllowCredentials: true, - }) + c := cors.New(cors.Options{ + AllowedOrigins: []string{"foo.com"}, + AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodDelete}, + AllowCredentials: true, + }) Then insert the handler in the chain: - handler = c.Handler(handler) + handler = c.Handler(handler) See Options documentation for more options. @@ -28,6 +28,10 @@ import ( "strings" ) +var headerVaryOrigin = []string{"Origin"} +var headerOriginAll = []string{"*"} +var headerTrue = []string{"true"} + // Options is a configuration container to setup the CORS middleware. type Options struct { // AllowedOrigins is a list of origins a cross-domain request can be executed from. @@ -37,27 +41,39 @@ type Options struct { // Only one wildcard can be used per origin. // Default value is ["*"] AllowedOrigins []string - // AllowOriginFunc is a custom function to validate the origin. It take the origin - // as argument and returns true if allowed or false otherwise. If this option is - // set, the content of AllowedOrigins is ignored. + // AllowOriginFunc is a custom function to validate the origin. It take the + // origin as argument and returns true if allowed or false otherwise. If + // this option is set, the content of `AllowedOrigins` is ignored. AllowOriginFunc func(origin string) bool - // AllowOriginRequestFunc is a custom function to validate the origin. It takes the HTTP Request object and the origin as - // argument and returns true if allowed or false otherwise. If this option is set, the content of `AllowedOrigins` - // and `AllowOriginFunc` is ignored. + // AllowOriginRequestFunc is a custom function to validate the origin. It + // takes the HTTP Request object and the origin as argument and returns true + // if allowed or false otherwise. If headers are used take the decision, + // consider using AllowOriginVaryRequestFunc instead. If this option is set, + // the content of `AllowedOrigins`, `AllowOriginFunc` are ignored. AllowOriginRequestFunc func(r *http.Request, origin string) bool + // AllowOriginVaryRequestFunc is a custom function to validate the origin. + // It takes the HTTP Request object and the origin as argument and returns + // true if allowed or false otherwise with a list of headers used to take + // that decision if any so they can be added to the Vary header. If this + // option is set, the content of `AllowedOrigins`, `AllowOriginFunc` and + // `AllowOriginRequestFunc` are ignored. + AllowOriginVaryRequestFunc func(r *http.Request, origin string) (bool, []string) // AllowedMethods is a list of methods the client is allowed to use with // cross-domain requests. Default value is simple methods (HEAD, GET and POST). AllowedMethods []string // AllowedHeaders is list of non simple headers the client is allowed to use with // cross-domain requests. // If the special "*" value is present in the list, all headers will be allowed. - // Default value is [] but "Origin" is always appended to the list. + // Default value is []. AllowedHeaders []string // ExposedHeaders indicates which headers are safe to expose to the API of a CORS // API specification ExposedHeaders []string // MaxAge indicates how long (in seconds) the results of a preflight request - // can be cached + // can be cached. Default value is 0, which stands for no + // Access-Control-Max-Age header to be sent back, resulting in browsers + // using their default value (5s by spec). If you need to force a 0 max-age, + // set `MaxAge` to a negative value (ie: -1). MaxAge int // AllowCredentials indicates whether the request can include user credentials like // cookies, HTTP authentication or client side SSL certificates. @@ -73,6 +89,8 @@ type Options struct { OptionsSuccessStatus int // Debugging flag adds additional output to debug server side CORS issues Debug bool + // Adds a custom logger, implies Debug is true + Logger Logger } // Logger generic interface for logger @@ -89,16 +107,15 @@ type Cors struct { // List of allowed origins containing wildcards allowedWOrigins []wildcard // Optional origin validator function - allowOriginFunc func(origin string) bool - // Optional origin validator (with request) function - allowOriginRequestFunc func(r *http.Request, origin string) bool + allowOriginFunc func(r *http.Request, origin string) (bool, []string) // Normalized list of allowed headers allowedHeaders []string // Normalized list of allowed methods allowedMethods []string - // Normalized list of exposed headers + // Pre-computed normalized list of exposed headers exposedHeaders []string - maxAge int + // Pre-computed maxAge header value + maxAge []string // Set to true when allowed origins contains a "*" allowedOriginsAll bool // Set to true when allowed headers contains a "*" @@ -108,30 +125,40 @@ type Cors struct { allowCredentials bool allowPrivateNetwork bool optionPassthrough bool + preflightVary []string } // New creates a new Cors handler with the provided options. func New(options Options) *Cors { c := &Cors{ - exposedHeaders: convert(options.ExposedHeaders, http.CanonicalHeaderKey), - allowOriginFunc: options.AllowOriginFunc, - allowOriginRequestFunc: options.AllowOriginRequestFunc, - allowCredentials: options.AllowCredentials, - allowPrivateNetwork: options.AllowPrivateNetwork, - maxAge: options.MaxAge, - optionPassthrough: options.OptionsPassthrough, + allowCredentials: options.AllowCredentials, + allowPrivateNetwork: options.AllowPrivateNetwork, + optionPassthrough: options.OptionsPassthrough, + Log: options.Logger, } if options.Debug && c.Log == nil { c.Log = log.New(os.Stdout, "[cors] ", log.LstdFlags) } + if options.AllowOriginVaryRequestFunc != nil { + c.allowOriginFunc = options.AllowOriginVaryRequestFunc + } else if options.AllowOriginRequestFunc != nil { + c.allowOriginFunc = func(r *http.Request, origin string) (bool, []string) { + return options.AllowOriginRequestFunc(r, origin), nil + } + } else if options.AllowOriginFunc != nil { + c.allowOriginFunc = func(r *http.Request, origin string) (bool, []string) { + return options.AllowOriginFunc(origin), nil + } + } + // Normalize options - // Note: for origins and methods matching, the spec requires a case-sensitive matching. + // Note: for origins matching, the spec requires a case-sensitive matching. // As it may error prone, we chose to ignore the spec here. // Allowed Origins if len(options.AllowedOrigins) == 0 { - if options.AllowOriginFunc == nil && options.AllowOriginRequestFunc == nil { + if c.allowOriginFunc == nil { // Default is all origins c.allowedOriginsAll = true } @@ -160,10 +187,9 @@ func New(options Options) *Cors { // Allowed Headers if len(options.AllowedHeaders) == 0 { // Use sensible defaults - c.allowedHeaders = []string{"Origin", "Accept", "Content-Type", "X-Requested-With"} + c.allowedHeaders = []string{"Accept", "Content-Type", "X-Requested-With"} } else { - // Origin is always appended as some browsers will always request for this header at preflight - c.allowedHeaders = convert(append(options.AllowedHeaders, "Origin"), http.CanonicalHeaderKey) + c.allowedHeaders = convert(options.AllowedHeaders, http.CanonicalHeaderKey) for _, h := range options.AllowedHeaders { if h == "*" { c.allowedHeadersAll = true @@ -178,7 +204,7 @@ func New(options Options) *Cors { // Default is spec's "simple" methods c.allowedMethods = []string{http.MethodGet, http.MethodPost, http.MethodHead} } else { - c.allowedMethods = convert(options.AllowedMethods, strings.ToUpper) + c.allowedMethods = options.AllowedMethods } // Options Success Status Code @@ -188,6 +214,25 @@ func New(options Options) *Cors { c.optionsSuccessStatus = options.OptionsSuccessStatus } + // Pre-compute exposed headers header value + if len(options.ExposedHeaders) > 0 { + c.exposedHeaders = []string{strings.Join(convert(options.ExposedHeaders, http.CanonicalHeaderKey), ", ")} + } + + // Pre-compute prefight Vary header to save allocations + if c.allowPrivateNetwork { + c.preflightVary = []string{"Origin, Access-Control-Request-Method, Access-Control-Request-Headers, Access-Control-Request-Private-Network"} + } else { + c.preflightVary = []string{"Origin, Access-Control-Request-Method, Access-Control-Request-Headers"} + } + + // Precompute max-age + if options.MaxAge > 0 { + c.maxAge = []string{strconv.Itoa(options.MaxAge)} + } else if options.MaxAge < 0 { + c.maxAge = []string{"0"} + } + return c } @@ -284,18 +329,21 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { // Always set Vary headers // see https://github.com/rs/cors/issues/10, // https://github.com/rs/cors/commit/dbdca4d95feaa7511a46e6f1efb3b3aa505bc43f#commitcomment-12352001 - headers.Add("Vary", "Origin") - headers.Add("Vary", "Access-Control-Request-Method") - headers.Add("Vary", "Access-Control-Request-Headers") - if c.allowPrivateNetwork { - headers.Add("Vary", "Access-Control-Request-Private-Network") + if vary, found := headers["Vary"]; found { + headers["Vary"] = append(vary, c.preflightVary[0]) + } else { + headers["Vary"] = c.preflightVary + } + allowed, additionalVaryHeaders := c.isOriginAllowed(r, origin) + if len(additionalVaryHeaders) > 0 { + headers.Add("Vary", strings.Join(convert(additionalVaryHeaders, http.CanonicalHeaderKey), ", ")) } if origin == "" { c.logf(" Preflight aborted: empty origin") return } - if !c.isOriginAllowed(r, origin) { + if !allowed { c.logf(" Preflight aborted: origin '%s' not allowed", origin) return } @@ -305,40 +353,41 @@ func (c *Cors) handlePreflight(w http.ResponseWriter, r *http.Request) { c.logf(" Preflight aborted: method '%s' not allowed", reqMethod) return } - // Amazon API Gateway is sometimes feeding multiple values for - // Access-Control-Request-Headers in a way where r.Header.Values() picks - // them all up, but r.Header.Get() does not. - // I suspect it is something like this: https://stackoverflow.com/a/4371395 - reqHeaderList := strings.Join(r.Header.Values("Access-Control-Request-Headers"), ",") - reqHeaders := parseHeaderList(reqHeaderList) + reqHeadersRaw := r.Header["Access-Control-Request-Headers"] + reqHeaders, reqHeadersEdited := convertDidCopy(splitHeaderValues(reqHeadersRaw), http.CanonicalHeaderKey) if !c.areHeadersAllowed(reqHeaders) { c.logf(" Preflight aborted: headers '%v' not allowed", reqHeaders) return } if c.allowedOriginsAll { - headers.Set("Access-Control-Allow-Origin", "*") + headers["Access-Control-Allow-Origin"] = headerOriginAll } else { - headers.Set("Access-Control-Allow-Origin", origin) + headers["Access-Control-Allow-Origin"] = r.Header["Origin"] } // Spec says: Since the list of methods can be unbounded, simply returning the method indicated // by Access-Control-Request-Method (if supported) can be enough - headers.Set("Access-Control-Allow-Methods", strings.ToUpper(reqMethod)) + headers["Access-Control-Allow-Methods"] = r.Header["Access-Control-Request-Method"] if len(reqHeaders) > 0 { - // Spec says: Since the list of headers can be unbounded, simply returning supported headers // from Access-Control-Request-Headers can be enough - headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", ")) + if reqHeadersEdited || len(reqHeaders) != len(reqHeadersRaw) { + headers.Set("Access-Control-Allow-Headers", strings.Join(reqHeaders, ", ")) + } else { + headers["Access-Control-Allow-Headers"] = reqHeadersRaw + } } if c.allowCredentials { - headers.Set("Access-Control-Allow-Credentials", "true") + headers["Access-Control-Allow-Credentials"] = headerTrue } if c.allowPrivateNetwork && r.Header.Get("Access-Control-Request-Private-Network") == "true" { - headers.Set("Access-Control-Allow-Private-Network", "true") + headers["Access-Control-Allow-Private-Network"] = headerTrue } - if c.maxAge > 0 { - headers.Set("Access-Control-Max-Age", strconv.Itoa(c.maxAge)) + if len(c.maxAge) > 0 { + headers["Access-Control-Max-Age"] = c.maxAge + } + if c.Log != nil { + c.logf(" Preflight response headers: %v", headers) } - c.logf(" Preflight response headers: %v", headers) } // handleActualRequest handles simple cross-origin requests, actual request or redirects @@ -346,13 +395,22 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { headers := w.Header() origin := r.Header.Get("Origin") + allowed, additionalVaryHeaders := c.isOriginAllowed(r, origin) + // Always set Vary, see https://github.com/rs/cors/issues/10 - headers.Add("Vary", "Origin") + if vary, found := headers["Vary"]; found { + headers["Vary"] = append(vary, headerVaryOrigin[0]) + } else { + headers["Vary"] = headerVaryOrigin + } + if len(additionalVaryHeaders) > 0 { + headers.Add("Vary", strings.Join(convert(additionalVaryHeaders, http.CanonicalHeaderKey), ", ")) + } if origin == "" { c.logf(" Actual request no headers added: missing origin") return } - if !c.isOriginAllowed(r, origin) { + if !allowed { c.logf(" Actual request no headers added: origin '%s' not allowed", origin) return } @@ -363,21 +421,22 @@ func (c *Cors) handleActualRequest(w http.ResponseWriter, r *http.Request) { // We think it's a nice feature to be able to have control on those methods though. if !c.isMethodAllowed(r.Method) { c.logf(" Actual request no headers added: method '%s' not allowed", r.Method) - return } if c.allowedOriginsAll { - headers.Set("Access-Control-Allow-Origin", "*") + headers["Access-Control-Allow-Origin"] = headerOriginAll } else { - headers.Set("Access-Control-Allow-Origin", origin) + headers["Access-Control-Allow-Origin"] = r.Header["Origin"] } if len(c.exposedHeaders) > 0 { - headers.Set("Access-Control-Expose-Headers", strings.Join(c.exposedHeaders, ", ")) + headers["Access-Control-Expose-Headers"] = c.exposedHeaders } if c.allowCredentials { - headers.Set("Access-Control-Allow-Credentials", "true") + headers["Access-Control-Allow-Credentials"] = headerTrue + } + if c.Log != nil { + c.logf(" Actual response added headers: %v", headers) } - c.logf(" Actual response added headers: %v", headers) } // convenience method. checks if a logger is set. @@ -390,33 +449,31 @@ func (c *Cors) logf(format string, a ...interface{}) { // check the Origin of a request. No origin at all is also allowed. func (c *Cors) OriginAllowed(r *http.Request) bool { origin := r.Header.Get("Origin") - return c.isOriginAllowed(r, origin) + allowed, _ := c.isOriginAllowed(r, origin) + return allowed } // isOriginAllowed checks if a given origin is allowed to perform cross-domain requests // on the endpoint -func (c *Cors) isOriginAllowed(r *http.Request, origin string) bool { - if c.allowOriginRequestFunc != nil { - return c.allowOriginRequestFunc(r, origin) - } +func (c *Cors) isOriginAllowed(r *http.Request, origin string) (allowed bool, varyHeaders []string) { if c.allowOriginFunc != nil { - return c.allowOriginFunc(origin) + return c.allowOriginFunc(r, origin) } if c.allowedOriginsAll { - return true + return true, nil } origin = strings.ToLower(origin) for _, o := range c.allowedOrigins { if o == origin { - return true + return true, nil } } for _, w := range c.allowedWOrigins { if w.match(origin) { - return true + return true, nil } } - return false + return false, nil } // isMethodAllowed checks if a given method can be used as part of a cross-domain request @@ -426,7 +483,6 @@ func (c *Cors) isMethodAllowed(method string) bool { // If no method allowed, always return false, even for preflight request return false } - method = strings.ToUpper(method) if method == http.MethodOptions { // Always allow preflight requests return true @@ -446,7 +502,6 @@ func (c *Cors) areHeadersAllowed(requestedHeaders []string) bool { return true } for _, header := range requestedHeaders { - header = http.CanonicalHeaderKey(header) found := false for _, h := range c.allowedHeaders { if h == header { diff --git a/vendor/github.com/rs/cors/utils.go b/vendor/github.com/rs/cors/utils.go index 6bb120caede..ca9983d3f71 100644 --- a/vendor/github.com/rs/cors/utils.go +++ b/vendor/github.com/rs/cors/utils.go @@ -1,8 +1,8 @@ package cors -import "strings" - -const toLower = 'a' - 'A' +import ( + "strings" +) type converter func(string) string @@ -15,57 +15,58 @@ func (w wildcard) match(s string) bool { return len(s) >= len(w.prefix)+len(w.suffix) && strings.HasPrefix(s, w.prefix) && strings.HasSuffix(s, w.suffix) } -// convert converts a list of string using the passed converter function -func convert(s []string, c converter) []string { - out := []string{} - for _, i := range s { - out = append(out, c(i)) - } - return out -} - -// parseHeaderList tokenize + normalize a string containing a list of headers -func parseHeaderList(headerList string) []string { - l := len(headerList) - h := make([]byte, 0, l) - upper := true - // Estimate the number headers in order to allocate the right splice size - t := 0 - for i := 0; i < l; i++ { - if headerList[i] == ',' { - t++ - } - } - headers := make([]string, 0, t) - for i := 0; i < l; i++ { - b := headerList[i] - switch { - case b >= 'a' && b <= 'z': - if upper { - h = append(h, b-toLower) - } else { - h = append(h, b) +// split compounded header values ["foo, bar", "baz"] -> ["foo", "bar", "baz"] +func splitHeaderValues(values []string) []string { + out := values + copied := false + for i, v := range values { + needsSplit := strings.IndexByte(v, ',') != -1 + if !copied { + if needsSplit { + split := strings.Split(v, ",") + out = make([]string, i, len(values)+len(split)-1) + copy(out, values[:i]) + for _, s := range split { + out = append(out, strings.TrimSpace(s)) + } + copied = true } - case b >= 'A' && b <= 'Z': - if !upper { - h = append(h, b+toLower) + } else { + if needsSplit { + split := strings.Split(v, ",") + for _, s := range split { + out = append(out, strings.TrimSpace(s)) + } } else { - h = append(h, b) + out = append(out, v) } - case b == '-' || b == '_' || b == '.' || (b >= '0' && b <= '9'): - h = append(h, b) } + } + return out +} + +// convert converts a list of string using the passed converter function +func convert(s []string, c converter) []string { + out, _ := convertDidCopy(s, c) + return out +} - if b == ' ' || b == ',' || i == l-1 { - if len(h) > 0 { - // Flush the found header - headers = append(headers, string(h)) - h = h[:0] - upper = true +// convertDidCopy is same as convert but returns true if it copied the slice +func convertDidCopy(s []string, c converter) ([]string, bool) { + out := s + copied := false + for i, v := range s { + if !copied { + v2 := c(v) + if v2 != v { + out = make([]string, len(s)) + copy(out, s[:i]) + out[i] = v2 + copied = true } } else { - upper = b == '-' || b == '_' + out[i] = c(v) } } - return headers + return out, copied } diff --git a/vendor/modules.txt b/vendor/modules.txt index e4bd429daa2..0f478de15cd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -807,8 +807,8 @@ github.com/pkg/errors # github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 ## explicit github.com/pmezard/go-difflib/difflib -# github.com/prometheus/alertmanager v0.26.0 -## explicit; go 1.18 +# github.com/prometheus/alertmanager v0.26.1-0.20231117200754-ca5089d33eab +## explicit; go 1.21 github.com/prometheus/alertmanager/api github.com/prometheus/alertmanager/api/metrics github.com/prometheus/alertmanager/api/v1 @@ -826,6 +826,7 @@ github.com/prometheus/alertmanager/cluster github.com/prometheus/alertmanager/cluster/clusterpb github.com/prometheus/alertmanager/config github.com/prometheus/alertmanager/dispatch +github.com/prometheus/alertmanager/featurecontrol github.com/prometheus/alertmanager/inhibit github.com/prometheus/alertmanager/nflog github.com/prometheus/alertmanager/nflog/nflogpb @@ -945,7 +946,7 @@ github.com/prometheus/prometheus/web/api/v1 # github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be ## explicit github.com/rainycape/unidecode -# github.com/rs/cors v1.9.0 +# github.com/rs/cors v1.10.1 ## explicit; go 1.13 github.com/rs/cors # github.com/rs/xid v1.5.0