2015-10-11 15:24:49 +00:00
|
|
|
// 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.
|
|
|
|
|
api: Implement OpenAPI generated Alertmanager API V2
The current Alertmanager API v1 is undocumented and written by hand.
This patch introduces a new Alertmanager API - v2. The API is fully
generated via an OpenAPI 2.0 [1] specification (see
`api/v2/openapi.yaml`) with the exception of the http handlers itself.
Pros:
- Generated server code
- Ability to generate clients in all major languages
(Go, Java, JS, Python, Ruby, Haskell, *elm* [3] ...)
- Strict contract (OpenAPI spec) between server and clients.
- Instant feedback on frontend-breaking changes, due to strictly
typed frontend language elm.
- Generated documentation (See Alertmanager online Swagger UI [4])
Cons:
- Dependency on open api ecosystem including go-swagger [2]
In addition this patch includes the following changes.
- README.md: Add API section
- test: Duplicate acceptance test to API v1 & API v2 version
The Alertmanager acceptance test framework has a decent test coverage
on the Alertmanager API. Introducing the Alertmanager API v2 does not go
hand in hand with deprecating API v1. They should live alongside each
other for a couple of minor Alertmanager versions.
Instead of porting the acceptance test framework to use the new API v2,
this patch duplicates the acceptance tests, one using the API v1, the
other API v2.
Once API v1 is removed we can simply remove `test/with_api_v1` and bring
`test/with_api_v2` to `test/`.
[1]
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
[2] https://github.com/go-swagger/go-swagger/
[3] https://github.com/ahultgren/swagger-elm
[4]
http://petstore.swagger.io/?url=https://raw.githubusercontent.com/mxinden/alertmanager/apiv2/api/v2/openapi.yaml
Signed-off-by: Max Leonard Inden <IndenML@gmail.com>
2018-04-26 06:12:49 +00:00
|
|
|
package v1
|
2015-07-01 11:17:08 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2018-01-21 14:29:51 +00:00
|
|
|
"errors"
|
2015-07-01 11:17:08 +00:00
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2017-06-26 16:20:26 +00:00
|
|
|
"regexp"
|
2017-09-02 09:35:17 +00:00
|
|
|
"sort"
|
2015-11-02 18:41:23 +00:00
|
|
|
"sync"
|
2015-09-26 09:12:59 +00:00
|
|
|
"time"
|
2015-07-01 11:17:08 +00:00
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
"github.com/go-kit/kit/log"
|
|
|
|
"github.com/go-kit/kit/log/level"
|
2016-01-09 12:16:00 +00:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
2015-10-16 12:02:22 +00:00
|
|
|
"github.com/prometheus/common/model"
|
2015-07-01 11:17:08 +00:00
|
|
|
"github.com/prometheus/common/route"
|
2016-05-15 10:01:12 +00:00
|
|
|
"github.com/prometheus/common/version"
|
2015-09-26 09:12:59 +00:00
|
|
|
|
2019-05-24 12:42:52 +00:00
|
|
|
"github.com/prometheus/alertmanager/api/metrics"
|
2018-02-07 15:36:47 +00:00
|
|
|
"github.com/prometheus/alertmanager/cluster"
|
2016-11-22 14:05:14 +00:00
|
|
|
"github.com/prometheus/alertmanager/config"
|
2016-08-09 09:04:01 +00:00
|
|
|
"github.com/prometheus/alertmanager/dispatch"
|
2019-09-16 08:56:29 +00:00
|
|
|
"github.com/prometheus/alertmanager/pkg/labels"
|
2015-09-26 09:12:59 +00:00
|
|
|
"github.com/prometheus/alertmanager/provider"
|
2016-08-30 09:58:27 +00:00
|
|
|
"github.com/prometheus/alertmanager/silence"
|
|
|
|
"github.com/prometheus/alertmanager/silence/silencepb"
|
2015-09-26 09:12:59 +00:00
|
|
|
"github.com/prometheus/alertmanager/types"
|
2015-07-01 11:17:08 +00:00
|
|
|
)
|
|
|
|
|
2016-09-04 15:43:25 +00:00
|
|
|
var corsHeaders = map[string]string{
|
|
|
|
"Access-Control-Allow-Headers": "Accept, Authorization, Content-Type, Origin",
|
2019-03-14 11:14:23 +00:00
|
|
|
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
|
2016-09-04 15:43:25 +00:00
|
|
|
"Access-Control-Allow-Origin": "*",
|
|
|
|
"Access-Control-Expose-Headers": "Date",
|
2018-08-06 16:51:54 +00:00
|
|
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
2016-09-04 15:43:25 +00:00
|
|
|
}
|
|
|
|
|
2018-08-23 14:03:49 +00:00
|
|
|
// 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"`
|
|
|
|
}
|
|
|
|
|
2016-09-04 15:43:25 +00:00
|
|
|
// Enables cross-site script calls.
|
|
|
|
func setCORS(w http.ResponseWriter) {
|
|
|
|
for h, v := range corsHeaders {
|
|
|
|
w.Header().Set(h, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-05 09:49:32 +00:00
|
|
|
// API provides registration of handlers for API routes.
|
2015-07-01 11:17:08 +00:00
|
|
|
type API struct {
|
2019-02-06 18:06:31 +00:00
|
|
|
alerts provider.Alerts
|
|
|
|
silences *silence.Silences
|
|
|
|
config *config.Config
|
|
|
|
route *dispatch.Route
|
|
|
|
uptime time.Time
|
2021-02-19 19:02:06 +00:00
|
|
|
peer cluster.ClusterPeer
|
2019-02-06 18:06:31 +00:00
|
|
|
logger log.Logger
|
2019-05-24 12:42:52 +00:00
|
|
|
m *metrics.Alerts
|
Various improvements after code review
Most importantly, `api.New` now takes an `Options` struct as an
argument, which allows some other things done here as well:
- Timout and concurrency limit are now in the options, streamlining
the registration and the implementation of the limiting middleware.
- A local registry is used for metrics, and the metrics used so far
inside any of the api packages are using it now.
The 'in flight' metric now contains the 'get' as a method label. I
have also added a TODO to instrument other methods in the same way
(otherwise, the label doesn't reall make sense, semantically). I have
also added an explicit error counter for requests rejected because of
the concurrency limit. (They also show up as 503s in the generic HTTP
instrumentation (or they would, if v2 were instrumented, too), but
those 503s might have a number of reasons, while users might want to
alert on concurrency limit problems explicitly).
Signed-off-by: beorn7 <beorn@soundcloud.com>
2019-02-12 16:42:11 +00:00
|
|
|
|
2017-04-28 22:46:52 +00:00
|
|
|
getAlertStatus getAlertStatusFn
|
2015-10-21 14:34:56 +00:00
|
|
|
|
2017-04-26 00:36:36 +00:00
|
|
|
mtx sync.RWMutex
|
2015-07-01 11:17:08 +00:00
|
|
|
}
|
|
|
|
|
2017-04-28 22:46:52 +00:00
|
|
|
type getAlertStatusFn func(model.Fingerprint) types.AlertStatus
|
2017-03-16 10:16:10 +00:00
|
|
|
|
2016-08-09 09:04:01 +00:00
|
|
|
// New returns a new API.
|
2018-02-07 15:36:47 +00:00
|
|
|
func New(
|
|
|
|
alerts provider.Alerts,
|
|
|
|
silences *silence.Silences,
|
|
|
|
sf getAlertStatusFn,
|
2021-02-19 19:02:06 +00:00
|
|
|
peer cluster.ClusterPeer,
|
2018-02-07 15:36:47 +00:00
|
|
|
l log.Logger,
|
Various improvements after code review
Most importantly, `api.New` now takes an `Options` struct as an
argument, which allows some other things done here as well:
- Timout and concurrency limit are now in the options, streamlining
the registration and the implementation of the limiting middleware.
- A local registry is used for metrics, and the metrics used so far
inside any of the api packages are using it now.
The 'in flight' metric now contains the 'get' as a method label. I
have also added a TODO to instrument other methods in the same way
(otherwise, the label doesn't reall make sense, semantically). I have
also added an explicit error counter for requests rejected because of
the concurrency limit. (They also show up as 503s in the generic HTTP
instrumentation (or they would, if v2 were instrumented, too), but
those 503s might have a number of reasons, while users might want to
alert on concurrency limit problems explicitly).
Signed-off-by: beorn7 <beorn@soundcloud.com>
2019-02-12 16:42:11 +00:00
|
|
|
r prometheus.Registerer,
|
2018-02-07 15:36:47 +00:00
|
|
|
) *API {
|
2018-02-13 15:26:34 +00:00
|
|
|
if l == nil {
|
|
|
|
l = log.NewNopLogger()
|
|
|
|
}
|
|
|
|
|
2015-11-02 18:41:23 +00:00
|
|
|
return &API{
|
2019-05-24 12:42:52 +00:00
|
|
|
alerts: alerts,
|
|
|
|
silences: silences,
|
|
|
|
getAlertStatus: sf,
|
|
|
|
uptime: time.Now(),
|
|
|
|
peer: peer,
|
|
|
|
logger: l,
|
|
|
|
m: metrics.NewAlerts("v1", r),
|
2015-07-01 11:17:08 +00:00
|
|
|
}
|
2015-11-02 18:41:23 +00:00
|
|
|
}
|
2015-07-01 16:36:37 +00:00
|
|
|
|
2016-02-14 14:40:48 +00:00
|
|
|
// Register registers the API handlers under their correct routes
|
2015-11-05 09:49:32 +00:00
|
|
|
// in the given router.
|
2015-11-02 18:41:23 +00:00
|
|
|
func (api *API) Register(r *route.Router) {
|
2018-03-28 13:28:38 +00:00
|
|
|
wrap := func(f http.HandlerFunc) http.HandlerFunc {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2016-09-04 15:43:25 +00:00
|
|
|
setCORS(w)
|
|
|
|
f(w, r)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-03-28 13:28:38 +00:00
|
|
|
r.Options("/*path", wrap(func(w http.ResponseWriter, r *http.Request) {}))
|
2016-01-09 12:16:00 +00:00
|
|
|
|
2018-03-28 13:28:38 +00:00
|
|
|
r.Get("/status", wrap(api.status))
|
|
|
|
r.Get("/receivers", wrap(api.receivers))
|
2015-07-01 11:17:08 +00:00
|
|
|
|
2018-03-28 13:28:38 +00:00
|
|
|
r.Get("/alerts", wrap(api.listAlerts))
|
|
|
|
r.Post("/alerts", wrap(api.addAlerts))
|
2015-07-01 11:17:08 +00:00
|
|
|
|
2018-03-28 13:28:38 +00:00
|
|
|
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))
|
2015-11-02 18:41:23 +00:00
|
|
|
}
|
|
|
|
|
2015-11-05 09:49:32 +00:00
|
|
|
// Update sets the configuration string to a new value.
|
2019-02-06 18:06:31 +00:00
|
|
|
func (api *API) Update(cfg *config.Config) {
|
2015-11-02 18:41:23 +00:00
|
|
|
api.mtx.Lock()
|
|
|
|
defer api.mtx.Unlock()
|
2015-07-01 11:17:08 +00:00
|
|
|
|
2017-06-08 11:14:37 +00:00
|
|
|
api.config = cfg
|
2017-06-26 16:20:26 +00:00
|
|
|
api.route = dispatch.NewRoute(cfg.Route, nil)
|
2015-07-01 11:17:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type errorType string
|
|
|
|
|
|
|
|
const (
|
2018-02-28 16:42:32 +00:00
|
|
|
errorInternal errorType = "server_error"
|
|
|
|
errorBadData errorType = "bad_data"
|
2015-07-01 11:17:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type apiError struct {
|
|
|
|
typ errorType
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *apiError) Error() string {
|
|
|
|
return fmt.Sprintf("%s: %s", e.typ, e.err)
|
|
|
|
}
|
|
|
|
|
2017-06-26 16:20:26 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respond(w, receivers)
|
2017-06-26 16:20:26 +00:00
|
|
|
}
|
|
|
|
|
2015-11-02 18:41:23 +00:00
|
|
|
func (api *API) status(w http.ResponseWriter, req *http.Request) {
|
|
|
|
api.mtx.RLock()
|
|
|
|
|
|
|
|
var status = struct {
|
2018-02-09 10:16:00 +00:00
|
|
|
ConfigYAML string `json:"configYAML"`
|
|
|
|
ConfigJSON *config.Config `json:"configJSON"`
|
|
|
|
VersionInfo map[string]string `json:"versionInfo"`
|
|
|
|
Uptime time.Time `json:"uptime"`
|
|
|
|
ClusterStatus *clusterStatus `json:"clusterStatus"`
|
2015-11-02 18:41:23 +00:00
|
|
|
}{
|
2017-06-08 11:14:37 +00:00
|
|
|
ConfigYAML: api.config.String(),
|
|
|
|
ConfigJSON: api.config,
|
2016-05-15 10:01:12 +00:00
|
|
|
VersionInfo: map[string]string{
|
|
|
|
"version": version.Version,
|
|
|
|
"revision": version.Revision,
|
|
|
|
"branch": version.Branch,
|
|
|
|
"buildUser": version.BuildUser,
|
|
|
|
"buildDate": version.BuildDate,
|
|
|
|
"goVersion": version.GoVersion,
|
|
|
|
},
|
2018-02-09 10:16:00 +00:00
|
|
|
Uptime: api.uptime,
|
|
|
|
ClusterStatus: getClusterStatus(api.peer),
|
2015-11-02 18:41:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
api.mtx.RUnlock()
|
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respond(w, status)
|
2015-11-02 18:41:23 +00:00
|
|
|
}
|
|
|
|
|
2017-03-07 17:17:03 +00:00
|
|
|
type peerStatus struct {
|
2018-02-09 10:16:00 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Address string `json:"address"`
|
2017-03-07 17:17:03 +00:00
|
|
|
}
|
|
|
|
|
2018-02-09 10:16:00 +00:00
|
|
|
type clusterStatus struct {
|
2018-03-02 14:45:21 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
Peers []peerStatus `json:"peers"`
|
2017-12-22 10:39:27 +00:00
|
|
|
}
|
|
|
|
|
2021-02-19 19:02:06 +00:00
|
|
|
func getClusterStatus(p cluster.ClusterPeer) *clusterStatus {
|
2018-02-09 10:16:00 +00:00
|
|
|
if p == nil {
|
2017-07-22 10:30:19 +00:00
|
|
|
return nil
|
|
|
|
}
|
2018-03-02 14:45:21 +00:00
|
|
|
s := &clusterStatus{Name: p.Name(), Status: p.Status()}
|
2017-07-22 10:30:19 +00:00
|
|
|
|
2018-02-09 10:16:00 +00:00
|
|
|
for _, n := range p.Peers() {
|
|
|
|
s.Peers = append(s.Peers, peerStatus{
|
2021-02-24 15:35:16 +00:00
|
|
|
Name: n.Name(),
|
2018-02-09 10:16:00 +00:00
|
|
|
Address: n.Address(),
|
2018-02-07 15:36:47 +00:00
|
|
|
})
|
2017-12-22 10:39:27 +00:00
|
|
|
}
|
2018-02-09 10:16:00 +00:00
|
|
|
return s
|
2017-03-07 17:17:03 +00:00
|
|
|
}
|
|
|
|
|
2017-04-28 22:46:52 +00:00
|
|
|
func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
|
2015-10-15 10:47:15 +00:00
|
|
|
var (
|
2017-11-12 16:35:49 +00:00
|
|
|
err error
|
|
|
|
receiverFilter *regexp.Regexp
|
2017-05-02 09:23:56 +00:00
|
|
|
// Initialize result slice to prevent api returning `null` when there
|
|
|
|
// are no alerts present
|
2018-08-23 14:03:49 +00:00
|
|
|
res = []*Alert{}
|
2018-05-07 16:07:19 +00:00
|
|
|
matchers = []*labels.Matcher{}
|
2019-02-06 17:15:19 +00:00
|
|
|
ctx = r.Context()
|
2018-05-07 16:07:19 +00:00
|
|
|
|
|
|
|
showActive, showInhibited bool
|
|
|
|
showSilenced, showUnprocessed bool
|
2015-10-15 10:47:15 +00:00
|
|
|
)
|
2017-04-28 22:46:52 +00:00
|
|
|
|
2018-05-07 16:07:19 +00:00
|
|
|
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)
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2017-04-28 22:46:52 +00:00
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
2018-05-07 16:07:19 +00:00
|
|
|
return false, err
|
2017-04-28 22:46:52 +00:00
|
|
|
}
|
2018-05-07 16:07:19 +00:00
|
|
|
return true, nil
|
2017-04-28 22:46:52 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 16:07:19 +00:00
|
|
|
if filter := r.FormValue("filter"); filter != "" {
|
2019-09-16 08:56:29 +00:00
|
|
|
matchers, err = labels.ParseMatchers(filter)
|
2018-05-07 16:07:19 +00:00
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2017-05-17 08:14:42 +00:00
|
|
|
typ: errorBadData,
|
2018-05-07 16:07:19 +00:00
|
|
|
err: err,
|
2017-05-17 08:14:42 +00:00
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-07 16:07:19 +00:00
|
|
|
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
|
2017-10-17 08:49:59 +00:00
|
|
|
}
|
|
|
|
|
2017-06-26 16:20:26 +00:00
|
|
|
if receiverParam := r.FormValue("receiver"); receiverParam != "" {
|
2017-11-12 16:35:49 +00:00
|
|
|
receiverFilter, err = regexp.Compile("^(?:" + receiverParam + ")$")
|
2017-06-26 16:20:26 +00:00
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2017-06-26 16:20:26 +00:00
|
|
|
typ: errorBadData,
|
|
|
|
err: fmt.Errorf(
|
|
|
|
"failed to parse receiver param: %s",
|
|
|
|
receiverParam,
|
|
|
|
),
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-28 22:46:52 +00:00
|
|
|
alerts := api.alerts.GetPending()
|
|
|
|
defer alerts.Close()
|
|
|
|
|
2017-12-21 11:08:39 +00:00
|
|
|
api.mtx.RLock()
|
2015-10-15 10:47:15 +00:00
|
|
|
for a := range alerts.Next() {
|
|
|
|
if err = alerts.Err(); err != nil {
|
|
|
|
break
|
|
|
|
}
|
2019-02-06 17:15:19 +00:00
|
|
|
if err = ctx.Err(); err != nil {
|
|
|
|
break
|
|
|
|
}
|
2017-04-28 22:46:52 +00:00
|
|
|
|
2017-06-26 16:20:26 +00:00
|
|
|
routes := api.route.Match(a.Labels)
|
|
|
|
receivers := make([]string, 0, len(routes))
|
|
|
|
for _, r := range routes {
|
|
|
|
receivers = append(receivers, r.RouteOpts.Receiver)
|
|
|
|
}
|
|
|
|
|
2017-11-12 16:35:49 +00:00
|
|
|
if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) {
|
2017-06-26 16:20:26 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-04-28 22:46:52 +00:00
|
|
|
if !alertMatchesFilterLabels(&a.Alert, matchers) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-05-07 16:07:19 +00:00
|
|
|
// Continue if the alert is resolved.
|
2017-05-29 12:07:05 +00:00
|
|
|
if !a.Alert.EndsAt.IsZero() && a.Alert.EndsAt.Before(time.Now()) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-04-28 22:46:52 +00:00
|
|
|
status := api.getAlertStatus(a.Fingerprint())
|
|
|
|
|
2018-05-07 16:07:19 +00:00
|
|
|
if !showActive && status.State == types.AlertStateActive {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !showUnprocessed && status.State == types.AlertStateUnprocessed {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-05-17 08:14:42 +00:00
|
|
|
if !showSilenced && len(status.SilencedBy) != 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-10-17 08:49:59 +00:00
|
|
|
if !showInhibited && len(status.InhibitedBy) != 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-08-23 14:03:49 +00:00
|
|
|
alert := &Alert{
|
2017-08-18 17:30:18 +00:00
|
|
|
Alert: &a.Alert,
|
|
|
|
Status: status,
|
|
|
|
Receivers: receivers,
|
|
|
|
Fingerprint: a.Fingerprint().String(),
|
2017-04-28 22:46:52 +00:00
|
|
|
}
|
|
|
|
|
2018-08-23 14:03:49 +00:00
|
|
|
res = append(res, alert)
|
2015-10-15 10:47:15 +00:00
|
|
|
}
|
2017-12-21 15:55:55 +00:00
|
|
|
api.mtx.RUnlock()
|
2015-10-15 10:47:15 +00:00
|
|
|
|
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2015-12-07 12:41:18 +00:00
|
|
|
typ: errorInternal,
|
2015-10-15 10:47:15 +00:00
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
2017-09-02 09:35:17 +00:00
|
|
|
sort.Slice(res, func(i, j int) bool {
|
|
|
|
return res[i].Fingerprint < res[j].Fingerprint
|
|
|
|
})
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respond(w, res)
|
2017-04-28 22:46:52 +00:00
|
|
|
}
|
|
|
|
|
2017-11-12 16:35:49 +00:00
|
|
|
func receiversMatchFilter(receivers []string, filter *regexp.Regexp) bool {
|
|
|
|
for _, r := range receivers {
|
|
|
|
if filter.MatchString(r) {
|
2017-06-26 16:20:26 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-04-28 22:46:52 +00:00
|
|
|
func alertMatchesFilterLabels(a *model.Alert, matchers []*labels.Matcher) bool {
|
2017-11-15 19:29:06 +00:00
|
|
|
sms := make(map[string]string)
|
|
|
|
for name, value := range a.Labels {
|
|
|
|
sms[string(name)] = string(value)
|
2017-04-28 22:46:52 +00:00
|
|
|
}
|
2017-11-15 19:29:06 +00:00
|
|
|
return matchFilterLabels(matchers, sms)
|
2015-09-26 09:12:59 +00:00
|
|
|
}
|
2015-09-25 16:14:46 +00:00
|
|
|
|
2015-12-09 17:21:06 +00:00
|
|
|
func (api *API) addAlerts(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var alerts []*types.Alert
|
2017-10-22 05:59:33 +00:00
|
|
|
if err := api.receive(r, &alerts); err != nil {
|
|
|
|
api.respondError(w, apiError{
|
2015-12-09 17:21:06 +00:00
|
|
|
typ: errorBadData,
|
2015-10-16 12:02:22 +00:00
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-12-09 17:21:06 +00:00
|
|
|
api.insertAlerts(w, r, alerts...)
|
2015-10-16 12:02:22 +00:00
|
|
|
}
|
|
|
|
|
2015-12-09 17:21:06 +00:00
|
|
|
func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...*types.Alert) {
|
2015-09-30 12:53:05 +00:00
|
|
|
now := time.Now()
|
|
|
|
|
2017-12-21 11:08:39 +00:00
|
|
|
api.mtx.RLock()
|
2019-02-06 18:06:31 +00:00
|
|
|
resolveTimeout := time.Duration(api.config.Global.ResolveTimeout)
|
2017-12-21 11:08:39 +00:00
|
|
|
api.mtx.RUnlock()
|
|
|
|
|
2015-09-30 12:53:05 +00:00
|
|
|
for _, alert := range alerts {
|
2015-09-29 15:26:44 +00:00
|
|
|
alert.UpdatedAt = now
|
|
|
|
|
2016-01-09 12:29:02 +00:00
|
|
|
// Ensure StartsAt is set.
|
2015-09-29 15:26:44 +00:00
|
|
|
if alert.StartsAt.IsZero() {
|
2018-02-13 15:26:34 +00:00
|
|
|
if alert.EndsAt.IsZero() {
|
|
|
|
alert.StartsAt = now
|
|
|
|
} else {
|
|
|
|
alert.StartsAt = alert.EndsAt
|
|
|
|
}
|
2015-09-26 09:12:59 +00:00
|
|
|
}
|
2016-01-09 12:29:02 +00:00
|
|
|
// If no end time is defined, set a timeout after which an alert
|
|
|
|
// is marked resolved if it is not updated.
|
2015-09-29 15:26:44 +00:00
|
|
|
if alert.EndsAt.IsZero() {
|
2015-09-30 12:53:05 +00:00
|
|
|
alert.Timeout = true
|
2017-12-21 11:08:39 +00:00
|
|
|
alert.EndsAt = now.Add(resolveTimeout)
|
2018-02-07 15:52:26 +00:00
|
|
|
}
|
|
|
|
if alert.EndsAt.After(time.Now()) {
|
2019-05-24 12:42:52 +00:00
|
|
|
api.m.Firing().Inc()
|
2016-01-09 12:29:02 +00:00
|
|
|
} else {
|
2019-05-24 12:42:52 +00:00
|
|
|
api.m.Resolved().Inc()
|
2015-09-26 09:12:59 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-25 16:14:46 +00:00
|
|
|
|
2016-01-09 12:29:02 +00:00
|
|
|
// Make a best effort to insert all alerts that are valid.
|
|
|
|
var (
|
|
|
|
validAlerts = make([]*types.Alert, 0, len(alerts))
|
|
|
|
validationErrs = &types.MultiError{}
|
|
|
|
)
|
2015-12-09 17:21:06 +00:00
|
|
|
for _, a := range alerts {
|
2018-03-20 11:06:34 +00:00
|
|
|
removeEmptyLabels(a.Labels)
|
|
|
|
|
2015-12-09 17:21:06 +00:00
|
|
|
if err := a.Validate(); err != nil {
|
2016-01-09 12:29:02 +00:00
|
|
|
validationErrs.Add(err)
|
2019-05-24 12:42:52 +00:00
|
|
|
api.m.Invalid().Inc()
|
2016-01-09 12:29:02 +00:00
|
|
|
continue
|
2015-12-09 17:21:06 +00:00
|
|
|
}
|
2016-01-09 12:29:02 +00:00
|
|
|
validAlerts = append(validAlerts, a)
|
2015-12-09 17:21:06 +00:00
|
|
|
}
|
2016-01-09 12:29:02 +00:00
|
|
|
if err := api.alerts.Put(validAlerts...); err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2015-12-07 12:41:18 +00:00
|
|
|
typ: errorInternal,
|
2015-09-26 09:12:59 +00:00
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
2016-01-09 12:29:02 +00:00
|
|
|
|
|
|
|
if validationErrs.Len() > 0 {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2016-01-09 12:29:02 +00:00
|
|
|
typ: errorBadData,
|
|
|
|
err: validationErrs,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
2015-09-25 16:14:46 +00:00
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respond(w, nil)
|
2015-09-26 09:12:59 +00:00
|
|
|
}
|
2015-09-25 16:14:46 +00:00
|
|
|
|
2018-03-20 11:06:34 +00:00
|
|
|
func removeEmptyLabels(ls model.LabelSet) {
|
|
|
|
for k, v := range ls {
|
|
|
|
if string(v) == "" {
|
|
|
|
delete(ls, k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-16 14:48:25 +00:00
|
|
|
func (api *API) setSilence(w http.ResponseWriter, r *http.Request) {
|
2016-08-09 12:13:05 +00:00
|
|
|
var sil types.Silence
|
2017-10-22 05:59:33 +00:00
|
|
|
if err := api.receive(r, &sil); err != nil {
|
|
|
|
api.respondError(w, apiError{
|
2015-12-16 15:39:37 +00:00
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
2015-09-27 12:07:04 +00:00
|
|
|
return
|
|
|
|
}
|
2018-01-21 14:29:51 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2016-08-30 09:58:27 +00:00
|
|
|
psil, err := silenceToProto(&sil)
|
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2016-05-29 23:12:05 +00:00
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
2015-12-09 17:21:06 +00:00
|
|
|
|
2017-05-16 14:48:25 +00:00
|
|
|
sid, err := api.silences.Set(psil)
|
2015-10-01 18:57:37 +00:00
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2017-05-16 14:48:25 +00:00
|
|
|
typ: errorBadData,
|
2015-09-27 12:07:04 +00:00
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respond(w, struct {
|
2016-08-30 09:58:27 +00:00
|
|
|
SilenceID string `json:"silenceId"`
|
2015-10-01 18:57:37 +00:00
|
|
|
}{
|
|
|
|
SilenceID: sid,
|
|
|
|
})
|
2015-09-27 12:07:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) getSilence(w http.ResponseWriter, r *http.Request) {
|
2017-04-26 00:36:36 +00:00
|
|
|
sid := route.Param(r.Context(), "sid")
|
2015-09-27 12:07:04 +00:00
|
|
|
|
2019-02-27 11:33:46 +00:00
|
|
|
sils, _, err := api.silences.Query(silence.QIDs(sid))
|
2016-08-30 09:58:27 +00:00
|
|
|
if err != nil || len(sils) == 0 {
|
2015-09-27 12:07:04 +00:00
|
|
|
http.Error(w, fmt.Sprint("Error getting silence: ", err), http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
2016-08-30 09:58:27 +00:00
|
|
|
sil, err := silenceFromProto(sils[0])
|
2015-09-27 12:07:04 +00:00
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2016-08-30 09:58:27 +00:00
|
|
|
typ: errorInternal,
|
2015-09-27 12:07:04 +00:00
|
|
|
err: err,
|
|
|
|
}, nil)
|
2015-12-09 17:21:06 +00:00
|
|
|
return
|
2015-09-27 12:07:04 +00:00
|
|
|
}
|
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respond(w, sil)
|
2016-08-30 09:58:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) delSilence(w http.ResponseWriter, r *http.Request) {
|
2017-04-26 00:36:36 +00:00
|
|
|
sid := route.Param(r.Context(), "sid")
|
2016-08-30 09:58:27 +00:00
|
|
|
|
|
|
|
if err := api.silences.Expire(sid); err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2016-08-30 09:58:27 +00:00
|
|
|
typ: errorBadData,
|
2015-09-27 12:07:04 +00:00
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respond(w, nil)
|
2015-09-27 12:07:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) listSilences(w http.ResponseWriter, r *http.Request) {
|
2019-02-27 11:33:46 +00:00
|
|
|
psils, _, err := api.silences.Query()
|
2015-09-27 12:07:04 +00:00
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2015-12-07 12:41:18 +00:00
|
|
|
typ: errorInternal,
|
2015-09-27 12:07:04 +00:00
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
2016-08-30 09:58:27 +00:00
|
|
|
|
2017-03-16 10:16:10 +00:00
|
|
|
matchers := []*labels.Matcher{}
|
|
|
|
if filter := r.FormValue("filter"); filter != "" {
|
2019-09-16 08:56:29 +00:00
|
|
|
matchers, err = labels.ParseMatchers(filter)
|
2017-03-16 10:16:10 +00:00
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2017-03-16 10:16:10 +00:00
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sils := []*types.Silence{}
|
2016-08-30 09:58:27 +00:00
|
|
|
for _, ps := range psils {
|
|
|
|
s, err := silenceFromProto(ps)
|
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respondError(w, apiError{
|
2016-08-30 09:58:27 +00:00
|
|
|
typ: errorInternal,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
2017-03-16 10:16:10 +00:00
|
|
|
|
2017-11-15 19:29:06 +00:00
|
|
|
if !silenceMatchesFilterLabels(s, matchers) {
|
2017-03-16 10:16:10 +00:00
|
|
|
continue
|
|
|
|
}
|
2016-08-30 09:58:27 +00:00
|
|
|
sils = append(sils, s)
|
|
|
|
}
|
|
|
|
|
2017-11-11 13:48:48 +00:00
|
|
|
var active, pending, expired []*types.Silence
|
2017-10-02 16:42:43 +00:00
|
|
|
|
|
|
|
for _, s := range sils {
|
|
|
|
switch s.Status.State {
|
2017-11-07 10:36:30 +00:00
|
|
|
case types.SilenceStateActive:
|
2017-10-02 16:42:43 +00:00
|
|
|
active = append(active, s)
|
2017-11-07 10:36:30 +00:00
|
|
|
case types.SilenceStatePending:
|
2017-10-02 16:42:43 +00:00
|
|
|
pending = append(pending, s)
|
2017-11-07 10:36:30 +00:00
|
|
|
case types.SilenceStateExpired:
|
2017-10-02 16:42:43 +00:00
|
|
|
expired = append(expired, s)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(active, func(i int, j int) bool {
|
|
|
|
return active[i].EndsAt.Before(active[j].EndsAt)
|
|
|
|
})
|
|
|
|
sort.Slice(pending, func(i int, j int) bool {
|
|
|
|
return pending[i].StartsAt.Before(pending[j].EndsAt)
|
|
|
|
})
|
|
|
|
sort.Slice(expired, func(i int, j int) bool {
|
|
|
|
return expired[i].EndsAt.After(expired[j].EndsAt)
|
|
|
|
})
|
|
|
|
|
2017-11-11 13:48:48 +00:00
|
|
|
// Initialize silences explicitly to an empty list (instead of nil)
|
|
|
|
// So that it does not get converted to "null" in JSON.
|
|
|
|
silences := []*types.Silence{}
|
2017-10-02 16:42:43 +00:00
|
|
|
silences = append(silences, active...)
|
|
|
|
silences = append(silences, pending...)
|
|
|
|
silences = append(silences, expired...)
|
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
api.respond(w, silences)
|
2015-09-27 12:07:04 +00:00
|
|
|
}
|
2015-07-01 11:17:08 +00:00
|
|
|
|
2017-11-15 19:29:06 +00:00
|
|
|
func silenceMatchesFilterLabels(s *types.Silence, matchers []*labels.Matcher) bool {
|
|
|
|
sms := make(map[string]string)
|
2017-03-16 10:16:10 +00:00
|
|
|
for _, m := range s.Matchers {
|
|
|
|
sms[m.Name] = m.Value
|
|
|
|
}
|
2017-11-15 19:29:06 +00:00
|
|
|
|
|
|
|
return matchFilterLabels(matchers, sms)
|
|
|
|
}
|
|
|
|
|
|
|
|
func matchFilterLabels(matchers []*labels.Matcher, sms map[string]string) bool {
|
2017-03-16 10:16:10 +00:00
|
|
|
for _, m := range matchers {
|
2017-11-15 19:29:06 +00:00
|
|
|
v, prs := sms[m.Name]
|
|
|
|
switch m.Type {
|
2018-03-20 15:11:01 +00:00
|
|
|
case labels.MatchNotRegexp, labels.MatchNotEqual:
|
2018-03-20 10:47:53 +00:00
|
|
|
if string(m.Value) == "" && prs {
|
|
|
|
continue
|
2018-03-20 09:08:20 +00:00
|
|
|
}
|
2017-11-15 19:29:06 +00:00
|
|
|
if !m.Matches(string(v)) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
default:
|
2018-03-20 10:47:53 +00:00
|
|
|
if string(m.Value) == "" && !prs {
|
|
|
|
continue
|
2018-03-20 09:08:20 +00:00
|
|
|
}
|
2020-11-06 10:26:32 +00:00
|
|
|
if !m.Matches(string(v)) {
|
2017-11-15 19:29:06 +00:00
|
|
|
return false
|
|
|
|
}
|
2017-03-16 10:16:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-08-30 09:58:27 +00:00
|
|
|
func silenceToProto(s *types.Silence) (*silencepb.Silence, error) {
|
|
|
|
sil := &silencepb.Silence{
|
|
|
|
Id: s.ID,
|
2017-04-18 08:46:40 +00:00
|
|
|
StartsAt: s.StartsAt,
|
|
|
|
EndsAt: s.EndsAt,
|
|
|
|
UpdatedAt: s.UpdatedAt,
|
2017-05-16 14:48:25 +00:00
|
|
|
Comment: s.Comment,
|
|
|
|
CreatedBy: s.CreatedBy,
|
2016-08-30 09:58:27 +00:00
|
|
|
}
|
|
|
|
for _, m := range s.Matchers {
|
|
|
|
matcher := &silencepb.Matcher{
|
|
|
|
Name: m.Name,
|
|
|
|
Pattern: m.Value,
|
|
|
|
}
|
2021-01-13 14:11:28 +00:00
|
|
|
switch m.Type {
|
|
|
|
case labels.MatchEqual:
|
|
|
|
matcher.Type = silencepb.Matcher_EQUAL
|
|
|
|
case labels.MatchNotEqual:
|
|
|
|
matcher.Type = silencepb.Matcher_NOT_EQUAL
|
|
|
|
case labels.MatchRegexp:
|
2016-08-30 09:58:27 +00:00
|
|
|
matcher.Type = silencepb.Matcher_REGEXP
|
2021-01-13 14:11:28 +00:00
|
|
|
case labels.MatchNotRegexp:
|
|
|
|
matcher.Type = silencepb.Matcher_NOT_REGEXP
|
2016-08-30 09:58:27 +00:00
|
|
|
}
|
|
|
|
sil.Matchers = append(sil.Matchers, matcher)
|
|
|
|
}
|
|
|
|
return sil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func silenceFromProto(s *silencepb.Silence) (*types.Silence, error) {
|
|
|
|
sil := &types.Silence{
|
|
|
|
ID: s.Id,
|
2017-04-18 08:46:40 +00:00
|
|
|
StartsAt: s.StartsAt,
|
|
|
|
EndsAt: s.EndsAt,
|
|
|
|
UpdatedAt: s.UpdatedAt,
|
2017-05-10 09:55:28 +00:00
|
|
|
Status: types.SilenceStatus{
|
|
|
|
State: types.CalcSilenceState(s.StartsAt, s.EndsAt),
|
|
|
|
},
|
2017-05-16 14:48:25 +00:00
|
|
|
Comment: s.Comment,
|
|
|
|
CreatedBy: s.CreatedBy,
|
2016-08-30 09:58:27 +00:00
|
|
|
}
|
|
|
|
for _, m := range s.Matchers {
|
2021-01-13 14:11:28 +00:00
|
|
|
var t labels.MatchType
|
2016-08-30 09:58:27 +00:00
|
|
|
switch m.Type {
|
|
|
|
case silencepb.Matcher_EQUAL:
|
2021-01-13 14:11:28 +00:00
|
|
|
t = labels.MatchEqual
|
|
|
|
case silencepb.Matcher_NOT_EQUAL:
|
|
|
|
t = labels.MatchNotEqual
|
2016-08-30 09:58:27 +00:00
|
|
|
case silencepb.Matcher_REGEXP:
|
2021-01-13 14:11:28 +00:00
|
|
|
t = labels.MatchRegexp
|
|
|
|
case silencepb.Matcher_NOT_REGEXP:
|
|
|
|
t = labels.MatchNotRegexp
|
2016-08-30 09:58:27 +00:00
|
|
|
}
|
2021-01-13 14:11:28 +00:00
|
|
|
matcher, err := labels.NewMatcher(t, m.Name, m.Pattern)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-08-30 09:58:27 +00:00
|
|
|
sil.Matchers = append(sil.Matchers, matcher)
|
|
|
|
}
|
|
|
|
|
|
|
|
return sil, nil
|
|
|
|
}
|
|
|
|
|
2015-07-01 11:17:08 +00:00
|
|
|
type status string
|
|
|
|
|
|
|
|
const (
|
|
|
|
statusSuccess status = "success"
|
2018-02-28 16:42:32 +00:00
|
|
|
statusError status = "error"
|
2015-07-01 11:17:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type response struct {
|
|
|
|
Status status `json:"status"`
|
|
|
|
Data interface{} `json:"data,omitempty"`
|
|
|
|
ErrorType errorType `json:"errorType,omitempty"`
|
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
func (api *API) respond(w http.ResponseWriter, data interface{}) {
|
2015-07-01 11:17:08 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2015-07-02 16:38:05 +00:00
|
|
|
w.WriteHeader(200)
|
2015-07-01 11:17:08 +00:00
|
|
|
|
|
|
|
b, err := json.Marshal(&response{
|
|
|
|
Status: statusSuccess,
|
|
|
|
Data: data,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2020-01-23 16:06:16 +00:00
|
|
|
level.Error(api.logger).Log("msg", "Error marshaling JSON", "err", err)
|
2015-07-01 11:17:08 +00:00
|
|
|
return
|
|
|
|
}
|
2018-08-14 17:14:00 +00:00
|
|
|
|
|
|
|
if _, err := w.Write(b); err != nil {
|
|
|
|
level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err)
|
|
|
|
}
|
2015-07-01 11:17:08 +00:00
|
|
|
}
|
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
func (api *API) respondError(w http.ResponseWriter, apiErr apiError, data interface{}) {
|
2015-07-01 11:17:08 +00:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2015-12-07 12:41:18 +00:00
|
|
|
|
|
|
|
switch apiErr.typ {
|
|
|
|
case errorBadData:
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
case errorInternal:
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
default:
|
2018-02-22 14:57:45 +00:00
|
|
|
panic(fmt.Sprintf("unknown error type %q", apiErr.Error()))
|
2015-12-07 12:41:18 +00:00
|
|
|
}
|
2015-07-01 11:17:08 +00:00
|
|
|
|
|
|
|
b, err := json.Marshal(&response{
|
|
|
|
Status: statusError,
|
|
|
|
ErrorType: apiErr.typ,
|
|
|
|
Error: apiErr.err.Error(),
|
|
|
|
Data: data,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2017-10-22 05:59:33 +00:00
|
|
|
level.Error(api.logger).Log("msg", "API error", "err", apiErr.Error())
|
2015-12-07 12:41:18 +00:00
|
|
|
|
2018-08-14 17:14:00 +00:00
|
|
|
if _, err := w.Write(b); err != nil {
|
|
|
|
level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err)
|
|
|
|
}
|
2015-07-01 11:17:08 +00:00
|
|
|
}
|
|
|
|
|
2017-10-22 05:59:33 +00:00
|
|
|
func (api *API) receive(r *http.Request, v interface{}) error {
|
2015-07-01 11:17:08 +00:00
|
|
|
dec := json.NewDecoder(r.Body)
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
2015-10-01 20:16:44 +00:00
|
|
|
err := dec.Decode(v)
|
|
|
|
if err != nil {
|
2017-10-22 05:59:33 +00:00
|
|
|
level.Debug(api.logger).Log("msg", "Decoding request failed", "err", err)
|
2019-09-27 08:05:09 +00:00
|
|
|
return err
|
2015-10-01 20:16:44 +00:00
|
|
|
}
|
2019-09-27 08:05:09 +00:00
|
|
|
return nil
|
2015-07-01 11:17:08 +00:00
|
|
|
}
|