Alerting: Add rule conversion package (#100224)
This commit is contained in:
@@ -295,7 +295,8 @@ type AlertRule struct {
|
||||
}
|
||||
|
||||
type AlertRuleMetadata struct {
|
||||
EditorSettings EditorSettings `json:"editor_settings"`
|
||||
EditorSettings EditorSettings `json:"editor_settings"`
|
||||
PrometheusStyleRule *PrometheusStyleRule `json:"prometheus_style_rule,omitempty"`
|
||||
}
|
||||
|
||||
type EditorSettings struct {
|
||||
@@ -303,6 +304,10 @@ type EditorSettings struct {
|
||||
SimplifiedNotificationsSection bool `json:"simplified_notifications_section"`
|
||||
}
|
||||
|
||||
type PrometheusStyleRule struct {
|
||||
OriginalRuleDefinition string `json:"original_rule_definition,omitempty"`
|
||||
}
|
||||
|
||||
// Namespaced describes a class of resources that are stored in a specific namespace.
|
||||
type Namespaced interface {
|
||||
GetNamespaceUID() string
|
||||
@@ -748,6 +753,11 @@ func (alertRule *AlertRule) Copy() *AlertRule {
|
||||
}
|
||||
}
|
||||
|
||||
if alertRule.Metadata.PrometheusStyleRule != nil {
|
||||
prometheusStyleRule := *alertRule.Metadata.PrometheusStyleRule
|
||||
result.Metadata.PrometheusStyleRule = &prometheusStyleRule
|
||||
}
|
||||
|
||||
for _, s := range alertRule.NotificationSettings {
|
||||
result.NotificationSettings = append(result.NotificationSettings, CopyNotificationSettings(s))
|
||||
}
|
||||
|
||||
@@ -841,7 +841,7 @@ func TestDiff(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should detect changes in Metadata", func(t *testing.T) {
|
||||
t.Run("should detect changes in Metadata.EditorSettings", func(t *testing.T) {
|
||||
rule1 := RuleGen.With(RuleGen.WithMetadata(AlertRuleMetadata{EditorSettings: EditorSettings{
|
||||
SimplifiedQueryAndExpressionsSection: false,
|
||||
SimplifiedNotificationsSection: false,
|
||||
@@ -858,6 +858,21 @@ func TestDiff(t *testing.T) {
|
||||
"Metadata.EditorSettings.SimplifiedNotificationsSection",
|
||||
}, diff.Paths())
|
||||
})
|
||||
|
||||
t.Run("should detect changes in Metadata.PrometheusStyleRule", func(t *testing.T) {
|
||||
rule1 := RuleGen.With(RuleGen.WithMetadata(AlertRuleMetadata{PrometheusStyleRule: &PrometheusStyleRule{
|
||||
OriginalRuleDefinition: "data",
|
||||
}})).GenerateRef()
|
||||
|
||||
rule2 := CopyRule(rule1, RuleGen.WithMetadata(AlertRuleMetadata{PrometheusStyleRule: &PrometheusStyleRule{
|
||||
OriginalRuleDefinition: "updated data",
|
||||
}}))
|
||||
|
||||
diff := rule1.Diff(rule2)
|
||||
assert.ElementsMatch(t, []string{
|
||||
"Metadata.PrometheusStyleRule.OriginalRuleDefinition",
|
||||
}, diff.Paths())
|
||||
})
|
||||
}
|
||||
|
||||
func TestSortByGroupIndex(t *testing.T) {
|
||||
@@ -940,11 +955,21 @@ func TestAlertRuleGetKeyWithGroup(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAlertRuleCopy(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
rule := RuleGen.GenerateRef()
|
||||
t.Run("should return a copy of the rule", func(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
rule := RuleGen.GenerateRef()
|
||||
copied := rule.Copy()
|
||||
require.Empty(t, rule.Diff(copied))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should create a copy of the prometheus rule definition from the metadata", func(t *testing.T) {
|
||||
rule := RuleGen.With(RuleGen.WithMetadata(AlertRuleMetadata{PrometheusStyleRule: &PrometheusStyleRule{
|
||||
OriginalRuleDefinition: "data",
|
||||
}})).GenerateRef()
|
||||
copied := rule.Copy()
|
||||
require.Empty(t, rule.Diff(copied))
|
||||
}
|
||||
require.NotSame(t, rule.Metadata.PrometheusStyleRule, copied.Metadata.PrometheusStyleRule)
|
||||
})
|
||||
}
|
||||
|
||||
// This test makes sure the default generator
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package prom
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
DatasourceUID string
|
||||
DatasourceType string
|
||||
FromTimeRange *time.Duration
|
||||
EvaluationOffset *time.Duration
|
||||
ExecErrState models.ExecutionErrorState
|
||||
NoDataState models.NoDataState
|
||||
RecordingRules RulesConfig
|
||||
AlertRules RulesConfig
|
||||
}
|
||||
|
||||
type RulesConfig struct {
|
||||
IsPaused bool
|
||||
}
|
||||
|
||||
var (
|
||||
defaultTimeRange = 600 * time.Second
|
||||
defaultEvaluationOffset = 0 * time.Minute
|
||||
|
||||
defaultConfig = Config{
|
||||
FromTimeRange: &defaultTimeRange,
|
||||
EvaluationOffset: &defaultEvaluationOffset,
|
||||
ExecErrState: models.ErrorErrState,
|
||||
NoDataState: models.NoData,
|
||||
}
|
||||
)
|
||||
|
||||
type Converter struct {
|
||||
cfg Config
|
||||
}
|
||||
|
||||
func NewConverter(cfg Config) (*Converter, error) {
|
||||
if cfg.DatasourceUID == "" {
|
||||
return nil, fmt.Errorf("datasource UID is required")
|
||||
}
|
||||
if cfg.DatasourceType == "" {
|
||||
return nil, fmt.Errorf("datasource type is required")
|
||||
}
|
||||
if cfg.FromTimeRange == nil {
|
||||
cfg.FromTimeRange = defaultConfig.FromTimeRange
|
||||
}
|
||||
if cfg.EvaluationOffset == nil {
|
||||
cfg.EvaluationOffset = defaultConfig.EvaluationOffset
|
||||
}
|
||||
if cfg.ExecErrState == "" {
|
||||
cfg.ExecErrState = defaultConfig.ExecErrState
|
||||
}
|
||||
if cfg.NoDataState == "" {
|
||||
cfg.NoDataState = defaultConfig.NoDataState
|
||||
}
|
||||
|
||||
if cfg.DatasourceType != datasources.DS_PROMETHEUS && cfg.DatasourceType != datasources.DS_LOKI {
|
||||
return nil, fmt.Errorf("invalid datasource type: %s", cfg.DatasourceType)
|
||||
}
|
||||
|
||||
return &Converter{
|
||||
cfg: cfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PrometheusRulesToGrafana converts a Prometheus rule group into Grafana Alerting rule group.
|
||||
func (p *Converter) PrometheusRulesToGrafana(orgID int64, namespaceUID string, group PrometheusRuleGroup) (*models.AlertRuleGroup, error) {
|
||||
for _, rule := range group.Rules {
|
||||
err := validatePrometheusRule(rule)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Prometheus rule '%s': %w", rule.Alert, err)
|
||||
}
|
||||
}
|
||||
|
||||
grafanaGroup, err := p.convertRuleGroup(orgID, namespaceUID, group)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert rule group '%s': %w", group.Name, err)
|
||||
}
|
||||
|
||||
return grafanaGroup, nil
|
||||
}
|
||||
|
||||
func validatePrometheusRule(rule PrometheusRule) error {
|
||||
if rule.KeepFiringFor != nil {
|
||||
return fmt.Errorf("keep_firing_for is not supported")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Converter) convertRuleGroup(orgID int64, namespaceUID string, promGroup PrometheusRuleGroup) (*models.AlertRuleGroup, error) {
|
||||
uniqueNames := map[string]int{}
|
||||
rules := make([]models.AlertRule, 0, len(promGroup.Rules))
|
||||
interval := time.Duration(promGroup.Interval)
|
||||
for i, rule := range promGroup.Rules {
|
||||
gr, err := p.convertRule(orgID, namespaceUID, promGroup.Name, rule)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert Prometheus rule '%s' to Grafana rule: %w", rule.Alert, err)
|
||||
}
|
||||
gr.RuleGroupIndex = i + 1
|
||||
gr.IntervalSeconds = int64(interval.Seconds())
|
||||
|
||||
// Check rule title uniqueness within the group.
|
||||
uniqueNames[gr.Title]++
|
||||
if val := uniqueNames[gr.Title]; val > 1 {
|
||||
gr.Title = fmt.Sprintf("%s (%d)", gr.Title, val)
|
||||
}
|
||||
|
||||
rules = append(rules, gr)
|
||||
}
|
||||
|
||||
result := &models.AlertRuleGroup{
|
||||
FolderUID: namespaceUID,
|
||||
Interval: int64(interval.Seconds()),
|
||||
Rules: rules,
|
||||
Title: promGroup.Name,
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Converter) convertRule(orgID int64, namespaceUID, group string, rule PrometheusRule) (models.AlertRule, error) {
|
||||
var forInterval time.Duration
|
||||
if rule.For != nil {
|
||||
forInterval = time.Duration(*rule.For)
|
||||
}
|
||||
|
||||
queryNode, err := createAlertQueryNode(p.cfg.DatasourceUID, p.cfg.DatasourceType, rule.Expr, *p.cfg.FromTimeRange, *p.cfg.EvaluationOffset)
|
||||
if err != nil {
|
||||
return models.AlertRule{}, err
|
||||
}
|
||||
|
||||
var title string
|
||||
if rule.Record != "" {
|
||||
title = rule.Record
|
||||
} else {
|
||||
title = rule.Alert
|
||||
}
|
||||
|
||||
labels := make(map[string]string, len(rule.Labels)+1)
|
||||
for k, v := range rule.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
|
||||
originalRuleDefinition, err := yaml.Marshal(rule)
|
||||
if err != nil {
|
||||
return models.AlertRule{}, fmt.Errorf("failed to marshal original rule definition: %w", err)
|
||||
}
|
||||
|
||||
result := models.AlertRule{
|
||||
OrgID: orgID,
|
||||
NamespaceUID: namespaceUID,
|
||||
Title: title,
|
||||
Data: []models.AlertQuery{queryNode},
|
||||
Condition: "A",
|
||||
NoDataState: p.cfg.NoDataState,
|
||||
ExecErrState: p.cfg.ExecErrState,
|
||||
Annotations: rule.Annotations,
|
||||
Labels: labels,
|
||||
For: forInterval,
|
||||
RuleGroup: group,
|
||||
Metadata: models.AlertRuleMetadata{
|
||||
PrometheusStyleRule: &models.PrometheusStyleRule{
|
||||
OriginalRuleDefinition: string(originalRuleDefinition),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if rule.Record != "" {
|
||||
result.Record = &models.Record{
|
||||
From: "A",
|
||||
Metric: rule.Record,
|
||||
}
|
||||
result.IsPaused = p.cfg.RecordingRules.IsPaused
|
||||
} else {
|
||||
result.IsPaused = p.cfg.AlertRules.IsPaused
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func createAlertQueryNode(datasourceUID, datasourceType, expr string, fromTimeRange, evaluationOffset time.Duration) (models.AlertQuery, error) {
|
||||
modelData := map[string]interface{}{
|
||||
"datasource": map[string]interface{}{
|
||||
"type": datasourceType,
|
||||
"uid": datasourceUID,
|
||||
},
|
||||
"expr": expr,
|
||||
"instant": true,
|
||||
"range": false,
|
||||
"refId": "A",
|
||||
}
|
||||
|
||||
if datasourceType == datasources.DS_LOKI {
|
||||
modelData["queryType"] = "instant"
|
||||
}
|
||||
|
||||
modelJSON, err := json.Marshal(modelData)
|
||||
if err != nil {
|
||||
return models.AlertQuery{}, err
|
||||
}
|
||||
|
||||
return models.AlertQuery{
|
||||
DatasourceUID: datasourceUID,
|
||||
Model: modelJSON,
|
||||
RefID: "A",
|
||||
RelativeTimeRange: models.RelativeTimeRange{
|
||||
From: models.Duration(fromTimeRange + evaluationOffset),
|
||||
To: models.Duration(0 + evaluationOffset),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package prom
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
func TestPrometheusRulesToGrafana(t *testing.T) {
|
||||
fiveMin := prommodel.Duration(5 * time.Minute)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
orgID int64
|
||||
namespace string
|
||||
promGroup PrometheusRuleGroup
|
||||
config Config
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "valid rule group",
|
||||
orgID: 1,
|
||||
namespace: "some-namespace-uid",
|
||||
promGroup: PrometheusRuleGroup{
|
||||
Name: "test-group-1",
|
||||
Interval: prommodel.Duration(10 * time.Second),
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "alert-1",
|
||||
Expr: "cpu_usage > 80",
|
||||
For: &fiveMin,
|
||||
Labels: map[string]string{
|
||||
"severity": "critical",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"summary": "CPU usage is critical",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "rules with keep_firing_for are not supported",
|
||||
orgID: 1,
|
||||
namespace: "namespaceUID",
|
||||
promGroup: PrometheusRuleGroup{
|
||||
Name: "test-group-1",
|
||||
Interval: prommodel.Duration(1 * time.Minute),
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "alert-1",
|
||||
Expr: "up == 0",
|
||||
KeepFiringFor: &fiveMin,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "rule with empty interval",
|
||||
orgID: 1,
|
||||
namespace: "namespaceUID",
|
||||
promGroup: PrometheusRuleGroup{
|
||||
Name: "test-group-1",
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "alert-1",
|
||||
Expr: "up == 0",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "recording rule",
|
||||
orgID: 1,
|
||||
namespace: "namespaceUID",
|
||||
promGroup: PrometheusRuleGroup{
|
||||
Name: "test-group-1",
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Record: "some_metric",
|
||||
Expr: "sum(rate(http_requests_total[5m]))",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.config.DatasourceUID = "datasource-uid"
|
||||
tc.config.DatasourceType = datasources.DS_PROMETHEUS
|
||||
converter, err := NewConverter(tc.config)
|
||||
require.NoError(t, err)
|
||||
|
||||
grafanaGroup, err := converter.PrometheusRulesToGrafana(tc.orgID, tc.namespace, tc.promGroup)
|
||||
|
||||
if tc.expectError {
|
||||
require.Error(t, err, tc.name)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err, tc.name)
|
||||
|
||||
require.Equal(t, tc.promGroup.Name, grafanaGroup.Title, tc.name)
|
||||
expectedInterval := int64(time.Duration(tc.promGroup.Interval).Seconds())
|
||||
require.Equal(t, expectedInterval, grafanaGroup.Interval, tc.name)
|
||||
|
||||
require.Equal(t, len(tc.promGroup.Rules), len(grafanaGroup.Rules), tc.name)
|
||||
|
||||
for j, promRule := range tc.promGroup.Rules {
|
||||
grafanaRule := grafanaGroup.Rules[j]
|
||||
|
||||
if promRule.Record != "" {
|
||||
require.Equal(t, promRule.Record, grafanaRule.Title)
|
||||
} else {
|
||||
require.Equal(t, promRule.Alert, grafanaRule.Title)
|
||||
}
|
||||
|
||||
var expectedFor time.Duration
|
||||
if promRule.For != nil {
|
||||
expectedFor = time.Duration(*promRule.For)
|
||||
}
|
||||
require.Equal(t, expectedFor, grafanaRule.For, tc.name)
|
||||
|
||||
expectedLabels := make(map[string]string, len(promRule.Labels)+1)
|
||||
for k, v := range promRule.Labels {
|
||||
expectedLabels[k] = v
|
||||
}
|
||||
|
||||
require.Equal(t, expectedLabels, grafanaRule.Labels, tc.name)
|
||||
require.Equal(t, promRule.Annotations, grafanaRule.Annotations, tc.name)
|
||||
require.Equal(t, models.Duration(0*time.Minute), grafanaRule.Data[0].RelativeTimeRange.To)
|
||||
require.Equal(t, models.Duration(10*time.Minute), grafanaRule.Data[0].RelativeTimeRange.From)
|
||||
|
||||
originalRuleDefinition, err := yaml.Marshal(promRule)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(originalRuleDefinition), grafanaRule.Metadata.PrometheusStyleRule.OriginalRuleDefinition)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrometheusRulesToGrafanaWithDuplicateRuleNames(t *testing.T) {
|
||||
cfg := Config{
|
||||
DatasourceUID: "datasource-uid",
|
||||
DatasourceType: datasources.DS_PROMETHEUS,
|
||||
}
|
||||
converter, err := NewConverter(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
promGroup := PrometheusRuleGroup{
|
||||
Name: "test-group-1",
|
||||
Interval: prommodel.Duration(10 * time.Second),
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "alert",
|
||||
Expr: "up",
|
||||
},
|
||||
{
|
||||
Alert: "alert",
|
||||
Expr: "up",
|
||||
},
|
||||
{
|
||||
Alert: "another alert",
|
||||
Expr: "up",
|
||||
},
|
||||
{
|
||||
Alert: "alert",
|
||||
Expr: "up",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
group, err := converter.PrometheusRulesToGrafana(1, "namespaceUID", promGroup)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "test-group-1", group.Title)
|
||||
require.Len(t, group.Rules, 4)
|
||||
require.Equal(t, "alert", group.Rules[0].Title)
|
||||
require.Equal(t, "alert (2)", group.Rules[1].Title)
|
||||
require.Equal(t, "another alert", group.Rules[2].Title)
|
||||
require.Equal(t, "alert (3)", group.Rules[3].Title)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package prom
|
||||
|
||||
import (
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
type PrometheusRulesFile struct {
|
||||
Groups []PrometheusRuleGroup `yaml:"groups"`
|
||||
}
|
||||
|
||||
type PrometheusRuleGroup struct {
|
||||
Name string `yaml:"name"`
|
||||
Interval prommodel.Duration `yaml:"interval"`
|
||||
Rules []PrometheusRule `yaml:"rules"`
|
||||
}
|
||||
|
||||
type PrometheusRule struct {
|
||||
Alert string `yaml:"alert,omitempty"`
|
||||
Expr string `yaml:"expr,omitempty"`
|
||||
For *prommodel.Duration `yaml:"for,omitempty"`
|
||||
KeepFiringFor *prommodel.Duration `yaml:"keep_firing_for,omitempty"`
|
||||
Labels map[string]string `yaml:"labels,omitempty"`
|
||||
Annotations map[string]string `yaml:"annotations,omitempty"`
|
||||
Record string `yaml:"record,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package prom
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
prommodel "github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestPrometheusRulesFileYAML(t *testing.T) {
|
||||
interval := prommodel.Duration(5 * time.Minute)
|
||||
alertFor := prommodel.Duration(10 * time.Minute)
|
||||
keepFiring := prommodel.Duration(15 * time.Minute)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input PrometheusRulesFile
|
||||
expectedYAML string
|
||||
}{
|
||||
{
|
||||
name: "simple alert rule and a recording rule",
|
||||
input: PrometheusRulesFile{
|
||||
Groups: []PrometheusRuleGroup{
|
||||
{
|
||||
Name: "test_group",
|
||||
Interval: interval,
|
||||
Rules: []PrometheusRule{
|
||||
{
|
||||
Alert: "alert-1",
|
||||
Expr: "vector(0) > 90",
|
||||
For: &alertFor,
|
||||
KeepFiringFor: &keepFiring,
|
||||
Labels: map[string]string{
|
||||
"team": "alerting",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"summary": "some summary",
|
||||
"description": "some description",
|
||||
},
|
||||
},
|
||||
{
|
||||
Record: "vector(1)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedYAML: `
|
||||
groups:
|
||||
- name: test_group
|
||||
interval: 5m
|
||||
rules:
|
||||
- alert: alert-1
|
||||
expr: vector(0) > 90
|
||||
for: 10m
|
||||
keep_firing_for: 15m
|
||||
labels:
|
||||
team: alerting
|
||||
annotations:
|
||||
description: some description
|
||||
summary: some summary
|
||||
- record: vector(1)
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "empty rules file",
|
||||
input: PrometheusRulesFile{
|
||||
Groups: []PrometheusRuleGroup{},
|
||||
},
|
||||
expectedYAML: `groups: []`,
|
||||
},
|
||||
{
|
||||
name: "empty group",
|
||||
input: PrometheusRulesFile{
|
||||
Groups: []PrometheusRuleGroup{
|
||||
{
|
||||
Name: "empty_group",
|
||||
Interval: interval,
|
||||
Rules: []PrometheusRule{},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedYAML: `
|
||||
groups:
|
||||
- name: empty_group
|
||||
interval: 5m
|
||||
rules: []`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
yamlData, err := yaml.Marshal(tt.input)
|
||||
require.NoError(t, err, "Failed to marshal to YAML")
|
||||
require.YAMLEq(t, tt.expectedYAML, string(yamlData))
|
||||
|
||||
var parsed PrometheusRulesFile
|
||||
err = yaml.Unmarshal(yamlData, &parsed)
|
||||
require.NoError(t, err, "Failed to unmarshal from YAML")
|
||||
|
||||
require.Equal(t, tt.input, parsed)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user