Alerting: Export of notification policies to HCL (#76411)

This commit is contained in:
Yuri Tseretyan 2023-10-12 17:10:08 +01:00 committed by GitHub
parent bdeb829cf6
commit c4ac4eb41b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 32 deletions

View File

@ -557,13 +557,14 @@ func exportHcl(download bool, body definitions.AlertingFileExport) response.Resp
// Body: &cp,
// })
// }
// for idx, cp := range ex.Policies {
// resources = append(resources, resourceBlock{
// Type: "grafana_notification_policy",
// Name: fmt.Sprintf("notification_policy_%d", idx),
// Body: &cp,
// })
//
for idx, cp := range body.Policies {
policy := cp.Policy
resources = append(resources, hcl.Resource{
Type: "grafana_notification_policy",
Name: fmt.Sprintf("notification_policy_%d", idx+1),
Body: policy,
})
}
hclBody, err := hcl.Encode(resources...)
if err != nil {

View File

@ -1134,6 +1134,21 @@ func TestProvisioningApi(t *testing.T) {
require.Equal(t, 200, response.Status())
require.Equal(t, expectedResponse, string(response.Body()))
})
t.Run("hcl body content is as expected", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
sut.policies = createFakeNotificationPolicyService()
rc := createTestRequestCtx()
rc.Context.Req.Form.Add("format", "hcl")
expectedResponse := "resource \"grafana_notification_policy\" \"notification_policy_1\" {\n contact_point = \"default-receiver\"\n group_by = [\"g1\", \"g2\"]\n\n policy {\n contact_point = \"nested-receiver\"\n group_by = [\"g3\", \"g4\"]\n\n matcher {\n label = \"foo\"\n match = \"=\"\n value = \"bar\"\n }\n\n mute_timings = [\"interval\"]\n continue = true\n group_wait = \"5m\"\n group_interval = \"5m\"\n repeat_interval = \"5m\"\n }\n\n group_wait = \"30s\"\n group_interval = \"5m\"\n repeat_interval = \"1h\"\n}\n"
response := sut.RouteGetPolicyTreeExport(&rc)
t.Log(string(response.Body()))
require.Equal(t, 200, response.Status())
require.Equal(t, expectedResponse, string(response.Body()))
})
})
})
}

View File

