2015-09-25 16:14:46 +00:00
|
|
|
package main
|
2015-07-01 11:17:08 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2015-09-27 12:07:04 +00:00
|
|
|
"strconv"
|
2015-09-26 09:12:59 +00:00
|
|
|
"time"
|
2015-07-01 11:17:08 +00:00
|
|
|
|
2015-10-01 20:16:44 +00:00
|
|
|
"github.com/prometheus/common/log"
|
2015-07-01 11:17:08 +00:00
|
|
|
"github.com/prometheus/common/route"
|
|
|
|
"golang.org/x/net/context"
|
2015-09-26 09:12:59 +00:00
|
|
|
|
|
|
|
"github.com/prometheus/alertmanager/provider"
|
|
|
|
"github.com/prometheus/alertmanager/types"
|
2015-07-01 11:17:08 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type API struct {
|
2015-09-27 12:07:04 +00:00
|
|
|
alerts provider.Alerts
|
|
|
|
silences provider.Silences
|
2015-07-01 11:17:08 +00:00
|
|
|
// context is an indirection for testing.
|
|
|
|
context func(r *http.Request) context.Context
|
|
|
|
}
|
|
|
|
|
2015-09-27 12:07:04 +00:00
|
|
|
func NewAPI(r *route.Router, alerts provider.Alerts, silences provider.Silences) *API {
|
2015-07-01 11:17:08 +00:00
|
|
|
api := &API{
|
2015-09-27 12:07:04 +00:00
|
|
|
context: route.Context,
|
|
|
|
alerts: alerts,
|
|
|
|
silences: silences,
|
2015-07-01 11:17:08 +00:00
|
|
|
}
|
2015-07-01 16:36:37 +00:00
|
|
|
|
2015-09-26 09:12:59 +00:00
|
|
|
r.Get("/alerts", api.listAlerts)
|
|
|
|
r.Post("/alerts", api.addAlerts)
|
2015-07-01 11:17:08 +00:00
|
|
|
|
2015-09-27 12:07:04 +00:00
|
|
|
r.Get("/silences", api.listSilences)
|
|
|
|
r.Post("/silences", api.addSilence)
|
2015-07-01 11:17:08 +00:00
|
|
|
|
2015-09-27 12:07:04 +00:00
|
|
|
r.Get("/silence/:sid", api.getSilence)
|
|
|
|
r.Put("/silence/:sid", api.setSilence)
|
|
|
|
r.Del("/silence/:sid", api.delSilence)
|
2015-07-01 11:17:08 +00:00
|
|
|
|
|
|
|
return api
|
|
|
|
}
|
|
|
|
|
|
|
|
type errorType string
|
|
|
|
|
|
|
|
const (
|
|
|
|
errorNone errorType = ""
|
|
|
|
errorTimeout = "timeout"
|
|
|
|
errorCanceled = "canceled"
|
|
|
|
errorBadData = "bad_data"
|
|
|
|
)
|
|
|
|
|
|
|
|
type apiError struct {
|
|
|
|
typ errorType
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *apiError) Error() string {
|
|
|
|
return fmt.Sprintf("%s: %s", e.typ, e.err)
|
|
|
|
}
|
|
|
|
|
2015-09-26 09:12:59 +00:00
|
|
|
func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
|
2015-09-29 08:45:58 +00:00
|
|
|
// alerts, err := api.alerts.GetAll()
|
|
|
|
// if err != nil {
|
|
|
|
// respondError(w, apiError{
|
|
|
|
// typ: errorBadData,
|
|
|
|
// err: err,
|
|
|
|
// }, nil)
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// respond(w, alerts)
|
2015-09-26 09:12:59 +00:00
|
|
|
}
|
2015-09-25 16:14:46 +00:00
|
|
|
|
2015-09-26 09:12:59 +00:00
|
|
|
func (api *API) addAlerts(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var alerts []*types.Alert
|
|
|
|
if err := receive(r, &alerts); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-09-30 12:53:05 +00:00
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
for _, alert := range alerts {
|
2015-09-29 15:26:44 +00:00
|
|
|
alert.UpdatedAt = now
|
|
|
|
|
|
|
|
if alert.StartsAt.IsZero() {
|
|
|
|
alert.StartsAt = now
|
2015-09-26 09:12:59 +00:00
|
|
|
}
|
2015-09-29 15:26:44 +00:00
|
|
|
if alert.EndsAt.IsZero() {
|
2015-09-30 12:53:05 +00:00
|
|
|
alert.Timeout = true
|
2015-09-29 15:26:44 +00:00
|
|
|
alert.EndsAt = alert.StartsAt.Add(ResolveTimeout)
|
2015-09-26 09:12:59 +00:00
|
|
|
}
|
|
|
|
}
|
2015-09-25 16:14:46 +00:00
|
|
|
|
2015-09-26 09:12:59 +00:00
|
|
|
// TODO(fabxc): validate input.
|
|
|
|
if err := api.alerts.Put(alerts...); err != nil {
|
|
|
|
respondError(w, apiError{
|
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
2015-09-25 16:14:46 +00:00
|
|
|
|
2015-09-26 09:12:59 +00:00
|
|
|
respond(w, nil)
|
|
|
|
}
|
2015-09-25 16:14:46 +00:00
|
|
|
|
2015-09-27 12:07:04 +00:00
|
|
|
func (api *API) addSilence(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var sil types.Silence
|
|
|
|
if err := receive(r, &sil); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// TODO(fabxc): validate input.
|
2015-10-01 18:57:37 +00:00
|
|
|
sid, err := api.silences.Set(&sil)
|
|
|
|
if err != nil {
|
2015-09-27 12:07:04 +00:00
|
|
|
respondError(w, apiError{
|
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-10-01 18:57:37 +00:00
|
|
|
respond(w, struct {
|
|
|
|
SilenceID uint64 `json:"silenceId"`
|
|
|
|
}{
|
|
|
|
SilenceID: sid,
|
|
|
|
})
|
2015-09-27 12:07:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) getSilence(w http.ResponseWriter, r *http.Request) {
|
|
|
|
sids := route.Param(api.context(r), "sid")
|
|
|
|
sid, err := strconv.ParseUint(sids, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
respondError(w, apiError{
|
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
}
|
|
|
|
|
2015-10-01 15:50:15 +00:00
|
|
|
sil, err := api.silences.Get(sid)
|
2015-09-27 12:07:04 +00:00
|
|
|
if err != nil {
|
|
|
|
http.Error(w, fmt.Sprint("Error getting silence: ", err), http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
respond(w, &sil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) setSilence(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var sil types.Silence
|
|
|
|
if err := receive(r, &sil); err != nil {
|
|
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sids := route.Param(api.context(r), "sid")
|
|
|
|
sid, err := strconv.ParseUint(sids, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
respondError(w, apiError{
|
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
}
|
2015-10-01 15:50:15 +00:00
|
|
|
sil.ID = sid
|
2015-09-27 12:07:04 +00:00
|
|
|
|
2015-10-01 18:57:37 +00:00
|
|
|
sid, err = api.silences.Set(&sil)
|
|
|
|
if err != nil {
|
2015-09-27 12:07:04 +00:00
|
|
|
respondError(w, apiError{
|
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, &sil)
|
|
|
|
return
|
|
|
|
}
|
2015-10-01 18:57:37 +00:00
|
|
|
respond(w, struct {
|
|
|
|
SilenceID uint64 `json:"silenceId"`
|
|
|
|
}{
|
|
|
|
SilenceID: sid,
|
|
|
|
})
|
2015-09-27 12:07:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) delSilence(w http.ResponseWriter, r *http.Request) {
|
|
|
|
sids := route.Param(api.context(r), "sid")
|
|
|
|
sid, err := strconv.ParseUint(sids, 10, 64)
|
|
|
|
if err != nil {
|
|
|
|
respondError(w, apiError{
|
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
}
|
|
|
|
|
2015-10-01 15:50:15 +00:00
|
|
|
if err := api.silences.Del(sid); err != nil {
|
2015-09-27 12:07:04 +00:00
|
|
|
respondError(w, apiError{
|
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
respond(w, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (api *API) listSilences(w http.ResponseWriter, r *http.Request) {
|
|
|
|
sils, err := api.silences.All()
|
|
|
|
if err != nil {
|
|
|
|
respondError(w, apiError{
|
|
|
|
typ: errorBadData,
|
|
|
|
err: err,
|
|
|
|
}, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
respond(w, sils)
|
|
|
|
}
|
2015-07-01 11:17:08 +00:00
|
|
|
|
|
|
|
type status string
|
|
|
|
|
|
|
|
const (
|
|
|
|
statusSuccess status = "success"
|
|
|
|
statusError = "error"
|
|
|
|
)
|
|
|
|
|
|
|
|
type response struct {
|
|
|
|
Status status `json:"status"`
|
|
|
|
Data interface{} `json:"data,omitempty"`
|
|
|
|
ErrorType errorType `json:"errorType,omitempty"`
|
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func respond(w http.ResponseWriter, data interface{}) {
|
|
|
|
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 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.Write(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func respondError(w http.ResponseWriter, apiErr apiError, data interface{}) {
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2015-07-02 16:38:05 +00:00
|
|
|
w.WriteHeader(422)
|
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
|
|
|
|
}
|
|
|
|
w.Write(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func receive(r *http.Request, v interface{}) error {
|
|
|
|
dec := json.NewDecoder(r.Body)
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
2015-10-01 20:16:44 +00:00
|
|
|
err := dec.Decode(v)
|
|
|
|
if err != nil {
|
|
|
|
log.Debugf("Decoding request failed: %v", err)
|
|
|
|
}
|
|
|
|
return err
|
2015-07-01 11:17:08 +00:00
|
|
|
}
|