Alerting: Support for imported Templates (#114196)
* refactor template service to contstruct notification template in one place, get provenance before creating and calculate resource version after. * refactor get by UID and name * introduce template kind in NotificationTemplate * introduce includeImported flag and use in the k8s api * support imported templates * add kind to template uid * tests for imported templates * update API model * set kind to default templates
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package v0alpha1
|
||||
|
||||
TemplateKind: *"grafana" | "mimir"
|
||||
|
||||
TemplateGroupSpec: {
|
||||
title: string
|
||||
content: string
|
||||
kind: TemplateKind
|
||||
}
|
||||
|
||||
Generated
+14
-3
@@ -2,13 +2,24 @@
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type TemplateGroupTemplateKind string
|
||||
|
||||
const (
|
||||
TemplateGroupTemplateKindGrafana TemplateGroupTemplateKind = "grafana"
|
||||
TemplateGroupTemplateKindMimir TemplateGroupTemplateKind = "mimir"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type TemplateGroupSpec struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Kind TemplateGroupTemplateKind `json:"kind"`
|
||||
}
|
||||
|
||||
// NewTemplateGroupSpec creates a new TemplateGroupSpec object.
|
||||
func NewTemplateGroupSpec() *TemplateGroupSpec {
|
||||
return &TemplateGroupSpec{}
|
||||
return &TemplateGroupSpec{
|
||||
Kind: TemplateGroupTemplateKindGrafana,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ var (
|
||||
rawSchemaRoutingTreev0alpha1 = []byte(`{"Matcher":{"additionalProperties":false,"properties":{"label":{"type":"string"},"type":{"enum":["=","!=","=~","!~"],"type":"string"},"value":{"type":"string"}},"required":["type","label","value"],"type":"object"},"Route":{"additionalProperties":false,"properties":{"active_time_intervals":{"items":{"type":"string"},"type":"array"},"continue":{"type":"boolean"},"group_by":{"items":{"type":"string"},"type":"array"},"group_interval":{"type":"string"},"group_wait":{"type":"string"},"matchers":{"items":{"$ref":"#/components/schemas/Matcher"},"type":"array"},"mute_time_intervals":{"items":{"type":"string"},"type":"array"},"receiver":{"type":"string"},"repeat_interval":{"type":"string"},"routes":{"items":{"$ref":"#/components/schemas/Route"},"type":"array"}},"required":["continue"],"type":"object"},"RouteDefaults":{"additionalProperties":false,"properties":{"group_by":{"items":{"type":"string"},"type":"array"},"group_interval":{"type":"string"},"group_wait":{"type":"string"},"receiver":{"type":"string"},"repeat_interval":{"type":"string"}},"required":["receiver"],"type":"object"},"RoutingTree":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"defaults":{"$ref":"#/components/schemas/RouteDefaults"},"routes":{"items":{"$ref":"#/components/schemas/Route"},"type":"array"}},"required":["defaults","routes"],"type":"object"}}`)
|
||||
versionSchemaRoutingTreev0alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaRoutingTreev0alpha1, &versionSchemaRoutingTreev0alpha1)
|
||||
rawSchemaTemplateGroupv0alpha1 = []byte(`{"TemplateGroup":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"content":{"type":"string"},"title":{"type":"string"}},"required":["title","content"],"type":"object"}}`)
|
||||
rawSchemaTemplateGroupv0alpha1 = []byte(`{"TemplateGroup":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"TemplateKind":{"enum":["grafana","mimir"],"type":"string"},"spec":{"additionalProperties":false,"properties":{"content":{"type":"string"},"kind":{"$ref":"#/components/schemas/TemplateKind","default":"grafana"},"title":{"type":"string"}},"required":["title","content","kind"],"type":"object"}}`)
|
||||
versionSchemaTemplateGroupv0alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaTemplateGroupv0alpha1, &versionSchemaTemplateGroupv0alpha1)
|
||||
rawSchemaTimeIntervalv0alpha1 = []byte(`{"Interval":{"additionalProperties":false,"properties":{"days_of_month":{"items":{"type":"string"},"type":"array"},"location":{"type":"string"},"months":{"items":{"type":"string"},"type":"array"},"times":{"items":{"$ref":"#/components/schemas/TimeRange"},"type":"array"},"weekdays":{"items":{"type":"string"},"type":"array"},"years":{"items":{"type":"string"},"type":"array"}},"type":"object"},"TimeInterval":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"TimeRange":{"additionalProperties":false,"properties":{"end_time":{"type":"string"},"start_time":{"type":"string"}},"required":["start_time","end_time"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"name":{"type":"string"},"time_intervals":{"items":{"$ref":"#/components/schemas/Interval"},"type":"array"}},"required":["name","time_intervals"],"type":"object"}}`)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/timeinterval"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/appinstaller"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
@@ -97,7 +98,12 @@ func (a AlertingNotificationsAppInstaller) GetLegacyStorage(gvr schema.GroupVers
|
||||
} else if gvr == timeinterval.ResourceInfo.GroupVersionResource() {
|
||||
return timeinterval.NewStorage(api.MuteTimings, namespacer)
|
||||
} else if gvr == templategroup.ResourceInfo.GroupVersionResource() {
|
||||
return templategroup.NewStorage(api.Templates, namespacer)
|
||||
srv := api.Templates
|
||||
//nolint:staticcheck // not yet migrated to OpenFeature
|
||||
if a.ng.FeatureToggles.IsEnabledGlobally(featuremgmt.FlagAlertingImportAlertmanagerAPI) {
|
||||
srv = srv.WithIncludeImported()
|
||||
}
|
||||
return templategroup.NewStorage(srv, namespacer)
|
||||
} else if gvr == routingtree.ResourceInfo.GroupVersionResource() {
|
||||
return routingtree.NewStorage(api.Policies, namespacer)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package templategroup
|
||||
|
||||
import (
|
||||
"github.com/grafana/alerting/definition"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@@ -36,6 +37,7 @@ func convertToK8sResource(orgID int64, template definitions.NotificationTemplate
|
||||
Spec: model.TemplateGroupSpec{
|
||||
Title: template.Name,
|
||||
Content: template.Template,
|
||||
Kind: model.TemplateGroupTemplateKind(template.Kind),
|
||||
},
|
||||
}
|
||||
result.SetProvenanceStatus(string(template.Provenance))
|
||||
@@ -50,5 +52,6 @@ func convertToDomainModel(template *model.TemplateGroup) definitions.Notificatio
|
||||
Template: template.Spec.Content,
|
||||
ResourceVersion: template.ResourceVersion,
|
||||
Provenance: definitions.Provenance(ngmodels.ProvenanceNone),
|
||||
Kind: definition.TemplateKind(template.Spec.Kind),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/alerting/definition"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -211,6 +212,7 @@ func (s *legacyStorage) defaultTemplate() (definitions.NotificationTemplate, err
|
||||
UID: defaultTemplate.Name,
|
||||
Provenance: definitions.Provenance("system"),
|
||||
Template: defaultTemplate.Template,
|
||||
Kind: definition.GrafanaTemplateKind,
|
||||
}
|
||||
|
||||
return dto, nil
|
||||
|
||||
@@ -5,7 +5,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/alerting/templates"
|
||||
"github.com/grafana/alerting/definition"
|
||||
"github.com/grafana/alerting/notify"
|
||||
"go.yaml.in/yaml/v3"
|
||||
)
|
||||
|
||||
@@ -31,11 +32,18 @@ func (t *NotificationTemplate) Validate() error {
|
||||
content = fmt.Sprintf("{{ define \"%s\" }}\n%s\n{{ end }}", t.Name, content)
|
||||
}
|
||||
t.Template = content
|
||||
def := templates.TemplateDefinition{
|
||||
Name: t.Name,
|
||||
Template: t.Template,
|
||||
Kind: templates.GrafanaKind,
|
||||
if t.Kind == "" {
|
||||
t.Kind = definition.GrafanaTemplateKind
|
||||
}
|
||||
postable := definition.PostableApiTemplate{
|
||||
Name: t.Name,
|
||||
Content: t.Template,
|
||||
Kind: t.Kind,
|
||||
}
|
||||
if err := postable.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
def := notify.PostableAPITemplateToTemplateDefinition(postable)
|
||||
return def.Validate()
|
||||
}
|
||||
|
||||
|
||||
@@ -462,6 +462,16 @@ func TestValidateNotificationTemplates(t *testing.T) {
|
||||
},
|
||||
expContent: `{{ define "Alert Instance Template" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}[what is this?]`,
|
||||
},
|
||||
{
|
||||
name: "unknown template kind",
|
||||
template: NotificationTemplate{
|
||||
Name: "Alert Instance Template",
|
||||
Template: `{{ define "Same name as definition" }}\nFiring: {{ .Labels.alertname }}\nSilence: {{ .SilenceURL }}\n{{ end }}`,
|
||||
Provenance: "test",
|
||||
Kind: "unknown",
|
||||
},
|
||||
expError: errors.New("unknown template kind: unknown"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tc {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package definitions
|
||||
|
||||
import "github.com/grafana/alerting/definition"
|
||||
|
||||
// swagger:route GET /v1/provisioning/templates provisioning stable RouteGetTemplates
|
||||
//
|
||||
// Get all notification template groups.
|
||||
@@ -55,11 +57,12 @@ type RouteDeleteTemplateParam struct {
|
||||
|
||||
// swagger:model
|
||||
type NotificationTemplate struct {
|
||||
UID string `json:"-" yaml:"-"`
|
||||
Name string `json:"name"`
|
||||
Template string `json:"template"`
|
||||
Provenance Provenance `json:"provenance,omitempty"`
|
||||
ResourceVersion string `json:"version,omitempty"`
|
||||
UID string `json:"-" yaml:"-"`
|
||||
Name string `json:"name"`
|
||||
Template string `json:"template"`
|
||||
Provenance Provenance `json:"provenance,omitempty"`
|
||||
ResourceVersion string `json:"version,omitempty"`
|
||||
Kind definition.TemplateKind `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// swagger:model
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
@@ -25,6 +26,10 @@ var (
|
||||
ErrTemplateNotFound = errutil.NotFound("alerting.notifications.templates.notFound")
|
||||
ErrTemplateInvalid = errutil.BadRequest("alerting.notifications.templates.invalidFormat").MustTemplate("Invalid format of the submitted template", errutil.WithPublic("Template is in invalid format. Correct the payload and try again."))
|
||||
ErrTemplateExists = errutil.BadRequest("alerting.notifications.templates.nameExists", errutil.WithPublicMessage("Template file with this name already exists. Use a different name or update existing one."))
|
||||
ErrTemplateOrigin = errutil.BadRequest("alerting.notifications.templates.originInvalid").MustTemplate(
|
||||
"Template '{{ .Public.Name }}' cannot be {{ .Public.Action }}d because it belongs to an imported configuration.",
|
||||
errutil.WithPublic("Template '{{ .Public.Name }}' cannot be {{ .Public.Action }}d because it belongs to an imported configuration. Finish the import of the configuration first."),
|
||||
)
|
||||
|
||||
ErrContactPointReferenced = errutil.Conflict("alerting.notifications.contact-points.referenced", errutil.WithPublicMessage("Contact point is currently referenced by a notification policy."))
|
||||
ErrContactPointUsedInRule = errutil.Conflict("alerting.notifications.contact-points.used-by-rule", errutil.WithPublicMessage("Contact point is currently used in the notification settings of one or many alert rules."))
|
||||
@@ -129,3 +134,7 @@ func MakeErrContactPointUidExists(uid, name string) error {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func makeErrTemplateOrigin(t definitions.NotificationTemplate, action string) error {
|
||||
return ErrTemplateOrigin.Build(errutil.TemplateData{Public: map[string]interface{}{"Action": action, "Name": t.Name}})
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"sort"
|
||||
"unsafe"
|
||||
|
||||
"github.com/grafana/alerting/definition"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
@@ -23,6 +25,7 @@ type TemplateService struct {
|
||||
xact TransactionManager
|
||||
log log.Logger
|
||||
validator validation.ProvenanceStatusTransitionValidator
|
||||
includeImported bool
|
||||
}
|
||||
|
||||
func NewTemplateService(config alertmanagerConfigStore, prov ProvisioningStore, xact TransactionManager, log log.Logger) *TemplateService {
|
||||
@@ -32,6 +35,18 @@ func NewTemplateService(config alertmanagerConfigStore, prov ProvisioningStore,
|
||||
xact: xact,
|
||||
validator: validation.ValidateProvenanceRelaxed,
|
||||
log: log,
|
||||
includeImported: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TemplateService) WithIncludeImported() *TemplateService {
|
||||
return &TemplateService{
|
||||
configStore: t.configStore,
|
||||
provenanceStore: t.provenanceStore,
|
||||
xact: t.xact,
|
||||
validator: t.validator,
|
||||
log: t.log,
|
||||
includeImported: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,35 +56,38 @@ func (t *TemplateService) GetTemplates(ctx context.Context, orgID int64) ([]defi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(revision.Config.TemplateFiles) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var templates []definitions.NotificationTemplate
|
||||
|
||||
provenances, err := t.provenanceStore.GetProvenances(ctx, orgID, (&definitions.NotificationTemplate{}).ResourceType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
templates := make([]definitions.NotificationTemplate, 0, len(revision.Config.TemplateFiles))
|
||||
names := slices.Collect(maps.Keys(revision.Config.TemplateFiles))
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
content := revision.Config.TemplateFiles[name]
|
||||
tmpl := definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(name),
|
||||
Name: name,
|
||||
Template: content,
|
||||
ResourceVersion: calculateTemplateFingerprint(content),
|
||||
if len(revision.Config.TemplateFiles) > 0 {
|
||||
provenances, err := t.provenanceStore.GetProvenances(ctx, orgID, (&definitions.NotificationTemplate{}).ResourceType())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
provenance, ok := provenances[tmpl.ResourceID()]
|
||||
if !ok {
|
||||
provenance = models.ProvenanceNone
|
||||
templates = make([]definitions.NotificationTemplate, 0, len(revision.Config.TemplateFiles))
|
||||
names := slices.Collect(maps.Keys(revision.Config.TemplateFiles))
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
content := revision.Config.TemplateFiles[name]
|
||||
provenance, ok := provenances[(&definitions.NotificationTemplate{Name: name}).ResourceID()]
|
||||
if !ok {
|
||||
provenance = models.ProvenanceNone
|
||||
}
|
||||
templates = append(templates, newNotificationTemplate(name, content, provenance, definition.GrafanaTemplateKind))
|
||||
}
|
||||
tmpl.Provenance = definitions.Provenance(provenance)
|
||||
templates = append(templates, tmpl)
|
||||
}
|
||||
|
||||
return templates, nil
|
||||
var importedTemplates []definitions.NotificationTemplate
|
||||
if t.includeImported && len(revision.Config.ExtraConfigs) > 0 && len(revision.Config.ExtraConfigs[0].TemplateFiles) > 0 {
|
||||
imported := revision.Config.ExtraConfigs[0].TemplateFiles
|
||||
importedTemplates = make([]definitions.NotificationTemplate, 0, len(imported))
|
||||
names := slices.Collect(maps.Keys(imported))
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
content := imported[name]
|
||||
templates = append(templates, newNotificationTemplate(name, content, models.ProvenanceConvertedPrometheus, definition.MimirTemplateKind))
|
||||
}
|
||||
}
|
||||
return append(templates, importedTemplates...), nil
|
||||
}
|
||||
|
||||
func (t *TemplateService) GetTemplate(ctx context.Context, orgID int64, nameOrUid string) (definitions.NotificationTemplate, error) {
|
||||
@@ -77,29 +95,21 @@ func (t *TemplateService) GetTemplate(ctx context.Context, orgID int64, nameOrUi
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, err
|
||||
}
|
||||
|
||||
existingName := nameOrUid
|
||||
existingContent, ok := revision.Config.TemplateFiles[nameOrUid]
|
||||
if !ok {
|
||||
existingName, existingContent, ok = getTemplateByUid(revision.Config.TemplateFiles, nameOrUid)
|
||||
}
|
||||
if !ok {
|
||||
return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("")
|
||||
}
|
||||
|
||||
tmpl := definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(existingName),
|
||||
Name: existingName,
|
||||
Template: existingContent,
|
||||
ResourceVersion: calculateTemplateFingerprint(existingContent),
|
||||
}
|
||||
|
||||
provenance, err := t.provenanceStore.GetProvenance(ctx, &tmpl, orgID)
|
||||
result, found, err := t.getTemplateByName(ctx, revision, orgID, nameOrUid)
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, err
|
||||
}
|
||||
tmpl.Provenance = definitions.Provenance(provenance)
|
||||
return tmpl, nil
|
||||
if found {
|
||||
return result, nil
|
||||
}
|
||||
result, found, err = t.getTemplateByUID(ctx, revision, orgID, nameOrUid)
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, err
|
||||
}
|
||||
if found {
|
||||
return result, nil
|
||||
}
|
||||
return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("")
|
||||
}
|
||||
|
||||
func (t *TemplateService) UpsertTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) {
|
||||
@@ -135,6 +145,10 @@ func (t *TemplateService) CreateTemplate(ctx context.Context, orgID int64, tmpl
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, MakeErrTemplateInvalid(err)
|
||||
}
|
||||
if tmpl.Kind == definition.MimirTemplateKind {
|
||||
return definitions.NotificationTemplate{}, MakeErrTemplateInvalid(errors.New("templates of kind 'Mimir' cannot be created"))
|
||||
}
|
||||
|
||||
revision, err := t.configStore.Get(ctx, orgID)
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, err
|
||||
@@ -143,6 +157,10 @@ func (t *TemplateService) CreateTemplate(ctx context.Context, orgID int64, tmpl
|
||||
}
|
||||
|
||||
func (t *TemplateService) createTemplate(ctx context.Context, revision *legacy_storage.ConfigRevision, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) {
|
||||
if tmpl.Kind == definition.MimirTemplateKind {
|
||||
return definitions.NotificationTemplate{}, MakeErrTemplateInvalid(errors.New("templates of kind 'Mimir' cannot be created"))
|
||||
}
|
||||
|
||||
if revision.Config.TemplateFiles == nil {
|
||||
revision.Config.TemplateFiles = map[string]string{}
|
||||
}
|
||||
@@ -164,13 +182,7 @@ func (t *TemplateService) createTemplate(ctx context.Context, revision *legacy_s
|
||||
return definitions.NotificationTemplate{}, err
|
||||
}
|
||||
|
||||
return definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name),
|
||||
Name: tmpl.Name,
|
||||
Template: tmpl.Template,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
|
||||
}, nil
|
||||
return newNotificationTemplate(tmpl.Name, tmpl.Template, models.Provenance(tmpl.Provenance), tmpl.Kind), nil
|
||||
}
|
||||
|
||||
func (t *TemplateService) UpdateTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) {
|
||||
@@ -192,37 +204,39 @@ func (t *TemplateService) updateTemplate(ctx context.Context, revision *legacy_s
|
||||
}
|
||||
|
||||
var found bool
|
||||
var existingName, existingContent string
|
||||
var err error
|
||||
var existing definitions.NotificationTemplate
|
||||
// if UID is specified, look by UID.
|
||||
if tmpl.UID != "" {
|
||||
existingName, existingContent, found = getTemplateByUid(revision.Config.TemplateFiles, tmpl.UID)
|
||||
// do not fall back to name because we address by UID, and resource can be deleted\renamed
|
||||
existing, found, err = t.getTemplateByUID(ctx, revision, orgID, tmpl.UID)
|
||||
} else {
|
||||
existingName = tmpl.Name
|
||||
existingContent, found = revision.Config.TemplateFiles[existingName]
|
||||
existing, found, err = t.getTemplateByName(ctx, revision, orgID, tmpl.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, err
|
||||
}
|
||||
if !found {
|
||||
return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("")
|
||||
}
|
||||
|
||||
if existingName != tmpl.Name { // if template is renamed, check if this name is already taken
|
||||
if existing.Name != tmpl.Name { // if template is renamed, check if this name is already taken
|
||||
_, ok := revision.Config.TemplateFiles[tmpl.Name]
|
||||
if ok {
|
||||
// return error if template is being renamed to one that already exists
|
||||
return definitions.NotificationTemplate{}, ErrTemplateExists.Errorf("")
|
||||
}
|
||||
}
|
||||
|
||||
// check that provenance is not changed in an invalid way
|
||||
storedProvenance, err := t.provenanceStore.GetProvenance(ctx, &tmpl, orgID)
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, err
|
||||
if existing.Kind != tmpl.Kind {
|
||||
return definitions.NotificationTemplate{}, MakeErrTemplateInvalid(errors.New("cannot change template kind"))
|
||||
}
|
||||
if err := t.validator(storedProvenance, models.Provenance(tmpl.Provenance)); err != nil {
|
||||
if existing.Provenance == definitions.Provenance(models.ProvenanceConvertedPrometheus) {
|
||||
return definitions.NotificationTemplate{}, makeErrTemplateOrigin(existing, "update")
|
||||
}
|
||||
if err := t.validator(models.Provenance(existing.Provenance), models.Provenance(tmpl.Provenance)); err != nil {
|
||||
return definitions.NotificationTemplate{}, err
|
||||
}
|
||||
|
||||
err = t.checkOptimisticConcurrency(tmpl.Name, existingContent, models.Provenance(tmpl.Provenance), tmpl.ResourceVersion, "update")
|
||||
err = t.checkOptimisticConcurrency(existing.Name, existing.Template, models.Provenance(tmpl.Provenance), tmpl.ResourceVersion, "update")
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, err
|
||||
}
|
||||
@@ -230,9 +244,9 @@ func (t *TemplateService) updateTemplate(ctx context.Context, revision *legacy_s
|
||||
revision.Config.TemplateFiles[tmpl.Name] = tmpl.Template
|
||||
|
||||
err = t.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
if existingName != tmpl.Name { // if template by was found by UID and it's name is different, then this is the rename operation. Delete old resources.
|
||||
delete(revision.Config.TemplateFiles, existingName)
|
||||
err := t.provenanceStore.DeleteProvenance(ctx, &definitions.NotificationTemplate{Name: existingName}, orgID)
|
||||
if existing.Name != tmpl.Name { // if template by was found by UID and it's name is different, then this is the rename operation. Delete old resources.
|
||||
delete(revision.Config.TemplateFiles, existing.Name)
|
||||
err := t.provenanceStore.DeleteProvenance(ctx, &existing, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -247,13 +261,8 @@ func (t *TemplateService) updateTemplate(ctx context.Context, revision *legacy_s
|
||||
return definitions.NotificationTemplate{}, err
|
||||
}
|
||||
|
||||
return definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name), // if name was changed, this UID will not match the incoming one
|
||||
Name: tmpl.Name,
|
||||
Template: tmpl.Template,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
|
||||
}, nil
|
||||
// if name was changed, this UID needs to be recalculated
|
||||
return newNotificationTemplate(tmpl.Name, tmpl.Template, models.Provenance(tmpl.Provenance), tmpl.Kind), nil
|
||||
}
|
||||
|
||||
func (t *TemplateService) DeleteTemplate(ctx context.Context, orgID int64, nameOrUid string, provenance definitions.Provenance, version string) error {
|
||||
@@ -261,44 +270,39 @@ func (t *TemplateService) DeleteTemplate(ctx context.Context, orgID int64, nameO
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if revision.Config.TemplateFiles == nil {
|
||||
existing, found, err := t.getTemplateByName(ctx, revision, orgID, nameOrUid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
existing, found, err = t.getTemplateByUID(ctx, revision, orgID, nameOrUid)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
existingName := nameOrUid
|
||||
existing, ok := revision.Config.TemplateFiles[nameOrUid]
|
||||
if !ok {
|
||||
existingName, existing, ok = getTemplateByUid(revision.Config.TemplateFiles, nameOrUid)
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
if existing.Provenance == definitions.Provenance(models.ProvenanceConvertedPrometheus) {
|
||||
return makeErrTemplateOrigin(existing, "delete")
|
||||
}
|
||||
|
||||
err = t.checkOptimisticConcurrency(existingName, existing, models.Provenance(provenance), version, "delete")
|
||||
err = t.checkOptimisticConcurrency(existing.Name, existing.Template, models.Provenance(provenance), version, "delete")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check that provenance is not changed in an invalid way
|
||||
storedProvenance, err := t.provenanceStore.GetProvenance(ctx, &definitions.NotificationTemplate{Name: existingName}, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = t.validator(storedProvenance, models.Provenance(provenance)); err != nil {
|
||||
if err = t.validator(models.Provenance(existing.Provenance), models.Provenance(provenance)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(revision.Config.TemplateFiles, existingName)
|
||||
delete(revision.Config.TemplateFiles, existing.Name)
|
||||
|
||||
return t.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
if err := t.configStore.Save(ctx, revision, orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
tgt := definitions.NotificationTemplate{
|
||||
Name: existingName,
|
||||
}
|
||||
return t.provenanceStore.DeleteProvenance(ctx, &tgt, orgID)
|
||||
return t.provenanceStore.DeleteProvenance(ctx, &existing, orgID)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -323,11 +327,58 @@ func calculateTemplateFingerprint(t string) string {
|
||||
return fmt.Sprintf("%016x", sum.Sum64())
|
||||
}
|
||||
|
||||
func getTemplateByUid(templates map[string]string, uid string) (string, string, bool) {
|
||||
for n, tmpl := range templates {
|
||||
if legacy_storage.NameToUid(n) == uid {
|
||||
return n, tmpl, true
|
||||
}
|
||||
func newNotificationTemplate(name, content string, provenance models.Provenance, kind definition.TemplateKind) definitions.NotificationTemplate {
|
||||
tmpl := definitions.NotificationTemplate{
|
||||
UID: templateUID(kind, name),
|
||||
Name: name,
|
||||
Template: content,
|
||||
Provenance: definitions.Provenance(provenance),
|
||||
Kind: kind,
|
||||
}
|
||||
return "", "", false
|
||||
tmpl.ResourceVersion = calculateTemplateFingerprint(content)
|
||||
return tmpl
|
||||
}
|
||||
|
||||
func (t *TemplateService) getTemplateByName(ctx context.Context, revision *legacy_storage.ConfigRevision, orgID int64, name string) (definitions.NotificationTemplate, bool, error) {
|
||||
existingContent, ok := revision.Config.TemplateFiles[name]
|
||||
if !ok {
|
||||
return definitions.NotificationTemplate{}, false, nil
|
||||
}
|
||||
provenance, err := t.provenanceStore.GetProvenance(ctx, &definitions.NotificationTemplate{Name: name}, orgID)
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, false, err
|
||||
}
|
||||
return newNotificationTemplate(name, existingContent, provenance, definition.GrafanaTemplateKind), true, nil
|
||||
}
|
||||
|
||||
func (t *TemplateService) getTemplateByUID(ctx context.Context, revision *legacy_storage.ConfigRevision, orgID int64, uid string) (definitions.NotificationTemplate, bool, error) {
|
||||
find := func(templates map[string]string, uid string, kind definition.TemplateKind) (string, string, bool) {
|
||||
for n, tmpl := range templates {
|
||||
if templateUID(kind, n) == uid {
|
||||
return n, tmpl, true
|
||||
}
|
||||
}
|
||||
return "", "", false
|
||||
}
|
||||
var provenance models.Provenance
|
||||
name, content, ok := find(revision.Config.TemplateFiles, uid, definition.GrafanaTemplateKind)
|
||||
if !ok {
|
||||
if t.includeImported && len(revision.Config.ExtraConfigs) > 0 {
|
||||
name, content, ok = find(revision.Config.ExtraConfigs[0].TemplateFiles, uid, definition.MimirTemplateKind)
|
||||
if ok {
|
||||
return newNotificationTemplate(name, content, models.ProvenanceConvertedPrometheus, definition.MimirTemplateKind), true, nil
|
||||
}
|
||||
}
|
||||
return definitions.NotificationTemplate{}, false, nil
|
||||
}
|
||||
var err error
|
||||
provenance, err = t.provenanceStore.GetProvenance(ctx, &definitions.NotificationTemplate{Name: name}, orgID)
|
||||
if err != nil {
|
||||
return definitions.NotificationTemplate{}, false, err
|
||||
}
|
||||
return newNotificationTemplate(name, content, provenance, definition.GrafanaTemplateKind), true, nil
|
||||
}
|
||||
|
||||
func templateUID(kind definition.TemplateKind, name string) string {
|
||||
return legacy_storage.NameToUid(fmt.Sprintf("%s|%s", string(kind), name))
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/alerting/definition"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -27,6 +28,15 @@ func TestGetTemplates(t *testing.T) {
|
||||
"template2": "test2",
|
||||
"template3": "test3",
|
||||
},
|
||||
ExtraConfigs: []definitions.ExtraConfiguration{
|
||||
{
|
||||
Identifier: "1234",
|
||||
TemplateFiles: map[string]string{
|
||||
"template1": "imported-test1",
|
||||
"template4": "imported-test4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -45,27 +55,24 @@ func TestGetTemplates(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []definitions.NotificationTemplate{
|
||||
{
|
||||
UID: legacy_storage.NameToUid("template1"),
|
||||
Name: "template1",
|
||||
Template: "test1",
|
||||
Provenance: definitions.Provenance(models.ProvenanceAPI),
|
||||
ResourceVersion: calculateTemplateFingerprint("test1"),
|
||||
},
|
||||
{
|
||||
UID: legacy_storage.NameToUid("template2"),
|
||||
Name: "template2",
|
||||
Template: "test2",
|
||||
Provenance: definitions.Provenance(models.ProvenanceFile),
|
||||
ResourceVersion: calculateTemplateFingerprint("test2"),
|
||||
},
|
||||
{
|
||||
UID: legacy_storage.NameToUid("template3"),
|
||||
Name: "template3",
|
||||
Template: "test3",
|
||||
Provenance: definitions.Provenance(models.ProvenanceNone),
|
||||
ResourceVersion: calculateTemplateFingerprint("test3"),
|
||||
},
|
||||
newNotificationTemplate(
|
||||
"template1",
|
||||
"test1",
|
||||
models.ProvenanceAPI,
|
||||
definition.GrafanaTemplateKind,
|
||||
),
|
||||
newNotificationTemplate(
|
||||
"template2",
|
||||
"test2",
|
||||
models.ProvenanceFile,
|
||||
definition.GrafanaTemplateKind,
|
||||
),
|
||||
newNotificationTemplate(
|
||||
"template3",
|
||||
"test3",
|
||||
models.ProvenanceNone,
|
||||
definition.GrafanaTemplateKind,
|
||||
),
|
||||
}
|
||||
|
||||
require.EqualValues(t, expected, result)
|
||||
@@ -89,6 +96,60 @@ func TestGetTemplates(t *testing.T) {
|
||||
prov.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("returns imported templates if enabled", func(t *testing.T) {
|
||||
sut, store, prov := createTemplateServiceSut()
|
||||
sut = sut.WithIncludeImported()
|
||||
store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) {
|
||||
assert.Equal(t, orgID, org)
|
||||
return revision, nil
|
||||
}
|
||||
prov.EXPECT().GetProvenances(mock.Anything, mock.Anything, mock.Anything).Return(map[string]models.Provenance{
|
||||
"template1": models.ProvenanceAPI,
|
||||
"template2": models.ProvenanceFile,
|
||||
}, nil)
|
||||
|
||||
result, err := sut.GetTemplates(context.Background(), orgID)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []definitions.NotificationTemplate{
|
||||
newNotificationTemplate(
|
||||
"template1",
|
||||
"test1",
|
||||
models.ProvenanceAPI,
|
||||
definition.GrafanaTemplateKind,
|
||||
),
|
||||
newNotificationTemplate(
|
||||
"template2",
|
||||
"test2",
|
||||
models.ProvenanceFile,
|
||||
definition.GrafanaTemplateKind,
|
||||
),
|
||||
newNotificationTemplate(
|
||||
"template3",
|
||||
"test3",
|
||||
models.ProvenanceNone,
|
||||
definition.GrafanaTemplateKind,
|
||||
),
|
||||
newNotificationTemplate(
|
||||
"template1",
|
||||
"imported-test1",
|
||||
models.ProvenanceConvertedPrometheus,
|
||||
definition.MimirTemplateKind,
|
||||
),
|
||||
newNotificationTemplate(
|
||||
"template4",
|
||||
"imported-test4",
|
||||
models.ProvenanceConvertedPrometheus,
|
||||
definition.MimirTemplateKind,
|
||||
),
|
||||
}
|
||||
|
||||
require.EqualValues(t, expected, result)
|
||||
|
||||
prov.AssertCalled(t, "GetProvenances", mock.Anything, orgID, (&definitions.NotificationTemplate{}).ResourceType())
|
||||
prov.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("propagates errors", func(t *testing.T) {
|
||||
t.Run("when unable to read config", func(t *testing.T) {
|
||||
sut, store, prov := createTemplateServiceSut()
|
||||
@@ -127,15 +188,25 @@ func TestGetTemplate(t *testing.T) {
|
||||
orgID := int64(1)
|
||||
templateName := "template1"
|
||||
templateContent := "test1"
|
||||
importedTemplateName := "template2"
|
||||
importedTemplateContent := "imported"
|
||||
revision := &legacy_storage.ConfigRevision{
|
||||
Config: &definitions.PostableUserConfig{
|
||||
TemplateFiles: map[string]string{
|
||||
templateName: templateContent,
|
||||
},
|
||||
ExtraConfigs: []definitions.ExtraConfiguration{
|
||||
{
|
||||
Identifier: "1234",
|
||||
TemplateFiles: map[string]string{
|
||||
importedTemplateName: importedTemplateContent,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("return a template from config file by name", func(t *testing.T) {
|
||||
t.Run("return a template from config by name", func(t *testing.T) {
|
||||
sut, store, prov := createTemplateServiceSut()
|
||||
store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) {
|
||||
assert.Equal(t, orgID, org)
|
||||
@@ -146,13 +217,12 @@ func TestGetTemplate(t *testing.T) {
|
||||
result, err := sut.GetTemplate(context.Background(), orgID, templateName)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(templateName),
|
||||
Name: templateName,
|
||||
Template: templateContent,
|
||||
Provenance: definitions.Provenance(models.ProvenanceAPI),
|
||||
ResourceVersion: calculateTemplateFingerprint(templateContent),
|
||||
}
|
||||
expected := newNotificationTemplate(
|
||||
templateName,
|
||||
templateContent,
|
||||
models.ProvenanceAPI,
|
||||
definition.GrafanaTemplateKind,
|
||||
)
|
||||
|
||||
require.Equal(t, expected, result)
|
||||
|
||||
@@ -162,6 +232,62 @@ func TestGetTemplate(t *testing.T) {
|
||||
prov.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("imported templates cannot be retrieved by name", func(t *testing.T) {
|
||||
sut, store, _ := createTemplateServiceSut()
|
||||
store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) {
|
||||
assert.Equal(t, orgID, org)
|
||||
return revision, nil
|
||||
}
|
||||
_, err := sut.GetTemplate(context.Background(), orgID, importedTemplateName)
|
||||
require.ErrorIs(t, err, ErrTemplateNotFound)
|
||||
})
|
||||
|
||||
t.Run("return a template from config by UID", func(t *testing.T) {
|
||||
sut, store, prov := createTemplateServiceSut()
|
||||
store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) {
|
||||
assert.Equal(t, orgID, org)
|
||||
return revision, nil
|
||||
}
|
||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil)
|
||||
|
||||
result, err := sut.GetTemplate(context.Background(), orgID, templateUID(definition.GrafanaTemplateKind, templateName))
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := newNotificationTemplate(
|
||||
templateName,
|
||||
templateContent,
|
||||
models.ProvenanceNone,
|
||||
definition.GrafanaTemplateKind,
|
||||
)
|
||||
require.Equal(t, expected, result)
|
||||
})
|
||||
|
||||
t.Run("return an imported template from config by UID", func(t *testing.T) {
|
||||
sut, store, prov := createTemplateServiceSut()
|
||||
store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) {
|
||||
assert.Equal(t, orgID, org)
|
||||
return revision, nil
|
||||
}
|
||||
|
||||
uid := templateUID(definition.MimirTemplateKind, importedTemplateName)
|
||||
t.Run("should be not found without flag enabled", func(t *testing.T) {
|
||||
_, err := sut.GetTemplate(context.Background(), orgID, uid)
|
||||
require.ErrorIs(t, err, ErrTemplateNotFound)
|
||||
})
|
||||
|
||||
result, err := sut.WithIncludeImported().GetTemplate(context.Background(), orgID, uid)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := newNotificationTemplate(
|
||||
importedTemplateName,
|
||||
importedTemplateContent,
|
||||
models.ProvenanceConvertedPrometheus,
|
||||
definition.MimirTemplateKind,
|
||||
)
|
||||
require.Equal(t, expected, result)
|
||||
prov.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("returns ErrTemplateNotFound when template does not exist", func(t *testing.T) {
|
||||
sut, store, prov := createTemplateServiceSut()
|
||||
store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) {
|
||||
@@ -242,18 +368,18 @@ func TestUpsertTemplate(t *testing.T) {
|
||||
Template: "{{ define \"test\"}} test {{ end }}",
|
||||
Provenance: definitions.Provenance(models.ProvenanceAPI),
|
||||
ResourceVersion: "",
|
||||
Kind: definition.GrafanaTemplateKind,
|
||||
}
|
||||
|
||||
result, err := sut.UpsertTemplate(context.Background(), orgID, tmpl)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name),
|
||||
Name: tmpl.Name,
|
||||
Template: tmpl.Template,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
|
||||
}, result)
|
||||
require.Equal(t, newNotificationTemplate(
|
||||
tmpl.Name,
|
||||
tmpl.Template,
|
||||
models.Provenance(tmpl.Provenance),
|
||||
tmpl.Kind,
|
||||
), result)
|
||||
|
||||
require.Len(t, store.Calls, 2)
|
||||
|
||||
@@ -284,18 +410,18 @@ func TestUpsertTemplate(t *testing.T) {
|
||||
Template: "{{ define \"test\"}} test {{ end }}",
|
||||
Provenance: definitions.Provenance(models.ProvenanceAPI),
|
||||
ResourceVersion: calculateTemplateFingerprint("test1"),
|
||||
Kind: definition.GrafanaTemplateKind,
|
||||
}
|
||||
|
||||
result, err := sut.UpsertTemplate(context.Background(), orgID, tmpl)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name),
|
||||
Name: tmpl.Name,
|
||||
Template: tmpl.Template,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
|
||||
}, result)
|
||||
assert.Equal(t, newNotificationTemplate(
|
||||
tmpl.Name,
|
||||
tmpl.Template,
|
||||
models.Provenance(tmpl.Provenance),
|
||||
tmpl.Kind,
|
||||
), result)
|
||||
|
||||
require.Len(t, store.Calls, 2)
|
||||
require.Equal(t, "Save", store.Calls[1].Method)
|
||||
@@ -326,13 +452,12 @@ func TestUpsertTemplate(t *testing.T) {
|
||||
result, err := sut.UpsertTemplate(context.Background(), orgID, tmpl)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name),
|
||||
Name: tmpl.Name,
|
||||
Template: tmpl.Template,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
|
||||
}, result)
|
||||
assert.Equal(t, newNotificationTemplate(
|
||||
tmpl.Name,
|
||||
tmpl.Template,
|
||||
models.Provenance(tmpl.Provenance),
|
||||
definition.GrafanaTemplateKind,
|
||||
), result)
|
||||
|
||||
require.Equal(t, "Save", store.Calls[1].Method)
|
||||
saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision)
|
||||
@@ -356,18 +481,18 @@ func TestUpsertTemplate(t *testing.T) {
|
||||
Template: "content",
|
||||
Provenance: definitions.Provenance(models.ProvenanceNone),
|
||||
ResourceVersion: calculateTemplateFingerprint(currentTemplateContent),
|
||||
Kind: definition.GrafanaTemplateKind,
|
||||
}
|
||||
|
||||
result, _ := sut.UpsertTemplate(context.Background(), orgID, tmpl)
|
||||
|
||||
expectedContent := fmt.Sprintf("{{ define \"%s\" }}\n content\n{{ end }}", templateName)
|
||||
require.Equal(t, definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name),
|
||||
Name: tmpl.Name,
|
||||
Template: expectedContent,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(expectedContent),
|
||||
}, result)
|
||||
require.Equal(t, newNotificationTemplate(
|
||||
tmpl.Name,
|
||||
expectedContent,
|
||||
models.Provenance(tmpl.Provenance),
|
||||
tmpl.Kind,
|
||||
), result)
|
||||
})
|
||||
|
||||
t.Run("does not reject template with unknown field", func(t *testing.T) {
|
||||
@@ -489,6 +614,21 @@ func TestUpsertTemplate(t *testing.T) {
|
||||
require.ErrorIs(t, err, ErrTemplateNotFound)
|
||||
})
|
||||
|
||||
t.Run("rejects new templates of mimir kind", func(t *testing.T) {
|
||||
sut, store, _ := createTemplateServiceSut()
|
||||
store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) {
|
||||
return revision(), nil
|
||||
}
|
||||
template := definitions.NotificationTemplate{
|
||||
Name: "template2",
|
||||
Template: "asdf-new",
|
||||
Provenance: definitions.Provenance(models.ProvenanceNone),
|
||||
Kind: definition.MimirTemplateKind,
|
||||
}
|
||||
_, err := sut.UpsertTemplate(context.Background(), orgID, template)
|
||||
require.ErrorIs(t, err, ErrTemplateInvalid)
|
||||
})
|
||||
|
||||
t.Run("propagates errors", func(t *testing.T) {
|
||||
tmpl := definitions.NotificationTemplate{
|
||||
Name: templateName,
|
||||
@@ -562,6 +702,7 @@ func TestCreateTemplate(t *testing.T) {
|
||||
Name: "new-template",
|
||||
Template: "{{ define \"test\"}} test {{ end }}",
|
||||
Provenance: definitions.Provenance(models.ProvenanceAPI),
|
||||
Kind: definition.GrafanaTemplateKind,
|
||||
}
|
||||
|
||||
revision := func() *legacy_storage.ConfigRevision {
|
||||
@@ -588,13 +729,12 @@ func TestCreateTemplate(t *testing.T) {
|
||||
result, err := sut.CreateTemplate(context.Background(), orgID, tmpl)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name),
|
||||
Name: tmpl.Name,
|
||||
Template: tmpl.Template,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
|
||||
}, result)
|
||||
require.Equal(t, newNotificationTemplate(
|
||||
tmpl.Name,
|
||||
tmpl.Template,
|
||||
models.Provenance(tmpl.Provenance),
|
||||
tmpl.Kind,
|
||||
), result)
|
||||
|
||||
require.Len(t, store.Calls, 2)
|
||||
|
||||
@@ -649,10 +789,33 @@ func TestCreateTemplate(t *testing.T) {
|
||||
require.ErrorIs(t, err, ErrTemplateInvalid)
|
||||
})
|
||||
|
||||
t.Run("invalid kind", func(t *testing.T) {
|
||||
tmpl := definitions.NotificationTemplate{
|
||||
Name: "new-template",
|
||||
Template: "{{ define \"test\"}} test {{ end }}",
|
||||
Kind: "unknown",
|
||||
}
|
||||
_, err := sut.CreateTemplate(context.Background(), orgID, tmpl)
|
||||
require.ErrorIs(t, err, ErrTemplateInvalid)
|
||||
})
|
||||
|
||||
require.Empty(t, store.Calls)
|
||||
prov.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("rejects templates with mimir kind", func(t *testing.T) {
|
||||
sut, _, _ := createTemplateServiceSut()
|
||||
|
||||
tmpl := definitions.NotificationTemplate{
|
||||
Name: "new-template",
|
||||
Template: "{{ define \"test\"}} test {{ end }}",
|
||||
Kind: definition.MimirTemplateKind,
|
||||
}
|
||||
|
||||
_, err := sut.CreateTemplate(context.Background(), orgID, tmpl)
|
||||
require.ErrorIs(t, err, ErrTemplateInvalid)
|
||||
})
|
||||
|
||||
t.Run("propagates errors", func(t *testing.T) {
|
||||
t.Run("when unable to read config", func(t *testing.T) {
|
||||
sut, store, _ := createTemplateServiceSut()
|
||||
@@ -706,6 +869,7 @@ func TestUpdateTemplate(t *testing.T) {
|
||||
Template: "{{ define \"test\"}} test {{ end }}",
|
||||
Provenance: definitions.Provenance(models.ProvenanceAPI),
|
||||
ResourceVersion: "",
|
||||
Kind: definition.GrafanaTemplateKind,
|
||||
}
|
||||
|
||||
amConfigToken := util.GenerateShortUID()
|
||||
@@ -771,7 +935,7 @@ func TestUpdateTemplate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by uid",
|
||||
templateUid: legacy_storage.NameToUid(tmpl.UID),
|
||||
templateUid: templateUID(tmpl.Kind, tmpl.Name),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -791,13 +955,12 @@ func TestUpdateTemplate(t *testing.T) {
|
||||
result, err := sut.UpdateTemplate(context.Background(), orgID, tmpl)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name),
|
||||
Name: tmpl.Name,
|
||||
Template: tmpl.Template,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
|
||||
}, result)
|
||||
assert.Equal(t, newNotificationTemplate(
|
||||
tmpl.Name,
|
||||
tmpl.Template,
|
||||
models.Provenance(tmpl.Provenance),
|
||||
tmpl.Kind,
|
||||
), result)
|
||||
|
||||
require.Len(t, store.Calls, 2)
|
||||
require.Equal(t, "Save", store.Calls[1].Method)
|
||||
@@ -821,13 +984,12 @@ func TestUpdateTemplate(t *testing.T) {
|
||||
result, err := sut.UpdateTemplate(context.Background(), orgID, tmpl)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name),
|
||||
Name: tmpl.Name,
|
||||
Template: tmpl.Template,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
|
||||
}, result)
|
||||
assert.Equal(t, newNotificationTemplate(
|
||||
tmpl.Name,
|
||||
tmpl.Template,
|
||||
models.Provenance(tmpl.Provenance),
|
||||
tmpl.Kind,
|
||||
), result)
|
||||
|
||||
require.Equal(t, "Save", store.Calls[1].Method)
|
||||
saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision)
|
||||
@@ -853,18 +1015,17 @@ func TestUpdateTemplate(t *testing.T) {
|
||||
|
||||
oldName := tmpl.Name
|
||||
tmpl := tmpl
|
||||
tmpl.UID = legacy_storage.NameToUid(tmpl.Name) // UID matches the current template
|
||||
tmpl.Name = "new-template-name" // but name is different
|
||||
tmpl.UID = templateUID(tmpl.Kind, tmpl.Name) // UID matches the current template
|
||||
tmpl.Name = "new-template-name" // but name is different
|
||||
result, err := sut.UpdateTemplate(context.Background(), orgID, tmpl)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, definitions.NotificationTemplate{
|
||||
UID: legacy_storage.NameToUid(tmpl.Name),
|
||||
Name: tmpl.Name,
|
||||
Template: tmpl.Template,
|
||||
Provenance: tmpl.Provenance,
|
||||
ResourceVersion: calculateTemplateFingerprint(tmpl.Template),
|
||||
}, result)
|
||||
assert.Equal(t, newNotificationTemplate(
|
||||
tmpl.Name,
|
||||
tmpl.Template,
|
||||
models.Provenance(tmpl.Provenance),
|
||||
tmpl.Kind,
|
||||
), result)
|
||||
|
||||
require.Len(t, store.Calls, 2)
|
||||
require.Equal(t, "Save", store.Calls[1].Method)
|
||||
@@ -882,6 +1043,7 @@ func TestUpdateTemplate(t *testing.T) {
|
||||
|
||||
t.Run("rejects rename operation if template with the new name exists", func(t *testing.T) {
|
||||
sut, store, prov := createTemplateServiceSut()
|
||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil)
|
||||
store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) {
|
||||
return &legacy_storage.ConfigRevision{
|
||||
Config: &definitions.PostableUserConfig{
|
||||
@@ -895,8 +1057,8 @@ func TestUpdateTemplate(t *testing.T) {
|
||||
}
|
||||
|
||||
tmpl := tmpl
|
||||
tmpl.UID = legacy_storage.NameToUid(tmpl.Name) // UID matches the current template
|
||||
tmpl.Name = "new-template-name" // but name matches another existing template
|
||||
tmpl.UID = templateUID(tmpl.Kind, tmpl.Name) // UID matches the current template
|
||||
tmpl.Name = "new-template-name" // but name matches another existing template
|
||||
_, err := sut.UpdateTemplate(context.Background(), orgID, tmpl)
|
||||
|
||||
require.ErrorIs(t, err, ErrTemplateExists)
|
||||
@@ -925,6 +1087,16 @@ func TestUpdateTemplate(t *testing.T) {
|
||||
require.ErrorIs(t, err, ErrTemplateInvalid)
|
||||
})
|
||||
|
||||
t.Run("invalid kind", func(t *testing.T) {
|
||||
tmpl := definitions.NotificationTemplate{
|
||||
Name: "",
|
||||
Template: "",
|
||||
Kind: "unknown",
|
||||
}
|
||||
_, err := sut.UpdateTemplate(context.Background(), orgID, tmpl)
|
||||
require.ErrorIs(t, err, ErrTemplateInvalid)
|
||||
})
|
||||
|
||||
require.Empty(t, store.Calls)
|
||||
prov.AssertExpectations(t)
|
||||
})
|
||||
@@ -975,6 +1147,27 @@ func TestUpdateTemplate(t *testing.T) {
|
||||
prov.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("rejects existing templates if kind changes", func(t *testing.T) {
|
||||
sut, store, prov := createTemplateServiceSut()
|
||||
store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) {
|
||||
return revision(), nil
|
||||
}
|
||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil)
|
||||
|
||||
template := definitions.NotificationTemplate{
|
||||
Name: "template1",
|
||||
Template: "asdf-new",
|
||||
ResourceVersion: "bad-version",
|
||||
Provenance: definitions.Provenance(models.ProvenanceNone),
|
||||
Kind: definition.MimirTemplateKind,
|
||||
}
|
||||
|
||||
_, err := sut.UpdateTemplate(context.Background(), orgID, template)
|
||||
|
||||
require.ErrorIs(t, err, ErrTemplateInvalid)
|
||||
prov.AssertExpectations(t)
|
||||
})
|
||||
|
||||
t.Run("propagates errors", func(t *testing.T) {
|
||||
t.Run("when unable to read config", func(t *testing.T) {
|
||||
sut, store, _ := createTemplateServiceSut()
|
||||
@@ -1062,7 +1255,7 @@ func TestDeleteTemplate(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "by uid",
|
||||
templateNameOrUid: legacy_storage.NameToUid(templateName),
|
||||
templateNameOrUid: templateUID(definition.GrafanaTemplateKind, templateName),
|
||||
},
|
||||
}
|
||||
for _, tt := range testCase {
|
||||
@@ -1125,7 +1318,7 @@ func TestDeleteTemplate(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("should look by name before uid", func(t *testing.T) {
|
||||
expectedToDelete := legacy_storage.NameToUid(templateName)
|
||||
expectedToDelete := templateUID(definition.GrafanaTemplateKind, templateName)
|
||||
sut, store, prov := createTemplateServiceSut()
|
||||
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||
return &legacy_storage.ConfigRevision{
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
package templateGroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.yaml.in/yaml/v3"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/tests/api/alerting"
|
||||
"github.com/grafana/grafana/pkg/tests/apis"
|
||||
"github.com/grafana/grafana/pkg/tests/apis/alerting/notifications/common"
|
||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||
"github.com/grafana/grafana/pkg/util/testutil"
|
||||
)
|
||||
|
||||
//go:embed test-data/*.*
|
||||
var testData embed.FS
|
||||
|
||||
func TestIntegrationImportedTemplates(t *testing.T) {
|
||||
testutil.SkipIntegrationTestInShortMode(t)
|
||||
|
||||
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||
EnableFeatureToggles: []string{
|
||||
featuremgmt.FlagAlertingImportAlertmanagerAPI,
|
||||
},
|
||||
})
|
||||
|
||||
client := common.NewTemplateGroupClient(t, helper.Org1.Admin)
|
||||
|
||||
cliCfg := helper.Org1.Admin.NewRestConfig()
|
||||
alertingApi := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
|
||||
|
||||
configYaml, err := testData.ReadFile(path.Join("test-data", "imported.yaml"))
|
||||
require.NoError(t, err)
|
||||
|
||||
identifier := "test-create-get-config"
|
||||
mergeMatchers := "_imported=true"
|
||||
|
||||
headers := map[string]string{
|
||||
"Content-Type": "application/yaml",
|
||||
"X-Grafana-Alerting-Config-Identifier": identifier,
|
||||
"X-Grafana-Alerting-Merge-Matchers": mergeMatchers,
|
||||
}
|
||||
var amConfig apimodels.AlertmanagerUserConfig
|
||||
require.NoError(t, yaml.Unmarshal(configYaml, &amConfig))
|
||||
|
||||
response := alertingApi.ConvertPrometheusPostAlertmanagerConfig(t, amConfig, headers)
|
||||
require.Equal(t, "success", response.Status)
|
||||
|
||||
templates, err := client.List(context.Background(), metav1.ListOptions{})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, templates.Items, 3)
|
||||
|
||||
require.Equal(t, v0alpha1.DefaultTemplateTitle, templates.Items[0].Spec.Title)
|
||||
require.Equal(t, "imported", templates.Items[1].Spec.Title)
|
||||
require.Equal(t, "template", templates.Items[2].Spec.Title)
|
||||
|
||||
t.Run("should be correct kind", func(t *testing.T) {
|
||||
assert.Equal(t,
|
||||
v0alpha1.TemplateGroupSpec{
|
||||
Title: "imported",
|
||||
Content: amConfig.TemplateFiles["imported"],
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindMimir,
|
||||
}, templates.Items[1].Spec)
|
||||
assert.Equal(t,
|
||||
v0alpha1.TemplateGroupSpec{
|
||||
Title: "template",
|
||||
Content: amConfig.TemplateFiles["template"],
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindMimir,
|
||||
}, templates.Items[2].Spec)
|
||||
})
|
||||
|
||||
t.Run("should be provisioned", func(t *testing.T) {
|
||||
for _, tpl := range templates.Items[1:] {
|
||||
assert.EqualValues(t, models.ProvenanceConvertedPrometheus, tpl.GetProvenanceStatus())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should not be able to update", func(t *testing.T) {
|
||||
tpl := templates.Items[1]
|
||||
tpl.Spec.Content = "new content"
|
||||
_, err := client.Update(context.Background(), &tpl, metav1.UpdateOptions{})
|
||||
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
|
||||
})
|
||||
|
||||
t.Run("should not be able to delete", func(t *testing.T) {
|
||||
err := client.Delete(context.Background(), templates.Items[1].Name, metav1.DeleteOptions{})
|
||||
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
|
||||
})
|
||||
|
||||
t.Run("should not conflict with Grafana kind", func(t *testing.T) {
|
||||
tpl := v0alpha1.TemplateGroup{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: templates.Items[1].Spec,
|
||||
}
|
||||
tpl.Spec.Kind = v0alpha1.TemplateGroupTemplateKindGrafana
|
||||
|
||||
created, err := client.Create(context.Background(), &tpl, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, templates.Items[1].Name, created.Name)
|
||||
})
|
||||
|
||||
t.Run("sort by kind and then name", func(t *testing.T) {
|
||||
templates, err := client.List(context.Background(), metav1.ListOptions{})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, templates.Items, 4)
|
||||
assert.Equal(t, v0alpha1.DefaultTemplateTitle, templates.Items[0].Spec.Title)
|
||||
assert.Equal(t, "imported", templates.Items[1].Spec.Title)
|
||||
assert.Equal(t, v0alpha1.TemplateGroupTemplateKindGrafana, templates.Items[1].Spec.Kind)
|
||||
assert.Equal(t, "imported", templates.Items[2].Spec.Title)
|
||||
assert.Equal(t, v0alpha1.TemplateGroupTemplateKindMimir, templates.Items[2].Spec.Kind)
|
||||
assert.Equal(t, "template", templates.Items[3].Spec.Title)
|
||||
})
|
||||
}
|
||||
@@ -54,6 +54,7 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
|
||||
Spec: v0alpha1.TemplateGroupSpec{
|
||||
Title: "templateGroup",
|
||||
Content: `{{ define "test" }} test {{ end }}`,
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -112,6 +113,7 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
|
||||
require.Equal(t, v0alpha1.TemplateGroupSpec{
|
||||
Title: v0alpha1.DefaultTemplateTitle,
|
||||
Content: defaultDefn.Template,
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
|
||||
}, actual.Spec)
|
||||
defaultTemplateGroup = actual
|
||||
})
|
||||
@@ -226,6 +228,7 @@ func TestIntegrationAccessControl(t *testing.T) {
|
||||
Spec: v0alpha1.TemplateGroupSpec{
|
||||
Title: fmt.Sprintf("template-group-1-%s", tc.user.Identity.GetLogin()),
|
||||
Content: `{{ define "test" }} test {{ end }}`,
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
|
||||
},
|
||||
}
|
||||
expected.SetProvenanceStatus("")
|
||||
@@ -385,6 +388,7 @@ func TestIntegrationProvisioning(t *testing.T) {
|
||||
Spec: v0alpha1.TemplateGroupSpec{
|
||||
Title: "template-group-1",
|
||||
Content: `{{ define "test" }} test {{ end }}`,
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
|
||||
},
|
||||
}, v1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
@@ -428,6 +432,7 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
|
||||
Spec: v0alpha1.TemplateGroupSpec{
|
||||
Title: "template-group-1",
|
||||
Content: `{{ define "test" }} test {{ end }}`,
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -510,6 +515,7 @@ func TestIntegrationPatch(t *testing.T) {
|
||||
Spec: v0alpha1.TemplateGroupSpec{
|
||||
Title: "template-group",
|
||||
Content: `{{ define "test" }} test {{ end }}`,
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -568,6 +574,7 @@ func TestIntegrationListSelector(t *testing.T) {
|
||||
Spec: v0alpha1.TemplateGroupSpec{
|
||||
Title: "test1",
|
||||
Content: `{{ define "test1" }} test {{ end }}`,
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
|
||||
},
|
||||
}
|
||||
template1, err := adminClient.Create(ctx, template1, v1.CreateOptions{})
|
||||
@@ -580,6 +587,7 @@ func TestIntegrationListSelector(t *testing.T) {
|
||||
Spec: v0alpha1.TemplateGroupSpec{
|
||||
Title: "test2",
|
||||
Content: `{{ define "test2" }} test {{ end }}`,
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
|
||||
},
|
||||
}
|
||||
template2, err = adminClient.Create(ctx, template2, v1.CreateOptions{})
|
||||
@@ -655,3 +663,37 @@ func TestIntegrationListSelector(t *testing.T) {
|
||||
require.NotEqualf(t, templates.DefaultTemplateName, list.Items[1].Name, "Expected non-default template but got %s", list.Items[1].Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationKinds(t *testing.T) {
|
||||
testutil.SkipIntegrationTestInShortMode(t)
|
||||
|
||||
ctx := context.Background()
|
||||
helper := getTestHelper(t)
|
||||
client := common.NewTemplateGroupClient(t, helper.Org1.Admin)
|
||||
|
||||
newTemplate := &v0alpha1.TemplateGroup{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v0alpha1.TemplateGroupSpec{
|
||||
Title: "templateGroup",
|
||||
Content: `{{ define "test" }} test {{ end }}`,
|
||||
Kind: v0alpha1.TemplateGroupTemplateKindMimir,
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("should not let create Mimir template", func(t *testing.T) {
|
||||
_, err := client.Create(ctx, newTemplate, v1.CreateOptions{})
|
||||
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
|
||||
})
|
||||
|
||||
t.Run("should not let change kind", func(t *testing.T) {
|
||||
newTemplate.Spec.Kind = v0alpha1.TemplateGroupTemplateKindGrafana
|
||||
created, err := client.Create(ctx, newTemplate, v1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
created.Spec.Kind = v0alpha1.TemplateGroupTemplateKindMimir
|
||||
_, err = client.Update(ctx, created, v1.UpdateOptions{})
|
||||
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
alertmanager_config: |
|
||||
route:
|
||||
receiver: sinkhole
|
||||
group_by:
|
||||
- alertname
|
||||
receivers:
|
||||
- name: sinkhole
|
||||
template_files:
|
||||
imported: |
|
||||
{{ define "imported" }}
|
||||
{{ end }}
|
||||
template: |
|
||||
{{ define "template" }}
|
||||
{{ template "imported" . }}
|
||||
{{ end }}
|
||||
+12
-1
@@ -3783,18 +3783,29 @@
|
||||
"type": "object",
|
||||
"required": [
|
||||
"title",
|
||||
"content"
|
||||
"content",
|
||||
"kind"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupTemplateKind"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupTemplateKind": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"grafana",
|
||||
"mimir"
|
||||
]
|
||||
},
|
||||
"com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
Reference in New Issue
Block a user