dd0a2d4cff
This moves some of the validation logic for rule groups from the legacy storage layer to the validator.
123 lines
3.9 KiB
Go
123 lines
3.9 KiB
Go
package alertrule
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-app-sdk/app"
|
|
"github.com/grafana/grafana-app-sdk/resource"
|
|
"github.com/grafana/grafana-app-sdk/simple"
|
|
model "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1"
|
|
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/config"
|
|
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/util"
|
|
prom_model "github.com/prometheus/common/model"
|
|
)
|
|
|
|
// validateGroupLabels now delegates to util.ValidateGroupLabels for shared logic.
|
|
func validateGroupLabels(r *model.AlertRule, oldObject resource.Object, action resource.AdmissionAction) error {
|
|
var oldLabels map[string]string
|
|
if oldObject != nil {
|
|
if oldRule, ok := oldObject.(*model.AlertRule); ok {
|
|
oldLabels = oldRule.Labels
|
|
} else {
|
|
return fmt.Errorf("old object is not of type *v0alpha1.AlertRule")
|
|
}
|
|
}
|
|
return util.ValidateGroupLabels(r.Labels, oldLabels, action)
|
|
}
|
|
|
|
func NewValidator(cfg config.RuntimeConfig) *simple.Validator {
|
|
return &simple.Validator{
|
|
ValidateFunc: func(ctx context.Context, req *app.AdmissionRequest) error {
|
|
// Cast to specific type
|
|
r, ok := req.Object.(*model.AlertRule)
|
|
if !ok {
|
|
return fmt.Errorf("object is not of type *v0alpha1.AlertRule")
|
|
}
|
|
// 1) Validate provenance status annotation
|
|
sourceProv := r.GetProvenanceStatus()
|
|
if !slices.Contains(model.AcceptedProvenanceStatuses, sourceProv) {
|
|
return fmt.Errorf("invalid provenance status: %s", sourceProv)
|
|
}
|
|
|
|
// 2) Validate group labels rules
|
|
if err := validateGroupLabels(r, req.OldObject, req.Action); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 3) Validate folder is set and exists
|
|
// Read folder UID directly from annotations
|
|
folderUID := ""
|
|
if r.Annotations != nil {
|
|
folderUID = r.Annotations[model.FolderAnnotationKey]
|
|
}
|
|
if folderUID == "" {
|
|
return fmt.Errorf("folder is required")
|
|
}
|
|
if cfg.FolderValidator != nil {
|
|
ok, verr := cfg.FolderValidator(ctx, folderUID)
|
|
if verr != nil {
|
|
return fmt.Errorf("failed to validate folder: %w", verr)
|
|
}
|
|
if !ok {
|
|
return fmt.Errorf("folder does not exist: %s", folderUID)
|
|
}
|
|
}
|
|
|
|
// 4) Validate notification settings receiver if provided
|
|
if r.Spec.NotificationSettings != nil && r.Spec.NotificationSettings.Receiver != "" && cfg.NotificationSettingsValidator != nil {
|
|
ok, nerr := cfg.NotificationSettingsValidator(ctx, r.Spec.NotificationSettings.Receiver)
|
|
if nerr != nil {
|
|
return fmt.Errorf("failed to validate notification settings: %w", nerr)
|
|
}
|
|
if !ok {
|
|
return fmt.Errorf("invalid notification receiver: %s", r.Spec.NotificationSettings.Receiver)
|
|
}
|
|
}
|
|
|
|
// 5) Enforce max title length
|
|
if len(r.Spec.Title) > model.AlertRuleMaxTitleLength {
|
|
return fmt.Errorf("alert rule title is too long. Max length is %d", model.AlertRuleMaxTitleLength)
|
|
}
|
|
|
|
// 6) Validate evaluation interval against base interval
|
|
if err := util.ValidateInterval(cfg.BaseEvaluationInterval, &r.Spec.Trigger.Interval); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 7) Disallow reserved/spec system label keys
|
|
if r.Spec.Labels != nil {
|
|
for key := range r.Spec.Labels {
|
|
if _, bad := cfg.ReservedLabelKeys[key]; bad {
|
|
return fmt.Errorf("label key is reserved and cannot be specified: %s", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 8) For and KeepFiringFor must be >= 0 if set
|
|
if r.Spec.For != nil {
|
|
d, err := prom_model.ParseDuration(*r.Spec.For)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid 'for' duration: %w", err)
|
|
}
|
|
if time.Duration(d) < 0 {
|
|
return fmt.Errorf("'for' cannot be less than 0")
|
|
}
|
|
}
|
|
if r.Spec.KeepFiringFor != nil {
|
|
d, err := prom_model.ParseDuration(*r.Spec.KeepFiringFor)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid 'keepFiringFor' duration: %w", err)
|
|
}
|
|
if time.Duration(d) < 0 {
|
|
return fmt.Errorf("'keepFiringFor' cannot be less than 0")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
}
|