diff --git a/pkg/services/provisioning/alerting/notification_policy_types.go b/pkg/services/provisioning/alerting/notification_policy_types.go index 21987888612..dae145211ad 100644 --- a/pkg/services/provisioning/alerting/notification_policy_types.go +++ b/pkg/services/provisioning/alerting/notification_policy_types.go @@ -1,26 +1,54 @@ package alerting import ( + "encoding/json" + "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/provisioning/values" ) type NotificiationPolicyV1 struct { - OrgID values.Int64Value `json:"orgId" yaml:"orgId"` - Policy definitions.Route `json:",inline" yaml:",inline"` + OrgID values.Int64Value `json:"orgId" yaml:"orgId"` + // We use JSONValue here, as we want to have interpolation the values. + Policy values.JSONValue `json:"-" yaml:"-"` } -func (v1 *NotificiationPolicyV1) mapToModel() NotificiationPolicy { +func (v1 *NotificiationPolicyV1) UnmarshalYAML(unmarshal func(interface{}) error) error { + err := v1.Policy.UnmarshalYAML(unmarshal) + if err != nil { + return err + } + // As we also want to unmarshal the orgId and any other field that might be + // added in the future we create an alias type that prevents recursion + // and just uses the default marshler. + type plain NotificiationPolicyV1 + return unmarshal((*plain)(v1)) +} + +func (v1 *NotificiationPolicyV1) mapToModel() (NotificiationPolicy, error) { orgID := v1.OrgID.Value() if orgID < 1 { orgID = 1 } - // we don't need any further validation here as it's done by - // the notification policy service + var route definitions.Route + // We need the string json representation, so we marshal the policy back + // as a string and interpolate it at the same time. + data, err := json.Marshal(v1.Policy.Value()) + if err != nil { + return NotificiationPolicy{}, err + } + // Now we can take the interpolated string json represtenation of the policy + // and unmarshal it in the concrete type. + err = json.Unmarshal(data, &route) + if err != nil { + return NotificiationPolicy{}, err + } + // We don't need any further validation here as it's done by + // the notification policy service. return NotificiationPolicy{ OrgID: orgID, - Policy: v1.Policy, - } + Policy: route, + }, nil } type NotificiationPolicy struct { diff --git a/pkg/services/provisioning/alerting/notification_policy_types_test.go b/pkg/services/provisioning/alerting/notification_policy_types_test.go new file mode 100644 index 00000000000..3538ad23657 --- /dev/null +++ b/pkg/services/provisioning/alerting/notification_policy_types_test.go @@ -0,0 +1,37 @@ +package alerting + +import ( + "os" + "testing" + + "gopkg.in/yaml.v2" + + "github.com/stretchr/testify/require" +) + +func TestNotificationPolicy(t *testing.T) { + const ( + envKey = "NOTIFIER_EMAIL_REMINDER_FREQUENCY" + envValue = "4h" + ) + err := os.Setenv(envKey, envValue) + require.NoError(t, err) + defer func() { + _ = os.Unsetenv(envKey) + }() + data := `orgId: 123 +receiver: test +continue: true +repeat_interval: ${NOTIFIER_EMAIL_REMINDER_FREQUENCY} +` + var model NotificiationPolicyV1 + + err = yaml.Unmarshal([]byte(data), &model) + require.NoError(t, err) + np, err := model.mapToModel() + require.NoError(t, err) + require.Equal(t, int64(123), np.OrgID) + require.Equal(t, "test", np.Policy.Receiver) + require.True(t, np.Policy.Continue) + require.Equal(t, envValue, np.Policy.RepeatInterval.String()) +} diff --git a/pkg/services/provisioning/alerting/types.go b/pkg/services/provisioning/alerting/types.go index 7efe34b4025..6b500c85dd1 100644 --- a/pkg/services/provisioning/alerting/types.go +++ b/pkg/services/provisioning/alerting/types.go @@ -51,7 +51,9 @@ func (fileV1 *AlertingFileV1) MapToModel() (AlertingFile, error) { if err := fileV1.mapContactPoint(&alertingFile); err != nil { return AlertingFile{}, fmt.Errorf("failure parsing contact points: %w", err) } - fileV1.mapPolicies(&alertingFile) + if err := fileV1.mapPolicies(&alertingFile); err != nil { + return AlertingFile{}, fmt.Errorf("failure parsing policies: %w", err) + } if err := fileV1.mapMuteTimes(&alertingFile); err != nil { return AlertingFile{}, fmt.Errorf("failure parsing mute times: %w", err) } @@ -89,13 +91,18 @@ func (fileV1 *AlertingFileV1) mapMuteTimes(alertingFile *AlertingFile) error { return nil } -func (fileV1 *AlertingFileV1) mapPolicies(alertingFile *AlertingFile) { +func (fileV1 *AlertingFileV1) mapPolicies(alertingFile *AlertingFile) error { for _, npV1 := range fileV1.Policies { - alertingFile.Policies = append(alertingFile.Policies, npV1.mapToModel()) + np, err := npV1.mapToModel() + if err != nil { + return err + } + alertingFile.Policies = append(alertingFile.Policies, np) } for _, orgIDV1 := range fileV1.ResetPolicies { alertingFile.ResetPolicies = append(alertingFile.ResetPolicies, OrgID(orgIDV1.Value())) } + return nil } func (fileV1 *AlertingFileV1) mapContactPoint(alertingFile *AlertingFile) error {