alertmanager/api/v1/api.go

809 lines
18 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.
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"
"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"
2015-09-26 09:12:59 +00:00
"github.com/prometheus/alertmanager/api/metrics"
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/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",
"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",
"Cache-Control": "no-cache, no-store, must-revalidate",
2016-09-04 15:43:25 +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 {
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
2017-04-26 00:36:36 +00:00
mtx sync.RWMutex
2015-07-01 11:17:08 +00:00
}
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,
sf getAlertStatusFn,
peer cluster.ClusterPeer,
2018-02-07 15:36:47 +00:00
l log.Logger,
r prometheus.Registerer,
2018-02-07 15:36:47 +00:00
) *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),
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
r.Get("/status", wrap(api.status))
r.Get("/receivers", wrap(api.receivers))
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.
func (api *API) Update(cfg *config.Config) {
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
api.route = dispatch.NewRoute(cfg.Route, nil)
2015-07-01 11:17:08 +00:00
}
type errorType string
const (
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.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(),
2018-02-07 15:36:47 +00:00
})
}
return s
}
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 = []*Alert{}
matchers = []*labels.Matcher{}
ctx = r.Context()
showActive, showInhibited bool
showSilenced, showUnprocessed bool
2015-10-15 10:47:15 +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)
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()
2015-10-15 10:47:15 +00:00
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)
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-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 := time.Duration(api.config.Global.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()) {
api.m.Firing().Inc()
} else {
api.m.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)
api.m.Invalid().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
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 {
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) {
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 = labels.ParseMatchers(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 !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,
}
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
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,
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 {
var t labels.MatchType
2016-08-30 09:58:27 +00:00
switch m.Type {
case silencepb.Matcher_EQUAL:
t = labels.MatchEqual
case silencepb.Matcher_NOT_EQUAL:
t = labels.MatchNotEqual
2016-08-30 09:58:27 +00:00
case silencepb.Matcher_REGEXP:
t = labels.MatchRegexp
case silencepb.Matcher_NOT_REGEXP:
t = labels.MatchNotRegexp
2016-08-30 09:58:27 +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"
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 marshaling JSON", "err", err)
2015-07-01 11:17:08 +00:00
return
}
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
}
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
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
}
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
}
return nil
2015-07-01 11:17:08 +00:00
}