Alerting: Fix rule API to accept 0 duration of field For (#50992)
* make 'for' pointer to distinguish between missing field and 0 * set 'for' to -1 if the value is missing but not allow negative in the request + path -1 with the value from original rule * update store validation to not allow negative 'for' * update usages to use pointer
This commit is contained in:
@@ -484,8 +484,9 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule, namespaceID int64, provena
|
||||
Provenance: provenance,
|
||||
},
|
||||
}
|
||||
forDuration := model.Duration(r.For)
|
||||
gettableExtendedRuleNode.ApiRuleNode = &apimodels.ApiRuleNode{
|
||||
For: model.Duration(r.For),
|
||||
For: &forDuration,
|
||||
Annotations: r.Annotations,
|
||||
Labels: r.Labels,
|
||||
}
|
||||
|
||||
@@ -110,8 +110,13 @@ func validateRuleNode(
|
||||
ExecErrState: errorState,
|
||||
}
|
||||
|
||||
var err error
|
||||
newAlertRule.For, err = validateForInterval(ruleNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ruleNode.ApiRuleNode != nil {
|
||||
newAlertRule.For = time.Duration(ruleNode.ApiRuleNode.For)
|
||||
newAlertRule.Annotations = ruleNode.ApiRuleNode.Annotations
|
||||
newAlertRule.Labels = ruleNode.ApiRuleNode.Labels
|
||||
|
||||
@@ -131,10 +136,24 @@ func validateRuleNode(
|
||||
newAlertRule.PanelID = &panelIDValue
|
||||
}
|
||||
}
|
||||
|
||||
return &newAlertRule, nil
|
||||
}
|
||||
|
||||
// validateForInterval validates ApiRuleNode.For and converts it to time.Duration. If the field is not specified returns 0 if GrafanaManagedAlert.UID is empty and -1 if it is not.
|
||||
func validateForInterval(ruleNode *apimodels.PostableExtendedRuleNode) (time.Duration, error) {
|
||||
if ruleNode.ApiRuleNode == nil || ruleNode.ApiRuleNode.For == nil {
|
||||
if ruleNode.GrafanaManagedAlert.UID != "" {
|
||||
return -1, nil // will be patched later with the real value of the current version of the rule
|
||||
}
|
||||
return 0, nil // if it's a new rule, use the 0 as the default
|
||||
}
|
||||
duration := time.Duration(*ruleNode.ApiRuleNode.For)
|
||||
if duration < 0 {
|
||||
return 0, fmt.Errorf("field `for` cannot be negative [%v]. 0 or any positive duration are allowed", *ruleNode.ApiRuleNode.For)
|
||||
}
|
||||
return duration, nil
|
||||
}
|
||||
|
||||
// validateRuleGroup validates API model (definitions.PostableRuleGroupConfig) and converts it to a collection of models.AlertRule.
|
||||
// Returns a slice that contains all rules described by API model or error if either group specification or an alert definition is not valid.
|
||||
func validateRuleGroup(
|
||||
|
||||
@@ -42,9 +42,10 @@ func config(t *testing.T) *setting.UnifiedAlertingSettings {
|
||||
}
|
||||
|
||||
func validRule() apimodels.PostableExtendedRuleNode {
|
||||
forDuration := model.Duration(rand.Int63n(1000))
|
||||
return apimodels.PostableExtendedRuleNode{
|
||||
ApiRuleNode: &apimodels.ApiRuleNode{
|
||||
For: model.Duration(rand.Int63n(1000)),
|
||||
For: &forDuration,
|
||||
Labels: map[string]string{
|
||||
"test-label": "data",
|
||||
},
|
||||
@@ -240,7 +241,7 @@ func TestValidateRuleNode_NoUID(t *testing.T) {
|
||||
require.Equal(t, name, alert.RuleGroup)
|
||||
require.Equal(t, models.NoDataState(api.GrafanaManagedAlert.NoDataState), alert.NoDataState)
|
||||
require.Equal(t, models.ExecutionErrorState(api.GrafanaManagedAlert.ExecErrState), alert.ExecErrState)
|
||||
require.Equal(t, time.Duration(api.ApiRuleNode.For), alert.For)
|
||||
require.Equal(t, time.Duration(*api.ApiRuleNode.For), alert.For)
|
||||
require.Equal(t, api.ApiRuleNode.Annotations, alert.Annotations)
|
||||
require.Equal(t, api.ApiRuleNode.Labels, alert.Labels)
|
||||
},
|
||||
|
||||
@@ -257,7 +257,7 @@ type ApiRuleNode struct {
|
||||
Record string `yaml:"record,omitempty" json:"record,omitempty"`
|
||||
Alert string `yaml:"alert,omitempty" json:"alert,omitempty"`
|
||||
Expr string `yaml:"expr" json:"expr"`
|
||||
For model.Duration `yaml:"for,omitempty" json:"for,omitempty"`
|
||||
For *model.Duration `yaml:"for,omitempty" json:"for,omitempty"`
|
||||
Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"`
|
||||
Annotations map[string]string `yaml:"annotations,omitempty" json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func Test_Rule_Marshaling(t *testing.T) {
|
||||
desc: "grafana with for, annotation and label properties",
|
||||
input: PostableExtendedRuleNode{
|
||||
ApiRuleNode: &ApiRuleNode{
|
||||
For: dur,
|
||||
For: &dur,
|
||||
Annotations: map[string]string{"foo": "bar"},
|
||||
Labels: map[string]string{"label1": "val1"}},
|
||||
GrafanaManagedAlert: &PostableGrafanaRule{},
|
||||
@@ -136,7 +136,7 @@ func Test_Rule_Group_Marshaling(t *testing.T) {
|
||||
Rules: []PostableExtendedRuleNode{
|
||||
{
|
||||
ApiRuleNode: &ApiRuleNode{
|
||||
For: dur,
|
||||
For: &dur,
|
||||
Annotations: map[string]string{"foo": "bar"},
|
||||
Labels: map[string]string{"label1": "val1"},
|
||||
},
|
||||
|
||||
@@ -388,7 +388,7 @@ func PatchPartialAlertRule(existingRule *AlertRule, ruleToPatch *AlertRule) {
|
||||
if ruleToPatch.NoDataState == "" {
|
||||
ruleToPatch.NoDataState = existingRule.NoDataState
|
||||
}
|
||||
if ruleToPatch.For == 0 {
|
||||
if ruleToPatch.For == -1 {
|
||||
ruleToPatch.For = existingRule.For
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,9 +113,9 @@ func TestPatchPartialAlertRule(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "For is 0",
|
||||
name: "For is -1",
|
||||
mutator: func(r *AlertRule) {
|
||||
r.For = 0
|
||||
r.For = -1
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -479,5 +479,8 @@ func (st DBstore) validateAlertRule(alertRule ngmodels.AlertRule) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if alertRule.For < 0 {
|
||||
return fmt.Errorf("%w: field `for` cannot be negative", ngmodels.ErrAlertRuleFailedValidation)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user