diff --git a/CHANGELOG.md b/CHANGELOG.md index 937db039e17..15e0bfcbdfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ ### Grafana Mimir +* [CHANGE] Alertmanager: Deprecates the `v1` API. All `v1` API endpoints now respond with a JSON deprecation notice and a status code of `410`. All endpoints have a `v2` equivalent. The list of endpoints is: + * `/api/v1/alerts` + * `/api/v1/receivers` + * `/api/v1/silence/{id}` + * `/api/v1/silences` + * `/api/v1/status` * [CHANGE] Ingester: Increase default value of `-blocks-storage.tsdb.head-postings-for-matchers-cache-max-bytes` and `-blocks-storage.tsdb.block-postings-for-matchers-cache-max-bytes` to 100 MiB (previous default value was 10 MiB). #6764 * [CHANGE] Validate tenant IDs according to [documented behavior](https://grafana.com/docs/mimir/latest/configure/about-tenant-ids/) even when tenant federation is not enabled. Note that this will cause some previously accepted tenant IDs to be rejected such as those longer than 150 bytes or containing `|` characters. #6959 * [CHANGE] Ruler: don't use backoff retry on remote evaluation in case of `4xx` errors. #7004 diff --git a/go.mod b/go.mod index 8deb12a30eb..1264656dde3 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.1-0.20231117200754-ca5089d33eab + github.com/prometheus/alertmanager v0.26.1-0.20240119104350-f92a08d07386 github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_model v0.5.0 github.com/prometheus/common v0.46.0 @@ -184,7 +184,7 @@ require ( github.com/hashicorp/go-msgpack v1.1.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/memberlist v0.5.0 // indirect github.com/hashicorp/serf v0.10.1 // indirect github.com/hashicorp/vault/api/auth/approle v0.5.0 @@ -217,7 +217,7 @@ require ( github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect - github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97 // indirect + github.com/prometheus/exporter-toolkit v0.11.0 // indirect github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect github.com/rs/cors v1.10.1 // indirect github.com/rs/xid v1.5.0 // indirect @@ -250,7 +250,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/telebot.v3 v3.1.3 // indirect + gopkg.in/telebot.v3 v3.2.1 // indirect k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect k8s.io/utils v0.0.0-20230711102312-30195339c3c7 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/go.sum b/go.sum index 5cfdcc3ab43..09a22ebf3d2 100644 --- a/go.sum +++ b/go.sum @@ -608,8 +608,9 @@ github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7ml github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= +github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -829,8 +830,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.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/alertmanager v0.26.1-0.20240119104350-f92a08d07386 h1:jcyYvVOHEllcJsH23+TU7MFPUaNHga4TJ43v+Zt4Zts= +github.com/prometheus/alertmanager v0.26.1-0.20240119104350-f92a08d07386/go.mod h1:pT85bjw8Hkt/ZfVpa/b0gLWQc015Yjbi02eCYD0yRl4= 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= @@ -854,8 +855,8 @@ github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqSc github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= -github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97 h1:oHcfzdJnM/SFppy2aUlvomk37GI33x9vgJULihE5Dt8= -github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97/go.mod h1:LoBCZeRh+5hX+fSULNyFnagYlQG/gBsyA/deNzROkq8= +github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7fdBvKPAEGkNf+g= +github.com/prometheus/exporter-toolkit v0.11.0/go.mod h1:BVnENhnNecpwoTLiABx7mrPB/OLRIgN74qlQbV+FK1Q= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= @@ -1581,8 +1582,8 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/telebot.v3 v3.1.3 h1:T+CTyOWpZMqp3ALHSweNgp1awQ9nMXdRAMpe/r6x9/s= -gopkg.in/telebot.v3 v3.1.3/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= +gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= +gopkg.in/telebot.v3 v3.2.1/go.mod h1:GJKwwWqp9nSkIVN51eRKU78aB5f5OnQuWdwiIZfPbko= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/integration/alertmanager_test.go b/integration/alertmanager_test.go index 9b3949de23a..b819bd44193 100644 --- a/integration/alertmanager_test.go +++ b/integration/alertmanager_test.go @@ -9,6 +9,7 @@ package integration import ( "bytes" "context" + "encoding/json" "fmt" "net/http" "testing" @@ -125,6 +126,56 @@ func TestAlertmanager(t *testing.T) { require.Equal(t, "Accept-Encoding", res.Header.Get("Vary")) } +func TestAlertmanagerV1Deprecated(t *testing.T) { + s, err := e2e.NewScenario(networkName) + require.NoError(t, err) + defer s.Close() + + consul := e2edb.NewConsul() + require.NoError(t, s.StartAndWaitReady(consul)) + + require.NoError(t, writeFileToSharedDir(s, "alertmanager_configs/user-1.yaml", []byte(mimirAlertmanagerUserConfigYaml))) + + alertmanager := e2emimir.NewAlertmanager( + "alertmanager", + mergeFlags( + AlertmanagerFlags(), + AlertmanagerLocalFlags(), + AlertmanagerShardingFlags(consul.NetworkHTTPEndpoint(), 1), + ), + ) + require.NoError(t, s.StartAndWaitReady(alertmanager)) + + endpoints := []string{ + "alerts", + "receivers", + "silence/id", + "silences", + "status", + } + for _, endpoint := range endpoints { + req, err := http.NewRequest("GET", fmt.Sprintf("http://%s/alertmanager/api/v1/%s", alertmanager.HTTPEndpoint(), endpoint), nil) + require.NoError(t, err) + req.Header.Set("X-Scope-OrgID", "user-1") + req.Header.Set("Accept-Encoding", "gzip") + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + // Execute HTTP request + res, err := http.DefaultClient.Do(req.WithContext(ctx)) + require.NoError(t, err) + require.Equal(t, http.StatusGone, res.StatusCode) + + var response = struct { + Status string `json:"status"` + Error string `json:"error"` + }{} + require.NoError(t, json.NewDecoder(res.Body).Decode(&response)) + require.Equal(t, "deprecated", response.Status) + require.Equal(t, "The Alertmanager v1 API was deprecated in version 0.16.0 and is removed as of version 0.28.0 - please use the equivalent route in the v2 API", response.Error) + } +} + func TestAlertmanagerLocalStore(t *testing.T) { s, err := e2e.NewScenario(networkName) require.NoError(t, err) @@ -387,58 +438,29 @@ func TestAlertmanagerSharding(t *testing.T) { assert.Equal(t, s3, ids[id3].Status.State) } - // Endpoint: GET /v1/silences - { - for _, c := range clients { - list, err := c.GetSilencesV1(context.Background()) - require.NoError(t, err) - assertSilences(list, types.SilenceStateActive, types.SilenceStateActive, types.SilenceStateActive) - } - } - // Endpoint: GET /v2/silences { for _, c := range clients { - list, err := c.GetSilencesV2(context.Background()) + list, err := c.GetSilences(context.Background()) require.NoError(t, err) assertSilences(list, types.SilenceStateActive, types.SilenceStateActive, types.SilenceStateActive) } } - // Endpoint: GET /v1/silence/{id} - { - for _, c := range clients { - sil1, err := c.GetSilenceV1(context.Background(), id1) - require.NoError(t, err) - assert.Equal(t, comment(1), sil1.Comment) - assert.Equal(t, types.SilenceStateActive, sil1.Status.State) - - sil2, err := c.GetSilenceV1(context.Background(), id2) - require.NoError(t, err) - assert.Equal(t, comment(2), sil2.Comment) - assert.Equal(t, types.SilenceStateActive, sil2.Status.State) - - sil3, err := c.GetSilenceV1(context.Background(), id3) - require.NoError(t, err) - assert.Equal(t, comment(3), sil3.Comment) - assert.Equal(t, types.SilenceStateActive, sil3.Status.State) - } - } - // Endpoint: GET /v2/silence/{id} { for _, c := range clients { - sil1, err := c.GetSilenceV2(context.Background(), id1) + sil1, err := c.GetSilence(context.Background(), id1) require.NoError(t, err) assert.Equal(t, comment(1), sil1.Comment) assert.Equal(t, types.SilenceStateActive, sil1.Status.State) - sil2, err := c.GetSilenceV2(context.Background(), id2) + sil2, err := c.GetSilence(context.Background(), id2) require.NoError(t, err) assert.Equal(t, comment(2), sil2.Comment) assert.Equal(t, types.SilenceStateActive, sil2.Status.State) - sil3, err := c.GetSilenceV2(context.Background(), id3) + sil3, err := c.GetSilence(context.Background(), id3) require.NoError(t, err) assert.Equal(t, comment(3), sil3.Comment) assert.Equal(t, types.SilenceStateActive, sil3.Status.State) @@ -483,7 +505,7 @@ func TestAlertmanagerSharding(t *testing.T) { require.NoError(t, waitForSilences("expired", 1*testCfg.replicationFactor)) for _, c := range clients { - list, err := c.GetSilencesV2(context.Background()) + list, err := c.GetSilences(context.Background()) require.NoError(t, err) assertSilences(list, types.SilenceStateActive, types.SilenceStateExpired, types.SilenceStateActive) } @@ -493,7 +515,7 @@ func TestAlertmanagerSharding(t *testing.T) { require.NoError(t, waitForSilences("expired", 2*testCfg.replicationFactor)) for _, c := range clients { - list, err := c.GetSilencesV2(context.Background()) + list, err := c.GetSilences(context.Background()) require.NoError(t, err) assertSilences(list, types.SilenceStateActive, types.SilenceStateExpired, types.SilenceStateExpired) } @@ -503,7 +525,7 @@ func TestAlertmanagerSharding(t *testing.T) { require.NoError(t, waitForSilences("expired", 3*testCfg.replicationFactor)) for _, c := range clients { - list, err := c.GetSilencesV2(context.Background()) + list, err := c.GetSilences(context.Background()) require.NoError(t, err) assertSilences(list, types.SilenceStateExpired, types.SilenceStateExpired, types.SilenceStateExpired) } @@ -543,22 +565,10 @@ func TestAlertmanagerSharding(t *testing.T) { e2e.SkipMissingMetrics)) } - // Endpoint: GET /v1/alerts - { - // Reads will query at least two replicas and merge the results. - // Therefore, the alerts we posted should always be visible. - - for _, c := range clients { - list, err := c.GetAlertsV1(context.Background()) - require.NoError(t, err) - assert.ElementsMatch(t, []string{"alert_1", "alert_2", "alert_3"}, alertNames(list)) - } - } - // Endpoint: GET /v2/alerts { for _, c := range clients { - list, err := c.GetAlertsV2(context.Background()) + list, err := c.GetAlerts(context.Background()) require.NoError(t, err) assert.ElementsMatch(t, []string{"alert_1", "alert_2", "alert_3"}, alertNames(list)) } @@ -581,8 +591,6 @@ func TestAlertmanagerSharding(t *testing.T) { require.Contains(t, groups, "group_2") assert.ElementsMatch(t, []string{"alert_3"}, alertNames(groups["group_2"])) } - - // Note: /v1/alerts/groups does not exist. } // Check the alerts were eventually written to every replica. diff --git a/integration/e2emimir/client.go b/integration/e2emimir/client.go index fe74b9e58fe..becd0e90045 100644 --- a/integration/e2emimir/client.go +++ b/integration/e2emimir/client.go @@ -486,9 +486,9 @@ func (r *addOrgIDRoundTripper) RoundTrip(req *http.Request) (*http.Response, err // ServerStatus represents a Alertmanager status response // TODO: Upgrade to Alertmanager v0.20.0+ and utilize vendored structs type ServerStatus struct { - Data struct { - ConfigYaml string `json:"configYAML"` - } `json:"data"` + Config struct { + Original string `json:"original"` + } `json:"config"` } type successResult struct { @@ -528,8 +528,8 @@ func (c *Client) GetPrometheusRules() ([]*promv1.RuleGroup, error) { } `json:"data"` } - decoded := &response{} - if err := json.Unmarshal(body, decoded); err != nil { + decoded := response{} + if err := json.Unmarshal(body, &decoded); err != nil { return nil, err } @@ -741,7 +741,7 @@ func (c *Client) doAlertmanagerRequest(ctx context.Context, method string, path // GetAlertmanagerConfig gets the status of an alertmanager instance func (c *Client) GetAlertmanagerConfig(ctx context.Context) (*alertConfig.Config, error) { - u := c.alertmanagerClient.URL("/alertmanager/api/v1/status", nil) + u := c.alertmanagerClient.URL("/alertmanager/api/v2/status", nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { @@ -768,7 +768,7 @@ func (c *Client) GetAlertmanagerConfig(ctx context.Context) (*alertConfig.Config } cfg := &alertConfig.Config{} - err = yaml.Unmarshal([]byte(ss.Data.ConfigYaml), cfg) + err = yaml.Unmarshal([]byte(ss.Config.Original), cfg) return cfg, err } @@ -1016,7 +1016,7 @@ func (c *Client) DeleteGrafanaAlertmanagerState(ctx context.Context) error { // SendAlertToAlermanager sends alerts to the Alertmanager API func (c *Client) SendAlertToAlermanager(ctx context.Context, alert *model.Alert) error { - u := c.alertmanagerClient.URL("/alertmanager/api/v1/alerts", nil) + u := c.alertmanagerClient.URL("/alertmanager/api/v2/alerts", nil) data, err := json.Marshal([]types.Alert{{Alert: *alert}}) if err != nil { @@ -1027,6 +1027,7 @@ func (c *Client) SendAlertToAlermanager(ctx context.Context, alert *model.Alert) if err != nil { return fmt.Errorf("error creating request: %v", err) } + req.Header.Set("Content-Type", "application/json") resp, body, err := c.alertmanagerClient.Do(ctx, req) if err != nil { @@ -1040,45 +1041,7 @@ func (c *Client) SendAlertToAlermanager(ctx context.Context, alert *model.Alert) return nil } -func (c *Client) GetAlertsV1(ctx context.Context) ([]model.Alert, error) { - u := c.alertmanagerClient.URL("alertmanager/api/v1/alerts", nil) - - req, err := http.NewRequest(http.MethodGet, u.String(), nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %v", err) - } - - resp, body, err := c.alertmanagerClient.Do(ctx, req) - if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusNotFound { - return nil, ErrNotFound - } - - if resp.StatusCode/100 != 2 { - return nil, fmt.Errorf("getting alerts failed with status %d and error %v", resp.StatusCode, string(body)) - } - - type response struct { - Status string `json:"status"` - Data []model.Alert `json:"data"` - } - - decoded := &response{} - if err := json.Unmarshal(body, decoded); err != nil { - return nil, err - } - - if decoded.Status != "success" { - return nil, fmt.Errorf("unexpected response status '%s'", decoded.Status) - } - - return decoded.Data, nil -} - -func (c *Client) GetAlertsV2(ctx context.Context) ([]model.Alert, error) { +func (c *Client) GetAlerts(ctx context.Context) ([]model.Alert, error) { u := c.alertmanagerClient.URL("alertmanager/api/v2/alerts", nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) @@ -1142,7 +1105,7 @@ func (c *Client) GetAlertGroups(ctx context.Context) ([]AlertGroup, error) { // CreateSilence creates a new silence and returns the unique identifier of the silence. func (c *Client) CreateSilence(ctx context.Context, silence types.Silence) (string, error) { - u := c.alertmanagerClient.URL("alertmanager/api/v1/silences", nil) + u := c.alertmanagerClient.URL("alertmanager/api/v2/silences", nil) data, err := json.Marshal(silence) if err != nil { @@ -1153,6 +1116,7 @@ func (c *Client) CreateSilence(ctx context.Context, silence types.Silence) (stri if err != nil { return "", fmt.Errorf("error creating request: %v", err) } + req.Header.Set("Content-Type", "application/json") resp, body, err := c.alertmanagerClient.Do(ctx, req) if err != nil { @@ -1164,63 +1128,18 @@ func (c *Client) CreateSilence(ctx context.Context, silence types.Silence) (stri } type response struct { - Status string `json:"status"` - Data struct { - SilenceID string `json:"silenceID"` - } `json:"data"` + SilenceID string `json:"silenceID"` } - decoded := &response{} - if err := json.Unmarshal(body, decoded); err != nil { + decoded := response{} + if err := json.Unmarshal(body, &decoded); err != nil { return "", err } - if decoded.Status != "success" { - return "", fmt.Errorf("unexpected response status '%s'", decoded.Status) - } - - return decoded.Data.SilenceID, nil + return decoded.SilenceID, nil } -func (c *Client) GetSilencesV1(ctx context.Context) ([]types.Silence, error) { - u := c.alertmanagerClient.URL("alertmanager/api/v1/silences", nil) - - req, err := http.NewRequest(http.MethodGet, u.String(), nil) - if err != nil { - return nil, fmt.Errorf("error creating request: %v", err) - } - - resp, body, err := c.alertmanagerClient.Do(ctx, req) - if err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusNotFound { - return nil, ErrNotFound - } - - if resp.StatusCode/100 != 2 { - return nil, fmt.Errorf("getting silences failed with status %d and error %v", resp.StatusCode, string(body)) - } - - type response struct { - Status string `json:"status"` - Data []types.Silence `json:"data"` - } - - decoded := &response{} - if err := json.Unmarshal(body, decoded); err != nil { - return nil, err - } - - if decoded.Status != "success" { - return nil, fmt.Errorf("unexpected response status '%s'", decoded.Status) - } - - return decoded.Data, nil -} - -func (c *Client) GetSilencesV2(ctx context.Context) ([]types.Silence, error) { +func (c *Client) GetSilences(ctx context.Context) ([]types.Silence, error) { u := c.alertmanagerClient.URL("alertmanager/api/v2/silences", nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) @@ -1249,45 +1168,7 @@ func (c *Client) GetSilencesV2(ctx context.Context) ([]types.Silence, error) { return decoded, nil } -func (c *Client) GetSilenceV1(ctx context.Context, id string) (types.Silence, error) { - u := c.alertmanagerClient.URL(fmt.Sprintf("alertmanager/api/v1/silence/%s", url.PathEscape(id)), nil) - - req, err := http.NewRequest(http.MethodGet, u.String(), nil) - if err != nil { - return types.Silence{}, fmt.Errorf("error creating request: %v", err) - } - - resp, body, err := c.alertmanagerClient.Do(ctx, req) - if err != nil { - return types.Silence{}, err - } - - if resp.StatusCode == http.StatusNotFound { - return types.Silence{}, ErrNotFound - } - - if resp.StatusCode/100 != 2 { - return types.Silence{}, fmt.Errorf("getting silence failed with status %d and error %v", resp.StatusCode, string(body)) - } - - type response struct { - Status string `json:"status"` - Data types.Silence `json:"data"` - } - - decoded := &response{} - if err := json.Unmarshal(body, decoded); err != nil { - return types.Silence{}, err - } - - if decoded.Status != "success" { - return types.Silence{}, fmt.Errorf("unexpected response status '%s'", decoded.Status) - } - - return decoded.Data, nil -} - -func (c *Client) GetSilenceV2(ctx context.Context, id string) (types.Silence, error) { +func (c *Client) GetSilence(ctx context.Context, id string) (types.Silence, error) { u := c.alertmanagerClient.URL(fmt.Sprintf("alertmanager/api/v2/silence/%s", url.PathEscape(id)), nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) @@ -1317,7 +1198,7 @@ func (c *Client) GetSilenceV2(ctx context.Context, id string) (types.Silence, er } func (c *Client) DeleteSilence(ctx context.Context, id string) error { - u := c.alertmanagerClient.URL(fmt.Sprintf("alertmanager/api/v1/silence/%s", url.PathEscape(id)), nil) + u := c.alertmanagerClient.URL(fmt.Sprintf("alertmanager/api/v2/silence/%s", url.PathEscape(id)), nil) req, err := http.NewRequest(http.MethodDelete, u.String(), nil) if err != nil { @@ -1341,7 +1222,7 @@ func (c *Client) DeleteSilence(ctx context.Context, id string) error { } func (c *Client) GetReceivers(ctx context.Context) ([]string, error) { - u := c.alertmanagerClient.URL("alertmanager/api/v1/receivers", nil) + u := c.alertmanagerClient.URL("alertmanager/api/v2/receivers", nil) req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { @@ -1361,21 +1242,20 @@ func (c *Client) GetReceivers(ctx context.Context) ([]string, error) { return nil, fmt.Errorf("getting receivers failed with status %d and error %v", resp.StatusCode, string(body)) } - type response struct { - Status string `json:"status"` - Data []string `json:"data"` + type response []struct { + Name string `json:"name"` } - decoded := &response{} - if err := json.Unmarshal(body, decoded); err != nil { + decoded := response{} + if err := json.Unmarshal(body, &decoded); err != nil { return nil, err } - if decoded.Status != "success" { - return nil, fmt.Errorf("unexpected response status '%s'", decoded.Status) + var receivers []string + for _, v := range decoded { + receivers = append(receivers, v.Name) } - - return decoded.Data, nil + return receivers, nil } // DoGet performs a HTTP GET request towards the supplied URL. The request diff --git a/integration/read_write_mode_test.go b/integration/read_write_mode_test.go index 2e8b70dcd30..82c11d43dfd 100644 --- a/integration/read_write_mode_test.go +++ b/integration/read_write_mode_test.go @@ -287,7 +287,7 @@ receivers: // Verify alert is firing require.NoError(t, cluster.backendInstance.WaitSumMetricsWithOptions(e2e.GreaterOrEqual(1), []string{"cortex_alertmanager_alerts_received_total"}, e2e.WaitMissingMetrics)) - alerts, err := client.GetAlertsV1(context.Background()) + alerts, err := client.GetAlerts(context.Background()) require.NoError(t, err) require.Len(t, alerts, 1) require.Equal(t, testAlertName, alerts[0].Name()) diff --git a/pkg/alertmanager/alertmanager.go b/pkg/alertmanager/alertmanager.go index 67023522916..364a03cd131 100644 --- a/pkg/alertmanager/alertmanager.go +++ b/pkg/alertmanager/alertmanager.go @@ -80,6 +80,7 @@ type Config struct { MaxConcurrentGetRequestsPerTenant int ExternalURL *url.URL Limits Limits + Features featurecontrol.Flagger // Tenant-specific local directory where AM can store its state (notifications, silences, templates). When AM is stopped, entire dir is removed. TenantDataDir string @@ -238,15 +239,7 @@ func New(cfg *Config, reg *prometheus.Registry) (*Alertmanager, error) { return nil, errors.Wrap(err, "failed to start state persister service") } - // 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) + am.pipelineBuilder = notify.NewPipelineBuilder(am.registry, cfg.Features) // Run the silences maintenance in a dedicated goroutine. am.wg.Add(1) diff --git a/pkg/alertmanager/alertmanager_http_test.go b/pkg/alertmanager/alertmanager_http_test.go index 24044cb2290..f0df6645f87 100644 --- a/pkg/alertmanager/alertmanager_http_test.go +++ b/pkg/alertmanager/alertmanager_http_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/go-kit/log" + "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" ) @@ -19,7 +20,7 @@ func TestMultitenantAlertmanager_GetStatusHandler(t *testing.T) { store := prepareInMemoryAlertStore() reg := prometheus.NewPedanticRegistry() cfg := mockAlertmanagerConfig(t) - am := setupSingleMultitenantAlertmanager(t, cfg, store, nil, log.NewNopLogger(), reg) + am := setupSingleMultitenantAlertmanager(t, cfg, store, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) req := httptest.NewRequest("GET", "http://alertmanager.cortex/status", nil) w := httptest.NewRecorder() diff --git a/pkg/alertmanager/alertmanager_test.go b/pkg/alertmanager/alertmanager_test.go index 0c901d45045..b1db1c7f187 100644 --- a/pkg/alertmanager/alertmanager_test.go +++ b/pkg/alertmanager/alertmanager_test.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/dskit/test" "github.com/prometheus/alertmanager/cluster/clusterpb" "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/alertmanager/types" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" @@ -62,6 +63,7 @@ func createAlertmanagerAndSendAlerts(t *testing.T, alertGroups, groupsLimit, exp UserID: user, Logger: log.NewNopLogger(), Limits: &mockAlertManagerLimits{maxDispatcherAggregationGroups: groupsLimit}, + Features: featurecontrol.NoopFlags{}, TenantDataDir: t.TempDir(), ExternalURL: &url.URL{Path: "/am"}, ShardingEnabled: true, @@ -146,6 +148,7 @@ func TestDispatcherLoggerInsightKey(t *testing.T) { UserID: user, Logger: logger, Limits: &mockAlertManagerLimits{maxDispatcherAggregationGroups: 10}, + Features: featurecontrol.NoopFlags{}, TenantDataDir: t.TempDir(), ExternalURL: &url.URL{Path: "/am"}, ShardingEnabled: true, diff --git a/pkg/alertmanager/api_test.go b/pkg/alertmanager/api_test.go index da944d5ba78..62d4fba3f43 100644 --- a/pkg/alertmanager/api_test.go +++ b/pkg/alertmanager/api_test.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/dskit/user" "github.com/pkg/errors" "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/client_golang/prometheus" commoncfg "github.com/prometheus/common/config" "github.com/stretchr/testify/assert" @@ -890,7 +891,7 @@ alertmanager_config: | limits.maxTemplatesCount = tc.maxTemplates limits.maxSizeOfTemplate = tc.maxTemplateSize - req := httptest.NewRequest(http.MethodPost, "http://alertmanager/api/v1/alerts", bytes.NewReader([]byte(tc.cfg))) + req := httptest.NewRequest(http.MethodPost, "http://alertmanager/api/v2/alerts", bytes.NewReader([]byte(tc.cfg))) ctx := user.InjectOrgID(req.Context(), "testing") w := httptest.NewRecorder() am.SetUserConfig(w, req.WithContext(ctx)) @@ -1013,7 +1014,7 @@ receivers: // Create the Multitenant Alertmanager. reg := prometheus.NewPedanticRegistry() cfg := mockAlertmanagerConfig(t) - am := setupSingleMultitenantAlertmanager(t, cfg, alertStore, nil, log.NewNopLogger(), reg) + am := setupSingleMultitenantAlertmanager(t, cfg, alertStore, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) err = am.loadAndSyncConfigs(context.Background(), reasonPeriodic) require.NoError(t, err) diff --git a/pkg/alertmanager/distributor.go b/pkg/alertmanager/distributor.go index 44c360ed820..b5d896e6575 100644 --- a/pkg/alertmanager/distributor.go +++ b/pkg/alertmanager/distributor.go @@ -83,21 +83,12 @@ func (d *Distributor) isUnaryDeletePath(p string) bool { } func (d *Distributor) isQuorumReadPath(p string) (bool, merger.Merger) { - if strings.HasSuffix(p, "/v1/alerts") { - return true, merger.V1Alerts{} - } if strings.HasSuffix(p, "/v2/alerts") { return true, merger.V2Alerts{} } if strings.HasSuffix(p, "/v2/alerts/groups") { return true, merger.V2AlertGroups{} } - if strings.HasSuffix(p, "/v1/silences") { - return true, merger.V1Silences{} - } - if strings.HasSuffix(path.Dir(p), "/v1/silence") { - return true, merger.V1SilenceID{} - } if strings.HasSuffix(p, "/v2/silences") { return true, merger.V2Silences{} } diff --git a/pkg/alertmanager/distributor_test.go b/pkg/alertmanager/distributor_test.go index 6947a55f645..49ac659c883 100644 --- a/pkg/alertmanager/distributor_test.go +++ b/pkg/alertmanager/distributor_test.go @@ -84,16 +84,6 @@ func TestDistributor_DistributeRequest(t *testing.T) { expStatusCode: http.StatusOK, expectedTotalCalls: 3, route: "/alerts", - }, { - name: "Read /v1/alerts is sent to 3 AMs", - numAM: 5, - numHappyAM: 5, - replicationFactor: 3, - isRead: true, - expStatusCode: http.StatusOK, - expectedTotalCalls: 3, - route: "/v1/alerts", - responseBody: []byte(`{"status":"success","data":[]}`), }, { name: "Read /v2/alerts is sent to 3 AMs", numAM: 5, @@ -123,16 +113,6 @@ func TestDistributor_DistributeRequest(t *testing.T) { expectedTotalCalls: 0, headersNotPreserved: true, route: "/alerts/groups", - }, { - name: "Read /v1/silences is sent to 3 AMs", - numAM: 5, - numHappyAM: 5, - replicationFactor: 3, - isRead: true, - expStatusCode: http.StatusOK, - expectedTotalCalls: 3, - route: "/v1/silences", - responseBody: []byte(`{"status":"success","data":[]}`), }, { name: "Read /v2/silences is sent to 3 AMs", numAM: 5, @@ -151,16 +131,6 @@ func TestDistributor_DistributeRequest(t *testing.T) { expStatusCode: http.StatusOK, expectedTotalCalls: 1, route: "/silences", - }, { - name: "Read /v1/silence/id is sent to 3 AMs", - numAM: 5, - numHappyAM: 5, - replicationFactor: 3, - isRead: true, - expStatusCode: http.StatusOK, - expectedTotalCalls: 3, - route: "/v1/silence/id", - responseBody: []byte(`{"status":"success","data":{"id":"aaa","updatedAt":"2020-01-01T00:00:00Z"}}`), }, { name: "Read /v2/silence/id is sent to 3 AMs", numAM: 5, diff --git a/pkg/alertmanager/merger/v1_alerts.go b/pkg/alertmanager/merger/v1_alerts.go deleted file mode 100644 index 762fb703882..00000000000 --- a/pkg/alertmanager/merger/v1_alerts.go +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/alertmanager/merger/v1_alerts.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Cortex Authors. - -package merger - -import ( - "encoding/json" - "fmt" - "sort" - - v1 "github.com/prometheus/alertmanager/api/v1" -) - -const ( - statusSuccess = "success" -) - -// V1Alerts implements the Merger interface for GET /v1/alerts. It returns the union of alerts over -// all the responses. When the same alert exists in multiple responses, the alert instance in the -// earliest response is returned in the final response. We cannot use the UpdatedAt timestamp as -// for V2Alerts, because the v1 API does not provide it. -type V1Alerts struct{} - -func (V1Alerts) MergeResponses(in [][]byte) ([]byte, error) { - type bodyType struct { - Status string `json:"status"` - Data []*v1.Alert `json:"data"` - } - - alerts := make([]*v1.Alert, 0) - for _, body := range in { - parsed := bodyType{} - if err := json.Unmarshal(body, &parsed); err != nil { - return nil, err - } - if parsed.Status != statusSuccess { - return nil, fmt.Errorf("unable to merge response of status: %s", parsed.Status) - } - alerts = append(alerts, parsed.Data...) - } - - merged, err := mergeV1Alerts(alerts) - if err != nil { - return nil, err - } - body := bodyType{ - Status: statusSuccess, - Data: merged, - } - - return json.Marshal(body) -} - -func mergeV1Alerts(in []*v1.Alert) ([]*v1.Alert, error) { - // Select an arbitrary alert for each distinct alert. - alerts := make(map[string]*v1.Alert) - for _, alert := range in { - key := alert.Fingerprint - if _, ok := alerts[key]; !ok { - alerts[key] = alert - } - } - - result := make([]*v1.Alert, 0, len(alerts)) - for _, alert := range alerts { - result = append(result, alert) - } - - // Mimic Alertmanager which returns alerts ordered by fingerprint (as string). - sort.Slice(result, func(i, j int) bool { - return result[i].Fingerprint < result[j].Fingerprint - }) - - return result, nil -} diff --git a/pkg/alertmanager/merger/v1_alerts_test.go b/pkg/alertmanager/merger/v1_alerts_test.go deleted file mode 100644 index f86a9a72679..00000000000 --- a/pkg/alertmanager/merger/v1_alerts_test.go +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/alertmanager/merger/v1_alerts_test.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Cortex Authors. - -package merger - -import ( - "testing" - "time" - - v1 "github.com/prometheus/alertmanager/api/v1" - "github.com/prometheus/alertmanager/types" - prom_model "github.com/prometheus/common/model" - "github.com/stretchr/testify/require" -) - -func TestV1Alerts(t *testing.T) { - - // This test is to check the parsing round-trip is working as expected, the merging logic is - // tested in TestMergeV1Alerts. The test data is based on captures from an actual Alertmanager. - - in := [][]byte{ - []byte(`{"status":"success","data":[` + - `{"labels":{"group":"group_1","name":"alert_1"},` + - `"annotations":null,"startsAt":"2021-04-21T09:47:32.16145934+02:00",` + - `"endsAt":"2021-04-21T10:47:32.161462204+02:00","generatorURL":"",` + - `"status":{"state":"unprocessed","silencedBy":[],"inhibitedBy":[]},` + - `"receivers":["dummy"],"fingerprint":"c4b6b79a607b6ba0"},` + - `{"labels":{"group":"group_1","name":"alert_2"},` + - `"annotations":null,"startsAt":"2021-04-21T09:47:32.163797336+02:00",` + - `"endsAt":"2021-04-21T10:47:32.163800129+02:00","generatorURL":"",` + - `"status":{"state":"unprocessed","silencedBy":[],"inhibitedBy":[]},` + - `"receivers":["dummy"],"fingerprint":"c4b8b79a607bee77"}]}`), - []byte(`{"status":"success","data":[` + - `{"labels":{"group":"group_2","name":"alert_3"},` + - `"annotations":null,"startsAt":"2021-04-21T09:47:32.165939585+02:00",` + - `"endsAt":"2021-04-21T10:47:32.165942448+02:00","generatorURL":"",` + - `"status":{"state":"unprocessed","silencedBy":[],"inhibitedBy":[]},` + - `"receivers":["dummy"],"fingerprint":"465de60f606461c3"}]}`), - []byte(`{"status":"success","data":[]}`), - } - - expected := []byte(`{"status":"success","data":[` + - `{"labels":{"group":"group_2","name":"alert_3"},"annotations":null,` + - `"startsAt":"2021-04-21T09:47:32.165939585+02:00",` + - `"endsAt":"2021-04-21T10:47:32.165942448+02:00","generatorURL":"",` + - `"status":{"state":"unprocessed","silencedBy":[],"inhibitedBy":[]},` + - `"receivers":["dummy"],"fingerprint":"465de60f606461c3"},` + - `{"labels":{"group":"group_1","name":"alert_1"},"annotations":null,` + - `"startsAt":"2021-04-21T09:47:32.16145934+02:00",` + - `"endsAt":"2021-04-21T10:47:32.161462204+02:00","generatorURL":"",` + - `"status":{"state":"unprocessed","silencedBy":[],"inhibitedBy":[]},` + - `"receivers":["dummy"],"fingerprint":"c4b6b79a607b6ba0"},` + - `{"labels":{"group":"group_1","name":"alert_2"},"annotations":null,` + - `"startsAt":"2021-04-21T09:47:32.163797336+02:00",` + - `"endsAt":"2021-04-21T10:47:32.163800129+02:00","generatorURL":"",` + - `"status":{"state":"unprocessed","silencedBy":[],"inhibitedBy":[]},` + - `"receivers":["dummy"],"fingerprint":"c4b8b79a607bee77"}]}`) - - out, err := V1Alerts{}.MergeResponses(in) - require.NoError(t, err) - require.Equal(t, expected, out) -} - -func v1ParseTime(s string) time.Time { - t, _ := time.Parse(time.RFC3339, s) - return t -} - -// v1alert is a convenience function to create alert structures with certain important fields set -// and with sensible defaults for the remaining fields to test they are passed through. -func v1alert(fingerprint, annotation string) *v1.Alert { - return &v1.Alert{ - Alert: &prom_model.Alert{ - Labels: prom_model.LabelSet{ - "label1": "value1", - }, - Annotations: prom_model.LabelSet{ - "annotation1": prom_model.LabelValue(annotation), - }, - StartsAt: v1ParseTime("2020-01-01T12:00:00.000Z"), - EndsAt: v1ParseTime("2020-01-01T12:00:00.000Z"), - GeneratorURL: "something", - }, - Status: types.AlertStatus{}, - Receivers: []string{"dummy"}, - Fingerprint: fingerprint, - } -} - -func v1alerts(alerts ...*v1.Alert) []*v1.Alert { - return alerts -} - -func TestMergeV1Alerts(t *testing.T) { - var ( - alert1 = v1alert("1111111111111111", "a1") - alert1b = v1alert("1111111111111111", "a1b") - alert2 = v1alert("2222222222222222", "a2") - alert3 = v1alert("3333333333333333", "a3") - ) - cases := []struct { - name string - in []*v1.Alert - err error - out []*v1.Alert - }{ - { - name: "no alerts, should return an empty list", - in: v1alerts(), - out: []*v1.Alert{}, - }, - { - name: "one alert, should return the alert", - in: v1alerts(alert1), - out: v1alerts(alert1), - }, - { - name: "two alerts, should return two alerts", - in: v1alerts(alert1, alert2), - out: v1alerts(alert1, alert2), - }, - { - name: "three alerts, should return three alerts", - in: v1alerts(alert1, alert2, alert3), - out: v1alerts(alert1, alert2, alert3), - }, - { - name: "three alerts out of order, should return three alerts in fingerprint order", - in: v1alerts(alert3, alert2, alert1), - out: v1alerts(alert1, alert2, alert3), - }, - { - name: "two identical alerts, should return one alert", - in: v1alerts(alert1, alert1), - out: v1alerts(alert1), - }, - { - name: "two identical alerts plus another, should return two alerts", - in: v1alerts(alert1, alert1, alert2), - out: v1alerts(alert1, alert2), - }, - { - name: "two duplicates out of sync alerts, should return first seen alert", - in: v1alerts(alert1, alert1b), - out: v1alerts(alert1), - }, - { - name: "two duplicates plus others, should return first seen alert and others", - in: v1alerts(alert1b, alert3, alert1, alert2), - out: v1alerts(alert1b, alert2, alert3), - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - out, err := mergeV1Alerts(c.in) - require.Equal(t, c.err, err) - require.Equal(t, c.out, out) - }) - } -} diff --git a/pkg/alertmanager/merger/v1_silence_id.go b/pkg/alertmanager/merger/v1_silence_id.go deleted file mode 100644 index 51332dd785c..00000000000 --- a/pkg/alertmanager/merger/v1_silence_id.go +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/alertmanager/merger/v1_silence_id.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Cortex Authors. - -package merger - -import ( - "encoding/json" - "errors" - "fmt" - - v2_models "github.com/prometheus/alertmanager/api/v2/models" -) - -// V1Silences implements the Merger interface for GET /v1/silences. This re-uses the logic for -// merging /v2/silences, with additional handling for the enclosing status/data fields. Unlike for -// alerts, the API definitions for silences are almost identical between v1 and v2. The differences -// are that the fields in the JSON output are ordered differently, and the timestamps have more -// precision in v1, but these differences should not be problematic to clients. -type V1SilenceID struct{} - -func (V1SilenceID) MergeResponses(in [][]byte) ([]byte, error) { - type bodyType struct { - Status string `json:"status"` - Data *v2_models.GettableSilence `json:"data"` - } - - silences := make(v2_models.GettableSilences, 0) - for _, body := range in { - parsed := bodyType{} - if err := json.Unmarshal(body, &parsed); err != nil { - return nil, err - } - if parsed.Status != statusSuccess { - return nil, fmt.Errorf("unable to merge response of status: %s", parsed.Status) - } - silences = append(silences, parsed.Data) - } - - merged, err := mergeV2Silences(silences) - if err != nil { - return nil, err - } - - if len(merged) != 1 { - return nil, errors.New("unexpected mismatched silence ids") - } - - body := bodyType{ - Status: statusSuccess, - Data: merged[0], - } - - return json.Marshal(body) -} diff --git a/pkg/alertmanager/merger/v1_silence_id_test.go b/pkg/alertmanager/merger/v1_silence_id_test.go deleted file mode 100644 index 82c33c14804..00000000000 --- a/pkg/alertmanager/merger/v1_silence_id_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/alertmanager/merger/v1_silence_id_test.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Cortex Authors. - -package merger - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestV1SilenceID_ReturnsNewestSilence(t *testing.T) { - in := [][]byte{ - []byte(`{"status":"success","data":{` + - `"id":"77b580dd-1d9c-4b7e-9bba-13ac173cb4e5",` + - `"matchers":[` + - `{` + - `"name":"instance",` + - `"value":"prometheus-one",` + - `"isRegex":false,` + - `"isEqual":true` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.725956017Z",` + - `"endsAt":"2021-04-28T20:31:01.722829007+02:00",` + - `"updatedAt":"2021-04-28T17:32:01.725956017Z",` + - `"createdBy":"",` + - `"comment":"The newer silence",` + - `"status":{"state":"active"}` + - `}}`), - []byte(`{"status":"success","data":{` + - `"id":"77b580dd-1d9c-4b7e-9bba-13ac173cb4e5",` + - `"matchers":[` + - `{` + - `"name":"instance",` + - `"value":"prometheus-one",` + - `"isRegex":false,` + - `"isEqual":true` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.725956017Z",` + - `"endsAt":"2021-04-28T20:31:01.722829007+02:00",` + - `"updatedAt":"2021-04-28T17:31:01.725956017Z",` + - `"createdBy":"",` + - `"comment":"Silence Comment #1",` + - `"status":{"state":"active"}` + - `}}`), - } - - expected := []byte(`{"status":"success","data":{` + - `"id":"77b580dd-1d9c-4b7e-9bba-13ac173cb4e5",` + - `"status":{"state":"active"},` + - `"updatedAt":"2021-04-28T17:32:01.725Z",` + - `"comment":"The newer silence",` + - `"createdBy":"",` + - `"endsAt":"2021-04-28T20:31:01.722+02:00",` + - `"matchers":[` + - `{` + - `"isEqual":true,` + - `"isRegex":false,` + - `"name":"instance",` + - `"value":"prometheus-one"` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.725Z"` + - `}}`) - - out, err := V1SilenceID{}.MergeResponses(in) - require.NoError(t, err) - require.Equal(t, string(expected), string(out)) -} - -func TestV1SilenceID_InvalidDifferentIDs(t *testing.T) { - in := [][]byte{ - []byte(`{"status":"success","data":{` + - `"id":"77b580dd-1d9c-4b7e-9bba-13ac173cb4e5",` + - `"matchers":[` + - `{` + - `"name":"instance",` + - `"value":"prometheus-one",` + - `"isRegex":false,` + - `"isEqual":true` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.725956017Z",` + - `"endsAt":"2021-04-28T20:31:01.722829007+02:00",` + - `"updatedAt":"2021-04-28T17:32:01.725956017Z",` + - `"createdBy":"",` + - `"comment":"Silence Comment #1",` + - `"status":{"state":"active"}` + - `}}`), - []byte(`{"status":"success","data":{` + - `"id":"261248d1-4ff7-4cf1-9957-850c65f4e48b",` + - `"matchers":[` + - `{` + - `"name":"instance",` + - `"value":"prometheus-one",` + - `"isRegex":false,` + - `"isEqual":true` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.725956017Z",` + - `"endsAt":"2021-04-28T20:31:01.722829007+02:00",` + - `"updatedAt":"2021-04-28T17:31:01.725956017Z",` + - `"createdBy":"",` + - `"comment":"Silence Comment #2",` + - `"status":{"state":"active"}` + - `}}`), - } - - _, err := V1SilenceID{}.MergeResponses(in) - require.Error(t, err) -} diff --git a/pkg/alertmanager/merger/v1_silences.go b/pkg/alertmanager/merger/v1_silences.go deleted file mode 100644 index 153b8eb162a..00000000000 --- a/pkg/alertmanager/merger/v1_silences.go +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/alertmanager/merger/v1_silences.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Cortex Authors. - -package merger - -import ( - "encoding/json" - "fmt" - - v2_models "github.com/prometheus/alertmanager/api/v2/models" -) - -// V1Silences implements the Merger interface for GET /v1/silences. Unlike for alerts, the API -// definitions for silences are almost identical between v1 and v2. The differences are that the -// fields in the JSON output are ordered differently, and the timestamps have more precision in v1, -// but these differences should not be problematic to clients. Therefore, the implementation -// re-uses the v2 types, with additional handling for the enclosing status/data fields. -type V1Silences struct{} - -func (V1Silences) MergeResponses(in [][]byte) ([]byte, error) { - type bodyType struct { - Status string `json:"status"` - Data v2_models.GettableSilences `json:"data"` - } - - silences := make(v2_models.GettableSilences, 0) - for _, body := range in { - parsed := bodyType{} - if err := json.Unmarshal(body, &parsed); err != nil { - return nil, err - } - if parsed.Status != statusSuccess { - return nil, fmt.Errorf("unable to merge response of status: %s", parsed.Status) - } - silences = append(silences, parsed.Data...) - } - - merged, err := mergeV2Silences(silences) - if err != nil { - return nil, err - } - body := bodyType{ - Status: statusSuccess, - Data: merged, - } - - return json.Marshal(body) -} diff --git a/pkg/alertmanager/merger/v1_silences_test.go b/pkg/alertmanager/merger/v1_silences_test.go deleted file mode 100644 index 2f19e6338dc..00000000000 --- a/pkg/alertmanager/merger/v1_silences_test.go +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/cortexproject/cortex/blob/master/pkg/alertmanager/merger/v1_silences_test.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Cortex Authors. - -package merger - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestV1Silences(t *testing.T) { - - // This test is to check the parsing round-trip is working as expected, the merging logic is - // tested in TestMergeV2Silences. The test data is based on captures from an actual Alertmanager. - - in := [][]byte{ - []byte(`{"status":"success","data":[` + - `{` + - `"id":"77b580dd-1d9c-4b7e-9bba-13ac173cb4e5",` + - `"matchers":[` + - `{` + - `"name":"instance",` + - `"value":"prometheus-one",` + - `"isRegex":false,` + - `"isEqual":true` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.725956017Z",` + - `"endsAt":"2021-04-28T20:31:01.722829007+02:00",` + - `"updatedAt":"2021-04-28T17:31:01.725956017Z",` + - `"createdBy":"",` + - `"comment":"Silence Comment #1",` + - `"status":{"state":"active"}` + - `},` + - `{` + - `"id":"17526003-c745-4464-a355-4f06de26a236",` + - `"matchers":[` + - `{` + - `"name":"instance",` + - `"value":"prometheus-one",` + - `"isRegex":false,` + - `"isEqual":true` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.731140275Z",` + - `"endsAt":"2021-04-28T18:31:01.727579131Z",` + - `"updatedAt":"2021-04-28T17:31:01.731140275Z",` + - `"createdBy":"",` + - `"comment":"Silence Comment #2",` + - `"status":{"state":"active"}},` + - `{` + - `"id":"261248d1-4ff7-4cf1-9957-850c65f4e48b",` + - `"matchers":[` + - `{` + - `"name":"instance",` + - `"value":"prometheus-one",` + - `"isRegex":false,` + - `"isEqual":true` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.73572697Z",` + - `"endsAt":"2021-04-28T18:31:01.732873879Z",` + - `"updatedAt":"2021-04-28T17:31:01.73572697Z",` + - `"createdBy":"",` + - `"comment":"Silence Comment #3",` + - `"status":{"state":"active"}}` + - `]}`), - []byte(`{"status":"success","data":[]}`), - } - - // Note that our implementation for v1 uses v2 code internally. This means the JSON fields - // come out in a slightly different order, and the timestamps lave less digits. - expected := []byte(`{"status":"success","data":[` + - `{` + - `"id":"77b580dd-1d9c-4b7e-9bba-13ac173cb4e5",` + - `"status":{"state":"active"},` + - `"updatedAt":"2021-04-28T17:31:01.725Z",` + - `"comment":"Silence Comment #1",` + - `"createdBy":"",` + - `"endsAt":"2021-04-28T20:31:01.722+02:00",` + - `"matchers":[` + - `{` + - `"isEqual":true,` + - `"isRegex":false,` + - `"name":"instance",` + - `"value":"prometheus-one"` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.725Z"` + - `},` + - `{` + - `"id":"17526003-c745-4464-a355-4f06de26a236",` + - `"status":{"state":"active"},` + - `"updatedAt":"2021-04-28T17:31:01.731Z",` + - `"comment":"Silence Comment #2",` + - `"createdBy":"",` + - `"endsAt":"2021-04-28T18:31:01.727Z",` + - `"matchers":[` + - `{` + - `"isEqual":true,` + - `"isRegex":false,` + - `"name":"instance",` + - `"value":"prometheus-one"` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.731Z"` + - `},` + - `{` + - `"id":"261248d1-4ff7-4cf1-9957-850c65f4e48b",` + - `"status":{"state":"active"},` + - `"updatedAt":"2021-04-28T17:31:01.735Z",` + - `"comment":"Silence Comment #3",` + - `"createdBy":"",` + - `"endsAt":"2021-04-28T18:31:01.732Z",` + - `"matchers":[` + - `{` + - `"isEqual":true,` + - `"isRegex":false,` + - `"name":"instance",` + - `"value":"prometheus-one"` + - `}` + - `],` + - `"startsAt":"2021-04-28T17:31:01.735Z"` + - `}` + - `]}`) - - out, err := V1Silences{}.MergeResponses(in) - require.NoError(t, err) - require.Equal(t, string(expected), string(out)) -} diff --git a/pkg/alertmanager/multitenant.go b/pkg/alertmanager/multitenant.go index f7ebbdaeef4..2800c832202 100644 --- a/pkg/alertmanager/multitenant.go +++ b/pkg/alertmanager/multitenant.go @@ -31,6 +31,7 @@ import ( "github.com/pkg/errors" "github.com/prometheus/alertmanager/cluster/clusterpb" amconfig "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "golang.org/x/time/rate" @@ -96,7 +97,7 @@ const ( defaultPeerTimeout = 15 * time.Second ) -// RegisterFlags adds the flags required to config this to the given FlagSet. +// RegisterFlags adds the features required to config this to the given FlagSet. func (cfg *MultitenantAlertmanagerConfig) RegisterFlags(f *flag.FlagSet, logger log.Logger) { f.StringVar(&cfg.DataDir, "alertmanager.storage.path", "./data-alertmanager/", "Directory to store Alertmanager state and temporarily configuration files. The content of this directory is not required to be persisted between restarts unless Alertmanager replication has been disabled.") f.DurationVar(&cfg.Retention, "alertmanager.storage.retention", 5*24*time.Hour, "How long should we store stateful data (notification logs and silences). For notification log entries, refers to how long should we keep entries before they expire and are deleted. For silences, refers to how long should tenants view silences after they expire and are deleted.") @@ -273,7 +274,8 @@ type MultitenantAlertmanager struct { alertmanagerClientsPool ClientsPool - limits Limits + limits Limits + features featurecontrol.Flagger registry prometheus.Registerer ringCheckErrors prometheus.Counter @@ -284,7 +286,7 @@ type MultitenantAlertmanager struct { } // NewMultitenantAlertmanager creates a new MultitenantAlertmanager. -func NewMultitenantAlertmanager(cfg *MultitenantAlertmanagerConfig, store alertstore.AlertStore, limits Limits, logger log.Logger, registerer prometheus.Registerer) (*MultitenantAlertmanager, error) { +func NewMultitenantAlertmanager(cfg *MultitenantAlertmanagerConfig, store alertstore.AlertStore, limits Limits, features featurecontrol.Flagger, logger log.Logger, registerer prometheus.Registerer) (*MultitenantAlertmanager, error) { err := os.MkdirAll(cfg.DataDir, 0777) if err != nil { return nil, fmt.Errorf("unable to create Alertmanager data directory %q: %s", cfg.DataDir, err) @@ -305,7 +307,7 @@ func NewMultitenantAlertmanager(cfg *MultitenantAlertmanagerConfig, store alerts return nil, errors.Wrap(err, "create KV store client") } - return createMultitenantAlertmanager(cfg, fallbackConfig, store, ringStore, limits, logger, registerer) + return createMultitenantAlertmanager(cfg, fallbackConfig, store, ringStore, limits, features, logger, registerer) } // ComputeFallbackConfig will load, vaildate and return the provided fallbackConfigFile @@ -341,7 +343,7 @@ func ComputeFallbackConfig(fallbackConfigFile string) ([]byte, error) { return fallbackConfig, nil } -func createMultitenantAlertmanager(cfg *MultitenantAlertmanagerConfig, fallbackConfig []byte, store alertstore.AlertStore, ringStore kv.Client, limits Limits, logger log.Logger, registerer prometheus.Registerer) (*MultitenantAlertmanager, error) { +func createMultitenantAlertmanager(cfg *MultitenantAlertmanagerConfig, fallbackConfig []byte, store alertstore.AlertStore, ringStore kv.Client, limits Limits, features featurecontrol.Flagger, logger log.Logger, registerer prometheus.Registerer) (*MultitenantAlertmanager, error) { am := &MultitenantAlertmanager{ cfg: cfg, fallbackConfig: string(fallbackConfig), @@ -353,6 +355,7 @@ func createMultitenantAlertmanager(cfg *MultitenantAlertmanagerConfig, fallbackC logger: log.With(logger, "component", "MultiTenantAlertmanager"), registry: registerer, limits: limits, + features: features, ringCheckErrors: promauto.With(registerer).NewCounter(prometheus.CounterOpts{ Name: "cortex_alertmanager_ring_check_errors_total", Help: "Number of errors that have occurred when checking the ring for ownership.", @@ -786,6 +789,7 @@ func (am *MultitenantAlertmanager) newAlertmanager(userID string, amConfig *amco Store: am.store, PersisterConfig: am.cfg.Persister, Limits: am.limits, + Features: am.features, }, reg) if err != nil { return nil, fmt.Errorf("unable to start Alertmanager for user %v: %v", userID, err) diff --git a/pkg/alertmanager/multitenant_test.go b/pkg/alertmanager/multitenant_test.go index 34f78dcf479..4036334c3b3 100644 --- a/pkg/alertmanager/multitenant_test.go +++ b/pkg/alertmanager/multitenant_test.go @@ -36,6 +36,7 @@ import ( "github.com/grafana/regexp" "github.com/prometheus/alertmanager/cluster/clusterpb" amconfig "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/featurecontrol" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/types" @@ -94,7 +95,7 @@ func mockAlertmanagerConfig(t *testing.T) *MultitenantAlertmanagerConfig { return cfg } -func setupSingleMultitenantAlertmanager(t *testing.T, cfg *MultitenantAlertmanagerConfig, store alertstore.AlertStore, limits Limits, logger log.Logger, registerer prometheus.Registerer) *MultitenantAlertmanager { +func setupSingleMultitenantAlertmanager(t *testing.T, cfg *MultitenantAlertmanagerConfig, store alertstore.AlertStore, limits Limits, features featurecontrol.Flagger, logger log.Logger, registerer prometheus.Registerer) *MultitenantAlertmanager { // The mock ring store means we do not need a real e.g. Consul running. ringStore, closer := consul.NewInMemoryClient(ring.GetCodec(), log.NewNopLogger(), nil) t.Cleanup(func() { @@ -105,7 +106,7 @@ func setupSingleMultitenantAlertmanager(t *testing.T, cfg *MultitenantAlertmanag amCfg, err := ComputeFallbackConfig("") require.NoError(t, err) - am, err := createMultitenantAlertmanager(cfg, amCfg, store, ringStore, limits, logger, registerer) + am, err := createMultitenantAlertmanager(cfg, amCfg, store, ringStore, limits, features, logger, registerer) require.NoError(t, err) // The mock client pool allows the distributor to talk to the instance @@ -237,7 +238,7 @@ templates: require.NoError(t, err) cfg.DataDir, err = filepath.Rel(cwd, cfg.DataDir) require.NoError(t, err) - am := setupSingleMultitenantAlertmanager(t, cfg, store, nil, log.NewNopLogger(), reg) + am := setupSingleMultitenantAlertmanager(t, cfg, store, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) // Ensure the configs are synced correctly and persist across several syncs for i := 0; i < 3; i++ { @@ -273,7 +274,7 @@ func TestMultitenantAlertmanager_loadAndSyncConfigs(t *testing.T) { reg := prometheus.NewPedanticRegistry() cfg := mockAlertmanagerConfig(t) - am := setupSingleMultitenantAlertmanager(t, cfg, store, nil, log.NewNopLogger(), reg) + am := setupSingleMultitenantAlertmanager(t, cfg, store, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) // Ensure the configs are synced correctly err := am.loadAndSyncConfigs(context.Background(), reasonPeriodic) @@ -650,11 +651,13 @@ receivers: overrides, err := validation.NewOverrides(limits, nil) require.NoError(t, err) + features := featurecontrol.NoopFlags{} + // Start the alertmanager. reg := prometheus.NewPedanticRegistry() logs := &concurrency.SyncBuffer{} logger := log.NewLogfmtLogger(logs) - am := setupSingleMultitenantAlertmanager(t, cfg, store, overrides, logger, reg) + am := setupSingleMultitenantAlertmanager(t, cfg, store, overrides, features, logger, reg) // Ensure the configs are synced correctly. require.NoError(t, testutil.GatherAndCompare(reg, bytes.NewBufferString(` @@ -678,7 +681,7 @@ receivers: require.NoError(t, err) // Push an alert. - req := httptest.NewRequest(http.MethodPost, cfg.ExternalURL.String()+"/api/v1/alerts", bytes.NewReader(alertsPayload)) + req := httptest.NewRequest(http.MethodPost, cfg.ExternalURL.String()+"/api/v2/alerts", bytes.NewReader(alertsPayload)) req.Header.Set("content-type", "application/json") reqCtx := user.InjectOrgID(req.Context(), userID) { @@ -748,7 +751,7 @@ func TestMultitenantAlertmanager_deleteUnusedLocalUserState(t *testing.T) { reg := prometheus.NewPedanticRegistry() cfg := mockAlertmanagerConfig(t) - am := setupSingleMultitenantAlertmanager(t, cfg, store, nil, log.NewNopLogger(), reg) + am := setupSingleMultitenantAlertmanager(t, cfg, store, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) createFile(t, filepath.Join(cfg.DataDir, user1, notificationLogSnapshot)) createFile(t, filepath.Join(cfg.DataDir, user1, silencesSnapshot)) @@ -795,7 +798,7 @@ func TestMultitenantAlertmanager_zoneAwareSharding(t *testing.T) { cfg.ShardingRing.ZoneAwarenessEnabled = true cfg.ShardingRing.InstanceZone = zone - am, err := createMultitenantAlertmanager(cfg, nil, alertStore, ringStore, nil, log.NewLogfmtLogger(os.Stdout), reg) + am, err := createMultitenantAlertmanager(cfg, nil, alertStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewLogfmtLogger(os.Stdout), reg) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, services.StopAndAwaitTerminated(ctx, am)) @@ -867,7 +870,7 @@ func TestMultitenantAlertmanager_deleteUnusedRemoteUserState(t *testing.T) { // Increase state write interval so that state gets written sooner, making test faster. cfg.Persister.Interval = 500 * time.Millisecond - am, err := createMultitenantAlertmanager(cfg, nil, alertStore, ringStore, nil, log.NewLogfmtLogger(os.Stdout), reg) + am, err := createMultitenantAlertmanager(cfg, nil, alertStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewLogfmtLogger(os.Stdout), reg) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, services.StopAndAwaitTerminated(ctx, am)) @@ -963,7 +966,7 @@ func TestMultitenantAlertmanager_deleteUnusedRemoteUserStateDisabled(t *testing. // Disable state cleanup. cfg.EnableStateCleanup = false - am, err := createMultitenantAlertmanager(cfg, nil, alertStore, ringStore, nil, log.NewLogfmtLogger(os.Stdout), reg) + am, err := createMultitenantAlertmanager(cfg, nil, alertStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewLogfmtLogger(os.Stdout), reg) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, services.StopAndAwaitTerminated(ctx, am)) @@ -1056,7 +1059,7 @@ func TestMultitenantAlertmanager_ServeHTTP(t *testing.T) { // Create the Multitenant Alertmanager. reg := prometheus.NewPedanticRegistry() - am := setupSingleMultitenantAlertmanager(t, amConfig, store, nil, log.NewNopLogger(), reg) + am := setupSingleMultitenantAlertmanager(t, amConfig, store, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) // Request when fallback user configuration is used, as user hasn't // created a configuration yet. @@ -1163,12 +1166,12 @@ receivers: amConfig.ExternalURL = externalURL // Create the Multitenant Alertmanager. - am := setupSingleMultitenantAlertmanager(t, amConfig, store, nil, log.NewNopLogger(), nil) + am := setupSingleMultitenantAlertmanager(t, amConfig, store, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), nil) require.NoError(t, err) am.fallbackConfig = fallbackCfg // Request when no user configuration is present. - req := httptest.NewRequest("GET", externalURL.String()+"/api/v1/status", nil) + req := httptest.NewRequest("GET", externalURL.String()+"/api/v2/status", nil) w := httptest.NewRecorder() am.ServeHTTP(w, req.WithContext(user.InjectOrgID(req.Context(), "user1"))) @@ -1234,7 +1237,7 @@ receivers: amConfig.ExternalURL = externalURL // Create the Multitenant Alertmanager. - am := setupSingleMultitenantAlertmanager(t, amConfig, store, nil, log.NewNopLogger(), nil) + am := setupSingleMultitenantAlertmanager(t, amConfig, store, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), nil) am.fallbackConfig = fallbackCfg // Upload config for the user. @@ -1245,7 +1248,7 @@ receivers: })) // Request before the user configuration loaded by polling loop. - req := httptest.NewRequest("GET", externalURL.String()+"/api/v1/status", nil) + req := httptest.NewRequest("GET", externalURL.String()+"/api/v2/status", nil) w := httptest.NewRecorder() am.ServeHTTP(w, req.WithContext(user.InjectOrgID(req.Context(), "user1"))) resp := w.Result() @@ -1334,7 +1337,7 @@ func TestMultitenantAlertmanager_InitialSync(t *testing.T) { })) } - am, err := createMultitenantAlertmanager(amConfig, nil, alertStore, ringStore, nil, log.NewNopLogger(), nil) + am, err := createMultitenantAlertmanager(amConfig, nil, alertStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), nil) require.NoError(t, err) defer services.StopAndAwaitTerminated(ctx, am) //nolint:errcheck @@ -1439,7 +1442,7 @@ func TestMultitenantAlertmanager_PerTenantSharding(t *testing.T) { amConfig.ShardingRing.RingCheckPeriod = time.Hour reg := prometheus.NewPedanticRegistry() - am, err := createMultitenantAlertmanager(amConfig, nil, alertStore, ringStore, nil, log.NewNopLogger(), reg) + am, err := createMultitenantAlertmanager(amConfig, nil, alertStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) require.NoError(t, err) defer services.StopAndAwaitTerminated(ctx, am) //nolint:errcheck @@ -1594,7 +1597,7 @@ func TestMultitenantAlertmanager_SyncOnRingTopologyChanges(t *testing.T) { alertStore := prepareInMemoryAlertStore() reg := prometheus.NewPedanticRegistry() - am, err := createMultitenantAlertmanager(amConfig, nil, alertStore, ringStore, nil, log.NewNopLogger(), reg) + am, err := createMultitenantAlertmanager(amConfig, nil, alertStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) require.NoError(t, err) require.NoError(t, ringStore.CAS(ctx, RingKey, func(in interface{}) (interface{}, bool, error) { @@ -1645,7 +1648,7 @@ func TestMultitenantAlertmanager_RingLifecyclerShouldAutoForgetUnhealthyInstance alertStore := prepareInMemoryAlertStore() - am, err := createMultitenantAlertmanager(amConfig, nil, alertStore, ringStore, nil, log.NewNopLogger(), nil) + am, err := createMultitenantAlertmanager(amConfig, nil, alertStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), nil) require.NoError(t, err) require.NoError(t, services.StartAndAwaitRunning(ctx, am)) defer services.StopAndAwaitTerminated(ctx, am) //nolint:errcheck @@ -1682,7 +1685,7 @@ func TestMultitenantAlertmanager_InitialSyncFailure(t *testing.T) { bkt.MockIter("alertmanager/", nil, nil) store := bucketclient.NewBucketAlertStore(bkt, nil, log.NewNopLogger()) - am, err := createMultitenantAlertmanager(amConfig, nil, store, ringStore, nil, log.NewNopLogger(), nil) + am, err := createMultitenantAlertmanager(amConfig, nil, store, ringStore, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), nil) require.NoError(t, err) defer services.StopAndAwaitTerminated(ctx, am) //nolint:errcheck @@ -1725,7 +1728,7 @@ func TestAlertmanager_ReplicasPosition(t *testing.T) { amConfig.ShardingRing.RingCheckPeriod = time.Hour reg := prometheus.NewPedanticRegistry() - am, err := createMultitenantAlertmanager(amConfig, nil, mockStore, ringStore, nil, log.NewNopLogger(), reg) + am, err := createMultitenantAlertmanager(amConfig, nil, mockStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) require.NoError(t, err) defer services.StopAndAwaitTerminated(ctx, am) //nolint:errcheck @@ -1831,7 +1834,7 @@ func TestAlertmanager_StateReplication(t *testing.T) { amConfig.ShardingRing.RingCheckPeriod = time.Hour reg := prometheus.NewPedanticRegistry() - am, err := createMultitenantAlertmanager(amConfig, nil, mockStore, ringStore, nil, log.NewNopLogger(), reg) + am, err := createMultitenantAlertmanager(amConfig, nil, mockStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) require.NoError(t, err) defer services.StopAndAwaitTerminated(ctx, am) //nolint:errcheck @@ -2010,7 +2013,7 @@ func TestAlertmanager_StateReplication_InitialSyncFromPeers(t *testing.T) { amConfig.ShardingRing.RingCheckPeriod = time.Hour reg := prometheus.NewPedanticRegistry() - am, err := createMultitenantAlertmanager(amConfig, nil, mockStore, ringStore, nil, log.NewNopLogger(), reg) + am, err := createMultitenantAlertmanager(amConfig, nil, mockStore, ringStore, nil, featurecontrol.NoopFlags{}, log.NewNopLogger(), reg) require.NoError(t, err) clientPool.setServer(amConfig.ShardingRing.Common.InstanceAddr+":0", am) @@ -2251,11 +2254,12 @@ receivers: emailNotificationRateLimit: 0, emailNotificationBurst: 0, } + features := featurecontrol.NoopFlags{} reg := prometheus.NewPedanticRegistry() cfg := mockAlertmanagerConfig(t) - am := setupSingleMultitenantAlertmanager(t, cfg, store, &limits, log.NewNopLogger(), reg) + am := setupSingleMultitenantAlertmanager(t, cfg, store, &limits, features, log.NewNopLogger(), reg) err := am.loadAndSyncConfigs(context.Background(), reasonPeriodic) require.NoError(t, err) diff --git a/pkg/mimir/modules.go b/pkg/mimir/modules.go index fd928786fc5..ffbeba88477 100644 --- a/pkg/mimir/modules.go +++ b/pkg/mimir/modules.go @@ -26,6 +26,8 @@ import ( "github.com/opentracing-contrib/go-stdlib/nethttp" "github.com/opentracing/opentracing-go" "github.com/pkg/errors" + "github.com/prometheus/alertmanager/featurecontrol" + "github.com/prometheus/alertmanager/matchers/compat" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/config" "github.com/prometheus/prometheus/model/labels" @@ -881,6 +883,13 @@ func (t *Mimir) initRuler() (serv services.Service, err error) { } func (t *Mimir) initAlertManager() (serv services.Service, err error) { + // Initialize the compat package in classic mode. This has the same behavior as if + // the compat package was not initialized, but with debug logs when a configuration + // is loaded or a new configuration is submitted. + features, err := featurecontrol.NewFlags(util_log.Logger, featurecontrol.FeatureClassicMode) + util_log.CheckFatal("initializing Alertmanager feature flags", err) + compat.InitFromFlags(util_log.Logger, compat.RegisteredMetrics, features) + t.Cfg.Alertmanager.ShardingRing.Common.ListenPort = t.Cfg.Server.GRPCListenPort t.Cfg.Alertmanager.CheckExternalURL(t.Cfg.API.AlertmanagerHTTPPrefix, util_log.Logger) @@ -889,7 +898,7 @@ func (t *Mimir) initAlertManager() (serv services.Service, err error) { return } - t.Alertmanager, err = alertmanager.NewMultitenantAlertmanager(&t.Cfg.Alertmanager, store, t.Overrides, util_log.Logger, t.Registerer) + t.Alertmanager, err = alertmanager.NewMultitenantAlertmanager(&t.Cfg.Alertmanager, store, t.Overrides, features, util_log.Logger, t.Registerer) if err != nil { return } diff --git a/vendor/github.com/hashicorp/go-sockaddr/LICENSE b/vendor/github.com/hashicorp/go-sockaddr/LICENSE index a612ad9813b..be467e386bb 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/LICENSE +++ b/vendor/github.com/hashicorp/go-sockaddr/LICENSE @@ -1,3 +1,5 @@ +Copyright (c) 2016 HashiCorp, Inc. + Mozilla Public License Version 2.0 ================================== diff --git a/vendor/github.com/hashicorp/go-sockaddr/ifaddrs.go b/vendor/github.com/hashicorp/go-sockaddr/ifaddrs.go index 80f61bef680..d2713afbf7e 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/ifaddrs.go +++ b/vendor/github.com/hashicorp/go-sockaddr/ifaddrs.go @@ -16,8 +16,10 @@ var ( // Centralize all regexps and regexp.Copy() where necessary. signRE *regexp.Regexp = regexp.MustCompile(`^[\s]*[+-]`) whitespaceRE *regexp.Regexp = regexp.MustCompile(`[\s]+`) - ifNameRE *regexp.Regexp = regexp.MustCompile(`^(?:Ethernet|Wireless LAN) adapter ([^:]+):`) - ipAddrRE *regexp.Regexp = regexp.MustCompile(`^ IPv[46] Address\. \. \. \. \. \. \. \. \. \. \. : ([^\s]+)`) + // These regular expressions enable the deprecated parseDefaultIfNameWindows + // and should be removed when those functions are. + ifNameRE *regexp.Regexp = regexp.MustCompile(`^(?:Ethernet|Wireless LAN) adapter ([^:]+):`) + ipAddrRE *regexp.Regexp = regexp.MustCompile(`^ IPv[46] Address\. \. \. \. \. \. \. \. \. \. \. : ([^\s]+)`) ) // IfAddrs is a slice of IfAddr @@ -1214,7 +1216,7 @@ func parseDefaultIfNameFromIPCmd(routeOut string) (string, error) { // Android. func parseDefaultIfNameFromIPCmdAndroid(routeOut string) (string, error) { parsedLines := parseIfNameFromIPCmd(routeOut) - if (len(parsedLines) > 0) { + if len(parsedLines) > 0 { ifName := strings.TrimSpace(parsedLines[0][4]) return ifName, nil } @@ -1222,7 +1224,6 @@ func parseDefaultIfNameFromIPCmdAndroid(routeOut string) (string, error) { return "", errors.New("No default interface found") } - // parseIfNameFromIPCmd parses interfaces from ip(8) for // Linux. func parseIfNameFromIPCmd(routeOut string) [][]string { @@ -1241,6 +1242,10 @@ func parseIfNameFromIPCmd(routeOut string) [][]string { // parseDefaultIfNameWindows parses the default interface from `netstat -rn` and // `ipconfig` on Windows. +// +// This has been deprecated in favor of a Powershell-based solution because of +// issues with localized Windows versions, but is currently retained for backward +// compatibility func parseDefaultIfNameWindows(routeOut, ipconfigOut string) (string, error) { defaultIPAddr, err := parseDefaultIPAddrWindowsRoute(routeOut) if err != nil { @@ -1262,6 +1267,10 @@ func parseDefaultIfNameWindows(routeOut, ipconfigOut string) (string, error) { // IPv6 connected host, submit an issue on github.com/hashicorp/go-sockaddr with // the output from `netstat -rn`, `ipconfig`, and version of Windows to see IPv6 // support added. +// +// This has been deprecated in favor of a Powershell-based solution because of +// issues with localized Windows versions, but is currently retained for backward +// compatibility. func parseDefaultIPAddrWindowsRoute(routeOut string) (string, error) { lines := strings.Split(routeOut, "\n") re := whitespaceRE.Copy() @@ -1282,6 +1291,10 @@ func parseDefaultIPAddrWindowsRoute(routeOut string) (string, error) { // parseDefaultIfNameWindowsIPConfig parses the output of `ipconfig` to find the // interface name forwarding traffic to the default gateway. +// +// This has been deprecated in favor of a Powershell-based solution because of +// issues with localized Windows versions, but is currently retained for backward +// compatibility func parseDefaultIfNameWindowsIPConfig(defaultIPAddr, routeOut string) (string, error) { lines := strings.Split(routeOut, "\n") ifNameRe := ifNameRE.Copy() diff --git a/vendor/github.com/hashicorp/go-sockaddr/route_info.go b/vendor/github.com/hashicorp/go-sockaddr/route_info.go index 2a3ee1db9e8..601a2b5838c 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/route_info.go +++ b/vendor/github.com/hashicorp/go-sockaddr/route_info.go @@ -1,5 +1,12 @@ package sockaddr +import "errors" + +var ( + ErrNoInterface = errors.New("No default interface found (unsupported platform)") + ErrNoRoute = errors.New("no route info found (unsupported platform)") +) + // RouteInterface specifies an interface for obtaining memoized route table and // network information from a given OS. type RouteInterface interface { @@ -9,6 +16,10 @@ type RouteInterface interface { GetDefaultInterfaceName() (string, error) } +type routeInfo struct { + cmds map[string][]string +} + // VisitCommands visits each command used by the platform-specific RouteInfo // implementation. func (ri routeInfo) VisitCommands(fn func(name string, cmd []string)) { diff --git a/vendor/github.com/hashicorp/go-sockaddr/route_info_bsd.go b/vendor/github.com/hashicorp/go-sockaddr/route_info_bsd.go index 705757abc7b..f5e548ac703 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/route_info_bsd.go +++ b/vendor/github.com/hashicorp/go-sockaddr/route_info_bsd.go @@ -1,17 +1,14 @@ +//go:build darwin || dragonfly || freebsd || netbsd || openbsd // +build darwin dragonfly freebsd netbsd openbsd package sockaddr import "os/exec" -var cmds map[string][]string = map[string][]string{ +var cmds = map[string][]string{ "route": {"/sbin/route", "-n", "get", "default"}, } -type routeInfo struct { - cmds map[string][]string -} - // NewRouteInfo returns a BSD-specific implementation of the RouteInfo // interface. func NewRouteInfo() (routeInfo, error) { diff --git a/vendor/github.com/hashicorp/go-sockaddr/route_info_default.go b/vendor/github.com/hashicorp/go-sockaddr/route_info_default.go index d1b009f6538..6df864ba0fc 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/route_info_default.go +++ b/vendor/github.com/hashicorp/go-sockaddr/route_info_default.go @@ -1,10 +1,19 @@ -// +build android nacl plan9 +//go:build android || nacl || plan9 || js +// +build android nacl plan9 js package sockaddr -import "errors" - // getDefaultIfName is the default interface function for unsupported platforms. func getDefaultIfName() (string, error) { - return "", errors.New("No default interface found (unsupported platform)") + return "", ErrNoInterface +} + +func NewRouteInfo() (routeInfo, error) { + return routeInfo{}, ErrNoRoute +} + +// GetDefaultInterfaceName returns the interface name attached to the default +// route on the default interface. +func (ri routeInfo) GetDefaultInterfaceName() (string, error) { + return "", ErrNoInterface } diff --git a/vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go b/vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go index b62ce6ecb21..cc5f68d0c83 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go +++ b/vendor/github.com/hashicorp/go-sockaddr/route_info_linux.go @@ -1,3 +1,4 @@ +//go:build !android // +build !android package sockaddr @@ -7,10 +8,6 @@ import ( "os/exec" ) -type routeInfo struct { - cmds map[string][]string -} - // NewRouteInfo returns a Linux-specific implementation of the RouteInfo // interface. func NewRouteInfo() (routeInfo, error) { diff --git a/vendor/github.com/hashicorp/go-sockaddr/route_info_solaris.go b/vendor/github.com/hashicorp/go-sockaddr/route_info_solaris.go index ee8e7984d79..5843d58c2eb 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/route_info_solaris.go +++ b/vendor/github.com/hashicorp/go-sockaddr/route_info_solaris.go @@ -9,10 +9,6 @@ var cmds map[string][]string = map[string][]string{ "route": {"/usr/sbin/route", "-n", "get", "default"}, } -type routeInfo struct { - cmds map[string][]string -} - // NewRouteInfo returns a BSD-specific implementation of the RouteInfo // interface. func NewRouteInfo() (routeInfo, error) { diff --git a/vendor/github.com/hashicorp/go-sockaddr/route_info_test_windows.go b/vendor/github.com/hashicorp/go-sockaddr/route_info_test_windows.go new file mode 100644 index 00000000000..a6eacdb2cf3 --- /dev/null +++ b/vendor/github.com/hashicorp/go-sockaddr/route_info_test_windows.go @@ -0,0 +1,25 @@ +package sockaddr + +import "testing" + +func Test_parseWindowsDefaultIfName_new_vs_old(t *testing.T) { + if !hasPowershell() { + t.Skip("this test requires powershell.") + return + } + ri, err := NewRouteInfo() + if err != nil { + t.Fatalf("bad: %v", err) + } + psVer, err1 := ri.GetDefaultInterfaceName() + legacyVer, err2 := ri.GetDefaultInterfaceNameLegacy() + if err1 != nil { + t.Errorf("err != nil for GetDefaultInterfaceName - %v", err1) + } + if err2 != nil { + t.Errorf("err != nil for GetDefaultInterfaceNameLegacy - %v", err2) + } + if psVer != legacyVer { + t.Errorf("got %s; want %s", psVer, legacyVer) + } +} diff --git a/vendor/github.com/hashicorp/go-sockaddr/route_info_windows.go b/vendor/github.com/hashicorp/go-sockaddr/route_info_windows.go index 3da972883e8..ed9bd0a3860 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/route_info_windows.go +++ b/vendor/github.com/hashicorp/go-sockaddr/route_info_windows.go @@ -1,16 +1,18 @@ package sockaddr -import "os/exec" +import ( + "os/exec" + "strings" +) var cmds map[string][]string = map[string][]string{ + "defaultInterface": {"powershell", "Get-NetRoute -DestinationPrefix '0.0.0.0/0' | select -ExpandProperty InterfaceAlias"}, + // These commands enable GetDefaultInterfaceNameLegacy and should be removed + // when it is. "netstat": {"netstat", "-rn"}, "ipconfig": {"ipconfig"}, } -type routeInfo struct { - cmds map[string][]string -} - // NewRouteInfo returns a BSD-specific implementation of the RouteInfo // interface. func NewRouteInfo() (routeInfo, error) { @@ -22,6 +24,23 @@ func NewRouteInfo() (routeInfo, error) { // GetDefaultInterfaceName returns the interface name attached to the default // route on the default interface. func (ri routeInfo) GetDefaultInterfaceName() (string, error) { + if !hasPowershell() { + // No powershell, fallback to legacy method + return ri.GetDefaultInterfaceNameLegacy() + } + + ifNameOut, err := exec.Command(cmds["defaultInterface"][0], cmds["defaultInterface"][1:]...).Output() + if err != nil { + return "", err + } + + ifName := strings.TrimSpace(string(ifNameOut[:])) + return ifName, nil +} + +// GetDefaultInterfaceNameLegacy provides legacy behavior for GetDefaultInterfaceName +// on Windows machines without powershell. +func (ri routeInfo) GetDefaultInterfaceNameLegacy() (string, error) { ifNameOut, err := exec.Command(cmds["netstat"][0], cmds["netstat"][1:]...).Output() if err != nil { return "", err @@ -39,3 +58,8 @@ func (ri routeInfo) GetDefaultInterfaceName() (string, error) { return ifName, nil } + +func hasPowershell() bool { + _, err := exec.LookPath("powershell") + return (err != nil) +} diff --git a/vendor/github.com/hashicorp/go-sockaddr/unixsock.go b/vendor/github.com/hashicorp/go-sockaddr/unixsock.go index f3be3f67e77..17aed98adf0 100644 --- a/vendor/github.com/hashicorp/go-sockaddr/unixsock.go +++ b/vendor/github.com/hashicorp/go-sockaddr/unixsock.go @@ -27,6 +27,16 @@ func NewUnixSock(s string) (ret UnixSock, err error) { return ret, nil } +// Contains returns true if sa and us have the same path +func (us UnixSock) Contains(sa SockAddr) bool { + usb, ok := sa.(UnixSock) + if !ok { + return false + } + + return usb.path == us.path +} + // CmpAddress follows the Cmp() standard protocol and returns: // // - -1 If the receiver should sort first because its name lexically sorts before arg @@ -41,6 +51,9 @@ func (us UnixSock) CmpAddress(sa SockAddr) int { return strings.Compare(us.Path(), usb.Path()) } +// CmpRFC doesn't make sense for a Unix socket, so just return defer decision +func (us UnixSock) CmpRFC(rfcNum uint, sa SockAddr) int { return sortDeferDecision } + // DialPacketArgs returns the arguments required to be passed to net.DialUnix() // with the `unixgram` network type. func (us UnixSock) DialPacketArgs() (network, dialArgs string) { diff --git a/vendor/github.com/prometheus/alertmanager/api/api.go b/vendor/github.com/prometheus/alertmanager/api/api.go index 59bfb76da66..2e1e1ea4255 100644 --- a/vendor/github.com/prometheus/alertmanager/api/api.go +++ b/vendor/github.com/prometheus/alertmanager/api/api.go @@ -25,7 +25,6 @@ import ( "github.com/prometheus/common/model" "github.com/prometheus/common/route" - apiv1 "github.com/prometheus/alertmanager/api/v1" apiv2 "github.com/prometheus/alertmanager/api/v2" "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/alertmanager/config" @@ -37,8 +36,9 @@ import ( // API represents all APIs of Alertmanager. type API struct { - v1 *apiv1.API - v2 *apiv2.API + v2 *apiv2.API + deprecationRouter *V1DeprecationRouter + requestsInFlight prometheus.Gauge concurrencyLimitExceeded prometheus.Counter timeout time.Duration @@ -96,7 +96,7 @@ func (o Options) validate() error { // call is also needed to get the APIs into an operational state. func New(opts Options) (*API, error) { if err := opts.validate(); err != nil { - return nil, fmt.Errorf("invalid API options: %s", err) + return nil, fmt.Errorf("invalid API options: %w", err) } l := opts.Logger if l == nil { @@ -110,15 +110,6 @@ func New(opts Options) (*API, error) { } } - v1 := apiv1.New( - opts.Alerts, - opts.Silences, - opts.StatusFunc, - opts.Peer, - log.With(l, "version", "v1"), - opts.Registry, - ) - v2, err := apiv2.NewAPI( opts.Alerts, opts.GroupFunc, @@ -154,7 +145,7 @@ func New(opts Options) (*API, error) { } return &API{ - v1: v1, + deprecationRouter: NewV1DeprecationRouter(log.With(l, "version", "v1")), v2: v2, requestsInFlight: requestsInFlight, concurrencyLimitExceeded: concurrencyLimitExceeded, @@ -163,8 +154,7 @@ func New(opts Options) (*API, error) { }, nil } -// Register all APIs. It registers APIv1 with the provided router directly. As -// APIv2 works on the http.Handler level, this method also creates a new +// Register API. As APIv2 works on the http.Handler level, this method also creates a new // http.ServeMux and then uses it to register both the provided router (to // handle "/") and APIv2 (to handle "/api/v2"). The method returns // the newly created http.ServeMux. If a timeout has been set on construction of @@ -172,7 +162,8 @@ func New(opts Options) (*API, error) { // true for the concurrency limit, with the exception that it is only applied to // GET requests. func (api *API) Register(r *route.Router, routePrefix string) *http.ServeMux { - api.v1.Register(r.WithPrefix("/api/v1")) + // TODO(gotjosh) API V1 was removed as of version 0.28, when we reach 1.0.0 we should removed these deprecation warnings. + api.deprecationRouter.Register(r.WithPrefix("/api/v1")) mux := http.NewServeMux() mux.Handle("/", api.limitHandler(r)) @@ -196,7 +187,6 @@ func (api *API) Register(r *route.Router, routePrefix string) *http.ServeMux { // Update config and resolve timeout of each API. APIv2 also needs // setAlertStatus to be updated. func (api *API) Update(cfg *config.Config, setAlertStatus func(model.LabelSet)) { - api.v1.Update(cfg) api.v2.Update(cfg, setAlertStatus) } diff --git a/vendor/github.com/prometheus/alertmanager/api/metrics/metrics.go b/vendor/github.com/prometheus/alertmanager/api/metrics/metrics.go index 483569ab9d7..ea45acc2ee1 100644 --- a/vendor/github.com/prometheus/alertmanager/api/metrics/metrics.go +++ b/vendor/github.com/prometheus/alertmanager/api/metrics/metrics.go @@ -15,7 +15,7 @@ package metrics import "github.com/prometheus/client_golang/prometheus" -// Alerts stores metrics for alerts which are common across all API versions. +// Alerts stores metrics for alerts. type Alerts struct { firing prometheus.Counter resolved prometheus.Counter @@ -23,16 +23,17 @@ type Alerts struct { } // NewAlerts returns an *Alerts struct for the given API version. -func NewAlerts(version string, r prometheus.Registerer) *Alerts { +// Since v1 was deprecated in 0.28, v2 is now hardcoded. +func NewAlerts(r prometheus.Registerer) *Alerts { numReceivedAlerts := prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "alertmanager_alerts_received_total", Help: "The total number of received alerts.", - ConstLabels: prometheus.Labels{"version": version}, + ConstLabels: prometheus.Labels{"version": "v2"}, }, []string{"status"}) numInvalidAlerts := prometheus.NewCounter(prometheus.CounterOpts{ Name: "alertmanager_alerts_invalid_total", Help: "The total number of received alerts that were invalid.", - ConstLabels: prometheus.Labels{"version": version}, + ConstLabels: prometheus.Labels{"version": "v2"}, }) if r != nil { r.MustRegister(numReceivedAlerts, numInvalidAlerts) diff --git a/vendor/github.com/prometheus/alertmanager/api/v1/api.go b/vendor/github.com/prometheus/alertmanager/api/v1/api.go deleted file mode 100644 index 39018a7589c..00000000000 --- a/vendor/github.com/prometheus/alertmanager/api/v1/api.go +++ /dev/null @@ -1,808 +0,0 @@ -// Copyright 2015 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 v1 - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "regexp" - "sort" - "sync" - "time" - - "github.com/go-kit/log" - "github.com/go-kit/log/level" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/common/model" - "github.com/prometheus/common/route" - "github.com/prometheus/common/version" - - "github.com/prometheus/alertmanager/api/metrics" - "github.com/prometheus/alertmanager/cluster" - "github.com/prometheus/alertmanager/config" - "github.com/prometheus/alertmanager/dispatch" - "github.com/prometheus/alertmanager/pkg/labels" - "github.com/prometheus/alertmanager/provider" - "github.com/prometheus/alertmanager/silence" - "github.com/prometheus/alertmanager/silence/silencepb" - "github.com/prometheus/alertmanager/types" -) - -var corsHeaders = map[string]string{ - "Access-Control-Allow-Headers": "Accept, Authorization, Content-Type, Origin", - "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS", - "Access-Control-Allow-Origin": "*", - "Access-Control-Expose-Headers": "Date", - "Cache-Control": "no-cache, no-store, must-revalidate", -} - -// Alert is the API representation of an alert, which is a regular alert -// annotated with silencing and inhibition info. -type Alert struct { - *model.Alert - Status types.AlertStatus `json:"status"` - Receivers []string `json:"receivers"` - Fingerprint string `json:"fingerprint"` -} - -// Enables cross-site script calls. -func setCORS(w http.ResponseWriter) { - for h, v := range corsHeaders { - w.Header().Set(h, v) - } -} - -// API provides registration of handlers for API routes. -type API struct { - alerts provider.Alerts - silences *silence.Silences - config *config.Config - route *dispatch.Route - uptime time.Time - peer cluster.ClusterPeer - logger log.Logger - m *metrics.Alerts - - getAlertStatus getAlertStatusFn - - mtx sync.RWMutex -} - -type getAlertStatusFn func(model.Fingerprint) types.AlertStatus - -// New returns a new API. -func New( - alerts provider.Alerts, - silences *silence.Silences, - sf getAlertStatusFn, - peer cluster.ClusterPeer, - l log.Logger, - r prometheus.Registerer, -) *API { - if l == nil { - l = log.NewNopLogger() - } - - return &API{ - alerts: alerts, - silences: silences, - getAlertStatus: sf, - uptime: time.Now(), - peer: peer, - logger: l, - m: metrics.NewAlerts("v1", r), - } -} - -// Register registers the API handlers under their correct routes -// in the given router. -func (api *API) Register(r *route.Router) { - wrap := func(f http.HandlerFunc) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - setCORS(w) - f(w, r) - }) - } - - r.Options("/*path", wrap(func(w http.ResponseWriter, r *http.Request) {})) - - r.Get("/status", wrap(api.status)) - r.Get("/receivers", wrap(api.receivers)) - - r.Get("/alerts", wrap(api.listAlerts)) - r.Post("/alerts", wrap(api.addAlerts)) - - r.Get("/silences", wrap(api.listSilences)) - r.Post("/silences", wrap(api.setSilence)) - r.Get("/silence/:sid", wrap(api.getSilence)) - r.Del("/silence/:sid", wrap(api.delSilence)) -} - -// Update sets the configuration string to a new value. -func (api *API) Update(cfg *config.Config) { - api.mtx.Lock() - defer api.mtx.Unlock() - - api.config = cfg - api.route = dispatch.NewRoute(cfg.Route, nil) -} - -type errorType string - -const ( - errorInternal errorType = "server_error" - errorBadData errorType = "bad_data" -) - -type apiError struct { - typ errorType - err error -} - -func (e *apiError) Error() string { - return fmt.Sprintf("%s: %s", e.typ, e.err) -} - -func (api *API) receivers(w http.ResponseWriter, req *http.Request) { - api.mtx.RLock() - defer api.mtx.RUnlock() - - receivers := make([]string, 0, len(api.config.Receivers)) - for _, r := range api.config.Receivers { - receivers = append(receivers, r.Name) - } - - api.respond(w, receivers) -} - -func (api *API) status(w http.ResponseWriter, req *http.Request) { - api.mtx.RLock() - - status := struct { - ConfigYAML string `json:"configYAML"` - ConfigJSON *config.Config `json:"configJSON"` - VersionInfo map[string]string `json:"versionInfo"` - Uptime time.Time `json:"uptime"` - ClusterStatus *clusterStatus `json:"clusterStatus"` - }{ - ConfigYAML: api.config.String(), - ConfigJSON: api.config, - VersionInfo: map[string]string{ - "version": version.Version, - "revision": version.Revision, - "branch": version.Branch, - "buildUser": version.BuildUser, - "buildDate": version.BuildDate, - "goVersion": version.GoVersion, - }, - Uptime: api.uptime, - ClusterStatus: getClusterStatus(api.peer), - } - - api.mtx.RUnlock() - - api.respond(w, status) -} - -type peerStatus struct { - Name string `json:"name"` - Address string `json:"address"` -} - -type clusterStatus struct { - Name string `json:"name"` - Status string `json:"status"` - Peers []peerStatus `json:"peers"` -} - -func getClusterStatus(p cluster.ClusterPeer) *clusterStatus { - if p == nil { - return nil - } - s := &clusterStatus{Name: p.Name(), Status: p.Status()} - - for _, n := range p.Peers() { - s.Peers = append(s.Peers, peerStatus{ - Name: n.Name(), - Address: n.Address(), - }) - } - return s -} - -func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) { - var ( - err error - receiverFilter *regexp.Regexp - // Initialize result slice to prevent api returning `null` when there - // are no alerts present - res = []*Alert{} - matchers = []*labels.Matcher{} - ctx = r.Context() - - showActive, showInhibited bool - showSilenced, showUnprocessed bool - ) - - getBoolParam := func(name string) (bool, error) { - v := r.FormValue(name) - if v == "" { - return true, nil - } - if v == "false" { - return false, nil - } - if v != "true" { - err := fmt.Errorf("parameter %q can either be 'true' or 'false', not %q", name, v) - api.respondError(w, apiError{ - typ: errorBadData, - err: err, - }, nil) - return false, err - } - return true, nil - } - - if filter := r.FormValue("filter"); filter != "" { - matchers, err = labels.ParseMatchers(filter) - if err != nil { - api.respondError(w, apiError{ - typ: errorBadData, - err: err, - }, nil) - return - } - } - - showActive, err = getBoolParam("active") - if err != nil { - return - } - - showSilenced, err = getBoolParam("silenced") - if err != nil { - return - } - - showInhibited, err = getBoolParam("inhibited") - if err != nil { - return - } - - showUnprocessed, err = getBoolParam("unprocessed") - if err != nil { - return - } - - if receiverParam := r.FormValue("receiver"); receiverParam != "" { - receiverFilter, err = regexp.Compile("^(?:" + receiverParam + ")$") - if err != nil { - api.respondError(w, apiError{ - typ: errorBadData, - err: fmt.Errorf( - "failed to parse receiver param: %s", - receiverParam, - ), - }, nil) - return - } - } - - alerts := api.alerts.GetPending() - defer alerts.Close() - - api.mtx.RLock() - for a := range alerts.Next() { - if err = alerts.Err(); err != nil { - break - } - if err = ctx.Err(); err != nil { - break - } - - routes := api.route.Match(a.Labels) - receivers := make([]string, 0, len(routes)) - for _, r := range routes { - receivers = append(receivers, r.RouteOpts.Receiver) - } - - if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) { - continue - } - - if !alertMatchesFilterLabels(&a.Alert, matchers) { - continue - } - - // Continue if the alert is resolved. - if !a.Alert.EndsAt.IsZero() && a.Alert.EndsAt.Before(time.Now()) { - continue - } - - status := api.getAlertStatus(a.Fingerprint()) - - if !showActive && status.State == types.AlertStateActive { - continue - } - - if !showUnprocessed && status.State == types.AlertStateUnprocessed { - continue - } - - if !showSilenced && len(status.SilencedBy) != 0 { - continue - } - - if !showInhibited && len(status.InhibitedBy) != 0 { - continue - } - - alert := &Alert{ - Alert: &a.Alert, - Status: status, - Receivers: receivers, - Fingerprint: a.Fingerprint().String(), - } - - res = append(res, alert) - } - api.mtx.RUnlock() - - if err != nil { - api.respondError(w, apiError{ - typ: errorInternal, - err: err, - }, nil) - return - } - sort.Slice(res, func(i, j int) bool { - return res[i].Fingerprint < res[j].Fingerprint - }) - api.respond(w, res) -} - -func receiversMatchFilter(receivers []string, filter *regexp.Regexp) bool { - for _, r := range receivers { - if filter.MatchString(r) { - return true - } - } - - return false -} - -func alertMatchesFilterLabels(a *model.Alert, matchers []*labels.Matcher) bool { - sms := make(map[string]string) - for name, value := range a.Labels { - sms[string(name)] = string(value) - } - return matchFilterLabels(matchers, sms) -} - -func (api *API) addAlerts(w http.ResponseWriter, r *http.Request) { - var alerts []*types.Alert - if err := api.receive(r, &alerts); err != nil { - api.respondError(w, apiError{ - typ: errorBadData, - err: err, - }, nil) - return - } - - api.insertAlerts(w, r, alerts...) -} - -func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...*types.Alert) { - now := time.Now() - - api.mtx.RLock() - resolveTimeout := time.Duration(api.config.Global.ResolveTimeout) - api.mtx.RUnlock() - - for _, alert := range alerts { - alert.UpdatedAt = now - - // Ensure StartsAt is set. - if alert.StartsAt.IsZero() { - if alert.EndsAt.IsZero() { - alert.StartsAt = now - } else { - alert.StartsAt = alert.EndsAt - } - } - // If no end time is defined, set a timeout after which an alert - // is marked resolved if it is not updated. - if alert.EndsAt.IsZero() { - alert.Timeout = true - alert.EndsAt = now.Add(resolveTimeout) - } - if alert.EndsAt.After(time.Now()) { - api.m.Firing().Inc() - } else { - api.m.Resolved().Inc() - } - } - - // Make a best effort to insert all alerts that are valid. - var ( - validAlerts = make([]*types.Alert, 0, len(alerts)) - validationErrs = &types.MultiError{} - ) - for _, a := range alerts { - removeEmptyLabels(a.Labels) - - if err := a.Validate(); err != nil { - validationErrs.Add(err) - api.m.Invalid().Inc() - continue - } - validAlerts = append(validAlerts, a) - } - if err := api.alerts.Put(validAlerts...); err != nil { - api.respondError(w, apiError{ - typ: errorInternal, - err: err, - }, nil) - return - } - - if validationErrs.Len() > 0 { - api.respondError(w, apiError{ - typ: errorBadData, - err: validationErrs, - }, nil) - return - } - - api.respond(w, nil) -} - -func removeEmptyLabels(ls model.LabelSet) { - for k, v := range ls { - if string(v) == "" { - delete(ls, k) - } - } -} - -func (api *API) setSilence(w http.ResponseWriter, r *http.Request) { - var sil types.Silence - if err := api.receive(r, &sil); err != nil { - api.respondError(w, apiError{ - typ: errorBadData, - err: err, - }, nil) - return - } - - // This is an API only validation, it cannot be done internally - // because the expired silence is semantically important. - // But one should not be able to create expired silences, that - // won't have any use. - if sil.Expired() { - api.respondError(w, apiError{ - typ: errorBadData, - err: errors.New("start time must not be equal to end time"), - }, nil) - return - } - - if sil.EndsAt.Before(time.Now()) { - api.respondError(w, apiError{ - typ: errorBadData, - err: errors.New("end time can't be in the past"), - }, nil) - return - } - - psil, err := silenceToProto(&sil) - if err != nil { - api.respondError(w, apiError{ - typ: errorBadData, - err: err, - }, nil) - return - } - - sid, err := api.silences.Set(psil) - if err != nil { - api.respondError(w, apiError{ - typ: errorBadData, - err: err, - }, nil) - return - } - - api.respond(w, struct { - SilenceID string `json:"silenceId"` - }{ - SilenceID: sid, - }) -} - -func (api *API) getSilence(w http.ResponseWriter, r *http.Request) { - sid := route.Param(r.Context(), "sid") - - sils, _, err := api.silences.Query(silence.QIDs(sid)) - if err != nil || len(sils) == 0 { - http.Error(w, fmt.Sprint("Error getting silence: ", err), http.StatusNotFound) - return - } - sil, err := silenceFromProto(sils[0]) - if err != nil { - api.respondError(w, apiError{ - typ: errorInternal, - err: err, - }, nil) - return - } - - api.respond(w, sil) -} - -func (api *API) delSilence(w http.ResponseWriter, r *http.Request) { - sid := route.Param(r.Context(), "sid") - - if err := api.silences.Expire(sid); err != nil { - api.respondError(w, apiError{ - typ: errorBadData, - err: err, - }, nil) - return - } - api.respond(w, nil) -} - -func (api *API) listSilences(w http.ResponseWriter, r *http.Request) { - psils, _, err := api.silences.Query() - if err != nil { - api.respondError(w, apiError{ - typ: errorInternal, - err: err, - }, nil) - return - } - - matchers := []*labels.Matcher{} - if filter := r.FormValue("filter"); filter != "" { - matchers, err = labels.ParseMatchers(filter) - if err != nil { - api.respondError(w, apiError{ - typ: errorBadData, - err: err, - }, nil) - return - } - } - - sils := []*types.Silence{} - for _, ps := range psils { - s, err := silenceFromProto(ps) - if err != nil { - api.respondError(w, apiError{ - typ: errorInternal, - err: err, - }, nil) - return - } - - if !silenceMatchesFilterLabels(s, matchers) { - continue - } - sils = append(sils, s) - } - - var active, pending, expired []*types.Silence - - for _, s := range sils { - switch s.Status.State { - case types.SilenceStateActive: - active = append(active, s) - case types.SilenceStatePending: - pending = append(pending, s) - case types.SilenceStateExpired: - expired = append(expired, s) - } - } - - sort.Slice(active, func(i, j int) bool { - return active[i].EndsAt.Before(active[j].EndsAt) - }) - sort.Slice(pending, func(i, j int) bool { - return pending[i].StartsAt.Before(pending[j].EndsAt) - }) - sort.Slice(expired, func(i, j int) bool { - return expired[i].EndsAt.After(expired[j].EndsAt) - }) - - // Initialize silences explicitly to an empty list (instead of nil) - // So that it does not get converted to "null" in JSON. - silences := []*types.Silence{} - silences = append(silences, active...) - silences = append(silences, pending...) - silences = append(silences, expired...) - - api.respond(w, silences) -} - -func silenceMatchesFilterLabels(s *types.Silence, matchers []*labels.Matcher) bool { - sms := make(map[string]string) - for _, m := range s.Matchers { - sms[m.Name] = m.Value - } - - return matchFilterLabels(matchers, sms) -} - -func matchFilterLabels(matchers []*labels.Matcher, sms map[string]string) bool { - for _, m := range matchers { - v, prs := sms[m.Name] - switch m.Type { - case labels.MatchNotRegexp, labels.MatchNotEqual: - if string(m.Value) == "" && prs { - continue - } - if !m.Matches(string(v)) { - return false - } - default: - if string(m.Value) == "" && !prs { - continue - } - if !m.Matches(string(v)) { - return false - } - } - } - - return true -} - -func silenceToProto(s *types.Silence) (*silencepb.Silence, error) { - sil := &silencepb.Silence{ - Id: s.ID, - StartsAt: s.StartsAt, - EndsAt: s.EndsAt, - UpdatedAt: s.UpdatedAt, - Comment: s.Comment, - CreatedBy: s.CreatedBy, - } - for _, m := range s.Matchers { - matcher := &silencepb.Matcher{ - Name: m.Name, - Pattern: m.Value, - } - switch m.Type { - case labels.MatchEqual: - matcher.Type = silencepb.Matcher_EQUAL - case labels.MatchNotEqual: - matcher.Type = silencepb.Matcher_NOT_EQUAL - case labels.MatchRegexp: - matcher.Type = silencepb.Matcher_REGEXP - case labels.MatchNotRegexp: - matcher.Type = silencepb.Matcher_NOT_REGEXP - } - sil.Matchers = append(sil.Matchers, matcher) - } - return sil, nil -} - -func silenceFromProto(s *silencepb.Silence) (*types.Silence, error) { - sil := &types.Silence{ - ID: s.Id, - StartsAt: s.StartsAt, - EndsAt: s.EndsAt, - UpdatedAt: s.UpdatedAt, - Status: types.SilenceStatus{ - State: types.CalcSilenceState(s.StartsAt, s.EndsAt), - }, - Comment: s.Comment, - CreatedBy: s.CreatedBy, - } - for _, m := range s.Matchers { - var t labels.MatchType - switch m.Type { - case silencepb.Matcher_EQUAL: - t = labels.MatchEqual - case silencepb.Matcher_NOT_EQUAL: - t = labels.MatchNotEqual - case silencepb.Matcher_REGEXP: - t = labels.MatchRegexp - case silencepb.Matcher_NOT_REGEXP: - t = labels.MatchNotRegexp - } - matcher, err := labels.NewMatcher(t, m.Name, m.Pattern) - if err != nil { - return nil, err - } - - sil.Matchers = append(sil.Matchers, matcher) - } - - return sil, nil -} - -type status string - -const ( - statusSuccess status = "success" - statusError status = "error" -) - -type response struct { - Status status `json:"status"` - Data interface{} `json:"data,omitempty"` - ErrorType errorType `json:"errorType,omitempty"` - Error string `json:"error,omitempty"` -} - -func (api *API) respond(w http.ResponseWriter, data interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - b, err := json.Marshal(&response{ - Status: statusSuccess, - Data: data, - }) - if err != nil { - level.Error(api.logger).Log("msg", "Error marshaling JSON", "err", err) - return - } - - if _, err := w.Write(b); err != nil { - level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err) - } -} - -func (api *API) respondError(w http.ResponseWriter, apiErr apiError, data interface{}) { - w.Header().Set("Content-Type", "application/json") - - switch apiErr.typ { - case errorBadData: - w.WriteHeader(http.StatusBadRequest) - case errorInternal: - w.WriteHeader(http.StatusInternalServerError) - default: - panic(fmt.Sprintf("unknown error type %q", apiErr.Error())) - } - - b, err := json.Marshal(&response{ - Status: statusError, - ErrorType: apiErr.typ, - Error: apiErr.err.Error(), - Data: data, - }) - if err != nil { - return - } - level.Error(api.logger).Log("msg", "API error", "err", apiErr.Error()) - - if _, err := w.Write(b); err != nil { - level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err) - } -} - -func (api *API) receive(r *http.Request, v interface{}) error { - dec := json.NewDecoder(r.Body) - defer r.Body.Close() - - err := dec.Decode(v) - if err != nil { - level.Debug(api.logger).Log("msg", "Decoding request failed", "err", err) - return err - } - return nil -} diff --git a/vendor/github.com/prometheus/alertmanager/api/v1_deprecation_router.go b/vendor/github.com/prometheus/alertmanager/api/v1_deprecation_router.go new file mode 100644 index 00000000000..3ebbbd076fa --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/api/v1_deprecation_router.go @@ -0,0 +1,67 @@ +// 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 l + +package api + +import ( + "encoding/json" + "net/http" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/common/route" +) + +// V1DeprecationRouter is the router to signal v1 users that the API v1 is now removed. +type V1DeprecationRouter struct { + logger log.Logger +} + +// NewV1DeprecationRouter returns a new V1DeprecationRouter. +func NewV1DeprecationRouter(l log.Logger) *V1DeprecationRouter { + return &V1DeprecationRouter{ + logger: l, + } +} + +// Register registers all the API v1 routes with an endpoint that returns a JSON deprecation notice and a logs a warning. +func (dr *V1DeprecationRouter) Register(r *route.Router) { + r.Get("/status", dr.deprecationHandler) + r.Get("/receivers", dr.deprecationHandler) + + r.Get("/alerts", dr.deprecationHandler) + r.Post("/alerts", dr.deprecationHandler) + + r.Get("/silences", dr.deprecationHandler) + r.Post("/silences", dr.deprecationHandler) + r.Get("/silence/:sid", dr.deprecationHandler) + r.Del("/silence/:sid", dr.deprecationHandler) +} + +func (dr *V1DeprecationRouter) deprecationHandler(w http.ResponseWriter, req *http.Request) { + level.Warn(dr.logger).Log("msg", "v1 API received a request on a removed endpoint", "path", req.URL.Path, "method", req.Method) + + resp := struct { + Status string `json:"status"` + Error string `json:"error"` + }{ + "deprecated", + "The Alertmanager v1 API was deprecated in version 0.16.0 and is removed as of version 0.28.0 - please use the equivalent route in the v2 API", + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(410) + + if err := json.NewEncoder(w).Encode(resp); err != nil { + level.Error(dr.logger).Log("msg", "failed to write response", "err", err) + } +} diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/api.go b/vendor/github.com/prometheus/alertmanager/api/v2/api.go index 1ddb2bcbaec..b4f57e75e20 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/api.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/api.go @@ -14,6 +14,7 @@ package v2 import ( + "errors" "fmt" "net/http" "regexp" @@ -44,6 +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/pkg/labels" "github.com/prometheus/alertmanager/provider" "github.com/prometheus/alertmanager/silence" @@ -97,7 +99,7 @@ func NewAPI( peer: peer, silences: silences, logger: l, - m: metrics.NewAlerts("v2", r), + m: metrics.NewAlerts(r), uptime: time.Now(), } @@ -505,17 +507,10 @@ func matchFilterLabels(matchers []*labels.Matcher, sms map[string]string) bool { func (api *API) getSilencesHandler(params silence_ops.GetSilencesParams) middleware.Responder { logger := api.requestLogger(params.HTTPRequest) - matchers := []*labels.Matcher{} - if params.Filter != nil { - for _, matcherString := range params.Filter { - matcher, err := labels.ParseMatcher(matcherString) - if err != nil { - level.Debug(logger).Log("msg", "Failed to parse matchers", "err", err) - return alert_ops.NewGetAlertsBadRequest().WithPayload(err.Error()) - } - - matchers = append(matchers, matcher) - } + matchers, err := parseFilter(params.Filter) + if err != nil { + level.Debug(logger).Log("msg", "Failed to parse matchers", "err", err) + return silence_ops.NewGetSilencesBadRequest().WithPayload(err.Error()) } psils, _, err := api.silences.Query() @@ -634,7 +629,7 @@ func (api *API) deleteSilenceHandler(params silence_ops.DeleteSilenceParams) mid sid := params.SilenceID.String() if err := api.silences.Expire(sid); err != nil { level.Error(logger).Log("msg", "Failed to expire silence", "err", err) - if err == silence.ErrNotFound { + if errors.Is(err, silence.ErrNotFound) { return silence_ops.NewDeleteSilenceNotFound() } return silence_ops.NewDeleteSilenceInternalServerError().WithPayload(err.Error()) @@ -668,7 +663,7 @@ func (api *API) postSilencesHandler(params silence_ops.PostSilencesParams) middl sid, err := api.silences.Set(sil) if err != nil { level.Error(logger).Log("msg", "Failed to create silence", "err", err) - if err == silence.ErrNotFound { + if errors.Is(err, silence.ErrNotFound) { return silence_ops.NewPostSilencesNotFound().WithPayload(err.Error()) } return silence_ops.NewPostSilencesBadRequest().WithPayload(err.Error()) @@ -682,7 +677,7 @@ func (api *API) postSilencesHandler(params silence_ops.PostSilencesParams) middl func parseFilter(filter []string) ([]*labels.Matcher, error) { matchers := make([]*labels.Matcher, 0, len(filter)) for _, matcherString := range filter { - matcher, err := labels.ParseMatcher(matcherString) + matcher, err := compat.Matcher(matcherString, "api") if err != nil { return nil, err } diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/openapi.yaml b/vendor/github.com/prometheus/alertmanager/api/v2/openapi.yaml index 801edf00bb4..549cc33e7f1 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/openapi.yaml +++ b/vendor/github.com/prometheus/alertmanager/api/v2/openapi.yaml @@ -53,6 +53,8 @@ paths: description: Get silences response schema: $ref: '#/definitions/gettableSilences' + '400': + $ref: '#/responses/BadRequest' '500': $ref: '#/responses/InternalServerError' parameters: diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/restapi/embedded_spec.go b/vendor/github.com/prometheus/alertmanager/api/v2/restapi/embedded_spec.go index caf371d884b..0e23efd0602 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/restapi/embedded_spec.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/restapi/embedded_spec.go @@ -317,6 +317,9 @@ func init() { "$ref": "#/definitions/gettableSilences" } }, + "400": { + "$ref": "#/responses/BadRequest" + }, "500": { "$ref": "#/responses/InternalServerError" } @@ -1128,6 +1131,12 @@ func init() { "$ref": "#/definitions/gettableSilences" } }, + "400": { + "description": "Bad request", + "schema": { + "type": "string" + } + }, "500": { "description": "Internal server error", "schema": { diff --git a/vendor/github.com/prometheus/alertmanager/api/v2/restapi/operations/silence/get_silences_responses.go b/vendor/github.com/prometheus/alertmanager/api/v2/restapi/operations/silence/get_silences_responses.go index f2f7b88f746..b09c772aa24 100644 --- a/vendor/github.com/prometheus/alertmanager/api/v2/restapi/operations/silence/get_silences_responses.go +++ b/vendor/github.com/prometheus/alertmanager/api/v2/restapi/operations/silence/get_silences_responses.go @@ -75,6 +75,49 @@ func (o *GetSilencesOK) WriteResponse(rw http.ResponseWriter, producer runtime.P } } +// GetSilencesBadRequestCode is the HTTP code returned for type GetSilencesBadRequest +const GetSilencesBadRequestCode int = 400 + +/* +GetSilencesBadRequest Bad request + +swagger:response getSilencesBadRequest +*/ +type GetSilencesBadRequest struct { + + /* + In: Body + */ + Payload string `json:"body,omitempty"` +} + +// NewGetSilencesBadRequest creates GetSilencesBadRequest with default headers values +func NewGetSilencesBadRequest() *GetSilencesBadRequest { + + return &GetSilencesBadRequest{} +} + +// WithPayload adds the payload to the get silences bad request response +func (o *GetSilencesBadRequest) WithPayload(payload string) *GetSilencesBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get silences bad request response +func (o *GetSilencesBadRequest) SetPayload(payload string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetSilencesBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + // GetSilencesInternalServerErrorCode is the HTTP code returned for type GetSilencesInternalServerError const GetSilencesInternalServerErrorCode int = 500 diff --git a/vendor/github.com/prometheus/alertmanager/asset/assets_vfsdata.go b/vendor/github.com/prometheus/alertmanager/asset/assets_vfsdata.go index fe0d3ce719a..f74112ad01c 100644 --- a/vendor/github.com/prometheus/alertmanager/asset/assets_vfsdata.go +++ b/vendor/github.com/prometheus/alertmanager/asset/assets_vfsdata.go @@ -163,9 +163,9 @@ var Assets = func() http.FileSystem { "/templates/default.tmpl": &vfsgen۰CompressedFileInfo{ name: "default.tmpl", modTime: time.Date(1970, 1, 1, 0, 0, 1, 0, time.UTC), - uncompressedSize: 5875, + uncompressedSize: 5951, - compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xec\x58\x41\x6f\xa3\x3a\x10\xbe\xf3\x2b\xac\xf4\xd2\x1c\x42\xdf\xb9\x52\xf5\x54\x3d\xbd\xdd\x4b\xb5\x5a\xa5\xca\x5e\x56\x2b\xe4\xc0\x84\xba\x31\x36\xb5\x87\xb4\x11\xe1\xbf\xaf\x0c\x94\x40\x0c\xa9\x49\xb3\xa7\xcd\xad\xb8\x33\xdf\x8c\xbf\xf9\x98\x19\x92\xe7\x24\x82\x15\x13\x40\x26\x41\x40\x39\x28\x4c\xa8\xa0\x31\xa8\x09\x29\x8a\xfb\xd6\x73\x9e\x13\x10\x11\x29\x0a\x6f\xd0\x65\x31\x7f\x30\x5e\x79\x4e\xfc\xff\xdf\x10\x94\xa0\x7c\x31\x7f\x20\x45\x71\x73\x75\x53\xda\xe9\x7f\x15\x84\xc0\x36\xa0\xee\x8c\xd1\xbc\x7e\x20\x3b\x92\x29\xfe\x92\x81\xda\x56\xee\x75\xa0\x6e\x24\x9d\x2d\x9f\x21\x44\x13\xe1\xa7\xf1\x7e\x44\x8a\x99\x26\x3b\x82\x72\x91\xa6\xa0\x2a\x57\xb6\x22\xf0\xd2\xfc\x73\xb2\x62\x8a\x89\xd8\xf8\xdc\x1a\x9f\xf2\x42\xda\xff\x52\x9e\x92\x1d\xe1\x20\xda\x11\x7f\x11\x63\xf4\x55\xc9\x2c\x7d\xa0\x4b\xe0\xda\x7f\x94\x0a\x21\xfa\x4e\x99\xd2\xfe\x0f\xca\x33\x30\x01\x9f\x25\x13\x64\x42\x0c\x2a\xa9\x42\xc6\x48\xae\x0d\x96\xff\x9f\x4c\x12\x29\x2a\xe7\x69\x7d\xd6\xc2\x9b\x92\xa2\xb8\xce\x73\xf2\xca\xf0\xa9\x6b\xec\xcf\x21\x91\x1b\xe8\x46\xff\x46\x13\xd0\x35\xa3\x7d\xd1\x9b\xc4\xa7\xcd\x5f\x03\x65\x8a\x40\x87\x8a\xa5\xc8\xa4\x98\x1c\xe1\x18\xe1\x0d\xab\x92\x06\x9c\x69\xac\x4d\x15\x15\x31\x10\x9f\x14\x45\x95\xd7\xad\xb7\x3f\xb4\x79\x32\xac\xcc\x4a\x22\x4d\xfa\xe6\xe9\x8e\x34\x17\xa8\x13\xab\x82\xdf\x0b\x21\x91\x9a\x9c\x3a\x90\xad\xe3\xd3\x70\x1f\x65\xa6\x42\xb8\xad\x8a\x09\x02\x14\x45\xa9\x2a\x25\x7a\x3d\x44\x1d\xa5\x20\x48\xa8\x5a\x47\xf2\x55\x58\x5c\x78\xae\x64\x38\x66\xed\x8d\xa7\xc3\x15\xd9\x89\x10\xaf\x9f\x11\xcd\x69\xb8\xf6\x23\x58\xd1\x8c\xa3\x8f\x0c\x39\xd4\x54\x20\x24\x29\xa7\xd8\x7d\x39\xfd\x21\x0d\x76\x71\x32\x6d\xda\x43\xd2\x07\xd5\x6d\x42\x8e\x78\x2b\xca\xf9\x92\x86\x6b\x0b\xaf\x37\x7d\x03\x4a\x76\xe4\x23\x43\xce\xc4\xda\x39\x83\xb0\xce\x80\x45\x13\x37\x87\x54\x81\xd1\x9a\xa3\x75\x2b\xa1\xa3\x8c\x95\x3d\xd8\x31\x65\x16\x4a\x01\x89\x7c\x66\x13\x77\xfb\x4c\x71\xd7\x8c\xdd\x2f\xb7\x92\x12\xab\x89\xd3\xd2\x60\xdb\x3c\x35\x57\x8b\x32\xdc\x36\x2e\x76\x43\x1b\x27\x47\x1b\x31\xe4\x0c\x04\x9e\x2e\xc8\x21\xc4\xfd\x54\x3c\xad\x66\x36\x2e\x13\x1a\xa9\x08\x41\xf7\xe0\x5a\x1d\xdc\x1f\x66\x55\xa6\x3a\x06\xc1\xa0\x01\x4e\x40\x6b\x1a\x9f\xf6\x7e\x5b\x60\x76\x85\xea\x81\x37\xd0\xd0\x7a\x27\x9c\x77\x30\x5f\x3b\x03\x7c\x4a\xfe\x21\x33\xd3\x38\xcb\x43\x52\x1d\x96\xad\xf3\x38\x23\xdd\x2d\xa0\x0c\x32\x6b\xdd\xa8\x27\xde\x1c\xb4\xe4\x1b\x88\x0e\x22\xbe\x1f\xbb\xc7\x7c\xf7\xb0\xa2\xce\x5c\x28\xd5\x65\x1f\x1f\xaf\xa6\x4e\xd5\x5f\x21\x7c\xa2\x38\xb6\xe6\xde\xa5\x7e\x47\xea\xd7\x5e\x94\x17\x8a\x5b\x78\xbd\xf5\x19\xa8\xfa\x41\x7d\x50\x06\x66\x58\x0e\x76\x52\xdb\x3c\xa5\x0a\xb7\x23\xec\x91\xc6\xae\xd6\x34\x06\x81\xc1\xe1\x88\xeb\xea\x6b\xc3\x42\x94\x4a\xa6\x7a\x2f\x5b\xa4\x08\x41\x57\x68\x17\x2d\x8d\xeb\x05\x36\xab\x20\x90\xe1\x36\x88\x98\x4e\x39\xdd\x06\x03\xdb\xd4\xc7\x8d\xdb\x46\x4e\xa4\x60\x28\x0d\x21\x01\x4a\xc9\x47\x8e\xc4\xce\xec\xca\xf4\x93\xdc\x80\x3a\xc3\xfe\x68\x41\xfd\x79\x3d\x9d\x47\x4e\xee\x6a\x3a\x9f\x98\xec\x95\xfe\x18\x93\xfb\x9d\x6e\xcc\x4c\x69\x6f\x73\xa2\xf5\xb2\xef\x3f\xd3\xc7\x7f\x23\xb4\x70\x2e\xe5\x1d\x53\xde\x36\x8b\x08\x1c\x62\x45\x93\x3e\x2a\xff\x5a\x52\x22\xa6\x43\xa9\xa2\x33\x34\xa2\x43\xa4\x0b\xbb\x66\x4d\x58\xc2\xdb\xe5\xd5\xfd\x34\x8f\x89\x46\xa0\x89\x3e\x83\x4a\x2d\xa4\xfa\x6b\xdc\x85\xda\x2b\x32\x8a\xdc\xd6\x4f\x64\x9f\x66\xb9\x09\xed\xca\x73\x4f\xf0\x8f\x08\xff\x1d\x00\x00\xff\xff\xc6\x76\xea\x54\xf3\x16\x00\x00"), + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x02\xff\xec\x58\x4f\x6f\xbb\x46\x10\xbd\xf3\x29\x56\xce\x25\x3e\x98\xf4\x1c\x29\xaa\xa2\xaa\xed\x25\xaa\x2a\x47\xee\xa5\xaa\xd0\x1a\xc6\x64\xe3\xfd\x43\x76\x07\x27\x16\xe6\xbb\x57\x0b\xc4\x06\x2f\x38\x8b\xe3\xdf\xe9\xe7\x5b\xd8\xcc\xbc\x99\x7d\x6f\x98\x19\x5c\x14\x24\x81\x15\x93\x40\x26\x51\x44\x39\x68\x14\x54\xd2\x14\xf4\x84\x94\xe5\x63\xeb\xb9\x28\x08\xc8\x84\x94\x65\x30\xe8\xb2\x98\x3f\x59\xaf\xa2\x20\xe1\xef\x1f\x08\x5a\x52\xbe\x98\x3f\x91\xb2\xbc\xbb\xb9\xab\xec\xcc\xaf\x1a\x62\x60\x1b\xd0\x0f\xd6\x68\xde\x3c\x90\x1d\xc9\x35\x7f\xcb\x41\x6f\x6b\xf7\x26\x50\x37\x92\xc9\x97\xaf\x10\xa3\x8d\xf0\xaf\xf5\x7e\x46\x8a\xb9\x21\x3b\x82\x6a\x91\x65\xa0\x6b\x57\xb6\x22\xf0\xb6\xff\xe7\x64\xc5\x34\x93\xa9\xf5\xb9\xb7\x3e\xd5\x85\x4c\xf8\x47\x75\x4a\x76\x84\x83\x6c\x47\xfc\x8f\x58\xa3\x3f\xb5\xca\xb3\x27\xba\x04\x6e\xc2\x67\xa5\x11\x92\xbf\x29\xd3\x26\xfc\x87\xf2\x1c\x6c\xc0\x57\xc5\x24\x99\x10\x8b\x4a\xea\x90\x29\x92\x5b\x8b\x15\xfe\xa6\x84\x50\xb2\x76\x9e\x36\x67\x2d\xbc\x29\x29\xcb\xdb\xa2\x20\xef\x0c\x5f\xba\xc6\xe1\x1c\x84\xda\x40\x37\xfa\x5f\x54\x80\x69\x18\xed\x8b\xbe\x4f\x7c\xba\xff\x6b\x40\xa6\x04\x4c\xac\x59\x86\x4c\xc9\xc9\x09\x8e\x11\x3e\xb0\x96\x34\xe2\xcc\x60\x63\xaa\xa9\x4c\x81\x84\xa4\x2c\xeb\xbc\xee\x83\xc3\xa1\xcb\x93\x65\x65\x56\x11\x69\xd3\xb7\x4f\x0f\x64\x7f\x81\x26\xb1\x3a\xf8\xa3\x94\x0a\xa9\xcd\xa9\x03\xd9\x3a\x3e\x0f\xf7\x59\xe5\x3a\x86\xfb\x5a\x4c\x90\xa0\x29\x2a\x5d\x57\x62\xd0\x43\xd4\x49\x0a\x22\x41\xf5\x3a\x51\xef\xd2\xe1\x22\xf0\x25\xc3\x33\xeb\x60\x3c\x1d\xbe\xc8\x5e\x84\x04\xfd\x8c\x18\x4e\xe3\x75\x98\xc0\x8a\xe6\x1c\x43\x64\xc8\xa1\xa1\x02\x41\x64\x9c\x62\xf7\xe5\x0c\x87\x6a\xb0\x8b\x93\x1b\xdb\x1e\x44\x1f\x54\xb7\x09\x79\xe2\xad\x28\xe7\x4b\x1a\xaf\x1d\xbc\xde\xf4\x2d\x28\xd9\x91\xaf\x0c\x39\x93\x6b\xef\x0c\xe2\x26\x03\x96\x4c\xfc\x1c\x32\x0d\xb6\xd6\x3c\xad\x5b\x09\x9d\x64\xac\xea\xc1\x9e\x29\xb3\x58\x49\x10\xea\x95\x4d\xfc\xed\x73\xcd\x7d\x33\xf6\xbf\xdc\x4a\x29\xac\x27\x4e\xab\x06\xdb\xe6\x99\xbd\x5a\x92\xe3\x76\xef\xe2\x36\xb4\x71\xe5\xe8\x22\xc6\x9c\x81\xc4\xf3\x0b\x72\x08\xf1\x30\x15\xcf\xd3\xcc\xc5\x65\xd2\x20\x95\x31\x98\x1e\x5c\xa7\x83\x87\xc3\xac\xaa\xcc\xa4\x20\x19\xec\x81\x05\x18\x43\xd3\xf3\xde\x6f\x07\xcc\x55\xa8\x19\x78\x03\x0d\xad\x77\xc2\x05\x47\xf3\xb5\x33\xc0\xa7\xe4\x17\x32\xb3\x8d\xb3\x3a\x24\xf5\x61\xd5\x3a\x4f\x33\xd2\xdd\x02\xaa\x20\xb3\xd6\x8d\x7a\xe2\xcd\xc1\x28\xbe\x81\xe4\x28\xe2\xe7\xb1\x7f\xcc\x4f\x0f\x27\xea\xcc\x87\x52\x53\xf5\xf1\xf1\xd5\xd4\x51\xfd\x1d\xe2\x17\x8a\x63\x35\x0f\xae\xfa\x9d\xd0\xaf\xbd\x28\x2f\x34\x77\xf0\x7a\xf5\x19\x50\xfd\x48\x1f\x54\x91\x1d\x96\x83\x9d\xd4\x35\xcf\xa8\xc6\xed\x08\x7b\xa4\xa9\xaf\x35\x4d\x41\x62\x74\x3c\xe2\xba\xf5\xb5\x61\x31\x2a\xad\x32\x73\x28\x5b\xa4\x08\x51\xb7\xd0\xae\xb5\x34\xae\x17\xb8\xac\x82\x44\x86\xdb\x28\x61\x26\xe3\x74\x1b\x0d\x6c\x53\x5f\x37\x6e\x17\x59\x28\xc9\x50\x59\x42\x22\x54\x8a\x8f\x1c\x89\x9d\xd9\x95\x9b\x17\xb5\x01\x7d\x81\xfd\xd1\x81\xfa\xf1\xf5\x74\x99\x72\xf2\xaf\xa6\xcb\x15\x93\xbb\xd2\x9f\x62\xf2\xb0\xd3\x8d\x99\x29\xed\x6d\x4e\xb6\x5e\xf6\xc3\x67\xfa\xf8\x6f\x84\x16\xce\x55\xde\x31\xf2\xb6\x59\x44\xe0\x90\x6a\x2a\xfa\xa8\xfc\x69\x49\x49\x98\x89\x95\x4e\x2e\xd0\x88\x8e\x91\xae\xec\xda\x35\x61\x09\x1f\xd7\x57\xf7\xdb\x3c\x0a\x83\x40\x45\xbb\x99\x0a\x41\xf5\xf6\xac\x3a\x3d\xc6\x3a\xbf\xe2\x1d\xa4\xe6\xcb\xde\x47\xa6\x1b\x32\x4a\xa8\xd6\xcf\x6d\xdf\x56\x6c\x1f\xda\x57\xb3\x9e\xe0\x5f\x89\xf7\x7f\x00\x00\x00\xff\xff\xf6\x0e\x88\xb1\x3f\x17\x00\x00"), }, "/templates/email.tmpl": &vfsgen۰CompressedFileInfo{ name: "email.tmpl", diff --git a/vendor/github.com/prometheus/alertmanager/cluster/advertise.go b/vendor/github.com/prometheus/alertmanager/cluster/advertise.go index b1b8fe949c4..ac734649d16 100644 --- a/vendor/github.com/prometheus/alertmanager/cluster/advertise.go +++ b/vendor/github.com/prometheus/alertmanager/cluster/advertise.go @@ -14,10 +14,11 @@ package cluster import ( + "errors" + "fmt" "net" "github.com/hashicorp/go-sockaddr" - "github.com/pkg/errors" ) type getIPFunc func() (string, error) @@ -38,7 +39,7 @@ func calculateAdvertiseAddress(bindAddr, advertiseAddr string, allowInsecureAdve if advertiseAddr != "" { ip := net.ParseIP(advertiseAddr) if ip == nil { - return nil, errors.Errorf("failed to parse advertise addr '%s'", advertiseAddr) + return nil, fmt.Errorf("failed to parse advertise addr '%s'", advertiseAddr) } if ip4 := ip.To4(); ip4 != nil { ip = ip4 @@ -52,7 +53,7 @@ func calculateAdvertiseAddress(bindAddr, advertiseAddr string, allowInsecureAdve ip := net.ParseIP(bindAddr) if ip == nil { - return nil, errors.Errorf("failed to parse bind addr '%s'", bindAddr) + return nil, fmt.Errorf("failed to parse bind addr '%s'", bindAddr) } return ip, nil } @@ -64,7 +65,7 @@ func calculateAdvertiseAddress(bindAddr, advertiseAddr string, allowInsecureAdve func discoverAdvertiseAddress(allowInsecureAdvertise bool) (net.IP, error) { addr, err := getPrivateAddress() if err != nil { - return nil, errors.Wrap(err, "failed to get private IP") + return nil, fmt.Errorf("failed to get private IP: %w", err) } if addr == "" && !allowInsecureAdvertise { return nil, errors.New("no private IP found, explicit advertise addr not provided") @@ -73,7 +74,7 @@ func discoverAdvertiseAddress(allowInsecureAdvertise bool) (net.IP, error) { if addr == "" { addr, err = getPublicAddress() if err != nil { - return nil, errors.Wrap(err, "failed to get public IP") + return nil, fmt.Errorf("failed to get public IP: %w", err) } if addr == "" { return nil, errors.New("no private/public IP found, explicit advertise addr not provided") @@ -82,7 +83,7 @@ func discoverAdvertiseAddress(allowInsecureAdvertise bool) (net.IP, error) { ip := net.ParseIP(addr) if ip == nil { - return nil, errors.Errorf("failed to parse discovered IP '%s'", addr) + return nil, fmt.Errorf("failed to parse discovered IP '%s'", addr) } return ip, nil } diff --git a/vendor/github.com/prometheus/alertmanager/cluster/cluster.go b/vendor/github.com/prometheus/alertmanager/cluster/cluster.go index 2c7ee945e6d..d85ee7f533d 100644 --- a/vendor/github.com/prometheus/alertmanager/cluster/cluster.go +++ b/vendor/github.com/prometheus/alertmanager/cluster/cluster.go @@ -15,6 +15,7 @@ package cluster import ( "context" + "errors" "fmt" "math/rand" "net" @@ -28,7 +29,6 @@ import ( "github.com/go-kit/log/level" "github.com/hashicorp/memberlist" "github.com/oklog/ulid" - "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" ) @@ -146,11 +146,11 @@ func Create( ) (*Peer, error) { bindHost, bindPortStr, err := net.SplitHostPort(bindAddr) if err != nil { - return nil, errors.Wrap(err, "invalid listen address") + return nil, fmt.Errorf("invalid listen address: %w", err) } bindPort, err := strconv.Atoi(bindPortStr) if err != nil { - return nil, errors.Wrapf(err, "address %s: invalid port", bindAddr) + return nil, fmt.Errorf("address %s: invalid port: %w", bindAddr, err) } var advertiseHost string @@ -159,17 +159,17 @@ func Create( var advertisePortStr string advertiseHost, advertisePortStr, err = net.SplitHostPort(advertiseAddr) if err != nil { - return nil, errors.Wrap(err, "invalid advertise address") + return nil, fmt.Errorf("invalid advertise address: %w", err) } advertisePort, err = strconv.Atoi(advertisePortStr) if err != nil { - return nil, errors.Wrapf(err, "address %s: invalid port", advertiseAddr) + return nil, fmt.Errorf("address %s: invalid port: %w", advertiseAddr, err) } } resolvedPeers, err := resolvePeers(context.Background(), knownPeers, advertiseAddr, &net.Resolver{}, waitIfEmpty) if err != nil { - return nil, errors.Wrap(err, "resolve peers") + return nil, fmt.Errorf("resolve peers: %w", err) } level.Debug(l).Log("msg", "resolved peers to following addresses", "peers", strings.Join(resolvedPeers, ",")) @@ -242,13 +242,13 @@ func Create( level.Info(l).Log("msg", "using TLS for gossip") cfg.Transport, err = NewTLSTransport(context.Background(), l, reg, cfg.BindAddr, cfg.BindPort, tlsTransportConfig) if err != nil { - return nil, errors.Wrap(err, "tls transport") + return nil, fmt.Errorf("tls transport: %w", err) } } ml, err := memberlist.Create(cfg) if err != nil { - return nil, errors.Wrap(err, "create memberlist") + return nil, fmt.Errorf("create memberlist: %w", err) } p.mlist = ml return p, nil @@ -736,7 +736,7 @@ func resolvePeers(ctx context.Context, peers []string, myAddress string, res *ne for _, peer := range peers { host, port, err := net.SplitHostPort(peer) if err != nil { - return nil, errors.Wrapf(err, "split host/port for peer %s", peer) + return nil, fmt.Errorf("split host/port for peer %s: %w", peer, err) } retryCtx, cancel := context.WithCancel(ctx) @@ -761,7 +761,7 @@ func resolvePeers(ctx context.Context, peers []string, myAddress string, res *ne ips, err = res.LookupIPAddr(retryCtx, host) if err != nil { lookupErrSpotted = true - return errors.Wrapf(err, "IP Addr lookup for peer %s", peer) + return fmt.Errorf("IP Addr lookup for peer %s: %w", peer, err) } ips = removeMyAddr(ips, port, myAddress) diff --git a/vendor/github.com/prometheus/alertmanager/cluster/connection_pool.go b/vendor/github.com/prometheus/alertmanager/cluster/connection_pool.go index 4d8085dd81a..b9bda3a82a1 100644 --- a/vendor/github.com/prometheus/alertmanager/cluster/connection_pool.go +++ b/vendor/github.com/prometheus/alertmanager/cluster/connection_pool.go @@ -15,12 +15,12 @@ package cluster import ( "crypto/tls" + "errors" "fmt" "sync" "time" lru "github.com/hashicorp/golang-lru/v2" - "github.com/pkg/errors" ) const capacity = 1024 @@ -38,7 +38,7 @@ func newConnectionPool(tlsClientCfg *tls.Config) (*connectionPool, error) { }, ) if err != nil { - return nil, errors.Wrap(err, "failed to create new LRU") + return nil, fmt.Errorf("failed to create new LRU: %w", err) } return &connectionPool{ cache: cache, diff --git a/vendor/github.com/prometheus/alertmanager/cluster/tls_connection.go b/vendor/github.com/prometheus/alertmanager/cluster/tls_connection.go index 21ffc95970d..5d6416fd1b2 100644 --- a/vendor/github.com/prometheus/alertmanager/cluster/tls_connection.go +++ b/vendor/github.com/prometheus/alertmanager/cluster/tls_connection.go @@ -17,6 +17,8 @@ import ( "bufio" "crypto/tls" "encoding/binary" + "errors" + "fmt" "io" "net" "sync" @@ -24,7 +26,6 @@ import ( "github.com/gogo/protobuf/proto" "github.com/hashicorp/memberlist" - "github.com/pkg/errors" "github.com/prometheus/alertmanager/cluster/clusterpb" ) @@ -99,7 +100,7 @@ func (conn *tlsConn) writePacket(fromAddr string, b []byte) error { }, ) if err != nil { - return errors.Wrap(err, "unable to marshal memeberlist packet message") + return fmt.Errorf("unable to marshal memeberlist packet message: %w", err) } buf := make([]byte, uint32length, uint32length+len(msg)) binary.LittleEndian.PutUint32(buf, uint32(len(msg))) @@ -116,7 +117,7 @@ func (conn *tlsConn) writeStream() error { }, ) if err != nil { - return errors.Wrap(err, "unable to marshal memeberlist stream message") + return fmt.Errorf("unable to marshal memeberlist stream message: %w", err) } buf := make([]byte, uint32length, uint32length+len(msg)) binary.LittleEndian.PutUint32(buf, uint32(len(msg))) @@ -136,7 +137,7 @@ func (conn *tlsConn) read() (*memberlist.Packet, error) { lenBuf := make([]byte, uint32length) _, err := io.ReadFull(reader, lenBuf) if err != nil { - return nil, errors.Wrap(err, "error reading message length") + return nil, fmt.Errorf("error reading message length: %w", err) } msgLen := binary.LittleEndian.Uint32(lenBuf) msgBuf := make([]byte, msgLen) @@ -144,12 +145,12 @@ func (conn *tlsConn) read() (*memberlist.Packet, error) { conn.mtx.Unlock() if err != nil { - return nil, errors.Wrap(err, "error reading message") + return nil, fmt.Errorf("error reading message: %w", err) } pb := clusterpb.MemberlistMessage{} err = proto.Unmarshal(msgBuf, &pb) if err != nil { - return nil, errors.Wrap(err, "error parsing message") + return nil, fmt.Errorf("error parsing message: %w", err) } if pb.Version != version { return nil, errors.New("tls memberlist message version incompatible") @@ -167,7 +168,7 @@ func (conn *tlsConn) read() (*memberlist.Packet, error) { func toPacket(pb clusterpb.MemberlistMessage) (*memberlist.Packet, error) { addr, err := net.ResolveTCPAddr(network, pb.FromAddr) if err != nil { - return nil, errors.Wrap(err, "error parsing packet sender address") + return nil, fmt.Errorf("error parsing packet sender address: %w", err) } return &memberlist.Packet{ Buf: pb.Msg, diff --git a/vendor/github.com/prometheus/alertmanager/cluster/tls_transport.go b/vendor/github.com/prometheus/alertmanager/cluster/tls_transport.go index eb521e049dd..7e39d6fed8d 100644 --- a/vendor/github.com/prometheus/alertmanager/cluster/tls_transport.go +++ b/vendor/github.com/prometheus/alertmanager/cluster/tls_transport.go @@ -20,6 +20,7 @@ package cluster import ( "context" "crypto/tls" + "errors" "fmt" "net" "strings" @@ -29,7 +30,6 @@ import ( "github.com/go-kit/log/level" "github.com/hashicorp/go-sockaddr" "github.com/hashicorp/memberlist" - "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" common "github.com/prometheus/common/config" "github.com/prometheus/exporter-toolkit/web" @@ -83,12 +83,12 @@ func NewTLSTransport( tlsServerCfg, err := web.ConfigToTLSConfig(cfg.TLSServerConfig) if err != nil { - return nil, errors.Wrap(err, "invalid TLS server config") + return nil, fmt.Errorf("invalid TLS server config: %w", err) } tlsClientCfg, err := common.NewTLSConfig(cfg.TLSClientConfig) if err != nil { - return nil, errors.Wrap(err, "invalid TLS client config") + return nil, fmt.Errorf("invalid TLS client config: %w", err) } ip := net.ParseIP(bindAddr) @@ -99,12 +99,12 @@ func NewTLSTransport( addr := &net.TCPAddr{IP: ip, Port: bindPort} listener, err := tls.Listen(network, addr.String(), tlsServerCfg) if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("failed to start TLS listener on %q port %d", bindAddr, bindPort)) + return nil, fmt.Errorf("failed to start TLS listener on %q port %d: %w", bindAddr, bindPort, err) } connPool, err := newConnectionPool(tlsClientCfg) if err != nil { - return nil, errors.Wrap(err, "failed to initialize tls transport connection pool") + return nil, fmt.Errorf("failed to initialize tls transport connection pool: %w", err) } ctx, cancel := context.WithCancel(ctx) @@ -155,7 +155,7 @@ func (t *TLSTransport) FinalAdvertiseAddr(ip string, port int) (net.IP, int, err var err error ip, err = sockaddr.GetPrivateIP() if err != nil { - return nil, 0, fmt.Errorf("failed to get interface addresses: %v", err) + return nil, 0, fmt.Errorf("failed to get interface addresses: %w", err) } if ip == "" { return nil, 0, fmt.Errorf("no private IP address found, and explicit IP not provided") @@ -203,13 +203,13 @@ func (t *TLSTransport) WriteTo(b []byte, addr string) (time.Time, error) { conn, err := t.connPool.borrowConnection(addr, DefaultTCPTimeout) if err != nil { t.writeErrs.WithLabelValues("packet").Inc() - return time.Now(), errors.Wrap(err, "failed to dial") + return time.Now(), fmt.Errorf("failed to dial: %w", err) } fromAddr := t.listener.Addr().String() err = conn.writePacket(fromAddr, b) if err != nil { t.writeErrs.WithLabelValues("packet").Inc() - return time.Now(), errors.Wrap(err, "failed to write packet") + return time.Now(), fmt.Errorf("failed to write packet: %w", err) } t.packetsSent.Add(float64(len(b))) return time.Now(), nil @@ -221,13 +221,13 @@ func (t *TLSTransport) DialTimeout(addr string, timeout time.Duration) (net.Conn conn, err := dialTLSConn(addr, timeout, t.tlsClientCfg) if err != nil { t.writeErrs.WithLabelValues("stream").Inc() - return nil, errors.Wrap(err, "failed to dial") + return nil, fmt.Errorf("failed to dial: %w", err) } err = conn.writeStream() netConn := conn.getRawConn() if err != nil { t.writeErrs.WithLabelValues("stream").Inc() - return netConn, errors.Wrap(err, "failed to create stream connection") + return netConn, fmt.Errorf("failed to create stream connection: %w", err) } t.streamsSent.Inc() return netConn, nil diff --git a/vendor/github.com/prometheus/alertmanager/config/config.go b/vendor/github.com/prometheus/alertmanager/config/config.go index bf236aefe03..7f3602e0662 100644 --- a/vendor/github.com/prometheus/alertmanager/config/config.go +++ b/vendor/github.com/prometheus/alertmanager/config/config.go @@ -15,6 +15,7 @@ package config import ( "encoding/json" + "errors" "fmt" "net" "net/url" @@ -25,11 +26,11 @@ import ( "strings" "time" - "github.com/pkg/errors" commoncfg "github.com/prometheus/common/config" "github.com/prometheus/common/model" "gopkg.in/yaml.v2" + "github.com/prometheus/alertmanager/matchers/compat" "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/alertmanager/timeinterval" ) @@ -688,7 +689,7 @@ func (hp *HostPort) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } if hp.Port == "" { - return errors.Errorf("address %q: port cannot be empty", s) + return fmt.Errorf("address %q: port cannot be empty", s) } return nil } @@ -710,7 +711,7 @@ func (hp *HostPort) UnmarshalJSON(data []byte) error { return err } if hp.Port == "" { - return errors.Errorf("address %q: port cannot be empty", s) + return fmt.Errorf("address %q: port cannot be empty", s) } return nil } @@ -813,7 +814,7 @@ func (r *Route) UnmarshalYAML(unmarshal func(interface{}) error) error { r.GroupByAll = true } else { labelName := model.LabelName(l) - if !labelName.IsValid() { + if !compat.IsValidLabelName(labelName) { return fmt.Errorf("invalid label name %q in group_by list", l) } r.GroupBy = append(r.GroupBy, labelName) @@ -1005,7 +1006,7 @@ func (m *Matchers) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } for _, line := range lines { - pm, err := labels.ParseMatchers(line) + pm, err := compat.Matchers(line, "config") if err != nil { return err } @@ -1031,7 +1032,7 @@ func (m *Matchers) UnmarshalJSON(data []byte) error { return err } for _, line := range lines { - pm, err := labels.ParseMatchers(line) + pm, err := compat.Matchers(line, "config") if err != nil { return err } diff --git a/vendor/github.com/prometheus/alertmanager/config/notifiers.go b/vendor/github.com/prometheus/alertmanager/config/notifiers.go index 2650db5f3b5..0759a573dfa 100644 --- a/vendor/github.com/prometheus/alertmanager/config/notifiers.go +++ b/vendor/github.com/prometheus/alertmanager/config/notifiers.go @@ -21,7 +21,6 @@ import ( "text/template" "time" - "github.com/pkg/errors" commoncfg "github.com/prometheus/common/config" "github.com/prometheus/common/sigv4" ) @@ -169,8 +168,9 @@ var ( NotifierConfig: NotifierConfig{ VSendResolved: true, }, - Title: `{{ template "msteams.default.title" . }}`, - Text: `{{ template "msteams.default.text" . }}`, + Title: `{{ template "msteams.default.title" . }}`, + Summary: `{{ template "msteams.default.summary" . }}`, + Text: `{{ template "msteams.default.text" . }}`, } ) @@ -540,7 +540,7 @@ func (c *WechatConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { } if !wechatTypeMatcher.MatchString(c.MessageType) { - return errors.Errorf("weChat message type %q does not match valid options %s", c.MessageType, wechatValidTypesRe) + return fmt.Errorf("weChat message type %q does not match valid options %s", c.MessageType, wechatValidTypesRe) } return nil @@ -586,18 +586,18 @@ func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(interface{}) error) error for _, r := range c.Responders { if r.ID == "" && r.Username == "" && r.Name == "" { - return errors.Errorf("opsGenieConfig responder %v has to have at least one of id, username or name specified", r) + return fmt.Errorf("opsGenieConfig responder %v has to have at least one of id, username or name specified", r) } if strings.Contains(r.Type, "{{") { _, err := template.New("").Parse(r.Type) if err != nil { - return errors.Errorf("opsGenieConfig responder %v type is not a valid template: %v", r, err) + return fmt.Errorf("opsGenieConfig responder %v type is not a valid template: %w", r, err) } } else { r.Type = strings.ToLower(r.Type) if !opsgenieTypeMatcher.MatchString(r.Type) { - return errors.Errorf("opsGenieConfig responder %v type does not match valid options %s", r, opsgenieValidTypesRe) + return fmt.Errorf("opsGenieConfig responder %v type does not match valid options %s", r, opsgenieValidTypesRe) } } } @@ -788,8 +788,9 @@ type MSTeamsConfig struct { HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"` WebhookURL *SecretURL `yaml:"webhook_url,omitempty" json:"webhook_url,omitempty"` - Title string `yaml:"title,omitempty" json:"title,omitempty"` - Text string `yaml:"text,omitempty" json:"text,omitempty"` + Title string `yaml:"title,omitempty" json:"title,omitempty"` + Summary string `yaml:"summary,omitempty" json:"summary,omitempty"` + Text string `yaml:"text,omitempty" json:"text,omitempty"` } func (c *MSTeamsConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { diff --git a/vendor/github.com/prometheus/alertmanager/dispatch/dispatch.go b/vendor/github.com/prometheus/alertmanager/dispatch/dispatch.go index 853d849688c..640b22abe27 100644 --- a/vendor/github.com/prometheus/alertmanager/dispatch/dispatch.go +++ b/vendor/github.com/prometheus/alertmanager/dispatch/dispatch.go @@ -15,6 +15,7 @@ package dispatch import ( "context" + "errors" "fmt" "sort" "sync" @@ -343,7 +344,7 @@ func (d *Dispatcher) processAlert(alert *types.Alert, route *Route) { _, _, err := d.stage.Exec(ctx, d.logger, alerts...) if err != nil { lvl := level.Error(d.logger) - if ctx.Err() == context.Canceled { + if errors.Is(ctx.Err(), context.Canceled) { // It is expected for the context to be canceled on // configuration reload or shutdown. In this case, the // message should only be logged at the debug level. diff --git a/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go b/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go index d48af09ad5f..a8a5585267f 100644 --- a/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go +++ b/vendor/github.com/prometheus/alertmanager/featurecontrol/featurecontrol.go @@ -25,26 +25,26 @@ import ( const ( FeatureReceiverNameInMetrics = "receiver-name-in-metrics" FeatureClassicMode = "classic-mode" - FeatureUTF8Mode = "utf8-mode" + FeatureUTF8StrictMode = "utf8-strict-mode" ) var AllowedFlags = []string{ FeatureReceiverNameInMetrics, FeatureClassicMode, - FeatureUTF8Mode, + FeatureUTF8StrictMode, } type Flagger interface { EnableReceiverNamesInMetrics() bool ClassicMode() bool - UTF8Mode() bool + UTF8StrictMode() bool } type Flags struct { logger log.Logger enableReceiverNamesInMetrics bool classicMode bool - utf8Mode bool + utf8StrictMode bool } func (f *Flags) EnableReceiverNamesInMetrics() bool { @@ -55,8 +55,8 @@ func (f *Flags) ClassicMode() bool { return f.classicMode } -func (f *Flags) UTF8Mode() bool { - return f.utf8Mode +func (f *Flags) UTF8StrictMode() bool { + return f.utf8StrictMode } type flagOption func(flags *Flags) @@ -73,9 +73,9 @@ func enableClassicMode() flagOption { } } -func enableUTF8Mode() flagOption { +func enableUTF8StrictMode() flagOption { return func(configs *Flags) { - configs.utf8Mode = true + configs.utf8StrictMode = true } } @@ -95,8 +95,8 @@ func NewFlags(logger log.Logger, features string) (Flagger, error) { case FeatureClassicMode: opts = append(opts, enableClassicMode()) level.Warn(logger).Log("msg", "Classic mode enabled") - case FeatureUTF8Mode: - opts = append(opts, enableUTF8Mode()) + case FeatureUTF8StrictMode: + opts = append(opts, enableUTF8StrictMode()) level.Warn(logger).Log("msg", "UTF-8 mode enabled") default: return nil, fmt.Errorf("Unknown option '%s' for --enable-feature", feature) @@ -107,7 +107,7 @@ func NewFlags(logger log.Logger, features string) (Flagger, error) { opt(fc) } - if fc.classicMode && fc.utf8Mode { + if fc.classicMode && fc.utf8StrictMode { return nil, errors.New("cannot have both classic and UTF-8 modes enabled") } @@ -120,4 +120,4 @@ func (n NoopFlags) EnableReceiverNamesInMetrics() bool { return false } func (n NoopFlags) ClassicMode() bool { return false } -func (n NoopFlags) UTF8Mode() bool { return false } +func (n NoopFlags) UTF8StrictMode() bool { return false } diff --git a/vendor/github.com/prometheus/alertmanager/matchers/compat/metrics.go b/vendor/github.com/prometheus/alertmanager/matchers/compat/metrics.go new file mode 100644 index 00000000000..4741cf182b9 --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/matchers/compat/metrics.go @@ -0,0 +1,60 @@ +// Copyright 2023 The Prometheus Authors +// 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 compat + +import ( + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +const ( + OriginAPI = "api" + OriginConfig = "config" +) + +var DefaultOrigins = []string{ + OriginAPI, + OriginConfig, +} + +var RegisteredMetrics = NewMetrics(prometheus.DefaultRegisterer) + +type Metrics struct { + Total *prometheus.GaugeVec + DisagreeTotal *prometheus.GaugeVec + IncompatibleTotal *prometheus.GaugeVec + InvalidTotal *prometheus.GaugeVec +} + +func NewMetrics(r prometheus.Registerer) *Metrics { + m := &Metrics{ + Total: promauto.With(r).NewGaugeVec(prometheus.GaugeOpts{ + Name: "alertmanager_matchers_parse", + Help: "Total number of matcher inputs parsed, including invalid inputs.", + }, []string{"origin"}), + DisagreeTotal: promauto.With(r).NewGaugeVec(prometheus.GaugeOpts{ + Name: "alertmanager_matchers_disagree", + Help: "Total number of matcher inputs which produce different parsings (disagreement).", + }, []string{"origin"}), + IncompatibleTotal: promauto.With(r).NewGaugeVec(prometheus.GaugeOpts{ + Name: "alertmanager_matchers_incompatible", + Help: "Total number of matcher inputs that are incompatible with the UTF-8 parser.", + }, []string{"origin"}), + InvalidTotal: promauto.With(r).NewGaugeVec(prometheus.GaugeOpts{ + Name: "alertmanager_matchers_invalid", + Help: "Total number of matcher inputs that could not be parsed.", + }, []string{"origin"}), + } + return m +} diff --git a/vendor/github.com/prometheus/alertmanager/matchers/compat/parse.go b/vendor/github.com/prometheus/alertmanager/matchers/compat/parse.go new file mode 100644 index 00000000000..4a9bfe89bcf --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/matchers/compat/parse.go @@ -0,0 +1,252 @@ +// Copyright 2023 The Prometheus Authors +// 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 compat + +import ( + "fmt" + "reflect" + "strings" + "unicode/utf8" + + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/model" + + "github.com/prometheus/alertmanager/featurecontrol" + "github.com/prometheus/alertmanager/matchers/parse" + "github.com/prometheus/alertmanager/pkg/labels" +) + +var ( + isValidLabelName = isValidClassicLabelName(log.NewNopLogger()) + parseMatcher = ClassicMatcherParser(log.NewNopLogger(), RegisteredMetrics) + parseMatchers = ClassicMatchersParser(log.NewNopLogger(), RegisteredMetrics) +) + +// IsValidLabelName returns true if the string is a valid label name. +func IsValidLabelName(name model.LabelName) bool { + return isValidLabelName(name) +} + +type ParseMatcher func(input, origin string) (*labels.Matcher, error) + +type ParseMatchers func(input, origin string) (labels.Matchers, error) + +// Matcher parses the matcher in the input string. It returns an error +// if the input is invalid or contains two or more matchers. +func Matcher(input, origin string) (*labels.Matcher, error) { + return parseMatcher(input, origin) +} + +// Matchers parses one or more matchers in the input string. It returns +// an error if the input is invalid. +func Matchers(input, origin string) (labels.Matchers, error) { + return parseMatchers(input, origin) +} + +// InitFromFlags initializes the compat package from the flagger. +func InitFromFlags(l log.Logger, m *Metrics, f featurecontrol.Flagger) { + if f.ClassicMode() { + isValidLabelName = isValidClassicLabelName(l) + parseMatcher = ClassicMatcherParser(l, m) + parseMatchers = ClassicMatchersParser(l, m) + } else if f.UTF8StrictMode() { + isValidLabelName = isValidUTF8LabelName(l) + parseMatcher = UTF8MatcherParser(l, m) + parseMatchers = UTF8MatchersParser(l, m) + } else { + isValidLabelName = isValidUTF8LabelName(l) + parseMatcher = FallbackMatcherParser(l, m) + parseMatchers = FallbackMatchersParser(l, m) + } +} + +// ClassicMatcherParser uses the pkg/labels parser to parse the matcher in +// the input string. +func ClassicMatcherParser(l log.Logger, m *Metrics) ParseMatcher { + return func(input, origin string) (matcher *labels.Matcher, err error) { + defer func() { + lbs := prometheus.Labels{"origin": origin} + m.Total.With(lbs).Inc() + if err != nil { + m.InvalidTotal.With(lbs).Inc() + } + }() + level.Debug(l).Log("msg", "Parsing with classic matchers parser", "input", input, "origin", origin) + return labels.ParseMatcher(input) + } +} + +// ClassicMatchersParser uses the pkg/labels parser to parse zero or more +// matchers in the input string. It returns an error if the input is invalid. +func ClassicMatchersParser(l log.Logger, m *Metrics) ParseMatchers { + return func(input, origin string) (matchers labels.Matchers, err error) { + defer func() { + lbs := prometheus.Labels{"origin": origin} + m.Total.With(lbs).Inc() + if err != nil { + m.InvalidTotal.With(lbs).Inc() + } + }() + level.Debug(l).Log("msg", "Parsing with classic matchers parser", "input", input, "origin", origin) + return labels.ParseMatchers(input) + } +} + +// UTF8MatcherParser uses the new matchers/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, m *Metrics) ParseMatcher { + return func(input, origin string) (matcher *labels.Matcher, err error) { + defer func() { + lbs := prometheus.Labels{"origin": origin} + m.Total.With(lbs).Inc() + if err != nil { + m.InvalidTotal.With(lbs).Inc() + } + }() + level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser", "input", input, "origin", origin) + if strings.HasPrefix(input, "{") || strings.HasSuffix(input, "}") { + return nil, fmt.Errorf("unexpected open or close brace: %s", input) + } + return parse.Matcher(input) + } +} + +// UTF8MatchersParser uses the new matchers/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, m *Metrics) ParseMatchers { + return func(input, origin string) (matchers labels.Matchers, err error) { + defer func() { + lbs := prometheus.Labels{"origin": origin} + m.Total.With(lbs).Inc() + if err != nil { + m.InvalidTotal.With(lbs).Inc() + } + }() + level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser", "input", input, "origin", origin) + return parse.Matchers(input) + } +} + +// FallbackMatcherParser uses the new matchers/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, m *Metrics) ParseMatcher { + return func(input, origin string) (matcher *labels.Matcher, err error) { + lbs := prometheus.Labels{"origin": origin} + defer func() { + m.Total.With(lbs).Inc() + if err != nil { + m.InvalidTotal.With(lbs).Inc() + } + }() + level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin) + if strings.HasPrefix(input, "{") || strings.HasSuffix(input, "}") { + return nil, fmt.Errorf("unexpected open or close brace: %s", input) + } + // Parse the input in both parsers to look for disagreement and incompatible + // inputs. + nMatcher, nErr := parse.Matcher(input) + cMatcher, cErr := labels.ParseMatcher(input) + if nErr != nil { + // If the input is invalid in both parsers, return the error. + if cErr != nil { + return nil, cErr + } + // The input is valid in the pkg/labels parser, but not the matchers/parse + // parser. This means the input is not forwards compatible. + m.IncompatibleTotal.With(lbs).Inc() + 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 old matchers parser as a fallback. To make this input compatible with the new 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", err, "suggestion", suggestion) + return cMatcher, nil + } + // If the input is valid in both parsers, but produces different results, + // then there is disagreement. + if nErr == nil && cErr == nil && !reflect.DeepEqual(nMatcher, cMatcher) { + m.DisagreeTotal.With(lbs).Inc() + level.Warn(l).Log("msg", "Matchers input has disagreement", "input", input, "origin", origin) + return cMatcher, nil + } + return nMatcher, nil + } +} + +// FallbackMatchersParser uses the new matchers/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, m *Metrics) ParseMatchers { + return func(input, origin string) (matchers labels.Matchers, err error) { + lbs := prometheus.Labels{"origin": origin} + defer func() { + m.Total.With(lbs).Inc() + if err != nil { + m.InvalidTotal.With(lbs).Inc() + } + }() + level.Debug(l).Log("msg", "Parsing with UTF-8 matchers parser, with fallback to classic matchers parser", "input", input, "origin", origin) + // Parse the input in both parsers to look for disagreement and incompatible + // inputs. + nMatchers, nErr := parse.Matchers(input) + cMatchers, cErr := labels.ParseMatchers(input) + if nErr != nil { + // If the input is invalid in both parsers, return the error. + if cErr != nil { + return nil, cErr + } + // The input is valid in the pkg/labels parser, but not the matchers/parse + // parser. This means the input is not forwards compatible. + m.IncompatibleTotal.With(lbs).Inc() + var sb strings.Builder + for i, n := range cMatchers { + sb.WriteString(n.String()) + if i < len(cMatchers)-1 { + sb.WriteRune(',') + } + } + suggestion := sb.String() + // The input is valid in the pkg/labels parser, but not the + // new matchers/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 old matchers parser as a fallback. To make this input compatible with the new 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", err, "suggestion", suggestion) + return cMatchers, nil + } + // If the input is valid in both parsers, but produces different results, + // then there is disagreement. We need to compare to labels.Matchers(cMatchers) + // as cMatchers is a []*labels.Matcher not labels.Matchers. + if nErr == nil && cErr == nil && !reflect.DeepEqual(nMatchers, labels.Matchers(cMatchers)) { + m.DisagreeTotal.With(lbs).Inc() + level.Warn(l).Log("msg", "Matchers input has disagreement", "input", input, "origin", origin) + return cMatchers, nil + } + return nMatchers, nil + } +} + +// isValidClassicLabelName returns true if the string is a valid classic label name. +func isValidClassicLabelName(_ log.Logger) func(model.LabelName) bool { + return func(name model.LabelName) bool { + return name.IsValid() + } +} + +// isValidUTF8LabelName returns true if the string is a valid UTF-8 label name. +func isValidUTF8LabelName(_ log.Logger) func(model.LabelName) bool { + return func(name model.LabelName) bool { + if len(name) == 0 { + return false + } + return utf8.ValidString(string(name)) + } +} diff --git a/vendor/github.com/prometheus/alertmanager/matchers/parse/lexer.go b/vendor/github.com/prometheus/alertmanager/matchers/parse/lexer.go new file mode 100644 index 00000000000..d6daa6a9e8b --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/matchers/parse/lexer.go @@ -0,0 +1,309 @@ +// Copyright 2023 The Prometheus Authors +// 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 parse + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" +) + +const ( + eof rune = -1 +) + +func isReserved(r rune) bool { + return unicode.IsSpace(r) || strings.ContainsRune("{}!=~,\\\"'`", r) +} + +// expectedError is returned when the next rune does not match what is expected. +type expectedError struct { + position + input string + expected string +} + +func (e expectedError) Error() string { + if e.offsetEnd >= len(e.input) { + return fmt.Sprintf("%d:%d: unexpected end of input, expected one of '%s'", + e.columnStart, + e.columnEnd, + e.expected, + ) + } + return fmt.Sprintf("%d:%d: %s: expected one of '%s'", + e.columnStart, + e.columnEnd, + e.input[e.offsetStart:e.offsetEnd], + e.expected, + ) +} + +// invalidInputError is returned when the next rune in the input does not match +// the grammar of Prometheus-like matchers. +type invalidInputError struct { + position + input string +} + +func (e invalidInputError) Error() string { + return fmt.Sprintf("%d:%d: %s: invalid input", + e.columnStart, + e.columnEnd, + e.input[e.offsetStart:e.offsetEnd], + ) +} + +// unterminatedError is returned when text in quotes does not have a closing quote. +type unterminatedError struct { + position + input string + quote rune +} + +func (e unterminatedError) Error() string { + return fmt.Sprintf("%d:%d: %s: missing end %c", + e.columnStart, + e.columnEnd, + e.input[e.offsetStart:e.offsetEnd], + e.quote, + ) +} + +// lexer scans a sequence of tokens that match the grammar of Prometheus-like +// matchers. A token is emitted for each call to scan() which returns the +// next token in the input or an error if the input does not conform to the +// grammar. A token can be one of a number of kinds and corresponds to a +// subslice of the input. Once the input has been consumed successive calls to +// scan() return a tokenEOF token. +type lexer struct { + input string + err error + start int // The offset of the current token. + pos int // The position of the cursor in the input. + width int // The width of the last rune. + column int // The column offset of the current token. + cols int // The number of columns (runes) decoded from the input. +} + +// Scans the next token in the input or an error if the input does not +// conform to the grammar. Once the input has been consumed successive +// calls scan() return a tokenEOF token. +func (l *lexer) scan() (token, error) { + t := token{} + // Do not attempt to emit more tokens if the input is invalid. + if l.err != nil { + return t, l.err + } + // Iterate over each rune in the input and either emit a token or an error. + for r := l.next(); r != eof; r = l.next() { + switch { + case r == '{': + t = l.emit(tokenOpenBrace) + return t, l.err + case r == '}': + t = l.emit(tokenCloseBrace) + return t, l.err + case r == ',': + t = l.emit(tokenComma) + return t, l.err + case r == '=' || r == '!': + l.rewind() + t, l.err = l.scanOperator() + return t, l.err + case r == '"': + l.rewind() + t, l.err = l.scanQuoted() + return t, l.err + case !isReserved(r): + l.rewind() + t, l.err = l.scanUnquoted() + return t, l.err + case unicode.IsSpace(r): + l.skip() + default: + l.err = invalidInputError{ + position: l.position(), + input: l.input, + } + return t, l.err + } + } + return t, l.err +} + +func (l *lexer) scanOperator() (token, error) { + // If the first rune is an '!' then it must be followed with either an + // '=' or '~' to not match a string or regex. + if l.accept("!") { + if l.accept("=") { + return l.emit(tokenNotEquals), nil + } + if l.accept("~") { + return l.emit(tokenNotMatches), nil + } + return token{}, expectedError{ + position: l.position(), + input: l.input, + expected: "=~", + } + } + // If the first rune is an '=' then it can be followed with an optional + // '~' to match a regex. + if l.accept("=") { + if l.accept("~") { + return l.emit(tokenMatches), nil + } + return l.emit(tokenEquals), nil + } + return token{}, expectedError{ + position: l.position(), + input: l.input, + expected: "!=", + } +} + +func (l *lexer) scanQuoted() (token, error) { + if err := l.expect("\""); err != nil { + return token{}, err + } + var isEscaped bool + for r := l.next(); r != eof; r = l.next() { + if isEscaped { + isEscaped = false + } else if r == '\\' { + isEscaped = true + } else if r == '"' { + l.rewind() + break + } + } + if err := l.expect("\""); err != nil { + return token{}, unterminatedError{ + position: l.position(), + input: l.input, + quote: '"', + } + } + return l.emit(tokenQuoted), nil +} + +func (l *lexer) scanUnquoted() (token, error) { + for r := l.next(); r != eof; r = l.next() { + if isReserved(r) { + l.rewind() + break + } + } + return l.emit(tokenUnquoted), nil +} + +// peek the next token in the input or an error if the input does not +// conform to the grammar. Once the input has been consumed successive +// calls peek() return a tokenEOF token. +func (l *lexer) peek() (token, error) { + start := l.start + pos := l.pos + width := l.width + column := l.column + cols := l.cols + // Do not reset l.err because we can return it on the next call to scan(). + defer func() { + l.start = start + l.pos = pos + l.width = width + l.column = column + l.cols = cols + }() + return l.scan() +} + +// position returns the position of the last emitted token. +func (l *lexer) position() position { + return position{ + offsetStart: l.start, + offsetEnd: l.pos, + columnStart: l.column, + columnEnd: l.cols, + } +} + +// accept consumes the next if its one of the valid runes. +// It returns true if the next rune was accepted, otherwise false. +func (l *lexer) accept(valid string) bool { + if strings.ContainsRune(valid, l.next()) { + return true + } + l.rewind() + return false +} + +// expect consumes the next rune if its one of the valid runes. +// it returns nil if the next rune is valid, otherwise an expectedError +// error. +func (l *lexer) expect(valid string) error { + if strings.ContainsRune(valid, l.next()) { + return nil + } + l.rewind() + return expectedError{ + position: l.position(), + input: l.input, + expected: valid, + } +} + +// emits returns the scanned input as a token. +func (l *lexer) emit(kind tokenKind) token { + t := token{ + kind: kind, + value: l.input[l.start:l.pos], + position: l.position(), + } + l.start = l.pos + l.column = l.cols + return t +} + +// next returns the next rune in the input or eof. +func (l *lexer) next() rune { + if l.pos >= len(l.input) { + l.width = 0 + return eof + } + r, width := utf8.DecodeRuneInString(l.input[l.pos:]) + l.width = width + l.pos += width + l.cols++ + return r +} + +// rewind the last rune in the input. It should not be called more than once +// between consecutive calls of next. +func (l *lexer) rewind() { + l.pos -= l.width + // When the next rune in the input is eof the width is zero. This check + // prevents cols from being decremented when the next rune being accepted + // is instead eof. + if l.width > 0 { + l.cols-- + } +} + +// skip the scanned input between start and pos. +func (l *lexer) skip() { + l.start = l.pos + l.column = l.cols +} diff --git a/vendor/github.com/prometheus/alertmanager/matchers/parse/parse.go b/vendor/github.com/prometheus/alertmanager/matchers/parse/parse.go new file mode 100644 index 00000000000..30a95b25544 --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/matchers/parse/parse.go @@ -0,0 +1,304 @@ +// Copyright 2023 The Prometheus Authors +// 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 parse + +import ( + "errors" + "fmt" + "os" + "runtime/debug" + + "github.com/prometheus/alertmanager/pkg/labels" +) + +var ( + errEOF = errors.New("end of input") + errExpectedEOF = errors.New("expected end of input") + errNoOpenBrace = errors.New("expected opening brace") + errNoCloseBrace = errors.New("expected close brace") + errNoLabelName = errors.New("expected label name") + errNoLabelValue = errors.New("expected label value") + errNoOperator = errors.New("expected an operator such as '=', '!=', '=~' or '!~'") + errExpectedComma = errors.New("expected a comma") + errExpectedCommaOrCloseBrace = errors.New("expected a comma or close brace") + errExpectedMatcherOrCloseBrace = errors.New("expected a matcher or close brace after comma") +) + +// Matchers parses one or more matchers in the input string. It returns an error +// if the input is invalid. +func Matchers(input string) (matchers labels.Matchers, err error) { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "parser panic: %s, %s", r, debug.Stack()) + err = errors.New("parser panic: this should never happen, check stderr for the stack trace") + } + }() + p := parser{lexer: lexer{input: input}} + return p.parse() +} + +// Matcher parses the matcher in the input string. It returns an error +// if the input is invalid or contains two or more matchers. +func Matcher(input string) (*labels.Matcher, error) { + m, err := Matchers(input) + if err != nil { + return nil, err + } + switch len(m) { + case 1: + return m[0], nil + case 0: + return nil, fmt.Errorf("no matchers") + default: + return nil, fmt.Errorf("expected 1 matcher, found %d", len(m)) + } +} + +// parseFunc is state in the finite state automata. +type parseFunc func(l *lexer) (parseFunc, error) + +// parser reads the sequence of tokens from the lexer and returns either a +// series of matchers or an error. It works as a finite state automata, where +// each state in the automata is a parseFunc. The finite state automata can move +// from one state to another by returning the next parseFunc. It terminates when +// a parseFunc returns nil as the next parseFunc, if the lexer attempts to scan +// input that does not match the expected grammar, or if the tokens returned from +// the lexer cannot be parsed into a complete series of matchers. +type parser struct { + matchers labels.Matchers + // Tracks if the input starts with an open brace and if we should expect to + // parse a close brace at the end of the input. + hasOpenBrace bool + lexer lexer +} + +func (p *parser) parse() (labels.Matchers, error) { + var ( + err error + fn = p.parseOpenBrace + l = &p.lexer + ) + for { + if fn, err = fn(l); err != nil { + return nil, err + } else if fn == nil { + break + } + } + return p.matchers, nil +} + +func (p *parser) parseOpenBrace(l *lexer) (parseFunc, error) { + var ( + hasCloseBrace bool + err error + ) + // Can start with an optional open brace. + p.hasOpenBrace, err = p.accept(l, tokenOpenBrace) + if err != nil { + if errors.Is(err, errEOF) { + return p.parseEOF, nil + } + return nil, err + } + // If the next token is a close brace there are no matchers in the input. + hasCloseBrace, err = p.acceptPeek(l, tokenCloseBrace) + if err != nil { + // If there is no more input after the open brace then parse the close brace + // so the error message contains ErrNoCloseBrace. + if errors.Is(err, errEOF) { + return p.parseCloseBrace, nil + } + return nil, err + } + if hasCloseBrace { + return p.parseCloseBrace, nil + } + return p.parseMatcher, nil +} + +func (p *parser) parseCloseBrace(l *lexer) (parseFunc, error) { + if p.hasOpenBrace { + // If there was an open brace there must be a matching close brace. + if _, err := p.expect(l, tokenCloseBrace); err != nil { + return nil, fmt.Errorf("0:%d: %w: %w", l.position().columnEnd, err, errNoCloseBrace) + } + } else { + // If there was no open brace there must not be a close brace either. + if _, err := p.expect(l, tokenCloseBrace); err == nil { + return nil, fmt.Errorf("0:%d: }: %w", l.position().columnEnd, errNoOpenBrace) + } + } + return p.parseEOF, nil +} + +func (p *parser) parseMatcher(l *lexer) (parseFunc, error) { + var ( + err error + t token + matchName, matchValue string + matchTy labels.MatchType + ) + // The first token should be the label name. + if t, err = p.expect(l, tokenQuoted, tokenUnquoted); err != nil { + return nil, fmt.Errorf("%w: %w", err, errNoLabelName) + } + matchName, err = t.unquote() + if err != nil { + return nil, fmt.Errorf("%d:%d: %s: invalid input", t.columnStart, t.columnEnd, t.value) + } + // The next token should be the operator. + if t, err = p.expect(l, tokenEquals, tokenNotEquals, tokenMatches, tokenNotMatches); err != nil { + return nil, fmt.Errorf("%w: %w", err, errNoOperator) + } + switch t.kind { + case tokenEquals: + matchTy = labels.MatchEqual + case tokenNotEquals: + matchTy = labels.MatchNotEqual + case tokenMatches: + matchTy = labels.MatchRegexp + case tokenNotMatches: + matchTy = labels.MatchNotRegexp + default: + panic(fmt.Sprintf("bad operator %s", t)) + } + // The next token should be the match value. Like the match name, this too + // can be either double-quoted UTF-8 or unquoted UTF-8 without reserved characters. + if t, err = p.expect(l, tokenUnquoted, tokenQuoted); err != nil { + return nil, fmt.Errorf("%w: %w", err, errNoLabelValue) + } + matchValue, err = t.unquote() + if err != nil { + return nil, fmt.Errorf("%d:%d: %s: invalid input", t.columnStart, t.columnEnd, t.value) + } + m, err := labels.NewMatcher(matchTy, matchName, matchValue) + if err != nil { + return nil, fmt.Errorf("failed to create matcher: %w", err) + } + p.matchers = append(p.matchers, m) + return p.parseEndOfMatcher, nil +} + +func (p *parser) parseEndOfMatcher(l *lexer) (parseFunc, error) { + t, err := p.expectPeek(l, tokenComma, tokenCloseBrace) + if err != nil { + if errors.Is(err, errEOF) { + // If this is the end of input we still need to check if the optional + // open brace has a matching close brace + return p.parseCloseBrace, nil + } + return nil, fmt.Errorf("%w: %w", err, errExpectedCommaOrCloseBrace) + } + switch t.kind { + case tokenComma: + return p.parseComma, nil + case tokenCloseBrace: + return p.parseCloseBrace, nil + default: + panic(fmt.Sprintf("bad token %s", t)) + } +} + +func (p *parser) parseComma(l *lexer) (parseFunc, error) { + if _, err := p.expect(l, tokenComma); err != nil { + return nil, fmt.Errorf("%w: %w", err, errExpectedComma) + } + // The token after the comma can be another matcher, a close brace or end of input. + t, err := p.expectPeek(l, tokenCloseBrace, tokenUnquoted, tokenQuoted) + if err != nil { + if errors.Is(err, errEOF) { + // If this is the end of input we still need to check if the optional + // open brace has a matching close brace + return p.parseCloseBrace, nil + } + return nil, fmt.Errorf("%w: %w", err, errExpectedMatcherOrCloseBrace) + } + if t.kind == tokenCloseBrace { + return p.parseCloseBrace, nil + } + return p.parseMatcher, nil +} + +func (p *parser) parseEOF(l *lexer) (parseFunc, error) { + t, err := l.scan() + if err != nil { + return nil, fmt.Errorf("%w: %w", err, errExpectedEOF) + } + if !t.isEOF() { + return nil, fmt.Errorf("%d:%d: %s: %w", t.columnStart, t.columnEnd, t.value, errExpectedEOF) + } + return nil, nil +} + +// accept returns true if the next token is one of the specified kinds, +// otherwise false. If the token is accepted it is consumed. tokenEOF is +// not an accepted kind and instead accept returns ErrEOF if there is no +// more input. +func (p *parser) accept(l *lexer, kinds ...tokenKind) (ok bool, err error) { + ok, err = p.acceptPeek(l, kinds...) + if ok { + if _, err = l.scan(); err != nil { + panic("failed to scan peeked token") + } + } + return ok, err +} + +// acceptPeek returns true if the next token is one of the specified kinds, +// otherwise false. However, unlike accept, acceptPeek does not consume accepted +// tokens. tokenEOF is not an accepted kind and instead accept returns ErrEOF +// if there is no more input. +func (p *parser) acceptPeek(l *lexer, kinds ...tokenKind) (bool, error) { + t, err := l.peek() + if err != nil { + return false, err + } + if t.isEOF() { + return false, errEOF + } + return t.isOneOf(kinds...), nil +} + +// expect returns the next token if it is one of the specified kinds, otherwise +// it returns an error. If the token is expected it is consumed. tokenEOF is not +// an accepted kind and instead expect returns ErrEOF if there is no more input. +func (p *parser) expect(l *lexer, kind ...tokenKind) (token, error) { + t, err := p.expectPeek(l, kind...) + if err != nil { + return t, err + } + if _, err = l.scan(); err != nil { + panic("failed to scan peeked token") + } + return t, nil +} + +// expect returns the next token if it is one of the specified kinds, otherwise +// it returns an error. However, unlike expect, expectPeek does not consume tokens. +// tokenEOF is not an accepted kind and instead expect returns ErrEOF if there is no +// more input. +func (p *parser) expectPeek(l *lexer, kind ...tokenKind) (token, error) { + t, err := l.peek() + if err != nil { + return t, err + } + if t.isEOF() { + return t, errEOF + } + if !t.isOneOf(kind...) { + return t, fmt.Errorf("%d:%d: unexpected %s", t.columnStart, t.columnEnd, t.value) + } + return t, nil +} diff --git a/vendor/github.com/prometheus/alertmanager/matchers/parse/token.go b/vendor/github.com/prometheus/alertmanager/matchers/parse/token.go new file mode 100644 index 00000000000..96baeeef43b --- /dev/null +++ b/vendor/github.com/prometheus/alertmanager/matchers/parse/token.go @@ -0,0 +1,108 @@ +// Copyright 2023 The Prometheus Authors +// 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 parse + +import ( + "errors" + "fmt" + "strconv" + "unicode/utf8" +) + +type tokenKind int + +const ( + tokenEOF tokenKind = iota + tokenOpenBrace + tokenCloseBrace + tokenComma + tokenEquals + tokenNotEquals + tokenMatches + tokenNotMatches + tokenQuoted + tokenUnquoted +) + +func (k tokenKind) String() string { + switch k { + case tokenOpenBrace: + return "OpenBrace" + case tokenCloseBrace: + return "CloseBrace" + case tokenComma: + return "Comma" + case tokenEquals: + return "Equals" + case tokenNotEquals: + return "NotEquals" + case tokenMatches: + return "Matches" + case tokenNotMatches: + return "NotMatches" + case tokenQuoted: + return "Quoted" + case tokenUnquoted: + return "Unquoted" + default: + return "EOF" + } +} + +type token struct { + kind tokenKind + value string + position +} + +// isEOF returns true if the token is an end of file token. +func (t token) isEOF() bool { + return t.kind == tokenEOF +} + +// isOneOf returns true if the token is one of the specified kinds. +func (t token) isOneOf(kinds ...tokenKind) bool { + for _, k := range kinds { + if k == t.kind { + return true + } + } + return false +} + +// unquote the value in token. If unquoted returns it unmodified. +func (t token) unquote() (string, error) { + if t.kind == tokenQuoted { + unquoted, err := strconv.Unquote(t.value) + if err != nil { + return "", err + } + if !utf8.ValidString(unquoted) { + return "", errors.New("quoted string contains invalid UTF-8 code points") + } + return unquoted, nil + } + return t.value, nil +} + +func (t token) String() string { + return fmt.Sprintf("(%s) '%s'", t.kind, t.value) +} + +type position struct { + offsetStart int // The start position in the input. + offsetEnd int // The end position in the input. + columnStart int // The column number. + columnEnd int // The end of the column. +} diff --git a/vendor/github.com/prometheus/alertmanager/nflog/nflog.go b/vendor/github.com/prometheus/alertmanager/nflog/nflog.go index c318cede809..c533dd0e668 100644 --- a/vendor/github.com/prometheus/alertmanager/nflog/nflog.go +++ b/vendor/github.com/prometheus/alertmanager/nflog/nflog.go @@ -212,7 +212,7 @@ func decodeState(r io.Reader) (state, error) { st[stateKey(string(e.Entry.GroupKey), e.Entry.Receiver)] = &e continue } - if err == io.EOF { + if errors.Is(err, io.EOF) { break } return nil, err diff --git a/vendor/github.com/prometheus/alertmanager/notify/email/email.go b/vendor/github.com/prometheus/alertmanager/notify/email/email.go index 944aabdf850..25b09875216 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/email/email.go +++ b/vendor/github.com/prometheus/alertmanager/notify/email/email.go @@ -17,6 +17,7 @@ import ( "bytes" "context" "crypto/tls" + "errors" "fmt" "math/rand" "mime" @@ -32,7 +33,6 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/pkg/errors" commoncfg "github.com/prometheus/common/config" "github.com/prometheus/alertmanager/config" @@ -133,7 +133,7 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { if n.conf.Smarthost.Port == "465" { tlsConfig, err := commoncfg.NewTLSConfig(&n.conf.TLSConfig) if err != nil { - return false, errors.Wrap(err, "parse TLS configuration") + return false, fmt.Errorf("parse TLS configuration: %w", err) } if tlsConfig.ServerName == "" { tlsConfig.ServerName = n.conf.Smarthost.Host @@ -141,7 +141,7 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { conn, err = tls.Dial("tcp", n.conf.Smarthost.String(), tlsConfig) if err != nil { - return true, errors.Wrap(err, "establish TLS connection to server") + return true, fmt.Errorf("establish TLS connection to server: %w", err) } } else { var ( @@ -150,13 +150,13 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { ) conn, err = d.DialContext(ctx, "tcp", n.conf.Smarthost.String()) if err != nil { - return true, errors.Wrap(err, "establish connection to server") + return true, fmt.Errorf("establish connection to server: %w", err) } } c, err = smtp.NewClient(conn, n.conf.Smarthost.Host) if err != nil { conn.Close() - return true, errors.Wrap(err, "create SMTP client") + return true, fmt.Errorf("create SMTP client: %w", err) } defer func() { // Try to clean up after ourselves but don't log anything if something has failed. @@ -168,37 +168,37 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { if n.conf.Hello != "" { err = c.Hello(n.conf.Hello) if err != nil { - return true, errors.Wrap(err, "send EHLO command") + return true, fmt.Errorf("send EHLO command: %w", err) } } // Global Config guarantees RequireTLS is not nil. if *n.conf.RequireTLS { if ok, _ := c.Extension("STARTTLS"); !ok { - return true, errors.Errorf("'require_tls' is true (default) but %q does not advertise the STARTTLS extension", n.conf.Smarthost) + 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) if err != nil { - return false, errors.Wrap(err, "parse TLS configuration") + return false, fmt.Errorf("parse TLS configuration: %w", err) } if tlsConf.ServerName == "" { tlsConf.ServerName = n.conf.Smarthost.Host } if err := c.StartTLS(tlsConf); err != nil { - return true, errors.Wrap(err, "send STARTTLS command") + return true, fmt.Errorf("send STARTTLS command: %w", err) } } if ok, mech := c.Extension("AUTH"); ok { auth, err := n.auth(mech) if err != nil { - return true, errors.Wrap(err, "find auth mechanism") + return true, fmt.Errorf("find auth mechanism: %w", err) } if auth != nil { if err := c.Auth(auth); err != nil { - return true, errors.Wrapf(err, "%T auth", auth) + return true, fmt.Errorf("%T auth: %w", auth, err) } } } @@ -210,37 +210,37 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { ) from := tmpl(n.conf.From) if tmplErr != nil { - return false, errors.Wrap(tmplErr, "execute 'from' template") + return false, fmt.Errorf("execute 'from' template: %w", tmplErr) } to := tmpl(n.conf.To) if tmplErr != nil { - return false, errors.Wrap(tmplErr, "execute 'to' template") + return false, fmt.Errorf("execute 'to' template: %w", tmplErr) } addrs, err := mail.ParseAddressList(from) if err != nil { - return false, errors.Wrap(err, "parse 'from' addresses") + return false, fmt.Errorf("parse 'from' addresses: %w", err) } if len(addrs) != 1 { - return false, errors.Errorf("must be exactly one 'from' address (got: %d)", len(addrs)) + return false, fmt.Errorf("must be exactly one 'from' address (got: %d)", len(addrs)) } if err = c.Mail(addrs[0].Address); err != nil { - return true, errors.Wrap(err, "send MAIL command") + return true, fmt.Errorf("send MAIL command: %w", err) } addrs, err = mail.ParseAddressList(to) if err != nil { - return false, errors.Wrapf(err, "parse 'to' addresses") + return false, fmt.Errorf("parse 'to' addresses: %w", err) } for _, addr := range addrs { if err = c.Rcpt(addr.Address); err != nil { - return true, errors.Wrapf(err, "send RCPT command") + return true, fmt.Errorf("send RCPT command: %w", err) } } // Send the email headers and body. message, err := c.Data() if err != nil { - return true, errors.Wrapf(err, "send DATA command") + return true, fmt.Errorf("send DATA command: %w", err) } defer message.Close() @@ -248,7 +248,7 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { for header, t := range n.conf.Headers { value, err := n.tmpl.ExecuteTextString(t, data) if err != nil { - return false, errors.Wrapf(err, "execute %q header template", header) + return false, fmt.Errorf("execute %q header template: %w", header, err) } fmt.Fprintf(buffer, "%s: %s\r\n", header, mime.QEncoding.Encode("utf-8", value)) } @@ -268,7 +268,7 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { // and active/resolved. _, err = message.Write(buffer.Bytes()) if err != nil { - return false, errors.Wrap(err, "write headers") + return false, fmt.Errorf("write headers: %w", err) } if len(n.conf.Text) > 0 { @@ -278,20 +278,20 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { "Content-Type": {"text/plain; charset=UTF-8"}, }) if err != nil { - return false, errors.Wrap(err, "create part for text template") + return false, fmt.Errorf("create part for text template: %w", err) } body, err := n.tmpl.ExecuteTextString(n.conf.Text, data) if err != nil { - return false, errors.Wrap(err, "execute text template") + return false, fmt.Errorf("execute text template: %w", err) } qw := quotedprintable.NewWriter(w) _, err = qw.Write([]byte(body)) if err != nil { - return true, errors.Wrap(err, "write text part") + return true, fmt.Errorf("write text part: %w", err) } err = qw.Close() if err != nil { - return true, errors.Wrap(err, "close text part") + return true, fmt.Errorf("close text part: %w", err) } } @@ -304,31 +304,31 @@ func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) { "Content-Type": {"text/html; charset=UTF-8"}, }) if err != nil { - return false, errors.Wrap(err, "create part for html template") + return false, fmt.Errorf("create part for html template: %w", err) } body, err := n.tmpl.ExecuteHTMLString(n.conf.HTML, data) if err != nil { - return false, errors.Wrap(err, "execute html template") + return false, fmt.Errorf("execute html template: %w", err) } qw := quotedprintable.NewWriter(w) _, err = qw.Write([]byte(body)) if err != nil { - return true, errors.Wrap(err, "write HTML part") + return true, fmt.Errorf("write HTML part: %w", err) } err = qw.Close() if err != nil { - return true, errors.Wrap(err, "close HTML part") + return true, fmt.Errorf("close HTML part: %w", err) } } err = multipartWriter.Close() if err != nil { - return false, errors.Wrap(err, "close multipartWriter") + return false, fmt.Errorf("close multipartWriter: %w", err) } _, err = message.Write(multipartBuffer.Bytes()) if err != nil { - return false, errors.Wrap(err, "write body buffer") + return false, fmt.Errorf("write body buffer: %w", err) } success = true diff --git a/vendor/github.com/prometheus/alertmanager/notify/msteams/msteams.go b/vendor/github.com/prometheus/alertmanager/notify/msteams/msteams.go index 8675d7b6da5..73994ee08d0 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/msteams/msteams.go +++ b/vendor/github.com/prometheus/alertmanager/notify/msteams/msteams.go @@ -52,6 +52,7 @@ type teamsMessage struct { Context string `json:"@context"` Type string `json:"type"` Title string `json:"title"` + Summary string `json:"summary"` Text string `json:"text"` ThemeColor string `json:"themeColor"` } @@ -98,6 +99,10 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) if err != nil { return false, err } + summary := tmpl(n.conf.Summary) + if err != nil { + return false, err + } alerts := types.Alerts(as...) color := colorGrey @@ -112,6 +117,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) Context: "http://schema.org/extensions", Type: "MessageCard", Title: title, + Summary: summary, Text: text, ThemeColor: color, } diff --git a/vendor/github.com/prometheus/alertmanager/notify/notify.go b/vendor/github.com/prometheus/alertmanager/notify/notify.go index 33d499af30c..0a2b0d032b3 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/notify.go +++ b/vendor/github.com/prometheus/alertmanager/notify/notify.go @@ -15,6 +15,7 @@ package notify import ( "context" + "errors" "fmt" "sort" "sync" @@ -24,7 +25,6 @@ import ( "github.com/cespare/xxhash/v2" "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" @@ -686,7 +686,7 @@ func (n *DedupStage) Exec(ctx context.Context, _ log.Logger, alerts ...*types.Al ctx = WithResolvedAlerts(ctx, resolved) entries, err := n.nflog.Query(nflog.QGroupKey(gkey), nflog.QReceiver(n.recv)) - if err != nil && err != nflog.ErrNotFound { + if err != nil && !errors.Is(err, nflog.ErrNotFound) { return ctx, nil, err } @@ -696,7 +696,7 @@ func (n *DedupStage) Exec(ctx context.Context, _ log.Logger, alerts ...*types.Al case 1: entry = entries[0] default: - return ctx, nil, errors.Errorf("unexpected entry result size %d", len(entries)) + return ctx, nil, fmt.Errorf("unexpected entry result size %d", len(entries)) } if n.needsUpdate(entry, firingSet, resolvedSet, repeatInterval) { @@ -736,7 +736,8 @@ func (r RetryStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Ale failureReason := DefaultReason.String() if err != nil { - if e, ok := errors.Cause(err).(*ErrorWithReason); ok { + var e *ErrorWithReason + if errors.As(err, &e) { failureReason = e.Reason.String() } r.metrics.numTotalFailedNotifications.WithLabelValues(append(r.labelValues, failureReason)...).Inc() @@ -790,9 +791,17 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale case <-ctx.Done(): if iErr == nil { iErr = ctx.Err() + if errors.Is(iErr, context.Canceled) { + iErr = NewErrorWithReason(ContextCanceledReason, iErr) + } else if errors.Is(iErr, context.DeadlineExceeded) { + iErr = NewErrorWithReason(ContextDeadlineExceededReason, iErr) + } } - return ctx, nil, errors.Wrapf(iErr, "%s/%s: notify retry canceled after %d attempts", r.groupName, r.integration.String(), i) + if iErr != nil { + return ctx, nil, fmt.Errorf("%s/%s: notify retry canceled after %d attempts: %w", r.groupName, r.integration.String(), i, iErr) + } + return ctx, nil, nil default: } @@ -806,16 +815,17 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale if err != nil { 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) + return ctx, alerts, fmt.Errorf("%s/%s: notify retry canceled due to unrecoverable error after %d attempts: %w", r.groupName, r.integration.String(), i, err) } - if ctx.Err() == nil && (iErr == nil || err.Error() != iErr.Error()) { - // Log the error if the context isn't done and the error isn't the same as before. - level.Warn(l).Log("msg", "Notify attempt failed, will retry later", "attempts", i, "err", err) + if ctx.Err() == nil { + if iErr == nil || err.Error() != iErr.Error() { + // Log the error if the context isn't done and the error isn't the same as before. + level.Warn(l).Log("msg", "Notify attempt failed, will retry later", "attempts", i, "err", err) + } + // Save this error to be able to return the last seen error by an + // integration upon context timeout. + iErr = err } - - // Save this error to be able to return the last seen error by an - // integration upon context timeout. - iErr = err } else { lvl := level.Info(l) if i <= 1 { @@ -828,9 +838,16 @@ func (r RetryStage) exec(ctx context.Context, l log.Logger, alerts ...*types.Ale case <-ctx.Done(): if iErr == nil { iErr = ctx.Err() + if errors.Is(iErr, context.Canceled) { + iErr = NewErrorWithReason(ContextCanceledReason, iErr) + } else if errors.Is(iErr, context.DeadlineExceeded) { + iErr = NewErrorWithReason(ContextDeadlineExceededReason, iErr) + } } - - return ctx, nil, errors.Wrapf(iErr, "%s/%s: notify retry canceled after %d attempts", r.groupName, r.integration.String(), i) + if iErr != nil { + return ctx, nil, fmt.Errorf("%s/%s: notify retry canceled after %d attempts: %w", r.groupName, r.integration.String(), i, iErr) + } + return ctx, nil, nil } } } diff --git a/vendor/github.com/prometheus/alertmanager/notify/opsgenie/opsgenie.go b/vendor/github.com/prometheus/alertmanager/notify/opsgenie/opsgenie.go index 0a5e218f3af..4421cd65454 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/opsgenie/opsgenie.go +++ b/vendor/github.com/prometheus/alertmanager/notify/opsgenie/opsgenie.go @@ -24,7 +24,6 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/pkg/errors" commoncfg "github.com/prometheus/common/config" "github.com/prometheus/common/model" @@ -281,13 +280,13 @@ func (n *Notifier) createRequests(ctx context.Context, as ...*types.Alert) ([]*h } else { content, err := os.ReadFile(n.conf.APIKeyFile) if err != nil { - return nil, false, errors.Wrap(err, "read key_file error") + return nil, false, fmt.Errorf("read key_file error: %w", err) } apiKey = tmpl(string(content)) } if err != nil { - return nil, false, errors.Wrap(err, "templating error") + return nil, false, fmt.Errorf("templating error: %w", err) } for _, req := range requests { diff --git a/vendor/github.com/prometheus/alertmanager/notify/pagerduty/pagerduty.go b/vendor/github.com/prometheus/alertmanager/notify/pagerduty/pagerduty.go index 39ec84d9195..6f6e1a20a8e 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/pagerduty/pagerduty.go +++ b/vendor/github.com/prometheus/alertmanager/notify/pagerduty/pagerduty.go @@ -17,6 +17,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -26,7 +27,6 @@ import ( "github.com/alecthomas/units" "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/pkg/errors" commoncfg "github.com/prometheus/common/config" "github.com/prometheus/common/model" @@ -120,7 +120,7 @@ type pagerDutyPayload struct { func (n *Notifier) encodeMessage(msg *pagerDutyMessage) (bytes.Buffer, error) { var buf bytes.Buffer if err := json.NewEncoder(&buf).Encode(msg); err != nil { - return buf, errors.Wrap(err, "failed to encode PagerDuty message") + return buf, fmt.Errorf("failed to encode PagerDuty message: %w", err) } if buf.Len() > maxEventSize { @@ -137,7 +137,7 @@ func (n *Notifier) encodeMessage(msg *pagerDutyMessage) (bytes.Buffer, error) { buf.Reset() if err := json.NewEncoder(&buf).Encode(msg); err != nil { - return buf, errors.Wrap(err, "failed to encode PagerDuty message") + return buf, fmt.Errorf("failed to encode PagerDuty message: %w", err) } } @@ -164,7 +164,7 @@ func (n *Notifier) notifyV1( if serviceKey == "" { content, fileErr := os.ReadFile(n.conf.ServiceKeyFile) if fileErr != nil { - return false, errors.Wrap(fileErr, "failed to read service key from file") + return false, fmt.Errorf("failed to read service key from file: %w", fileErr) } serviceKey = strings.TrimSpace(string(content)) } @@ -183,7 +183,7 @@ func (n *Notifier) notifyV1( } if tmplErr != nil { - return false, errors.Wrap(tmplErr, "failed to template PagerDuty v1 message") + return false, fmt.Errorf("failed to template PagerDuty v1 message: %w", tmplErr) } // Ensure that the service key isn't empty after templating. @@ -198,7 +198,7 @@ func (n *Notifier) notifyV1( resp, err := notify.PostJSON(ctx, n.client, n.apiV1, &encodedMsg) if err != nil { - return true, errors.Wrap(err, "failed to post message to PagerDuty v1") + return true, fmt.Errorf("failed to post message to PagerDuty v1: %w", err) } defer notify.Drain(resp) @@ -229,7 +229,7 @@ func (n *Notifier) notifyV2( if routingKey == "" { content, fileErr := os.ReadFile(n.conf.RoutingKeyFile) if fileErr != nil { - return false, errors.Wrap(fileErr, "failed to read routing key from file") + return false, fmt.Errorf("failed to read routing key from file: %w", fileErr) } routingKey = strings.TrimSpace(string(content)) } @@ -277,7 +277,7 @@ func (n *Notifier) notifyV2( } if tmplErr != nil { - return false, errors.Wrap(tmplErr, "failed to template PagerDuty v2 message") + return false, fmt.Errorf("failed to template PagerDuty v2 message: %w", tmplErr) } // Ensure that the routing key isn't empty after templating. @@ -292,7 +292,7 @@ func (n *Notifier) notifyV2( resp, err := notify.PostJSON(ctx, n.client, n.conf.URL.String(), &encodedMsg) if err != nil { - return true, errors.Wrap(err, "failed to post message to PagerDuty") + return true, fmt.Errorf("failed to post message to PagerDuty: %w", err) } defer notify.Drain(resp) @@ -325,7 +325,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) for k, v := range n.conf.Details { detail, err := n.tmpl.ExecuteTextString(v, data) if err != nil { - return false, errors.Wrapf(err, "%q: failed to template %q", k, v) + return false, fmt.Errorf("%q: failed to template %q: %w", k, v, err) } details[k] = detail } diff --git a/vendor/github.com/prometheus/alertmanager/notify/slack/slack.go b/vendor/github.com/prometheus/alertmanager/notify/slack/slack.go index 79097b7a7eb..b4b5a195e91 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/slack/slack.go +++ b/vendor/github.com/prometheus/alertmanager/notify/slack/slack.go @@ -25,7 +25,6 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/pkg/errors" commoncfg "github.com/prometheus/common/config" "github.com/prometheus/alertmanager/config" @@ -216,7 +215,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) // classify them as retriable or not. retry, err := n.retrier.Check(resp.StatusCode, resp.Body) if err != nil { - err = errors.Wrap(err, fmt.Sprintf("channel %q", req.Channel)) + err = fmt.Errorf("channel %q: %w", req.Channel, err) return retry, notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(resp.StatusCode), err) } @@ -224,7 +223,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) // https://slack.dev/node-slack-sdk/web-api#handle-errors retry, err = checkResponseError(resp) if err != nil { - err = errors.Wrap(err, fmt.Sprintf("channel %q", req.Channel)) + err = fmt.Errorf("channel %q: %w", req.Channel, err) return retry, notify.NewErrorWithReason(notify.ClientErrorReason, err) } @@ -235,7 +234,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) func checkResponseError(resp *http.Response) (bool, error) { body, err := io.ReadAll(resp.Body) if err != nil { - return true, errors.Wrap(err, "could not read response body") + return true, fmt.Errorf("could not read response body: %w", err) } if strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { @@ -265,7 +264,7 @@ func checkJSONResponseError(body []byte) (bool, error) { var data response if err := json.Unmarshal(body, &data); err != nil { - return true, errors.Wrapf(err, "could not unmarshal JSON response %q", string(body)) + return true, fmt.Errorf("could not unmarshal JSON response %q: %w", string(body), err) } if !data.OK { return false, fmt.Errorf("error response from Slack: %s", data.Error) diff --git a/vendor/github.com/prometheus/alertmanager/notify/sns/sns.go b/vendor/github.com/prometheus/alertmanager/notify/sns/sns.go index b9881b01750..996566c7262 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/sns/sns.go +++ b/vendor/github.com/prometheus/alertmanager/notify/sns/sns.go @@ -15,6 +15,7 @@ package sns import ( "context" + "errors" "fmt" "net/http" "strings" @@ -69,7 +70,8 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err client, err := n.createSNSClient(tmpl) if err != nil { - if e, ok := err.(awserr.RequestFailure); ok { + var e awserr.RequestFailure + if errors.As(err, &e) { return n.retrier.Check(e.StatusCode(), strings.NewReader(e.Message())) } return true, err @@ -82,7 +84,8 @@ func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, err publishOutput, err := client.Publish(publishInput) if err != nil { - if e, ok := err.(awserr.RequestFailure); ok { + var e awserr.RequestFailure + if errors.As(err, &e) { retryable, error := n.retrier.Check(e.StatusCode(), strings.NewReader(e.Message())) reasonErr := notify.NewErrorWithReason(notify.GetFailureReasonFromStatusCode(e.StatusCode()), error) diff --git a/vendor/github.com/prometheus/alertmanager/notify/util.go b/vendor/github.com/prometheus/alertmanager/notify/util.go index 706856c1605..bd54e2b47de 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/util.go +++ b/vendor/github.com/prometheus/alertmanager/notify/util.go @@ -16,6 +16,7 @@ package notify import ( "context" "crypto/sha256" + "errors" "fmt" "io" "net/http" @@ -24,7 +25,6 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/pkg/errors" "github.com/prometheus/common/version" "github.com/prometheus/alertmanager/template" @@ -39,8 +39,8 @@ var UserAgentHeader = fmt.Sprintf("Alertmanager/%s", version.Version) // RedactURL removes the URL part from an error of *url.Error type. func RedactURL(err error) error { - e, ok := err.(*url.Error) - if !ok { + var e *url.Error + if !errors.As(err, &e) { return err } e.URL = "" @@ -160,7 +160,7 @@ type Key string func ExtractGroupKey(ctx context.Context) (Key, error) { key, ok := GroupKey(ctx) if !ok { - return "", errors.Errorf("group key missing") + return "", fmt.Errorf("group key missing") } return Key(key), nil } @@ -270,6 +270,8 @@ const ( DefaultReason Reason = iota ClientErrorReason ServerErrorReason + ContextCanceledReason + ContextDeadlineExceededReason ) func (s Reason) String() string { @@ -280,13 +282,17 @@ func (s Reason) String() string { return "clientError" case ServerErrorReason: return "serverError" + case ContextCanceledReason: + return "contextCanceled" + case ContextDeadlineExceededReason: + return "contextDeadlineExceeded" default: panic(fmt.Sprintf("unknown Reason: %d", s)) } } // possibleFailureReasonCategory is a list of possible failure reason. -var possibleFailureReasonCategory = []string{DefaultReason.String(), ClientErrorReason.String(), ServerErrorReason.String()} +var possibleFailureReasonCategory = []string{DefaultReason.String(), ClientErrorReason.String(), ServerErrorReason.String(), ContextCanceledReason.String(), ContextDeadlineExceededReason.String()} // GetFailureReasonFromStatusCode returns the reason for the failure based on the status code provided. func GetFailureReasonFromStatusCode(statusCode int) Reason { diff --git a/vendor/github.com/prometheus/alertmanager/notify/victorops/victorops.go b/vendor/github.com/prometheus/alertmanager/notify/victorops/victorops.go index 780f7a8d1da..44088b4eb36 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/victorops/victorops.go +++ b/vendor/github.com/prometheus/alertmanager/notify/victorops/victorops.go @@ -24,7 +24,6 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/pkg/errors" commoncfg "github.com/prometheus/common/config" "github.com/prometheus/common/model" @@ -83,14 +82,14 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } else { content, fileErr := os.ReadFile(n.conf.APIKeyFile) if fileErr != nil { - return false, errors.Wrap(fileErr, "failed to read API key from file") + return false, fmt.Errorf("failed to read API key from file: %w", fileErr) } apiKey = strings.TrimSpace(string(content)) } apiURL.Path += fmt.Sprintf("%s/%s", apiKey, tmpl(n.conf.RoutingKey)) if err != nil { - return false, fmt.Errorf("templating error: %s", err) + return false, fmt.Errorf("templating error: %w", err) } buf, err := n.createVictorOpsPayload(ctx, as...) @@ -155,14 +154,14 @@ func (n *Notifier) createVictorOpsPayload(ctx context.Context, as ...*types.Aler } if err != nil { - return nil, fmt.Errorf("templating error: %s", err) + return nil, fmt.Errorf("templating error: %w", err) } // Add custom fields to the payload. for k, v := range n.conf.CustomFields { msg[k] = tmpl(v) if err != nil { - return nil, fmt.Errorf("templating error: %s", err) + return nil, fmt.Errorf("templating error: %w", err) } } diff --git a/vendor/github.com/prometheus/alertmanager/notify/wechat/wechat.go b/vendor/github.com/prometheus/alertmanager/notify/wechat/wechat.go index 2e3b2c29eca..9eb77b66a5e 100644 --- a/vendor/github.com/prometheus/alertmanager/notify/wechat/wechat.go +++ b/vendor/github.com/prometheus/alertmanager/notify/wechat/wechat.go @@ -17,6 +17,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -25,7 +26,6 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/pkg/errors" commoncfg "github.com/prometheus/common/config" "github.com/prometheus/alertmanager/config" @@ -101,7 +101,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) parameters.Add("corpsecret", tmpl(string(n.conf.APISecret))) parameters.Add("corpid", tmpl(string(n.conf.CorpID))) if err != nil { - return false, fmt.Errorf("templating error: %s", err) + return false, fmt.Errorf("templating error: %w", err) } u := n.conf.APIURL.Copy() @@ -147,7 +147,7 @@ func (n *Notifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) } } if err != nil { - return false, fmt.Errorf("templating error: %s", err) + return false, fmt.Errorf("templating error: %w", err) } var buf bytes.Buffer diff --git a/vendor/github.com/prometheus/alertmanager/pkg/labels/parse.go b/vendor/github.com/prometheus/alertmanager/pkg/labels/parse.go index a125d59d8a8..5138716e0a2 100644 --- a/vendor/github.com/prometheus/alertmanager/pkg/labels/parse.go +++ b/vendor/github.com/prometheus/alertmanager/pkg/labels/parse.go @@ -14,11 +14,10 @@ package labels import ( + "fmt" "regexp" "strings" "unicode/utf8" - - "github.com/pkg/errors" ) var ( @@ -118,7 +117,7 @@ func ParseMatchers(s string) ([]*Matcher, error) { func ParseMatcher(s string) (_ *Matcher, err error) { ms := re.FindStringSubmatch(s) if len(ms) == 0 { - return nil, errors.Errorf("bad matcher format: %s", s) + return nil, fmt.Errorf("bad matcher format: %s", s) } var ( @@ -134,7 +133,7 @@ func ParseMatcher(s string) (_ *Matcher, err error) { } if !utf8.ValidString(rawValue) { - return nil, errors.Errorf("matcher value not valid UTF-8: %s", ms[3]) + return nil, fmt.Errorf("matcher value not valid UTF-8: %s", ms[3]) } // Unescape the rawValue: @@ -163,7 +162,7 @@ func ParseMatcher(s string) (_ *Matcher, err error) { value.WriteByte('\\') case '"': if !expectTrailingQuote || i < len(rawValue)-1 { - return nil, errors.Errorf("matcher value contains unescaped double quote: %s", ms[3]) + return nil, fmt.Errorf("matcher value contains unescaped double quote: %s", ms[3]) } expectTrailingQuote = false default: @@ -172,7 +171,7 @@ func ParseMatcher(s string) (_ *Matcher, err error) { } if expectTrailingQuote { - return nil, errors.Errorf("matcher value contains unescaped double quote: %s", ms[3]) + return nil, fmt.Errorf("matcher value contains unescaped double quote: %s", ms[3]) } return NewMatcher(typeMap[ms[2]], ms[1], value.String()) diff --git a/vendor/github.com/prometheus/alertmanager/silence/silence.go b/vendor/github.com/prometheus/alertmanager/silence/silence.go index 68844d38b0f..710323f747c 100644 --- a/vendor/github.com/prometheus/alertmanager/silence/silence.go +++ b/vendor/github.com/prometheus/alertmanager/silence/silence.go @@ -17,6 +17,7 @@ package silence import ( "bytes" + "errors" "fmt" "io" "math/rand" @@ -32,11 +33,11 @@ import ( "github.com/go-kit/log/level" uuid "github.com/gofrs/uuid" "github.com/matttproud/golang_protobuf_extensions/pbutil" - "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" "github.com/prometheus/alertmanager/cluster" + "github.com/prometheus/alertmanager/matchers/compat" "github.com/prometheus/alertmanager/pkg/labels" pb "github.com/prometheus/alertmanager/silence/silencepb" "github.com/prometheus/alertmanager/types" @@ -77,7 +78,7 @@ func (c matcherCache) add(s *pb.Silence) (labels.Matchers, error) { case pb.Matcher_NOT_REGEXP: mt = labels.MatchNotRegexp default: - return nil, errors.Errorf("unknown matcher type %q", m.Type) + return nil, fmt.Errorf("unknown matcher type %q", m.Type) } matcher, err := labels.NewMatcher(mt, m.Name, m.Pattern) if err != nil { @@ -331,6 +332,7 @@ func New(o Options) (*Silences, error) { if err := o.validate(); err != nil { return nil, err } + s := &Silences{ clock: clock.New(), mc: matcherCache{}, @@ -467,9 +469,8 @@ func (s *Silences) GC() (int, error) { return n, nil } -// ValidateMatcher runs validation on the matcher name, type, and pattern. -var ValidateMatcher = func(m *pb.Matcher) error { - if !model.LabelName(m.Name).IsValid() { +func validateMatcher(m *pb.Matcher) error { + if !compat.IsValidLabelName(model.LabelName(m.Name)) { return fmt.Errorf("invalid label name %q", m.Name) } switch m.Type { @@ -479,7 +480,7 @@ var ValidateMatcher = func(m *pb.Matcher) error { } case pb.Matcher_REGEXP, pb.Matcher_NOT_REGEXP: if _, err := regexp.Compile(m.Pattern); err != nil { - return fmt.Errorf("invalid regular expression %q: %s", m.Pattern, err) + return fmt.Errorf("invalid regular expression %q: %w", m.Pattern, err) } default: return fmt.Errorf("unknown matcher type %q", m.Type) @@ -507,9 +508,10 @@ func validateSilence(s *pb.Silence) error { return errors.New("at least one matcher required") } allMatchEmpty := true + for i, m := range s.Matchers { - if err := ValidateMatcher(m); err != nil { - return fmt.Errorf("invalid label matcher %d: %s", i, err) + if err := validateMatcher(m); err != nil { + return fmt.Errorf("invalid label matcher %d: %w", i, err) } allMatchEmpty = allMatchEmpty && matchesEmpty(m) } @@ -545,11 +547,13 @@ func (s *Silences) getSilence(id string) (*pb.Silence, bool) { return msil.Silence, true } -func (s *Silences) setSilence(sil *pb.Silence, now time.Time) error { +func (s *Silences) setSilence(sil *pb.Silence, now time.Time, skipValidate bool) error { sil.UpdatedAt = now - if err := validateSilence(sil); err != nil { - return errors.Wrap(err, "silence invalid") + if !skipValidate { + if err := validateSilence(sil); err != nil { + return fmt.Errorf("silence invalid: %w", err) + } } msil := &pb.MeshSilence{ @@ -583,19 +587,19 @@ func (s *Silences) Set(sil *pb.Silence) (string, error) { } if ok { if canUpdate(prev, sil, now) { - return sil.Id, s.setSilence(sil, now) + return sil.Id, 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 "", errors.Wrap(err, "expire previous silence") + return "", fmt.Errorf("expire previous silence: %w", err) } } } // If we got here it's either a new silence or a replacing one. uid, err := uuid.NewV4() if err != nil { - return "", errors.Wrap(err, "generate uuid") + return "", fmt.Errorf("generate uuid: %w", err) } sil.Id = uid.String() @@ -603,7 +607,7 @@ func (s *Silences) Set(sil *pb.Silence) (string, error) { sil.StartsAt = now } - return sil.Id, s.setSilence(sil, now) + return sil.Id, s.setSilence(sil, now, false) } // canUpdate returns true if silence a can be updated to b without @@ -662,7 +666,9 @@ func (s *Silences) expire(id string) error { sil.EndsAt = now } - return s.setSilence(sil, 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) } // QueryParam expresses parameters along which silences are queried. @@ -951,7 +957,7 @@ func decodeState(r io.Reader) (state, error) { st[s.Silence.Id] = &s continue } - if err == io.EOF { + if errors.Is(err, io.EOF) { break } return nil, err diff --git a/vendor/github.com/prometheus/alertmanager/template/default.tmpl b/vendor/github.com/prometheus/alertmanager/template/default.tmpl index a1bbfe96fc3..8b2bb7470e7 100644 --- a/vendor/github.com/prometheus/alertmanager/template/default.tmpl +++ b/vendor/github.com/prometheus/alertmanager/template/default.tmpl @@ -146,6 +146,7 @@ Alerts Resolved: {{ end }} {{ end }} +{{ define "msteams.default.summary" }}{{ template "__subject" . }}{{ end }} {{ define "msteams.default.title" }}{{ template "__subject" . }}{{ end }} {{ define "msteams.default.text" }} {{ if gt (len .Alerts.Firing) 0 }} diff --git a/vendor/github.com/prometheus/alertmanager/types/types.go b/vendor/github.com/prometheus/alertmanager/types/types.go index b427a3d1d3f..54a889ab9c6 100644 --- a/vendor/github.com/prometheus/alertmanager/types/types.go +++ b/vendor/github.com/prometheus/alertmanager/types/types.go @@ -14,6 +14,7 @@ package types import ( + "fmt" "strings" "sync" "time" @@ -21,6 +22,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/model" + "github.com/prometheus/alertmanager/matchers/compat" "github.com/prometheus/alertmanager/pkg/labels" ) @@ -302,6 +304,39 @@ type Alert struct { Timeout bool } +func validateLs(ls model.LabelSet) error { + for ln, lv := range ls { + if !compat.IsValidLabelName(ln) { + return fmt.Errorf("invalid name %q", ln) + } + if !lv.IsValid() { + return fmt.Errorf("invalid value %q", lv) + } + } + return nil +} + +// Validate overrides the same method in model.Alert to allow UTF-8 labels. +// This can be removed once prometheus/common has support for UTF-8. +func (a *Alert) Validate() error { + if a.StartsAt.IsZero() { + return fmt.Errorf("start time missing") + } + if !a.EndsAt.IsZero() && a.EndsAt.Before(a.StartsAt) { + return fmt.Errorf("start time must be before end time") + } + if len(a.Labels) == 0 { + return fmt.Errorf("at least one label pair required") + } + if err := validateLs(a.Labels); err != nil { + return fmt.Errorf("invalid label set: %w", err) + } + if err := validateLs(a.Annotations); err != nil { + return fmt.Errorf("invalid annotations: %w", err) + } + return nil +} + // AlertSlice is a sortable slice of Alerts. type AlertSlice []*Alert diff --git a/vendor/gopkg.in/telebot.v3/admin.go b/vendor/gopkg.in/telebot.v3/admin.go index c4c828d19ea..cc8b33a2876 100644 --- a/vendor/gopkg.in/telebot.v3/admin.go +++ b/vendor/gopkg.in/telebot.v3/admin.go @@ -21,12 +21,29 @@ type Rights struct { CanRestrictMembers bool `json:"can_restrict_members"` CanPromoteMembers bool `json:"can_promote_members"` CanSendMessages bool `json:"can_send_messages"` - CanSendMedia bool `json:"can_send_media_messages"` CanSendPolls bool `json:"can_send_polls"` CanSendOther bool `json:"can_send_other_messages"` CanAddPreviews bool `json:"can_add_web_page_previews"` CanManageVideoChats bool `json:"can_manage_video_chats"` CanManageChat bool `json:"can_manage_chat"` + CanManageTopics bool `json:"can_manage_topics"` + + CanSendMedia bool `json:"can_send_media_messages,omitempty"` // deprecated + CanSendAudios bool `json:"can_send_audios"` + CanSendDocuments bool `json:"can_send_documents"` + CanSendPhotos bool `json:"can_send_photos"` + CanSendVideos bool `json:"can_send_videos"` + CanSendVideoNotes bool `json:"can_send_video_notes"` + CanSendVoiceNotes bool `json:"can_send_voice_notes"` + + // Independent defines whether the chat permissions are set independently. + // If not, the can_send_other_messages and can_add_web_page_previews permissions + // will imply the can_send_messages, can_send_audios, can_send_documents, can_send_photos, + // can_send_videos, can_send_video_notes, and can_send_voice_notes permissions; + // the can_send_polls permission will imply the can_send_messages permission. + // + // Works for Restrict and SetGroupPermissions methods only. + Independent bool `json:"-"` } // NoRights is the default Rights{}. @@ -35,9 +52,8 @@ func NoRights() Rights { return Rights{} } // NoRestrictions should be used when un-restricting or // un-promoting user. // -// member.Rights = tele.NoRestrictions() -// b.Restrict(chat, member) -// +// member.Rights = tele.NoRestrictions() +// b.Restrict(chat, member) func NoRestrictions() Rights { return Rights{ CanBeEdited: true, @@ -50,12 +66,18 @@ func NoRestrictions() Rights { CanPinMessages: false, CanPromoteMembers: false, CanSendMessages: true, - CanSendMedia: true, CanSendPolls: true, CanSendOther: true, CanAddPreviews: true, CanManageVideoChats: false, CanManageChat: false, + CanManageTopics: false, + CanSendAudios: true, + CanSendDocuments: true, + CanSendPhotos: true, + CanSendVideos: true, + CanSendVideoNotes: true, + CanSendVoiceNotes: true, } } @@ -72,12 +94,18 @@ func AdminRights() Rights { CanPinMessages: true, CanPromoteMembers: true, CanSendMessages: true, - CanSendMedia: true, CanSendPolls: true, CanSendOther: true, CanAddPreviews: true, CanManageVideoChats: true, CanManageChat: true, + CanManageTopics: true, + CanSendAudios: true, + CanSendDocuments: true, + CanSendPhotos: true, + CanSendVideos: true, + CanSendVideoNotes: true, + CanSendVoiceNotes: true, } } @@ -120,20 +148,22 @@ func (b *Bot) Unban(chat *Chat, user *User, forBanned ...bool) error { // Restrict lets you restrict a subset of member's rights until // member.RestrictedUntil, such as: // -// * can send messages -// * can send media -// * can send other -// * can add web page previews -// +// - can send messages +// - can send media +// - can send other +// - can add web page previews func (b *Bot) Restrict(chat *Chat, member *ChatMember) error { - prv, until := member.Rights, member.RestrictedUntil + perms, until := member.Rights, member.RestrictedUntil params := map[string]interface{}{ - "chat_id": chat.Recipient(), - "user_id": member.User.Recipient(), - "until_date": strconv.FormatInt(until, 10), + "chat_id": chat.Recipient(), + "user_id": member.User.Recipient(), + "until_date": strconv.FormatInt(until, 10), + "permissions": perms, + } + if perms.Independent { + params["use_independent_chat_permissions"] = true } - embedRights(params, prv) _, err := b.Raw("restrictChatMember", params) return err @@ -141,24 +171,21 @@ func (b *Bot) Restrict(chat *Chat, member *ChatMember) error { // Promote lets you update member's admin rights, such as: // -// * can change info -// * can post messages -// * can edit messages -// * can delete messages -// * can invite users -// * can restrict members -// * can pin messages -// * can promote members -// +// - can change info +// - can post messages +// - can edit messages +// - can delete messages +// - can invite users +// - can restrict members +// - can pin messages +// - can promote members func (b *Bot) Promote(chat *Chat, member *ChatMember) error { - prv := member.Rights - params := map[string]interface{}{ "chat_id": chat.Recipient(), "user_id": member.User.Recipient(), "is_anonymous": member.Anonymous, } - embedRights(params, prv) + embedRights(params, member.Rights) _, err := b.Raw("promoteChatMember", params) return err @@ -171,7 +198,6 @@ func (b *Bot) Promote(chat *Chat, member *ChatMember) error { // // If the chat is a group or a supergroup and // no administrators were appointed, only the creator will be returned. -// func (b *Bot) AdminsOf(chat *Chat) ([]ChatMember, error) { params := map[string]string{ "chat_id": chat.Recipient(), diff --git a/vendor/gopkg.in/telebot.v3/bot.go b/vendor/gopkg.in/telebot.v3/bot.go index 52d6bcc0f99..e3a9a41f26a 100644 --- a/vendor/gopkg.in/telebot.v3/bot.go +++ b/vendor/gopkg.in/telebot.v3/bot.go @@ -160,21 +160,20 @@ var ( // // Example: // -// b.Handle("/start", func (c tele.Context) error { -// return c.Reply("Hello!") -// }) +// b.Handle("/start", func (c tele.Context) error { +// return c.Reply("Hello!") +// }) // -// b.Handle(&inlineButton, func (c tele.Context) error { -// return c.Respond(&tele.CallbackResponse{Text: "Hello!"}) -// }) +// b.Handle(&inlineButton, func (c tele.Context) error { +// return c.Respond(&tele.CallbackResponse{Text: "Hello!"}) +// }) // // Middleware usage: // -// b.Handle("/ban", onBan, middleware.Whitelist(ids...)) -// +// b.Handle("/ban", onBan, middleware.Whitelist(ids...)) func (b *Bot) Handle(endpoint interface{}, h HandlerFunc, m ...MiddlewareFunc) { if len(b.group.middleware) > 0 { - m = append(b.group.middleware, m...) + m = appendMiddleware(b.group.middleware, m) } handler := func(c Context) error { @@ -256,16 +255,16 @@ func (b *Bot) NewContext(u Update) Context { // some Sendable (or string!) and optional send options. // // NOTE: -// Since most arguments are of type interface{}, but have pointer -// method receivers, make sure to pass them by-pointer, NOT by-value. // -// What is a send option exactly? It can be one of the following types: +// Since most arguments are of type interface{}, but have pointer +// method receivers, make sure to pass them by-pointer, NOT by-value. // -// - *SendOptions (the actual object accepted by Telegram API) -// - *ReplyMarkup (a component of SendOptions) -// - Option (a shortcut flag for popular options) -// - ParseMode (HTML, Markdown, etc) +// What is a send option exactly? It can be one of the following types: // +// - *SendOptions (the actual object accepted by Telegram API) +// - *ReplyMarkup (a component of SendOptions) +// - Option (a shortcut flag for popular options) +// - ParseMode (HTML, Markdown, etc) func (b *Bot) Send(to Recipient, what interface{}, opts ...interface{}) (*Message, error) { if to == nil { return nil, ErrBadRecipient @@ -284,6 +283,7 @@ func (b *Bot) Send(to Recipient, what interface{}, opts ...interface{}) (*Messag } // SendAlbum sends multiple instances of media as a single message. +// To include the caption, make sure the first Inputtable of an album has it. // From all existing options, it only supports tele.Silent. func (b *Bot) SendAlbum(to Recipient, a Album, opts ...interface{}) ([]Message, error) { if to == nil { @@ -437,14 +437,13 @@ func (b *Bot) Copy(to Recipient, msg Editable, options ...interface{}) (*Message // // Use cases: // -// b.Edit(m, m.Text, newMarkup) -// b.Edit(m, "new text", tele.ModeHTML) -// b.Edit(m, &tele.ReplyMarkup{...}) -// b.Edit(m, &tele.Photo{File: ...}) -// b.Edit(m, tele.Location{42.1337, 69.4242}) -// b.Edit(c, "edit inline message from the callback") -// b.Edit(r, "edit message from chosen inline result") -// +// b.Edit(m, m.Text, newMarkup) +// b.Edit(m, "new text", tele.ModeHTML) +// b.Edit(m, &tele.ReplyMarkup{...}) +// b.Edit(m, &tele.Photo{File: ...}) +// b.Edit(m, tele.Location{42.1337, 69.4242}) +// b.Edit(c, "edit inline message from the callback") +// b.Edit(r, "edit message from chosen inline result") func (b *Bot) Edit(msg Editable, what interface{}, opts ...interface{}) (*Message, error) { var ( method string @@ -503,7 +502,6 @@ func (b *Bot) Edit(msg Editable, what interface{}, opts ...interface{}) (*Messag // // If edited message is sent by the bot, returns it, // otherwise returns nil and ErrTrueResult. -// func (b *Bot) EditReplyMarkup(msg Editable, markup *ReplyMarkup) (*Message, error) { msgID, chatID := msg.MessageSig() params := make(map[string]string) @@ -537,7 +535,6 @@ func (b *Bot) EditReplyMarkup(msg Editable, markup *ReplyMarkup) (*Message, erro // // If edited message is sent by the bot, returns it, // otherwise returns nil and ErrTrueResult. -// func (b *Bot) EditCaption(msg Editable, caption string, opts ...interface{}) (*Message, error) { msgID, chatID := msg.MessageSig() @@ -571,9 +568,8 @@ func (b *Bot) EditCaption(msg Editable, caption string, opts ...interface{}) (*M // // Use cases: // -// b.EditMedia(m, &tele.Photo{File: tele.FromDisk("chicken.jpg")}) -// b.EditMedia(m, &tele.Video{File: tele.FromURL("http://video.mp4")}) -// +// b.EditMedia(m, &tele.Photo{File: tele.FromDisk("chicken.jpg")}) +// b.EditMedia(m, &tele.Video{File: tele.FromURL("http://video.mp4")}) func (b *Bot) EditMedia(msg Editable, media Inputtable, opts ...interface{}) (*Message, error) { var ( repr string @@ -655,15 +651,14 @@ func (b *Bot) EditMedia(msg Editable, media Inputtable, opts ...interface{}) (*M // Delete removes the message, including service messages. // This function will panic upon nil Editable. // -// - A message can only be deleted if it was sent less than 48 hours ago. -// - A dice message in a private chat can only be deleted if it was sent more than 24 hours ago. -// - Bots can delete outgoing messages in private chats, groups, and supergroups. -// - Bots can delete incoming messages in private chats. -// - Bots granted can_post_messages permissions can delete outgoing messages in channels. -// - If the bot is an administrator of a group, it can delete any message there. -// - If the bot has can_delete_messages permission in a supergroup or a -// channel, it can delete any message there. -// +// - A message can only be deleted if it was sent less than 48 hours ago. +// - A dice message in a private chat can only be deleted if it was sent more than 24 hours ago. +// - Bots can delete outgoing messages in private chats, groups, and supergroups. +// - Bots can delete incoming messages in private chats. +// - Bots granted can_post_messages permissions can delete outgoing messages in channels. +// - If the bot is an administrator of a group, it can delete any message there. +// - If the bot has can_delete_messages permission in a supergroup or a +// channel, it can delete any message there. func (b *Bot) Delete(msg Editable) error { msgID, chatID := msg.MessageSig() @@ -685,8 +680,7 @@ func (b *Bot) Delete(msg Editable) error { // // Currently, Telegram supports only a narrow range of possible // actions, these are aligned as constants of this package. -// -func (b *Bot) Notify(to Recipient, action ChatAction) error { +func (b *Bot) Notify(to Recipient, action ChatAction, threadID ...int) error { if to == nil { return ErrBadRecipient } @@ -696,6 +690,10 @@ func (b *Bot) Notify(to Recipient, action ChatAction) error { "action": string(action), } + if len(threadID) > 0 { + params["message_thread_id"] = strconv.Itoa(threadID[0]) + } + _, err := b.Raw("sendChatAction", params) return err } @@ -705,10 +703,9 @@ func (b *Bot) Notify(to Recipient, action ChatAction) error { // // Example: // -// b.Ship(query) // OK -// b.Ship(query, opts...) // OK with options -// b.Ship(query, "Oops!") // Error message -// +// b.Ship(query) // OK +// b.Ship(query, opts...) // OK with options +// b.Ship(query, "Oops!") // Error message func (b *Bot) Ship(query *ShippingQuery, what ...interface{}) error { params := map[string]string{ "shipping_query_id": query.ID, @@ -761,9 +758,8 @@ func (b *Bot) Accept(query *PreCheckoutQuery, errorMessage ...string) error { // // Example: // -// b.Respond(c) -// b.Respond(c, response) -// +// b.Respond(c) +// b.Respond(c, response) func (b *Bot) Respond(c *Callback, resp ...*CallbackResponse) error { var r *CallbackResponse if resp == nil { @@ -821,7 +817,6 @@ func (b *Bot) AnswerWebApp(query *Query, r Result) (*WebAppMessage, error) { // // Usually, Telegram-provided File objects miss FilePath so you might need to // perform an additional request to fetch them. -// func (b *Bot) FileByID(fileID string) (File, error) { params := map[string]string{ "file_id": fileID, @@ -901,7 +896,6 @@ func (b *Bot) File(file *File) (io.ReadCloser, error) { // // If the message is sent by the bot, returns it, // otherwise returns nil and ErrTrueResult. -// func (b *Bot) StopLiveLocation(msg Editable, opts ...interface{}) (*Message, error) { msgID, chatID := msg.MessageSig() @@ -926,7 +920,6 @@ func (b *Bot) StopLiveLocation(msg Editable, opts ...interface{}) (*Message, err // // It supports ReplyMarkup. // This function will panic upon nil Editable. -// func (b *Bot) StopPoll(msg Editable, opts ...interface{}) (*Poll, error) { msgID, chatID := msg.MessageSig() @@ -966,7 +959,6 @@ func (b *Bot) Leave(chat *Chat) error { // // It supports Silent option. // This function will panic upon nil Editable. -// func (b *Bot) Pin(msg Editable, opts ...interface{}) error { msgID, chatID := msg.MessageSig() @@ -1011,7 +1003,6 @@ func (b *Bot) UnpinAll(chat *Chat) error { // // Including current name of the user for one-on-one conversations, // current username of a user, group or channel, etc. -// func (b *Bot) ChatByID(id int64) (*Chat, error) { return b.ChatByUsername(strconv.FormatInt(id, 10)) } @@ -1109,9 +1100,8 @@ func (b *Bot) MenuButton(chat *User) (*MenuButton, error) { // // It accepts two kinds of menu button arguments: // -// - MenuButtonType for simple menu buttons (default, commands) -// - MenuButton complete structure for web_app menu button type -// +// - MenuButtonType for simple menu buttons (default, commands) +// - MenuButton complete structure for web_app menu button type func (b *Bot) SetMenuButton(chat *User, mb interface{}) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), diff --git a/vendor/gopkg.in/telebot.v3/chat.go b/vendor/gopkg.in/telebot.v3/chat.go index ab767463ca8..74d6333bf09 100644 --- a/vendor/gopkg.in/telebot.v3/chat.go +++ b/vendor/gopkg.in/telebot.v3/chat.go @@ -10,13 +10,16 @@ import ( type User struct { ID int64 `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Username string `json:"username"` - LanguageCode string `json:"language_code"` - IsBot bool `json:"is_bot"` - IsPremium bool `json:"is_premium"` - AddedToMenu bool `json:"added_to_attachment_menu"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + IsForum bool `json:"is_forum"` + Username string `json:"username"` + LanguageCode string `json:"language_code"` + IsBot bool `json:"is_bot"` + IsPremium bool `json:"is_premium"` + AddedToMenu bool `json:"added_to_attachment_menu"` + Usernames []string `json:"active_usernames"` + CustomEmojiStatus string `json:"emoji_status_custom_emoji_id"` // Returns only in getMe CanJoinGroups bool `json:"can_join_groups"` @@ -44,20 +47,22 @@ type Chat struct { Username string `json:"username"` // Returns only in getChat - Bio string `json:"bio,omitempty"` - Photo *ChatPhoto `json:"photo,omitempty"` - Description string `json:"description,omitempty"` - InviteLink string `json:"invite_link,omitempty"` - PinnedMessage *Message `json:"pinned_message,omitempty"` - Permissions *Rights `json:"permissions,omitempty"` - SlowMode int `json:"slow_mode_delay,omitempty"` - StickerSet string `json:"sticker_set_name,omitempty"` - CanSetStickerSet bool `json:"can_set_sticker_set,omitempty"` - LinkedChatID int64 `json:"linked_chat_id,omitempty"` - ChatLocation *ChatLocation `json:"location,omitempty"` - Private bool `json:"has_private_forwards,omitempty"` - Protected bool `json:"has_protected_content,omitempty"` - NoVoiceAndVideo bool `json:"has_restricted_voice_and_video_messages"` + Bio string `json:"bio,omitempty"` + Photo *ChatPhoto `json:"photo,omitempty"` + Description string `json:"description,omitempty"` + InviteLink string `json:"invite_link,omitempty"` + PinnedMessage *Message `json:"pinned_message,omitempty"` + Permissions *Rights `json:"permissions,omitempty"` + SlowMode int `json:"slow_mode_delay,omitempty"` + StickerSet string `json:"sticker_set_name,omitempty"` + CanSetStickerSet bool `json:"can_set_sticker_set,omitempty"` + LinkedChatID int64 `json:"linked_chat_id,omitempty"` + ChatLocation *ChatLocation `json:"location,omitempty"` + Private bool `json:"has_private_forwards,omitempty"` + Protected bool `json:"has_protected_content,omitempty"` + NoVoiceAndVideo bool `json:"has_restricted_voice_and_video_messages"` + HiddenMembers bool `json:"has_hidden_members,omitempty"` + AggressiveAntiSpam bool `json:"has_aggressive_anti_spam_enabled,omitempty"` } // Recipient returns chat ID (see Recipient interface). @@ -101,6 +106,7 @@ type ChatMember struct { Role MemberStatus `json:"status"` Title string `json:"custom_title"` Anonymous bool `json:"is_anonymous"` + Member bool `json:"is_member,omitempty"` // Date when restrictions will be lifted for the user, unix time. // @@ -162,14 +168,13 @@ func (c *ChatMemberUpdate) Time() time.Time { // // Example: // -// group := tele.ChatID(-100756389456) -// b.Send(group, "Hello!") -// -// type Config struct { -// AdminGroup tele.ChatID `json:"admin_group"` -// } -// b.Send(conf.AdminGroup, "Hello!") +// group := tele.ChatID(-100756389456) +// b.Send(group, "Hello!") // +// type Config struct { +// AdminGroup tele.ChatID `json:"admin_group"` +// } +// b.Send(conf.AdminGroup, "Hello!") type ChatID int64 // Recipient returns chat ID (see Recipient interface). @@ -185,6 +190,12 @@ type ChatJoinRequest struct { // Sender is the user that sent the join request. Sender *User `json:"from"` + // UserChatID is an ID of a private chat with the user + // who sent the join request. The bot can use this ID + // for 5 minutes to send messages until the join request + // is processed, assuming no other administrator contacted the user. + UserChatID int64 `json:"user_chat_id"` + // Unixtime, use ChatJoinRequest.Time() to get time.Time. Unixtime int64 `json:"date"` @@ -427,6 +438,9 @@ func (b *Bot) SetGroupPermissions(chat *Chat, perms Rights) error { "chat_id": chat.Recipient(), "permissions": perms, } + if perms.Independent { + params["use_independent_chat_permissions"] = true + } _, err := b.Raw("setChatPermissions", params) return err diff --git a/vendor/gopkg.in/telebot.v3/context.go b/vendor/gopkg.in/telebot.v3/context.go index 9654aaeb596..3306c8f737b 100644 --- a/vendor/gopkg.in/telebot.v3/context.go +++ b/vendor/gopkg.in/telebot.v3/context.go @@ -46,12 +46,15 @@ type Context interface { // ChatMember returns chat member changes. ChatMember() *ChatMemberUpdate - // ChatJoinRequest returns cha + // ChatJoinRequest returns the chat join request. ChatJoinRequest() *ChatJoinRequest // Migration returns both migration from and to chat IDs. Migration() (int64, int64) + // Topic returns the topic changes. + Topic() *Topic + // Sender returns the current recipient, depending on the context type. // Returns nil if user is not presented. Sender() *User @@ -240,6 +243,22 @@ func (c *nativeContext) Migration() (int64, int64) { return c.u.Message.MigrateFrom, c.u.Message.MigrateTo } +func (c *nativeContext) Topic() *Topic { + m := c.u.Message + if m == nil { + return nil + } + switch { + case m.TopicCreated != nil: + return m.TopicCreated + case m.TopicReopened != nil: + return m.TopicReopened + case m.TopicEdited != nil: + return m.TopicEdited + } + return nil +} + func (c *nativeContext) Sender() *User { switch { case c.u.Callback != nil: diff --git a/vendor/gopkg.in/telebot.v3/errors.go b/vendor/gopkg.in/telebot.v3/errors.go index 73d6d74e1c9..0197e1934ec 100644 --- a/vendor/gopkg.in/telebot.v3/errors.go +++ b/vendor/gopkg.in/telebot.v3/errors.go @@ -77,6 +77,7 @@ var ( // Bad request errors var ( ErrBadButtonData = NewError(400, "Bad Request: BUTTON_DATA_INVALID") + ErrBadUserID = NewError(400, "Bad Request: USER_ID_INVALID") ErrBadPollOptions = NewError(400, "Bad Request: expected an Array of String as options") ErrBadURLContent = NewError(400, "Bad Request: failed to get HTTP URL content") ErrCantEditMessage = NewError(400, "Bad Request: message can't be edited") @@ -117,6 +118,10 @@ var ( ErrWrongTypeOfContent = NewError(400, "Bad Request: wrong type of the web page content") ErrWrongURL = NewError(400, "Bad Request: wrong HTTP URL specified") ErrForwardMessage = NewError(400, "Bad Request: administrators of the chat restricted message forwarding") + ErrUserAlreadyParticipant = NewError(400, "Bad Request: USER_ALREADY_PARTICIPANT", "User is already a participant") + ErrHideRequesterMissing = NewError(400, "Bad Request: HIDE_REQUESTER_MISSING") + ErrChannelsTooMuch = NewError(400, "Bad Request: CHANNELS_TOO_MUCH") + ErrChannelsTooMuchUser = NewError(400, "Bad Request: USER_CHANNELS_TOO_MUCH") ) // Forbidden errors @@ -124,6 +129,7 @@ var ( ErrBlockedByUser = NewError(403, "Forbidden: bot was blocked by the user") ErrKickedFromGroup = NewError(403, "Forbidden: bot was kicked from the group chat") ErrKickedFromSuperGroup = NewError(403, "Forbidden: bot was kicked from the supergroup chat") + ErrKickedFromChannel = NewError(403, "Forbidden: bot was kicked from the channel chat") ErrNotStartedByUser = NewError(403, "Forbidden: bot can't initiate conversation with a user") ErrUserIsDeactivated = NewError(403, "Forbidden: user is deactivated") ) @@ -141,6 +147,8 @@ func Err(s string) error { return ErrInternal case ErrBadButtonData.ʔ(): return ErrBadButtonData + case ErrBadUserID.ʔ(): + return ErrBadUserID case ErrBadPollOptions.ʔ(): return ErrBadPollOptions case ErrBadURLContent.ʔ(): @@ -225,12 +233,22 @@ func Err(s string) error { return ErrKickedFromGroup case ErrKickedFromSuperGroup.ʔ(): return ErrKickedFromSuperGroup + case ErrKickedFromChannel.ʔ(): + return ErrKickedFromChannel case ErrNotStartedByUser.ʔ(): return ErrNotStartedByUser case ErrUserIsDeactivated.ʔ(): return ErrUserIsDeactivated case ErrForwardMessage.ʔ(): return ErrForwardMessage + case ErrUserAlreadyParticipant.ʔ(): + return ErrUserAlreadyParticipant + case ErrHideRequesterMissing.ʔ(): + return ErrHideRequesterMissing + case ErrChannelsTooMuch.ʔ(): + return ErrChannelsTooMuch + case ErrChannelsTooMuchUser.ʔ(): + return ErrChannelsTooMuchUser default: return nil } diff --git a/vendor/gopkg.in/telebot.v3/inline.go b/vendor/gopkg.in/telebot.v3/inline.go index b3691393cb7..2a889582201 100644 --- a/vendor/gopkg.in/telebot.v3/inline.go +++ b/vendor/gopkg.in/telebot.v3/inline.go @@ -93,9 +93,9 @@ type Results []Result // MarshalJSON makes sure IQRs have proper IDs and Type variables set. func (results Results) MarshalJSON() ([]byte, error) { - for _, result := range results { + for i, result := range results { if result.ResultID() == "" { - result.SetResultID(fmt.Sprintf("%d", &result)) + result.SetResultID(fmt.Sprintf("%d", &results[i])) } if err := inferIQR(result); err != nil { return nil, err diff --git a/vendor/gopkg.in/telebot.v3/markup.go b/vendor/gopkg.in/telebot.v3/markup.go index 77bca6bf943..29236db34e1 100644 --- a/vendor/gopkg.in/telebot.v3/markup.go +++ b/vendor/gopkg.in/telebot.v3/markup.go @@ -53,6 +53,9 @@ type ReplyMarkup struct { // Placeholder will be shown in the input field when the reply is active. Placeholder string `json:"input_field_placeholder,omitempty"` + + // IsPersistent allows to control when the keyboard is shown. + IsPersistent bool `json:"is_persistent,omitempty"` } func (r *ReplyMarkup) copy() *ReplyMarkup { @@ -79,17 +82,19 @@ func (r *ReplyMarkup) copy() *ReplyMarkup { // Btn is a constructor button, which will later become either a reply, or an inline button. type Btn struct { - Unique string `json:"unique,omitempty"` - Text string `json:"text,omitempty"` - URL string `json:"url,omitempty"` - Data string `json:"callback_data,omitempty"` - InlineQuery string `json:"switch_inline_query,omitempty"` - InlineQueryChat string `json:"switch_inline_query_current_chat,omitempty"` - Contact bool `json:"request_contact,omitempty"` - Location bool `json:"request_location,omitempty"` - Poll PollType `json:"request_poll,omitempty"` - Login *Login `json:"login_url,omitempty"` - WebApp *WebApp `json:"web_app,omitempty"` + Unique string `json:"unique,omitempty"` + Text string `json:"text,omitempty"` + URL string `json:"url,omitempty"` + Data string `json:"callback_data,omitempty"` + InlineQuery string `json:"switch_inline_query,omitempty"` + InlineQueryChat string `json:"switch_inline_query_current_chat,omitempty"` + Login *Login `json:"login_url,omitempty"` + WebApp *WebApp `json:"web_app,omitempty"` + Contact bool `json:"request_contact,omitempty"` + Location bool `json:"request_location,omitempty"` + Poll PollType `json:"request_poll,omitempty"` + User *ReplyRecipient `json:"request_user,omitempty"` + Chat *ReplyRecipient `json:"request_chat,omitempty"` } // Row represents an array of buttons, a row. @@ -106,7 +111,6 @@ func (r *ReplyMarkup) Row(many ...Btn) Row { // // `Split(3, []Btn{six buttons...}) -> [[1, 2, 3], [4, 5, 6]]` // `Split(2, []Btn{six buttons...}) -> [[1, 2],[3, 4],[5, 6]]` -// func (r *ReplyMarkup) Split(max int, btns []Btn) []Row { rows := make([]Row, (max-1+len(btns))/max) for i, b := range btns { @@ -158,18 +162,6 @@ func (r *ReplyMarkup) Text(text string) Btn { return Btn{Text: text} } -func (r *ReplyMarkup) Contact(text string) Btn { - return Btn{Contact: true, Text: text} -} - -func (r *ReplyMarkup) Location(text string) Btn { - return Btn{Location: true, Text: text} -} - -func (r *ReplyMarkup) Poll(text string, poll PollType) Btn { - return Btn{Poll: poll, Text: text} -} - func (r *ReplyMarkup) Data(text, unique string, data ...string) Btn { return Btn{ Unique: unique, @@ -190,6 +182,26 @@ func (r *ReplyMarkup) QueryChat(text, query string) Btn { return Btn{Text: text, InlineQueryChat: query} } +func (r *ReplyMarkup) Contact(text string) Btn { + return Btn{Contact: true, Text: text} +} + +func (r *ReplyMarkup) Location(text string) Btn { + return Btn{Location: true, Text: text} +} + +func (r *ReplyMarkup) Poll(text string, poll PollType) Btn { + return Btn{Poll: poll, Text: text} +} + +func (r *ReplyMarkup) User(text string, user *ReplyRecipient) Btn { + return Btn{Text: text, User: user} +} + +func (r *ReplyMarkup) Chat(text string, chat *ReplyRecipient) Btn { + return Btn{Text: text, Chat: chat} +} + func (r *ReplyMarkup) Login(text string, login *Login) Btn { return Btn{Login: login, Text: text} } @@ -202,14 +214,15 @@ func (r *ReplyMarkup) WebApp(text string, app *WebApp) Btn { // // Set either Contact or Location to true in order to request // sensitive info, such as user's phone number or current location. -// type ReplyButton struct { Text string `json:"text"` - Contact bool `json:"request_contact,omitempty"` - Location bool `json:"request_location,omitempty"` - Poll PollType `json:"request_poll,omitempty"` - WebApp *WebApp `json:"web_app,omitempty"` + Contact bool `json:"request_contact,omitempty"` + Location bool `json:"request_location,omitempty"` + Poll PollType `json:"request_poll,omitempty"` + User *ReplyRecipient `json:"request_user,omitempty"` + Chat *ReplyRecipient `json:"request_chat,omitempty"` + WebApp *WebApp `json:"web_app,omitempty"` } // MarshalJSON implements json.Marshaler. It allows passing PollType as a @@ -222,6 +235,34 @@ func (pt PollType) MarshalJSON() ([]byte, error) { }) } +// ReplyRecipient combines both KeyboardButtonRequestUser +// and KeyboardButtonRequestChat objects. Use inside ReplyButton +// to request the user or chat sharing with respective settings. +// +// To pass the pointers to bool use a special tele.Flag function, +// that way you will be able to reflect the three-state bool (nil, false, true). +type ReplyRecipient struct { + ID int32 `json:"request_id"` + + Bot *bool `json:"user_is_bot,omitempty"` // user only, optional + Premium *bool `json:"user_is_premium,omitempty"` // user only, optional + + Channel bool `json:"chat_is_channel,omitempty"` // chat only, required + Forum *bool `json:"chat_is_forum,omitempty"` // chat only, optional + WithUsername *bool `json:"chat_has_username,omitempty"` // chat only, optional + Created *bool `json:"chat_is_created,omitempty"` // chat only, optional + UserRights *Rights `json:"user_administrator_rights,omitempty"` // chat only, optional + BotRights *Rights `json:"bot_administrator_rights,omitempty"` // chat only, optional + BotMember *bool `json:"bot_is_member,omitempty"` // chat only, optional +} + +// RecipientShared combines both UserShared and ChatShared objects. +type RecipientShared struct { + ID int32 `json:"request_id"` + UserID int64 `json:"user_id"` + ChatID int64 `json:"chat_id"` +} + // InlineButton represents a button displayed in the message. type InlineButton struct { // Unique slagish name for this kind of button, @@ -279,6 +320,8 @@ func (b Btn) Reply() *ReplyButton { Contact: b.Contact, Location: b.Location, Poll: b.Poll, + User: b.User, + Chat: b.Chat, WebApp: b.WebApp, } } diff --git a/vendor/gopkg.in/telebot.v3/media.go b/vendor/gopkg.in/telebot.v3/media.go index 7dcabce9dc8..d161aa58a0b 100644 --- a/vendor/gopkg.in/telebot.v3/media.go +++ b/vendor/gopkg.in/telebot.v3/media.go @@ -29,6 +29,7 @@ type InputMedia struct { Performer string `json:"performer,omitempty"` Streaming bool `json:"supports_streaming,omitempty"` DisableTypeDetection bool `json:"disable_content_type_detection,omitempty"` + HasSpoiler bool `json:"is_spoiler,omitempty"` } // Inputtable is a generic type for all kinds of media you @@ -279,15 +280,17 @@ func (v *VideoNote) MediaFile() *File { // Sticker object represents a WebP image, so-called sticker. type Sticker struct { File - Width int `json:"width"` - Height int `json:"height"` - Animated bool `json:"is_animated"` - Video bool `json:"is_video"` - Thumbnail *Photo `json:"thumb"` - Emoji string `json:"emoji"` - SetName string `json:"set_name"` - MaskPosition *MaskPosition `json:"mask_position"` - PremiumAnimation *File `json:"premium_animation"` + Width int `json:"width"` + Height int `json:"height"` + Animated bool `json:"is_animated"` + Video bool `json:"is_video"` + Thumbnail *Photo `json:"thumb"` + Emoji string `json:"emoji"` + SetName string `json:"set_name"` + MaskPosition *MaskPosition `json:"mask_position"` + PremiumAnimation *File `json:"premium_animation"` + Type StickerSetType `json:"type"` + CustomEmoji string `json:"custom_emoji_id"` } func (s *Sticker) MediaType() string { diff --git a/vendor/gopkg.in/telebot.v3/message.go b/vendor/gopkg.in/telebot.v3/message.go index 47c072194eb..82997569a39 100644 --- a/vendor/gopkg.in/telebot.v3/message.go +++ b/vendor/gopkg.in/telebot.v3/message.go @@ -10,6 +10,9 @@ import ( type Message struct { ID int `json:"message_id"` + // (Optional) Unique identifier of a message thread to which the message belongs; for supergroups only + ThreadID int `json:"message_thread_id"` + // For message sent to channels, Sender will be nil Sender *User `json:"from"` @@ -59,6 +62,9 @@ type Message struct { // (Optional) Time of last edit in Unix. LastEdit int64 `json:"edit_date"` + // (Optional) True, if the message is sent to a forum topic. + TopicMessage bool `json:"is_topic_message"` + // (Optional) Message can't be forwarded. Protected bool `json:"has_protected_content,omitempty"` @@ -223,6 +229,12 @@ type Message struct { // Message is a service message about a successful payment. Payment *Payment `json:"successful_payment"` + // For a service message, a user was shared with the bot. + UserShared *RecipientShared `json:"user_shared,omitempty"` + + // For a service message, a chat was shared with the bot. + ChatShared *RecipientShared `json:"chat_shared,omitempty"` + // The domain name of the website on which the user has logged in. ConnectedWebsite string `json:"connected_website,omitempty"` @@ -250,6 +262,30 @@ type Message struct { // Inline keyboard attached to the message. ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` + + // Service message: forum topic created + TopicCreated *Topic `json:"forum_topic_created,omitempty"` + + // Service message: forum topic closed + TopicClosed *struct{} `json:"forum_topic_closed,omitempty"` + + // Service message: forum topic reopened + TopicReopened *Topic `json:"forum_topic_reopened,omitempty"` + + // Service message: forum topic deleted + TopicEdited *Topic `json:"forum_topic_edited,omitempty"` + + // Service message: general forum topic hidden + GeneralTopicHidden *struct{} `json:"general_topic_hidden,omitempty"` + + // Service message: general forum topic unhidden + GeneralTopicUnhidden *struct{} `json:"general_topic_unhidden,omitempty"` + + // Service message: represents spoiler information about the message. + HasMediaSpoiler bool `json:"has_media_spoiler,omitempty"` + + // Service message: the user allowed the bot added to the attachment menu to write messages + WriteAccessAllowed *WriteAccessAllowed `json:"write_access_allowed,omitempty"` } // MessageEntity object represents "special" parts of text messages, @@ -365,7 +401,6 @@ func (m *Message) FromChannel() bool { // Service messages are automatically sent messages, which // typically occur on some global action. For instance, when // anyone leaves the chat or chat title changes. -// func (m *Message) IsService() bool { fact := false @@ -386,7 +421,6 @@ func (m *Message) IsService() bool { // // It's safer than manually slicing Text because Telegram uses // UTF-16 indices whereas Go string are []byte. -// func (m *Message) EntityText(e MessageEntity) string { text := m.Text if text == "" { diff --git a/vendor/gopkg.in/telebot.v3/middleware.go b/vendor/gopkg.in/telebot.v3/middleware.go index aa21ca2e061..a8e2912a5a9 100644 --- a/vendor/gopkg.in/telebot.v3/middleware.go +++ b/vendor/gopkg.in/telebot.v3/middleware.go @@ -4,6 +4,15 @@ package telebot // which get called before the endpoint group or specific handler. type MiddlewareFunc func(HandlerFunc) HandlerFunc +func appendMiddleware(a, b []MiddlewareFunc) []MiddlewareFunc { + if len(a) == 0 { + return b + } + + m := make([]MiddlewareFunc, 0, len(a)+len(b)) + return append(m, append(a, b...)...) +} + func applyMiddleware(h HandlerFunc, m ...MiddlewareFunc) HandlerFunc { for i := len(m) - 1; i >= 0; i-- { h = m[i](h) @@ -25,5 +34,5 @@ func (g *Group) Use(middleware ...MiddlewareFunc) { // Handle adds endpoint handler to the bot, combining group's middleware // with the optional given middleware. func (g *Group) Handle(endpoint interface{}, h HandlerFunc, m ...MiddlewareFunc) { - g.b.Handle(endpoint, h, append(g.middleware, m...)...) + g.b.Handle(endpoint, h, appendMiddleware(g.middleware, m)...) } diff --git a/vendor/gopkg.in/telebot.v3/options.go b/vendor/gopkg.in/telebot.v3/options.go index 2aa28055ec8..56e0d9c295c 100644 --- a/vendor/gopkg.in/telebot.v3/options.go +++ b/vendor/gopkg.in/telebot.v3/options.go @@ -11,7 +11,6 @@ import ( // flags instead. // // Supported options are defined as iota-constants. -// type Option int const ( @@ -54,7 +53,6 @@ func Placeholder(text string) *SendOptions { // Despite its power, SendOptions is rather inconvenient to use all // the way through bot logic, so you might want to consider storing // and re-using it somewhere or be using Option flags instead. -// type SendOptions struct { // If the message is a reply, original message. ReplyTo *Message @@ -77,8 +75,15 @@ type SendOptions struct { // AllowWithoutReply allows sending messages not a as reply if the replied-to message has already been deleted. AllowWithoutReply bool - // Protected protects the contents of the sent message from forwarding and saving + // Protected protects the contents of sent message from forwarding and saving. Protected bool + + // ThreadID supports sending messages to a thread. + ThreadID int + + // HasSpoiler marks the message as containing a spoiler. + HasSpoiler bool + } func (og *SendOptions) copy() *SendOptions { @@ -189,6 +194,14 @@ func (b *Bot) embedSendOptions(params map[string]string, opt *SendOptions) { if opt.Protected { params["protect_content"] = "true" } + + if opt.ThreadID != 0 { + params["message_thread_id"] = strconv.Itoa(opt.ThreadID) + } + + if opt.HasSpoiler { + params["spoiler"] = "true" + } } func processButtons(keys [][]InlineButton) { diff --git a/vendor/gopkg.in/telebot.v3/sendable.go b/vendor/gopkg.in/telebot.v3/sendable.go index 2e782e33a7a..ecaae7fed1c 100644 --- a/vendor/gopkg.in/telebot.v3/sendable.go +++ b/vendor/gopkg.in/telebot.v3/sendable.go @@ -18,7 +18,6 @@ type Recipient interface { // This is pretty cool, since it lets bots implement // custom Sendables for complex kind of media or // chat objects spanning across multiple messages. -// type Sendable interface { Send(*Bot, Recipient, *SendOptions) (*Message, error) } diff --git a/vendor/gopkg.in/telebot.v3/telebot.go b/vendor/gopkg.in/telebot.v3/telebot.go index 2aa1cd9a811..b8271e9a4d4 100644 --- a/vendor/gopkg.in/telebot.v3/telebot.go +++ b/vendor/gopkg.in/telebot.v3/telebot.go @@ -2,29 +2,28 @@ // // Example: // -// package main +// package main // -// import ( -// "time" -// tele "gopkg.in/telebot.v3" -// ) +// import ( +// "time" +// tele "gopkg.in/telebot.v3" +// ) // -// func main() { -// b, err := tele.NewBot(tele.Settings{ -// Token: "...", -// Poller: &tele.LongPoller{Timeout: 10 * time.Second}, -// }) -// if err != nil { -// return -// } -// -// b.Handle("/start", func(c tele.Context) error { -// return c.Send("Hello world!") -// }) -// -// b.Start() +// func main() { +// b, err := tele.NewBot(tele.Settings{ +// Token: "...", +// Poller: &tele.LongPoller{Timeout: 10 * time.Second}, +// }) +// if err != nil { +// return // } // +// b.Handle("/start", func(c tele.Context) error { +// return c.Send("Hello world!") +// }) +// +// b.Start() +// } package telebot import "errors" @@ -43,35 +42,43 @@ const DefaultApiURL = "https://api.telegram.org" // // For convenience, all Telebot-provided endpoints start with // an "alert" character \a. -// const ( // Basic message handlers. - OnText = "\atext" - OnEdited = "\aedited" - OnPhoto = "\aphoto" - OnAudio = "\aaudio" - OnAnimation = "\aanimation" - OnDocument = "\adocument" - OnSticker = "\asticker" - OnVideo = "\avideo" - OnVoice = "\avoice" - OnVideoNote = "\avideo_note" - OnContact = "\acontact" - OnLocation = "\alocation" - OnVenue = "\avenue" - OnDice = "\adice" - OnInvoice = "\ainvoice" - OnPayment = "\apayment" - OnGame = "\agame" - OnPoll = "\apoll" - OnPollAnswer = "\apoll_answer" - OnPinned = "\apinned" - OnChannelPost = "\achannel_post" - OnEditedChannelPost = "\aedited_channel_post" + OnText = "\atext" + OnEdited = "\aedited" + OnPhoto = "\aphoto" + OnAudio = "\aaudio" + OnAnimation = "\aanimation" + OnDocument = "\adocument" + OnSticker = "\asticker" + OnVideo = "\avideo" + OnVoice = "\avoice" + OnVideoNote = "\avideo_note" + OnContact = "\acontact" + OnLocation = "\alocation" + OnVenue = "\avenue" + OnDice = "\adice" + OnInvoice = "\ainvoice" + OnPayment = "\apayment" + OnGame = "\agame" + OnPoll = "\apoll" + OnPollAnswer = "\apoll_answer" + OnPinned = "\apinned" + OnChannelPost = "\achannel_post" + OnEditedChannelPost = "\aedited_channel_post" + OnTopicCreated = "\atopic_created" + OnTopicReopened = "\atopic_reopened" + OnTopicClosed = "\atopic_closed" + OnTopicEdited = "\atopic_edited" + OnGeneralTopicHidden = "\ageneral_topic_hidden" + OnGeneralTopicUnhidden = "\ageneral_topic_unhidden" + OnWriteAccessAllowed = "\awrite_access_allowed" OnAddedToGroup = "\aadded_to_group" OnUserJoined = "\auser_joined" OnUserLeft = "\auser_left" + OnUserShared = "\auser_shared" + OnChatShared = "\achat_shared" OnNewGroupTitle = "\anew_chat_title" OnNewGroupPhoto = "\anew_chat_photo" OnGroupPhotoDeleted = "\achat_photo_deleted" @@ -131,6 +138,13 @@ const ( ModeHTML ParseMode = "HTML" ) -// M is a shortcut for map[string]interface{}. Use it for passing -// arguments to the layout functions. +// M is a shortcut for map[string]interface{}. +// Useful for passing arguments to the layout functions. type M = map[string]interface{} + +// Flag returns a pointer to the given bool. +// Useful for passing the three-state flags to a Bot API. +// For example, see ReplyRecipient type. +func Flag(b bool) *bool { + return &b +} diff --git a/vendor/gopkg.in/telebot.v3/topic.go b/vendor/gopkg.in/telebot.v3/topic.go new file mode 100644 index 00000000000..d81fe9a9436 --- /dev/null +++ b/vendor/gopkg.in/telebot.v3/topic.go @@ -0,0 +1,172 @@ +package telebot + +import ( + "encoding/json" + "strconv" +) + +type Topic struct { + Name string `json:"name"` + IconColor int `json:"icon_color"` + IconCustomEmojiID string `json:"icon_custom_emoji_id"` + ThreadID int `json:"message_thread_id"` +} + +// CreateTopic creates a topic in a forum supergroup chat. +func (b *Bot) CreateTopic(chat *Chat, topic *Topic) (*Topic, error) { + params := map[string]string{ + "chat_id": chat.Recipient(), + "name": topic.Name, + } + + if topic.IconColor != 0 { + params["icon_color"] = strconv.Itoa(topic.IconColor) + } + if topic.IconCustomEmojiID != "" { + params["icon_custom_emoji_id"] = topic.IconCustomEmojiID + } + + data, err := b.Raw("createForumTopic", params) + if err != nil { + return nil, err + } + + var resp struct { + Result *Topic + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, err +} + +// EditTopic edits name and icon of a topic in a forum supergroup chat. +func (b *Bot) EditTopic(chat *Chat, topic *Topic) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + "message_thread_id": topic.ThreadID, + } + + if topic.Name != "" { + params["name"] = topic.Name + } + if topic.IconCustomEmojiID != "" { + params["icon_custom_emoji_id"] = topic.IconCustomEmojiID + } + + _, err := b.Raw("editForumTopic", params) + return err +} + +// CloseTopic closes an open topic in a forum supergroup chat. +func (b *Bot) CloseTopic(chat *Chat, topic *Topic) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + "message_thread_id": topic.ThreadID, + } + + _, err := b.Raw("closeForumTopic", params) + return err +} + +// ReopenTopic reopens a closed topic in a forum supergroup chat. +func (b *Bot) ReopenTopic(chat *Chat, topic *Topic) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + "message_thread_id": topic.ThreadID, + } + + _, err := b.Raw("reopenForumTopic", params) + return err +} + +// DeleteTopic deletes a forum topic along with all its messages in a forum supergroup chat. +func (b *Bot) DeleteTopic(chat *Chat, topic *Topic) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + "message_thread_id": topic.ThreadID, + } + + _, err := b.Raw("deleteForumTopic", params) + return err +} + +// UnpinAllTopicMessages clears the list of pinned messages in a forum topic. The bot must be an administrator in the chat for this to work and must have the can_pin_messages administrator right in the supergroup. +func (b *Bot) UnpinAllTopicMessages(chat *Chat, topic *Topic) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + "message_thread_id": topic.ThreadID, + } + + _, err := b.Raw("unpinAllForumTopicMessages", params) + return err +} + +// TopicIconStickers gets custom emoji stickers, which can be used as a forum topic icon by any user. +func (b *Bot) TopicIconStickers() ([]Sticker, error) { + params := map[string]string{} + + data, err := b.Raw("getForumTopicIconStickers", params) + if err != nil { + return nil, err + } + + var resp struct { + Result []Sticker + } + if err := json.Unmarshal(data, &resp); err != nil { + return nil, wrapError(err) + } + return resp.Result, nil +} + +// EditGeneralTopic edits name of the 'General' topic in a forum supergroup chat. +func (b *Bot) EditGeneralTopic(chat *Chat, topic *Topic) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + "name": topic.Name, + } + + _, err := b.Raw("editGeneralForumTopic", params) + return err +} + +// CloseGeneralTopic closes an open 'General' topic in a forum supergroup chat. +func (b *Bot) CloseGeneralTopic(chat *Chat) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + } + + _, err := b.Raw("closeGeneralForumTopic", params) + return err +} + +// ReopenGeneralTopic reopens a closed 'General' topic in a forum supergroup chat. +func (b *Bot) ReopenGeneralTopic(chat *Chat) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + } + + _, err := b.Raw("reopenGeneralForumTopic", params) + return err +} + +// HideGeneralTopic hides the 'General' topic in a forum supergroup chat. +func (b *Bot) HideGeneralTopic(chat *Chat) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + } + + _, err := b.Raw("hideGeneralForumTopic", params) + return err +} + +// UnhideGeneralTopic unhides the 'General' topic in a forum supergroup chat. +func (b *Bot) UnhideGeneralTopic(chat *Chat) error { + params := map[string]interface{}{ + "chat_id": chat.Recipient(), + } + + _, err := b.Raw("unhideGeneralForumTopic", params) + return err +} diff --git a/vendor/gopkg.in/telebot.v3/update.go b/vendor/gopkg.in/telebot.v3/update.go index 76da7cd8f70..12a065ab229 100644 --- a/vendor/gopkg.in/telebot.v3/update.go +++ b/vendor/gopkg.in/telebot.v3/update.go @@ -99,6 +99,35 @@ func (b *Bot) ProcessUpdate(u Update) { return } + if m.TopicCreated != nil { + b.handle(OnTopicCreated, c) + return + } + if m.TopicReopened != nil { + b.handle(OnTopicReopened, c) + return + } + if m.TopicClosed != nil { + b.handle(OnTopicClosed, c) + return + } + if m.TopicEdited != nil { + b.handle(OnTopicEdited, c) + return + } + if m.GeneralTopicHidden != nil { + b.handle(OnGeneralTopicHidden, c) + return + } + if m.GeneralTopicUnhidden != nil { + b.handle(OnGeneralTopicUnhidden, c) + return + } + if m.WriteAccessAllowed != nil { + b.handle(OnWriteAccessAllowed, c) + return + } + wasAdded := (m.UserJoined != nil && m.UserJoined.ID == b.Me.ID) || (m.UsersJoined != nil && isUserInList(b.Me, m.UsersJoined)) if m.GroupCreated || m.SuperGroupCreated || wasAdded { @@ -110,7 +139,6 @@ func (b *Bot) ProcessUpdate(u Update) { b.handle(OnUserJoined, c) return } - if m.UsersJoined != nil { for _, user := range m.UsersJoined { m.UserJoined = &user @@ -118,22 +146,28 @@ func (b *Bot) ProcessUpdate(u Update) { } return } - if m.UserLeft != nil { b.handle(OnUserLeft, c) return } + if m.UserShared != nil { + b.handle(OnUserShared, c) + return + } + if m.ChatShared != nil { + b.handle(OnChatShared, c) + return + } + if m.NewGroupTitle != "" { b.handle(OnNewGroupTitle, c) return } - if m.NewGroupPhoto != nil { b.handle(OnNewGroupPhoto, c) return } - if m.GroupPhotoDeleted { b.handle(OnGroupPhotoDeleted, c) return @@ -143,12 +177,10 @@ func (b *Bot) ProcessUpdate(u Update) { b.handle(OnGroupCreated, c) return } - if m.SuperGroupCreated { b.handle(OnSuperGroupCreated, c) return } - if m.ChannelCreated { b.handle(OnChannelCreated, c) return @@ -164,17 +196,14 @@ func (b *Bot) ProcessUpdate(u Update) { b.handle(OnVideoChatStarted, c) return } - if m.VideoChatEnded != nil { b.handle(OnVideoChatEnded, c) return } - if m.VideoChatParticipants != nil { b.handle(OnVideoChatParticipants, c) return } - if m.VideoChatScheduled != nil { b.handle(OnVideoChatScheduled, c) return @@ -182,13 +211,13 @@ func (b *Bot) ProcessUpdate(u Update) { if m.WebAppData != nil { b.handle(OnWebApp, c) + return } if m.ProximityAlert != nil { b.handle(OnProximityAlert, c) return } - if m.AutoDeleteTimer != nil { b.handle(OnAutoDeleteTimer, c) return diff --git a/vendor/gopkg.in/telebot.v3/video_chat.go b/vendor/gopkg.in/telebot.v3/video_chat.go index 448db4161e0..4952e36b4e1 100644 --- a/vendor/gopkg.in/telebot.v3/video_chat.go +++ b/vendor/gopkg.in/telebot.v3/video_chat.go @@ -2,26 +2,28 @@ package telebot import "time" -// VideoChatStarted represents a service message about a video chat -// started in the chat. -type VideoChatStarted struct{} +type ( + // VideoChatStarted represents a service message about a video chat + // started in the chat. + VideoChatStarted struct{} -// VideoChatEnded represents a service message about a video chat -// ended in the chat. -type VideoChatEnded struct { - Duration int `json:"duration"` // in seconds -} + // VideoChatEnded represents a service message about a video chat + // ended in the chat. + VideoChatEnded struct { + Duration int `json:"duration"` // in seconds + } -// VideoChatParticipants represents a service message about new -// members invited to a video chat -type VideoChatParticipants struct { - Users []User `json:"users"` -} + // VideoChatParticipants represents a service message about new + // members invited to a video chat + VideoChatParticipants struct { + Users []User `json:"users"` + } -// VideoChatScheduled represents a service message about a video chat scheduled in the chat. -type VideoChatScheduled struct { - Unixtime int64 `json:"start_date"` -} + // VideoChatScheduled represents a service message about a video chat scheduled in the chat. + VideoChatScheduled struct { + Unixtime int64 `json:"start_date"` + } +) // StartsAt returns the point when the video chat is supposed to be started by a chat administrator. func (v *VideoChatScheduled) StartsAt() time.Time { diff --git a/vendor/gopkg.in/telebot.v3/web_app.go b/vendor/gopkg.in/telebot.v3/web_app.go index 841378416a0..e5c9070f2c4 100644 --- a/vendor/gopkg.in/telebot.v3/web_app.go +++ b/vendor/gopkg.in/telebot.v3/web_app.go @@ -16,3 +16,9 @@ type WebAppData struct { Data string `json:"data"` Text string `json:"button_text"` } + +// WebAppAccessAllowed represents a service message about a user allowing +// a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link. +type WriteAccessAllowed struct { + WebAppName string `json:"web_app_name,omitempty"` +} diff --git a/vendor/gopkg.in/telebot.v3/webhook.go b/vendor/gopkg.in/telebot.v3/webhook.go index 13e99bcdfd6..5a48d6c3b36 100644 --- a/vendor/gopkg.in/telebot.v3/webhook.go +++ b/vendor/gopkg.in/telebot.v3/webhook.go @@ -119,7 +119,7 @@ func (h *Webhook) getParams() map[string]string { func (h *Webhook) Poll(b *Bot, dest chan Update, stop chan struct{}) { if err := b.SetWebhook(h); err != nil { - b.debug(err) + b.OnError(err, nil) close(stop) return } diff --git a/vendor/modules.txt b/vendor/modules.txt index a6f4ea35b6d..5f61572c92b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -639,8 +639,8 @@ github.com/hashicorp/go-secure-stdlib/parseutil # github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 ## explicit; go 1.16 github.com/hashicorp/go-secure-stdlib/strutil -# github.com/hashicorp/go-sockaddr v1.0.2 -## explicit +# github.com/hashicorp/go-sockaddr v1.0.6 +## explicit; go 1.19 github.com/hashicorp/go-sockaddr # github.com/hashicorp/go-version v1.6.0 ## explicit @@ -822,11 +822,10 @@ 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.1-0.20231117200754-ca5089d33eab +# github.com/prometheus/alertmanager v0.26.1-0.20240119104350-f92a08d07386 ## explicit; go 1.21 github.com/prometheus/alertmanager/api github.com/prometheus/alertmanager/api/metrics -github.com/prometheus/alertmanager/api/v1 github.com/prometheus/alertmanager/api/v2 github.com/prometheus/alertmanager/api/v2/models github.com/prometheus/alertmanager/api/v2/restapi @@ -843,6 +842,8 @@ github.com/prometheus/alertmanager/config 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/matchers/parse github.com/prometheus/alertmanager/nflog github.com/prometheus/alertmanager/nflog/nflogpb github.com/prometheus/alertmanager/notify @@ -896,7 +897,7 @@ github.com/prometheus/common/version # github.com/prometheus/common/sigv4 v0.1.0 ## explicit; go 1.15 github.com/prometheus/common/sigv4 -# github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97 +# github.com/prometheus/exporter-toolkit v0.11.0 ## explicit; go 1.18 github.com/prometheus/exporter-toolkit/web # github.com/prometheus/procfs v0.12.0 @@ -1442,7 +1443,7 @@ google.golang.org/protobuf/types/known/timestamppb # gopkg.in/ini.v1 v1.67.0 ## explicit gopkg.in/ini.v1 -# gopkg.in/telebot.v3 v3.1.3 +# gopkg.in/telebot.v3 v3.2.1 ## explicit; go 1.13 gopkg.in/telebot.v3 # gopkg.in/yaml.v2 v2.4.0