feat(pkger): add export support for notification rules

pull/16315/head
Johnny Steenbergen 2019-12-20 12:51:27 -08:00 committed by Johnny Steenbergen
parent 08f523ddc0
commit af5b7fd7d3
9 changed files with 424 additions and 37 deletions

View File

@ -12,6 +12,7 @@
1. [16297](https://github.com/influxdata/influxdb/pull/16297): Add support for notification rule to pkger parser
1. [16298](https://github.com/influxdata/influxdb/pull/16298): Add support for notification rule pkger dry run functionality
1. [16305](https://github.com/influxdata/influxdb/pull/16305): Add support for notification rule pkger apply functionality
1. [16312](https://github.com/influxdata/influxdb/pull/16312): Add support for notification rule pkger export functionality
### Bug Fixes

View File

@ -55,6 +55,7 @@ type cmdPkgBuilder struct {
dashboards string
endpoints string
labels string
rules string
telegrafs string
variables string
}
@ -229,6 +230,7 @@ func (b *cmdPkgBuilder) cmdPkgExport() *cobra.Command {
cmd.Flags().StringVar(&b.exportOpts.dashboards, "dashboards", "", "List of dashboard ids comma separated")
cmd.Flags().StringVar(&b.exportOpts.endpoints, "endpoints", "", "List of notification endpoint ids comma separated")
cmd.Flags().StringVar(&b.exportOpts.labels, "labels", "", "List of label ids comma separated")
cmd.Flags().StringVar(&b.exportOpts.rules, "rules", "", "List of notification rule ids comma separated")
cmd.Flags().StringVar(&b.exportOpts.telegrafs, "telegraf-configs", "", "List of telegraf config ids comma separated")
cmd.Flags().StringVar(&b.exportOpts.variables, "variables", "", "List of variable ids comma separated")
@ -253,8 +255,9 @@ func (b *cmdPkgBuilder) pkgExportRunEFn() func(*cobra.Command, []string) error {
{kind: pkger.KindBucket, idStrs: strings.Split(b.exportOpts.buckets, ",")},
{kind: pkger.KindCheck, idStrs: strings.Split(b.exportOpts.checks, ",")},
{kind: pkger.KindDashboard, idStrs: strings.Split(b.exportOpts.dashboards, ",")},
{kind: pkger.KindNotificationEndpoint, idStrs: strings.Split(b.exportOpts.endpoints, ",")},
{kind: pkger.KindLabel, idStrs: strings.Split(b.exportOpts.labels, ",")},
{kind: pkger.KindNotificationEndpoint, idStrs: strings.Split(b.exportOpts.endpoints, ",")},
{kind: pkger.KindNotificationRule, idStrs: strings.Split(b.exportOpts.rules, ",")},
{kind: pkger.KindTelegraf, idStrs: strings.Split(b.exportOpts.telegrafs, ",")},
{kind: pkger.KindVariable, idStrs: strings.Split(b.exportOpts.variables, ",")},
}

View File

@ -400,6 +400,11 @@ spec:
}
resWithNewName := []pkger.ResourceToClone{
{
Kind: pkger.KindNotificationRule,
Name: "new rule name",
ID: influxdb.ID(rule.ID),
},
{
Kind: pkger.KindVariable,
Name: "new name",
@ -457,6 +462,13 @@ spec:
assert.Equal(t, endpoints[0].NotificationEndpoint.GetDescription(), newEndpoints[0].NotificationEndpoint.GetDescription())
hasLabelAssociations(t, newEndpoints[0].LabelAssociations, 1, "label_1")
require.Len(t, newSum.NotificationRules, 1)
newRule := newSum.NotificationRules[0]
assert.Equal(t, "new rule name", newRule.Name)
assert.Zero(t, newRule.EndpointID)
assert.Equal(t, rule.EndpointName, newRule.EndpointName)
hasLabelAssociations(t, newRule.LabelAssociations, 1, "label_1")
require.Len(t, newSum.TelegrafConfigs, 1)
assert.Equal(t, teles[0].TelegrafConfig.Name, newSum.TelegrafConfigs[0].TelegrafConfig.Name)
assert.Equal(t, teles[0].TelegrafConfig.Description, newSum.TelegrafConfigs[0].TelegrafConfig.Description)
@ -636,6 +648,16 @@ spec:
level: INfO
min: 30.0
max: 45.0
- type: outside_range
level: WARN
min: 60.0
max: 70.0
- type: greater
level: CRIT
val: 80
- type: lesser
level: OK
val: 30
associations:
- kind: Label
name: label_1

View File

@ -7156,6 +7156,7 @@ components:
- dashboard
- label
- notification_endpoint
- notification_rule
- telegraf
- variable
name:

View File

@ -8,6 +8,7 @@ import (
"github.com/influxdata/influxdb/notification"
icheck "github.com/influxdata/influxdb/notification/check"
"github.com/influxdata/influxdb/notification/endpoint"
"github.com/influxdata/influxdb/notification/rule"
)
// ResourceToClone is a resource that will be cloned.
@ -79,18 +80,14 @@ func checkToResource(ch influxdb.Check, name string) Resource {
}
assignNonZeroStrings(r, map[string]string{fieldDescription: ch.GetDescription()})
assignFluxDur := func(field string, dur *notification.Duration) {
if dur == nil {
return
}
r[field] = dur.TimeDuration().String()
}
assignBase := func(base icheck.Base) {
r[fieldQuery] = base.Query.Text
r[fieldCheckStatusMessageTemplate] = base.StatusMessageTemplate
assignFluxDur(fieldEvery, base.Every)
assignFluxDur(fieldOffset, base.Offset)
assignNonZeroFluxDurs(r, map[string]*notification.Duration{
fieldEvery: base.Every,
fieldOffset: base.Offset,
})
var tags []Resource
for _, t := range base.Tags {
if t.Valid() != nil {
@ -110,8 +107,10 @@ func checkToResource(ch influxdb.Check, name string) Resource {
case *icheck.Deadman:
r[fieldKind] = KindCheckDeadman.title()
assignBase(cT.Base)
assignFluxDur(fieldCheckTimeSince, cT.TimeSince)
assignFluxDur(fieldCheckStaleTime, cT.StaleTime)
assignNonZeroFluxDurs(r, map[string]*notification.Duration{
fieldCheckTimeSince: cT.TimeSince,
fieldCheckStaleTime: cT.StaleTime,
})
r[fieldLevel] = cT.Level.String()
assignNonZeroBools(r, map[string]bool{fieldCheckReportZero: cT.ReportZero})
case *icheck.Threshold:
@ -129,15 +128,21 @@ func checkToResource(ch influxdb.Check, name string) Resource {
func convertThreshold(th icheck.ThresholdConfig) Resource {
r := Resource{fieldLevel: th.GetLevel().String()}
assignLesser := func(threshType thresholdType, allValues bool, val float64) {
r[fieldType] = string(threshType)
assignNonZeroBools(r, map[string]bool{fieldCheckAllValues: allValues})
r[fieldValue] = val
}
switch realType := th.(type) {
case icheck.Lesser:
r[fieldType] = string(thresholdTypeLesser)
assignNonZeroBools(r, map[string]bool{fieldCheckAllValues: realType.AllValues})
r[fieldValue] = realType.Value
assignLesser(thresholdTypeLesser, realType.AllValues, realType.Value)
case *icheck.Lesser:
assignLesser(thresholdTypeLesser, realType.AllValues, realType.Value)
case icheck.Greater:
r[fieldType] = string(thresholdTypeGreater)
assignNonZeroBools(r, map[string]bool{fieldCheckAllValues: realType.AllValues})
r[fieldValue] = realType.Value
assignLesser(thresholdTypeGreater, realType.AllValues, realType.Value)
case *icheck.Greater:
assignLesser(thresholdTypeGreater, realType.AllValues, realType.Value)
case icheck.Range:
assignRangeThreshold(r, realType)
case *icheck.Range:
@ -433,6 +438,67 @@ func endpointToResource(e influxdb.NotificationEndpoint, name string) Resource {
return r
}
func ruleToResource(iRule influxdb.NotificationRule, endpointName, name string) Resource {
if name == "" {
name = iRule.GetName()
}
r := Resource{
fieldKind: KindNotificationRule.title(),
fieldName: name,
fieldNotificationRuleEndpointName: endpointName,
}
assignNonZeroStrings(r, map[string]string{
fieldDescription: iRule.GetDescription(),
})
assignBase := func(base rule.Base) {
assignNonZeroFluxDurs(r, map[string]*notification.Duration{
fieldEvery: base.Every,
fieldOffset: base.Offset,
})
var tagRes []Resource
for _, tRule := range base.TagRules {
tagRes = append(tagRes, Resource{
fieldKey: tRule.Key,
fieldValue: tRule.Value,
fieldOperator: tRule.Operator.String(),
})
}
if len(tagRes) > 0 {
r[fieldNotificationRuleTagRules] = tagRes
}
var statusRuleRes []Resource
for _, sRule := range base.StatusRules {
sRes := Resource{
fieldNotificationRuleCurrentLevel: sRule.CurrentLevel.String(),
}
if sRule.PreviousLevel != nil {
sRes[fieldNotificationRulePreviousLevel] = sRule.PreviousLevel.String()
}
statusRuleRes = append(statusRuleRes, sRes)
}
if len(statusRuleRes) > 0 {
r[fieldNotificationRuleStatusRules] = statusRuleRes
}
}
switch t := iRule.(type) {
case *rule.HTTP:
assignBase(t.Base)
case *rule.PagerDuty:
assignBase(t.Base)
r[fieldNotificationRuleMessageTemplate] = t.MessageTemplate
case *rule.Slack:
assignBase(t.Base)
r[fieldNotificationRuleMessageTemplate] = t.MessageTemplate
assignNonZeroStrings(r, map[string]string{fieldNotificationRuleChannel: t.Channel})
}
return r
}
func telegrafToResource(t influxdb.TelegrafConfig, name string) Resource {
if name == "" {
name = t.Name
@ -487,6 +553,18 @@ func variableToResource(v influxdb.Variable, name string) Resource {
return r
}
func assignNonZeroFluxDurs(r Resource, m map[string]*notification.Duration) {
for field, dur := range m {
if dur == nil {
continue
}
if dur.TimeDuration() == 0 {
continue
}
r[field] = dur.TimeDuration().String()
}
}
func assignNonZeroBools(r Resource, m map[string]bool) {
for k, v := range m {
if v {

View File

@ -54,6 +54,19 @@ var kinds = map[Kind]bool{
KindVariable: true,
}
var kindsUniqByName = map[Kind]bool{
KindBucket: true,
KindCheck: true,
KindCheckDeadman: true,
KindCheckThreshold: true,
KindLabel: true,
KindNotificationEndpoint: true,
KindNotificationEndpointHTTP: true,
KindNotificationEndpointPagerDuty: true,
KindNotificationEndpointSlack: true,
KindVariable: true,
}
// Kind is a resource kind.
type Kind string

View File

@ -1274,7 +1274,13 @@ func uniqResources(resources []Resource) []Resource {
kind Kind
name string
}
// these 2 maps are used to eliminate duplicates that come
// from dependencies while keeping the Resource that has any
// associations. If there are no associations, then the resources
// are no different from one another.
m := make(map[key]bool)
res := make(map[key]Resource)
out := make([]Resource, 0, len(resources))
for _, r := range resources {
@ -1285,18 +1291,22 @@ func uniqResources(resources []Resource) []Resource {
if err := k.OK(); err != nil {
continue
}
switch k {
// these 3 kinds are unique, have existing state identifiable by name
case KindBucket, KindLabel, KindVariable:
if kindsUniqByName[k] {
rKey := key{kind: k, name: r.Name()}
if m[rKey] {
if hasAssociations, ok := m[rKey]; ok && hasAssociations {
continue
}
m[rKey] = true
fallthrough
default:
out = append(out, r)
_, hasAssociations := r[fieldAssociations]
m[rKey] = hasAssociations
res[rKey] = r
continue
}
out = append(out, r)
}
for _, r := range res {
out = append(out, r)
}
return out
}

View File

@ -257,10 +257,17 @@ func (s *Service) CreatePkg(ctx context.Context, setters ...CreatePkgSetFn) (*Pk
}
var kindPriorities = map[Kind]int{
KindLabel: 1,
KindBucket: 2,
KindVariable: 3,
KindDashboard: 4,
KindLabel: 1,
KindBucket: 2,
KindCheckDeadman: 3,
KindCheckThreshold: 4,
KindNotificationEndpointHTTP: 5,
KindNotificationEndpointPagerDuty: 6,
KindNotificationEndpointSlack: 7,
KindNotificationRule: 8,
KindVariable: 9,
KindTelegraf: 10,
KindDashboard: 11,
}
sort.Slice(pkg.Spec.Resources, func(i, j int) bool {
@ -302,6 +309,10 @@ func (s *Service) cloneOrgResources(ctx context.Context, orgID influxdb.ID) ([]R
resType: KindNotificationEndpoint.ResourceType(),
cloneFn: s.cloneOrgNotificationEndpoints,
},
{
resType: KindNotificationRule.ResourceType(),
cloneFn: s.cloneOrgNotificationRules,
},
{
resType: KindTelegraf.ResourceType(),
cloneFn: s.cloneOrgTelegrafs,
@ -417,6 +428,24 @@ func (s *Service) cloneOrgNotificationEndpoints(ctx context.Context, orgID influ
return resources, nil
}
func (s *Service) cloneOrgNotificationRules(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) {
rules, _, err := s.ruleSVC.FindNotificationRules(ctx, influxdb.NotificationRuleFilter{
OrgID: &orgID,
})
if err != nil {
return nil, err
}
resources := make([]ResourceToClone, 0, len(rules))
for _, r := range rules {
resources = append(resources, ResourceToClone{
Kind: KindNotificationRule,
ID: r.GetID(),
})
}
return resources, nil
}
func (s *Service) cloneOrgTelegrafs(ctx context.Context, orgID influxdb.ID) ([]ResourceToClone, error) {
teles, _, err := s.teleSVC.FindTelegrafConfigs(ctx, influxdb.TelegrafConfigFilter{OrgID: &orgID})
if err != nil {
@ -458,7 +487,11 @@ func (s *Service) resourceCloneToResource(ctx context.Context, r ResourceToClone
e = ierrors.Wrap(e, "cloning resource")
}
}()
var newResource Resource
var (
newResource Resource
sidecarResources []Resource
)
switch {
case r.Kind.is(KindBucket):
bkt, err := s.bucketSVC.FindBucketByID(ctx, r.ID)
@ -495,6 +528,12 @@ func (s *Service) resourceCloneToResource(ctx context.Context, r ResourceToClone
return nil, err
}
newResource = endpointToResource(e, r.Name)
case r.Kind.is(KindNotificationRule):
ruleRes, endpointRes, err := s.exportNotificationRule(ctx, r)
if err != nil {
return nil, err
}
newResource, sidecarResources = ruleRes, append(sidecarResources, endpointRes)
case r.Kind.is(KindTelegraf):
t, err := s.teleSVC.FindTelegrafConfigByID(ctx, r.ID)
if err != nil {
@ -519,7 +558,21 @@ func (s *Service) resourceCloneToResource(ctx context.Context, r ResourceToClone
newResource[fieldAssociations] = ass.associations
}
return append([]Resource{newResource}, ass.newLableResources...), nil
return append(ass.newLableResources, append(sidecarResources, newResource)...), nil
}
func (s *Service) exportNotificationRule(ctx context.Context, r ResourceToClone) (Resource, Resource, error) {
rule, err := s.ruleSVC.FindNotificationRuleByID(ctx, r.ID)
if err != nil {
return nil, nil, err
}
ruleEndpoint, err := s.endpointSVC.FindNotificationEndpointByID(ctx, rule.GetEndpointID())
if err != nil {
return nil, nil, err
}
return ruleToResource(rule, ruleEndpoint.GetName(), r.Name), endpointToResource(ruleEndpoint, ""), nil
}
type (
@ -1043,10 +1096,8 @@ func (s *Service) Apply(ctx context.Context, orgID, userID influxdb.ID, pkg *Pkg
}
}
// this is required after first primary run and before secondary run, b/c this has dependencies
// on the notification endpoints being live in the source of truth (store). Hence we break
// it up after the 1st group and before the 2nd. The 2nd group needs these done first so the
// label mappings are accurate.
// this has to be run after the above primary resources, because it relies on
// notification endpoints already being applied.
app, err := s.applyNotificationRulesGenerator(ctx, orgID, pkg.notificationRules())
if err != nil {
return Summary{}, err

View File

@ -14,6 +14,7 @@ import (
"github.com/influxdata/influxdb/notification"
icheck "github.com/influxdata/influxdb/notification/check"
"github.com/influxdata/influxdb/notification/endpoint"
"github.com/influxdata/influxdb/notification/rule"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
@ -1966,6 +1967,171 @@ func TestService(t *testing.T) {
}
})
t.Run("notification rules", func(t *testing.T) {
newRuleBase := func(id int) rule.Base {
return rule.Base{
ID: 9000,
Name: "old_name",
Description: "desc",
EndpointID: influxdb.ID(id),
Every: mustDuration(t, time.Hour),
Offset: mustDuration(t, time.Minute),
TagRules: []notification.TagRule{
{Tag: influxdb.Tag{Key: "k1", Value: "v1"}},
},
StatusRules: []notification.StatusRule{
{CurrentLevel: notification.Ok, PreviousLevel: levelPtr(notification.Warn)},
{CurrentLevel: notification.Critical},
},
}
}
tests := []struct {
name string
newName string
endpoint influxdb.NotificationEndpoint
rule influxdb.NotificationRule
}{
{
name: "pager duty",
newName: "pager_duty_name",
endpoint: &endpoint.PagerDuty{
Base: endpoint.Base{
ID: newTestIDPtr(13),
Name: "endpoint_0",
Description: "desc",
Status: influxdb.TaskStatusActive,
},
ClientURL: "http://example.com",
RoutingKey: influxdb.SecretField{Key: "-routing-key"},
},
rule: &rule.PagerDuty{
Base: newRuleBase(13),
MessageTemplate: "Template",
},
},
{
name: "slack",
endpoint: &endpoint.Slack{
Base: endpoint.Base{
ID: newTestIDPtr(13),
Name: "endpoint_0",
Description: "desc",
Status: influxdb.TaskStatusInactive,
},
URL: "http://example.com",
Token: influxdb.SecretField{Key: "tokne"},
},
rule: &rule.Slack{
Base: newRuleBase(13),
Channel: "abc",
MessageTemplate: "SLACK TEMPlate",
},
},
{
name: "http none",
endpoint: &endpoint.HTTP{
Base: endpoint.Base{
ID: newTestIDPtr(13),
Name: "endpoint_0",
Description: "desc",
Status: influxdb.TaskStatusInactive,
},
AuthMethod: "none",
Method: "GET",
URL: "http://example.com",
},
rule: &rule.HTTP{
Base: newRuleBase(13),
},
},
}
for _, tt := range tests {
fn := func(t *testing.T) {
endpointSVC := mock.NewNotificationEndpointService()
endpointSVC.FindNotificationEndpointByIDF = func(ctx context.Context, id influxdb.ID) (influxdb.NotificationEndpoint, error) {
if id != tt.endpoint.GetID() {
return nil, errors.New("uh ohhh, wrong id here: " + id.String())
}
return tt.endpoint, nil
}
ruleSVC := mock.NewNotificationRuleStore()
ruleSVC.FindNotificationRuleByIDF = func(ctx context.Context, id influxdb.ID) (influxdb.NotificationRule, error) {
return tt.rule, nil
}
svc := newTestService(
WithNotificationEndpointSVC(endpointSVC),
WithNotificationRuleSVC(ruleSVC),
)
resToClone := ResourceToClone{
Kind: KindNotificationRule,
ID: tt.rule.GetID(),
Name: tt.newName,
}
pkg, err := svc.CreatePkg(context.TODO(), CreateWithExistingResources(resToClone))
require.NoError(t, err)
require.Len(t, pkg.Summary().NotificationRules, 1)
actualRule := pkg.Summary().NotificationRules[0]
assert.Zero(t, actualRule.ID)
assert.Zero(t, actualRule.EndpointID)
assert.Zero(t, actualRule.EndpointType)
assert.Equal(t, "endpoint_0", actualRule.EndpointName)
baseEqual := func(t *testing.T, base rule.Base) {
t.Helper()
expectedName := base.Name
if tt.newName != "" {
expectedName = tt.newName
}
assert.Equal(t, expectedName, actualRule.Name)
assert.Equal(t, base.Description, actualRule.Description)
assert.Equal(t, base.Every.TimeDuration().String(), actualRule.Every)
assert.Equal(t, base.Offset.TimeDuration().String(), actualRule.Offset)
for _, sRule := range base.StatusRules {
expected := SummaryStatusRule{CurrentLevel: sRule.CurrentLevel.String()}
if sRule.PreviousLevel != nil {
expected.PreviousLevel = sRule.PreviousLevel.String()
}
assert.Contains(t, actualRule.StatusRules, expected)
}
for _, tRule := range base.TagRules {
expected := SummaryTagRule{
Key: tRule.Key,
Value: tRule.Value,
Operator: tRule.Operator.String(),
}
assert.Contains(t, actualRule.TagRules, expected)
}
}
switch p := tt.rule.(type) {
case *rule.HTTP:
baseEqual(t, p.Base)
case *rule.PagerDuty:
baseEqual(t, p.Base)
assert.Equal(t, p.MessageTemplate, actualRule.MessageTemplate)
case *rule.Slack:
baseEqual(t, p.Base)
assert.Equal(t, p.MessageTemplate, actualRule.MessageTemplate)
}
require.Len(t, pkg.Summary().NotificationEndpoints, 1)
actualEndpoint := pkg.Summary().NotificationEndpoints[0].NotificationEndpoint
assert.Equal(t, tt.endpoint.GetName(), actualEndpoint.GetName())
assert.Equal(t, tt.endpoint.GetDescription(), actualEndpoint.GetDescription())
assert.Equal(t, tt.endpoint.GetStatus(), actualEndpoint.GetStatus())
}
t.Run(tt.name, fn)
}
})
t.Run("variable", func(t *testing.T) {
tests := []struct {
name string
@ -2242,7 +2408,17 @@ func TestService(t *testing.T) {
endpointSVC.FindNotificationEndpointsF = func(ctx context.Context, f influxdb.NotificationEndpointFilter, _ ...influxdb.FindOptions) ([]influxdb.NotificationEndpoint, int, error) {
id := influxdb.ID(2)
endpoints := []influxdb.NotificationEndpoint{
&endpoint.HTTP{Base: endpoint.Base{ID: &id}},
&endpoint.HTTP{
Base: endpoint.Base{
ID: &id,
Name: "http",
},
URL: "http://example.com",
Username: influxdb.SecretField{Key: id.String() + "-username"},
Password: influxdb.SecretField{Key: id.String() + "-password"},
AuthMethod: "basic",
Method: "POST",
},
}
return endpoints, len(endpoints), nil
}
@ -2260,6 +2436,23 @@ func TestService(t *testing.T) {
}, nil
}
ruleSVC := mock.NewNotificationRuleStore()
ruleSVC.FindNotificationRulesF = func(ctx context.Context, f influxdb.NotificationRuleFilter, _ ...influxdb.FindOptions) ([]influxdb.NotificationRule, int, error) {
out := []influxdb.NotificationRule{&rule.HTTP{Base: rule.Base{ID: 91}}}
return out, len(out), nil
}
ruleSVC.FindNotificationRuleByIDF = func(ctx context.Context, id influxdb.ID) (influxdb.NotificationRule, error) {
return &rule.HTTP{
Base: rule.Base{
ID: id,
Name: "rule_0",
EndpointID: 2,
Every: mustDuration(t, time.Minute),
StatusRules: []notification.StatusRule{{CurrentLevel: notification.Critical}},
},
}, nil
}
labelSVC := mock.NewLabelService()
labelSVC.FindLabelsFn = func(_ context.Context, f influxdb.LabelFilter) ([]*influxdb.Label, error) {
if f.OrgID == nil || *f.OrgID != orgID {
@ -2294,6 +2487,7 @@ func TestService(t *testing.T) {
WithDashboardSVC(dashSVC),
WithLabelSVC(labelSVC),
WithNotificationEndpointSVC(endpointSVC),
WithNotificationRuleSVC(ruleSVC),
WithVariableSVC(varSVC),
)
@ -2321,9 +2515,23 @@ func TestService(t *testing.T) {
require.Len(t, endpoints, 1)
assert.Equal(t, "http", endpoints[0].NotificationEndpoint.GetName())
rules := summary.NotificationRules
require.Len(t, rules, 1)
assert.Equal(t, "rule_0", rules[0].Name)
assert.Equal(t, "http", rules[0].EndpointName)
vars := summary.Variables
require.Len(t, vars, 1)
assert.Equal(t, "variable", vars[0].Name)
})
})
}
func newTestIDPtr(i int) *influxdb.ID {
id := influxdb.ID(i)
return &id
}
func levelPtr(l notification.CheckLevel) *notification.CheckLevel {
return &l
}