mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
Alerting: Create endpoints for exporting in provisioning file format (#58623)
This adds provisioning endpoints for downloading alert rules and alert rule groups in a format that is compatible with file provisioning. Each endpoint supports both json and yaml response types via Accept header as well as a query parameter download=true/false that will set Content-Disposition to recommend initiating a download or inline display. This also makes some package changes to keep structs with potential to drift closer together. Eventually, other alerting file structs should also move into this new file package, but the rest require some refactoring that is out of scope for this PR.
This commit is contained in:
parent
d5294eb8fa
commit
c006df375a
@ -22,20 +22,20 @@ Details on how to set up the files and which fields are required for each object
|
||||
|
||||
**Note:**
|
||||
|
||||
Provisioning takes place during the initial set up of your Grafana system, but you can re-run it at any time using the [Grafana Alerting provisioning API](https://grafana.com/docs/grafana/latest/developers/http_api/admin/#reload-provisioning-configurations).
|
||||
Provisioning takes place during the initial set up of your Grafana system, but you can re-run it at any time using the [Grafana Admin API](https://grafana.com/docs/grafana/latest/developers/http_api/admin/#reload-provisioning-configurations).
|
||||
|
||||
### Provision alert rules
|
||||
|
||||
Create or delete alert rules in your Grafana instance(s).
|
||||
|
||||
1. Create an alert rule in Grafana.
|
||||
1. Use the [Alerting provisioning API](https://grafana.com/docs/grafana/latest/developers/http_api/alerting_provisioning/#route-get-alert-rule) to extract the alert rule.
|
||||
1. Create alert rules in Grafana.
|
||||
1. Use the [Alerting provisioning API](https://grafana.com/docs/grafana/latest/developers/http_api/alerting_provisioning/#route-get-alert-rule-export) export endpoints to download a provisioning file for your alert rules.
|
||||
1. Copy the contents into a YAML or JSON configuration file in the default provisioning directory or in your configured directory.
|
||||
|
||||
Example configuration files can be found below.
|
||||
|
||||
1. Ensure that your files are in the right directory on the node running the Grafana server, so that they deploy alongside your Grafana instance(s).
|
||||
1. Delete the alert rule in Grafana.
|
||||
1. Delete the alert rules in Grafana that will be provisioned.
|
||||
|
||||
**Note:**
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,6 +9,7 @@ import (
|
||||
"reflect"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
@ -173,7 +174,8 @@ func (r *RedirectResponse) Body() []byte {
|
||||
|
||||
// JSON creates a JSON response.
|
||||
func JSON(status int, body interface{}) *NormalResponse {
|
||||
return Respond(status, body).SetHeader("Content-Type", "application/json")
|
||||
return Respond(status, body).
|
||||
SetHeader("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
// JSONStreaming creates a streaming JSON response.
|
||||
@ -187,6 +189,30 @@ func JSONStreaming(status int, body interface{}) StreamingResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// JSONDownload creates a JSON response indicating that it should be downloaded.
|
||||
func JSONDownload(status int, body interface{}, filename string) *NormalResponse {
|
||||
return JSON(status, body).
|
||||
SetHeader("Content-Disposition", fmt.Sprintf(`attachment;filename="%s"`, filename))
|
||||
}
|
||||
|
||||
// YAML creates a YAML response.
|
||||
func YAML(status int, body interface{}) *NormalResponse {
|
||||
b, err := yaml.Marshal(body)
|
||||
if err != nil {
|
||||
return Error(http.StatusInternalServerError, "body yaml marshal", err)
|
||||
}
|
||||
// As of now, application/yaml is downloaded by default in chrome regardless of Content-Disposition, so we use text/yaml instead.
|
||||
return Respond(status, b).
|
||||
SetHeader("Content-Type", "text/yaml")
|
||||
}
|
||||
|
||||
// YAMLDownload creates a YAML response indicating that it should be downloaded.
|
||||
func YAMLDownload(status int, body interface{}, filename string) *NormalResponse {
|
||||
return YAML(status, body).
|
||||
SetHeader("Content-Type", "application/yaml").
|
||||
SetHeader("Content-Disposition", fmt.Sprintf(`attachment;filename="%s"`, filename))
|
||||
}
|
||||
|
||||
// Success create a successful response
|
||||
func Success(message string) *NormalResponse {
|
||||
resp := make(map[string]interface{})
|
||||
|
@ -3,7 +3,9 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
@ -12,6 +14,7 @@ import (
|
||||
alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/alerting/file"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@ -60,6 +63,9 @@ type AlertRuleService interface {
|
||||
DeleteAlertRule(ctx context.Context, orgID int64, ruleUID string, provenance alerting_models.Provenance) error
|
||||
GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (alerting_models.AlertRuleGroup, error)
|
||||
ReplaceRuleGroup(ctx context.Context, orgID int64, group alerting_models.AlertRuleGroup, userID int64, provenance alerting_models.Provenance) error
|
||||
GetAlertRuleWithFolderTitle(ctx context.Context, orgID int64, ruleUID string) (provisioning.AlertRuleWithFolderTitle, error)
|
||||
GetAlertRuleGroupWithFolderTitle(ctx context.Context, orgID int64, folder, group string) (file.AlertRuleGroupWithFolderTitle, error)
|
||||
GetAlertGroupsWithFolderTitle(ctx context.Context, orgID int64) ([]file.AlertRuleGroupWithFolderTitle, error)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RouteGetPolicyTree(c *contextmodel.ReqContext) response.Response {
|
||||
@ -334,6 +340,66 @@ func (srv *ProvisioningSrv) RouteGetAlertRuleGroup(c *contextmodel.ReqContext, f
|
||||
return response.JSON(http.StatusOK, definitions.NewAlertRuleGroupFromModel(g))
|
||||
}
|
||||
|
||||
// RouteGetAlertRulesExport retrieves all alert rules in a format compatible with file provisioning.
|
||||
func (srv *ProvisioningSrv) RouteGetAlertRulesExport(c *contextmodel.ReqContext) response.Response {
|
||||
groupsWithTitle, err := srv.alertRules.GetAlertGroupsWithFolderTitle(c.Req.Context(), c.OrgID)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules")
|
||||
}
|
||||
|
||||
e, err := file.NewAlertingFileExport(groupsWithTitle)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to create alerting file export")
|
||||
}
|
||||
|
||||
return exportResponse(c, e)
|
||||
}
|
||||
|
||||
// RouteGetAlertRuleGroupExport retrieves the given alert rule group in a format compatible with file provisioning.
|
||||
func (srv *ProvisioningSrv) RouteGetAlertRuleGroupExport(c *contextmodel.ReqContext, folder string, group string) response.Response {
|
||||
g, err := srv.alertRules.GetAlertRuleGroupWithFolderTitle(c.Req.Context(), c.OrgID, folder, group)
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrAlertRuleGroupNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to get alert rule group")
|
||||
}
|
||||
|
||||
e, err := file.NewAlertingFileExport([]file.AlertRuleGroupWithFolderTitle{g})
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to create alerting file export")
|
||||
}
|
||||
|
||||
return exportResponse(c, e)
|
||||
}
|
||||
|
||||
// RouteGetAlertRuleExport retrieves the given alert rule in a format compatible with file provisioning.
|
||||
func (srv *ProvisioningSrv) RouteGetAlertRuleExport(c *contextmodel.ReqContext, UID string) response.Response {
|
||||
rule, err := srv.alertRules.GetAlertRuleWithFolderTitle(c.Req.Context(), c.OrgID, UID)
|
||||
if err != nil {
|
||||
if errors.Is(err, alerting_models.ErrAlertRuleNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
|
||||
e, err := file.NewAlertingFileExport([]file.AlertRuleGroupWithFolderTitle{{
|
||||
AlertRuleGroup: &alerting_models.AlertRuleGroup{
|
||||
Title: rule.AlertRule.RuleGroup,
|
||||
FolderUID: rule.AlertRule.NamespaceUID,
|
||||
Interval: rule.AlertRule.IntervalSeconds,
|
||||
Rules: []alerting_models.AlertRule{rule.AlertRule},
|
||||
},
|
||||
OrgID: c.OrgID,
|
||||
FolderTitle: rule.FolderTitle,
|
||||
}})
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to create alerting file export")
|
||||
}
|
||||
|
||||
return exportResponse(c, e)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *contextmodel.ReqContext, ag definitions.AlertRuleGroup, folderUID string, group string) response.Response {
|
||||
ag.FolderUID = folderUID
|
||||
ag.Title = group
|
||||
@ -360,3 +426,26 @@ func determineProvenance(ctx *contextmodel.ReqContext) alerting_models.Provenanc
|
||||
}
|
||||
return alerting_models.ProvenanceAPI
|
||||
}
|
||||
|
||||
func exportResponse(c *contextmodel.ReqContext, body any) response.Response {
|
||||
format := "json"
|
||||
acceptHeader := c.Req.Header.Get("Accept")
|
||||
if strings.Contains(acceptHeader, "yaml") && !strings.Contains(acceptHeader, "json") {
|
||||
format = "yaml"
|
||||
}
|
||||
|
||||
download := c.QueryBoolWithDefault("download", false)
|
||||
if download {
|
||||
r := response.JSONDownload
|
||||
if format == "yaml" {
|
||||
r = response.YAMLDownload
|
||||
}
|
||||
return r(http.StatusOK, body, fmt.Sprintf("export.%s", format))
|
||||
}
|
||||
|
||||
r := response.JSON
|
||||
if format == "yaml" {
|
||||
r = response.YAML
|
||||
}
|
||||
return r(http.StatusOK, body)
|
||||
}
|
||||
|
@ -5,18 +5,22 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
prometheus "github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/timeinterval"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||
@ -370,17 +374,394 @@ func TestProvisioningApi(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("exports", func(t *testing.T) {
|
||||
t.Run("alert rule group", func(t *testing.T) {
|
||||
t.Run("are present, GET returns 200", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
})
|
||||
|
||||
t.Run("are missing, GET returns 404", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "does not exist")
|
||||
|
||||
require.Equal(t, 404, response.Status())
|
||||
})
|
||||
|
||||
t.Run("accept header contains yaml, GET returns text yaml", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("accept header contains json, GET returns json", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("accept header contains json and yaml, GET returns json", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/json, application/yaml")
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("query param download=true, GET returns content disposition attachment", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Form.Set("download", "true")
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Contains(t, rc.Context.Resp.Header().Get("Content-Disposition"), "attachment")
|
||||
})
|
||||
|
||||
t.Run("query param download=false, GET returns empty content disposition", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Form.Set("download", "false")
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
||||
})
|
||||
|
||||
t.Run("query param download not set, GET returns empty content disposition", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
||||
})
|
||||
|
||||
t.Run("json body content is as expected", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
insertRule(t, sut, createTestAlertRule("rule2", 1))
|
||||
|
||||
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"},{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]}]}`
|
||||
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, expectedResponse, string(response.Body()))
|
||||
})
|
||||
|
||||
t.Run("yaml body content is as expected", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
insertRule(t, sut, createTestAlertRule("rule2", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
||||
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n"
|
||||
|
||||
response := sut.RouteGetAlertRuleGroupExport(&rc, "folder-uid", "my-cool-group")
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, expectedResponse, string(response.Body()))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("alert rule", func(t *testing.T) {
|
||||
t.Run("are present, GET returns 200", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
})
|
||||
|
||||
t.Run("are missing, GET returns 404", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule404")
|
||||
|
||||
require.Equal(t, 404, response.Status())
|
||||
})
|
||||
|
||||
t.Run("accept header contains yaml, GET returns text yaml", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("accept header contains json, GET returns json", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("accept header contains json and yaml, GET returns json", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/json, application/yaml")
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("query param download=true, GET returns content disposition attachment", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
rc.Context.Req.Form.Set("download", "true")
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Contains(t, rc.Context.Resp.Header().Get("Content-Disposition"), "attachment")
|
||||
})
|
||||
|
||||
t.Run("query param download=false, GET returns empty content disposition", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
rc.Context.Req.Form.Set("download", "false")
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
||||
})
|
||||
|
||||
t.Run("query param download not set, GET returns empty content disposition", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
||||
})
|
||||
|
||||
t.Run("json body content is as expected", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"my-cool-group","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]}]}`
|
||||
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, expectedResponse, string(response.Body()))
|
||||
})
|
||||
|
||||
t.Run("yaml body content is as expected", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule1", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
||||
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: my-cool-group\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n"
|
||||
|
||||
response := sut.RouteGetAlertRuleExport(&rc, "rule1")
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, expectedResponse, string(response.Body()))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("all alert rules", func(t *testing.T) {
|
||||
t.Run("are present, GET returns 200", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
response := sut.RouteGetAlertRulesExport(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
})
|
||||
|
||||
t.Run("accept header contains yaml, GET returns text yaml", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
||||
response := sut.RouteGetAlertRulesExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "text/yaml", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("accept header contains json, GET returns json", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||
response := sut.RouteGetAlertRulesExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("accept header contains json and yaml, GET returns json", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/json, application/yaml")
|
||||
response := sut.RouteGetAlertRulesExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "application/json", rc.Context.Resp.Header().Get("Content-Type"))
|
||||
})
|
||||
|
||||
t.Run("query param download=true, GET returns content disposition attachment", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Form.Set("download", "true")
|
||||
response := sut.RouteGetAlertRulesExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Contains(t, rc.Context.Resp.Header().Get("Content-Disposition"), "attachment")
|
||||
})
|
||||
|
||||
t.Run("query param download=false, GET returns empty content disposition", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
rc.Context.Req.Form.Set("download", "false")
|
||||
response := sut.RouteGetAlertRulesExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
||||
})
|
||||
|
||||
t.Run("query param download not set, GET returns empty content disposition", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
response := sut.RouteGetAlertRulesExport(&rc)
|
||||
response.WriteTo(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, "", rc.Context.Resp.Header().Get("Content-Disposition"))
|
||||
})
|
||||
|
||||
t.Run("json body content is as expected", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule1", 1, "folder-uid", "groupa"))
|
||||
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule2", 1, "folder-uid", "groupb"))
|
||||
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule3", 1, "folder-uid2", "groupb"))
|
||||
|
||||
expectedResponse := `{"apiVersion":1,"groups":[{"orgId":1,"name":"groupa","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule1","title":"rule1","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]},{"orgId":1,"name":"groupb","folder":"Folder Title","interval":"1m","rules":[{"uid":"rule2","title":"rule2","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]},{"orgId":1,"name":"groupb","folder":"Folder Title2","interval":"1m","rules":[{"uid":"rule3","title":"rule3","condition":"A","data":[{"refId":"A","relativeTimeRange":{"from":0,"to":0},"datasourceUid":"","model":{"conditions":[{"evaluator":{"params":[3],"type":"gt"},"operator":{"type":"and"},"query":{"params":["A"]},"reducer":{"type":"last"},"type":"query"}],"datasource":{"type":"__expr__","uid":"-100"},"expression":"1==0","intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}}],"noDataState":"OK","execErrState":"OK","for":"0s"}]}]}`
|
||||
|
||||
response := sut.RouteGetAlertRulesExport(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, expectedResponse, string(response.Body()))
|
||||
})
|
||||
|
||||
t.Run("yaml body content is as expected", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule1", 1, "folder-uid", "groupa"))
|
||||
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule2", 1, "folder-uid", "groupb"))
|
||||
insertRule(t, sut, createTestAlertRuleWithFolderAndGroup("rule3", 1, "folder-uid2", "groupb"))
|
||||
|
||||
rc.Context.Req.Header.Add("Accept", "application/yaml")
|
||||
expectedResponse := "apiVersion: 1\ngroups:\n - orgId: 1\n name: groupa\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule1\n title: rule1\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n - orgId: 1\n name: groupb\n folder: Folder Title\n interval: 1m\n rules:\n - uid: rule2\n title: rule2\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n - orgId: 1\n name: groupb\n folder: Folder Title2\n interval: 1m\n rules:\n - uid: rule3\n title: rule3\n condition: A\n data:\n - refId: A\n datasourceUid: \"\"\n model:\n conditions:\n - evaluator:\n params:\n - 3\n type: gt\n operator:\n type: and\n query:\n params:\n - A\n reducer:\n type: last\n type: query\n datasource:\n type: __expr__\n uid: \"-100\"\n expression: 1==0\n intervalMs: 1000\n maxDataPoints: 43200\n refId: A\n type: math\n noDataState: OK\n execErrState: OK\n for: 0s\n"
|
||||
|
||||
response := sut.RouteGetAlertRulesExport(&rc)
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, expectedResponse, string(response.Body()))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// testEnvironment binds together common dependencies for testing alerting APIs.
|
||||
type testEnvironment struct {
|
||||
secrets secrets.Service
|
||||
log log.Logger
|
||||
store store.DBstore
|
||||
configs provisioning.AMConfigStore
|
||||
xact provisioning.TransactionManager
|
||||
quotas provisioning.QuotaChecker
|
||||
prov provisioning.ProvisioningStore
|
||||
secrets secrets.Service
|
||||
log log.Logger
|
||||
store store.DBstore
|
||||
dashboardService dashboards.DashboardService
|
||||
configs provisioning.AMConfigStore
|
||||
xact provisioning.TransactionManager
|
||||
quotas provisioning.QuotaChecker
|
||||
prov provisioning.ProvisioningStore
|
||||
}
|
||||
|
||||
func createTestEnv(t *testing.T) testEnvironment {
|
||||
@ -407,14 +788,29 @@ func createTestEnv(t *testing.T) testEnvironment {
|
||||
prov.EXPECT().SaveSucceeds()
|
||||
prov.EXPECT().GetReturns(models.ProvenanceNone)
|
||||
|
||||
dashboardService := dashboards.NewFakeDashboardService(t)
|
||||
dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(&dashboards.Dashboard{
|
||||
UID: "folder-uid",
|
||||
Title: "Folder Title",
|
||||
}, nil).Maybe()
|
||||
dashboardService.On("GetDashboards", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardsQuery")).Return([]*dashboards.Dashboard{{
|
||||
UID: "folder-uid",
|
||||
Title: "Folder Title",
|
||||
},
|
||||
{
|
||||
UID: "folder-uid2",
|
||||
Title: "Folder Title2",
|
||||
}}, nil).Maybe()
|
||||
|
||||
return testEnvironment{
|
||||
secrets: secrets,
|
||||
log: log,
|
||||
configs: configs,
|
||||
store: store,
|
||||
xact: xact,
|
||||
prov: prov,
|
||||
quotas: quotas,
|
||||
secrets: secrets,
|
||||
log: log,
|
||||
configs: configs,
|
||||
store: store,
|
||||
dashboardService: dashboardService,
|
||||
xact: xact,
|
||||
prov: prov,
|
||||
quotas: quotas,
|
||||
}
|
||||
}
|
||||
|
||||
@ -434,14 +830,18 @@ func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) Provisi
|
||||
contactPointService: provisioning.NewContactPointService(env.configs, env.secrets, env.prov, env.xact, env.log),
|
||||
templates: provisioning.NewTemplateService(env.configs, env.prov, env.xact, env.log),
|
||||
muteTimings: provisioning.NewMuteTimingService(env.configs, env.prov, env.xact, env.log),
|
||||
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.quotas, env.xact, 60, 10, env.log),
|
||||
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.dashboardService, env.quotas, env.xact, 60, 10, env.log),
|
||||
}
|
||||
}
|
||||
|
||||
func createTestRequestCtx() contextmodel.ReqContext {
|
||||
return contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
Req: &http.Request{
|
||||
Header: make(http.Header),
|
||||
Form: make(url.Values),
|
||||
},
|
||||
Resp: web.NewResponseWriter("GET", httptest.NewRecorder()),
|
||||
},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgID: 1,
|
||||
@ -555,15 +955,23 @@ func createInvalidAlertRuleGroup() definitions.AlertRuleGroup {
|
||||
}
|
||||
}
|
||||
|
||||
func createTestAlertRuleWithFolderAndGroup(title string, orgID int64, folderUid string, group string) definitions.ProvisionedAlertRule {
|
||||
rule := createTestAlertRule(title, orgID)
|
||||
rule.FolderUID = folderUid
|
||||
rule.RuleGroup = group
|
||||
return rule
|
||||
}
|
||||
|
||||
func createTestAlertRule(title string, orgID int64) definitions.ProvisionedAlertRule {
|
||||
return definitions.ProvisionedAlertRule{
|
||||
UID: title,
|
||||
OrgID: orgID,
|
||||
Title: title,
|
||||
Condition: "A",
|
||||
Data: []models.AlertQuery{
|
||||
{
|
||||
RefID: "A",
|
||||
Model: json.RawMessage("{}"),
|
||||
Model: json.RawMessage(testModel),
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(60),
|
||||
To: models.Duration(0),
|
||||
@ -600,6 +1008,42 @@ func deserializeRule(t *testing.T, data []byte) definitions.ProvisionedAlertRule
|
||||
return rule
|
||||
}
|
||||
|
||||
var testModel = `
|
||||
{
|
||||
"conditions": [
|
||||
{
|
||||
"evaluator": {
|
||||
"params": [
|
||||
3
|
||||
],
|
||||
"type": "gt"
|
||||
},
|
||||
"operator": {
|
||||
"type": "and"
|
||||
},
|
||||
"query": {
|
||||
"params": [
|
||||
"A"
|
||||
]
|
||||
},
|
||||
"reducer": {
|
||||
"type": "last"
|
||||
},
|
||||
"type": "query"
|
||||
}
|
||||
],
|
||||
"datasource": {
|
||||
"type": "__expr__",
|
||||
"uid": "-100"
|
||||
},
|
||||
"expression": "1==0",
|
||||
"intervalMs": 1000,
|
||||
"maxDataPoints": 43200,
|
||||
"refId": "A",
|
||||
"type": "math"
|
||||
}
|
||||
`
|
||||
|
||||
var testConfig = `
|
||||
{
|
||||
"template_files": {
|
||||
|
@ -201,7 +201,10 @@ func (api *API) authorize(method, path string) web.Handler {
|
||||
http.MethodGet + "/api/v1/provisioning/mute-timings/{name}",
|
||||
http.MethodGet + "/api/v1/provisioning/alert-rules",
|
||||
http.MethodGet + "/api/v1/provisioning/alert-rules/{UID}",
|
||||
http.MethodGet + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}":
|
||||
http.MethodGet + "/api/v1/provisioning/alert-rules/export",
|
||||
http.MethodGet + "/api/v1/provisioning/alert-rules/{UID}/export",
|
||||
http.MethodGet + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}",
|
||||
http.MethodGet + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export":
|
||||
fallback = middleware.ReqOrgAdmin
|
||||
eval = ac.EvalPermission(ac.ActionAlertingProvisioningRead) // organization scope
|
||||
|
||||
|
@ -49,7 +49,7 @@ func TestAuthorize(t *testing.T) {
|
||||
}
|
||||
paths[p] = methods
|
||||
}
|
||||
require.Len(t, paths, 41)
|
||||
require.Len(t, paths, 44)
|
||||
|
||||
ac := acmock.New()
|
||||
api := &API{AccessControl: ac}
|
||||
|
@ -24,8 +24,11 @@ type ProvisioningApi interface {
|
||||
RouteDeleteMuteTiming(*contextmodel.ReqContext) response.Response
|
||||
RouteDeleteTemplate(*contextmodel.ReqContext) response.Response
|
||||
RouteGetAlertRule(*contextmodel.ReqContext) response.Response
|
||||
RouteGetAlertRuleExport(*contextmodel.ReqContext) response.Response
|
||||
RouteGetAlertRuleGroup(*contextmodel.ReqContext) response.Response
|
||||
RouteGetAlertRuleGroupExport(*contextmodel.ReqContext) response.Response
|
||||
RouteGetAlertRules(*contextmodel.ReqContext) response.Response
|
||||
RouteGetAlertRulesExport(*contextmodel.ReqContext) response.Response
|
||||
RouteGetContactpoints(*contextmodel.ReqContext) response.Response
|
||||
RouteGetMuteTiming(*contextmodel.ReqContext) response.Response
|
||||
RouteGetMuteTimings(*contextmodel.ReqContext) response.Response
|
||||
@ -69,15 +72,29 @@ func (f *ProvisioningApiHandler) RouteGetAlertRule(ctx *contextmodel.ReqContext)
|
||||
uIDParam := web.Params(ctx.Req)[":UID"]
|
||||
return f.handleRouteGetAlertRule(ctx, uIDParam)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteGetAlertRuleExport(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
uIDParam := web.Params(ctx.Req)[":UID"]
|
||||
return f.handleRouteGetAlertRuleExport(ctx, uIDParam)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteGetAlertRuleGroup(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
folderUIDParam := web.Params(ctx.Req)[":FolderUID"]
|
||||
groupParam := web.Params(ctx.Req)[":Group"]
|
||||
return f.handleRouteGetAlertRuleGroup(ctx, folderUIDParam, groupParam)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteGetAlertRuleGroupExport(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
folderUIDParam := web.Params(ctx.Req)[":FolderUID"]
|
||||
groupParam := web.Params(ctx.Req)[":Group"]
|
||||
return f.handleRouteGetAlertRuleGroupExport(ctx, folderUIDParam, groupParam)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteGetAlertRules(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteGetAlertRules(ctx)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteGetAlertRulesExport(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteGetAlertRulesExport(ctx)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteGetContactpoints(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.handleRouteGetContactpoints(ctx)
|
||||
}
|
||||
@ -239,6 +256,16 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApi, m *metrics
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/v1/provisioning/alert-rules/{UID}/export"),
|
||||
api.authorize(http.MethodGet, "/api/v1/provisioning/alert-rules/{UID}/export"),
|
||||
metrics.Instrument(
|
||||
http.MethodGet,
|
||||
"/api/v1/provisioning/alert-rules/{UID}/export",
|
||||
srv.RouteGetAlertRuleExport,
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}"),
|
||||
api.authorize(http.MethodGet, "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}"),
|
||||
@ -249,6 +276,16 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApi, m *metrics
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export"),
|
||||
api.authorize(http.MethodGet, "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export"),
|
||||
metrics.Instrument(
|
||||
http.MethodGet,
|
||||
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export",
|
||||
srv.RouteGetAlertRuleGroupExport,
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/v1/provisioning/alert-rules"),
|
||||
api.authorize(http.MethodGet, "/api/v1/provisioning/alert-rules"),
|
||||
@ -259,6 +296,16 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApi, m *metrics
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/v1/provisioning/alert-rules/export"),
|
||||
api.authorize(http.MethodGet, "/api/v1/provisioning/alert-rules/export"),
|
||||
metrics.Instrument(
|
||||
http.MethodGet,
|
||||
"/api/v1/provisioning/alert-rules/export",
|
||||
srv.RouteGetAlertRulesExport,
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Get(
|
||||
toMacaronPath("/api/v1/provisioning/contact-points"),
|
||||
api.authorize(http.MethodGet, "/api/v1/provisioning/contact-points"),
|
||||
|
@ -84,6 +84,14 @@ func (f *ProvisioningApiHandler) handleRouteGetAlertRule(ctx *contextmodel.ReqCo
|
||||
return f.svc.RouteRouteGetAlertRule(ctx, UID)
|
||||
}
|
||||
|
||||
func (f *ProvisioningApiHandler) handleRouteGetAlertRuleExport(ctx *contextmodel.ReqContext, UID string) response.Response {
|
||||
return f.svc.RouteGetAlertRuleExport(ctx, UID)
|
||||
}
|
||||
|
||||
func (f *ProvisioningApiHandler) handleRouteGetAlertRulesExport(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.svc.RouteGetAlertRulesExport(ctx)
|
||||
}
|
||||
|
||||
func (f *ProvisioningApiHandler) handleRoutePostAlertRule(ctx *contextmodel.ReqContext, ar apimodels.ProvisionedAlertRule) response.Response {
|
||||
return f.svc.RoutePostAlertRule(ctx, ar)
|
||||
}
|
||||
@ -104,6 +112,10 @@ func (f *ProvisioningApiHandler) handleRouteGetAlertRuleGroup(ctx *contextmodel.
|
||||
return f.svc.RouteGetAlertRuleGroup(ctx, folder, group)
|
||||
}
|
||||
|
||||
func (f *ProvisioningApiHandler) handleRouteGetAlertRuleGroupExport(ctx *contextmodel.ReqContext, folder, group string) response.Response {
|
||||
return f.svc.RouteGetAlertRuleGroupExport(ctx, folder, group)
|
||||
}
|
||||
|
||||
func (f *ProvisioningApiHandler) handleRoutePutAlertRuleGroup(ctx *contextmodel.ReqContext, ag apimodels.AlertRuleGroup, folder, group string) response.Response {
|
||||
return f.svc.RoutePutAlertRuleGroup(ctx, ag, folder, group)
|
||||
}
|
||||
|
@ -121,6 +121,28 @@
|
||||
"title": "AlertQuery represents a single query associated with an alert definition.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertQueryExport": {
|
||||
"properties": {
|
||||
"datasourceUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"additionalProperties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"queryType": {
|
||||
"type": "string"
|
||||
},
|
||||
"refId": {
|
||||
"type": "string"
|
||||
},
|
||||
"relativeTimeRange": {
|
||||
"$ref": "#/definitions/RelativeTimeRange"
|
||||
}
|
||||
},
|
||||
"title": "AlertQueryExport is the provisioned export of models.AlertQuery.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertResponse": {
|
||||
"properties": {
|
||||
"data": {
|
||||
@ -141,6 +163,65 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AlertRuleExport": {
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"dasboardUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertQueryExport"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"execErrState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
"Error",
|
||||
"OK"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"for": {
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"labels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"noDataState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
"NoData",
|
||||
"OK"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"panelId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "AlertRuleExport is the provisioned file export of models.AlertRule.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertRuleGroup": {
|
||||
"properties": {
|
||||
"folderUid": {
|
||||
@ -162,6 +243,31 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AlertRuleGroupExport": {
|
||||
"properties": {
|
||||
"folder": {
|
||||
"type": "string"
|
||||
},
|
||||
"interval": {
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"orgId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rules": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertRuleExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "AlertRuleGroupExport is the provisioned file export of AlertRuleGroupV1.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertRuleGroupMetadata": {
|
||||
"properties": {
|
||||
"interval": {
|
||||
@ -171,6 +277,22 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AlertingFileExport": {
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"groups": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertRuleGroupExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "AlertingFileExport is the full provisioned file export.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertingRule": {
|
||||
"description": "adapted from cortex",
|
||||
"properties": {
|
||||
@ -3155,7 +3277,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@ -3191,7 +3312,7 @@
|
||||
"$ref": "#/definitions/Userinfo"
|
||||
}
|
||||
},
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"type": "object"
|
||||
},
|
||||
"Userinfo": {
|
||||
@ -3551,6 +3672,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
},
|
||||
@ -3611,7 +3733,6 @@
|
||||
"type": "array"
|
||||
},
|
||||
"integration": {
|
||||
"description": "Integration integration",
|
||||
"properties": {
|
||||
"lastNotifyAttempt": {
|
||||
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
||||
@ -3793,6 +3914,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"properties": {
|
||||
"active": {
|
||||
"description": "active",
|
||||
@ -3967,6 +4089,35 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/export": {
|
||||
"get": {
|
||||
"operationId": "RouteGetAlertRulesExport",
|
||||
"parameters": [
|
||||
{
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"in": "query",
|
||||
"name": "download",
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
},
|
||||
"summary": "Export all alert rules in provisioning file format.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/{UID}": {
|
||||
"delete": {
|
||||
"operationId": "RouteDeleteAlertRule",
|
||||
@ -4062,6 +4213,47 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/{UID}/export": {
|
||||
"get": {
|
||||
"operationId": "RouteGetAlertRuleExport",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Alert rule UID",
|
||||
"in": "path",
|
||||
"name": "UID",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"in": "query",
|
||||
"name": "download",
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/yaml",
|
||||
"text/yaml"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
},
|
||||
"summary": "Export an alert rule in provisioning file format.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/contact-points": {
|
||||
"get": {
|
||||
"operationId": "RouteGetContactpoints",
|
||||
@ -4265,6 +4457,52 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export": {
|
||||
"get": {
|
||||
"operationId": "RouteGetAlertRuleGroupExport",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "FolderUID",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "Group",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"in": "query",
|
||||
"name": "download",
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/yaml",
|
||||
"text/yaml"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
},
|
||||
"summary": "Export an alert rule group in provisioning file format.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/mute-timings": {
|
||||
"get": {
|
||||
"operationId": "RouteGetMuteTimings",
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/alerting/file"
|
||||
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
@ -14,6 +16,14 @@ import (
|
||||
// Responses:
|
||||
// 200: ProvisionedAlertRules
|
||||
|
||||
// swagger:route GET /api/v1/provisioning/alert-rules/export provisioning stable RouteGetAlertRulesExport
|
||||
//
|
||||
// Export all alert rules in provisioning file format.
|
||||
//
|
||||
// Responses:
|
||||
// 200: AlertingFileExport
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route GET /api/v1/provisioning/alert-rules/{UID} provisioning stable RouteGetAlertRule
|
||||
//
|
||||
// Get a specific alert rule by UID.
|
||||
@ -22,6 +32,19 @@ import (
|
||||
// 200: ProvisionedAlertRule
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route GET /api/v1/provisioning/alert-rules/{UID}/export provisioning stable RouteGetAlertRuleExport
|
||||
//
|
||||
// Export an alert rule in provisioning file format.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
// - application/yaml
|
||||
// - text/yaml
|
||||
//
|
||||
// Responses:
|
||||
// 200: AlertingFileExport
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route POST /api/v1/provisioning/alert-rules provisioning stable RoutePostAlertRule
|
||||
//
|
||||
// Create a new alert rule.
|
||||
@ -51,7 +74,7 @@ import (
|
||||
// Responses:
|
||||
// 204: description: The alert rule was deleted successfully.
|
||||
|
||||
// swagger:parameters RouteGetAlertRule RoutePutAlertRule RouteDeleteAlertRule
|
||||
// swagger:parameters RouteGetAlertRule RoutePutAlertRule RouteDeleteAlertRule RouteGetAlertRuleExport
|
||||
type AlertRuleUIDReference struct {
|
||||
// Alert rule UID
|
||||
// in:path
|
||||
@ -168,6 +191,19 @@ func NewAlertRules(rules []*models.AlertRule) ProvisionedAlertRules {
|
||||
// 200: AlertRuleGroup
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route GET /api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export provisioning stable RouteGetAlertRuleGroupExport
|
||||
//
|
||||
// Export an alert rule group in provisioning file format.
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
// - application/yaml
|
||||
// - text/yaml
|
||||
//
|
||||
// Responses:
|
||||
// 200: AlertingFileExport
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route PUT /api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RoutePutAlertRuleGroup
|
||||
//
|
||||
// Update the interval of a rule group.
|
||||
@ -179,13 +215,13 @@ func NewAlertRules(rules []*models.AlertRule) ProvisionedAlertRules {
|
||||
// 200: AlertRuleGroup
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup
|
||||
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup RouteGetAlertRuleGroupExport
|
||||
type FolderUIDPathParam struct {
|
||||
// in:path
|
||||
FolderUID string `json:"FolderUID"`
|
||||
}
|
||||
|
||||
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup
|
||||
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup RouteGetAlertRuleGroupExport
|
||||
type RuleGroupPathParam struct {
|
||||
// in:path
|
||||
Group string `json:"Group"`
|
||||
@ -202,6 +238,15 @@ type AlertRuleGroupMetadata struct {
|
||||
Interval int64 `json:"interval"`
|
||||
}
|
||||
|
||||
// swagger:parameters RouteGetAlertRuleGroupExport RouteGetAlertRuleExport RouteGetAlertRulesExport
|
||||
type ExportQueryParams struct {
|
||||
// Whether to initiate a download of the file or not.
|
||||
// in: query
|
||||
// required: false
|
||||
// default: false
|
||||
Download bool `json:"download"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
type AlertRuleGroup struct {
|
||||
Title string `json:"title"`
|
||||
@ -210,6 +255,10 @@ type AlertRuleGroup struct {
|
||||
Rules []ProvisionedAlertRule `json:"rules"`
|
||||
}
|
||||
|
||||
// AlertingFileExport is the full provisioned file export.
|
||||
// swagger:model
|
||||
type AlertingFileExport = file.AlertingFileExport
|
||||
|
||||
func (a *AlertRuleGroup) ToModel() (models.AlertRuleGroup, error) {
|
||||
ruleGroup := models.AlertRuleGroup{
|
||||
Title: a.Title,
|
||||
|
@ -121,6 +121,28 @@
|
||||
"title": "AlertQuery represents a single query associated with an alert definition.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertQueryExport": {
|
||||
"properties": {
|
||||
"datasourceUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"additionalProperties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"queryType": {
|
||||
"type": "string"
|
||||
},
|
||||
"refId": {
|
||||
"type": "string"
|
||||
},
|
||||
"relativeTimeRange": {
|
||||
"$ref": "#/definitions/RelativeTimeRange"
|
||||
}
|
||||
},
|
||||
"title": "AlertQueryExport is the provisioned export of models.AlertQuery.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertResponse": {
|
||||
"properties": {
|
||||
"data": {
|
||||
@ -141,6 +163,77 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AlertRuleExport": {
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"dasboardUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertQueryExport"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"execErrState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
"Error",
|
||||
"OK"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"for": {
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"labels": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"noDataState": {
|
||||
"enum": [
|
||||
"Alerting",
|
||||
"NoData",
|
||||
"OK"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"panelId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "AlertRuleExport is the provisioned export of models.AlertRule.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertRuleFileExport": {
|
||||
"properties": {
|
||||
"groups": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertRuleGroupExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "AlertRuleFileExport is the provisioned export of multiple models.AlertRuleGroup.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertRuleGroup": {
|
||||
"properties": {
|
||||
"folderUid": {
|
||||
@ -162,6 +255,31 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"AlertRuleGroupExport": {
|
||||
"properties": {
|
||||
"folder": {
|
||||
"type": "string"
|
||||
},
|
||||
"interval": {
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"orgId": {
|
||||
"format": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
"rules": {
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertRuleExport"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"title": "AlertRuleGroupExport is the provisioned export of models.AlertRuleGroup.",
|
||||
"type": "object"
|
||||
},
|
||||
"AlertRuleGroupMetadata": {
|
||||
"properties": {
|
||||
"interval": {
|
||||
@ -3155,6 +3273,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@ -3190,7 +3309,7 @@
|
||||
"$ref": "#/definitions/Userinfo"
|
||||
}
|
||||
},
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"type": "object"
|
||||
},
|
||||
"Userinfo": {
|
||||
@ -3391,7 +3510,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
},
|
||||
@ -3496,7 +3614,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableAlert": {
|
||||
"description": "GettableAlert gettable alert",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"$ref": "#/definitions/labelSet"
|
||||
@ -3558,6 +3675,7 @@
|
||||
"type": "array"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -5640,6 +5758,35 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/export": {
|
||||
"get": {
|
||||
"operationId": "RouteGetAlertRulesExport",
|
||||
"parameters": [
|
||||
{
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"in": "query",
|
||||
"name": "download",
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertRuleFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertRuleFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
},
|
||||
"summary": "Export all alert rules in provisioning file format.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/{UID}": {
|
||||
"delete": {
|
||||
"operationId": "RouteDeleteAlertRule",
|
||||
@ -5735,6 +5882,47 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/{UID}/export": {
|
||||
"get": {
|
||||
"operationId": "RouteGetAlertRuleExport",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Alert rule UID",
|
||||
"in": "path",
|
||||
"name": "UID",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"in": "query",
|
||||
"name": "download",
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/yaml",
|
||||
"text/yaml"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertRuleExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertRuleExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
},
|
||||
"summary": "Export an alert rule in provisioning file format.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/contact-points": {
|
||||
"get": {
|
||||
"operationId": "RouteGetContactpoints",
|
||||
@ -5938,6 +6126,52 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export": {
|
||||
"get": {
|
||||
"operationId": "RouteGetAlertRuleGroupExport",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "FolderUID",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "Group",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"in": "query",
|
||||
"name": "download",
|
||||
"type": "boolean"
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/yaml",
|
||||
"text/yaml"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertRuleGroupExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertRuleGroupExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
},
|
||||
"summary": "Export an alert rule group in provisioning file format.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/mute-timings": {
|
||||
"get": {
|
||||
"operationId": "RouteGetMuteTimings",
|
||||
|
@ -1746,6 +1746,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/export": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"provisioning",
|
||||
"stable"
|
||||
],
|
||||
"summary": "Export all alert rules in provisioning file format.",
|
||||
"operationId": "RouteGetAlertRulesExport",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"name": "download",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/{UID}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -1844,6 +1874,48 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/{UID}/export": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/yaml",
|
||||
"text/yaml"
|
||||
],
|
||||
"tags": [
|
||||
"provisioning",
|
||||
"stable"
|
||||
],
|
||||
"summary": "Export an alert rule in provisioning file format.",
|
||||
"operationId": "RouteGetAlertRuleExport",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Alert rule UID",
|
||||
"name": "UID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"name": "download",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/contact-points": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -2053,6 +2125,53 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/yaml",
|
||||
"text/yaml"
|
||||
],
|
||||
"tags": [
|
||||
"provisioning",
|
||||
"stable"
|
||||
],
|
||||
"summary": "Export an alert rule group in provisioning file format.",
|
||||
"operationId": "RouteGetAlertRuleGroupExport",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "FolderUID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Group",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"name": "download",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/mute-timings": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -2612,6 +2731,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertQueryExport": {
|
||||
"type": "object",
|
||||
"title": "AlertQueryExport is the provisioned export of models.AlertQuery.",
|
||||
"properties": {
|
||||
"datasourceUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"queryType": {
|
||||
"type": "string"
|
||||
},
|
||||
"refId": {
|
||||
"type": "string"
|
||||
},
|
||||
"relativeTimeRange": {
|
||||
"$ref": "#/definitions/RelativeTimeRange"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -2632,6 +2773,65 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertRuleExport": {
|
||||
"type": "object",
|
||||
"title": "AlertRuleExport is the provisioned file export of models.AlertRule.",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"dasboardUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertQueryExport"
|
||||
}
|
||||
},
|
||||
"execErrState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Alerting",
|
||||
"Error",
|
||||
"OK"
|
||||
]
|
||||
},
|
||||
"for": {
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"labels": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"noDataState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Alerting",
|
||||
"NoData",
|
||||
"OK"
|
||||
]
|
||||
},
|
||||
"panelId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertRuleGroup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -2653,6 +2853,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertRuleGroupExport": {
|
||||
"type": "object",
|
||||
"title": "AlertRuleGroupExport is the provisioned file export of AlertRuleGroupV1.",
|
||||
"properties": {
|
||||
"folder": {
|
||||
"type": "string"
|
||||
},
|
||||
"interval": {
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"orgId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"rules": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertRuleExport"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertRuleGroupMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -2662,6 +2887,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertingFileExport": {
|
||||
"type": "object",
|
||||
"title": "AlertingFileExport is the full provisioned file export.",
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertRuleGroupExport"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
},
|
||||
"AlertingRule": {
|
||||
"description": "adapted from cortex",
|
||||
"type": "object",
|
||||
@ -5887,7 +6129,6 @@
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
@ -5993,7 +6234,6 @@
|
||||
}
|
||||
},
|
||||
"gettableAlert": {
|
||||
"description": "GettableAlert gettable alert",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"labels",
|
||||
@ -6057,6 +6297,7 @@
|
||||
"$ref": "#/definitions/gettableAlerts"
|
||||
},
|
||||
"gettableSilence": {
|
||||
"description": "GettableSilence gettable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
@ -6113,7 +6354,6 @@
|
||||
"$ref": "#/definitions/gettableSilences"
|
||||
},
|
||||
"integration": {
|
||||
"description": "Integration integration",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
@ -6296,7 +6536,6 @@
|
||||
"$ref": "#/definitions/postableSilence"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"active",
|
||||
|
@ -239,7 +239,7 @@ func (ng *AlertNG) init() error {
|
||||
contactPointService := provisioning.NewContactPointService(store, ng.SecretsService, store, store, ng.Log)
|
||||
templateService := provisioning.NewTemplateService(store, store, store, ng.Log)
|
||||
muteTimingService := provisioning.NewMuteTimingService(store, store, store, ng.Log)
|
||||
alertRuleService := provisioning.NewAlertRuleService(store, store, ng.QuotaService, store,
|
||||
alertRuleService := provisioning.NewAlertRuleService(store, store, ng.dashboardService, ng.QuotaService, store,
|
||||
int64(ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
||||
int64(ng.Cfg.UnifiedAlerting.BaseInterval.Seconds()), ng.Log)
|
||||
|
||||
|
@ -4,11 +4,14 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/alerting/file"
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@ -18,6 +21,7 @@ type AlertRuleService struct {
|
||||
baseIntervalSeconds int64
|
||||
ruleStore RuleStore
|
||||
provenanceStore ProvisioningStore
|
||||
dashboardService dashboards.DashboardService
|
||||
quotas QuotaChecker
|
||||
xact TransactionManager
|
||||
log log.Logger
|
||||
@ -25,6 +29,7 @@ type AlertRuleService struct {
|
||||
|
||||
func NewAlertRuleService(ruleStore RuleStore,
|
||||
provenanceStore ProvisioningStore,
|
||||
dashboardService dashboards.DashboardService,
|
||||
quotas QuotaChecker,
|
||||
xact TransactionManager,
|
||||
defaultIntervalSeconds int64,
|
||||
@ -35,6 +40,7 @@ func NewAlertRuleService(ruleStore RuleStore,
|
||||
baseIntervalSeconds: baseIntervalSeconds,
|
||||
ruleStore: ruleStore,
|
||||
provenanceStore: provenanceStore,
|
||||
dashboardService: dashboardService,
|
||||
quotas: quotas,
|
||||
xact: xact,
|
||||
log: log,
|
||||
@ -69,6 +75,38 @@ func (service *AlertRuleService) GetAlertRule(ctx context.Context, orgID int64,
|
||||
return *query.Result, provenance, nil
|
||||
}
|
||||
|
||||
type AlertRuleWithFolderTitle struct {
|
||||
AlertRule models.AlertRule
|
||||
FolderTitle string
|
||||
}
|
||||
|
||||
// GetAlertRuleWithFolderTitle returns a single alert rule with its folder title.
|
||||
func (service *AlertRuleService) GetAlertRuleWithFolderTitle(ctx context.Context, orgID int64, ruleUID string) (AlertRuleWithFolderTitle, error) {
|
||||
query := &models.GetAlertRuleByUIDQuery{
|
||||
OrgID: orgID,
|
||||
UID: ruleUID,
|
||||
}
|
||||
err := service.ruleStore.GetAlertRuleByUID(ctx, query)
|
||||
if err != nil {
|
||||
return AlertRuleWithFolderTitle{}, err
|
||||
}
|
||||
|
||||
dq := dashboards.GetDashboardQuery{
|
||||
OrgID: orgID,
|
||||
UID: query.Result.NamespaceUID,
|
||||
}
|
||||
|
||||
dash, err := service.dashboardService.GetDashboard(ctx, &dq)
|
||||
if err != nil {
|
||||
return AlertRuleWithFolderTitle{}, err
|
||||
}
|
||||
|
||||
return AlertRuleWithFolderTitle{
|
||||
AlertRule: *query.Result,
|
||||
FolderTitle: dash.Title,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateAlertRule creates a new alert rule. This function will ignore any
|
||||
// interval that is set in the rule struct and use the already existing group
|
||||
// interval or the default one.
|
||||
@ -114,10 +152,10 @@ func (service *AlertRuleService) CreateAlertRule(ctx context.Context, rule model
|
||||
return rule, nil
|
||||
}
|
||||
|
||||
func (service *AlertRuleService) GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (models.AlertRuleGroup, error) {
|
||||
func (service *AlertRuleService) GetRuleGroup(ctx context.Context, orgID int64, namespaceUID, group string) (models.AlertRuleGroup, error) {
|
||||
q := models.ListAlertRulesQuery{
|
||||
OrgID: orgID,
|
||||
NamespaceUIDs: []string{folder},
|
||||
NamespaceUIDs: []string{namespaceUID},
|
||||
RuleGroup: group,
|
||||
}
|
||||
if err := service.ruleStore.ListAlertRules(ctx, &q); err != nil {
|
||||
@ -366,6 +404,114 @@ func (service *AlertRuleService) deleteRules(ctx context.Context, orgID int64, t
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAlertRuleGroupWithFolderTitle returns the alert rule group with folder title.
|
||||
func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Context, orgID int64, namespaceUID, group string) (file.AlertRuleGroupWithFolderTitle, error) {
|
||||
q := models.ListAlertRulesQuery{
|
||||
OrgID: orgID,
|
||||
NamespaceUIDs: []string{namespaceUID},
|
||||
RuleGroup: group,
|
||||
}
|
||||
if err := service.ruleStore.ListAlertRules(ctx, &q); err != nil {
|
||||
return file.AlertRuleGroupWithFolderTitle{}, err
|
||||
}
|
||||
if len(q.Result) == 0 {
|
||||
return file.AlertRuleGroupWithFolderTitle{}, store.ErrAlertRuleGroupNotFound
|
||||
}
|
||||
|
||||
dq := dashboards.GetDashboardQuery{
|
||||
OrgID: orgID,
|
||||
UID: namespaceUID,
|
||||
}
|
||||
dash, err := service.dashboardService.GetDashboard(ctx, &dq)
|
||||
if err != nil {
|
||||
return file.AlertRuleGroupWithFolderTitle{}, err
|
||||
}
|
||||
|
||||
res := file.AlertRuleGroupWithFolderTitle{
|
||||
AlertRuleGroup: &models.AlertRuleGroup{
|
||||
Title: q.Result[0].RuleGroup,
|
||||
FolderUID: q.Result[0].NamespaceUID,
|
||||
Interval: q.Result[0].IntervalSeconds,
|
||||
Rules: []models.AlertRule{},
|
||||
},
|
||||
OrgID: orgID,
|
||||
FolderTitle: dash.Title,
|
||||
}
|
||||
for _, r := range q.Result {
|
||||
if r != nil {
|
||||
res.AlertRuleGroup.Rules = append(res.AlertRuleGroup.Rules, *r)
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetAlertGroupsWithFolderTitle returns all groups with folder title that have at least one alert.
|
||||
func (service *AlertRuleService) GetAlertGroupsWithFolderTitle(ctx context.Context, orgID int64) ([]file.AlertRuleGroupWithFolderTitle, error) {
|
||||
q := models.ListAlertRulesQuery{
|
||||
OrgID: orgID,
|
||||
}
|
||||
|
||||
if err := service.ruleStore.ListAlertRules(ctx, &q); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groups := make(map[models.AlertRuleGroupKey][]models.AlertRule)
|
||||
namespaces := make(map[string][]*models.AlertRuleGroupKey)
|
||||
for _, r := range q.Result {
|
||||
groupKey := r.GetGroupKey()
|
||||
group := groups[groupKey]
|
||||
group = append(group, *r)
|
||||
groups[groupKey] = group
|
||||
|
||||
namespaces[r.NamespaceUID] = append(namespaces[r.NamespaceUID], &groupKey)
|
||||
}
|
||||
|
||||
dq := dashboards.GetDashboardsQuery{
|
||||
DashboardUIDs: nil,
|
||||
}
|
||||
for uid := range namespaces {
|
||||
dq.DashboardUIDs = append(dq.DashboardUIDs, uid)
|
||||
}
|
||||
|
||||
// We need folder titles for the provisioning file format. We do it this way instead of using GetUserVisibleNamespaces to avoid folder:read permissions that should not apply to those with alert.provisioning:read.
|
||||
dashes, err := service.dashboardService.GetDashboards(ctx, &dq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
folderUidToTitle := make(map[string]string)
|
||||
for _, dash := range dashes {
|
||||
folderUidToTitle[dash.UID] = dash.Title
|
||||
}
|
||||
|
||||
result := make([]file.AlertRuleGroupWithFolderTitle, 0)
|
||||
for groupKey, rules := range groups {
|
||||
title, ok := folderUidToTitle[groupKey.NamespaceUID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot find title for folder with uid '%s'", groupKey.NamespaceUID)
|
||||
}
|
||||
result = append(result, file.AlertRuleGroupWithFolderTitle{
|
||||
AlertRuleGroup: &models.AlertRuleGroup{
|
||||
Title: rules[0].RuleGroup,
|
||||
FolderUID: rules[0].NamespaceUID,
|
||||
Interval: rules[0].IntervalSeconds,
|
||||
Rules: rules,
|
||||
},
|
||||
OrgID: orgID,
|
||||
FolderTitle: title,
|
||||
})
|
||||
}
|
||||
|
||||
// Return results in a stable manner.
|
||||
sort.SliceStable(result, func(i, j int) bool {
|
||||
if result[i].AlertRuleGroup.FolderUID == result[j].AlertRuleGroup.FolderUID {
|
||||
return result[i].AlertRuleGroup.Title < result[j].AlertRuleGroup.Title
|
||||
}
|
||||
return result[i].AlertRuleGroup.FolderUID < result[j].AlertRuleGroup.FolderUID
|
||||
})
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// syncRuleGroupFields synchronizes calculated fields across multiple rules in a group.
|
||||
func syncGroupRuleFields(group *models.AlertRuleGroup, orgID int64) *models.AlertRuleGroup {
|
||||
for i := range group.Rules {
|
||||
|
@ -1,4 +1,4 @@
|
||||
package alerting
|
||||
package file
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@ -31,11 +31,11 @@ type AlertRuleGroupV1 struct {
|
||||
Rules []AlertRuleV1 `json:"rules" yaml:"rules"`
|
||||
}
|
||||
|
||||
func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (AlertRuleGroup, error) {
|
||||
ruleGroup := AlertRuleGroup{}
|
||||
ruleGroup.Name = ruleGroupV1.Name.Value()
|
||||
if strings.TrimSpace(ruleGroup.Name) == "" {
|
||||
return AlertRuleGroup{}, errors.New("rule group has no name set")
|
||||
func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (AlertRuleGroupWithFolderTitle, error) {
|
||||
ruleGroup := AlertRuleGroupWithFolderTitle{AlertRuleGroup: &models.AlertRuleGroup{}}
|
||||
ruleGroup.Title = ruleGroupV1.Name.Value()
|
||||
if strings.TrimSpace(ruleGroup.Title) == "" {
|
||||
return AlertRuleGroupWithFolderTitle{}, errors.New("rule group has no name set")
|
||||
}
|
||||
ruleGroup.OrgID = ruleGroupV1.OrgID.Value()
|
||||
if ruleGroup.OrgID < 1 {
|
||||
@ -43,29 +43,27 @@ func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (AlertRuleGroup, error) {
|
||||
}
|
||||
interval, err := model.ParseDuration(ruleGroupV1.Interval.Value())
|
||||
if err != nil {
|
||||
return AlertRuleGroup{}, err
|
||||
return AlertRuleGroupWithFolderTitle{}, err
|
||||
}
|
||||
ruleGroup.Interval = time.Duration(interval)
|
||||
ruleGroup.Folder = ruleGroupV1.Folder.Value()
|
||||
if strings.TrimSpace(ruleGroup.Folder) == "" {
|
||||
return AlertRuleGroup{}, errors.New("rule group has no folder set")
|
||||
ruleGroup.Interval = int64(time.Duration(interval).Seconds())
|
||||
ruleGroup.FolderTitle = ruleGroupV1.Folder.Value()
|
||||
if strings.TrimSpace(ruleGroup.FolderTitle) == "" {
|
||||
return AlertRuleGroupWithFolderTitle{}, errors.New("rule group has no folder set")
|
||||
}
|
||||
for _, ruleV1 := range ruleGroupV1.Rules {
|
||||
rule, err := ruleV1.mapToModel(ruleGroup.OrgID)
|
||||
if err != nil {
|
||||
return AlertRuleGroup{}, err
|
||||
return AlertRuleGroupWithFolderTitle{}, err
|
||||
}
|
||||
ruleGroup.Rules = append(ruleGroup.Rules, rule)
|
||||
}
|
||||
return ruleGroup, nil
|
||||
}
|
||||
|
||||
type AlertRuleGroup struct {
|
||||
OrgID int64
|
||||
Name string
|
||||
Folder string
|
||||
Interval time.Duration
|
||||
Rules []models.AlertRule
|
||||
type AlertRuleGroupWithFolderTitle struct {
|
||||
*models.AlertRuleGroup
|
||||
OrgID int64
|
||||
FolderTitle string
|
||||
}
|
||||
|
||||
type AlertRuleV1 struct {
|
||||
@ -175,3 +173,130 @@ func (queryV1 *QueryV1) mapToModel() (models.AlertQuery, error) {
|
||||
Model: rawMessage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Response structs
|
||||
|
||||
// AlertingFileExport is the full provisioned file export.
|
||||
// swagger:model
|
||||
type AlertingFileExport struct {
|
||||
APIVersion int64 `json:"apiVersion" yaml:"apiVersion"`
|
||||
Groups []AlertRuleGroupExport `json:"groups" yaml:"groups"`
|
||||
}
|
||||
|
||||
// AlertRuleGroupExport is the provisioned file export of AlertRuleGroupV1.
|
||||
type AlertRuleGroupExport struct {
|
||||
OrgID int64 `json:"orgId" yaml:"orgId"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Folder string `json:"folder" yaml:"folder"`
|
||||
Interval model.Duration `json:"interval" yaml:"interval"`
|
||||
Rules []AlertRuleExport `json:"rules" yaml:"rules"`
|
||||
}
|
||||
|
||||
// AlertRuleExport is the provisioned file export of models.AlertRule.
|
||||
type AlertRuleExport struct {
|
||||
UID string `json:"uid" yaml:"uid"`
|
||||
Title string `json:"title" yaml:"title"`
|
||||
Condition string `json:"condition" yaml:"condition"`
|
||||
Data []AlertQueryExport `json:"data" yaml:"data"`
|
||||
DashboardUID string `json:"dasboardUid,omitempty" yaml:"dashboardUid,omitempty"`
|
||||
PanelID int64 `json:"panelId,omitempty" yaml:"panelId,omitempty"`
|
||||
NoDataState models.NoDataState `json:"noDataState" yaml:"noDataState"`
|
||||
ExecErrState models.ExecutionErrorState `json:"execErrState" yaml:"execErrState"`
|
||||
For model.Duration `json:"for" yaml:"for"`
|
||||
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// AlertQueryExport is the provisioned export of models.AlertQuery.
|
||||
type AlertQueryExport struct {
|
||||
RefID string `json:"refId" yaml:"refId"`
|
||||
QueryType string `json:"queryType,omitempty" yaml:"queryType,omitempty"`
|
||||
RelativeTimeRange models.RelativeTimeRange `json:"relativeTimeRange,omitempty" yaml:"relativeTimeRange,omitempty"`
|
||||
DatasourceUID string `json:"datasourceUid" yaml:"datasourceUid"`
|
||||
Model map[string]interface{} `json:"model" yaml:"model"`
|
||||
}
|
||||
|
||||
// NewAlertingFileExport creates an AlertingFileExport DTO from []AlertRuleGroupWithFolderTitle.
|
||||
func NewAlertingFileExport(groups []AlertRuleGroupWithFolderTitle) (AlertingFileExport, error) {
|
||||
f := AlertingFileExport{APIVersion: 1}
|
||||
for _, group := range groups {
|
||||
export, err := newAlertRuleGroupExport(group)
|
||||
if err != nil {
|
||||
return AlertingFileExport{}, err
|
||||
}
|
||||
f.Groups = append(f.Groups, export)
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// newAlertRuleGroupExport creates a AlertRuleGroupExport DTO from models.AlertRuleGroup.
|
||||
func newAlertRuleGroupExport(d AlertRuleGroupWithFolderTitle) (AlertRuleGroupExport, error) {
|
||||
rules := make([]AlertRuleExport, 0, len(d.Rules))
|
||||
for i := range d.Rules {
|
||||
alert, err := newAlertRuleExport(d.Rules[i])
|
||||
if err != nil {
|
||||
return AlertRuleGroupExport{}, err
|
||||
}
|
||||
rules = append(rules, alert)
|
||||
}
|
||||
return AlertRuleGroupExport{
|
||||
OrgID: d.OrgID,
|
||||
Name: d.Title,
|
||||
Folder: d.FolderTitle,
|
||||
Interval: model.Duration(time.Duration(d.Interval) * time.Second),
|
||||
Rules: rules,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newAlertRuleExport creates a AlertRuleExport DTO from models.AlertRule.
|
||||
func newAlertRuleExport(rule models.AlertRule) (AlertRuleExport, error) {
|
||||
data := make([]AlertQueryExport, 0, len(rule.Data))
|
||||
for i := range rule.Data {
|
||||
query, err := newAlertQueryExport(rule.Data[i])
|
||||
if err != nil {
|
||||
return AlertRuleExport{}, err
|
||||
}
|
||||
data = append(data, query)
|
||||
}
|
||||
|
||||
var dashboardUID string
|
||||
if rule.DashboardUID != nil {
|
||||
dashboardUID = *rule.DashboardUID
|
||||
}
|
||||
|
||||
var panelID int64
|
||||
if rule.PanelID != nil {
|
||||
panelID = *rule.PanelID
|
||||
}
|
||||
|
||||
return AlertRuleExport{
|
||||
UID: rule.UID,
|
||||
Title: rule.Title,
|
||||
For: model.Duration(rule.For),
|
||||
Condition: rule.Condition,
|
||||
Data: data,
|
||||
DashboardUID: dashboardUID,
|
||||
PanelID: panelID,
|
||||
NoDataState: rule.NoDataState,
|
||||
ExecErrState: rule.ExecErrState,
|
||||
Annotations: rule.Annotations,
|
||||
Labels: rule.Labels,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newAlertQueryExport creates a AlertQueryExport DTO from models.AlertQuery.
|
||||
func newAlertQueryExport(query models.AlertQuery) (AlertQueryExport, error) {
|
||||
// We unmarshal the json.RawMessage model into a map in order to facilitate yaml marshalling.
|
||||
var mdl map[string]interface{}
|
||||
err := json.Unmarshal(query.Model, &mdl)
|
||||
if err != nil {
|
||||
return AlertQueryExport{}, err
|
||||
}
|
||||
return AlertQueryExport{
|
||||
RefID: query.RefID,
|
||||
QueryType: query.QueryType,
|
||||
RelativeTimeRange: query.RelativeTimeRange,
|
||||
DatasourceUID: query.DatasourceUID,
|
||||
Model: mdl,
|
||||
}, nil
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
package alerting
|
||||
package file
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/values"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/values"
|
||||
)
|
||||
|
||||
func TestRuleGroup(t *testing.T) {
|
||||
@ -60,7 +61,7 @@ func TestRuleGroup(t *testing.T) {
|
||||
rg.Interval = interval
|
||||
rgMapped, err := rg.MapToModel()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 48*time.Hour, rgMapped.Interval)
|
||||
require.Equal(t, int64(48*time.Hour/time.Second), rgMapped.Interval)
|
||||
})
|
||||
t.Run("a rule group with an empty org id should default to 1", func(t *testing.T) {
|
||||
rg := validRuleGroupV1(t)
|
@ -41,24 +41,24 @@ func (prov *defaultAlertRuleProvisioner) Provision(ctx context.Context,
|
||||
files []*AlertingFile) error {
|
||||
for _, file := range files {
|
||||
for _, group := range file.Groups {
|
||||
folderUID, err := prov.getOrCreateFolderUID(ctx, group.Folder, group.OrgID)
|
||||
folderUID, err := prov.getOrCreateFolderUID(ctx, group.FolderTitle, group.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prov.logger.Debug("provisioning alert rule group",
|
||||
"org", group.OrgID,
|
||||
"folder", group.Folder,
|
||||
"folder", group.FolderTitle,
|
||||
"folderUID", folderUID,
|
||||
"name", group.Name)
|
||||
"name", group.Title)
|
||||
for _, rule := range group.Rules {
|
||||
rule.NamespaceUID = folderUID
|
||||
rule.RuleGroup = group.Name
|
||||
err = prov.provisionRule(ctx, group.OrgID, rule, group.Folder, folderUID)
|
||||
rule.RuleGroup = group.Title
|
||||
err = prov.provisionRule(ctx, group.OrgID, rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = prov.ruleService.UpdateRuleGroup(ctx, group.OrgID, folderUID, group.Name, int64(group.Interval.Seconds()))
|
||||
err = prov.ruleService.UpdateRuleGroup(ctx, group.OrgID, folderUID, group.Title, group.Interval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -77,9 +77,7 @@ func (prov *defaultAlertRuleProvisioner) Provision(ctx context.Context,
|
||||
func (prov *defaultAlertRuleProvisioner) provisionRule(
|
||||
ctx context.Context,
|
||||
orgID int64,
|
||||
rule alert_models.AlertRule,
|
||||
folder,
|
||||
folderUID string) error {
|
||||
rule alert_models.AlertRule) error {
|
||||
prov.logger.Debug("provisioning alert rule", "uid", rule.UID, "org", rule.OrgID)
|
||||
_, _, err := prov.ruleService.GetAlertRule(ctx, orgID, rule.UID)
|
||||
if err != nil && !errors.Is(err, alert_models.ErrAlertRuleNotFound) {
|
||||
|
@ -3,6 +3,7 @@ package alerting
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/alerting/file"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/values"
|
||||
)
|
||||
|
||||
@ -15,8 +16,8 @@ type OrgID int64
|
||||
type AlertingFile struct {
|
||||
configVersion
|
||||
Filename string
|
||||
Groups []AlertRuleGroup
|
||||
DeleteRules []RuleDelete
|
||||
Groups []file.AlertRuleGroupWithFolderTitle
|
||||
DeleteRules []file.RuleDelete
|
||||
ContactPoints []ContactPoint
|
||||
DeleteContactPoints []DeleteContactPoint
|
||||
Policies []NotificiationPolicy
|
||||
@ -30,8 +31,8 @@ type AlertingFile struct {
|
||||
type AlertingFileV1 struct {
|
||||
configVersion
|
||||
Filename string
|
||||
Groups []AlertRuleGroupV1 `json:"groups" yaml:"groups"`
|
||||
DeleteRules []RuleDeleteV1 `json:"deleteRules" yaml:"deleteRules"`
|
||||
Groups []file.AlertRuleGroupV1 `json:"groups" yaml:"groups"`
|
||||
DeleteRules []file.RuleDeleteV1 `json:"deleteRules" yaml:"deleteRules"`
|
||||
ContactPoints []ContactPointV1 `json:"contactPoints" yaml:"contactPoints"`
|
||||
DeleteContactPoints []DeleteContactPointV1 `json:"deleteContactPoints" yaml:"deleteContactPoints"`
|
||||
Policies []NotificiationPolicyV1 `json:"policies" yaml:"policies"`
|
||||
@ -132,7 +133,7 @@ func (fileV1 *AlertingFileV1) mapRules(alertingFile *AlertingFile) error {
|
||||
if orgID < 1 {
|
||||
orgID = 1
|
||||
}
|
||||
ruleDelete := RuleDelete{
|
||||
ruleDelete := file.RuleDelete{
|
||||
UID: ruleDeleteV1.UID.Value(),
|
||||
OrgID: orgID,
|
||||
}
|
||||
|
@ -269,6 +269,7 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error
|
||||
ruleService := provisioning.NewAlertRuleService(
|
||||
st,
|
||||
st,
|
||||
ps.dashboardService,
|
||||
ps.quotaService,
|
||||
ps.SQLStore,
|
||||
int64(ps.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
||||
|
@ -2496,6 +2496,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/export": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"provisioning"
|
||||
],
|
||||
"summary": "Export all alert rules in provisioning file format.",
|
||||
"operationId": "RouteGetAlertRulesExport",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"name": "download",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/{UID}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -2591,6 +2620,47 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/alert-rules/{UID}/export": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/yaml",
|
||||
"text/yaml"
|
||||
],
|
||||
"tags": [
|
||||
"provisioning"
|
||||
],
|
||||
"summary": "Export an alert rule in provisioning file format.",
|
||||
"operationId": "RouteGetAlertRuleExport",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Alert rule UID",
|
||||
"name": "UID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"name": "download",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/contact-points": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -2794,6 +2864,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json",
|
||||
"application/yaml",
|
||||
"text/yaml"
|
||||
],
|
||||
"tags": [
|
||||
"provisioning"
|
||||
],
|
||||
"summary": "Export an alert rule group in provisioning file format.",
|
||||
"operationId": "RouteGetAlertRuleGroupExport",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "FolderUID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Group",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether to initiate a download of the file or not.",
|
||||
"name": "download",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "AlertingFileExport",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/AlertingFileExport"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": " Not found."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/mute-timings": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -11044,6 +11160,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertQueryExport": {
|
||||
"type": "object",
|
||||
"title": "AlertQueryExport is the provisioned export of models.AlertQuery.",
|
||||
"properties": {
|
||||
"datasourceUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"type": "object",
|
||||
"additionalProperties": false
|
||||
},
|
||||
"queryType": {
|
||||
"type": "string"
|
||||
},
|
||||
"refId": {
|
||||
"type": "string"
|
||||
},
|
||||
"relativeTimeRange": {
|
||||
"$ref": "#/definitions/RelativeTimeRange"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -11064,6 +11202,65 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertRuleExport": {
|
||||
"type": "object",
|
||||
"title": "AlertRuleExport is the provisioned file export of models.AlertRule.",
|
||||
"properties": {
|
||||
"annotations": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"condition": {
|
||||
"type": "string"
|
||||
},
|
||||
"dasboardUid": {
|
||||
"type": "string"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertQueryExport"
|
||||
}
|
||||
},
|
||||
"execErrState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Alerting",
|
||||
"Error",
|
||||
"OK"
|
||||
]
|
||||
},
|
||||
"for": {
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"labels": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"noDataState": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"Alerting",
|
||||
"NoData",
|
||||
"OK"
|
||||
]
|
||||
},
|
||||
"panelId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"uid": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertRuleGroup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -11085,6 +11282,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertRuleGroupExport": {
|
||||
"type": "object",
|
||||
"title": "AlertRuleGroupExport is the provisioned file export of AlertRuleGroupV1.",
|
||||
"properties": {
|
||||
"folder": {
|
||||
"type": "string"
|
||||
},
|
||||
"interval": {
|
||||
"$ref": "#/definitions/Duration"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"orgId": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"rules": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertRuleExport"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertRuleGroupMetadata": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@ -11174,6 +11396,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertingFileExport": {
|
||||
"type": "object",
|
||||
"title": "AlertingFileExport is the full provisioned file export.",
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/AlertRuleGroupExport"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AlertingRule": {
|
||||
"description": "adapted from cortex",
|
||||
"type": "object",
|
||||
@ -17824,9 +18062,8 @@
|
||||
"type": "string"
|
||||
},
|
||||
"URL": {
|
||||
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.",
|
||||
"type": "object",
|
||||
"title": "A URL represents a parsed URL (technically, a URI reference).",
|
||||
"title": "URL is a custom URL type that allows validation at configuration load time.",
|
||||
"properties": {
|
||||
"ForceQuery": {
|
||||
"type": "boolean"
|
||||
@ -18852,6 +19089,7 @@
|
||||
}
|
||||
},
|
||||
"gettableAlerts": {
|
||||
"description": "GettableAlerts gettable alerts",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableAlert"
|
||||
@ -18912,7 +19150,6 @@
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
"description": "Integration integration",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
|
Loading…
Reference in New Issue
Block a user