Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for jsonnet rules file format #83

Merged
merged 2 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
To enable them, you need to set some of the new flags described below
- Changed: The Prometheus results cache format has changed to reduce it's size and improve performance. **Delete the old cache file** before upgrade.
Also now if the cache contains time of creation and URL of the Prometheus it has data for. From now on, if the URL does not match, the case is pruned.
- Added: :rocket: Support for validation of rule files in the [Jsonnet](https://jsonnet.org/) format.
- Added: New flags `--support-thanos`, `--support-mimir`, `--support-loki` to enable special rule file fields of Thanos, Mimir or Loki
- Added: :tada: **Support for validation of Loki rules!** Now you can validate Loki rules as well. First two validators are:
- `expressionIsValidLogQL` to check if the expression is a valid LogQL query
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ version


validate [<flags>] <path>...
Validate Prometheus rule files using validation rules from config file.
Validate Prometheus rule files in YAML or jsonnet format using validation rules from config file(s).

-d, --disable-rule=DISABLE-RULE ...
Allows to disable any validation rules by it's name. Can be passed multiple times.
Expand All @@ -98,6 +98,10 @@ validation-docs [<flags>]
Format of the output.
```

#### Jsonnet support
Promruval supports the default YAML format (`.yaml` or `.yml`) of rule files but also supports rules written in [Jsonnet](https://jsonnet.org/) (`.jsonnet`).
If will be rendered using the [go-jsonnet](https://github.com/google/go-jsonnet) library and then validated as usual, so you don't have to evaluate those by yourself before running the validation.

#### Configuration composition

The `--config-file` flag can be passed multiple times. Promruval will append the additional validation rules from the
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/bmatcuk/doublestar/v4 v4.6.1
github.com/creasty/defaults v1.7.0
github.com/google/go-jsonnet v0.20.0
github.com/grafana/dskit v0.0.0-20240528015923-27d7d41066d3
github.com/prometheus/client_golang v1.19.1
github.com/prometheus/common v0.55.0
Expand Down Expand Up @@ -108,6 +109,7 @@ require (
k8s.io/client-go v0.30.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)

require (
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g=
github.com/google/go-jsonnet v0.20.0/go.mod h1:VbgWF9JX7ztlv770x/TolZNGGFfiHEVx9G6ca2eUmeA=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
Expand Down Expand Up @@ -402,6 +404,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sercand/kuberesolver/v5 v5.1.1 h1:CYH+d67G0sGBj7q5wLK61yzqJJ8gLLC8aeprPTHb6yY=
github.com/sercand/kuberesolver/v5 v5.1.1/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYMWZJ294T3BtmVCpQ=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
Expand Down Expand Up @@ -697,3 +701,5 @@ k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ var (

versionCmd = app.Command("version", "Print version and build information.")

validateCmd = app.Command("validate", "Validate Prometheus rule files using validation rules from config file.")
filePaths = validateCmd.Arg("path", "File paths to be validated, can use even double star globs or ~. Will be expanded if not done by bash.").Required().Strings()
validateCmd = app.Command("validate", "Validate Prometheus rule files in YAML or jsonnet format using validation rules from config file(s).")
filePaths = validateCmd.Arg("path", "Rule file paths to be validated (.yaml, .yml or .jsonnet), can use even double star globs or ~. Will be expanded if not done by bash.").Required().Strings()
disabledRules = validateCmd.Flag("disable-rule", "Allows to disable any validation rules by it's name. Can be passed multiple times.").Short('d').Strings()
enabledRules = validateCmd.Flag("enable-rule", "Only enable these validation rules. Can be passed multiple times.").Short('e').Strings()
validationOutputFormat = validateCmd.Flag("output", "Format of the output.").Short('o').PlaceHolder("[text,json,yaml]").Default("text").Enum("text", "json", "yaml")
Expand Down
32 changes: 24 additions & 8 deletions pkg/validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/fusakla/promruval/v2/pkg/unmarshaler"
"github.com/fusakla/promruval/v2/pkg/validationrule"
"github.com/fusakla/promruval/v2/pkg/validator"
"github.com/google/go-jsonnet"
"github.com/prometheus/prometheus/model/rulefmt"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -42,23 +43,38 @@ func Files(fileNames []string, validationRules []*validationrule.ValidationRule,
for _, r := range validationRules {
validationReport.ValidationRules = append(validationReport.ValidationRules, r)
}
jsonnetVM := jsonnet.MakeVM()
start := time.Now()
fileCount := len(fileNames)
for i, fileName := range fileNames {
log.Infof("processing file %d/%d %s", i+1, fileCount, fileName)
validationReport.FilesCount++
fileReport := validationReport.NewFileReport(fileName)
f, err := os.Open(fileName)
if err != nil {
validationReport.Failed = true
fileReport.Valid = false
fileReport.Errors = []error{fmt.Errorf("cannot read file %s: %w", fileName, err)}
continue
var yamlReader io.Reader
if strings.HasSuffix(fileName, ".yaml") || strings.HasSuffix(fileName, ".yml") {
var err error
yamlReader, err = os.Open(fileName)
if err != nil {
validationReport.Failed = true
fileReport.Valid = false
fileReport.Errors = []error{fmt.Errorf("cannot read file %s: %w", fileName, err)}
continue
}
} else if strings.HasSuffix(fileName, ".jsonnet") {
log.Debugf("evaluating jsonnet file %s", fileName)
jsonnetOutput, err := jsonnetVM.EvaluateFile(fileName)
if err != nil {
validationReport.Failed = true
fileReport.Valid = false
fileReport.Errors = []error{fmt.Errorf("cannot evaluate jsonnet file %s: %w", fileName, err)}
continue
}
yamlReader = strings.NewReader(jsonnetOutput)
}
var rf unmarshaler.RulesFileWithComment
decoder := yaml.NewDecoder(f)
decoder := yaml.NewDecoder(yamlReader)
decoder.KnownFields(true)
err = decoder.Decode(&rf)
err := decoder.Decode(&rf)
if err != nil {
if errors.Is(err, io.EOF) {
continue
Expand Down