92d6762a3a
* introduce new fields created_by in rule tables * update domain model and compat layer to support UpdatedBy * add alert rule generator mutators for UpdatedBy * ignore UpdatedBy in diff and hash calculation * Add user context to alert rule insert/update operations Updated InsertAlertRules and UpdateAlertRules methods to accept a user context parameter. This change ensures auditability and better tracking of user actions when creating or updating alert rules. Adjusted all relevant calls and interfaces to pass the user context accordingly. * set UpdatedBy in PreSave because this is where Updated is set * Use nil userID for system-initiated updates This ensures differentiation between system and user-initiated changes for better traceability and clarity in update origins. --------- Signed-off-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
1366 lines
36 KiB
Go
1366 lines
36 KiB
Go
package models
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"slices"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-openapi/strfmt"
|
|
alertingNotify "github.com/grafana/alerting/notify"
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
|
"github.com/prometheus/alertmanager/pkg/labels"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/exp/maps"
|
|
|
|
alertingModels "github.com/grafana/alerting/models"
|
|
|
|
"github.com/grafana/grafana/pkg/expr"
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
|
"github.com/grafana/grafana/pkg/services/folder"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
var (
|
|
RuleMuts = AlertRuleMutators{}
|
|
NSMuts = NotificationSettingsMutators{}
|
|
RuleGen = &AlertRuleGenerator{
|
|
mutators: []AlertRuleMutator{
|
|
RuleMuts.WithUniqueUID(), RuleMuts.WithUniqueTitle(),
|
|
},
|
|
}
|
|
)
|
|
|
|
type AlertRuleMutator func(r *AlertRule)
|
|
|
|
type AlertRuleGenerator struct {
|
|
AlertRuleMutators
|
|
mutators []AlertRuleMutator
|
|
}
|
|
|
|
func (g *AlertRuleGenerator) With(mutators ...AlertRuleMutator) *AlertRuleGenerator {
|
|
return &AlertRuleGenerator{
|
|
AlertRuleMutators: g.AlertRuleMutators,
|
|
mutators: append(g.mutators, mutators...),
|
|
}
|
|
}
|
|
|
|
func (g *AlertRuleGenerator) Generate() AlertRule {
|
|
randNoDataState := func() NoDataState {
|
|
s := [...]NoDataState{
|
|
Alerting,
|
|
NoData,
|
|
OK,
|
|
}
|
|
return s[rand.Intn(len(s))]
|
|
}
|
|
|
|
randErrState := func() ExecutionErrorState {
|
|
s := [...]ExecutionErrorState{
|
|
AlertingErrState,
|
|
ErrorErrState,
|
|
OkErrState,
|
|
}
|
|
return s[rand.Intn(len(s))]
|
|
}
|
|
|
|
interval := (rand.Int63n(6) + 1) * 10
|
|
forInterval := time.Duration(interval*rand.Int63n(6)) * time.Second
|
|
|
|
var annotations map[string]string = nil
|
|
if rand.Int63()%2 == 0 {
|
|
annotations = GenerateAlertLabels(rand.Intn(5), "ann-")
|
|
}
|
|
var labels map[string]string = nil
|
|
if rand.Int63()%2 == 0 {
|
|
labels = GenerateAlertLabels(rand.Intn(5), "lbl-")
|
|
}
|
|
|
|
var dashUID *string = nil
|
|
var panelID *int64 = nil
|
|
if rand.Int63()%2 == 0 {
|
|
d := util.GenerateShortUID()
|
|
dashUID = &d
|
|
p := rand.Int63n(1500)
|
|
panelID = &p
|
|
}
|
|
|
|
var ns []NotificationSettings
|
|
if rand.Int63()%2 == 0 {
|
|
ns = append(ns, NotificationSettingsGen()())
|
|
}
|
|
|
|
var updatedBy *UserUID
|
|
if rand.Int63()%2 == 0 {
|
|
updatedBy = util.Pointer(UserUID(util.GenerateShortUID()))
|
|
}
|
|
|
|
rule := AlertRule{
|
|
ID: 0,
|
|
OrgID: rand.Int63n(1500) + 1, // Prevent OrgID=0 as this does not pass alert rule validation.
|
|
Title: fmt.Sprintf("title-%s", util.GenerateShortUID()),
|
|
Condition: "A",
|
|
Data: []AlertQuery{g.GenerateQuery()},
|
|
Updated: time.Now().Add(-time.Duration(rand.Intn(100) + 1)),
|
|
UpdatedBy: updatedBy,
|
|
IntervalSeconds: rand.Int63n(60) + 1,
|
|
Version: rand.Int63n(1500), // Don't generate a rule ID too big for postgres
|
|
UID: util.GenerateShortUID(),
|
|
NamespaceUID: util.GenerateShortUID(),
|
|
DashboardUID: dashUID,
|
|
PanelID: panelID,
|
|
RuleGroup: fmt.Sprintf("group-%s,", util.GenerateShortUID()),
|
|
RuleGroupIndex: rand.Intn(1500),
|
|
NoDataState: randNoDataState(),
|
|
ExecErrState: randErrState(),
|
|
For: forInterval,
|
|
Annotations: annotations,
|
|
Labels: labels,
|
|
NotificationSettings: ns,
|
|
}
|
|
|
|
for _, mutator := range g.mutators {
|
|
mutator(&rule)
|
|
}
|
|
return rule
|
|
}
|
|
|
|
func (g *AlertRuleGenerator) GenerateRef() *AlertRule {
|
|
r := g.Generate()
|
|
return &r
|
|
}
|
|
|
|
func (g *AlertRuleGenerator) getCount(bounds ...int) int {
|
|
count := 0
|
|
if len(bounds) == 0 {
|
|
count = rand.Intn(5) + 1
|
|
}
|
|
if len(bounds) == 1 {
|
|
count = bounds[0]
|
|
}
|
|
if len(bounds) == 2 {
|
|
if bounds[0] > bounds[1] {
|
|
panic("min should not be greater than max")
|
|
} else if bounds[0] < bounds[1] {
|
|
count = rand.Intn(bounds[1]-bounds[0]) + bounds[0]
|
|
} else {
|
|
count = bounds[0]
|
|
}
|
|
}
|
|
if len(bounds) > 2 {
|
|
panic("invalid number of parameter must be up to 2")
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (g *AlertRuleGenerator) GenerateMany(bounds ...int) []AlertRule {
|
|
count := g.getCount(bounds...)
|
|
result := make([]AlertRule, 0, count)
|
|
for i := 0; i < count; i++ {
|
|
result = append(result, g.Generate())
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (g *AlertRuleGenerator) GenerateManyRef(bounds ...int) []*AlertRule {
|
|
count := g.getCount(bounds...)
|
|
|
|
result := make([]*AlertRule, 0)
|
|
for i := 0; i < count; i++ {
|
|
r := g.Generate()
|
|
result = append(result, &r)
|
|
}
|
|
return result
|
|
}
|
|
|
|
type AlertRuleMutators struct {
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithNotEmptyLabels(count int, prefix string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.Labels = GenerateAlertLabels(count, prefix)
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithUniqueID() AlertRuleMutator {
|
|
ids := sync.Map{}
|
|
return func(rule *AlertRule) {
|
|
id := rule.ID
|
|
for {
|
|
_, exists := ids.LoadOrStore(id, struct{}{})
|
|
if !exists {
|
|
rule.ID = id
|
|
return
|
|
}
|
|
id = rand.Int63n(1500) + 1
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithEditorSettingsSimplifiedQueryAndExpressionsSection(enabled bool) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.Metadata.EditorSettings.SimplifiedQueryAndExpressionsSection = enabled
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithEditorSettingsSimplifiedNotificationsSection(enabled bool) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.Metadata.EditorSettings.SimplifiedNotificationsSection = enabled
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithGroupIndex(groupIndex int) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.RuleGroupIndex = groupIndex
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithUniqueGroupIndex() AlertRuleMutator {
|
|
usedIdx := sync.Map{}
|
|
return func(rule *AlertRule) {
|
|
idx := rule.RuleGroupIndex
|
|
for {
|
|
if _, exists := usedIdx.LoadOrStore(idx, struct{}{}); !exists {
|
|
rule.RuleGroupIndex = idx
|
|
return
|
|
}
|
|
idx = rand.Int()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithSequentialGroupIndex() AlertRuleMutator {
|
|
idx := 1
|
|
m := sync.Mutex{}
|
|
return func(rule *AlertRule) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
rule.RuleGroupIndex = idx
|
|
idx++
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithOrgID(orgId int64) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.OrgID = orgId
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithUniqueOrgID() AlertRuleMutator {
|
|
orgs := sync.Map{}
|
|
return func(rule *AlertRule) {
|
|
orgID := rule.OrgID
|
|
for {
|
|
if _, exist := orgs.LoadOrStore(orgID, struct{}{}); !exist {
|
|
rule.OrgID = orgID
|
|
return
|
|
}
|
|
orgID = rand.Int63()
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithNamespaceUIDNotIn generates a random namespace UID if it is among excluded
|
|
func (a *AlertRuleMutators) WithNamespaceUIDNotIn(exclude ...string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
for {
|
|
if !slices.Contains(exclude, rule.NamespaceUID) {
|
|
return
|
|
}
|
|
rule.NamespaceUID = util.GenerateShortUID()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithNamespaceUID(namespaceUID string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.NamespaceUID = namespaceUID
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithNamespace(namespace *folder.Folder) AlertRuleMutator {
|
|
return a.WithNamespaceUID(namespace.UID)
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithInterval(interval time.Duration) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.IntervalSeconds = int64(interval.Seconds())
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithIntervalSeconds(seconds int64) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.IntervalSeconds = seconds
|
|
}
|
|
}
|
|
|
|
// WithIntervalMatching mutator that generates random interval and `for` duration that are times of the provided base interval.
|
|
func (a *AlertRuleMutators) WithIntervalMatching(baseInterval time.Duration) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.IntervalSeconds = int64(baseInterval.Seconds()) * (rand.Int63n(10) + 1)
|
|
rule.For = time.Duration(rule.IntervalSeconds*rand.Int63n(9)+1) * time.Second
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithIntervalBetween(min, max int64) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.IntervalSeconds = rand.Int63n(max-min) + min
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithTitle(title string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.Title = title
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithFor(duration time.Duration) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.For = duration
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithForNTimes(timesOfInterval int64) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.For = time.Duration(rule.IntervalSeconds*timesOfInterval) * time.Second
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithNoDataExecAs(nodata NoDataState) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.NoDataState = nodata
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithErrorExecAs(err ExecutionErrorState) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.ExecErrState = err
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithAnnotations(lbls data.Labels) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.Annotations = lbls
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithAnnotation(key, value string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
if rule.Annotations == nil {
|
|
rule.Annotations = data.Labels{}
|
|
}
|
|
rule.Annotations[key] = value
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithLabels(lbls data.Labels) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.Labels = lbls
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithLabel(key, value string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
if rule.Labels == nil {
|
|
rule.Labels = data.Labels{}
|
|
}
|
|
rule.Labels[key] = value
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithDashboardAndPanel(dashboardUID *string, panelID *int64) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.DashboardUID = dashboardUID
|
|
rule.PanelID = panelID
|
|
}
|
|
}
|
|
|
|
// WithUniqueUID returns AlertRuleMutator that generates a random UID if it is among UIDs known by the instance of mutator.
|
|
// NOTE: two instances of the mutator do not share known UID.
|
|
// Example #1 reuse mutator instance:
|
|
//
|
|
// mut := WithUniqueUID()
|
|
// rule1 := RuleGen.With(mut).Generate()
|
|
// rule2 := RuleGen.With(mut).Generate()
|
|
//
|
|
// Example #2 reuse generator:
|
|
//
|
|
// gen := RuleGen.With(WithUniqueUID())
|
|
// rule1 := gen.Generate()
|
|
// rule2 := gen.Generate()
|
|
//
|
|
// Example #3 non-unique:
|
|
//
|
|
// rule1 := RuleGen.With(WithUniqueUID()).Generate
|
|
// rule2 := RuleGen.With(WithUniqueUID()).Generate
|
|
func (a *AlertRuleMutators) WithUniqueUID() AlertRuleMutator {
|
|
uids := sync.Map{}
|
|
return func(rule *AlertRule) {
|
|
uid := rule.UID
|
|
for {
|
|
_, exist := uids.LoadOrStore(uid, struct{}{})
|
|
if !exist {
|
|
rule.UID = uid
|
|
return
|
|
}
|
|
uid = util.GenerateShortUID()
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithUniqueTitle returns AlertRuleMutator that generates a random title if the rule's title is among titles known by the instance of mutator.
|
|
// Two instances of the mutator do not share known titles.
|
|
// Example #1 reuse mutator instance:
|
|
//
|
|
// mut := WithUniqueTitle()
|
|
// rule1 := RuleGen.With(mut).Generate()
|
|
// rule2 := RuleGen.With(mut).Generate()
|
|
//
|
|
// Example #2 reuse generator:
|
|
//
|
|
// gen := RuleGen.With(WithUniqueTitle())
|
|
// rule1 := gen.Generate()
|
|
// rule2 := gen.Generate()
|
|
//
|
|
// Example #3 non-unique:
|
|
//
|
|
// rule1 := RuleGen.With(WithUniqueTitle()).Generate
|
|
// rule2 := RuleGen.With(WithUniqueTitle()).Generate
|
|
func (a *AlertRuleMutators) WithUniqueTitle() AlertRuleMutator {
|
|
titles := sync.Map{}
|
|
return func(rule *AlertRule) {
|
|
title := rule.Title
|
|
for {
|
|
_, exist := titles.LoadOrStore(title, struct{}{})
|
|
if !exist {
|
|
rule.Title = title
|
|
return
|
|
}
|
|
title = fmt.Sprintf("title-%s", util.GenerateShortUID())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithQuery(query ...AlertQuery) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.Data = query
|
|
if len(query) > 1 {
|
|
rule.Condition = query[0].RefID
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithGroupName(groupName string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.RuleGroup = groupName
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithGroupPrefix(prefix string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.RuleGroup = fmt.Sprintf("%s%s", prefix, util.GenerateShortUID())
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithGroupKey(groupKey AlertRuleGroupKey) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.RuleGroup = groupKey.RuleGroup
|
|
rule.OrgID = groupKey.OrgID
|
|
rule.NamespaceUID = groupKey.NamespaceUID
|
|
}
|
|
}
|
|
|
|
// WithSameGroup generates a random group name and assigns it to all rules passed to it
|
|
func (a *AlertRuleMutators) WithSameGroup() AlertRuleMutator {
|
|
once := sync.Once{}
|
|
name := ""
|
|
return func(rule *AlertRule) {
|
|
once.Do(func() {
|
|
name = util.GenerateShortUID()
|
|
})
|
|
rule.RuleGroup = name
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithNotificationSettingsGen(ns func() NotificationSettings) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.NotificationSettings = []NotificationSettings{ns()}
|
|
}
|
|
}
|
|
func (a *AlertRuleMutators) WithNotificationSettings(ns NotificationSettings) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.NotificationSettings = []NotificationSettings{ns}
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithNoNotificationSettings() AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.NotificationSettings = nil
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithIsPaused(paused bool) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
rule.IsPaused = paused
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithRandomRecordingRules() AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
if rand.Int63()%2 == 0 {
|
|
return
|
|
}
|
|
ConvertToRecordingRule(rule)
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithAllRecordingRules() AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
ConvertToRecordingRule(rule)
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithMetric(metric string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
if rule.Record == nil {
|
|
rule.Record = &Record{}
|
|
}
|
|
rule.Record.Metric = metric
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithRecordFrom(from string) AlertRuleMutator {
|
|
return func(rule *AlertRule) {
|
|
if rule.Record == nil {
|
|
rule.Record = &Record{}
|
|
}
|
|
rule.Record.From = from
|
|
}
|
|
}
|
|
|
|
func (a *AlertRuleMutators) WithUpdatedBy(uid *UserUID) AlertRuleMutator {
|
|
return func(r *AlertRule) {
|
|
r.UpdatedBy = uid
|
|
}
|
|
}
|
|
|
|
func (g *AlertRuleGenerator) GenerateLabels(min, max int, prefix string) data.Labels {
|
|
count := max
|
|
if min > max {
|
|
panic("min should not be greater than max")
|
|
} else if min < max {
|
|
count = rand.Intn(max-min) + min
|
|
}
|
|
labels := make(data.Labels, count)
|
|
for i := 0; i < count; i++ {
|
|
labels[prefix+"key-"+util.GenerateShortUID()] = prefix + "value-" + util.GenerateShortUID()
|
|
}
|
|
return labels
|
|
}
|
|
|
|
func GenerateAlertLabels(count int, prefix string) data.Labels {
|
|
return RuleGen.GenerateLabels(count, count, prefix)
|
|
}
|
|
|
|
func GenerateAlertQuery() AlertQuery {
|
|
return RuleGen.GenerateQuery()
|
|
}
|
|
|
|
func (g *AlertRuleGenerator) GenerateQuery() AlertQuery {
|
|
f := rand.Intn(10) + 5
|
|
t := rand.Intn(f)
|
|
|
|
return AlertQuery{
|
|
DatasourceUID: util.GenerateShortUID(),
|
|
Model: json.RawMessage(fmt.Sprintf(`{
|
|
"%s": "%s",
|
|
"%s":"%d"
|
|
}`, util.GenerateShortUID(), util.GenerateShortUID(), util.GenerateShortUID(), rand.Int())),
|
|
RelativeTimeRange: RelativeTimeRange{
|
|
From: Duration(time.Duration(f) * time.Minute),
|
|
To: Duration(time.Duration(t) * time.Minute),
|
|
},
|
|
RefID: util.GenerateShortUID(),
|
|
QueryType: util.GenerateShortUID(),
|
|
}
|
|
}
|
|
|
|
func (g *AlertRuleGenerator) WithCondition(condition string) AlertRuleMutator {
|
|
return func(r *AlertRule) {
|
|
r.Condition = condition
|
|
}
|
|
}
|
|
|
|
// GenerateRuleKey generates a random alert rule key
|
|
func GenerateRuleKey(orgID int64) AlertRuleKey {
|
|
return AlertRuleKey{
|
|
OrgID: orgID,
|
|
UID: util.GenerateShortUID(),
|
|
}
|
|
}
|
|
|
|
// GenerateRuleKeyWithGroup generates a random alert rule key with group
|
|
func GenerateRuleKeyWithGroup(orgID int64) AlertRuleKeyWithGroup {
|
|
return AlertRuleKeyWithGroup{
|
|
AlertRuleKey: GenerateRuleKey(orgID),
|
|
RuleGroup: util.GenerateShortUID(),
|
|
}
|
|
}
|
|
|
|
// GenerateGroupKey generates a random group key
|
|
func GenerateGroupKey(orgID int64) AlertRuleGroupKey {
|
|
return AlertRuleGroupKey{
|
|
OrgID: orgID,
|
|
NamespaceUID: util.GenerateShortUID(),
|
|
RuleGroup: util.GenerateShortUID(),
|
|
}
|
|
}
|
|
|
|
// CopyRule creates a deep copy of AlertRule
|
|
func CopyRule(r *AlertRule, mutators ...AlertRuleMutator) *AlertRule {
|
|
result := AlertRule{
|
|
ID: r.ID,
|
|
OrgID: r.OrgID,
|
|
Title: r.Title,
|
|
Condition: r.Condition,
|
|
Updated: r.Updated,
|
|
UpdatedBy: r.UpdatedBy,
|
|
IntervalSeconds: r.IntervalSeconds,
|
|
Version: r.Version,
|
|
UID: r.UID,
|
|
NamespaceUID: r.NamespaceUID,
|
|
RuleGroup: r.RuleGroup,
|
|
RuleGroupIndex: r.RuleGroupIndex,
|
|
NoDataState: r.NoDataState,
|
|
ExecErrState: r.ExecErrState,
|
|
For: r.For,
|
|
Record: r.Record,
|
|
IsPaused: r.IsPaused,
|
|
}
|
|
|
|
if r.DashboardUID != nil {
|
|
dash := *r.DashboardUID
|
|
result.DashboardUID = &dash
|
|
}
|
|
if r.PanelID != nil {
|
|
p := *r.PanelID
|
|
result.PanelID = &p
|
|
}
|
|
|
|
for _, d := range r.Data {
|
|
q := AlertQuery{
|
|
RefID: d.RefID,
|
|
QueryType: d.QueryType,
|
|
RelativeTimeRange: d.RelativeTimeRange,
|
|
DatasourceUID: d.DatasourceUID,
|
|
}
|
|
q.Model = make([]byte, 0, cap(d.Model))
|
|
q.Model = append(q.Model, d.Model...)
|
|
result.Data = append(result.Data, q)
|
|
}
|
|
|
|
if r.Annotations != nil {
|
|
result.Annotations = make(map[string]string, len(r.Annotations))
|
|
for s, s2 := range r.Annotations {
|
|
result.Annotations[s] = s2
|
|
}
|
|
}
|
|
|
|
if r.Labels != nil {
|
|
result.Labels = make(map[string]string, len(r.Labels))
|
|
for s, s2 := range r.Labels {
|
|
result.Labels[s] = s2
|
|
}
|
|
}
|
|
|
|
if r.Record != nil {
|
|
result.Record = &Record{
|
|
From: r.Record.From,
|
|
Metric: r.Record.Metric,
|
|
}
|
|
}
|
|
|
|
for _, s := range r.NotificationSettings {
|
|
result.NotificationSettings = append(result.NotificationSettings, CopyNotificationSettings(s))
|
|
}
|
|
|
|
if len(mutators) > 0 {
|
|
for _, mutator := range mutators {
|
|
mutator(&result)
|
|
}
|
|
}
|
|
|
|
return &result
|
|
}
|
|
|
|
func CreateClassicConditionExpression(refID string, inputRefID string, reducer string, operation string, threshold int) AlertQuery {
|
|
return AlertQuery{
|
|
RefID: refID,
|
|
QueryType: expr.DatasourceType,
|
|
DatasourceUID: expr.DatasourceUID,
|
|
// the format corresponds to model `ClassicConditionJSON` in /pkg/expr/classic/classic.go
|
|
Model: json.RawMessage(fmt.Sprintf(`
|
|
{
|
|
"refId": "%[1]s",
|
|
"hide": false,
|
|
"type": "classic_conditions",
|
|
"datasource": {
|
|
"uid": "%[6]s",
|
|
"type": "%[7]s"
|
|
},
|
|
"conditions": [
|
|
{
|
|
"type": "query",
|
|
"evaluator": {
|
|
"params": [
|
|
%[4]d
|
|
],
|
|
"type": "%[3]s"
|
|
},
|
|
"operator": {
|
|
"type": "and"
|
|
},
|
|
"query": {
|
|
"params": [
|
|
"%[2]s"
|
|
]
|
|
},
|
|
"reducer": {
|
|
"params": [],
|
|
"type": "%[5]s"
|
|
}
|
|
}
|
|
]
|
|
}`, refID, inputRefID, operation, threshold, reducer, expr.DatasourceUID, expr.DatasourceType)),
|
|
}
|
|
}
|
|
|
|
func CreateReduceExpression(refID string, inputRefID string, reducer string) AlertQuery {
|
|
return AlertQuery{
|
|
RefID: refID,
|
|
QueryType: expr.DatasourceType,
|
|
DatasourceUID: expr.DatasourceUID,
|
|
Model: json.RawMessage(fmt.Sprintf(`
|
|
{
|
|
"refId": "%[1]s",
|
|
"hide": false,
|
|
"type": "reduce",
|
|
"expression": "%[2]s",
|
|
"reducer": "%[3]s",
|
|
"datasource": {
|
|
"uid": "%[4]s",
|
|
"type": "%[5]s"
|
|
}
|
|
}`, refID, inputRefID, reducer, expr.DatasourceUID, expr.DatasourceType)),
|
|
}
|
|
}
|
|
|
|
func CreatePrometheusQuery(refID string, expr string, intervalMs int64, maxDataPoints int64, isInstant bool, datasourceUID string) AlertQuery {
|
|
return AlertQuery{
|
|
RefID: refID,
|
|
QueryType: "",
|
|
DatasourceUID: datasourceUID,
|
|
Model: json.RawMessage(fmt.Sprintf(`
|
|
{
|
|
"refId": "%[1]s",
|
|
"expr": "%[2]s",
|
|
"intervalMs": %[3]d,
|
|
"maxDataPoints": %[4]d,
|
|
"exemplar": false,
|
|
"instant": %[5]t,
|
|
"range": %[6]t,
|
|
"datasource": {
|
|
"uid": "%[7]s",
|
|
"type": "%[8]s"
|
|
}
|
|
}`, refID, expr, intervalMs, maxDataPoints, isInstant, !isInstant, datasourceUID, datasources.DS_PROMETHEUS)),
|
|
}
|
|
}
|
|
|
|
func CreateLokiQuery(refID string, expr string, intervalMs int64, maxDataPoints int64, queryType string, datasourceUID string) AlertQuery {
|
|
return AlertQuery{
|
|
RefID: refID,
|
|
QueryType: queryType,
|
|
DatasourceUID: datasourceUID,
|
|
Model: json.RawMessage(fmt.Sprintf(`
|
|
{
|
|
"refId": "%[1]s",
|
|
"expr": "%[2]s",
|
|
"intervalMs": %[3]d,
|
|
"maxDataPoints": %[4]d,
|
|
"queryType": "%[5]s",
|
|
"datasource": {
|
|
"uid": "%[6]s",
|
|
"type": "%[7]s"
|
|
}
|
|
}`, refID, expr, intervalMs, maxDataPoints, queryType, datasourceUID, datasources.DS_LOKI)),
|
|
}
|
|
}
|
|
|
|
func CreateHysteresisExpression(t *testing.T, refID string, inputRefID string, threshold int, recoveryThreshold int) AlertQuery {
|
|
t.Helper()
|
|
q := AlertQuery{
|
|
RefID: refID,
|
|
QueryType: expr.DatasourceType,
|
|
DatasourceUID: expr.DatasourceUID,
|
|
Model: json.RawMessage(fmt.Sprintf(`
|
|
{
|
|
"refId": "%[1]s",
|
|
"type": "threshold",
|
|
"datasource": {
|
|
"uid": "%[5]s",
|
|
"type": "%[6]s"
|
|
},
|
|
"expression": "%[2]s",
|
|
"conditions": [
|
|
{
|
|
"type": "query",
|
|
"evaluator": {
|
|
"params": [
|
|
%[3]d
|
|
],
|
|
"type": "gt"
|
|
},
|
|
"unloadEvaluator": {
|
|
"params": [
|
|
%[4]d
|
|
],
|
|
"type": "lt"
|
|
}
|
|
}
|
|
]
|
|
}`, refID, inputRefID, threshold, recoveryThreshold, expr.DatasourceUID, expr.DatasourceType)),
|
|
}
|
|
h, err := q.IsHysteresisExpression()
|
|
require.NoError(t, err)
|
|
require.Truef(t, h, "test model is expected to be a hysteresis expression")
|
|
return q
|
|
}
|
|
|
|
type AlertInstanceMutator func(*AlertInstance)
|
|
|
|
// AlertInstanceGen provides a factory function that generates a random AlertInstance.
|
|
// The mutators arguments allows changing fields of the resulting structure.
|
|
func AlertInstanceGen(mutators ...AlertInstanceMutator) *AlertInstance {
|
|
var labels map[string]string = nil
|
|
if rand.Int63()%2 == 0 {
|
|
labels = GenerateAlertLabels(rand.Intn(5), "lbl-")
|
|
}
|
|
|
|
randState := func() InstanceStateType {
|
|
s := [...]InstanceStateType{
|
|
InstanceStateFiring,
|
|
InstanceStateNormal,
|
|
InstanceStatePending,
|
|
InstanceStateNoData,
|
|
InstanceStateError,
|
|
}
|
|
return s[rand.Intn(len(s))]
|
|
}
|
|
|
|
currentStateSince := time.Now().Add(-time.Duration(rand.Intn(100) + 1))
|
|
|
|
instance := &AlertInstance{
|
|
AlertInstanceKey: AlertInstanceKey{
|
|
RuleOrgID: rand.Int63n(1500),
|
|
RuleUID: util.GenerateShortUID(),
|
|
LabelsHash: util.GenerateShortUID(),
|
|
},
|
|
Labels: labels,
|
|
CurrentState: randState(),
|
|
CurrentReason: "TEST-REASON-" + util.GenerateShortUID(),
|
|
CurrentStateSince: currentStateSince,
|
|
CurrentStateEnd: currentStateSince.Add(time.Duration(rand.Intn(100) + 200)),
|
|
LastEvalTime: time.Now().Add(-time.Duration(rand.Intn(100) + 50)),
|
|
LastSentAt: util.Pointer(time.Now().Add(-time.Duration(rand.Intn(100) + 50))),
|
|
}
|
|
|
|
if instance.CurrentState == InstanceStateNormal && rand.Intn(2) == 1 {
|
|
instance.ResolvedAt = util.Pointer(time.Now().Add(-time.Duration(rand.Intn(100) + 50)))
|
|
}
|
|
|
|
for _, mutator := range mutators {
|
|
mutator(instance)
|
|
}
|
|
return instance
|
|
}
|
|
|
|
type Mutator[T any] func(*T)
|
|
|
|
// CopyNotificationSettings creates a deep copy of NotificationSettings.
|
|
func CopyNotificationSettings(ns NotificationSettings, mutators ...Mutator[NotificationSettings]) NotificationSettings {
|
|
c := NotificationSettings{
|
|
Receiver: ns.Receiver,
|
|
}
|
|
if ns.GroupWait != nil {
|
|
c.GroupWait = util.Pointer(*ns.GroupWait)
|
|
}
|
|
if ns.GroupInterval != nil {
|
|
c.GroupInterval = util.Pointer(*ns.GroupInterval)
|
|
}
|
|
if ns.RepeatInterval != nil {
|
|
c.RepeatInterval = util.Pointer(*ns.RepeatInterval)
|
|
}
|
|
if ns.GroupBy != nil {
|
|
c.GroupBy = make([]string, len(ns.GroupBy))
|
|
copy(c.GroupBy, ns.GroupBy)
|
|
}
|
|
if ns.MuteTimeIntervals != nil {
|
|
c.MuteTimeIntervals = make([]string, len(ns.MuteTimeIntervals))
|
|
copy(c.MuteTimeIntervals, ns.MuteTimeIntervals)
|
|
}
|
|
for _, mutator := range mutators {
|
|
mutator(&c)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// NotificationSettingsGen generates NotificationSettings using a base and mutators.
|
|
func NotificationSettingsGen(mutators ...Mutator[NotificationSettings]) func() NotificationSettings {
|
|
return func() NotificationSettings {
|
|
c := NotificationSettings{
|
|
Receiver: util.GenerateShortUID(),
|
|
GroupBy: []string{model.AlertNameLabel, FolderTitleLabel, util.GenerateShortUID()},
|
|
GroupWait: util.Pointer(model.Duration(time.Duration(rand.Intn(100)+1) * time.Second)),
|
|
GroupInterval: util.Pointer(model.Duration(time.Duration(rand.Intn(100)+1) * time.Second)),
|
|
RepeatInterval: util.Pointer(model.Duration(time.Duration(rand.Intn(100)+1) * time.Second)),
|
|
MuteTimeIntervals: []string{util.GenerateShortUID(), util.GenerateShortUID()},
|
|
}
|
|
for _, mutator := range mutators {
|
|
mutator(&c)
|
|
}
|
|
return c
|
|
}
|
|
}
|
|
|
|
type NotificationSettingsMutators struct{}
|
|
|
|
func (n NotificationSettingsMutators) WithReceiver(receiver string) Mutator[NotificationSettings] {
|
|
return func(ns *NotificationSettings) {
|
|
ns.Receiver = receiver
|
|
}
|
|
}
|
|
|
|
func (n NotificationSettingsMutators) WithGroupWait(groupWait *time.Duration) Mutator[NotificationSettings] {
|
|
return func(ns *NotificationSettings) {
|
|
if groupWait == nil {
|
|
ns.GroupWait = nil
|
|
return
|
|
}
|
|
dur := model.Duration(*groupWait)
|
|
ns.GroupWait = &dur
|
|
}
|
|
}
|
|
|
|
func (n NotificationSettingsMutators) WithGroupInterval(groupInterval *time.Duration) Mutator[NotificationSettings] {
|
|
return func(ns *NotificationSettings) {
|
|
if groupInterval == nil {
|
|
ns.GroupInterval = nil
|
|
return
|
|
}
|
|
dur := model.Duration(*groupInterval)
|
|
ns.GroupInterval = &dur
|
|
}
|
|
}
|
|
|
|
func (n NotificationSettingsMutators) WithRepeatInterval(repeatInterval *time.Duration) Mutator[NotificationSettings] {
|
|
return func(ns *NotificationSettings) {
|
|
if repeatInterval == nil {
|
|
ns.RepeatInterval = nil
|
|
return
|
|
}
|
|
dur := model.Duration(*repeatInterval)
|
|
ns.RepeatInterval = &dur
|
|
}
|
|
}
|
|
|
|
func (n NotificationSettingsMutators) WithGroupBy(groupBy ...string) Mutator[NotificationSettings] {
|
|
return func(ns *NotificationSettings) {
|
|
ns.GroupBy = groupBy
|
|
}
|
|
}
|
|
|
|
func (n NotificationSettingsMutators) WithMuteTimeIntervals(muteTimeIntervals ...string) Mutator[NotificationSettings] {
|
|
return func(ns *NotificationSettings) {
|
|
ns.MuteTimeIntervals = muteTimeIntervals
|
|
}
|
|
}
|
|
|
|
// Silences
|
|
|
|
// CopySilenceWith creates a deep copy of Silence and then applies mutators to it.
|
|
func CopySilenceWith(s Silence, mutators ...Mutator[Silence]) Silence {
|
|
c := CopySilence(s)
|
|
for _, mutator := range mutators {
|
|
mutator(&c)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// CopySilence creates a deep copy of Silence.
|
|
func CopySilence(s Silence) Silence {
|
|
c := Silence{
|
|
Silence: amv2.Silence{},
|
|
}
|
|
|
|
if s.ID != nil {
|
|
c.ID = util.Pointer(*s.ID)
|
|
}
|
|
if s.Status != nil {
|
|
c.Status = util.Pointer(*s.Status)
|
|
}
|
|
if s.UpdatedAt != nil {
|
|
c.UpdatedAt = util.Pointer(*s.UpdatedAt)
|
|
}
|
|
if s.Silence.Comment != nil {
|
|
c.Silence.Comment = util.Pointer(*s.Silence.Comment)
|
|
}
|
|
if s.Silence.CreatedBy != nil {
|
|
c.Silence.CreatedBy = util.Pointer(*s.Silence.CreatedBy)
|
|
}
|
|
if s.Silence.EndsAt != nil {
|
|
c.Silence.EndsAt = util.Pointer(*s.Silence.EndsAt)
|
|
}
|
|
if s.Silence.StartsAt != nil {
|
|
c.Silence.StartsAt = util.Pointer(*s.Silence.StartsAt)
|
|
}
|
|
if s.Silence.Matchers != nil {
|
|
c.Silence.Matchers = CopyMatchers(s.Silence.Matchers)
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
// CopyMatchers creates a deep copy of Matchers.
|
|
func CopyMatchers(matchers []*amv2.Matcher) []*amv2.Matcher {
|
|
copies := make([]*amv2.Matcher, len(matchers))
|
|
for i, m := range matchers {
|
|
c := amv2.Matcher{}
|
|
if m.IsEqual != nil {
|
|
c.IsEqual = util.Pointer(*m.IsEqual)
|
|
}
|
|
if m.IsRegex != nil {
|
|
c.IsRegex = util.Pointer(*m.IsRegex)
|
|
}
|
|
if m.Name != nil {
|
|
c.Name = util.Pointer(*m.Name)
|
|
}
|
|
if m.Value != nil {
|
|
c.Value = util.Pointer(*m.Value)
|
|
}
|
|
copies[i] = &c
|
|
}
|
|
return copies
|
|
}
|
|
|
|
// SilenceGen generates Silence using a base and mutators.
|
|
func SilenceGen(mutators ...Mutator[Silence]) func() Silence {
|
|
return func() Silence {
|
|
now := time.Now()
|
|
c := Silence{
|
|
ID: util.Pointer(util.GenerateShortUID()),
|
|
Status: util.Pointer(amv2.SilenceStatus{State: util.Pointer(amv2.SilenceStatusStateActive)}),
|
|
UpdatedAt: util.Pointer(strfmt.DateTime(now.Add(time.Minute))),
|
|
Silence: amv2.Silence{
|
|
Comment: util.Pointer(util.GenerateShortUID()),
|
|
CreatedBy: util.Pointer(util.GenerateShortUID()),
|
|
StartsAt: util.Pointer(strfmt.DateTime(now.Add(-time.Minute))),
|
|
EndsAt: util.Pointer(strfmt.DateTime(now.Add(time.Minute))),
|
|
Matchers: []*amv2.Matcher{{Name: util.Pointer(util.GenerateShortUID()), Value: util.Pointer(util.GenerateShortUID()), IsRegex: util.Pointer(false), IsEqual: util.Pointer(true)}},
|
|
},
|
|
}
|
|
for _, mutator := range mutators {
|
|
mutator(&c)
|
|
}
|
|
return c
|
|
}
|
|
}
|
|
|
|
var (
|
|
SilenceMuts = SilenceMutators{}
|
|
)
|
|
|
|
type SilenceMutators struct{}
|
|
|
|
func (n SilenceMutators) WithMatcher(name, value string, matchType labels.MatchType) Mutator[Silence] {
|
|
return func(s *Silence) {
|
|
m := amv2.Matcher{
|
|
Name: &name,
|
|
Value: &value,
|
|
IsRegex: util.Pointer(matchType == labels.MatchRegexp || matchType == labels.MatchNotRegexp),
|
|
IsEqual: util.Pointer(matchType == labels.MatchRegexp || matchType == labels.MatchEqual),
|
|
}
|
|
s.Silence.Matchers = append(s.Silence.Matchers, &m)
|
|
}
|
|
}
|
|
func (n SilenceMutators) WithRuleUID(value string) Mutator[Silence] {
|
|
return func(s *Silence) {
|
|
name := alertingModels.RuleUIDLabel
|
|
m := amv2.Matcher{
|
|
Name: &name,
|
|
Value: &value,
|
|
IsRegex: util.Pointer(false),
|
|
IsEqual: util.Pointer(true),
|
|
}
|
|
for _, matcher := range s.Silence.Matchers {
|
|
if isRuleUIDMatcher(*matcher) {
|
|
*matcher = m
|
|
return
|
|
}
|
|
}
|
|
s.Silence.Matchers = append(s.Silence.Matchers, &m)
|
|
}
|
|
}
|
|
func (n SilenceMutators) Expired() Mutator[Silence] {
|
|
return func(s *Silence) {
|
|
s.EndsAt = util.Pointer(strfmt.DateTime(time.Now().Add(-time.Minute)))
|
|
}
|
|
}
|
|
|
|
func (n SilenceMutators) WithEmptyId() Mutator[Silence] {
|
|
return func(s *Silence) {
|
|
s.ID = util.Pointer("")
|
|
}
|
|
}
|
|
|
|
// Receivers
|
|
|
|
// CopyReceiverWith creates a deep copy of Receiver and then applies mutators to it.
|
|
func CopyReceiverWith(r Receiver, mutators ...Mutator[Receiver]) Receiver {
|
|
c := r.Clone()
|
|
for _, mutator := range mutators {
|
|
mutator(&c)
|
|
}
|
|
c.Version = c.Fingerprint()
|
|
return c
|
|
}
|
|
|
|
// ReceiverGen generates Receiver using a base and mutators.
|
|
func ReceiverGen(mutators ...Mutator[Receiver]) func() Receiver {
|
|
return func() Receiver {
|
|
name := util.GenerateShortUID()
|
|
integration := IntegrationGen(IntegrationMuts.WithName(name))()
|
|
c := Receiver{
|
|
UID: nameToUid(name),
|
|
Name: name,
|
|
Integrations: []*Integration{&integration},
|
|
Provenance: ProvenanceNone,
|
|
}
|
|
for _, mutator := range mutators {
|
|
mutator(&c)
|
|
}
|
|
c.Version = c.Fingerprint()
|
|
return c
|
|
}
|
|
}
|
|
|
|
var (
|
|
ReceiverMuts = ReceiverMutators{}
|
|
)
|
|
|
|
type ReceiverMutators struct{}
|
|
|
|
func (n ReceiverMutators) WithName(name string) Mutator[Receiver] {
|
|
return func(r *Receiver) {
|
|
r.Name = name
|
|
r.UID = nameToUid(name)
|
|
}
|
|
}
|
|
|
|
func (n ReceiverMutators) WithProvenance(provenance Provenance) Mutator[Receiver] {
|
|
return func(r *Receiver) {
|
|
r.Provenance = provenance
|
|
}
|
|
}
|
|
|
|
func (n ReceiverMutators) WithValidIntegration(integrationType string) Mutator[Receiver] {
|
|
return func(r *Receiver) {
|
|
integration := IntegrationGen(IntegrationMuts.WithValidConfig(integrationType))()
|
|
r.Integrations = []*Integration{&integration}
|
|
}
|
|
}
|
|
|
|
func (n ReceiverMutators) WithInvalidIntegration(integrationType string) Mutator[Receiver] {
|
|
return func(r *Receiver) {
|
|
integration := IntegrationGen(IntegrationMuts.WithInvalidConfig(integrationType))()
|
|
r.Integrations = []*Integration{&integration}
|
|
}
|
|
}
|
|
|
|
func (n ReceiverMutators) WithIntegrations(integration ...Integration) Mutator[Receiver] {
|
|
return func(r *Receiver) {
|
|
integrations := make([]*Integration, len(integration))
|
|
for i, v := range integration {
|
|
clone := v.Clone()
|
|
integrations[i] = &clone
|
|
}
|
|
r.Integrations = integrations
|
|
}
|
|
}
|
|
|
|
func (n ReceiverMutators) Encrypted(fn EncryptFn) Mutator[Receiver] {
|
|
return func(r *Receiver) {
|
|
_ = r.Encrypt(fn)
|
|
}
|
|
}
|
|
func (n ReceiverMutators) Decrypted(fn DecryptFn) Mutator[Receiver] {
|
|
return func(r *Receiver) {
|
|
_ = r.Decrypt(fn)
|
|
}
|
|
}
|
|
|
|
// Integrations
|
|
|
|
// CopyIntegrationWith creates a deep copy of Integration and then applies mutators to it.
|
|
func CopyIntegrationWith(r Integration, mutators ...Mutator[Integration]) Integration {
|
|
c := r.Clone()
|
|
for _, mutator := range mutators {
|
|
mutator(&c)
|
|
}
|
|
return c
|
|
}
|
|
|
|
// IntegrationGen generates Integration using a base and mutators.
|
|
func IntegrationGen(mutators ...Mutator[Integration]) func() Integration {
|
|
return func() Integration {
|
|
name := util.GenerateShortUID()
|
|
randomIntegrationType, _ := randomMapKey(alertingNotify.AllKnownConfigsForTesting)
|
|
|
|
c := Integration{
|
|
UID: util.GenerateShortUID(),
|
|
Name: name,
|
|
DisableResolveMessage: rand.Intn(2) == 1,
|
|
Settings: make(map[string]any),
|
|
SecureSettings: make(map[string]string),
|
|
}
|
|
|
|
IntegrationMuts.WithValidConfig(randomIntegrationType)(&c)
|
|
|
|
for _, mutator := range mutators {
|
|
mutator(&c)
|
|
}
|
|
return c
|
|
}
|
|
}
|
|
|
|
var (
|
|
IntegrationMuts = IntegrationMutators{}
|
|
Base64Enrypt = func(s string) (string, error) {
|
|
return base64.StdEncoding.EncodeToString([]byte(s)), nil
|
|
}
|
|
Base64Decrypt = func(s string) (string, error) {
|
|
b, err := base64.StdEncoding.DecodeString(s)
|
|
return string(b), err
|
|
}
|
|
)
|
|
|
|
type IntegrationMutators struct{}
|
|
|
|
func (n IntegrationMutators) WithUID(uid string) Mutator[Integration] {
|
|
return func(s *Integration) {
|
|
s.UID = uid
|
|
}
|
|
}
|
|
|
|
func (n IntegrationMutators) WithName(name string) Mutator[Integration] {
|
|
return func(s *Integration) {
|
|
s.Name = name
|
|
}
|
|
}
|
|
|
|
func (n IntegrationMutators) WithValidConfig(integrationType string) Mutator[Integration] {
|
|
return func(c *Integration) {
|
|
config := alertingNotify.AllKnownConfigsForTesting[integrationType].GetRawNotifierConfig(c.Name)
|
|
integrationConfig, _ := IntegrationConfigFromType(integrationType)
|
|
c.Config = integrationConfig
|
|
|
|
var settings map[string]any
|
|
_ = json.Unmarshal(config.Settings, &settings)
|
|
|
|
c.Settings = settings
|
|
|
|
// Decrypt secure settings over to normal settings.
|
|
for k, v := range c.SecureSettings {
|
|
decodeValue, _ := base64.StdEncoding.DecodeString(v)
|
|
settings[k] = string(decodeValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n IntegrationMutators) WithInvalidConfig(integrationType string) Mutator[Integration] {
|
|
return func(c *Integration) {
|
|
integrationConfig, _ := IntegrationConfigFromType(integrationType)
|
|
c.Config = integrationConfig
|
|
c.Settings = map[string]interface{}{}
|
|
c.SecureSettings = map[string]string{}
|
|
if integrationType == "webex" {
|
|
// Webex passes validation without any settings but should fail with an unparsable URL.
|
|
c.Settings["api_url"] = "(*^$*^%!@#$*()"
|
|
}
|
|
}
|
|
}
|
|
|
|
func (n IntegrationMutators) WithSettings(settings map[string]any) Mutator[Integration] {
|
|
return func(c *Integration) {
|
|
c.Settings = maps.Clone(settings)
|
|
}
|
|
}
|
|
|
|
func (n IntegrationMutators) AddSetting(key string, val any) Mutator[Integration] {
|
|
return func(c *Integration) {
|
|
c.Settings[key] = val
|
|
}
|
|
}
|
|
|
|
func (n IntegrationMutators) WithSecureSettings(secureSettings map[string]string) Mutator[Integration] {
|
|
return func(r *Integration) {
|
|
r.SecureSettings = maps.Clone(secureSettings)
|
|
}
|
|
}
|
|
|
|
func (n IntegrationMutators) AddSecureSetting(key, val string) Mutator[Integration] {
|
|
return func(r *Integration) {
|
|
r.SecureSettings[key] = val
|
|
}
|
|
}
|
|
|
|
func randomMapKey[K comparable, V any](m map[K]V) (K, V) {
|
|
randIdx := rand.Intn(len(m))
|
|
i := 0
|
|
|
|
for key, val := range m {
|
|
if i == randIdx {
|
|
return key, val
|
|
}
|
|
i++
|
|
}
|
|
return *new(K), *new(V)
|
|
}
|
|
|
|
func ConvertToRecordingRule(rule *AlertRule) {
|
|
if rule.Record == nil {
|
|
rule.Record = &Record{}
|
|
}
|
|
if rule.Record.From == "" {
|
|
rule.Record.From = rule.Condition
|
|
}
|
|
if rule.Record.Metric == "" {
|
|
rule.Record.Metric = fmt.Sprintf("some_metric_%s", util.GenerateShortUID())
|
|
}
|
|
rule.Condition = ""
|
|
rule.NoDataState = ""
|
|
rule.ExecErrState = ""
|
|
rule.For = 0
|
|
rule.NotificationSettings = nil
|
|
}
|
|
|
|
func nameToUid(name string) string { // Avoid legacy_storage.NameToUid import cycle.
|
|
return base64.RawURLEncoding.EncodeToString([]byte(name))
|
|
}
|