mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Inhouse alerting api (#33129)
* init
* autogens AM route
* POST dashboards/db spec
* POST alert-notifications spec
* fix description
* re inits vendor, updates grafana to master
* go mod updates
* alerting routes
* renames to receivers
* prometheus endpoints
* align config endpoint with cortex, include templates
* Change grafana receiver type
* Update receivers.go
* rename struct to stop swagger thrashing
* add rules API
* index html
* standalone swagger ui html page
* Update README.md
* Expose GrafanaManagedAlert properties
* Some fixes
- /api/v1/rules/{Namespace} should return a map
- update ExtendedUpsertAlertDefinitionCommand properties
* am alerts routes
* rename prom swagger section for clarity, remove example endpoints
* Add missing json and yaml tags
* folder perms
* make folders POST again
* fix grafana receiver type
* rename fodler->namespace for perms
* make ruler json again
* PR fixes
* silences
* fix Ok -> Ack
* Add id to POST /api/v1/silences (#9)
Signed-off-by: Ganesh Vernekar <cs15btech11018@iith.ac.in>
* Add POST /api/v1/alerts (#10)
Signed-off-by: Ganesh Vernekar <cs15btech11018@iith.ac.in>
* fix silences
* Add testing endpoints
* removes grpc replace directives
* [wip] starts validation
* pkg cleanup
* go mod tidy
* ignores vendor dir
* Change response type for Cortex/Loki alerts
* receiver unmarshaling tests
* ability to split routes between AM & Grafana
* api marshaling & validation
* begins work on routing lib
* [hack] ignores embedded field in generation
* path specific datasource for alerting
* align endpoint names with cloud
* single route per Alerting config
* removes unused routing pkg
* regens spec
* adds datasource param to ruler/prom route paths
* Modifications for supporting migration
* Apply suggestions from code review
* hack for cleaning circular refs in swagger definition
* generates files
* minor fixes for prom endpoints
* decorate prom apis with required: true where applicable
* Revert "generates files"
This reverts commit ef7e975584
.
* removes server autogen
* Update imported structs from ngalert
* Fix listing rules response
* Update github.com/prometheus/common dependency
* Update get silence response
* Update get silences response
* adds ruler validation & backend switching
* Fix GET /alertmanager/{DatasourceId}/config/api/v1/alerts response
* Distinct gettable and postable grafana receivers
* Remove permissions routes
* Latest JSON specs
* Fix testing routes
* inline yaml annotation on apirulenode
* yaml test & yamlv3 + comments
* Fix yaml annotations for embedded type
* Rename DatasourceId path parameter
* Implement Backend.String()
* backend zero value is a real backend
* exports DiscoveryBase
* Fix GO initialisms
* Silences: Use PostableSilence as the base struct for creating silences
* Use type alias instead of struct embedding
* More fixes to alertmanager silencing routes
* post and spec JSONs
* Split rule config to postable/gettable
* Fix empty POST /silences payload
Recreating the generated JSON specs fixes the issue
without further modifications
* better yaml unmarshaling for nested yaml docs in cortex-am configs
* regens spec
* re-adds config.receivers
* omitempty to align with prometheus API behavior
* Prefix routes with /api
* Update Alertmanager models
* Make adjustments to follow the Alertmanager API
* ruler: add for and annotations to grafana alert (#45)
* Modify testing API routes
* Fix grafana rule for field type
* Move PostableUserConfig validation to this library
* Fix PostableUserConfig YAML encoding/decoding
* Use common fields for grafana and lotex rules
* Add namespace id in GettableGrafanaRule
* Apply suggestions from code review
* fixup
* more changes
* Apply suggestions from code review
* aligns structure pre merge
* fix new imports & tests
* updates tooling readme
* goimports
* lint
* more linting!!
* revive lint
Co-authored-by: Sofia Papagiannaki <papagian@gmail.com>
Co-authored-by: Domas <domasx2@gmail.com>
Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
Co-authored-by: Ganesh Vernekar <15064823+codesome@users.noreply.github.com>
Co-authored-by: gotjosh <josue@grafana.com>
Co-authored-by: David Parrott <stomp.box.yo@gmail.com>
Co-authored-by: Kyle Brandt <kyle@grafana.com>
This commit is contained in:
parent
6238a7c4c4
commit
e37a780e14
1
go.mod
1
go.mod
@ -68,6 +68,7 @@ require (
|
||||
github.com/prometheus/client_golang v1.10.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/common v0.20.0
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210217141258-a6be548dbc17
|
||||
github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/russellhaering/goxmldsig v1.1.0
|
||||
|
1
go.sum
1
go.sum
@ -1044,6 +1044,7 @@ github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E
|
||||
github.com/jung-kurt/gofpdf v1.16.2 h1:jgbatWHfRlPYiK85qgevsZTHviWXKwB1TTiKdz5PtRc=
|
||||
github.com/jung-kurt/gofpdf v1.16.2/go.mod h1:1hl7y57EsiPAkLbOwzpzqgx1A30nQCk/YmFV8S2vmK0=
|
||||
github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
|
@ -7,12 +7,12 @@ import (
|
||||
|
||||
"github.com/go-macaron/binding"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
|
@ -4,10 +4,10 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
|
@ -10,10 +10,10 @@ import (
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||
)
|
||||
|
||||
|
@ -7,11 +7,11 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
coreapi "github.com/grafana/grafana/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/prometheus/common/model"
|
||||
|
@ -6,11 +6,11 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
@ -3,10 +3,10 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
)
|
||||
|
||||
// ForkedRuler will validate and proxy requests to the correct backend type depending on the datasource.
|
||||
|
@ -3,10 +3,10 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
)
|
||||
|
||||
type ForkedAMSvc struct {
|
||||
|
@ -3,10 +3,10 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
)
|
||||
|
||||
type ForkedPromSvc struct {
|
||||
|
@ -9,11 +9,11 @@ package api
|
||||
import (
|
||||
"github.com/go-macaron/binding"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
)
|
||||
|
||||
type AlertmanagerApiService interface {
|
||||
|
@ -9,11 +9,11 @@ package api
|
||||
import (
|
||||
"github.com/go-macaron/binding"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
)
|
||||
|
||||
type RulerApiService interface {
|
||||
|
@ -9,11 +9,11 @@ package api
|
||||
import (
|
||||
"github.com/go-macaron/binding"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
)
|
||||
|
||||
type TestingApiService interface {
|
||||
|
@ -6,12 +6,12 @@ import (
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/expr/translate"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
|
@ -6,10 +6,10 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
@ -4,10 +4,10 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
)
|
||||
|
||||
type promEndpoints struct {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
|
14
pkg/services/ngalert/api/tooling/Makefile
Normal file
14
pkg/services/ngalert/api/tooling/Makefile
Normal file
@ -0,0 +1,14 @@
|
||||
.DEFAULT_GOAL := openapi
|
||||
|
||||
API_DIR = pkg/api
|
||||
GO_PKG_FILES = $(shell find $(API_DIR) -name *.go -print)
|
||||
|
||||
spec.json: $(GO_PKG_FILES)
|
||||
swagger generate spec -m -w $(API_DIR) -o $@
|
||||
|
||||
post.json: spec.json
|
||||
go run cmd/clean-swagger/main.go -if $(<) -of $@
|
||||
|
||||
.PHONY: openapi
|
||||
openapi: post.json
|
||||
docker run --rm -p 80:8080 -v $$(pwd):/tmp -e SWAGGER_FILE=/tmp/$(<) swaggerapi/swagger-editor
|
13
pkg/services/ngalert/api/tooling/README.md
Normal file
13
pkg/services/ngalert/api/tooling/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
## What
|
||||
|
||||
[view api](http://localhost)
|
||||
|
||||
This aims to define the unified alerting API as code. It generates OpenAPI definitions from go structs
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
`make openapi`
|
||||
|
||||
## Requires
|
||||
- [go-swagger](https://github.com/go-swagger/go-swagger)
|
63
pkg/services/ngalert/api/tooling/cmd/clean-swagger/main.go
Normal file
63
pkg/services/ngalert/api/tooling/cmd/clean-swagger/main.go
Normal file
@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const RefKey = "$ref"
|
||||
|
||||
func main() {
|
||||
var input, output string
|
||||
flag.StringVar(&input, "if", "", "input file")
|
||||
flag.StringVar(&output, "of", "", "output file")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if input == "" || output == "" {
|
||||
log.Fatal("no file specified, input", input, ", output", output)
|
||||
}
|
||||
|
||||
//nolint
|
||||
b, err := ioutil.ReadFile(input)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
if err := json.Unmarshal(b, &data); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
definitions, ok := data["definitions"]
|
||||
if !ok {
|
||||
log.Fatal("no definitions")
|
||||
}
|
||||
|
||||
defs := definitions.(map[string]interface{})
|
||||
for k, v := range defs {
|
||||
vMap := v.(map[string]interface{})
|
||||
refKey, ok := vMap[RefKey]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.TrimPrefix(refKey.(string), "#/definitions/") == k {
|
||||
log.Println("removing circular ref key", refKey)
|
||||
delete(vMap, RefKey)
|
||||
}
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(output, out, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
565
pkg/services/ngalert/api/tooling/definitions/alertmanager.go
Normal file
565
pkg/services/ngalert/api/tooling/definitions/alertmanager.go
Normal file
@ -0,0 +1,565 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// swagger:route POST /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RoutePostAlertingConfig
|
||||
//
|
||||
// sets an Alerting config
|
||||
//
|
||||
// Responses:
|
||||
// 201: Ack
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route GET /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RouteGetAlertingConfig
|
||||
//
|
||||
// gets an Alerting config
|
||||
//
|
||||
// Responses:
|
||||
// 200: GettableUserConfig
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route DELETE /api/alertmanager/{Recipient}/config/api/v1/alerts alertmanager RouteDeleteAlertingConfig
|
||||
//
|
||||
// deletes the Alerting config for a tenant
|
||||
//
|
||||
// Responses:
|
||||
// 200: Ack
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route GET /api/alertmanager/{Recipient}/api/v2/alerts alertmanager RouteGetAMAlerts
|
||||
//
|
||||
// get alertmanager alerts
|
||||
//
|
||||
// Responses:
|
||||
// 200: GettableAlerts
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route POST /api/alertmanager/{Recipient}/api/v2/alerts alertmanager RoutePostAMAlerts
|
||||
//
|
||||
// create alertmanager alerts
|
||||
//
|
||||
// Responses:
|
||||
// 200: Ack
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route GET /api/alertmanager/{Recipient}/api/v2/alerts/groups alertmanager RouteGetAMAlertGroups
|
||||
//
|
||||
// get alertmanager alerts
|
||||
//
|
||||
// Responses:
|
||||
// 200: AlertGroups
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route GET /api/alertmanager/{Recipient}/api/v2/silences alertmanager RouteGetSilences
|
||||
//
|
||||
// get silences
|
||||
//
|
||||
// Responses:
|
||||
// 200: GettableSilences
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route POST /api/alertmanager/{Recipient}/api/v2/silences alertmanager RouteCreateSilence
|
||||
//
|
||||
// create silence
|
||||
//
|
||||
// Responses:
|
||||
// 201: GettableSilence
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route GET /api/alertmanager/{Recipient}/api/v2/silence/{SilenceId} alertmanager RouteGetSilence
|
||||
//
|
||||
// get silence
|
||||
//
|
||||
// Responses:
|
||||
// 200: GettableSilence
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route DELETE /api/alertmanager/{Recipient}/api/v2/silence/{SilenceId} alertmanager RouteDeleteSilence
|
||||
//
|
||||
// delete silence
|
||||
//
|
||||
// Responses:
|
||||
// 200: Ack
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:parameters RouteCreateSilence
|
||||
type CreateSilenceParams struct {
|
||||
// in:body
|
||||
Silence PostableSilence
|
||||
}
|
||||
|
||||
// swagger:parameters RouteGetSilence RouteDeleteSilence
|
||||
type GetDeleteSilenceParams struct {
|
||||
// in:path
|
||||
SilenceId string
|
||||
}
|
||||
|
||||
// swagger:parameters RouteGetSilences
|
||||
type GetSilencesParams struct {
|
||||
// in:query
|
||||
Filter []string `json:"filter"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type PostableSilence = amv2.PostableSilence
|
||||
|
||||
// swagger:model
|
||||
type GettableSilences = amv2.GettableSilences
|
||||
|
||||
// swagger:model
|
||||
type GettableSilence = amv2.GettableSilence
|
||||
|
||||
// swagger:model
|
||||
type GettableAlerts = amv2.GettableAlerts
|
||||
|
||||
// swagger:model
|
||||
type GettableAlert = amv2.GettableAlert
|
||||
|
||||
// swagger:model
|
||||
type AlertGroups = amv2.AlertGroups
|
||||
|
||||
// swagger:model
|
||||
type AlertGroup = amv2.AlertGroup
|
||||
|
||||
// swagger:model
|
||||
type Receiver = amv2.Receiver
|
||||
|
||||
// swagger:parameters RouteGetAMAlerts RouteGetAMAlertGroups
|
||||
type AlertsParams struct {
|
||||
|
||||
// Show active alerts
|
||||
// in: query
|
||||
// required: false
|
||||
// default: true
|
||||
Active bool `json:"active"`
|
||||
|
||||
// Show silenced alerts
|
||||
// in: query
|
||||
// required: false
|
||||
// default: true
|
||||
Silenced bool `json:"silenced"`
|
||||
|
||||
// Show inhibited alerts
|
||||
// in: query
|
||||
// required: false
|
||||
// default: true
|
||||
Inhibited bool `json:"inhibited"`
|
||||
|
||||
// A list of matchers to filter alerts by
|
||||
// in: query
|
||||
// required: false
|
||||
Matchers []string `json:"filter"`
|
||||
|
||||
// A regex matching receivers to filter alerts by
|
||||
// in: query
|
||||
// required: false
|
||||
Receivers string `json:"receiver"`
|
||||
}
|
||||
|
||||
// swagger:parameters RoutePostAMAlerts
|
||||
type PostableAlerts struct {
|
||||
// in:body
|
||||
PostableAlerts []amv2.PostableAlert `yaml:"" json:""`
|
||||
}
|
||||
|
||||
// swagger:parameters RoutePostAlertingConfig
|
||||
type BodyAlertingConfig struct {
|
||||
// in:body
|
||||
Body PostableUserConfig
|
||||
}
|
||||
|
||||
// alertmanager routes
|
||||
// swagger:parameters RoutePostAlertingConfig RouteGetAlertingConfig RouteDeleteAlertingConfig RouteGetAMAlerts RoutePostAMAlerts RouteGetAMAlertGroups RouteGetSilences RouteCreateSilence RouteGetSilence RouteDeleteSilence RoutePostAlertingConfig
|
||||
// ruler routes
|
||||
// swagger:parameters RouteGetRulesConfig RoutePostNameRulesConfig RouteGetNamespaceRulesConfig RouteDeleteNamespaceRulesConfig RouteGetRulegGroupConfig RouteDeleteRuleGroupConfig
|
||||
// prom routes
|
||||
// swagger:parameters RouteGetRuleStatuses RouteGetAlertStatuses
|
||||
// testing routes
|
||||
// swagger:parameters RouteTestReceiverConfig RouteTestRuleConfig
|
||||
type DatasourceReference struct {
|
||||
// Recipient should be "grafana" for requests to be handled by grafana
|
||||
// and the numeric datasource id for requests to be forwarded to a datasource
|
||||
// in:path
|
||||
Recipient string
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type PostableUserConfig struct {
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
AlertmanagerConfig PostableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
||||
func (c *PostableUserConfig) UnmarshalJSON(b []byte) error {
|
||||
type plain PostableUserConfig
|
||||
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.validate()
|
||||
}
|
||||
|
||||
func (c *PostableUserConfig) validate() error {
|
||||
// Taken from https://github.com/prometheus/alertmanager/blob/master/config/config.go#L170-L191
|
||||
// Check if we have a root route. We cannot check for it in the
|
||||
// UnmarshalYAML method because it won't be called if the input is empty
|
||||
// (e.g. the config file is empty or only contains whitespace).
|
||||
if c.AlertmanagerConfig.Route == nil {
|
||||
return fmt.Errorf("no route provided in config")
|
||||
}
|
||||
|
||||
// Check if continue in root route.
|
||||
if c.AlertmanagerConfig.Route.Continue {
|
||||
return fmt.Errorf("cannot have continue in root route")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML implements yaml.Marshaller.
|
||||
func (c *PostableUserConfig) MarshalYAML() (interface{}, error) {
|
||||
yml, err := yaml.Marshal(c.AlertmanagerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// cortex/loki actually pass the AM config as a string.
|
||||
cortexPostableUserConfig := struct {
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
AlertmanagerConfig string `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}{
|
||||
TemplateFiles: c.TemplateFiles,
|
||||
AlertmanagerConfig: string(yml),
|
||||
}
|
||||
return cortexPostableUserConfig, nil
|
||||
}
|
||||
|
||||
func (c *PostableUserConfig) UnmarshalYAML(value *yaml.Node) error {
|
||||
// cortex/loki actually pass the AM config as a string.
|
||||
type cortexPostableUserConfig struct {
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
AlertmanagerConfig string `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
||||
var tmp cortexPostableUserConfig
|
||||
|
||||
if err := value.Decode(&tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal([]byte(tmp.AlertmanagerConfig), &c.AlertmanagerConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.TemplateFiles = tmp.TemplateFiles
|
||||
return nil
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type GettableUserConfig struct {
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
AlertmanagerConfig GettableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
||||
func (c *GettableUserConfig) UnmarshalYAML(value *yaml.Node) error {
|
||||
// cortex/loki actually pass the AM config as a string.
|
||||
type cortexGettableUserConfig struct {
|
||||
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
|
||||
AlertmanagerConfig string `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
||||
var tmp cortexGettableUserConfig
|
||||
|
||||
if err := value.Decode(&tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal([]byte(tmp.AlertmanagerConfig), &c.AlertmanagerConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.TemplateFiles = tmp.TemplateFiles
|
||||
return nil
|
||||
}
|
||||
|
||||
type GettableApiAlertingConfig struct {
|
||||
Config `yaml:",inline"`
|
||||
|
||||
// Override with our superset receiver type
|
||||
Receivers []*GettableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
|
||||
}
|
||||
|
||||
func (c *GettableApiAlertingConfig) UnmarshalJSON(b []byte) error {
|
||||
type plain GettableApiAlertingConfig
|
||||
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.validate()
|
||||
}
|
||||
|
||||
// validate ensures that the two routing trees use the correct receiver types.
|
||||
func (c *GettableApiAlertingConfig) validate() error {
|
||||
receivers := make(map[string]struct{}, len(c.Receivers))
|
||||
|
||||
var hasGrafReceivers, hasAMReceivers bool
|
||||
for _, r := range c.Receivers {
|
||||
receivers[r.Name] = struct{}{}
|
||||
switch r.Type() {
|
||||
case GrafanaReceiverType:
|
||||
hasGrafReceivers = true
|
||||
case AlertmanagerReceiverType:
|
||||
hasAMReceivers = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasGrafReceivers && hasAMReceivers {
|
||||
return fmt.Errorf("cannot mix Alertmanager & Grafana receiver types")
|
||||
}
|
||||
|
||||
for _, receiver := range AllReceivers(c.Route) {
|
||||
_, ok := receivers[receiver]
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected receiver (%s) is undefined", receiver)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type requires validate has been called and just checks the first receiver type
|
||||
func (c *GettableApiAlertingConfig) Type() (backend Backend) {
|
||||
for _, r := range c.Receivers {
|
||||
switch r.Type() {
|
||||
case GrafanaReceiverType:
|
||||
return GrafanaBackend
|
||||
case AlertmanagerReceiverType:
|
||||
return AlertmanagerBackend
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Config is the top-level configuration for Alertmanager's config files.
|
||||
type Config struct {
|
||||
Global *config.GlobalConfig `yaml:"global,omitempty" json:"global,omitempty"`
|
||||
Route *config.Route `yaml:"route,omitempty" json:"route,omitempty"`
|
||||
InhibitRules []*config.InhibitRule `yaml:"inhibit_rules,omitempty" json:"inhibit_rules,omitempty"`
|
||||
Receivers []*config.Receiver `yaml:"-" json:"receivers,omitempty"`
|
||||
Templates []string `yaml:"templates" json:"templates"`
|
||||
}
|
||||
|
||||
type PostableApiAlertingConfig struct {
|
||||
Config `yaml:",inline"`
|
||||
|
||||
// Override with our superset receiver type
|
||||
Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
|
||||
}
|
||||
|
||||
func (c *PostableApiAlertingConfig) UnmarshalJSON(b []byte) error {
|
||||
type plain PostableApiAlertingConfig
|
||||
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.validate()
|
||||
}
|
||||
|
||||
// validate ensures that the two routing trees use the correct receiver types.
|
||||
func (c *PostableApiAlertingConfig) validate() error {
|
||||
receivers := make(map[string]struct{}, len(c.Receivers))
|
||||
|
||||
var hasGrafReceivers, hasAMReceivers bool
|
||||
for _, r := range c.Receivers {
|
||||
receivers[r.Name] = struct{}{}
|
||||
switch r.Type() {
|
||||
case GrafanaReceiverType:
|
||||
hasGrafReceivers = true
|
||||
case AlertmanagerReceiverType:
|
||||
hasAMReceivers = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasGrafReceivers && hasAMReceivers {
|
||||
return fmt.Errorf("cannot mix Alertmanager & Grafana receiver types")
|
||||
}
|
||||
|
||||
for _, receiver := range AllReceivers(c.Route) {
|
||||
_, ok := receivers[receiver]
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected receiver (%s) is undefined", receiver)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Type requires validate has been called and just checks the first receiver type
|
||||
func (c *PostableApiAlertingConfig) Type() (backend Backend) {
|
||||
for _, r := range c.Receivers {
|
||||
switch r.Type() {
|
||||
case GrafanaReceiverType:
|
||||
return GrafanaBackend
|
||||
case AlertmanagerReceiverType:
|
||||
return AlertmanagerBackend
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// AllReceivers will recursively walk a routing tree and return a list of all the
|
||||
// referenced receiver names.
|
||||
func AllReceivers(route *config.Route) (res []string) {
|
||||
res = append(res, route.Receiver)
|
||||
for _, subRoute := range route.Routes {
|
||||
res = append(res, AllReceivers(subRoute)...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type GettableGrafanaReceiver dtos.AlertNotification
|
||||
type PostableGrafanaReceiver models.CreateAlertNotificationCommand
|
||||
|
||||
type ReceiverType int
|
||||
|
||||
const (
|
||||
GrafanaReceiverType ReceiverType = iota
|
||||
AlertmanagerReceiverType
|
||||
)
|
||||
|
||||
type GettableApiReceiver struct {
|
||||
config.Receiver `yaml:",inline"`
|
||||
GettableGrafanaReceivers `yaml:",inline"`
|
||||
}
|
||||
|
||||
func (r *GettableApiReceiver) UnmarshalJSON(b []byte) error {
|
||||
type plain GettableApiReceiver
|
||||
if err := json.Unmarshal(b, (*plain)(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasGrafanaReceivers := len(r.GettableGrafanaReceivers.GrafanaManagedReceivers) > 0
|
||||
|
||||
if hasGrafanaReceivers {
|
||||
if len(r.EmailConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager EmailConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.PagerdutyConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager PagerdutyConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.SlackConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager SlackConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.WebhookConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager WebhookConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.OpsGenieConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager OpsGenieConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.WechatConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager WechatConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.PushoverConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager PushoverConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.VictorOpsConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager VictorOpsConfigs & Grafana receivers together")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *GettableApiReceiver) Type() ReceiverType {
|
||||
if len(r.GettableGrafanaReceivers.GrafanaManagedReceivers) > 0 {
|
||||
return GrafanaReceiverType
|
||||
}
|
||||
return AlertmanagerReceiverType
|
||||
}
|
||||
|
||||
type PostableApiReceiver struct {
|
||||
config.Receiver `yaml:",inline"`
|
||||
PostableGrafanaReceivers `yaml:",inline"`
|
||||
}
|
||||
|
||||
func (r *PostableApiReceiver) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var grafanaReceivers PostableGrafanaReceivers
|
||||
if err := unmarshal(&grafanaReceivers); err != nil {
|
||||
return err
|
||||
}
|
||||
r.PostableGrafanaReceivers = grafanaReceivers
|
||||
|
||||
var cfg config.Receiver
|
||||
if err := unmarshal(&cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Name = cfg.Name
|
||||
r.EmailConfigs = cfg.EmailConfigs
|
||||
r.PagerdutyConfigs = cfg.PagerdutyConfigs
|
||||
r.SlackConfigs = cfg.SlackConfigs
|
||||
r.WebhookConfigs = cfg.WebhookConfigs
|
||||
r.OpsGenieConfigs = cfg.OpsGenieConfigs
|
||||
r.WechatConfigs = cfg.WechatConfigs
|
||||
r.PushoverConfigs = cfg.PushoverConfigs
|
||||
r.VictorOpsConfigs = cfg.VictorOpsConfigs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostableApiReceiver) UnmarshalJSON(b []byte) error {
|
||||
type plain PostableApiReceiver
|
||||
if err := json.Unmarshal(b, (*plain)(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasGrafanaReceivers := len(r.PostableGrafanaReceivers.GrafanaManagedReceivers) > 0
|
||||
|
||||
if hasGrafanaReceivers {
|
||||
if len(r.EmailConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager EmailConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.PagerdutyConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager PagerdutyConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.SlackConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager SlackConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.WebhookConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager WebhookConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.OpsGenieConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager OpsGenieConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.WechatConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager WechatConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.PushoverConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager PushoverConfigs & Grafana receivers together")
|
||||
}
|
||||
if len(r.VictorOpsConfigs) > 0 {
|
||||
return fmt.Errorf("cannot have both Alertmanager VictorOpsConfigs & Grafana receivers together")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *PostableApiReceiver) Type() ReceiverType {
|
||||
if len(r.PostableGrafanaReceivers.GrafanaManagedReceivers) > 0 {
|
||||
return GrafanaReceiverType
|
||||
}
|
||||
return AlertmanagerReceiverType
|
||||
}
|
||||
|
||||
type GettableGrafanaReceivers struct {
|
||||
GrafanaManagedReceivers []*GettableGrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"`
|
||||
}
|
||||
|
||||
type PostableGrafanaReceivers struct {
|
||||
GrafanaManagedReceivers []*PostableGrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"`
|
||||
}
|
@ -0,0 +1,378 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func Test_ApiReceiver_Marshaling(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
input PostableApiReceiver
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "success AM",
|
||||
input: PostableApiReceiver{
|
||||
Receiver: config.Receiver{
|
||||
Name: "foo",
|
||||
EmailConfigs: []*config.EmailConfig{{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "success GM",
|
||||
input: PostableApiReceiver{
|
||||
Receiver: config.Receiver{
|
||||
Name: "foo",
|
||||
},
|
||||
PostableGrafanaReceivers: PostableGrafanaReceivers{
|
||||
GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "failure mixed",
|
||||
input: PostableApiReceiver{
|
||||
Receiver: config.Receiver{
|
||||
Name: "foo",
|
||||
EmailConfigs: []*config.EmailConfig{{}},
|
||||
},
|
||||
PostableGrafanaReceivers: PostableGrafanaReceivers{
|
||||
GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}},
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
encoded, err := json.Marshal(tc.input)
|
||||
require.Nil(t, err)
|
||||
|
||||
var out PostableApiReceiver
|
||||
err = json.Unmarshal(encoded, &out)
|
||||
|
||||
if tc.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.input, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AllReceivers(t *testing.T) {
|
||||
input := &config.Route{
|
||||
Receiver: "foo",
|
||||
Routes: []*config.Route{
|
||||
{
|
||||
Receiver: "bar",
|
||||
Routes: []*config.Route{
|
||||
{
|
||||
Receiver: "bazz",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Receiver: "buzz",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require.Equal(t, []string{"foo", "bar", "bazz", "buzz"}, AllReceivers(input))
|
||||
}
|
||||
|
||||
func Test_ApiAlertingConfig_Marshaling(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
input PostableApiAlertingConfig
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "success am",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Receiver: "am",
|
||||
Routes: []*config.Route{
|
||||
{
|
||||
Receiver: "am",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Receivers: []*PostableApiReceiver{
|
||||
{
|
||||
Receiver: config.Receiver{
|
||||
Name: "am",
|
||||
EmailConfigs: []*config.EmailConfig{{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "success graf",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Receiver: "graf",
|
||||
Routes: []*config.Route{
|
||||
{
|
||||
Receiver: "graf",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Receivers: []*PostableApiReceiver{
|
||||
{
|
||||
Receiver: config.Receiver{
|
||||
Name: "graf",
|
||||
},
|
||||
PostableGrafanaReceivers: PostableGrafanaReceivers{
|
||||
GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "failure undefined am receiver",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Receiver: "am",
|
||||
Routes: []*config.Route{
|
||||
{
|
||||
Receiver: "unmentioned",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Receivers: []*PostableApiReceiver{
|
||||
{
|
||||
Receiver: config.Receiver{
|
||||
Name: "am",
|
||||
EmailConfigs: []*config.EmailConfig{{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "failure undefined graf receiver",
|
||||
input: PostableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Route: &config.Route{
|
||||
Receiver: "graf",
|
||||
Routes: []*config.Route{
|
||||
{
|
||||
Receiver: "unmentioned",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Receivers: []*PostableApiReceiver{
|
||||
{
|
||||
Receiver: config.Receiver{
|
||||
Name: "graf",
|
||||
},
|
||||
PostableGrafanaReceivers: PostableGrafanaReceivers{
|
||||
GrafanaManagedReceivers: []*PostableGrafanaReceiver{{}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
encoded, err := json.Marshal(tc.input)
|
||||
require.Nil(t, err)
|
||||
|
||||
var out PostableApiAlertingConfig
|
||||
err = json.Unmarshal(encoded, &out)
|
||||
|
||||
if tc.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.input, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PostableApiReceiver_Unmarshaling_YAML(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
input string
|
||||
rtype ReceiverType
|
||||
}{
|
||||
{
|
||||
desc: "grafana receivers",
|
||||
input: `
|
||||
name: grafana_managed
|
||||
grafana_managed_receiver_configs:
|
||||
- uid: alertmanager UID
|
||||
name: an alert manager receiver
|
||||
type: prometheus-alertmanager
|
||||
sendreminder: false
|
||||
disableresolvemessage: false
|
||||
frequency: 5m
|
||||
isdefault: false
|
||||
settings: {}
|
||||
securesettings:
|
||||
basicAuthPassword: <basicAuthPassword>
|
||||
- uid: dingding UID
|
||||
name: a dingding receiver
|
||||
type: dingding
|
||||
sendreminder: false
|
||||
disableresolvemessage: false
|
||||
frequency: 5m
|
||||
isdefault: false`,
|
||||
rtype: GrafanaReceiverType,
|
||||
},
|
||||
{
|
||||
desc: "receiver",
|
||||
input: `
|
||||
name: example-email
|
||||
email_configs:
|
||||
- to: 'youraddress@example.org'`,
|
||||
rtype: AlertmanagerReceiverType,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
var r PostableApiReceiver
|
||||
err := yaml.Unmarshal([]byte(tc.input), &r)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, tc.rtype, r.Type())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GettableUserConfigUnmarshaling(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
desc, input string
|
||||
output GettableUserConfig
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "empty",
|
||||
input: ``,
|
||||
output: GettableUserConfig{},
|
||||
},
|
||||
{
|
||||
desc: "empty-ish",
|
||||
input: `
|
||||
template_files: {}
|
||||
alertmanager_config: ""
|
||||
`,
|
||||
output: GettableUserConfig{
|
||||
TemplateFiles: map[string]string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "bad type for template",
|
||||
input: `
|
||||
template_files: abc
|
||||
alertmanager_config: ""
|
||||
`,
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "existing templates",
|
||||
input: `
|
||||
template_files:
|
||||
foo: bar
|
||||
alertmanager_config: ""
|
||||
`,
|
||||
output: GettableUserConfig{
|
||||
TemplateFiles: map[string]string{"foo": "bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "existing templates inline",
|
||||
input: `
|
||||
template_files: {foo: bar}
|
||||
alertmanager_config: ""
|
||||
`,
|
||||
output: GettableUserConfig{
|
||||
TemplateFiles: map[string]string{"foo": "bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "existing am config",
|
||||
input: `
|
||||
template_files: {foo: bar}
|
||||
alertmanager_config: |
|
||||
route:
|
||||
receiver: am
|
||||
continue: false
|
||||
routes:
|
||||
- receiver: am
|
||||
continue: false
|
||||
templates: []
|
||||
receivers:
|
||||
- name: am
|
||||
email_configs:
|
||||
- to: foo
|
||||
from: bar
|
||||
headers:
|
||||
Bazz: buzz
|
||||
text: hi
|
||||
html: there
|
||||
`,
|
||||
output: GettableUserConfig{
|
||||
TemplateFiles: map[string]string{"foo": "bar"},
|
||||
AlertmanagerConfig: GettableApiAlertingConfig{
|
||||
Config: Config{
|
||||
Templates: []string{},
|
||||
Route: &config.Route{
|
||||
Receiver: "am",
|
||||
Routes: []*config.Route{
|
||||
{
|
||||
Receiver: "am",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Receivers: []*GettableApiReceiver{
|
||||
{
|
||||
Receiver: config.Receiver{
|
||||
Name: "am",
|
||||
EmailConfigs: []*config.EmailConfig{{
|
||||
To: "foo",
|
||||
From: "bar",
|
||||
Headers: map[string]string{
|
||||
"Bazz": "buzz",
|
||||
},
|
||||
Text: "hi",
|
||||
HTML: "there",
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
var out GettableUserConfig
|
||||
err := yaml.Unmarshal([]byte(tc.input), &out)
|
||||
if tc.err {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.output, out)
|
||||
})
|
||||
}
|
||||
}
|
51
pkg/services/ngalert/api/tooling/definitions/api.go
Normal file
51
pkg/services/ngalert/api/tooling/definitions/api.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Documentation of the API.
|
||||
//
|
||||
// Schemes: http, https
|
||||
// BasePath: /api/v1
|
||||
// Version: 1.0.0
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Security:
|
||||
// - basic
|
||||
//
|
||||
// SecurityDefinitions:
|
||||
// basic:
|
||||
// type: basic
|
||||
//
|
||||
// swagger:meta
|
||||
|
||||
package api
|
||||
|
||||
// swagger:model
|
||||
type ValidationError struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type Ack struct{}
|
||||
|
||||
type Backend int
|
||||
|
||||
const (
|
||||
GrafanaBackend Backend = iota
|
||||
AlertmanagerBackend
|
||||
LoTexRulerBackend
|
||||
)
|
||||
|
||||
func (b Backend) String() string {
|
||||
switch b {
|
||||
case GrafanaBackend:
|
||||
return "grafana"
|
||||
case AlertmanagerBackend:
|
||||
return "alertmanager"
|
||||
case LoTexRulerBackend:
|
||||
return "lotex"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
323
pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go
Normal file
323
pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go
Normal file
@ -0,0 +1,323 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// swagger:route Get /api/ruler/{Recipient}/api/v1/rules ruler RouteGetRulesConfig
|
||||
//
|
||||
// List rule groups
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 202: NamespaceConfigResponse
|
||||
|
||||
// swagger:route POST /api/ruler/{Recipient}/api/v1/rules/{Namespace} ruler RoutePostNameRulesConfig
|
||||
//
|
||||
// Creates or updates a rule group
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
// - application/yaml
|
||||
//
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
|
||||
// swagger:route Get /api/ruler/{Recipient}/api/v1/rules/{Namespace} ruler RouteGetNamespaceRulesConfig
|
||||
//
|
||||
// Get rule groups by namespace
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 202: NamespaceConfigResponse
|
||||
|
||||
// swagger:route Delete /api/ruler/{Recipient}/api/v1/rules/{Namespace} ruler RouteDeleteNamespaceRulesConfig
|
||||
//
|
||||
// Delete namespace
|
||||
//
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
|
||||
// swagger:route Get /api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname} ruler RouteGetRulegGroupConfig
|
||||
//
|
||||
// Get rule group
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 202: RuleGroupConfigResponse
|
||||
|
||||
// swagger:route Delete /api/ruler/{Recipient}/api/v1/rules/{Namespace}/{Groupname} ruler RouteDeleteRuleGroupConfig
|
||||
//
|
||||
// Delete rule group
|
||||
//
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
|
||||
// swagger:parameters RoutePostNameRulesConfig
|
||||
type NamespaceConfig struct {
|
||||
// in:path
|
||||
Namespace string
|
||||
// in:body
|
||||
Body PostableRuleGroupConfig
|
||||
}
|
||||
|
||||
// swagger:parameters RouteGetNamespaceRulesConfig RouteDeleteNamespaceRulesConfig
|
||||
type PathNamespaceConfig struct {
|
||||
// in: path
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// swagger:parameters RouteGetRulegGroupConfig RouteDeleteRuleGroupConfig
|
||||
type PathRouleGroupConfig struct {
|
||||
// in: path
|
||||
Namespace string
|
||||
// in: path
|
||||
Groupname string
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type RuleGroupConfigResponse struct {
|
||||
GettableRuleGroupConfig
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type NamespaceConfigResponse map[string][]GettableRuleGroupConfig
|
||||
|
||||
// swagger:model
|
||||
type PostableRuleGroupConfig struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Interval model.Duration `yaml:"interval,omitempty" json:"interval,omitempty"`
|
||||
Rules []PostableExtendedRuleNode `yaml:"rules" json:"rules"`
|
||||
}
|
||||
|
||||
func (c *PostableRuleGroupConfig) UnmarshalJSON(b []byte) error {
|
||||
type plain PostableRuleGroupConfig
|
||||
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.validate()
|
||||
}
|
||||
|
||||
// Type requires validate has been called and just checks the first rule type
|
||||
func (c *PostableRuleGroupConfig) Type() (backend Backend) {
|
||||
for _, rule := range c.Rules {
|
||||
switch rule.Type() {
|
||||
case GrafanaManagedRule:
|
||||
return GrafanaBackend
|
||||
case LoTexManagedRule:
|
||||
return LoTexRulerBackend
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *PostableRuleGroupConfig) validate() error {
|
||||
var hasGrafRules, hasLotexRules bool
|
||||
for _, rule := range c.Rules {
|
||||
switch rule.Type() {
|
||||
case GrafanaManagedRule:
|
||||
hasGrafRules = true
|
||||
case LoTexManagedRule:
|
||||
hasLotexRules = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasGrafRules && hasLotexRules {
|
||||
return fmt.Errorf("cannot mix Grafana & Prometheus style rules")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type GettableRuleGroupConfig struct {
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Interval model.Duration `yaml:"interval,omitempty" json:"interval,omitempty"`
|
||||
Rules []GettableExtendedRuleNode `yaml:"rules" json:"rules"`
|
||||
}
|
||||
|
||||
func (c *GettableRuleGroupConfig) UnmarshalJSON(b []byte) error {
|
||||
type plain GettableRuleGroupConfig
|
||||
if err := json.Unmarshal(b, (*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.validate()
|
||||
}
|
||||
|
||||
// Type requires validate has been called and just checks the first rule type
|
||||
func (c *GettableRuleGroupConfig) Type() (backend Backend) {
|
||||
for _, rule := range c.Rules {
|
||||
switch rule.Type() {
|
||||
case GrafanaManagedRule:
|
||||
return GrafanaBackend
|
||||
case LoTexManagedRule:
|
||||
return LoTexRulerBackend
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *GettableRuleGroupConfig) validate() error {
|
||||
var hasGrafRules, hasLotexRules bool
|
||||
for _, rule := range c.Rules {
|
||||
switch rule.Type() {
|
||||
case GrafanaManagedRule:
|
||||
hasGrafRules = true
|
||||
case LoTexManagedRule:
|
||||
hasLotexRules = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasGrafRules && hasLotexRules {
|
||||
return fmt.Errorf("cannot mix Grafana & Prometheus style rules")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ApiRuleNode struct {
|
||||
Record string `yaml:"record,omitempty" json:"record,omitempty"`
|
||||
Alert string `yaml:"alert,omitempty" json:"alert,omitempty"`
|
||||
Expr string `yaml:"expr" json:"expr"`
|
||||
For model.Duration `yaml:"for,omitempty" json:"for,omitempty"`
|
||||
Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||
Annotations map[string]string `yaml:"annotations,omitempty" json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
type RuleType int
|
||||
|
||||
const (
|
||||
GrafanaManagedRule RuleType = iota
|
||||
LoTexManagedRule
|
||||
)
|
||||
|
||||
type PostableExtendedRuleNode struct {
|
||||
// note: this works with yaml v3 but not v2 (the inline tag isn't accepted on pointers in v2)
|
||||
*ApiRuleNode `yaml:",inline"`
|
||||
//GrafanaManagedAlert yaml.Node `yaml:"grafana_alert,omitempty"`
|
||||
GrafanaManagedAlert *PostableGrafanaRule `yaml:"grafana_alert,omitempty" json:"grafana_alert,omitempty"`
|
||||
}
|
||||
|
||||
func (n *PostableExtendedRuleNode) Type() RuleType {
|
||||
if n.GrafanaManagedAlert != nil {
|
||||
return GrafanaManagedRule
|
||||
}
|
||||
|
||||
return LoTexManagedRule
|
||||
}
|
||||
|
||||
func (n *PostableExtendedRuleNode) UnmarshalJSON(b []byte) error {
|
||||
type plain PostableExtendedRuleNode
|
||||
if err := json.Unmarshal(b, (*plain)(n)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.validate()
|
||||
}
|
||||
|
||||
func (n *PostableExtendedRuleNode) validate() error {
|
||||
if n.ApiRuleNode == nil && n.GrafanaManagedAlert == nil {
|
||||
return fmt.Errorf("cannot have empty rule")
|
||||
}
|
||||
|
||||
if n.GrafanaManagedAlert != nil {
|
||||
if n.ApiRuleNode != nil && (n.ApiRuleNode.Expr != "" || n.ApiRuleNode.Record != "") {
|
||||
return fmt.Errorf("cannot have both Prometheus style rules and Grafana rules together")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type GettableExtendedRuleNode struct {
|
||||
// note: this works with yaml v3 but not v2 (the inline tag isn't accepted on pointers in v2)
|
||||
*ApiRuleNode `yaml:",inline"`
|
||||
//GrafanaManagedAlert yaml.Node `yaml:"grafana_alert,omitempty"`
|
||||
GrafanaManagedAlert *GettableGrafanaRule `yaml:"grafana_alert,omitempty" json:"grafana_alert,omitempty"`
|
||||
}
|
||||
|
||||
func (n *GettableExtendedRuleNode) Type() RuleType {
|
||||
if n.GrafanaManagedAlert != nil {
|
||||
return GrafanaManagedRule
|
||||
}
|
||||
return LoTexManagedRule
|
||||
}
|
||||
|
||||
func (n *GettableExtendedRuleNode) UnmarshalJSON(b []byte) error {
|
||||
type plain GettableExtendedRuleNode
|
||||
if err := json.Unmarshal(b, (*plain)(n)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.validate()
|
||||
}
|
||||
|
||||
func (n *GettableExtendedRuleNode) validate() error {
|
||||
if n.ApiRuleNode == nil && n.GrafanaManagedAlert == nil {
|
||||
return fmt.Errorf("cannot have empty rule")
|
||||
}
|
||||
|
||||
if n.GrafanaManagedAlert != nil {
|
||||
if n.ApiRuleNode != nil && (n.ApiRuleNode.Expr != "" || n.ApiRuleNode.Record != "") {
|
||||
return fmt.Errorf("cannot have both Prometheus style rules and Grafana rules together")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// swagger:enum NoDataState
|
||||
type NoDataState string
|
||||
|
||||
const (
|
||||
Alerting NoDataState = "Alerting"
|
||||
NoData NoDataState = "NoData"
|
||||
KeepLastState NoDataState = "KeepLastState"
|
||||
OK NoDataState = "OK"
|
||||
)
|
||||
|
||||
// swagger:enum ExecutionErrorState
|
||||
type ExecutionErrorState string
|
||||
|
||||
const (
|
||||
AlertingErrState ExecutionErrorState = "Alerting"
|
||||
KeepLastStateErrState ExecutionErrorState = "KeepLastState"
|
||||
)
|
||||
|
||||
// swagger:model
|
||||
type PostableGrafanaRule struct {
|
||||
OrgID int64 `json:"-" yaml:"-"`
|
||||
Title string `json:"title" yaml:"title"`
|
||||
Condition string `json:"condition" yaml:"condition"`
|
||||
Data []models.AlertQuery `json:"data" yaml:"data"`
|
||||
UID string `json:"uid" yaml:"uid"`
|
||||
NoDataState NoDataState `json:"no_data_state" yaml:"no_data_state"`
|
||||
ExecErrState ExecutionErrorState `json:"exec_err_state" yaml:"exec_err_state"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type GettableGrafanaRule struct {
|
||||
ID int64 `json:"id" yaml:"id"`
|
||||
OrgID int64 `json:"orgId" yaml:"orgId"`
|
||||
Title string `json:"title" yaml:"title"`
|
||||
Condition string `json:"condition" yaml:"condition"`
|
||||
Data []models.AlertQuery `json:"data" yaml:"data"`
|
||||
Updated time.Time `json:"updated" yaml:"updated"`
|
||||
IntervalSeconds int64 `json:"intervalSeconds" yaml:"intervalSeconds"`
|
||||
Version int64 `json:"version" yaml:"version"`
|
||||
UID string `json:"uid" yaml:"uid"`
|
||||
NamespaceUID string `json:"namespace_uid" yaml:"namespace_uid"`
|
||||
NamespaceID int64 `json:"namespace_id" yaml:"namespace_id"`
|
||||
RuleGroup string `json:"rule_group" yaml:"rule_group"`
|
||||
NoDataState NoDataState `json:"no_data_state" yaml:"no_data_state"`
|
||||
ExecErrState ExecutionErrorState `json:"exec_err_state" yaml:"exec_err_state"`
|
||||
}
|
@ -0,0 +1,238 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func Test_Rule_Marshaling(t *testing.T) {
|
||||
dur, err := model.ParseDuration("1m")
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
input PostableExtendedRuleNode
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "success lotex",
|
||||
input: PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "success grafana",
|
||||
input: PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "failure mixed with alert lotex rule",
|
||||
input: PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{Expr: "<string>"},
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "failure mixed with record lotex rule",
|
||||
input: PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{Record: "<string>"},
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "grafana with for, annotation and label properties",
|
||||
input: PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{
|
||||
For: dur,
|
||||
Annotations: map[string]string{"foo": "bar"},
|
||||
Labels: map[string]string{"label1": "val1"}},
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
encoded, err := json.Marshal(tc.input)
|
||||
require.Nil(t, err)
|
||||
|
||||
var out PostableExtendedRuleNode
|
||||
err = json.Unmarshal(encoded, &out)
|
||||
|
||||
if tc.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.input, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Rule_Group_Marshaling(t *testing.T) {
|
||||
dur, err := model.ParseDuration("1m")
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
input PostableRuleGroupConfig
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "success lotex",
|
||||
input: PostableRuleGroupConfig{
|
||||
Name: "foo",
|
||||
Interval: 0,
|
||||
Rules: []PostableExtendedRuleNode{
|
||||
PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{},
|
||||
},
|
||||
PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "success grafana",
|
||||
input: PostableRuleGroupConfig{
|
||||
Name: "foo",
|
||||
Interval: 0,
|
||||
Rules: []PostableExtendedRuleNode{
|
||||
PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "success grafana with for and annotations",
|
||||
input: PostableRuleGroupConfig{
|
||||
Name: "foo",
|
||||
Interval: 0,
|
||||
Rules: []PostableExtendedRuleNode{
|
||||
PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{
|
||||
For: dur,
|
||||
Annotations: map[string]string{"foo": "bar"},
|
||||
Labels: map[string]string{"label1": "val1"},
|
||||
},
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "failure mixed",
|
||||
input: PostableRuleGroupConfig{
|
||||
Name: "foo",
|
||||
Interval: 0,
|
||||
Rules: []PostableExtendedRuleNode{
|
||||
PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
encoded, err := json.Marshal(tc.input)
|
||||
require.Nil(t, err)
|
||||
|
||||
var out PostableRuleGroupConfig
|
||||
err = json.Unmarshal(encoded, &out)
|
||||
|
||||
if tc.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.input, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Rule_Group_Type(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
input PostableRuleGroupConfig
|
||||
expected Backend
|
||||
}{
|
||||
{
|
||||
desc: "success lotex",
|
||||
input: PostableRuleGroupConfig{
|
||||
Name: "foo",
|
||||
Interval: 0,
|
||||
Rules: []PostableExtendedRuleNode{
|
||||
PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{},
|
||||
},
|
||||
PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: LoTexRulerBackend,
|
||||
},
|
||||
{
|
||||
desc: "success grafana",
|
||||
input: PostableRuleGroupConfig{
|
||||
Name: "foo",
|
||||
Interval: 0,
|
||||
Rules: []PostableExtendedRuleNode{
|
||||
PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: GrafanaBackend,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
bt := tc.input.Type()
|
||||
require.Equal(t, tc.expected, bt)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceMarshalling(t *testing.T) {
|
||||
var data = `copy:
|
||||
- name: loki_alerts
|
||||
rules:
|
||||
- alert: logs_exist
|
||||
expr: rate({cluster="us-central1", job="loki-prod/loki-canary"}[1m]) > 0
|
||||
for: 1m
|
||||
simple_rules:
|
||||
- name: loki_alerts
|
||||
rules:
|
||||
- alert: logs_exist
|
||||
expr: rate({cluster="us-central1", job="loki-prod/loki-canary"}[1m]) > 0
|
||||
for: 1m
|
||||
`
|
||||
|
||||
var res NamespaceConfigResponse
|
||||
|
||||
err := yaml.Unmarshal([]byte(data), &res)
|
||||
require.Nil(t, err)
|
||||
b, err := yaml.Marshal(res)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, data, string(b))
|
||||
}
|
130
pkg/services/ngalert/api/tooling/definitions/prom.go
Normal file
130
pkg/services/ngalert/api/tooling/definitions/prom.go
Normal file
@ -0,0 +1,130 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
v1 "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
)
|
||||
|
||||
// swagger:route GET /api/prometheus/{Recipient}/api/v1/rules prometheus RouteGetRuleStatuses
|
||||
//
|
||||
// gets the evaluation statuses of all rules
|
||||
//
|
||||
// Responses:
|
||||
// 200: RuleResponse
|
||||
|
||||
// swagger:route GET /api/prometheus/{Recipient}/api/v1/alerts prometheus RouteGetAlertStatuses
|
||||
//
|
||||
// gets the current alerts
|
||||
//
|
||||
// Responses:
|
||||
// 200: AlertResponse
|
||||
|
||||
// swagger:model
|
||||
type RuleResponse struct {
|
||||
// in: body
|
||||
DiscoveryBase
|
||||
// in: body
|
||||
Data RuleDiscovery `json:"data"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type AlertResponse struct {
|
||||
// in: body
|
||||
DiscoveryBase
|
||||
// in: body
|
||||
Data AlertDiscovery `json:"data"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type DiscoveryBase struct {
|
||||
// required: true
|
||||
Status string `json:"status"`
|
||||
// required: false
|
||||
ErrorType v1.ErrorType `json:"errorType,omitempty"`
|
||||
// required: false
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type RuleDiscovery struct {
|
||||
// required: true
|
||||
RuleGroups []*RuleGroup `json:"groups"`
|
||||
}
|
||||
|
||||
// AlertDiscovery has info for all active alerts.
|
||||
// swagger:model
|
||||
type AlertDiscovery struct {
|
||||
// required: true
|
||||
Alerts []*Alert `json:"alerts"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type RuleGroup struct {
|
||||
// required: true
|
||||
Name string `json:"name"`
|
||||
// required: true
|
||||
File string `json:"file"`
|
||||
// In order to preserve rule ordering, while exposing type (alerting or recording)
|
||||
// specific properties, both alerting and recording rules are exposed in the
|
||||
// same array.
|
||||
// required: true
|
||||
Rules []AlertingRule `json:"rules"`
|
||||
// required: true
|
||||
Interval float64 `json:"interval"`
|
||||
LastEvaluation time.Time `json:"lastEvaluation"`
|
||||
EvaluationTime float64 `json:"evaluationTime"`
|
||||
}
|
||||
|
||||
// adapted from cortex
|
||||
// swagger:model
|
||||
type AlertingRule struct {
|
||||
// State can be "pending", "firing", "inactive".
|
||||
// required: true
|
||||
State string `json:"state,omitempty"`
|
||||
// required: true
|
||||
Name string `json:"name,omitempty"`
|
||||
// required: true
|
||||
Query string `json:"query,omitempty"`
|
||||
Duration float64 `json:"duration,omitempty"`
|
||||
// required: true
|
||||
Annotations labels `json:"annotations,omitempty"`
|
||||
// required: true
|
||||
Alerts []*Alert `json:"alerts,omitempty"`
|
||||
Rule
|
||||
}
|
||||
|
||||
// adapted from cortex
|
||||
// swagger:model
|
||||
type Rule struct {
|
||||
// required: true
|
||||
Name string `json:"name"`
|
||||
// required: true
|
||||
Query string `json:"query"`
|
||||
Labels labels `json:"labels"`
|
||||
// required: true
|
||||
Health string `json:"health"`
|
||||
LastError string `json:"lastError"`
|
||||
// required: true
|
||||
Type v1.RuleType `json:"type"`
|
||||
LastEvaluation time.Time `json:"lastEvaluation"`
|
||||
EvaluationTime float64 `json:"evaluationTime"`
|
||||
}
|
||||
|
||||
// Alert has info for an alert.
|
||||
// swagger:model
|
||||
type Alert struct {
|
||||
// required: true
|
||||
Labels labels `json:"labels"`
|
||||
// required: true
|
||||
Annotations labels `json:"annotations"`
|
||||
// required: true
|
||||
State string `json:"state"`
|
||||
ActiveAt *time.Time `json:"activeAt"`
|
||||
// required: true
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// override the labels type with a map for generation.
|
||||
// The custom marshaling for labels.Labels ends up doing this anyways.
|
||||
type labels map[string]string
|
131
pkg/services/ngalert/api/tooling/definitions/testing.go
Normal file
131
pkg/services/ngalert/api/tooling/definitions/testing.go
Normal file
@ -0,0 +1,131 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
)
|
||||
|
||||
// swagger:route Post /api/v1/receiver/test/{Recipient} testing RouteTestReceiverConfig
|
||||
//
|
||||
// Test receiver
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 200: Success
|
||||
// 412: SmtpNotEnabled
|
||||
// 500: Failure
|
||||
|
||||
// swagger:route Post /api/v1/rule/test/{Recipient} testing RouteTestRuleConfig
|
||||
//
|
||||
// Test rule
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 200: TestRuleResponse
|
||||
|
||||
// swagger:parameters RouteTestReceiverConfig
|
||||
type TestReceiverRequest struct {
|
||||
// in:body
|
||||
Body ExtendedReceiver
|
||||
}
|
||||
|
||||
// swagger:parameters RouteTestRuleConfig
|
||||
type TestRuleRequest struct {
|
||||
// in:body
|
||||
Body TestRulePayload
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type TestRulePayload struct {
|
||||
// Example: (node_filesystem_avail_bytes{fstype!="",job="integrations/node_exporter"} node_filesystem_size_bytes{fstype!="",job="integrations/node_exporter"} * 100 < 5 and node_filesystem_readonly{fstype!="",job="integrations/node_exporter"} == 0)
|
||||
Expr string `json:"expr,omitempty"`
|
||||
// GrafanaManagedCondition for grafana alerts
|
||||
GrafanaManagedCondition *models.EvalAlertConditionCommand `json:"grafana_condition,omitempty"`
|
||||
}
|
||||
|
||||
func (p *TestRulePayload) UnmarshalJSON(b []byte) error {
|
||||
type plain TestRulePayload
|
||||
if err := json.Unmarshal(b, (*plain)(p)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.validate()
|
||||
}
|
||||
|
||||
func (p *TestRulePayload) validate() error {
|
||||
if p.Expr != "" && p.GrafanaManagedCondition != nil {
|
||||
return fmt.Errorf("cannot mix Grafana & Prometheus style expressions")
|
||||
}
|
||||
|
||||
if p.Expr == "" && p.GrafanaManagedCondition == nil {
|
||||
return fmt.Errorf("missing either Grafana or Prometheus style expressions")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *TestRulePayload) Type() (backend Backend) {
|
||||
if p.Expr != "" {
|
||||
return LoTexRulerBackend
|
||||
}
|
||||
|
||||
if p.GrafanaManagedCondition != nil {
|
||||
return GrafanaBackend
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type TestRuleResponse struct {
|
||||
Alerts promql.Vector `json:"alerts"`
|
||||
GrafanaAlertInstances AlertInstancesResponse `json:"grafana_alert_instances"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type AlertInstancesResponse struct {
|
||||
// Instances is an array of arrow encoded dataframes
|
||||
// each frame has a single row, and a column for each instance (alert identified by unique labels) with a boolean value (firing/not firing)
|
||||
Instances [][]byte `json:"instances"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type ExtendedReceiver struct {
|
||||
EmailConfigs config.EmailConfig `yaml:"email_configs,omitempty" json:"email_configs,omitempty"`
|
||||
PagerdutyConfigs config.PagerdutyConfig `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"`
|
||||
SlackConfigs config.SlackConfig `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`
|
||||
WebhookConfigs config.WebhookConfig `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"`
|
||||
OpsGenieConfigs config.OpsGenieConfig `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
|
||||
WechatConfigs config.WechatConfig `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"`
|
||||
PushoverConfigs config.PushoverConfig `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
|
||||
VictorOpsConfigs config.VictorOpsConfig `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
|
||||
GrafanaReceiver PostableGrafanaReceiver `yaml:"grafana_managed_receiver,omitempty" json:"grafana_managed_receiver,omitempty"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type Success ResponseDetails
|
||||
|
||||
// swagger:model
|
||||
type SmtpNotEnabled ResponseDetails
|
||||
|
||||
// swagger:model
|
||||
type Failure ResponseDetails
|
||||
|
||||
// swagger:model
|
||||
type ResponseDetails struct {
|
||||
Msg string `json:"msg"`
|
||||
}
|
69
pkg/services/ngalert/api/tooling/definitions/testing_test.go
Normal file
69
pkg/services/ngalert/api/tooling/definitions/testing_test.go
Normal file
@ -0,0 +1,69 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRulePayloadMarshaling(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
input TestRulePayload
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
desc: "success lotex",
|
||||
input: TestRulePayload{
|
||||
Expr: "rate({cluster=\"us-central1\", job=\"loki-prod/loki-canary\"}[1m]) > 0",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "success grafana",
|
||||
input: func() TestRulePayload {
|
||||
data := models.AlertQuery{}
|
||||
|
||||
// hack around that the struct embeds the json message inside of it as well
|
||||
raw, _ := json.Marshal(data)
|
||||
data.Model = raw
|
||||
|
||||
return TestRulePayload{
|
||||
GrafanaManagedCondition: &models.EvalAlertConditionCommand{
|
||||
Condition: "placeholder",
|
||||
Data: []models.AlertQuery{data},
|
||||
},
|
||||
}
|
||||
}(),
|
||||
},
|
||||
{
|
||||
desc: "failure mixed",
|
||||
input: TestRulePayload{
|
||||
Expr: "rate({cluster=\"us-central1\", job=\"loki-prod/loki-canary\"}[1m]) > 0",
|
||||
GrafanaManagedCondition: &models.EvalAlertConditionCommand{},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
desc: "failure both empty",
|
||||
input: TestRulePayload{},
|
||||
err: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
encoded, err := json.Marshal(tc.input)
|
||||
require.Nil(t, err)
|
||||
|
||||
var out TestRulePayload
|
||||
err = json.Unmarshal(encoded, &out)
|
||||
|
||||
if tc.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tc.input, out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
35
pkg/services/ngalert/api/tooling/index.html
Normal file
35
pkg/services/ngalert/api/tooling/index.html
Normal file
@ -0,0 +1,35 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.19.5/swagger-ui.css" >
|
||||
<style>
|
||||
.topbar {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.19.5/swagger-ui-bundle.js"> </script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.19.5/swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "https://grafana.github.io/alerting-api/spec.json?cachebuster=" + Math.random(),
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
3762
pkg/services/ngalert/api/tooling/post.json
Normal file
3762
pkg/services/ngalert/api/tooling/post.json
Normal file
File diff suppressed because it is too large
Load Diff
3771
pkg/services/ngalert/api/tooling/spec.json
Normal file
3771
pkg/services/ngalert/api/tooling/spec.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,12 +11,12 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/alertmanager/provider"
|
||||
"github.com/prometheus/alertmanager/provider/mem"
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/alertmanager/provider"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
gokit_log "github.com/go-kit/kit/log"
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/alertmanager/dispatch"
|
||||
"github.com/prometheus/alertmanager/nflog"
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/pkg/errors"
|
||||
v2 "github.com/prometheus/alertmanager/api/v2"
|
||||
"github.com/prometheus/alertmanager/dispatch"
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
api "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/alerting-api/pkg/api"
|
||||
api "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/pkg/errors"
|
||||
v2 "github.com/prometheus/alertmanager/api/v2"
|
||||
"github.com/prometheus/alertmanager/silence"
|
||||
|
@ -2,7 +2,7 @@ package schedule
|
||||
|
||||
import (
|
||||
"github.com/go-openapi/strfmt"
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/prometheus/alertmanager/api/v2/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
|
Loading…
Reference in New Issue
Block a user