mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Export of notification policies to HCL (#76411)
This commit is contained in:
parent
bdeb829cf6
commit
c4ac4eb41b
@ -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 {
|
||||
|
@ -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()))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -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,
|
||||
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,
|
||||
Receiver: route.Receiver,
|
||||
GroupByStr: NilIfEmpty(util.Pointer(route.GroupByStr)),
|
||||
Match: route.Match,
|
||||
MatchRE: route.MatchRE,
|
||||
Matchers: route.Matchers,
|
||||
ObjectMatchers: route.ObjectMatchers,
|
||||
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
|
||||
}
|
||||
|
@ -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"`
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user