...

Source file src/github.com/prometheus/alertmanager/api/v1/api.go

Documentation: github.com/prometheus/alertmanager/api/v1

     1  // Copyright 2015 Prometheus Team
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package v1
    15  
    16  import (
    17  	"encoding/json"
    18  	"errors"
    19  	"fmt"
    20  	"net/http"
    21  	"regexp"
    22  	"sort"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/go-kit/log"
    27  	"github.com/go-kit/log/level"
    28  	"github.com/prometheus/client_golang/prometheus"
    29  	"github.com/prometheus/common/model"
    30  	"github.com/prometheus/common/route"
    31  	"github.com/prometheus/common/version"
    32  
    33  	"github.com/prometheus/alertmanager/api/metrics"
    34  	"github.com/prometheus/alertmanager/cluster"
    35  	"github.com/prometheus/alertmanager/config"
    36  	"github.com/prometheus/alertmanager/dispatch"
    37  	"github.com/prometheus/alertmanager/pkg/labels"
    38  	"github.com/prometheus/alertmanager/provider"
    39  	"github.com/prometheus/alertmanager/silence"
    40  	"github.com/prometheus/alertmanager/silence/silencepb"
    41  	"github.com/prometheus/alertmanager/types"
    42  )
    43  
    44  var corsHeaders = map[string]string{
    45  	"Access-Control-Allow-Headers":  "Accept, Authorization, Content-Type, Origin",
    46  	"Access-Control-Allow-Methods":  "GET, POST, DELETE, OPTIONS",
    47  	"Access-Control-Allow-Origin":   "*",
    48  	"Access-Control-Expose-Headers": "Date",
    49  	"Cache-Control":                 "no-cache, no-store, must-revalidate",
    50  }
    51  
    52  // Alert is the API representation of an alert, which is a regular alert
    53  // annotated with silencing and inhibition info.
    54  type Alert struct {
    55  	*model.Alert
    56  	Status      types.AlertStatus `json:"status"`
    57  	Receivers   []string          `json:"receivers"`
    58  	Fingerprint string            `json:"fingerprint"`
    59  }
    60  
    61  // Enables cross-site script calls.
    62  func setCORS(w http.ResponseWriter) {
    63  	for h, v := range corsHeaders {
    64  		w.Header().Set(h, v)
    65  	}
    66  }
    67  
    68  // API provides registration of handlers for API routes.
    69  type API struct {
    70  	alerts   provider.Alerts
    71  	silences *silence.Silences
    72  	config   *config.Config
    73  	route    *dispatch.Route
    74  	uptime   time.Time
    75  	peer     cluster.ClusterPeer
    76  	logger   log.Logger
    77  	m        *metrics.Alerts
    78  
    79  	getAlertStatus getAlertStatusFn
    80  
    81  	mtx sync.RWMutex
    82  }
    83  
    84  type getAlertStatusFn func(model.Fingerprint) types.AlertStatus
    85  
    86  // New returns a new API.
    87  func New(
    88  	alerts provider.Alerts,
    89  	silences *silence.Silences,
    90  	sf getAlertStatusFn,
    91  	peer cluster.ClusterPeer,
    92  	l log.Logger,
    93  	r prometheus.Registerer,
    94  ) *API {
    95  	if l == nil {
    96  		l = log.NewNopLogger()
    97  	}
    98  
    99  	return &API{
   100  		alerts:         alerts,
   101  		silences:       silences,
   102  		getAlertStatus: sf,
   103  		uptime:         time.Now(),
   104  		peer:           peer,
   105  		logger:         l,
   106  		m:              metrics.NewAlerts("v1", r),
   107  	}
   108  }
   109  
   110  // Register registers the API handlers under their correct routes
   111  // in the given router.
   112  func (api *API) Register(r *route.Router) {
   113  	wrap := func(f http.HandlerFunc) http.HandlerFunc {
   114  		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   115  			setCORS(w)
   116  			f(w, r)
   117  		})
   118  	}
   119  
   120  	r.Options("/*path", wrap(func(w http.ResponseWriter, r *http.Request) {}))
   121  
   122  	r.Get("/status", wrap(api.status))
   123  	r.Get("/receivers", wrap(api.receivers))
   124  
   125  	r.Get("/alerts", wrap(api.listAlerts))
   126  	r.Post("/alerts", wrap(api.addAlerts))
   127  
   128  	r.Get("/silences", wrap(api.listSilences))
   129  	r.Post("/silences", wrap(api.setSilence))
   130  	r.Get("/silence/:sid", wrap(api.getSilence))
   131  	r.Del("/silence/:sid", wrap(api.delSilence))
   132  }
   133  
   134  // Update sets the configuration string to a new value.
   135  func (api *API) Update(cfg *config.Config) {
   136  	api.mtx.Lock()
   137  	defer api.mtx.Unlock()
   138  
   139  	api.config = cfg
   140  	api.route = dispatch.NewRoute(cfg.Route, nil)
   141  }
   142  
   143  type errorType string
   144  
   145  const (
   146  	errorInternal errorType = "server_error"
   147  	errorBadData  errorType = "bad_data"
   148  )
   149  
   150  type apiError struct {
   151  	typ errorType
   152  	err error
   153  }
   154  
   155  func (e *apiError) Error() string {
   156  	return fmt.Sprintf("%s: %s", e.typ, e.err)
   157  }
   158  
   159  func (api *API) receivers(w http.ResponseWriter, req *http.Request) {
   160  	api.mtx.RLock()
   161  	defer api.mtx.RUnlock()
   162  
   163  	receivers := make([]string, 0, len(api.config.Receivers))
   164  	for _, r := range api.config.Receivers {
   165  		receivers = append(receivers, r.Name)
   166  	}
   167  
   168  	api.respond(w, receivers)
   169  }
   170  
   171  func (api *API) status(w http.ResponseWriter, req *http.Request) {
   172  	api.mtx.RLock()
   173  
   174  	status := struct {
   175  		ConfigYAML    string            `json:"configYAML"`
   176  		ConfigJSON    *config.Config    `json:"configJSON"`
   177  		VersionInfo   map[string]string `json:"versionInfo"`
   178  		Uptime        time.Time         `json:"uptime"`
   179  		ClusterStatus *clusterStatus    `json:"clusterStatus"`
   180  	}{
   181  		ConfigYAML: api.config.String(),
   182  		ConfigJSON: api.config,
   183  		VersionInfo: map[string]string{
   184  			"version":   version.Version,
   185  			"revision":  version.Revision,
   186  			"branch":    version.Branch,
   187  			"buildUser": version.BuildUser,
   188  			"buildDate": version.BuildDate,
   189  			"goVersion": version.GoVersion,
   190  		},
   191  		Uptime:        api.uptime,
   192  		ClusterStatus: getClusterStatus(api.peer),
   193  	}
   194  
   195  	api.mtx.RUnlock()
   196  
   197  	api.respond(w, status)
   198  }
   199  
   200  type peerStatus struct {
   201  	Name    string `json:"name"`
   202  	Address string `json:"address"`
   203  }
   204  
   205  type clusterStatus struct {
   206  	Name   string       `json:"name"`
   207  	Status string       `json:"status"`
   208  	Peers  []peerStatus `json:"peers"`
   209  }
   210  
   211  func getClusterStatus(p cluster.ClusterPeer) *clusterStatus {
   212  	if p == nil {
   213  		return nil
   214  	}
   215  	s := &clusterStatus{Name: p.Name(), Status: p.Status()}
   216  
   217  	for _, n := range p.Peers() {
   218  		s.Peers = append(s.Peers, peerStatus{
   219  			Name:    n.Name(),
   220  			Address: n.Address(),
   221  		})
   222  	}
   223  	return s
   224  }
   225  
   226  func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
   227  	var (
   228  		err            error
   229  		receiverFilter *regexp.Regexp
   230  		// Initialize result slice to prevent api returning `null` when there
   231  		// are no alerts present
   232  		res      = []*Alert{}
   233  		matchers = []*labels.Matcher{}
   234  		ctx      = r.Context()
   235  
   236  		showActive, showInhibited     bool
   237  		showSilenced, showUnprocessed bool
   238  	)
   239  
   240  	getBoolParam := func(name string) (bool, error) {
   241  		v := r.FormValue(name)
   242  		if v == "" {
   243  			return true, nil
   244  		}
   245  		if v == "false" {
   246  			return false, nil
   247  		}
   248  		if v != "true" {
   249  			err := fmt.Errorf("parameter %q can either be 'true' or 'false', not %q", name, v)
   250  			api.respondError(w, apiError{
   251  				typ: errorBadData,
   252  				err: err,
   253  			}, nil)
   254  			return false, err
   255  		}
   256  		return true, nil
   257  	}
   258  
   259  	if filter := r.FormValue("filter"); filter != "" {
   260  		matchers, err = labels.ParseMatchers(filter)
   261  		if err != nil {
   262  			api.respondError(w, apiError{
   263  				typ: errorBadData,
   264  				err: err,
   265  			}, nil)
   266  			return
   267  		}
   268  	}
   269  
   270  	showActive, err = getBoolParam("active")
   271  	if err != nil {
   272  		return
   273  	}
   274  
   275  	showSilenced, err = getBoolParam("silenced")
   276  	if err != nil {
   277  		return
   278  	}
   279  
   280  	showInhibited, err = getBoolParam("inhibited")
   281  	if err != nil {
   282  		return
   283  	}
   284  
   285  	showUnprocessed, err = getBoolParam("unprocessed")
   286  	if err != nil {
   287  		return
   288  	}
   289  
   290  	if receiverParam := r.FormValue("receiver"); receiverParam != "" {
   291  		receiverFilter, err = regexp.Compile("^(?:" + receiverParam + ")$")
   292  		if err != nil {
   293  			api.respondError(w, apiError{
   294  				typ: errorBadData,
   295  				err: fmt.Errorf(
   296  					"failed to parse receiver param: %s",
   297  					receiverParam,
   298  				),
   299  			}, nil)
   300  			return
   301  		}
   302  	}
   303  
   304  	alerts := api.alerts.GetPending()
   305  	defer alerts.Close()
   306  
   307  	api.mtx.RLock()
   308  	for a := range alerts.Next() {
   309  		if err = alerts.Err(); err != nil {
   310  			break
   311  		}
   312  		if err = ctx.Err(); err != nil {
   313  			break
   314  		}
   315  
   316  		routes := api.route.Match(a.Labels)
   317  		receivers := make([]string, 0, len(routes))
   318  		for _, r := range routes {
   319  			receivers = append(receivers, r.RouteOpts.Receiver)
   320  		}
   321  
   322  		if receiverFilter != nil && !receiversMatchFilter(receivers, receiverFilter) {
   323  			continue
   324  		}
   325  
   326  		if !alertMatchesFilterLabels(&a.Alert, matchers) {
   327  			continue
   328  		}
   329  
   330  		// Continue if the alert is resolved.
   331  		if !a.Alert.EndsAt.IsZero() && a.Alert.EndsAt.Before(time.Now()) {
   332  			continue
   333  		}
   334  
   335  		status := api.getAlertStatus(a.Fingerprint())
   336  
   337  		if !showActive && status.State == types.AlertStateActive {
   338  			continue
   339  		}
   340  
   341  		if !showUnprocessed && status.State == types.AlertStateUnprocessed {
   342  			continue
   343  		}
   344  
   345  		if !showSilenced && len(status.SilencedBy) != 0 {
   346  			continue
   347  		}
   348  
   349  		if !showInhibited && len(status.InhibitedBy) != 0 {
   350  			continue
   351  		}
   352  
   353  		alert := &Alert{
   354  			Alert:       &a.Alert,
   355  			Status:      status,
   356  			Receivers:   receivers,
   357  			Fingerprint: a.Fingerprint().String(),
   358  		}
   359  
   360  		res = append(res, alert)
   361  	}
   362  	api.mtx.RUnlock()
   363  
   364  	if err != nil {
   365  		api.respondError(w, apiError{
   366  			typ: errorInternal,
   367  			err: err,
   368  		}, nil)
   369  		return
   370  	}
   371  	sort.Slice(res, func(i, j int) bool {
   372  		return res[i].Fingerprint < res[j].Fingerprint
   373  	})
   374  	api.respond(w, res)
   375  }
   376  
   377  func receiversMatchFilter(receivers []string, filter *regexp.Regexp) bool {
   378  	for _, r := range receivers {
   379  		if filter.MatchString(r) {
   380  			return true
   381  		}
   382  	}
   383  
   384  	return false
   385  }
   386  
   387  func alertMatchesFilterLabels(a *model.Alert, matchers []*labels.Matcher) bool {
   388  	sms := make(map[string]string)
   389  	for name, value := range a.Labels {
   390  		sms[string(name)] = string(value)
   391  	}
   392  	return matchFilterLabels(matchers, sms)
   393  }
   394  
   395  func (api *API) addAlerts(w http.ResponseWriter, r *http.Request) {
   396  	var alerts []*types.Alert
   397  	if err := api.receive(r, &alerts); err != nil {
   398  		api.respondError(w, apiError{
   399  			typ: errorBadData,
   400  			err: err,
   401  		}, nil)
   402  		return
   403  	}
   404  
   405  	api.insertAlerts(w, r, alerts...)
   406  }
   407  
   408  func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...*types.Alert) {
   409  	now := time.Now()
   410  
   411  	api.mtx.RLock()
   412  	resolveTimeout := time.Duration(api.config.Global.ResolveTimeout)
   413  	api.mtx.RUnlock()
   414  
   415  	for _, alert := range alerts {
   416  		alert.UpdatedAt = now
   417  
   418  		// Ensure StartsAt is set.
   419  		if alert.StartsAt.IsZero() {
   420  			if alert.EndsAt.IsZero() {
   421  				alert.StartsAt = now
   422  			} else {
   423  				alert.StartsAt = alert.EndsAt
   424  			}
   425  		}
   426  		// If no end time is defined, set a timeout after which an alert
   427  		// is marked resolved if it is not updated.
   428  		if alert.EndsAt.IsZero() {
   429  			alert.Timeout = true
   430  			alert.EndsAt = now.Add(resolveTimeout)
   431  		}
   432  		if alert.EndsAt.After(time.Now()) {
   433  			api.m.Firing().Inc()
   434  		} else {
   435  			api.m.Resolved().Inc()
   436  		}
   437  	}
   438  
   439  	// Make a best effort to insert all alerts that are valid.
   440  	var (
   441  		validAlerts    = make([]*types.Alert, 0, len(alerts))
   442  		validationErrs = &types.MultiError{}
   443  	)
   444  	for _, a := range alerts {
   445  		removeEmptyLabels(a.Labels)
   446  
   447  		if err := a.Validate(); err != nil {
   448  			validationErrs.Add(err)
   449  			api.m.Invalid().Inc()
   450  			continue
   451  		}
   452  		validAlerts = append(validAlerts, a)
   453  	}
   454  	if err := api.alerts.Put(validAlerts...); err != nil {
   455  		api.respondError(w, apiError{
   456  			typ: errorInternal,
   457  			err: err,
   458  		}, nil)
   459  		return
   460  	}
   461  
   462  	if validationErrs.Len() > 0 {
   463  		api.respondError(w, apiError{
   464  			typ: errorBadData,
   465  			err: validationErrs,
   466  		}, nil)
   467  		return
   468  	}
   469  
   470  	api.respond(w, nil)
   471  }
   472  
   473  func removeEmptyLabels(ls model.LabelSet) {
   474  	for k, v := range ls {
   475  		if string(v) == "" {
   476  			delete(ls, k)
   477  		}
   478  	}
   479  }
   480  
   481  func (api *API) setSilence(w http.ResponseWriter, r *http.Request) {
   482  	var sil types.Silence
   483  	if err := api.receive(r, &sil); err != nil {
   484  		api.respondError(w, apiError{
   485  			typ: errorBadData,
   486  			err: err,
   487  		}, nil)
   488  		return
   489  	}
   490  
   491  	// This is an API only validation, it cannot be done internally
   492  	// because the expired silence is semantically important.
   493  	// But one should not be able to create expired silences, that
   494  	// won't have any use.
   495  	if sil.Expired() {
   496  		api.respondError(w, apiError{
   497  			typ: errorBadData,
   498  			err: errors.New("start time must not be equal to end time"),
   499  		}, nil)
   500  		return
   501  	}
   502  
   503  	if sil.EndsAt.Before(time.Now()) {
   504  		api.respondError(w, apiError{
   505  			typ: errorBadData,
   506  			err: errors.New("end time can't be in the past"),
   507  		}, nil)
   508  		return
   509  	}
   510  
   511  	psil, err := silenceToProto(&sil)
   512  	if err != nil {
   513  		api.respondError(w, apiError{
   514  			typ: errorBadData,
   515  			err: err,
   516  		}, nil)
   517  		return
   518  	}
   519  
   520  	sid, err := api.silences.Set(psil)
   521  	if err != nil {
   522  		api.respondError(w, apiError{
   523  			typ: errorBadData,
   524  			err: err,
   525  		}, nil)
   526  		return
   527  	}
   528  
   529  	api.respond(w, struct {
   530  		SilenceID string `json:"silenceId"`
   531  	}{
   532  		SilenceID: sid,
   533  	})
   534  }
   535  
   536  func (api *API) getSilence(w http.ResponseWriter, r *http.Request) {
   537  	sid := route.Param(r.Context(), "sid")
   538  
   539  	sils, _, err := api.silences.Query(silence.QIDs(sid))
   540  	if err != nil || len(sils) == 0 {
   541  		http.Error(w, fmt.Sprint("Error getting silence: ", err), http.StatusNotFound)
   542  		return
   543  	}
   544  	sil, err := silenceFromProto(sils[0])
   545  	if err != nil {
   546  		api.respondError(w, apiError{
   547  			typ: errorInternal,
   548  			err: err,
   549  		}, nil)
   550  		return
   551  	}
   552  
   553  	api.respond(w, sil)
   554  }
   555  
   556  func (api *API) delSilence(w http.ResponseWriter, r *http.Request) {
   557  	sid := route.Param(r.Context(), "sid")
   558  
   559  	if err := api.silences.Expire(sid); err != nil {
   560  		api.respondError(w, apiError{
   561  			typ: errorBadData,
   562  			err: err,
   563  		}, nil)
   564  		return
   565  	}
   566  	api.respond(w, nil)
   567  }
   568  
   569  func (api *API) listSilences(w http.ResponseWriter, r *http.Request) {
   570  	psils, _, err := api.silences.Query()
   571  	if err != nil {
   572  		api.respondError(w, apiError{
   573  			typ: errorInternal,
   574  			err: err,
   575  		}, nil)
   576  		return
   577  	}
   578  
   579  	matchers := []*labels.Matcher{}
   580  	if filter := r.FormValue("filter"); filter != "" {
   581  		matchers, err = labels.ParseMatchers(filter)
   582  		if err != nil {
   583  			api.respondError(w, apiError{
   584  				typ: errorBadData,
   585  				err: err,
   586  			}, nil)
   587  			return
   588  		}
   589  	}
   590  
   591  	sils := []*types.Silence{}
   592  	for _, ps := range psils {
   593  		s, err := silenceFromProto(ps)
   594  		if err != nil {
   595  			api.respondError(w, apiError{
   596  				typ: errorInternal,
   597  				err: err,
   598  			}, nil)
   599  			return
   600  		}
   601  
   602  		if !silenceMatchesFilterLabels(s, matchers) {
   603  			continue
   604  		}
   605  		sils = append(sils, s)
   606  	}
   607  
   608  	var active, pending, expired []*types.Silence
   609  
   610  	for _, s := range sils {
   611  		switch s.Status.State {
   612  		case types.SilenceStateActive:
   613  			active = append(active, s)
   614  		case types.SilenceStatePending:
   615  			pending = append(pending, s)
   616  		case types.SilenceStateExpired:
   617  			expired = append(expired, s)
   618  		}
   619  	}
   620  
   621  	sort.Slice(active, func(i, j int) bool {
   622  		return active[i].EndsAt.Before(active[j].EndsAt)
   623  	})
   624  	sort.Slice(pending, func(i, j int) bool {
   625  		return pending[i].StartsAt.Before(pending[j].EndsAt)
   626  	})
   627  	sort.Slice(expired, func(i, j int) bool {
   628  		return expired[i].EndsAt.After(expired[j].EndsAt)
   629  	})
   630  
   631  	// Initialize silences explicitly to an empty list (instead of nil)
   632  	// So that it does not get converted to "null" in JSON.
   633  	silences := []*types.Silence{}
   634  	silences = append(silences, active...)
   635  	silences = append(silences, pending...)
   636  	silences = append(silences, expired...)
   637  
   638  	api.respond(w, silences)
   639  }
   640  
   641  func silenceMatchesFilterLabels(s *types.Silence, matchers []*labels.Matcher) bool {
   642  	sms := make(map[string]string)
   643  	for _, m := range s.Matchers {
   644  		sms[m.Name] = m.Value
   645  	}
   646  
   647  	return matchFilterLabels(matchers, sms)
   648  }
   649  
   650  func matchFilterLabels(matchers []*labels.Matcher, sms map[string]string) bool {
   651  	for _, m := range matchers {
   652  		v, prs := sms[m.Name]
   653  		switch m.Type {
   654  		case labels.MatchNotRegexp, labels.MatchNotEqual:
   655  			if string(m.Value) == "" && prs {
   656  				continue
   657  			}
   658  			if !m.Matches(string(v)) {
   659  				return false
   660  			}
   661  		default:
   662  			if string(m.Value) == "" && !prs {
   663  				continue
   664  			}
   665  			if !m.Matches(string(v)) {
   666  				return false
   667  			}
   668  		}
   669  	}
   670  
   671  	return true
   672  }
   673  
   674  func silenceToProto(s *types.Silence) (*silencepb.Silence, error) {
   675  	sil := &silencepb.Silence{
   676  		Id:        s.ID,
   677  		StartsAt:  s.StartsAt,
   678  		EndsAt:    s.EndsAt,
   679  		UpdatedAt: s.UpdatedAt,
   680  		Comment:   s.Comment,
   681  		CreatedBy: s.CreatedBy,
   682  	}
   683  	for _, m := range s.Matchers {
   684  		matcher := &silencepb.Matcher{
   685  			Name:    m.Name,
   686  			Pattern: m.Value,
   687  		}
   688  		switch m.Type {
   689  		case labels.MatchEqual:
   690  			matcher.Type = silencepb.Matcher_EQUAL
   691  		case labels.MatchNotEqual:
   692  			matcher.Type = silencepb.Matcher_NOT_EQUAL
   693  		case labels.MatchRegexp:
   694  			matcher.Type = silencepb.Matcher_REGEXP
   695  		case labels.MatchNotRegexp:
   696  			matcher.Type = silencepb.Matcher_NOT_REGEXP
   697  		}
   698  		sil.Matchers = append(sil.Matchers, matcher)
   699  	}
   700  	return sil, nil
   701  }
   702  
   703  func silenceFromProto(s *silencepb.Silence) (*types.Silence, error) {
   704  	sil := &types.Silence{
   705  		ID:        s.Id,
   706  		StartsAt:  s.StartsAt,
   707  		EndsAt:    s.EndsAt,
   708  		UpdatedAt: s.UpdatedAt,
   709  		Status: types.SilenceStatus{
   710  			State: types.CalcSilenceState(s.StartsAt, s.EndsAt),
   711  		},
   712  		Comment:   s.Comment,
   713  		CreatedBy: s.CreatedBy,
   714  	}
   715  	for _, m := range s.Matchers {
   716  		var t labels.MatchType
   717  		switch m.Type {
   718  		case silencepb.Matcher_EQUAL:
   719  			t = labels.MatchEqual
   720  		case silencepb.Matcher_NOT_EQUAL:
   721  			t = labels.MatchNotEqual
   722  		case silencepb.Matcher_REGEXP:
   723  			t = labels.MatchRegexp
   724  		case silencepb.Matcher_NOT_REGEXP:
   725  			t = labels.MatchNotRegexp
   726  		}
   727  		matcher, err := labels.NewMatcher(t, m.Name, m.Pattern)
   728  		if err != nil {
   729  			return nil, err
   730  		}
   731  
   732  		sil.Matchers = append(sil.Matchers, matcher)
   733  	}
   734  
   735  	return sil, nil
   736  }
   737  
   738  type status string
   739  
   740  const (
   741  	statusSuccess status = "success"
   742  	statusError   status = "error"
   743  )
   744  
   745  type response struct {
   746  	Status    status      `json:"status"`
   747  	Data      interface{} `json:"data,omitempty"`
   748  	ErrorType errorType   `json:"errorType,omitempty"`
   749  	Error     string      `json:"error,omitempty"`
   750  }
   751  
   752  func (api *API) respond(w http.ResponseWriter, data interface{}) {
   753  	w.Header().Set("Content-Type", "application/json")
   754  	w.WriteHeader(200)
   755  
   756  	b, err := json.Marshal(&response{
   757  		Status: statusSuccess,
   758  		Data:   data,
   759  	})
   760  	if err != nil {
   761  		level.Error(api.logger).Log("msg", "Error marshaling JSON", "err", err)
   762  		return
   763  	}
   764  
   765  	if _, err := w.Write(b); err != nil {
   766  		level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err)
   767  	}
   768  }
   769  
   770  func (api *API) respondError(w http.ResponseWriter, apiErr apiError, data interface{}) {
   771  	w.Header().Set("Content-Type", "application/json")
   772  
   773  	switch apiErr.typ {
   774  	case errorBadData:
   775  		w.WriteHeader(http.StatusBadRequest)
   776  	case errorInternal:
   777  		w.WriteHeader(http.StatusInternalServerError)
   778  	default:
   779  		panic(fmt.Sprintf("unknown error type %q", apiErr.Error()))
   780  	}
   781  
   782  	b, err := json.Marshal(&response{
   783  		Status:    statusError,
   784  		ErrorType: apiErr.typ,
   785  		Error:     apiErr.err.Error(),
   786  		Data:      data,
   787  	})
   788  	if err != nil {
   789  		return
   790  	}
   791  	level.Error(api.logger).Log("msg", "API error", "err", apiErr.Error())
   792  
   793  	if _, err := w.Write(b); err != nil {
   794  		level.Error(api.logger).Log("msg", "failed to write data to connection", "err", err)
   795  	}
   796  }
   797  
   798  func (api *API) receive(r *http.Request, v interface{}) error {
   799  	dec := json.NewDecoder(r.Body)
   800  	defer r.Body.Close()
   801  
   802  	err := dec.Decode(v)
   803  	if err != nil {
   804  		level.Debug(api.logger).Log("msg", "Decoding request failed", "err", err)
   805  		return err
   806  	}
   807  	return nil
   808  }
   809  

View as plain text