Skip to content

Commit 41b3398

Browse files
authored
Alerting: Enable interpolation for notification policies in file provisioning (grafana#58956)
1 parent d5274df commit 41b3398

File tree

3 files changed

+82
-10
lines changed

3 files changed

+82
-10
lines changed

pkg/services/provisioning/alerting/notification_policy_types.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,54 @@
11
package alerting
22

33
import (
4+
"encoding/json"
5+
46
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
57
"github.com/grafana/grafana/pkg/services/provisioning/values"
68
)
79

810
type NotificiationPolicyV1 struct {
9-
OrgID values.Int64Value `json:"orgId" yaml:"orgId"`
10-
Policy definitions.Route `json:",inline" yaml:",inline"`
11+
OrgID values.Int64Value `json:"orgId" yaml:"orgId"`
12+
// We use JSONValue here, as we want to have interpolation the values.
13+
Policy values.JSONValue `json:"-" yaml:"-"`
14+
}
15+
16+
func (v1 *NotificiationPolicyV1) UnmarshalYAML(unmarshal func(interface{}) error) error {
17+
err := v1.Policy.UnmarshalYAML(unmarshal)
18+
if err != nil {
19+
return err
20+
}
21+
// As we also want to unmarshal the orgId and any other field that might be
22+
// added in the future we create an alias type that prevents recursion
23+
// and just uses the default marshler.
24+
type plain NotificiationPolicyV1
25+
return unmarshal((*plain)(v1))
1126
}
1227

13-
func (v1 *NotificiationPolicyV1) mapToModel() NotificiationPolicy {
28+
func (v1 *NotificiationPolicyV1) mapToModel() (NotificiationPolicy, error) {
1429
orgID := v1.OrgID.Value()
1530
if orgID < 1 {
1631
orgID = 1
1732
}
18-
// we don't need any further validation here as it's done by
19-
// the notification policy service
33+
var route definitions.Route
34+
// We need the string json representation, so we marshal the policy back
35+
// as a string and interpolate it at the same time.
36+
data, err := json.Marshal(v1.Policy.Value())
37+
if err != nil {
38+
return NotificiationPolicy{}, err
39+
}
40+
// Now we can take the interpolated string json represtenation of the policy
41+
// and unmarshal it in the concrete type.
42+
err = json.Unmarshal(data, &route)
43+
if err != nil {
44+
return NotificiationPolicy{}, err
45+
}
46+
// We don't need any further validation here as it's done by
47+
// the notification policy service.
2048
return NotificiationPolicy{
2149
OrgID: orgID,
22-
Policy: v1.Policy,
23-
}
50+
Policy: route,
51+
}, nil
2452
}
2553

2654
type NotificiationPolicy struct {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package alerting
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"gopkg.in/yaml.v2"
8+
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestNotificationPolicy(t *testing.T) {
13+
const (
14+
envKey = "NOTIFIER_EMAIL_REMINDER_FREQUENCY"
15+
envValue = "4h"
16+
)
17+
err := os.Setenv(envKey, envValue)
18+
require.NoError(t, err)
19+
defer func() {
20+
_ = os.Unsetenv(envKey)
21+
}()
22+
data := `orgId: 123
23+
receiver: test
24+
continue: true
25+
repeat_interval: ${NOTIFIER_EMAIL_REMINDER_FREQUENCY}
26+
`
27+
var model NotificiationPolicyV1
28+
29+
err = yaml.Unmarshal([]byte(data), &model)
30+
require.NoError(t, err)
31+
np, err := model.mapToModel()
32+
require.NoError(t, err)
33+
require.Equal(t, int64(123), np.OrgID)
34+
require.Equal(t, "test", np.Policy.Receiver)
35+
require.True(t, np.Policy.Continue)
36+
require.Equal(t, envValue, np.Policy.RepeatInterval.String())
37+
}

pkg/services/provisioning/alerting/types.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ func (fileV1 *AlertingFileV1) MapToModel() (AlertingFile, error) {
5151
if err := fileV1.mapContactPoint(&alertingFile); err != nil {
5252
return AlertingFile{}, fmt.Errorf("failure parsing contact points: %w", err)
5353
}
54-
fileV1.mapPolicies(&alertingFile)
54+
if err := fileV1.mapPolicies(&alertingFile); err != nil {
55+
return AlertingFile{}, fmt.Errorf("failure parsing policies: %w", err)
56+
}
5557
if err := fileV1.mapMuteTimes(&alertingFile); err != nil {
5658
return AlertingFile{}, fmt.Errorf("failure parsing mute times: %w", err)
5759
}
@@ -89,13 +91,18 @@ func (fileV1 *AlertingFileV1) mapMuteTimes(alertingFile *AlertingFile) error {
8991
return nil
9092
}
9193

92-
func (fileV1 *AlertingFileV1) mapPolicies(alertingFile *AlertingFile) {
94+
func (fileV1 *AlertingFileV1) mapPolicies(alertingFile *AlertingFile) error {
9395
for _, npV1 := range fileV1.Policies {
94-
alertingFile.Policies = append(alertingFile.Policies, npV1.mapToModel())
96+
np, err := npV1.mapToModel()
97+
if err != nil {
98+
return err
99+
}
100+
alertingFile.Policies = append(alertingFile.Policies, np)
95101
}
96102
for _, orgIDV1 := range fileV1.ResetPolicies {
97103
alertingFile.ResetPolicies = append(alertingFile.ResetPolicies, OrgID(orgIDV1.Value()))
98104
}
105+
return nil
99106
}
100107

101108
func (fileV1 *AlertingFileV1) mapContactPoint(alertingFile *AlertingFile) error {

0 commit comments

Comments
 (0)