diff --git a/CHANGELOG.md b/CHANGELOG.md index 9772f3c95be..89044e74490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,14 @@ ### Features -1. [16234](https://github.com/influxdata/influxdb/pull/16234): add support for notification endpoints to influx templates/pkgs. -2. [16242](https://github.com/influxdata/influxdb/pull/16242): drop id prefix for secret key requirement for notification endpoints -3. [16259](https://github.com/influxdata/influxdb/pull/16259): add support for check resource to pkger parser -4. [16262](https://github.com/influxdata/influxdb/pull/16262): add support for check resource dry run functionality -5. [16275](https://github.com/influxdata/influxdb/pull/16275): add support for check resource apply functionality -6. [16283](https://github.com/influxdata/influxdb/pull/16283): add support for check resource export functionality +1. [16234](https://github.com/influxdata/influxdb/pull/16234): Add support for notification endpoints to influx templates/pkgs. +2. [16242](https://github.com/influxdata/influxdb/pull/16242): Drop id prefix for secret key requirement for notification endpoints +3. [16259](https://github.com/influxdata/influxdb/pull/16259): Add support for check resource to pkger parser +4. [16262](https://github.com/influxdata/influxdb/pull/16262): Add support for check resource dry run functionality +5. [16275](https://github.com/influxdata/influxdb/pull/16275): Add support for check resource apply functionality +6. [16283](https://github.com/influxdata/influxdb/pull/16283): Add support for check resource export functionality 1. [16212](https://github.com/influxdata/influxdb/pull/16212): Add new kv.ForwardCursor interface +1. [16297](https://github.com/influxdata/influxdb/pull/16297): Add support for notification rule to pkger parser ### Bug Fixes diff --git a/pkger/clone_resource.go b/pkger/clone_resource.go index c38812558c6..4fe5f869d93 100644 --- a/pkger/clone_resource.go +++ b/pkger/clone_resource.go @@ -89,8 +89,8 @@ func checkToResource(ch influxdb.Check, name string) Resource { assignBase := func(base icheck.Base) { r[fieldQuery] = base.Query.Text r[fieldCheckStatusMessageTemplate] = base.StatusMessageTemplate - assignFluxDur(fieldCheckEvery, base.Every) - assignFluxDur(fieldCheckOffset, base.Offset) + assignFluxDur(fieldEvery, base.Every) + assignFluxDur(fieldOffset, base.Offset) var tags []Resource for _, t := range base.Tags { if t.Valid() != nil { @@ -112,7 +112,7 @@ func checkToResource(ch influxdb.Check, name string) Resource { assignBase(cT.Base) assignFluxDur(fieldCheckTimeSince, cT.TimeSince) assignFluxDur(fieldCheckStaleTime, cT.StaleTime) - r[fieldCheckLevel] = cT.Level.String() + r[fieldLevel] = cT.Level.String() assignNonZeroBools(r, map[string]bool{fieldCheckReportZero: cT.ReportZero}) case *icheck.Threshold: r[fieldKind] = KindCheckThreshold.title() @@ -127,7 +127,7 @@ func checkToResource(ch influxdb.Check, name string) Resource { } func convertThreshold(th icheck.ThresholdConfig) Resource { - r := Resource{fieldCheckLevel: th.GetLevel().String()} + r := Resource{fieldLevel: th.GetLevel().String()} switch realType := th.(type) { case icheck.Lesser: diff --git a/pkger/models.go b/pkger/models.go index 19b598876b6..090c928bae0 100644 --- a/pkger/models.go +++ b/pkger/models.go @@ -6,6 +6,7 @@ import ( "fmt" "net/url" "reflect" + "sort" "strconv" "strings" "time" @@ -29,6 +30,7 @@ const ( KindNotificationEndpointPagerDuty Kind = "notification_endpoint_pager_duty" KindNotificationEndpointHTTP Kind = "notification_endpoint_http" KindNotificationEndpointSlack Kind = "notification_endpoint_slack" + KindNotificationRule Kind = "notification_rule" KindPackage Kind = "package" KindTelegraf Kind = "telegraf" KindVariable Kind = "variable" @@ -45,6 +47,7 @@ var kinds = map[Kind]bool{ KindNotificationEndpointHTTP: true, KindNotificationEndpointPagerDuty: true, KindNotificationEndpointSlack: true, + KindNotificationRule: true, KindPackage: true, KindTelegraf: true, KindVariable: true, @@ -97,6 +100,8 @@ func (k Kind) ResourceType() influxdb.ResourceType { KindNotificationEndpointPagerDuty, KindNotificationEndpointSlack: return influxdb.NotificationEndpointResourceType + case KindNotificationRule: + return influxdb.NotificationRuleResourceType case KindTelegraf: return influxdb.TelegrafsResourceType case KindVariable: @@ -107,7 +112,11 @@ func (k Kind) ResourceType() influxdb.ResourceType { } func (k Kind) title() string { - return strings.Title(k.String()) + pieces := strings.Split(string(k), "_") + for i := range pieces { + pieces[i] = strings.Title(pieces[i]) + } + return strings.Join(pieces, "_") } func (k Kind) is(comps ...Kind) bool { @@ -448,6 +457,7 @@ type Summary struct { Checks []SummaryCheck `json:"checks"` Dashboards []SummaryDashboard `json:"dashboards"` NotificationEndpoints []SummaryNotificationEndpoint `json:"notificationEndpoints"` + NotificationRules []SummaryNotificationRule `json:"notificationRules"` Labels []SummaryLabel `json:"labels"` LabelMappings []SummaryLabelMapping `json:"labelMappings"` TelegrafConfigs []SummaryTelegraf `json:"telegrafConfigs"` @@ -583,7 +593,7 @@ func (s *SummaryChart) UnmarshalJSON(b []byte) error { return nil } -// SummaryNotificationEndpoint provides a summary of a pkg endpoint rule. +// SummaryNotificationEndpoint provides a summary of a pkg notification endpoint. type SummaryNotificationEndpoint struct { NotificationEndpoint influxdb.NotificationEndpoint `json:"notificationEndpoint"` LabelAssociations []SummaryLabel `json:"labelAssociations"` @@ -606,6 +616,33 @@ func (s *SummaryNotificationEndpoint) UnmarshalJSON(b []byte) error { return err } +// Summary types for NotificationRules which provide a summary of a pkg notification rule. +type ( + SummaryNotificationRule struct { + Name string `json:"name"` + Description string `json:"description"` + EndpointName string `json:"endpointName"` + Every string `json:"every"` + LabelAssociations []SummaryLabel `json:"labelAssociations"` + Offset string `json:"offset"` + MessageTemplate string `json:"messageTemplate"` + Status influxdb.Status `json:"status"` + StatusRules []SummaryStatusRule `json:"statusRules"` + TagRules []SummaryTagRule `json:"tagRules"` + } + + SummaryStatusRule struct { + CurrentLevel string `json:"curLvl"` + PreviousLevel string `json:"prevLvl"` + } + + SummaryTagRule struct { + Key string `json:"key"` + Value string `json:"value"` + Operator string `json:"operator"` + } +) + // SummaryLabel provides a summary of a pkg label. type SummaryLabel struct { ID SafeID `json:"id"` @@ -646,12 +683,16 @@ type SummaryVariable struct { const ( fieldAssociations = "associations" fieldDescription = "description" + fieldEvery = "every" fieldKey = "key" fieldKind = "kind" fieldLanguage = "language" + fieldLevel = "level" fieldMin = "min" fieldMax = "max" fieldName = "name" + fieldOffset = "offset" + fieldOperator = "operator" fieldPrefix = "prefix" fieldQuery = "query" fieldSuffix = "suffix" @@ -806,9 +847,6 @@ const ( const ( fieldCheckAllValues = "allValues" - fieldCheckEvery = "every" - fieldCheckLevel = "level" - fieldCheckOffset = "offset" fieldCheckReportZero = "reportZero" fieldCheckStaleTime = "staleTime" fieldCheckStatusMessageTemplate = "statusMessageTemplate" @@ -866,7 +904,7 @@ func (c *check) ResourceType() influxdb.ResourceType { func (c *check) Status() influxdb.Status { status := influxdb.Status(c.status) if status == "" { - status = influxdb.TaskStatusActive + status = influxdb.Active } return status } @@ -912,7 +950,7 @@ func (c *check) valid() []validationErr { var vErrs []validationErr if c.every == 0 { vErrs = append(vErrs, validationErr{ - Field: fieldCheckEvery, + Field: fieldEvery, Msg: "duration value must be provided that is >= 5s (seconds)", }) } @@ -922,18 +960,19 @@ func (c *check) valid() []validationErr { Msg: "must provide a non zero value", }) } - if c.status != "" && !(c.status == influxdb.TaskStatusActive || c.status == influxdb.TaskStatusInactive) { - vErrs = append(vErrs, validationErr{ - Field: fieldStatus, - Msg: "must be 1 of [active, inactive]", - }) - } if c.statusMessage == "" { vErrs = append(vErrs, validationErr{ Field: fieldCheckStatusMessageTemplate, Msg: `must provide a template; ex. "Check: ${ r._check_name } is: ${ r._level }"`, }) } + if status := c.Status(); status != influxdb.Active && status != influxdb.Inactive { + vErrs = append(vErrs, validationErr{ + Field: fieldStatus, + Msg: "must be 1 of [active, inactive]", + }) + } + switch c.kind { case checkKindThreshold: if len(c.thresholds) == 0 { @@ -990,7 +1029,7 @@ func (t threshold) valid() []validationErr { var vErrs []validationErr if notification.ParseCheckLevel(t.level) == notification.Unknown { vErrs = append(vErrs, validationErr{ - Field: fieldCheckLevel, + Field: fieldLevel, Msg: fmt.Sprintf("must be 1 in [CRIT, WARN, INFO, OK]; got=%q", t.level), }) } @@ -1270,7 +1309,7 @@ func (n *notificationEndpoint) base() endpoint.Base { e := endpoint.Base{ Name: n.Name(), Description: n.description, - Status: influxdb.TaskStatusActive, + Status: influxdb.Active, } if id := n.ID(); id > 0 { e.ID = &id @@ -1344,7 +1383,8 @@ func (n *notificationEndpoint) valid() []validationErr { }) } - if n.status != "" && influxdb.TaskStatusInactive != n.status && influxdb.TaskStatusActive != n.status { + status := influxdb.Status(n.status) + if status != "" && influxdb.Inactive != status && influxdb.Active != status { failures = append(failures, validationErr{ Field: fieldStatus, Msg: "not a valid status; valid statues are one of [active, inactive]", @@ -1415,6 +1455,163 @@ func (n mapperNotificationEndpoints) Len() int { return len(n) } +const ( + fieldNotificationRuleCurrentLevel = "currentLevel" + fieldNotificationRuleEndpointName = "endpointName" + fieldNotificationRuleMessageTemplate = "messageTemplate" + fieldNotificationRulePreviousLevel = "previousLevel" + fieldNotificationRuleStatusRules = "statusRules" + fieldNotificationRuleTagRules = "tagRules" +) + +type notificationRule struct { + name string + description string + endpointName string + every time.Duration + msgTemplate string + offset time.Duration + status string + statusRules []struct{ curLvl, prevLvl string } + tagRules []struct{ k, v, op string } + + labels sortedLabels +} + +func (r *notificationRule) Name() string { + return r.name +} + +func (r *notificationRule) ResourceType() influxdb.ResourceType { + return KindNotificationRule.ResourceType() +} + +func (r *notificationRule) Status() influxdb.Status { + if r.status == "" { + return influxdb.Active + } + return influxdb.Status(r.status) +} + +func (r *notificationRule) summarize() SummaryNotificationRule { + sum := SummaryNotificationRule{ + Name: r.Name(), + EndpointName: r.endpointName, + Description: r.description, + Every: r.every.String(), + LabelAssociations: toSummaryLabels(r.labels...), + Offset: r.offset.String(), + MessageTemplate: r.msgTemplate, + Status: r.Status(), + } + + for _, sRule := range r.statusRules { + sum.StatusRules = append(sum.StatusRules, SummaryStatusRule{ + CurrentLevel: sRule.curLvl, + PreviousLevel: sRule.prevLvl, + }) + } + sort.Slice(sum.StatusRules, func(i, j int) bool { + si, sj := sum.StatusRules[i], sum.StatusRules[j] + if si.CurrentLevel == sj.CurrentLevel { + return si.PreviousLevel < sj.PreviousLevel + } + return si.CurrentLevel < sj.CurrentLevel + }) + + for _, tRule := range r.tagRules { + sum.TagRules = append(sum.TagRules, SummaryTagRule{ + Key: tRule.k, + Value: tRule.v, + Operator: tRule.op, + }) + } + sort.Slice(sum.TagRules, func(i, j int) bool { + ti, tj := sum.TagRules[i], sum.TagRules[j] + if ti.Key == tj.Key && ti.Value == tj.Value { + return ti.Operator < tj.Operator + } + if ti.Key == tj.Key { + return ti.Value < tj.Value + } + return ti.Key < tj.Key + }) + + return sum +} + +func (r *notificationRule) valid() []validationErr { + var vErrs []validationErr + if r.endpointName == "" { + vErrs = append(vErrs, validationErr{ + Field: fieldNotificationRuleEndpointName, + Msg: "must be provided", + }) + } + if r.every == 0 { + vErrs = append(vErrs, validationErr{ + Field: fieldEvery, + Msg: "must be provided", + }) + } + if status := r.Status(); status != influxdb.Active && status != influxdb.Inactive { + vErrs = append(vErrs, validationErr{ + Field: fieldStatus, + Msg: fmt.Sprintf("must be 1 in [active, inactive]; got=%q", r.status), + }) + } + + if len(r.statusRules) == 0 { + vErrs = append(vErrs, validationErr{ + Field: fieldNotificationRuleStatusRules, + Msg: "must provide at least 1", + }) + } + + var sRuleErrs []validationErr + for i, sRule := range r.statusRules { + if notification.ParseCheckLevel(sRule.curLvl) == notification.Unknown { + sRuleErrs = append(sRuleErrs, validationErr{ + Field: fieldNotificationRuleCurrentLevel, + Msg: fmt.Sprintf("must be 1 in [CRIT, WARN, INFO, OK]; got=%q", sRule.curLvl), + Index: intPtr(i), + }) + } + if sRule.prevLvl != "" && notification.ParseCheckLevel(sRule.prevLvl) == notification.Unknown { + sRuleErrs = append(sRuleErrs, validationErr{ + Field: fieldNotificationRulePreviousLevel, + Msg: fmt.Sprintf("must be 1 in [CRIT, WARN, INFO, OK]; got=%q", sRule.prevLvl), + Index: intPtr(i), + }) + } + } + if len(sRuleErrs) > 0 { + vErrs = append(vErrs, validationErr{ + Field: fieldNotificationRuleStatusRules, + Nested: sRuleErrs, + }) + } + + var tagErrs []validationErr + for i, tRule := range r.tagRules { + if tRule.op != "equal" { + tagErrs = append(tagErrs, validationErr{ + Field: fieldOperator, + Msg: fmt.Sprintf("must be 1 in [equal]; got=%q", tRule.op), + Index: intPtr(i), + }) + } + } + if len(tagErrs) > 0 { + vErrs = append(vErrs, validationErr{ + Field: fieldNotificationRuleTagRules, + Nested: tagErrs, + }) + } + + return vErrs +} + const ( fieldTelegrafConfig = "config" ) diff --git a/pkger/parser.go b/pkger/parser.go index f62955a4ae0..29ddca2fb58 100644 --- a/pkger/parser.go +++ b/pkger/parser.go @@ -138,6 +138,7 @@ type Pkg struct { mChecks map[string]*check mDashboards []*dashboard mNotificationEndpoints map[string]*notificationEndpoint + mNotificationRules []*notificationRule mTelegrafs []*telegraf mVariables map[string]*variable @@ -175,6 +176,10 @@ func (p *Pkg) Summary() Summary { sum.NotificationEndpoints = append(sum.NotificationEndpoints, n.summarize()) } + for _, r := range p.notificationRules() { + sum.NotificationRules = append(sum.NotificationRules, r.summarize()) + } + for _, t := range p.telegrafs() { sum.TelegrafConfigs = append(sum.TelegrafConfigs, t.summarize()) } @@ -291,6 +296,12 @@ func (p *Pkg) notificationEndpoints() []*notificationEndpoint { return endpoints } +func (p *Pkg) notificationRules() []*notificationRule { + rules := p.mNotificationRules[:] + sort.Slice(rules, func(i, j int) bool { return rules[i].name < rules[j].name }) + return rules +} + func (p *Pkg) secrets() map[string]bool { // copies the secrets map so we can destroy this one without concern secrets := make(map[string]bool, len(p.mSecrets)) @@ -426,6 +437,7 @@ func (p *Pkg) graphResources() error { p.graphChecks, p.graphDashboards, p.graphNotificationEndpoints, + p.graphNotificationRules, p.graphTelegrafs, } @@ -528,9 +540,9 @@ func (p *Pkg) graphChecks() *parseErr { kind: k.checkKind, name: r.Name(), description: r.stringShort(fieldDescription), - every: r.durationShort(fieldCheckEvery), - level: r.stringShort(fieldCheckLevel), - offset: r.durationShort(fieldCheckOffset), + every: r.durationShort(fieldEvery), + level: r.stringShort(fieldLevel), + offset: r.durationShort(fieldOffset), query: strings.TrimSpace(r.stringShort(fieldQuery)), reportZero: r.boolShort(fieldCheckReportZero), staleTime: r.durationShort(fieldCheckStaleTime), @@ -548,7 +560,7 @@ func (p *Pkg) graphChecks() *parseErr { ch.thresholds = append(ch.thresholds, threshold{ threshType: thresholdType(normStr(th.stringShort(fieldType))), allVals: th.boolShort(fieldCheckAllValues), - level: strings.TrimSpace(strings.ToUpper(th.stringShort(fieldCheckLevel))), + level: strings.TrimSpace(strings.ToUpper(th.stringShort(fieldLevel))), max: th.float64Short(fieldMax), min: th.float64Short(fieldMin), val: th.float64Short(fieldValue), @@ -680,6 +692,46 @@ func (p *Pkg) graphNotificationEndpoints() *parseErr { return nil } +func (p *Pkg) graphNotificationRules() *parseErr { + p.mNotificationRules = make([]*notificationRule, 0) + return p.eachResource(KindNotificationRule, 1, func(r Resource) []validationErr { + rule := ¬ificationRule{ + name: r.Name(), + endpointName: r.stringShort(fieldNotificationRuleEndpointName), + description: r.stringShort(fieldDescription), + every: r.durationShort(fieldEvery), + msgTemplate: r.stringShort(fieldNotificationRuleMessageTemplate), + offset: r.durationShort(fieldOffset), + status: normStr(r.stringShort(fieldStatus)), + } + + for _, sRule := range r.slcResource(fieldNotificationRuleStatusRules) { + rule.statusRules = append(rule.statusRules, struct{ curLvl, prevLvl string }{ + curLvl: strings.TrimSpace(strings.ToUpper(sRule.stringShort(fieldNotificationRuleCurrentLevel))), + prevLvl: strings.TrimSpace(strings.ToUpper(sRule.stringShort(fieldNotificationRulePreviousLevel))), + }) + } + + for _, tRule := range r.slcResource(fieldNotificationRuleTagRules) { + rule.tagRules = append(rule.tagRules, struct{ k, v, op string }{ + k: tRule.stringShort(fieldKey), + v: tRule.stringShort(fieldValue), + op: normStr(tRule.stringShort(fieldOperator)), + }) + } + + failures := p.parseNestedLabels(r, func(l *label) error { + rule.labels = append(rule.labels, l) + p.mLabels[l.Name()].setMapping(rule, false) + return nil + }) + sort.Sort(rule.labels) + + p.mNotificationRules = append(p.mNotificationRules, rule) + return append(failures, rule.valid()...) + }) +} + func (p *Pkg) graphVariables() *parseErr { p.mVariables = make(map[string]*variable) return p.eachResource(KindVariable, 1, func(r Resource) []validationErr { diff --git a/pkger/parser_test.go b/pkger/parser_test.go index d966163a619..294518dc529 100644 --- a/pkger/parser_test.go +++ b/pkger/parser_test.go @@ -492,7 +492,7 @@ spec: }, } assert.Equal(t, expectedThresholds, thresholdCheck.Thresholds) - assert.Equal(t, influxdb.Status(influxdb.TaskStatusInactive), check1.Status) + assert.Equal(t, influxdb.Inactive, check1.Status) assert.Len(t, check1.LabelAssociations, 1) check2 := sum.Checks[1] @@ -512,7 +512,7 @@ spec: } expectedBase.Query.Text = "from(bucket: \"rucket_1\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r._measurement == \"cpu\")\n |> filter(fn: (r) => r._field == \"usage_idle\")\n |> aggregateWindow(every: 1m, fn: mean)\n |> yield(name: \"mean\")" assert.Equal(t, expectedBase, deadmanCheck.Base) - assert.Equal(t, influxdb.Status(influxdb.TaskStatusActive), check2.Status) + assert.Equal(t, influxdb.Active, check2.Status) assert.Equal(t, mustDuration(t, 10*time.Minute), deadmanCheck.StaleTime) assert.Equal(t, mustDuration(t, 90*time.Second), deadmanCheck.TimeSince) assert.True(t, deadmanCheck.ReportZero) @@ -579,7 +579,7 @@ spec: resErr: testPkgResourceError{ name: "missing every duration", validationErrs: 1, - valFields: []string{fieldCheckEvery}, + valFields: []string{fieldEvery}, pkgStr: `apiVersion: 0.1.0 kind: Package meta: @@ -605,7 +605,7 @@ spec: resErr: testPkgResourceError{ name: "invalid threshold value provided", validationErrs: 1, - valFields: []string{fieldCheckLevel}, + valFields: []string{fieldLevel}, pkgStr: `apiVersion: 0.1.0 kind: Package meta: @@ -788,7 +788,7 @@ spec: resErr: testPkgResourceError{ name: "missing every", validationErrs: 1, - valFields: []string{fieldCheckEvery}, + valFields: []string{fieldEvery}, pkgStr: `apiVersion: 0.1.0 kind: Package meta: @@ -3540,6 +3540,302 @@ spec: }) }) + t.Run("pkg with notification rules", func(t *testing.T) { + testfileRunner(t, "testdata/notification_rule", func(t *testing.T, pkg *Pkg) { + sum := pkg.Summary() + rules := sum.NotificationRules + require.Len(t, rules, 1) + + rule := rules[0] + assert.Equal(t, "rule_0", rule.Name) + assert.Equal(t, "endpoint_0", rule.EndpointName) + assert.Equal(t, "desc_0", rule.Description) + assert.Equal(t, (10 * time.Minute).String(), rule.Every) + assert.Equal(t, (30 * time.Second).String(), rule.Offset) + expectedMsgTempl := "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + assert.Equal(t, expectedMsgTempl, rule.MessageTemplate) + assert.Equal(t, influxdb.Active, rule.Status) + + expectedStatusRules := []SummaryStatusRule{ + {CurrentLevel: "CRIT", PreviousLevel: "OK"}, + {CurrentLevel: "WARN"}, + } + assert.Equal(t, expectedStatusRules, rule.StatusRules) + + expectedTagRules := []SummaryTagRule{ + {Key: "k1", Value: "v1", Operator: "equal"}, + {Key: "k1", Value: "v2", Operator: "equal"}, + } + assert.Equal(t, expectedTagRules, rule.TagRules) + + require.Len(t, sum.Labels, 1) + require.Len(t, rule.LabelAssociations, 1) + }) + + t.Run("handles bad config", func(t *testing.T) { + tests := []struct { + kind Kind + resErr testPkgResourceError + }{ + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "missing name", + validationErrs: 1, + valFields: []string{fieldName}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Notification_Rule + endpointName: endpoint_0 + every: 10m + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + statusRules: + - currentLevel: WARN +`, + }, + }, + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "missing endpoint name", + validationErrs: 1, + valFields: []string{fieldNotificationRuleEndpointName}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Notification_Rule + name: rule_0 + every: 10m + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + statusRules: + - currentLevel: WARN +`, + }, + }, + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "missing endpoint name", + validationErrs: 1, + valFields: []string{fieldEvery}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Notification_Rule + name: rule_0 + endpointName: endpoint_0 + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + statusRules: + - currentLevel: WARN +`, + }, + }, + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "missing status rules", + validationErrs: 1, + valFields: []string{fieldNotificationRuleStatusRules}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Notification_Rule + name: rule_0 + endpointName: endpoint_0 + every: 10m + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" +`, + }, + }, + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "bad current status rule level", + validationErrs: 1, + valFields: []string{fieldNotificationRuleStatusRules}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Notification_Rule + name: rule_0 + endpointName: endpoint_0 + every: 10m + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + statusRules: + - currentLevel: WRONGO +`, + }, + }, + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "bad previous status rule level", + validationErrs: 1, + valFields: []string{fieldNotificationRuleStatusRules}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Notification_Rule + name: rule_0 + endpointName: endpoint_0 + every: 10m + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + statusRules: + - currentLevel: CRIT + previousLevel: WRONGO +`, + }, + }, + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "bad tag rule operator", + validationErrs: 1, + valFields: []string{fieldNotificationRuleTagRules}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Notification_Rule + name: rule_0 + endpointName: endpoint_0 + every: 10m + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + statusRules: + - currentLevel: CRIT + tagRules: + - key: k1 + value: v1 + operator: WRONG +`, + }, + }, + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "bad status provided", + validationErrs: 1, + valFields: []string{fieldStatus}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Notification_Rule + name: rule_0 + endpointName: endpoint_0 + every: 10m + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + status: RANDO + statusRules: + - currentLevel: CRIT +`, + }, + }, + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "label association does not exist", + validationErrs: 1, + valFields: []string{fieldAssociations}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Notification_Rule + name: rule_0 + endpointName: endpoint_0 + every: 10m + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + statusRules: + - currentLevel: CRIT + associations: + - kind: Label + name: label_1 +`, + }, + }, + { + kind: KindNotificationRule, + resErr: testPkgResourceError{ + name: "label association dupe", + validationErrs: 1, + valFields: []string{fieldAssociations}, + pkgStr: `apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Label + name: label_1 + - kind: Notification_Rule + name: rule_0 + endpointName: endpoint_0 + every: 10m + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + statusRules: + - currentLevel: CRIT + associations: + - kind: Label + name: label_1 + - kind: Label + name: label_1 +`, + }, + }, + } + + for _, tt := range tests { + testPkgErrors(t, tt.kind, tt.resErr) + } + }) + }) + t.Run("pkg with telegraf and label associations", func(t *testing.T) { t.Run("with valid fields", func(t *testing.T) { testfileRunner(t, "testdata/telegraf", func(t *testing.T, pkg *Pkg) { diff --git a/pkger/testdata/notification_rule.json b/pkger/testdata/notification_rule.json new file mode 100644 index 00000000000..99e4955f3b9 --- /dev/null +++ b/pkger/testdata/notification_rule.json @@ -0,0 +1,56 @@ +{ + "apiVersion": "0.1.0", + "kind": "Package", + "meta": { + "pkgName": "pkg_name", + "pkgVersion": "1", + "description": "pack description" + }, + "spec": { + "resources": [ + { + "kind": "Label", + "name": "label_1" + }, + { + "kind": "Notification_Rule", + "name": "rule_0", + "description": "desc_0", + "endpointName": "endpoint_0", + "every": "10m", + "offset": "30s", + "messageTemplate": "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }", + "status": "active", + "statusRules": [ + { + "currentLevel": "warn" + }, + { + "currentLevel": "CRIT", + "previousLevel": "OK" + } + ], + "tagRules": [ + { + "key": "k1", + "value": "v2", + "operator": "EQUAL" + }, + { + "key": "k1", + "value": "v1", + "operator": "EQUAL" + } + ], + "associations": [ + { + "kind": "Label", + "name": "label_1" + } + ] + } + ] + } +} + + diff --git a/pkger/testdata/notification_rule.yml b/pkger/testdata/notification_rule.yml new file mode 100644 index 00000000000..36b4ae777d6 --- /dev/null +++ b/pkger/testdata/notification_rule.yml @@ -0,0 +1,32 @@ +apiVersion: 0.1.0 +kind: Package +meta: + pkgName: pkg_name + pkgVersion: 1 + description: pack description +spec: + resources: + - kind: Label + name: label_1 + - kind: Notification_Rule + name: rule_0 + description: desc_0 + endpointName: endpoint_0 + every: 10m + offset: 30s + messageTemplate: "Notification Rule: ${ r._notification_rule_name } triggered by check: ${ r._check_name }: ${ r._message }" + status: active + statusRules: + - currentLevel: WARN + - currentLevel: CRIT + previousLevel: OK + tagRules: + - key: k1 + value: v2 + operator: eQuAl + - key: k1 + value: v1 + operator: eQuAl + associations: + - kind: Label + name: label_1