alertmanager/api/api.go

846 lines
19 KiB
Go
Raw Normal View History

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.
package api
2015-07-01 11:17:08 +00:00
import (
"encoding/json"
"errors"
2015-07-01 11:17:08 +00:00
"fmt"
"net/http"
"regexp"
"sort"
"sync"
2015-09-26 09:12:59 +00:00
"time"
2015-07-01 11:17:08 +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"
"github.com/prometheus/common/version"
"github.com/prometheus/prometheus/pkg/labels"
2015-09-26 09:12:59 +00:00
2018-02-07 15:36:47 +00:00
"github.com/prometheus/alertmanager/cluster"
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/dispatch"
"github.com/prometheus/alertmanager/pkg/parse"
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
)
var (
numReceivedAlerts = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "alertmanager",
Name: "alerts_received_total",
Help: "The total number of received alerts.",
}, []string{"status"})
numInvalidAlerts = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "alertmanager",
2016-01-09 12:49:25 +00:00
Name: "alerts_invalid_total",
Help: "The total number of received alerts that were invalid.",
})
)
func init() {
prometheus.Register(numReceivedAlerts)
prometheus.Register(numInvalidAlerts)
}
2016-09-04 15:43:25 +00:00
var corsHeaders = map[string]string{
"Access-Control-Allow-Headers": "Accept, Authorization, Content-Type, Origin",
2017-01-17 09:42:29 +00:00
"Access-Control-Allow-Methods": "GET, DELETE, OPTIONS",
2016-09-04 15:43:25 +00:00
"Access-Control-Allow-Origin": "*",
"Access-Control-Expose-Headers": "Date",
}
// 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 {
2015-11-27 14:41:22 +00:00
alerts provider.Alerts
2016-08-30 09:58:27 +00:00
silences *silence.Silences
2017-06-08 11:14:37 +00:00
config *config.Config
route *dispatch.Route
2015-11-27 14:41:22 +00:00
resolveTimeout time.Duration
uptime time.Time
2018-02-07 15:36:47 +00:00
peer *cluster.Peer
logger log.Logger
groups groupsFn
getAlertStatus getAlertStatusFn
2017-04-26 00:36:36 +00:00
mtx sync.RWMutex
2015-07-01 11:17:08 +00:00
}
type groupsFn func([]*labels.Matcher) dispatch.AlertOverview
type getAlertStatusFn func(model.Fingerprint) types.AlertStatus
// New returns a new API.
2018-02-07 15:36:47 +00:00
func New(
alerts provider.Alerts,
silences *silence.Silences,
gf groupsFn,
sf getAlertStatusFn,
peer *cluster.Peer,
l log.Logger,
) *API {
if l == nil {
l = log.NewNopLogger()
}
return &API{
alerts: alerts,
silences: silences,
groups: gf,
getAlertStatus: sf,
uptime: time.Now(),
2018-02-07 15:36:47 +00:00
peer: peer,
logger: l,
2015-07-01 11:17:08 +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.
func (api *API) Register(r *route.Router) {
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)
})
}
r.Options("/*path", wrap(func(w http.ResponseWriter, r *http.Request) {}))
2016-01-09 12:16:00 +00:00
2015-10-16 12:02:22 +00:00
// Register legacy forwarder for alert pushing.
r.Post("/alerts", wrap(api.legacyAddAlerts))
2015-10-16 12:02:22 +00:00
// Register actual API.
r = r.WithPrefix("/v1")
r.Get("/status", wrap(api.status))
r.Get("/receivers", wrap(api.receivers))
r.Get("/alerts/groups", wrap(api.alertGroups))
2015-07-01 11:17:08 +00:00
r.Get("/alerts", wrap(api.listAlerts))
r.Post("/alerts", wrap(api.addAlerts))
2015-07-01 11:17:08 +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-05 09:49:32 +00:00
// Update sets the configuration string to a new value.
2017-06-08 11:14:37 +00:00
func (api *API) Update(cfg *config.Config, resolveTimeout time.Duration) error {
api.mtx.Lock()
defer api.mtx.Unlock()
2015-07-01 11:17:08 +00:00
2015-11-27 14:41:22 +00:00
api.resolveTimeout = resolveTimeout
2017-06-08 11:14:37 +00:00
api.config = cfg
api.route = dispatch.NewRoute(cfg.Route, nil)
return nil
2015-07-01 11:17:08 +00:00
}
type errorType string
const (
errorNone errorType = ""
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)
}
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()
var 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"`
}{
2017-06-08 11:14:37 +00:00
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.Peer) *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(),
2018-02-07 15:36:47 +00:00
})
}
return s
}
func (api *API) alertGroups(w http.ResponseWriter, r *http.Request) {
var err error
matchers := []*labels.Matcher{}
if filter := r.FormValue("filter"); filter != "" {
matchers, err = parse.Matchers(filter)
if err != nil {
api.respondError(w, apiError{
typ: errorBadData,
err: err,
}, nil)
return
}
}
groups := api.groups(matchers)
api.respond(w, groups)
}
func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
2015-10-15 10:47:15 +00:00
var (
err error
receiverFilter *regexp.Regexp
// Initialize result slice to prevent api returning `null` when there
// are no alerts present
res = []*dispatch.APIAlert{}
matchers = []*labels.Matcher{}
showSilenced = true
showInhibited = true
2015-10-15 10:47:15 +00:00
)
if filter := r.FormValue("filter"); filter != "" {
matchers, err = parse.Matchers(filter)
if err != nil {
api.respondError(w, apiError{
typ: errorBadData,
err: err,
}, nil)
return
}
}
if silencedParam := r.FormValue("silenced"); silencedParam != "" {
if silencedParam == "false" {
showSilenced = false
} else if silencedParam != "true" {
api.respondError(w, apiError{
typ: errorBadData,
err: fmt.Errorf(
"parameter 'silenced' can either be 'true' or 'false', not '%v'",
silencedParam,
),
}, nil)
return
}
}
if inhibitedParam := r.FormValue("inhibited"); inhibitedParam != "" {
if inhibitedParam == "false" {
showInhibited = false
} else if inhibitedParam != "true" {
api.respondError(w, apiError{
typ: errorBadData,
err: fmt.Errorf(
"parameter 'inhibited' can either be 'true' or 'false', not '%v'",
inhibitedParam,
),
}, 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()
2015-10-15 10:47:15 +00:00
// TODO(fabxc): enforce a sensible timeout.
for a := range alerts.Next() {
if err = alerts.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 alert is resolved
if !a.Alert.EndsAt.IsZero() && a.Alert.EndsAt.Before(time.Now()) {
continue
}
status := api.getAlertStatus(a.Fingerprint())
if !showSilenced && len(status.SilencedBy) != 0 {
continue
}
if !showInhibited && len(status.InhibitedBy) != 0 {
continue
}
apiAlert := &dispatch.APIAlert{
Alert: &a.Alert,
Status: status,
Receivers: receivers,
Fingerprint: a.Fingerprint().String(),
}
res = append(res, apiAlert)
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 {
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
}
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)
2015-09-26 09:12:59 +00:00
}
2015-09-25 16:14:46 +00:00
2015-10-16 12:02:22 +00:00
func (api *API) legacyAddAlerts(w http.ResponseWriter, r *http.Request) {
var legacyAlerts = []struct {
Summary model.LabelValue `json:"summary"`
Description model.LabelValue `json:"description"`
Runbook model.LabelValue `json:"runbook"`
Labels model.LabelSet `json:"labels"`
Payload model.LabelSet `json:"payload"`
}{}
if err := api.receive(r, &legacyAlerts); err != nil {
2015-10-16 12:02:22 +00:00
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
var alerts []*types.Alert
for _, la := range legacyAlerts {
a := &types.Alert{
Alert: model.Alert{
Labels: la.Labels,
Annotations: la.Payload,
},
}
if a.Annotations == nil {
a.Annotations = model.LabelSet{}
}
2015-10-16 12:02:22 +00:00
a.Annotations["summary"] = la.Summary
a.Annotations["description"] = la.Description
a.Annotations["runbook"] = la.Runbook
alerts = append(alerts, a)
}
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) addAlerts(w http.ResponseWriter, r *http.Request) {
var alerts []*types.Alert
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) {
now := time.Now()
api.mtx.RLock()
resolveTimeout := api.resolveTimeout
api.mtx.RUnlock()
for _, alert := range alerts {
2015-09-29 15:26:44 +00:00
alert.UpdatedAt = now
// Ensure StartsAt is set.
2015-09-29 15:26:44 +00:00
if alert.StartsAt.IsZero() {
if alert.EndsAt.IsZero() {
alert.StartsAt = now
} else {
alert.StartsAt = alert.EndsAt
}
2015-09-26 09:12:59 +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() {
alert.Timeout = true
alert.EndsAt = now.Add(resolveTimeout)
}
if alert.EndsAt.After(time.Now()) {
numReceivedAlerts.WithLabelValues("firing").Inc()
} else {
numReceivedAlerts.WithLabelValues("resolved").Inc()
2015-09-26 09:12:59 +00:00
}
}
2015-09-25 16:14:46 +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 {
removeEmptyLabels(a.Labels)
2015-12-09 17:21:06 +00:00
if err := a.Validate(); err != nil {
validationErrs.Add(err)
numInvalidAlerts.Inc()
continue
2015-12-09 17:21:06 +00:00
}
validAlerts = append(validAlerts, a)
2015-12-09 17:21:06 +00:00
}
if err := api.alerts.Put(validAlerts...); err != nil {
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
}
if validationErrs.Len() > 0 {
api.respondError(w, apiError{
typ: errorBadData,
err: validationErrs,
}, nil)
return
}
2015-09-25 16:14:46 +00:00
api.respond(w, nil)
2015-09-26 09:12:59 +00:00
}
2015-09-25 16:14:46 +00:00
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) {
2016-08-09 12:13:05 +00:00
var sil types.Silence
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
}
// 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 {
api.respondError(w, apiError{
typ: errorBadData,
err: err,
}, nil)
return
}
2015-12-09 17:21:06 +00:00
sid, err := api.silences.Set(psil)
if err != nil {
api.respondError(w, apiError{
typ: errorBadData,
2015-09-27 12:07:04 +00:00
err: err,
}, nil)
return
}
api.respond(w, struct {
2016-08-30 09:58:27 +00:00
SilenceID string `json:"silenceId"`
}{
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
2016-08-30 09:58:27 +00:00
sils, err := api.silences.Query(silence.QIDs(sid))
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 {
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
}
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 {
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
}
api.respond(w, nil)
2015-09-27 12:07:04 +00:00
}
func (api *API) listSilences(w http.ResponseWriter, r *http.Request) {
2016-08-30 09:58:27 +00:00
psils, err := api.silences.Query()
2015-09-27 12:07:04 +00:00
if err != nil {
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
matchers := []*labels.Matcher{}
if filter := r.FormValue("filter"); filter != "" {
matchers, err = parse.Matchers(filter)
if err != nil {
api.respondError(w, apiError{
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 {
api.respondError(w, apiError{
2016-08-30 09:58:27 +00:00
typ: errorInternal,
err: err,
}, nil)
return
}
if !silenceMatchesFilterLabels(s, matchers) {
continue
}
2016-08-30 09:58:27 +00:00
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 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)
})
// 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)
2015-09-27 12:07:04 +00:00
}
2015-07-01 11:17:08 +00:00
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 {
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
}
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
}
if !prs || !m.Matches(string(v)) {
return false
}
}
}
return true
}
2016-08-30 09:58:27 +00:00
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,
2016-08-30 09:58:27 +00:00
}
for _, m := range s.Matchers {
matcher := &silencepb.Matcher{
Name: m.Name,
Pattern: m.Value,
Type: silencepb.Matcher_EQUAL,
}
if m.IsRegex {
matcher.Type = silencepb.Matcher_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,
2016-08-30 09:58:27 +00:00
}
for _, m := range s.Matchers {
matcher := &types.Matcher{
Name: m.Name,
Value: m.Pattern,
}
switch m.Type {
case silencepb.Matcher_EQUAL:
case silencepb.Matcher_REGEXP:
matcher.IsRegex = true
default:
return nil, fmt.Errorf("unknown matcher type")
}
sil.Matchers = append(sil.Matchers, matcher)
}
return sil, nil
}
2015-07-01 11:17:08 +00:00
type status string
const (
statusSuccess status = "success"
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"`
}
func (api *API) respond(w http.ResponseWriter, data interface{}) {
2015-07-01 11:17:08 +00:00
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
2015-07-01 11:17:08 +00:00
b, err := json.Marshal(&response{
Status: statusSuccess,
Data: data,
})
if err != nil {
level.Error(api.logger).Log("msg", "Error marshalling JSON", "err", err)
2015-07-01 11:17:08 +00:00
return
}
w.Write(b)
}
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:
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
}
level.Error(api.logger).Log("msg", "API error", "err", apiErr.Error())
2015-12-07 12:41:18 +00:00
2015-07-01 11:17:08 +00:00
w.Write(b)
}
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()
err := dec.Decode(v)
if err != nil {
level.Debug(api.logger).Log("msg", "Decoding request failed", "err", err)
}
return err
2015-07-01 11:17:08 +00:00
}