@ -281,18 +281,36 @@ func AlertingFileExportFromRoute(orgID int64, route definitions.Route) (definiti
// RouteExportFromRoute creates a definitions.RouteExport DTO from definitions.Route.
func RouteExportFromRoute(route *definitions.Route) *definitions.RouteExport {
toStringIfNotNil := func(d *model.Duration) *string {
if d == nil {
return nil
}
s := d.String()
return &s
}
matchers := make([]*definitions.MatcherExport, 0, len(route.ObjectMatchers))
for _, matcher := range route.ObjectMatchers {
matchers = append(matchers, &definitions.MatcherExport{
Label: matcher.Name,
Match: matcher.Type.String(),
Value: matcher.Value,
})
}
export := definitions.RouteExport{
Receiver: route.Receiver,
GroupByStr: route.GroupByStr,
GroupByStr: NilIfEmpty(util.Pointer(route.GroupByStr)),
Match: route.Match,
MatchRE: route.MatchRE,
Matchers: route.Matchers,
ObjectMatchers: route.ObjectMatchers,
MuteTimeIntervals: route.MuteTimeIntervals,
Continue: route.Continue,
GroupWait: route.GroupWait,
GroupInterval: route.GroupInterval,
RepeatInterval: route.RepeatInterval,
ObjectMatchersSlice: matchers,
MuteTimeIntervals: NilIfEmpty(util.Pointer(route.MuteTimeIntervals)),
Continue: OmitDefault(util.Pointer(route.Continue)),
GroupWait: toStringIfNotNil(route.GroupWait),
GroupInterval: toStringIfNotNil(route.GroupInterval),
RepeatInterval: toStringIfNotNil(route.RepeatInterval),
}
if len(route.Routes) > 0 {
@ -304,3 +322,23 @@ func RouteExportFromRoute(route *definitions.Route) *definitions.RouteExport {
return &export
}
// OmitDefault returns nil if the value is the default.
func OmitDefault[T comparable](v *T) *T {
var def T
if v == nil {
return v
}
if *v == def {
return nil
}
return v
}
// NilIfEmpty returns nil if pointer to slice points to the empty slice.
func NilIfEmpty[T any](v *[]T) *[]T {
if v == nil || len(*v) == 0 {
return nil
}
return v
}

View File

@ -2,7 +2,6 @@ package definitions
import (
"github.com/prometheus/alertmanager/config"
"github.com/prometheus/common/model"
)
// swagger:route GET /api/v1/provisioning/policies provisioning stable RouteGetPolicyTree
@ -58,20 +57,27 @@ type NotificationPolicyExport struct {
// RouteExport is the provisioned file export of definitions.Route. This is needed to hide fields that aren't useable in
// provisioning file format. An alternative would be to define a custom MarshalJSON and MarshalYAML that excludes them.
type RouteExport struct {
Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty"`
Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty" hcl:"contact_point"`
GroupByStr []string `yaml:"group_by,omitempty" json:"group_by,omitempty"`
GroupByStr *[]string `yaml:"group_by,omitempty" json:"group_by,omitempty" hcl:"group_by"`
// Deprecated. Remove before v1.0 release.
Match map[string]string `yaml:"match,omitempty" json:"match,omitempty"`
// Deprecated. Remove before v1.0 release.
MatchRE config.MatchRegexps `yaml:"match_re,omitempty" json:"match_re,omitempty"`
Matchers config.Matchers `yaml:"matchers,omitempty" json:"matchers,omitempty"`
ObjectMatchers ObjectMatchers `yaml:"object_matchers,omitempty" json:"object_matchers,omitempty"`
MuteTimeIntervals []string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty"`
Continue bool `yaml:"continue,omitempty" json:"continue,omitempty"` // Added omitempty to yaml for a cleaner export.
Routes []*RouteExport `yaml:"routes,omitempty" json:"routes,omitempty"`
ObjectMatchersSlice []*MatcherExport `yaml:"-" json:"-" hcl:"matcher,block"`
MuteTimeIntervals *[]string `yaml:"mute_time_intervals,omitempty" json:"mute_time_intervals,omitempty" hcl:"mute_timings"`
Continue *bool `yaml:"continue,omitempty" json:"continue,omitempty" hcl:"continue,optional"` // Added omitempty to yaml for a cleaner export.
Routes []*RouteExport `yaml:"routes,omitempty" json:"routes,omitempty" hcl:"policy,block"`
GroupWait *model.Duration `yaml:"group_wait,omitempty" json:"group_wait,omitempty"`
GroupInterval *model.Duration `yaml:"group_interval,omitempty" json:"group_interval,omitempty"`
RepeatInterval *model.Duration `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty"`
GroupWait *string `yaml:"group_wait,omitempty" json:"group_wait,omitempty" hcl:"group_wait,optional"`
GroupInterval *string `yaml:"group_interval,omitempty" json:"group_interval,omitempty" hcl:"group_interval,optional"`
RepeatInterval *string `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty" hcl:"repeat_interval,optional"`
}
type MatcherExport struct {
Label string `yaml:"-" json:"-" hcl:"label"`
Match string `yaml:"-" json:"-" hcl:"match"`
Value string `yaml:"-" json:"-" hcl:"value"`
}

View File

@ -6,7 +6,7 @@ import { alertRuleApi } from '../../api/alertRuleApi';
import { FileExportPreview } from './FileExportPreview';
import { GrafanaExportDrawer } from './GrafanaExportDrawer';
import { ExportFormats, jsonAndYamlGrafanaExportProviders } from './providers';
import { allGrafanaExportProviders, ExportFormats } from './providers';
interface GrafanaPoliciesPreviewProps {
exportFormat: ExportFormats;
onClose: () => void;
@ -45,7 +45,7 @@ export const GrafanaPoliciesExporter = ({ onClose }: GrafanaPoliciesExporterProp
activeTab={activeTab}
onTabChange={setActiveTab}
onClose={onClose}
formatProviders={jsonAndYamlGrafanaExportProviders}
formatProviders={Object.values(allGrafanaExportProviders)}
>
<GrafanaPoliciesExporterPreview exportFormat={activeTab} onClose={onClose} />
</GrafanaExportDrawer>