diff --git a/pkg/services/provisioning/alerting/config_reader_test.go b/pkg/services/provisioning/alerting/config_reader_test.go index e43ce9f64e1..49456cb2112 100644 --- a/pkg/services/provisioning/alerting/config_reader_test.go +++ b/pkg/services/provisioning/alerting/config_reader_test.go @@ -29,6 +29,9 @@ const ( testFileCorrectProperties_mt = "./testdata/mute_times/correct-properties" testFileCorrectPropertiesWithOrg_mt = "./testdata/mute_times/correct-properties-with-org" testFileMultipleMts = "./testdata/mute_times/multiple-mute-times" + testFileCorrectProperties_t = "./testdata/templates/correct-properties" + testFileCorrectPropertiesWithOrg_t = "./testdata/templates/correct-properties-with-org" + testFileMultipleTs = "./testdata/templates/multiple-templates" ) func TestConfigReader(t *testing.T) { @@ -138,4 +141,20 @@ func TestConfigReader(t *testing.T) { require.NoError(t, err) require.Len(t, file[0].MuteTimes, 2) }) + t.Run("a template file with correct properties and specific org should not error", func(t *testing.T) { + _, err := configReader.readConfig(ctx, testFileCorrectProperties_t) + require.NoError(t, err) + }) + t.Run("a template file with correct properties and specific org should not error", func(t *testing.T) { + file, err := configReader.readConfig(ctx, testFileCorrectPropertiesWithOrg_t) + require.NoError(t, err) + t.Run("when an organization is set it should not overwrite it with the default of 1", func(t *testing.T) { + require.Equal(t, int64(1337), file[0].Templates[0].OrgID) + }) + }) + t.Run("the config reader should be able to read a file with multiple mute times", func(t *testing.T) { + file, err := configReader.readConfig(ctx, testFileMultipleTs) + require.NoError(t, err) + require.Len(t, file[0].Templates, 2) + }) } diff --git a/pkg/services/provisioning/alerting/provisioner.go b/pkg/services/provisioning/alerting/provisioner.go index d8af9f3bf29..bff80ce8706 100644 --- a/pkg/services/provisioning/alerting/provisioner.go +++ b/pkg/services/provisioning/alerting/provisioner.go @@ -17,6 +17,7 @@ type ProvisionerConfig struct { ContactPointService provisioning.ContactPointService NotificiationPolicyService provisioning.NotificationPolicyService MuteTimingService provisioning.MuteTimingService + TemplateService provisioning.TemplateService } func Provision(ctx context.Context, cfg ProvisionerConfig) error { @@ -47,6 +48,11 @@ func Provision(ctx context.Context, cfg ProvisionerConfig) error { if err != nil { return fmt.Errorf("mute times: %w", err) } + ttProvsioner := NewTextTemplateProvisioner(logger, cfg.TemplateService) + err = ttProvsioner.Provision(ctx, files) + if err != nil { + return fmt.Errorf("text templates: %w", err) + } npProvisioner := NewNotificationPolicyProvisoner(logger, cfg.NotificiationPolicyService) err = npProvisioner.Provision(ctx, files) if err != nil { @@ -64,6 +70,10 @@ func Provision(ctx context.Context, cfg ProvisionerConfig) error { if err != nil { return fmt.Errorf("mute times: %w", err) } + err = ttProvsioner.Unprovision(ctx, files) + if err != nil { + return fmt.Errorf("text templates: %w", err) + } logger.Info("finished to provision alerting") return nil } diff --git a/pkg/services/provisioning/alerting/testdata/templates/correct-properties-with-org/mute_times.yml b/pkg/services/provisioning/alerting/testdata/templates/correct-properties-with-org/mute_times.yml new file mode 100644 index 00000000000..8943cf30610 --- /dev/null +++ b/pkg/services/provisioning/alerting/testdata/templates/correct-properties-with-org/mute_times.yml @@ -0,0 +1,5 @@ +apiVersion: 1 +templates: + - orgId: 1337 + name: my_first_template + template: "test" \ No newline at end of file diff --git a/pkg/services/provisioning/alerting/testdata/templates/correct-properties/mute_times.yml b/pkg/services/provisioning/alerting/testdata/templates/correct-properties/mute_times.yml new file mode 100644 index 00000000000..4f2cfd350b1 --- /dev/null +++ b/pkg/services/provisioning/alerting/testdata/templates/correct-properties/mute_times.yml @@ -0,0 +1,4 @@ +apiVersion: 1 +templates: + - name: my_first_template + template: "test" diff --git a/pkg/services/provisioning/alerting/testdata/templates/multiple-templates/mute_times.yml b/pkg/services/provisioning/alerting/testdata/templates/multiple-templates/mute_times.yml new file mode 100644 index 00000000000..d7521db5459 --- /dev/null +++ b/pkg/services/provisioning/alerting/testdata/templates/multiple-templates/mute_times.yml @@ -0,0 +1,7 @@ +apiVersion: 1 +templates: + - orgId: 1337 + name: my_first_template + template: "test" + - name: my_second_template + template: "test" \ No newline at end of file diff --git a/pkg/services/provisioning/alerting/text_templates.go b/pkg/services/provisioning/alerting/text_templates.go new file mode 100644 index 00000000000..9d3f7dc1887 --- /dev/null +++ b/pkg/services/provisioning/alerting/text_templates.go @@ -0,0 +1,54 @@ +package alerting + +import ( + "context" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning" +) + +type TextTemplateProvisioner interface { + Provision(ctx context.Context, files []*AlertingFile) error + Unprovision(ctx context.Context, files []*AlertingFile) error +} + +type defaultTextTemplateProvisioner struct { + logger log.Logger + templateService provisioning.TemplateService +} + +func NewTextTemplateProvisioner(logger log.Logger, + templateService provisioning.TemplateService) TextTemplateProvisioner { + return &defaultTextTemplateProvisioner{ + logger: logger, + templateService: templateService, + } +} + +func (c *defaultTextTemplateProvisioner) Provision(ctx context.Context, + files []*AlertingFile) error { + for _, file := range files { + for _, template := range file.Templates { + template.Data.Provenance = models.ProvenanceFile + _, err := c.templateService.SetTemplate(ctx, template.OrgID, template.Data) + if err != nil { + return err + } + } + } + return nil +} + +func (c *defaultTextTemplateProvisioner) Unprovision(ctx context.Context, + files []*AlertingFile) error { + for _, file := range files { + for _, deleteTemplate := range file.DeleteTemplates { + err := c.templateService.DeleteTemplate(ctx, deleteTemplate.OrgID, deleteTemplate.Name) + if err != nil { + return err + } + } + } + return nil +} diff --git a/pkg/services/provisioning/alerting/text_templates_types.go b/pkg/services/provisioning/alerting/text_templates_types.go new file mode 100644 index 00000000000..70476ae323b --- /dev/null +++ b/pkg/services/provisioning/alerting/text_templates_types.go @@ -0,0 +1,55 @@ +package alerting + +import ( + "errors" + "strings" + + "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/provisioning/values" +) + +type TemplateV1 struct { + OrgID values.Int64Value `json:"orgId" yaml:"orgId"` + Template definitions.MessageTemplate `json:",inline" yaml:",inline"` +} + +func (v1 *TemplateV1) mapToModel() Template { + orgID := v1.OrgID.Value() + if orgID < 1 { + orgID = 1 + } + return Template{ + Data: v1.Template, + OrgID: orgID, + } +} + +type Template struct { + OrgID int64 + Data definitions.MessageTemplate +} + +type DeleteTemplateV1 struct { + OrgID values.Int64Value `json:"orgId" yaml:"orgId"` + Name values.StringValue `json:"name" yaml:"name"` +} + +func (v1 *DeleteTemplateV1) mapToModel() (DeleteTemplate, error) { + name := strings.TrimSpace(v1.Name.Value()) + if name == "" { + return DeleteTemplate{}, errors.New("delete template missing name") + } + orgID := v1.OrgID.Value() + if orgID < 1 { + orgID = 1 + } + return DeleteTemplate{ + Name: name, + OrgID: orgID, + }, nil +} + +type DeleteTemplate struct { + OrgID int64 + Name string +} diff --git a/pkg/services/provisioning/alerting/types.go b/pkg/services/provisioning/alerting/types.go index b934ef8fe45..7efe34b4025 100644 --- a/pkg/services/provisioning/alerting/types.go +++ b/pkg/services/provisioning/alerting/types.go @@ -23,6 +23,8 @@ type AlertingFile struct { ResetPolicies []OrgID MuteTimes []MuteTime DeleteMuteTimes []DeleteMuteTime + Templates []Template + DeleteTemplates []DeleteTemplate } type AlertingFileV1 struct { @@ -36,6 +38,8 @@ type AlertingFileV1 struct { ResetPolicies []values.Int64Value `json:"resetPolicies" yaml:"resetPolicies"` MuteTimes []MuteTimeV1 `json:"muteTimes" yaml:"muteTimes"` DeleteMuteTimes []DeleteMuteTimeV1 `json:"deleteMuteTimes" yaml:"deleteMuteTimes"` + Templates []TemplateV1 `json:"templates" yaml:"templates"` + DeleteTemplates []DeleteTemplateV1 `json:"deleteTemplates" yaml:"deleteTemplates"` } func (fileV1 *AlertingFileV1) MapToModel() (AlertingFile, error) { @@ -51,9 +55,26 @@ func (fileV1 *AlertingFileV1) MapToModel() (AlertingFile, error) { if err := fileV1.mapMuteTimes(&alertingFile); err != nil { return AlertingFile{}, fmt.Errorf("failure parsing mute times: %w", err) } + if err := fileV1.mapTemplates(&alertingFile); err != nil { + return AlertingFile{}, fmt.Errorf("failure parsing templates: %w", err) + } return alertingFile, nil } +func (fileV1 *AlertingFileV1) mapTemplates(alertingFile *AlertingFile) error { + for _, ttV1 := range fileV1.Templates { + alertingFile.Templates = append(alertingFile.Templates, ttV1.mapToModel()) + } + for _, deleteV1 := range fileV1.DeleteTemplates { + delReq, err := deleteV1.mapToModel() + if err != nil { + return err + } + alertingFile.DeleteTemplates = append(alertingFile.DeleteTemplates, delReq) + } + return nil +} + func (fileV1 *AlertingFileV1) mapMuteTimes(alertingFile *AlertingFile) error { for _, mtV1 := range fileV1.MuteTimes { alertingFile.MuteTimes = append(alertingFile.MuteTimes, mtV1.mapToModel()) diff --git a/pkg/services/provisioning/provisioning.go b/pkg/services/provisioning/provisioning.go index 6a10a567b29..050c948eca5 100644 --- a/pkg/services/provisioning/provisioning.go +++ b/pkg/services/provisioning/provisioning.go @@ -275,6 +275,7 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error notificationPolicyService := provisioning.NewNotificationPolicyService(&st, st, ps.SQLStore, ps.Cfg.UnifiedAlerting, ps.log) mutetimingsService := provisioning.NewMuteTimingService(&st, st, &st, ps.log) + templateService := provisioning.NewTemplateService(&st, st, &st, ps.log) cfg := prov_alerting.ProvisionerConfig{ Path: alertingPath, RuleService: *ruleService, @@ -283,6 +284,7 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error ContactPointService: *contactPointService, NotificiationPolicyService: *notificationPolicyService, MuteTimingService: *mutetimingsService, + TemplateService: *templateService, } return ps.provisionAlerting(ctx, cfg) }