diff --git a/Makefile b/Makefile index f9a54d4..f8595ba 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ intermediate-docs: @go run ./main.go -h > ./docs/_intermediate/help.txt @go run ./main.go completion -h > ./docs/_intermediate/completion.txt @go run ./main.go lint -h > ./docs/_intermediate/lint.txt - @go run ./main.go rules > ./docs/_intermediate/rules.txt + @go run ./main.go rules --experimental > ./docs/_intermediate/rules.txt @echo "Can't automate everything, please replace the #Rules section of index.md with the contents of ./docs/_intermediate/rules.txt" embedmd: diff --git a/docs/index.md b/docs/index.md index 53f9a62..dd94589 100644 --- a/docs/index.md +++ b/docs/index.md @@ -52,8 +52,10 @@ Usage: Flags: -c, --config string path to a configuration file + --experimental enable experimental rules --fix automatically fix problems if possible -h, --help help for lint + --stdin read from stdin --strict fail upon linting error or warning --verbose show more information about linting ``` @@ -62,20 +64,32 @@ Flags: The linter implements the following rules: -* [template-datasource-rule](./rules/template-datasource-rule.md) - Checks that the dashboard has a templated datasource. -* [template-job-rule](./rules/template-job-rule.md) - Checks that the dashboard has a templated job. -* [template-instance-rule](./rules/template-instance-rule.md) - Checks that the dashboard has a templated instance. -* [template-label-promql-rule](./rules/template-label-promql-rule.md) - Checks that the dashboard templated labels have proper PromQL expressions. -* [template-on-time-change-reload-rule](./rules/template-on-time-change-reload-rule.md) - Checks that the dashboard template variables are configured to reload on time change. -* [panel-datasource-rule](./rules/panel-datasource-rule.md) - Checks that each panel uses the templated datasource. -* [panel-title-description-rule](./rules/panel-title-description-rule.md) - Checks that each panel has a title and description. -* [panel-units-rule](./rules/panel-units-rule.md) - Checks that each panel uses has valid units defined. -* `panel-no-targets-rule` - Checks that each panel has at least one target. -* [target-promql-rule](./rules/target-promql-rule.md) - Checks that each target uses a valid PromQL query. -* [target-rate-interval-rule](./rules/target-rate-interval-rule.md) - Checks that each target uses $__rate_interval. -* [target-job-rule](./rules/target-job-rule.md) - Checks that every PromQL query has a job matcher. -* [target-instance-rule](./rules/target-instance-rule.md) - Checks that every PromQL query has a instance matcher. -* `target-counter-agg-rule` - Checks that any counter metric (ending in _total) is aggregated with rate, irate, or increase. +* [template-datasource-rule](./rules/template-datasource-rule) - ``stable`` - Checks that the dashboard has a templated datasource. +* [template-job-rule](./rules/template-job-rule) - `stable` - Checks that the dashboard has a templated job. +* [template-instance-rule](./rules/template-instance-rule) - `stable` - Checks that the dashboard has a templated instance. +* [template-label-promql-rule](./rules/template-label-promql-rule) - `stable` - Checks that the dashboard templated labels have proper PromQL expressions. +* [template-on-time-change-reload-rule](./rules/template-on-time-change-reload-rule) - `stable` - Checks that the dashboard template variables are configured to reload on time change. +* [panel-datasource-rule](./rules/panel-datasource-rule) - `stable` - Checks that each panel uses the templated datasource. +* [panel-title-description-rule](./rules/panel-title-description-rule) - `stable` - Checks that each panel has a title and description. +* [panel-units-rule](./rules/panel-units-rule) - `stable` - Checks that each panel uses has valid units defined. +* [panel-no-targets-rule](./rules/panel-no-targets-rule) - `stable` - Checks that each panel has at least one target. +* [target-promql-rule](./rules/target-promql-rule) - `stable` - Checks that each target uses a valid PromQL query. +* [target-rate-interval-rule](./rules/target-rate-interval-rule) - `stable` - Checks that each target uses $__rate_interval. +* [target-job-rule](./rules/target-job-rule) - `stable` - Checks that every PromQL query has a job matcher. +* [target-instance-rule](./rules/target-instance-rule) - `stable` - Checks that every PromQL query has a instance matcher. +* [target-counter-agg-rule](./rules/target-counter-agg-rule) - `stable` - Checks that any counter metric (ending in _total) is aggregated with rate, irate, or increase. +* [uneditable-dashboard-rule](./rules/uneditable-dashboard-rule) - `stable` - Checks that the dashboard is not editable. +* [target-required-matchers-rule](./rules/target-required-matchers-rule) - `experimental` - Checks that target PromQL query has the required matchers +* [template-required-variables-rule](./rules/template-required-variables-rule) - `experimental` - Checks that the dashboard has a template variable for required variables or matchers that use variables + +## Rule stability +- **Stable** rules have gone through testing and been widely adopted. + +- **Experimental** rules are for new and experimental features. + These rules are not enabled by default, but can be enabled by providing the `experimental` flag. + Allowing early adopters to gain confidence with new features. + +- **Deprecated** rules may be removed or replaced when they are marked as deprecated. ## Related Rules diff --git a/docs/rules/target-required-matchers-rule.md b/docs/rules/target-required-matchers-rule.md new file mode 100644 index 0000000..51fd807 --- /dev/null +++ b/docs/rules/target-required-matchers-rule.md @@ -0,0 +1,20 @@ +# target-required-matchers-rule +Checks that each PromQL query has a the matchers specified in rule settings. This rule is experimental and is designed to work with Prometheus datasources. + +## Rule Settings + +```yaml +settings: + target-required-matchers-rule: + matchers: + - cluster=~"$cluster" + - someLabel="someValue" +``` +Legacy config example for job and instance +```yaml +settings: + target-required-matchers-rule: + matchers: + - job + - instance +``` \ No newline at end of file diff --git a/docs/rules/template-required-variables-rule.md b/docs/rules/template-required-variables-rule.md new file mode 100644 index 0000000..6c05500 --- /dev/null +++ b/docs/rules/template-required-variables-rule.md @@ -0,0 +1,31 @@ +# template-required-variables-rule +Checks that each dashboard has a templated variable based on provided rule settings and detected variable usage for the target-required-matchers-rule. + +# Best Practice +The rule ensures all of the following conditions. + +* The dashboard template exists. +* The dashboard template is named `xxx`. +* The dashboard template is labeled `xxx`. +* The dashboard template uses a templated datasource, specifically named `$datasource`. +* The dashboard template uses a Prometheus query to find available matching instances. +* The dashboard template is multi select +* The dashboard template has an allValue of `.+` + +## Rule Settings + +```yaml +settings: + template-required-variables-rule: + variables: + - cluster + - namespace +``` +Legacy config example for job and instance +```yaml +settings: + template-required-variables-rule: + variables: + - job + - instance +``` \ No newline at end of file diff --git a/go.mod b/go.mod index e187f0e..046317f 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/grafana/dashboard-linter -go 1.21 +go 1.21.0 -toolchain go1.22.1 +toolchain go1.22.5 require ( - github.com/prometheus/prometheus v0.53.1 + github.com/prometheus/prometheus v0.54.1 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 @@ -15,12 +15,21 @@ require ( ) require ( - cloud.google.com/go v0.114.0 // indirect - cloud.google.com/go/auth v0.5.1 // indirect + github.com/aws/aws-sdk-go v1.54.19 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect + github.com/prometheus/common/sigv4 v0.1.0 // indirect +) + +require ( + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.7.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect - cloud.google.com/go/iam v1.1.8 // indirect - cloud.google.com/go/storage v1.40.0 // indirect + cloud.google.com/go/compute/metadata v0.4.0 // indirect + cloud.google.com/go/iam v1.1.10 // indirect + cloud.google.com/go/storage v1.41.0 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -31,14 +40,14 @@ require ( github.com/ghodss/yaml v1.0.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.4 // indirect + github.com/googleapis/gax-go/v2 v2.12.5 // indirect github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -46,10 +55,11 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/alertmanager v0.27.0 github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.54.0 // indirect - github.com/prometheus/procfs v0.13.0 // indirect + github.com/prometheus/common v0.55.0 + github.com/prometheus/procfs v0.15.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -62,25 +72,25 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect - go.opentelemetry.io/otel v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.27.0 // indirect - go.opentelemetry.io/otel/trace v1.27.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.24.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/time v0.5.0 // indirect - google.golang.org/api v0.183.0 // indirect - google.golang.org/genproto v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect - google.golang.org/grpc v1.64.0 // indirect - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/api v0.188.0 // indirect + google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 5216866..f054b89 100644 --- a/go.sum +++ b/go.sum @@ -1,38 +1,83 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.114.0 h1:OIPFAdfrFDFO2ve2U7r/H5SwSbBzEdrBdE7xkgwc+kY= -cloud.google.com/go v0.114.0/go.mod h1:ZV9La5YYxctro1HTPug5lXH/GefROyW8PPD4T8n9J8E= -cloud.google.com/go/auth v0.5.1 h1:0QNO7VThG54LUzKiQxv8C6x1YX7lUrzlAa1nVLF8CIw= -cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/auth v0.7.0 h1:kf/x9B3WTbBUHkC+1VS8wwwli9TzhSt0vSTVBmMR8Ts= +cloud.google.com/go/auth v0.7.0/go.mod h1:D+WqdrpcjmiCgWrXmLLxOVq1GACoE36chW6KXoEvuIw= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= -cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= -cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw= -cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2 h1:FDif4R1+UUR+00q6wquyX90K7A8dN+R5E8GEadoP7sU= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0 h1:sUFnFjzDUie80h24I7mrKtwCKgLY9L8h5Tp2x9+TWqk= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.6.0/go.mod h1:52JbnQTp15qg5mRkMBHwp0j0ZFwHJ42Sx3zVV5RE9p0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute/metadata v0.4.0 h1:vHzJCWaM4g8XIcm8kopr3XmDA4Gy/lblD3EhhSux05c= +cloud.google.com/go/compute/metadata v0.4.0/go.mod h1:SIQh1Kkb4ZJ8zJ874fqVkslA29PRXuleyj6vOzlbK7M= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v1.1.10 h1:ZSAr64oEhQSClwBL670MsJAW5/RLiC6kfw3Bqmd5ZDI= +cloud.google.com/go/iam v1.1.10/go.mod h1:iEgMq62sg8zx446GCaijmA2Miwg5o3UbO+nI47WHJps= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0= +cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs= -github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/aws/aws-sdk-go v1.53.16 h1:8oZjKQO/ml1WLUZw5hvF7pvYjPf8o9f57Wldoy/q9Qc= -github.com/aws/aws-sdk-go v1.53.16/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg= +github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.54.19 h1:tyWV+07jagrNiCcGRzRhdtVjQs7Vy41NwsuOcl0IbVI= +github.com/aws/aws-sdk-go v1.54.19/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps= github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -54,45 +99,87 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -100,30 +187,59 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= -github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= @@ -132,22 +248,43 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/alertmanager v0.27.0 h1:V6nTa2J5V4s8TG4C4HtrBP/WNSebCCTYGGv4qecA/+I= +github.com/prometheus/alertmanager v0.27.0/go.mod h1:8Ia/R3urPmbzJ8OsdvmZvIprDwvwmYCmUbwBL+jlPOE= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.54.0 h1:ZlZy0BgJhTwVZUn7dLOkwCZHUkrAqd3WYtcFCWnM1D8= -github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/common/sigv4 v0.1.0 h1:qoVebwtwwEhS85Czm2dSROY5fTo2PAPEVdDeppTwGX4= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= -github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= -github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= -github.com/prometheus/prometheus v0.53.1 h1:B0xu4VuVTKYrIuBMn/4YSUoIPYxs956qsOfcS4rqCuA= -github.com/prometheus/prometheus v0.53.1/go.mod h1:RZDkzs+ShMBDkAPQkLEaLBXpjmDcjhNxU2drUVPgKUU= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/prometheus v0.54.1 h1:vKuwQNjnYN2/mDoWfHXDhAsz/68q/dQDb+YbcEqU7MQ= +github.com/prometheus/prometheus v0.54.1/go.mod h1:xlLByHhk2g3ycakQGrMaU8K7OySZx98BzeCR99991NY= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -155,6 +292,9 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -168,10 +308,14 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -187,94 +331,299 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeitlinger/conflate v0.0.0-20230622100834-279724abda8c h1:PtECnCzGLw8MuQ0tmPRaN5c95ZfNTFZOobvgC6A83zk= github.com/zeitlinger/conflate v0.0.0-20230622100834-279724abda8c/go.mod h1:KsJBt1tGR0Q7u+3T7CLN+zITAI06GiXVi/cgP9Xrpb8= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= -go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= -go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= -go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= -go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= -go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI= -go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A= -go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= -go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8= golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.183.0 h1:PNMeRDwo1pJdgNcFQ9GstuLe/noWKIc89pRWRLMvLwE= -google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.188.0 h1:51y8fJ/b1AaaBRJr4yWm96fPcuxSo0JcegXE3DaHQHw= +google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240528184218-531527333157 h1:u7WMYrIrVvs0TF5yaKwKNbcJyySYf+HAIFXxWltJOXE= -google.golang.org/genproto v0.0.0-20240528184218-531527333157/go.mod h1:ubQlAQnzejB8uZzszhrTCU2Fyp6Vi7ZE5nn0c3W8+qQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw= -google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b h1:dSTjko30weBaMj3eERKc0ZVXW4GudCswM3m+P++ukU0= +google.golang.org/genproto v0.0.0-20240708141625-4ad9e859172b/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b h1:04+jVzTs2XBnOZcPsLnmrTGqltqJbZQ1Ey26hjYdQQ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240708141625-4ad9e859172b/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= -google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -283,27 +632,47 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg= k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/lint/configuration.go b/lint/configuration.go index 5e84074..a131b25 100644 --- a/lint/configuration.go +++ b/lint/configuration.go @@ -11,10 +11,11 @@ import ( // ConfigurationFile contains a map for rule exclusions, and warnings, where the key is the // rule name to be excluded or downgraded to a warning type ConfigurationFile struct { - Exclusions map[string]*ConfigurationRuleEntries `yaml:"exclusions"` - Warnings map[string]*ConfigurationRuleEntries `yaml:"warnings"` - Verbose bool `yaml:"-"` - Autofix bool `yaml:"-"` + Exclusions map[string]*ConfigurationRuleEntries `yaml:"exclusions"` + Warnings map[string]*ConfigurationRuleEntries `yaml:"warnings"` + RuleSettings ConfigurationRuleSettings `yaml:"settings,omitempty"` + Verbose bool `yaml:"-"` + Autofix bool `yaml:"-"` } type ConfigurationRuleEntries struct { @@ -22,6 +23,11 @@ type ConfigurationRuleEntries struct { Entries []ConfigurationEntry `json:"entries,omitempty"` } +type ConfigurationRuleSettings struct { + TemplateRequiredVariablesRule *TemplateRequiredVariablesRuleSettings `yaml:"template-required-variables-rule,omitempty"` + TargetRequiredMatchersRule *TargetRequiredMatchersRuleSettings `yaml:"target-required-matchers-rule,omitempty"` +} + // ConfigurationEntry will exist precisely once for every instance of a rule violation you wish // exclude or downgrade to a warning. Each ConfigurationEntry will have to be an *exact* match // to the combination of attributes set. Reason will not be evaluated, and is an opportunity for @@ -124,6 +130,10 @@ func NewConfigurationFile() *ConfigurationFile { return &ConfigurationFile{ Exclusions: map[string]*ConfigurationRuleEntries{}, Warnings: map[string]*ConfigurationRuleEntries{}, + RuleSettings: ConfigurationRuleSettings{ + TemplateRequiredVariablesRule: &TemplateRequiredVariablesRuleSettings{}, + TargetRequiredMatchersRule: &TargetRequiredMatchersRuleSettings{}, + }, } } diff --git a/lint/constants.go b/lint/constants.go index ea96b9c..da9678d 100644 --- a/lint/constants.go +++ b/lint/constants.go @@ -10,3 +10,9 @@ const ( panelTypeTimeSeries = "timeseries" panelTypeTimeTable = "table" ) + +const ( + ruleStabilityStable = "stable" + ruleStabilityExperimental = "experimental" + ruleStabilityDeprecated = "deprecated" +) diff --git a/lint/lint.go b/lint/lint.go index 59f0332..2b147b9 100644 --- a/lint/lint.go +++ b/lint/lint.go @@ -273,7 +273,7 @@ type Dashboard struct { } `json:"templating"` Rows []Row `json:"rows,omitempty"` Panels []Panel `json:"panels,omitempty"` - Editable bool `json:"editable,omitempty"` + Editable bool `json:"editable"` // Do not omitempty, since false is seen as empty, and if it is not included, it defaults to true. } // GetPanels returns the all panels whether they are nested in the (now deprecated) "rows" property or diff --git a/lint/results.go b/lint/results.go index 7cf9be0..2841f8f 100644 --- a/lint/results.go +++ b/lint/results.go @@ -34,12 +34,26 @@ type TargetRuleResults struct { Results []TargetResult } +func TargetMessage(d Dashboard, p Panel, t Target, message string) string { + return fmt.Sprintf("Dashboard '%s', panel '%s', target idx '%d' %s", d.Title, p.Title, t.Idx, message) +} + func (r *TargetRuleResults) AddError(d Dashboard, p Panel, t Target, message string) { r.Results = append(r.Results, TargetResult{ Result: Result{ Severity: Error, - Message: fmt.Sprintf("Dashboard '%s', panel '%s', target idx '%d' %s", d.Title, p.Title, t.Idx, message), + Message: TargetMessage(d, p, t, message), + }, + }) +} + +func (r *TargetRuleResults) AddFixableError(d Dashboard, p Panel, t Target, message string, fix func(Dashboard, Panel, *Target)) { + r.Results = append(r.Results, TargetResult{ + Result: Result{ + Severity: Error, + Message: TargetMessage(d, p, t, message), }, + Fix: fix, }) } diff --git a/lint/rule_panel_datasource.go b/lint/rule_panel_datasource.go index ac0a9d2..55d51a5 100644 --- a/lint/rule_panel_datasource.go +++ b/lint/rule_panel_datasource.go @@ -8,6 +8,7 @@ func NewPanelDatasourceRule() *PanelRuleFunc { return &PanelRuleFunc{ name: "panel-datasource-rule", description: "Checks that each panel uses the templated datasource.", + stability: ruleStabilityStable, fn: func(d Dashboard, p Panel) PanelRuleResults { r := PanelRuleResults{} diff --git a/lint/rule_panel_no_targets.go b/lint/rule_panel_no_targets.go index 79a249a..dd512a8 100644 --- a/lint/rule_panel_no_targets.go +++ b/lint/rule_panel_no_targets.go @@ -4,6 +4,7 @@ func NewPanelNoTargetsRule() *PanelRuleFunc { return &PanelRuleFunc{ name: "panel-no-targets-rule", description: "Checks that each panel has at least one target.", + stability: ruleStabilityStable, fn: func(d Dashboard, p Panel) PanelRuleResults { r := PanelRuleResults{} switch p.Type { diff --git a/lint/rule_panel_title_description.go b/lint/rule_panel_title_description.go index eeb7506..76c6e7c 100644 --- a/lint/rule_panel_title_description.go +++ b/lint/rule_panel_title_description.go @@ -6,6 +6,7 @@ func NewPanelTitleDescriptionRule() *PanelRuleFunc { return &PanelRuleFunc{ name: "panel-title-description-rule", description: "Checks that each panel has a title and description.", + stability: ruleStabilityStable, fn: func(d Dashboard, p Panel) PanelRuleResults { r := PanelRuleResults{} switch p.Type { diff --git a/lint/rule_panel_units.go b/lint/rule_panel_units.go index ace2c3f..220b264 100644 --- a/lint/rule_panel_units.go +++ b/lint/rule_panel_units.go @@ -66,6 +66,7 @@ func NewPanelUnitsRule() *PanelRuleFunc { return &PanelRuleFunc{ name: "panel-units-rule", description: "Checks that each panel uses has valid units defined.", + stability: ruleStabilityStable, fn: func(d Dashboard, p Panel) PanelRuleResults { r := PanelRuleResults{} switch p.Type { diff --git a/lint/rule_target_counter_agg.go b/lint/rule_target_counter_agg.go index bb87d06..9f9b065 100644 --- a/lint/rule_target_counter_agg.go +++ b/lint/rule_target_counter_agg.go @@ -11,6 +11,7 @@ func NewTargetCounterAggRule() *TargetRuleFunc { return &TargetRuleFunc{ name: "target-counter-agg-rule", description: "Checks that any counter metric (ending in _total) is aggregated with rate, irate, or increase.", + stability: ruleStabilityStable, fn: func(d Dashboard, p Panel, t Target) TargetRuleResults { r := TargetRuleResults{} expr, err := parsePromQL(t.Expr, d.Templating.List) diff --git a/lint/rule_target_job_instance.go b/lint/rule_target_job_instance.go index 968f679..4e52572 100644 --- a/lint/rule_target_job_instance.go +++ b/lint/rule_target_job_instance.go @@ -11,6 +11,7 @@ func newTargetRequiredMatcherRule(matcher string) *TargetRuleFunc { return &TargetRuleFunc{ name: fmt.Sprintf("target-%s-rule", matcher), description: fmt.Sprintf("Checks that every PromQL query has a %s matcher.", matcher), + stability: ruleStabilityStable, fn: func(d Dashboard, p Panel, t Target) TargetRuleResults { r := TargetRuleResults{} // TODO: The RuleSet should be responsible for routing rule checks based on their query type (prometheus, loki, mysql, etc) diff --git a/lint/rule_target_promql.go b/lint/rule_target_promql.go index 2159b80..1e13d23 100644 --- a/lint/rule_target_promql.go +++ b/lint/rule_target_promql.go @@ -39,6 +39,7 @@ func NewTargetPromQLRule() *TargetRuleFunc { return &TargetRuleFunc{ name: "target-promql-rule", description: "Checks that each target uses a valid PromQL query.", + stability: ruleStabilityStable, fn: func(d Dashboard, p Panel, t Target) TargetRuleResults { r := TargetRuleResults{} diff --git a/lint/rule_target_promql_test.go b/lint/rule_target_promql_test.go index 90554f8..2a15a0a 100644 --- a/lint/rule_target_promql_test.go +++ b/lint/rule_target_promql_test.go @@ -54,7 +54,7 @@ func TestTargetPromQLRule(t *testing.T) { { result: []Result{{ Severity: Error, - Message: "Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query 'foo(bar.baz)': 1:8: parse error: unexpected character: '.'", + Message: "Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query 'foo(bar.baz)': could not expand variables: failed to parse expression: foo(bar.baz)", }}, panel: Panel{ Title: "panel", @@ -113,7 +113,7 @@ func TestTargetPromQLRule(t *testing.T) { Type: "singlestat", Targets: []Target{ { - Expr: `sum (rate(foo[$interval:$resolution]))`, + Expr: `max by($var) (rate(cpu{}[$interval:$resolution]))`, }, }, }, @@ -134,7 +134,7 @@ func TestTargetPromQLRule(t *testing.T) { { result: []Result{{ Severity: Error, - Message: "Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query '': unknown position: parse error: no expression found in input", + Message: "Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query '': could not expand variables: failed to parse expression: ", }}, panel: Panel{ Title: "panel", @@ -155,7 +155,7 @@ func TestTargetPromQLRule(t *testing.T) { }, { Severity: Error, - Message: "Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query '': unknown position: parse error: no expression found in input", + Message: "Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query '': could not expand variables: failed to parse expression: ", }, }, panel: Panel{ @@ -194,6 +194,11 @@ func TestTargetPromQLRule(t *testing.T) { Name: "sampling", Current: map[string]interface{}{"value": "$__auto_interval_sampling"}, }, + { + Name: "var", + Type: "query", + Current: map[string]interface{}{"value": "value"}, + }, { Type: "resolution", Name: "resolution", diff --git a/lint/rule_target_rate_interval.go b/lint/rule_target_rate_interval.go index 55fbc6f..c5ca1e7 100644 --- a/lint/rule_target_rate_interval.go +++ b/lint/rule_target_rate_interval.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/promql/parser" ) @@ -19,14 +20,10 @@ func (f inspector) Visit(node parser.Node, path []parser.Node) (parser.Visitor, // NewTargetRateIntervalRule builds a lint rule for panels with Prometheus queries which checks // all range vector selectors use $__rate_interval. func NewTargetRateIntervalRule() *TargetRuleFunc { - rateIntervalMagicDuration, err := time.ParseDuration(globalVariables["__rate_interval"].(string)) - if err != nil { - // Will not happen - panic(err) - } return &TargetRuleFunc{ name: "target-rate-interval-rule", description: "Checks that each target uses $__rate_interval.", + stability: ruleStabilityStable, fn: func(d Dashboard, p Panel, t Target) TargetRuleResults { r := TargetRuleResults{} if t := getTemplateDatasource(d); t == nil || t.Query != Prometheus { @@ -44,6 +41,11 @@ func NewTargetRateIntervalRule() *TargetRuleFunc { // Invalid PromQL is another rule return r } + rateIntervalMagicDuration, err := model.ParseDuration(placeholderByVariable["$__rate_interval"].value + "s") + if err != nil { + // Will not happen + panic(err) + } err = parser.Walk(inspector(func(node parser.Node, parents []parser.Node) error { selector, ok := node.(*parser.MatrixSelector) if !ok { @@ -51,7 +53,7 @@ func NewTargetRateIntervalRule() *TargetRuleFunc { return nil } - if selector.Range == rateIntervalMagicDuration { + if selector.Range == time.Duration(rateIntervalMagicDuration) { // Range vector selector is $__rate_interval return nil } diff --git a/lint/rule_target_rate_interval_test.go b/lint/rule_target_rate_interval_test.go index b7f84b6..bf0d5c1 100644 --- a/lint/rule_target_rate_interval_test.go +++ b/lint/rule_target_rate_interval_test.go @@ -6,7 +6,6 @@ import ( func TestTargetRateIntervalRule(t *testing.T) { linter := NewTargetRateIntervalRule() - for _, tc := range []struct { result Result panel Panel diff --git a/lint/rule_target_required_matchers.go b/lint/rule_target_required_matchers.go new file mode 100644 index 0000000..9f27743 --- /dev/null +++ b/lint/rule_target_required_matchers.go @@ -0,0 +1,106 @@ +package lint + +import ( + "fmt" + + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/promql/parser" +) + +type TargetRequiredMatchersRuleSettings struct { + Matchers config.Matchers `yaml:"matchers"` +} + +func NewTargetRequiredMatchersRule(config *TargetRequiredMatchersRuleSettings) *TargetRuleFunc { + return &TargetRuleFunc{ + name: "target-required-matchers-rule", + description: "Checks that target PromQL query has the required matchers", + stability: ruleStabilityExperimental, + fn: func(d Dashboard, p Panel, t Target) TargetRuleResults { + r := TargetRuleResults{} + // TODO: The RuleSet should be responsible for routing rule checks based on their query type (prometheus, loki, mysql, etc) + // and for ensuring that the datasource is set. + if t := getTemplateDatasource(d); t == nil || t.Query != Prometheus { + // Missing template datasource is a separate rule. + // Non prometheus datasources don't have rules yet + return r + } + + expr, err := parsePromQL(t.Expr, d.Templating.List) + if err != nil { + // Invalid PromQL is another rule + return r + } + if config != nil { + for _, m := range config.Matchers { + for _, selector := range parser.ExtractSelectors(expr) { + // Check if the template variable would require a matcher to be regexp... + mType := labels.MatchType(m.Type) + for _, v := range d.Templating.List { + if fmt.Sprintf("$%s", v.Name) == m.Value { + if v.Multi || v.AllValue != "" { + mType = labels.MatchRegexp + } + } + } + if err := checkForMatcher(selector, m.Name, mType, m.Value); err != nil { + r.AddFixableError(d, p, t, fmt.Sprintf("invalid PromQL query '%s': %v", t.Expr, err), fixTargetRequiredMatcherRule(m.Name, mType, m.Value)) + } + } + } + } + return r + }, + } +} + +func fixTargetRequiredMatcherRule(name string, ty labels.MatchType, value string) func(Dashboard, Panel, *Target) { + return func(d Dashboard, p Panel, t *Target) { + // using t.Expr to ensure matchers added earlier in the loop are not lost + // no need to check for errors here, as the expression was already parsed and validated + expr, _ := parsePromQL(t.Expr, d.Templating.List) + // Walk the expression tree and add the matcher to all vector selectors + err := parser.Walk(addMatchers(name, ty, value), expr, nil) + if err != nil { + return + } + e, err := revertExpandedVariables(expr.String()) + if err != nil { + return + } + t.Expr = e + } +} + +type matcherAdder func(node parser.Node) error + +func (f matcherAdder) Visit(node parser.Node, path []parser.Node) (w parser.Visitor, err error) { + err = f(node) + return f, err +} + +func addMatchers(name string, ty labels.MatchType, value string) matcherAdder { + return func(node parser.Node) error { + if n, ok := node.(*parser.VectorSelector); ok { + matcherfixed := false + for _, m := range n.LabelMatchers { + if m.Name == name { + if m.Type != ty || m.Value != value { + m.Type = ty + m.Value = value + } + matcherfixed = true + } + } + if !matcherfixed { + n.LabelMatchers = append(n.LabelMatchers, &labels.Matcher{ + Name: name, + Type: ty, + Value: value, + }) + } + } + return nil + } +} diff --git a/lint/rule_target_required_matchers_test.go b/lint/rule_target_required_matchers_test.go new file mode 100644 index 0000000..e4b5e72 --- /dev/null +++ b/lint/rule_target_required_matchers_test.go @@ -0,0 +1,223 @@ +package lint + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/pkg/labels" + "github.com/stretchr/testify/require" +) + +func TestTargetRequiredMatcherRule(t *testing.T) { + linter := NewTargetRequiredMatchersRule(&TargetRequiredMatchersRuleSettings{ + Matchers: config.Matchers{ + { + Name: "instance", + Type: labels.MatchRegexp, + Value: "$instance", + }, + }, + }) + + for _, tc := range []struct { + name string + result Result + target Target + fixed *Target + }{ + // Happy path + { + name: "OK", + result: ResultSuccess, + target: Target{ + Expr: fmt.Sprintf(`sum(rate(foo{%s=~"$%s"}[5m]))`, "instance", "instance"), + }, + }, + // Also happy when the promql is invalid + { + name: "OK-invalid-promql", + result: ResultSuccess, + target: Target{ + Expr: `foo(bar.baz))`, + }, + }, + // Missing matcher + { + name: "autofix-missing-matcher", + result: Result{ + Severity: Fixed, + Message: fmt.Sprintf("Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query 'sum(rate(foo[5m]))': %s selector not found", "instance"), + }, + target: Target{ + Expr: `sum(rate(foo[5m]))`, + }, + fixed: &Target{ + Expr: fmt.Sprintf(`sum(rate(foo{%s=~"$%s"}[5m]))`, "instance", "instance"), + }, + }, + // Not a regex matcher + { + name: "autofix-not-regex-matcher", + result: Result{ + Severity: Fixed, + Message: fmt.Sprintf("Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query 'sum(rate(foo{%s=\"$%s\"}[5m]))': %s selector is =, not =~", "instance", "instance", "instance"), + }, + target: Target{ + Expr: fmt.Sprintf(`sum(rate(foo{%s="$%s"}[5m]))`, "instance", "instance"), + }, + fixed: &Target{ + Expr: fmt.Sprintf(`sum(rate(foo{%s=~"$%s"}[5m]))`, "instance", "instance"), + }, + }, + // Wrong template variable + { + name: "autofix-wrong-template-variable", + result: Result{ + Severity: Fixed, + Message: fmt.Sprintf("Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query 'sum(rate(foo{%s=~\"$foo\"}[5m]))': %s selector is $foo, not $%s", "instance", "instance", "instance"), + }, + target: Target{ + Expr: fmt.Sprintf(`sum(rate(foo{%s=~"$foo"}[5m]))`, "instance"), + }, + fixed: &Target{ + Expr: fmt.Sprintf(`sum(rate(foo{%s=~"$%s"}[5m]))`, "instance", "instance"), + }, + }, + // Using Grafana global-variable + { + name: "autofix-reverse-expanded-variables", + result: Result{ + Severity: Fixed, + Message: fmt.Sprintf("Dashboard 'dashboard', panel 'panel', target idx '0' invalid PromQL query 'sum(rate(foo[$__rate_interval]))': %s selector not found", "instance"), + }, + target: Target{ + Expr: `sum(rate(foo[$__rate_interval]))`, + }, + fixed: &Target{ + Expr: fmt.Sprintf(`sum(rate(foo{%s=~"$%s"}[$__rate_interval]))`, "instance", "instance"), + }, + }, + } { + dashboard := Dashboard{ + Title: "dashboard", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + }, + }, + Panels: []Panel{ + { + Title: "panel", + Type: "singlestat", + Targets: []Target{tc.target}, + }, + }, + } + t.Run(tc.name, func(t *testing.T) { + autofix := tc.fixed != nil + testRuleWithAutofix(t, linter, &dashboard, []Result{tc.result}, autofix) + if autofix { + fixedDashboard := Dashboard{ + Title: "dashboard", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + }, + }, + Panels: []Panel{ + { + Title: "panel", + Type: "singlestat", + Targets: []Target{*tc.fixed}, + }, + }, + } + expected, _ := json.Marshal(fixedDashboard) + actual, _ := json.Marshal(dashboard) + require.Equal(t, string(expected), string(actual)) + } + }) + } +} + +func TestTargetRequiredMatcherRuleNilInput(t *testing.T) { + linter := NewTargetRequiredMatchersRule(nil) + + for _, tc := range []struct { + name string + result Result + target Target + fixed *Target + }{ + // Happy path + { + name: "OK", + result: ResultSuccess, + target: Target{ + Expr: fmt.Sprintf(`sum(rate(foo{%s=~"$%s"}[5m]))`, "instance", "instance"), + }, + }, + } { + dashboard := Dashboard{ + Title: "dashboard", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + }, + }, + Panels: []Panel{ + { + Title: "panel", + Type: "singlestat", + Targets: []Target{tc.target}, + }, + }, + } + t.Run(tc.name, func(t *testing.T) { + autofix := tc.fixed != nil + testRuleWithAutofix(t, linter, &dashboard, []Result{tc.result}, autofix) + if autofix { + fixedDashboard := Dashboard{ + Title: "dashboard", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + }, + }, + Panels: []Panel{ + { + Title: "panel", + Type: "singlestat", + Targets: []Target{*tc.fixed}, + }, + }, + } + expected, _ := json.Marshal(fixedDashboard) + actual, _ := json.Marshal(dashboard) + require.Equal(t, string(expected), string(actual)) + } + }) + } +} diff --git a/lint/rule_template_datasource.go b/lint/rule_template_datasource.go index 37bffab..d3df644 100644 --- a/lint/rule_template_datasource.go +++ b/lint/rule_template_datasource.go @@ -12,6 +12,7 @@ func NewTemplateDatasourceRule() *DashboardRuleFunc { return &DashboardRuleFunc{ name: "template-datasource-rule", description: "Checks that the dashboard has a templated datasource.", + stability: ruleStabilityStable, fn: func(d Dashboard) DashboardRuleResults { r := DashboardRuleResults{} diff --git a/lint/rule_template_instance.go b/lint/rule_template_instance.go index 01142e4..3b2df2d 100644 --- a/lint/rule_template_instance.go +++ b/lint/rule_template_instance.go @@ -4,6 +4,7 @@ func NewTemplateInstanceRule() *DashboardRuleFunc { return &DashboardRuleFunc{ name: "template-instance-rule", description: "Checks that the dashboard has a templated instance.", + stability: ruleStabilityStable, fn: func(d Dashboard) DashboardRuleResults { r := DashboardRuleResults{} diff --git a/lint/rule_template_job.go b/lint/rule_template_job.go index 6eff4ed..f034ef4 100644 --- a/lint/rule_template_job.go +++ b/lint/rule_template_job.go @@ -1,16 +1,10 @@ package lint -import ( - "fmt" - - "golang.org/x/text/cases" - "golang.org/x/text/language" -) - func NewTemplateJobRule() *DashboardRuleFunc { return &DashboardRuleFunc{ name: "template-job-rule", description: "Checks that the dashboard has a templated job.", + stability: ruleStabilityStable, fn: func(d Dashboard) DashboardRuleResults { r := DashboardRuleResults{} @@ -24,51 +18,3 @@ func NewTemplateJobRule() *DashboardRuleFunc { }, } } - -func checkTemplate(d Dashboard, name string, r *DashboardRuleResults) { - t := getTemplate(d, name) - if t == nil { - r.AddError(d, fmt.Sprintf("is missing the %s template", name)) - return - } - - // TODO: Adding the prometheus_datasource here is hacky. This check function also assumes that all template vars which it will - // ever check are only prometheus queries, which may not always be the case. - src, err := t.GetDataSource() - if err != nil { - r.AddError(d, fmt.Sprintf("%s template has invalid datasource %v", name, err)) - } - - srcUid := src.UID - if srcUid != "$datasource" && srcUid != "${datasource}" && srcUid != "$prometheus_datasource" && srcUid != "${prometheus_datasource}" { - r.AddError(d, fmt.Sprintf("%s template should use datasource '$datasource', is currently '%s'", name, srcUid)) - } - - if t.Type != targetTypeQuery { - r.AddError(d, fmt.Sprintf("%s template should be a Prometheus query, is currently '%s'", name, t.Type)) - } - - titleCaser := cases.Title(language.English) - labelTitle := titleCaser.String(name) - - if t.Label != labelTitle { - r.AddWarning(d, fmt.Sprintf("%s template should be a labeled '%s', is currently '%s'", name, labelTitle, t.Label)) - } - - if !t.Multi { - r.AddError(d, fmt.Sprintf("%s template should be a multi select", name)) - } - - if t.AllValue != ".+" { - r.AddError(d, fmt.Sprintf("%s template allValue should be '.+', is currently '%s'", name, t.AllValue)) - } -} - -func getTemplate(d Dashboard, name string) *Template { - for _, template := range d.Templating.List { - if template.Name == name { - return &template - } - } - return nil -} diff --git a/lint/rule_template_label_promql.go b/lint/rule_template_label_promql.go index 62c3357..eec0c5f 100644 --- a/lint/rule_template_label_promql.go +++ b/lint/rule_template_label_promql.go @@ -5,34 +5,45 @@ import ( "regexp" ) -var templatedLabelRegexp = regexp.MustCompile(`([a-z_]+)\((.+)\)`) +var ( + lvNoQueryRegexp = regexp.MustCompile(`(?s)label_values\((.+)\)`) // label_values(label) + lvRegexp = regexp.MustCompile(`(?s)label_values\((.+),.+\)`) // label_values(metric, label) + mRegexp = regexp.MustCompile(`(?s)metrics\((.+)\)`) // metrics(metric) + lnRegexp = regexp.MustCompile(`(?s)label_names\((.+)\)`) // label_names() + qrRegexp = regexp.MustCompile(`(?s)query_result\((.+)\)`) // query_result(query) +) -func labelHasValidDataSourceFunction(name string) bool { - // https://grafana.com/docs/grafana/v8.1/datasources/prometheus/#query-variable - names := []string{"label_names", "label_values", "metrics", "query_result"} - for _, n := range names { - if name == n { - return true - } +func extractPromQLQuery(q string) []string { + // label_values(query, label) + switch { + case lvRegexp.MatchString(q): + return lvRegexp.FindStringSubmatch(q) + case lvNoQueryRegexp.MatchString(q): + return nil // No query so no metric. + case mRegexp.MatchString(q): + return mRegexp.FindStringSubmatch(q) + case lnRegexp.MatchString(q): + return lnRegexp.FindStringSubmatch(q) + case qrRegexp.MatchString(q): + return qrRegexp.FindStringSubmatch(q) + default: + return nil } - return false } // parseTemplatedLabelPromQL returns error in case // 1) The given PromQL expressions is invalid // 2) Use of invalid label function func parseTemplatedLabelPromQL(t Template, variables []Template) error { - // regex capture must return slice of 3 strings. - // 1) given query 2) function name 3) function arg. - tokens := templatedLabelRegexp.FindStringSubmatch(t.Query) + // regex capture must return slice of 2 strings. + // 1) given query 2) function arg. + + tokens := extractPromQLQuery(t.Query) if tokens == nil { return fmt.Errorf("invalid 'query': %v", t.Query) } - if !labelHasValidDataSourceFunction(tokens[1]) { - return fmt.Errorf("invalid 'function': %v", tokens[1]) - } - expr, err := parsePromQL(tokens[2], variables) + expr, err := parsePromQL(tokens[1], variables) if expr != nil { return nil } @@ -43,6 +54,7 @@ func NewTemplateLabelPromQLRule() *DashboardRuleFunc { return &DashboardRuleFunc{ name: "template-label-promql-rule", description: "Checks that the dashboard templated labels have proper PromQL expressions.", + stability: ruleStabilityStable, fn: func(d Dashboard) DashboardRuleResults { r := DashboardRuleResults{} @@ -58,7 +70,6 @@ func NewTemplateLabelPromQLRule() *DashboardRuleFunc { r.AddError(d, fmt.Sprintf("template '%s' invalid templated label '%s': %v", template.Name, template.Query, err)) } } - return r }, } diff --git a/lint/rule_template_label_promql_test.go b/lint/rule_template_label_promql_test.go index 8ef33ac..aec7778 100644 --- a/lint/rule_template_label_promql_test.go +++ b/lint/rule_template_label_promql_test.go @@ -57,7 +57,7 @@ func TestTemplateLabelPromQLRule(t *testing.T) { name: "Error", result: Result{ Severity: Error, - Message: `Dashboard 'test' template 'namespaces' invalid templated label 'label_values(up{, namespace)': 1:4: parse error: unexpected "," in label matching, expected identifier or "}"`, + Message: `Dashboard 'test' template 'namespaces' invalid templated label 'label_values(up{, namespace)': could not expand variables: failed to parse expression: up{`, }, dashboard: Dashboard{ Title: "test", @@ -84,7 +84,7 @@ func TestTemplateLabelPromQLRule(t *testing.T) { name: "Invalid function.", result: Result{ Severity: Error, - Message: `Dashboard 'test' template 'namespaces' invalid templated label 'foo(up, namespace)': invalid 'function': foo`, + Message: `Dashboard 'test' template 'namespaces' invalid templated label 'foo(up, namespace)': invalid 'query': foo(up, namespace)`, }, dashboard: Dashboard{ Title: "test", diff --git a/lint/rule_template_on_time_change_reload.go b/lint/rule_template_on_time_change_reload.go index cef025a..75c6beb 100644 --- a/lint/rule_template_on_time_change_reload.go +++ b/lint/rule_template_on_time_change_reload.go @@ -8,6 +8,7 @@ func NewTemplateOnTimeRangeReloadRule() *DashboardRuleFunc { return &DashboardRuleFunc{ name: "template-on-time-change-reload-rule", description: "Checks that the dashboard template variables are configured to reload on time change.", + stability: ruleStabilityStable, fn: func(d Dashboard) DashboardRuleResults { r := DashboardRuleResults{} diff --git a/lint/rule_template_required_variables.go b/lint/rule_template_required_variables.go new file mode 100644 index 0000000..479475d --- /dev/null +++ b/lint/rule_template_required_variables.go @@ -0,0 +1,58 @@ +package lint + +import ( + "strings" +) + +type TemplateRequiredVariablesRuleSettings struct { + Variables []string `yaml:"variables"` +} + +func NewTemplateRequiredVariablesRule(config *TemplateRequiredVariablesRuleSettings, requiredMatchers *TargetRequiredMatchersRuleSettings) *DashboardRuleFunc { + return &DashboardRuleFunc{ + name: "template-required-variables-rule", + description: "Checks that the dashboard has a template variable for required variables or matchers that use variables", + stability: ruleStabilityExperimental, + fn: func(d Dashboard) DashboardRuleResults { + r := DashboardRuleResults{} + + template := getTemplateDatasource(d) + if template == nil || template.Query != Prometheus { + return r + } + + // Create a map and a slice, map for uniqueness and slice to keep the order... + var varMap = make(map[string]bool) + var varSlice = []string{} + + if config != nil { + // Convert the config.variables to a map to leverage uniqueness... + for _, v := range config.Variables { + if varMap[v] { + continue + } + varMap[v] = true + varSlice = append(varSlice, v) + } + } + + if requiredMatchers != nil { + // Check that all required matchers that use variables form target-required-matchers have a corresponding template variable + for _, m := range requiredMatchers.Matchers { + if strings.HasPrefix(m.Value, "$") { + if varMap[m.Value[1:]] { + continue + } + varMap[m.Value[1:]] = true + varSlice = append(varSlice, m.Value[1:]) + } + } + } + + for _, v := range varSlice { + checkTemplate(d, v, &r) + } + return r + }, + } +} diff --git a/lint/rule_template_required_variables_test.go b/lint/rule_template_required_variables_test.go new file mode 100644 index 0000000..7c6b386 --- /dev/null +++ b/lint/rule_template_required_variables_test.go @@ -0,0 +1,351 @@ +package lint + +import ( + "testing" + + "github.com/prometheus/alertmanager/config" + "github.com/prometheus/alertmanager/pkg/labels" +) + +func TestTemplateRequiredVariable(t *testing.T) { + linter := NewTemplateRequiredVariablesRule( + &TemplateRequiredVariablesRuleSettings{ + Variables: []string{"job"}, + }, + &TargetRequiredMatchersRuleSettings{ + Matchers: config.Matchers{ + { + Name: "instance", + Type: labels.MatchRegexp, + Value: "$instance", + }, + }, + }) + + for _, tc := range []struct { + name string + result []Result + dashboard Dashboard + }{ + { + name: "Non-promtheus dashboards shouldn't fail.", + result: []Result{ResultSuccess}, + dashboard: Dashboard{ + Title: "test", + }, + }, + { + name: "Missing job/instance template.", + result: []Result{ + {Severity: Error, Message: "Dashboard 'test' is missing the job template"}, + {Severity: Error, Message: "Dashboard 'test' is missing the instance template"}, + }, + dashboard: Dashboard{ + Title: "test", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + }, + }, + }, + }, + { + name: "Wrong datasource.", + result: []Result{ + {Severity: Error, Message: "Dashboard 'test' job template should use datasource '$datasource', is currently 'foo'"}, + {Severity: Error, Message: "Dashboard 'test' job template should be a Prometheus query, is currently ''"}, + {Severity: Warning, Message: "Dashboard 'test' job template should be a labeled 'Job', is currently ''"}, + {Severity: Error, Message: "Dashboard 'test' job template should be a multi select"}, + {Severity: Error, Message: "Dashboard 'test' job template allValue should be '.+', is currently ''"}, + {Severity: Error, Message: "Dashboard 'test' instance template should use datasource '$datasource', is currently 'foo'"}, + {Severity: Error, Message: "Dashboard 'test' instance template should be a Prometheus query, is currently ''"}, + {Severity: Warning, Message: "Dashboard 'test' instance template should be a labeled 'Instance', is currently ''"}, + {Severity: Error, Message: "Dashboard 'test' instance template should be a multi select"}, + {Severity: Error, Message: "Dashboard 'test' instance template allValue should be '.+', is currently ''"}, + }, + dashboard: Dashboard{ + Title: "test", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + { + Name: "job", + Datasource: "foo", + }, + { + Name: "instance", + Datasource: "foo", + }, + }, + }, + }, + }, + { + name: "Wrong type.", + result: []Result{ + {Severity: Error, Message: "Dashboard 'test' job template should be a Prometheus query, is currently 'bar'"}, + {Severity: Warning, Message: "Dashboard 'test' job template should be a labeled 'Job', is currently ''"}, + {Severity: Error, Message: "Dashboard 'test' job template should be a multi select"}, + {Severity: Error, Message: "Dashboard 'test' job template allValue should be '.+', is currently ''"}, + {Severity: Error, Message: "Dashboard 'test' instance template should be a Prometheus query, is currently 'bar'"}, + {Severity: Warning, Message: "Dashboard 'test' instance template should be a labeled 'Instance', is currently ''"}, + {Severity: Error, Message: "Dashboard 'test' instance template should be a multi select"}, + {Severity: Error, Message: "Dashboard 'test' instance template allValue should be '.+', is currently ''"}, + }, + dashboard: Dashboard{ + Title: "test", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + { + Name: "job", + Datasource: "$datasource", + Type: "bar", + }, + { + Name: "instance", + Datasource: "$datasource", + Type: "bar", + }, + }, + }, + }, + }, + { + name: "Wrong job/instance label.", + result: []Result{ + {Severity: Warning, Message: "Dashboard 'test' job template should be a labeled 'Job', is currently 'bar'"}, + {Severity: Error, Message: "Dashboard 'test' job template should be a multi select"}, + {Severity: Error, Message: "Dashboard 'test' job template allValue should be '.+', is currently ''"}, + {Severity: Warning, Message: "Dashboard 'test' instance template should be a labeled 'Instance', is currently 'bar'"}, + {Severity: Error, Message: "Dashboard 'test' instance template should be a multi select"}, + {Severity: Error, Message: "Dashboard 'test' instance template allValue should be '.+', is currently ''"}, + }, + dashboard: Dashboard{ + Title: "test", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + { + Name: "job", + Datasource: "$datasource", + Type: "query", + Label: "bar", + }, + { + Name: "instance", + Datasource: "$datasource", + Type: "query", + Label: "bar", + }, + }, + }, + }, + }, + { + name: "OK", + result: []Result{ResultSuccess}, + dashboard: Dashboard{ + Title: "test", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + { + Name: "job", + Datasource: "$datasource", + Type: "query", + Label: "Job", + Multi: true, + AllValue: ".+", + }, + { + Name: "instance", + Datasource: "${datasource}", + Type: "query", + Label: "Instance", + Multi: true, + AllValue: ".+", + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + testMultiResultRule(t, linter, tc.dashboard, tc.result) + }) + } +} + +func TestTemplateRequiredVariableNilRequiredMatchers(t *testing.T) { + linter := NewTemplateRequiredVariablesRule( + nil, + &TargetRequiredMatchersRuleSettings{ + Matchers: config.Matchers{ + { + Name: "instance", + Type: labels.MatchRegexp, + Value: "$instance", + }, + }, + }) + + for _, tc := range []struct { + name string + result []Result + dashboard Dashboard + }{ + { + name: "Missing instance template.", + result: []Result{ + {Severity: Error, Message: "Dashboard 'test' is missing the instance template"}, + }, + dashboard: Dashboard{ + Title: "test", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + { + Name: "job", + Datasource: "$datasource", + Type: "query", + Label: "Job", + Multi: true, + AllValue: ".+", + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + testMultiResultRule(t, linter, tc.dashboard, tc.result) + }) + } +} + +func TestTemplateRequiredVariableNilConfig(t *testing.T) { + linter := NewTemplateRequiredVariablesRule( + &TemplateRequiredVariablesRuleSettings{ + Variables: []string{"job"}, + }, + nil) + + for _, tc := range []struct { + name string + result []Result + dashboard Dashboard + }{ + { + name: "Missing job template.", + result: []Result{ + {Severity: Error, Message: "Dashboard 'test' is missing the job template"}, + }, + dashboard: Dashboard{ + Title: "test", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + { + Name: "instance", + Datasource: "${datasource}", + Type: "query", + Label: "Instance", + Multi: true, + AllValue: ".+", + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + testMultiResultRule(t, linter, tc.dashboard, tc.result) + }) + } +} + +func TestTemplateRequiredVariableNilInput(t *testing.T) { + linter := NewTemplateRequiredVariablesRule( + nil, + nil) + + for _, tc := range []struct { + name string + result []Result + dashboard Dashboard + }{ + { + name: "OK", + result: []Result{ResultSuccess}, + dashboard: Dashboard{ + Title: "test", + Templating: struct { + List []Template `json:"list"` + }{ + List: []Template{ + { + Type: "datasource", + Query: "prometheus", + }, + { + Name: "job", + Datasource: "$datasource", + Type: "query", + Label: "Job", + Multi: true, + AllValue: ".+", + }, + { + Name: "instance", + Datasource: "${datasource}", + Type: "query", + Label: "Instance", + Multi: true, + AllValue: ".+", + }, + }, + }, + }, + }, + } { + t.Run(tc.name, func(t *testing.T) { + testMultiResultRule(t, linter, tc.dashboard, tc.result) + }) + } +} diff --git a/lint/rule_uneditable.go b/lint/rule_uneditable.go index ac5f410..c1d6194 100644 --- a/lint/rule_uneditable.go +++ b/lint/rule_uneditable.go @@ -2,8 +2,9 @@ package lint func NewUneditableRule() *DashboardRuleFunc { return &DashboardRuleFunc{ - name: "uneditable-dashboard", + name: "uneditable-dashboard-rule", description: "Checks that the dashboard is not editable.", + stability: ruleStabilityStable, fn: func(d Dashboard) DashboardRuleResults { r := DashboardRuleResults{} if d.Editable { diff --git a/lint/rules.go b/lint/rules.go index 0ffbe4b..7cbefde 100644 --- a/lint/rules.go +++ b/lint/rules.go @@ -3,20 +3,22 @@ package lint type Rule interface { Description() string Name() string + Stability() string Lint(Dashboard, *ResultSet) } type DashboardRuleFunc struct { - name, description string - fn func(Dashboard) DashboardRuleResults + name, description, stability string + fn func(Dashboard) DashboardRuleResults } -func NewDashboardRuleFunc(name, description string, fn func(Dashboard) DashboardRuleResults) Rule { - return &DashboardRuleFunc{name, description, fn} +func NewDashboardRuleFunc(name, description, stability string, fn func(Dashboard) DashboardRuleResults) Rule { + return &DashboardRuleFunc{name, description, stability, fn} } func (f DashboardRuleFunc) Name() string { return f.name } func (f DashboardRuleFunc) Description() string { return f.description } +func (f DashboardRuleFunc) Stability() string { return f.stability } func (f DashboardRuleFunc) Lint(d Dashboard, s *ResultSet) { dashboardResults := f.fn(d).Results if len(dashboardResults) == 0 { @@ -49,16 +51,17 @@ func (f DashboardRuleFunc) Lint(d Dashboard, s *ResultSet) { } type PanelRuleFunc struct { - name, description string - fn func(Dashboard, Panel) PanelRuleResults + name, description, stability string + fn func(Dashboard, Panel) PanelRuleResults } -func NewPanelRuleFunc(name, description string, fn func(Dashboard, Panel) PanelRuleResults) Rule { - return &PanelRuleFunc{name, description, fn} +func NewPanelRuleFunc(name, description, stability string, fn func(Dashboard, Panel) PanelRuleResults) Rule { + return &PanelRuleFunc{name, description, stability, fn} } func (f PanelRuleFunc) Name() string { return f.name } func (f PanelRuleFunc) Description() string { return f.description } +func (f PanelRuleFunc) Stability() string { return f.stability } func (f PanelRuleFunc) Lint(d Dashboard, s *ResultSet) { for pi, p := range d.GetPanels() { p := p // capture loop variable @@ -104,16 +107,17 @@ func fixPanel(pi int, r PanelResult) func(dashboard *Dashboard) { } type TargetRuleFunc struct { - name, description string - fn func(Dashboard, Panel, Target) TargetRuleResults + name, description, stability string + fn func(Dashboard, Panel, Target) TargetRuleResults } -func NewTargetRuleFunc(name, description string, fn func(Dashboard, Panel, Target) TargetRuleResults) Rule { - return &TargetRuleFunc{name, description, fn} +func NewTargetRuleFunc(name, description, stability string, fn func(Dashboard, Panel, Target) TargetRuleResults) Rule { + return &TargetRuleFunc{name, description, stability, fn} } func (f TargetRuleFunc) Name() string { return f.name } func (f TargetRuleFunc) Description() string { return f.description } +func (f TargetRuleFunc) Stability() string { return f.stability } func (f TargetRuleFunc) Lint(d Dashboard, s *ResultSet) { for pi, p := range d.GetPanels() { p := p // capture loop variable @@ -169,26 +173,33 @@ type RuleSet struct { rules []Rule } -func NewRuleSet() RuleSet { - return RuleSet{ - rules: []Rule{ - NewTemplateDatasourceRule(), - NewTemplateJobRule(), - NewTemplateInstanceRule(), - NewTemplateLabelPromQLRule(), - NewTemplateOnTimeRangeReloadRule(), - NewPanelDatasourceRule(), - NewPanelTitleDescriptionRule(), - NewPanelUnitsRule(), - NewPanelNoTargetsRule(), - NewTargetPromQLRule(), - NewTargetRateIntervalRule(), - NewTargetJobRule(), - NewTargetInstanceRule(), - NewTargetCounterAggRule(), - NewUneditableRule(), - }, +func NewRuleSet(experimental bool, ruleSettings ConfigurationRuleSettings) RuleSet { + // Add stable rules here + rules := []Rule{ + NewTemplateDatasourceRule(), + NewTemplateJobRule(), + NewTemplateInstanceRule(), + NewTemplateLabelPromQLRule(), + NewTemplateOnTimeRangeReloadRule(), + NewPanelDatasourceRule(), + NewPanelTitleDescriptionRule(), + NewPanelUnitsRule(), + NewPanelNoTargetsRule(), + NewTargetPromQLRule(), + NewTargetRateIntervalRule(), + NewTargetJobRule(), + NewTargetInstanceRule(), + NewTargetCounterAggRule(), + NewUneditableRule(), } + // Add experimental rules here + if experimental { + rules = append(rules, + NewTargetRequiredMatchersRule(ruleSettings.TargetRequiredMatchersRule), + NewTemplateRequiredVariablesRule(ruleSettings.TemplateRequiredVariablesRule, ruleSettings.TargetRequiredMatchersRule), + ) + } + return RuleSet{rules} } func (s *RuleSet) Rules() []Rule { diff --git a/lint/rules_test.go b/lint/rules_test.go index 0263a09..2a9509d 100644 --- a/lint/rules_test.go +++ b/lint/rules_test.go @@ -19,7 +19,7 @@ func TestCustomRules(t *testing.T) { { desc: "Should allow addition of dashboard rule", rule: lint.NewDashboardRuleFunc( - "test-dashboard-rule", "Test dashboard rule", + "test-dashboard-rule", "Test dashboard rule", "stable", func(lint.Dashboard) lint.DashboardRuleResults { return lint.DashboardRuleResults{Results: []lint.DashboardResult{{ Result: lint.Result{Severity: lint.Error, Message: "Error found"}, @@ -30,7 +30,7 @@ func TestCustomRules(t *testing.T) { { desc: "Should allow addition of panel rule", rule: lint.NewPanelRuleFunc( - "test-panel-rule", "Test panel rule", + "test-panel-rule", "Test panel rule", "stable", func(d lint.Dashboard, p lint.Panel) lint.PanelRuleResults { return lint.PanelRuleResults{Results: []lint.PanelResult{{ Result: lint.Result{Severity: lint.Error, Message: "Error found"}, @@ -41,7 +41,7 @@ func TestCustomRules(t *testing.T) { { desc: "Should allow addition of target rule", rule: lint.NewTargetRuleFunc( - "test-target-rule", "Test target rule", + "test-target-rule", "Test target rule", "stable", func(lint.Dashboard, lint.Panel, lint.Target) lint.TargetRuleResults { return lint.TargetRuleResults{Results: []lint.TargetResult{{ Result: lint.Result{Severity: lint.Error, Message: "Error found"}, diff --git a/lint/template_utils.go b/lint/template_utils.go new file mode 100644 index 0000000..ed75a6f --- /dev/null +++ b/lint/template_utils.go @@ -0,0 +1,56 @@ +package lint + +import ( + "fmt" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +func checkTemplate(d Dashboard, name string, r *DashboardRuleResults) { + t := getTemplate(d, name) + if t == nil { + r.AddError(d, fmt.Sprintf("is missing the %s template", name)) + return + } + + // TODO: Adding the prometheus_datasource here is hacky. This check function also assumes that all template vars which it will + // ever check are only prometheus queries, which may not always be the case. + src, err := t.GetDataSource() + if err != nil { + r.AddError(d, fmt.Sprintf("%s template has invalid datasource %v", name, err)) + } + + srcUid := src.UID + if srcUid != "$datasource" && srcUid != "${datasource}" && srcUid != "$prometheus_datasource" && srcUid != "${prometheus_datasource}" { + r.AddError(d, fmt.Sprintf("%s template should use datasource '$datasource', is currently '%s'", name, srcUid)) + } + + if t.Type != targetTypeQuery { + r.AddError(d, fmt.Sprintf("%s template should be a Prometheus query, is currently '%s'", name, t.Type)) + } + + titleCaser := cases.Title(language.English) + labelTitle := titleCaser.String(name) + + if t.Label != labelTitle { + r.AddWarning(d, fmt.Sprintf("%s template should be a labeled '%s', is currently '%s'", name, labelTitle, t.Label)) + } + + if !t.Multi { + r.AddError(d, fmt.Sprintf("%s template should be a multi select", name)) + } + + if t.AllValue != ".+" { + r.AddError(d, fmt.Sprintf("%s template allValue should be '.+', is currently '%s'", name, t.AllValue)) + } +} + +func getTemplate(d Dashboard, name string) *Template { + for _, template := range d.Templating.List { + if template.Name == name { + return &template + } + } + return nil +} diff --git a/lint/variables.go b/lint/variables.go index 9effdc7..b5b0578 100644 --- a/lint/variables.go +++ b/lint/variables.go @@ -1,215 +1,407 @@ package lint import ( - "encoding/json" "fmt" - "net/url" "regexp" "strconv" "strings" - "time" + + "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/promql/parser" +) + +const ( + rateInterval = "__rate_interval" + interval = "__interval" + intervalMs = "__interval_ms" + rangeMs = "__range_ms" + rangeS = "__range_s" + rangeVar = "__range" + dashboard = "__dashboard" + from = "__from" + to = "__to" + name = "__name" + org = "__org" + orgName = "__org.name" + userID = "__user.id" + userLogin = "__user.login" + userEmail = "__user.email" + timeFilter = "timeFilter" + timeFilter2 = "__timeFilter" + // magicTimeRange = model.Duration(time.Hour*24*211 + time.Hour*12 + time.Minute*44 + time.Second*22 + time.Millisecond*50) // 211d12h44m22s50ms + magicTimeRange = 11277964 // seconds 130d12h46m4s + magicEpoch = float64(1294671549254) + magicString = "bgludgvy" +) + +const ( + valTypeString valType = iota + valTypeTimeRange + valTypeEpoch + valTypeMatcher ) -// https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/ -var globalVariables = map[string]interface{}{ - "__rate_interval": "8869990787ms", - "__interval": "4867856611ms", - "__interval_ms": "7781188786", - "__range_ms": "6737667980", - "__range_s": "9397795485", - "__range": "6069770749ms", - "__dashboard": "AwREbnft", - "__from": time.Date(2020, 7, 13, 20, 19, 9, 254000000, time.UTC), - "__to": time.Date(2020, 7, 13, 20, 19, 9, 254000000, time.UTC), - "__name": "name", - "__org": 42, - "__org.name": "orgname", - "__user.id": 42, - "__user.login": "user", - "__user.email": "user@test.com", - "timeFilter": "time > now() - 7d", - "__timeFilter": "time > now() - 7d", +type valType int + +type placeholder struct { + variable string // variable including the "variable syntax" i.e. $var, ${var}, [[var]] + valType valType + value string +} + +var placeholderByVariable = make(map[string]*placeholder) +var placeholderByValue = make(map[string]*placeholder) + +var globalVariablesInit = false + +// list of global variables in the for om a list of placeholders +var globalVariables = []*placeholder{ + { + variable: rateInterval, + valType: valTypeTimeRange, + }, + { + variable: interval, + valType: valTypeTimeRange, + }, + { + variable: intervalMs, + valType: valTypeTimeRange, + }, + { + variable: rangeMs, + valType: valTypeTimeRange, + }, + { + variable: rangeS, + valType: valTypeTimeRange, + }, + { + variable: rangeVar, + valType: valTypeTimeRange, + }, + { + variable: dashboard, + valType: valTypeString, + }, + { + variable: from, + valType: valTypeEpoch, + }, + { + variable: to, + valType: valTypeEpoch, + }, + { + variable: name, + valType: valTypeString, + }, + { + variable: org, + valType: valTypeEpoch, // not really an epoch, but it is a float64 + }, + { + variable: orgName, + valType: valTypeString, + }, + { + variable: userID, + valType: valTypeEpoch, // not really an epoch, but it is a float64 + }, + { + variable: userLogin, + valType: valTypeString, + }, + { + variable: userEmail, + valType: valTypeString, + }, + { + variable: timeFilter, + valType: valTypeString, // not really a string, but currently we do only support prometheus queries, and this would not be a valid prometheus query... + }, + { + variable: timeFilter2, + valType: valTypeString, // not really a string, but currently we do only support prometheus queries, and this would not be a valid prometheus query... + }, } -func stringValue(name string, value interface{}, kind, format string) (string, error) { - switch val := value.(type) { - case int: - return strconv.Itoa(val), nil - case time.Time: - // Implements https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/#__from-and-__to - switch kind { - case "date": - switch format { - case "seconds": - return strconv.FormatInt(val.Unix(), 10), nil - case "iso": - return val.Format(time.RFC3339), nil - default: - return "", fmt.Errorf("Unsupported momentjs time format: " + format) +// var supportedFormatOptions = []string{"csv", "distributed", "doublequote", "glob", "json", "lucene", "percentencode", "pipe", "raw", "regex", "singlequote", "sqlstring", "text", "queryparam"} + +var variableRegexp = regexp.MustCompile( + strings.Join([]string{ + `("\$|\$)([[:word:]]+)`, // $var syntax + `("\$|\$)\{([^}]+)\}`, // ${var} syntax + `\[\[([^\[\]]+)\]\]`, // [[var]] syntax + }, "|"), +) + +func expandVariables(expr string, variables []Template) (string, error) { + // initialize global variables if not already initialized + if !globalVariablesInit { + for _, v := range globalVariables { + // assign placeholder to global variable 3 times to account for the 3 different ways a variable can be defined + // $var, ${var}, [[var]] + p := []placeholder{ + { + variable: fmt.Sprintf("$%s", v.variable), + valType: v.valType, + }, + { + variable: fmt.Sprintf("${%s}", v.variable), + valType: v.valType, + }, + { + variable: fmt.Sprintf("[[%s]]", v.variable), + valType: v.valType, + }, } - default: - switch format { - case "date": - return val.Format(time.RFC3339), nil - default: - return strconv.FormatInt(val.UnixMilli(), 10), nil + for _, v := range p { + createPlaceholder(v.variable, v.valType) } } - default: - // Use variable name as sample value - svalue := fmt.Sprintf("%s", value) - // For list types, repeat it 3 times (arbitrary value) - svalueList := []string{svalue, svalue, svalue} - // Implements https://grafana.com/docs/grafana/latest/variables/advanced-variable-format-options/ - switch format { - case "csv": - return strings.Join(svalueList, ","), nil - case "doublequote": - return "\"" + strings.Join(svalueList, "\",\"") + "\"", nil - case "glob": - return "{" + strings.Join(svalueList, ",") + "}", nil - case "json": - data, err := json.Marshal(svalueList) + globalVariablesInit = true + } + // add template variables to placeholder maps + for _, v := range variables { + if v.Name != "" { + // create placeholder 3 times to account for the 3 different ways a variable can be defined + // at this point, we do not care about the value of the variable, we just need a placeholder for it. + valType := getValueType(getTemplateVariableValue(v)) + createPlaceholder(fmt.Sprintf("$%s", v.Name), valType) + createPlaceholder(fmt.Sprintf("${%s}", v.Name), valType) + createPlaceholder(fmt.Sprintf("[[%s]]", v.Name), valType) + } + } + + expr = variableRegexp.ReplaceAllStringFunc(expr, RegexpExpandVariables) + + // Check if the expression can be parsed + _, err := parser.ParseExpr(expr) + if err != nil { + // not using promql parser error since it contains memory address which is hard to test... + return "", fmt.Errorf("failed to parse expression: %s", expr) + } + + return expr, nil +} + +func revertExpandedVariables(expr string) (string, error) { + for _, p := range placeholderByValue { + if p.valType == valTypeTimeRange { + // Replace all versions of time range placeholder + expr = strings.ReplaceAll(expr, p.value, p.variable) + + // Parse time duration + d, err := model.ParseDuration(p.value + "s") if err != nil { - return "", err + return "", fmt.Errorf("failed to parse duration: %s when reverting expanded variable: %s", p.value, p.variable) } - return string(data), nil - case "lucene": - return "(\"" + strings.Join(svalueList, "\" OR \"") + "\")", nil - case "percentencode": - return url.QueryEscape(strings.Join(svalueList, ",")), nil - case "pipe": - return strings.Join(svalueList, "|"), nil - case "raw": - return strings.Join(svalueList, ","), nil - case "regex": - return strings.Join(svalueList, "|"), nil - case "singlequote": - return "'" + strings.Join(svalueList, "','") + "'", nil - case "sqlstring": - return "'" + strings.Join(svalueList, "','") + "'", nil - case "text": - return strings.Join(svalueList, " + "), nil - case "queryparam": - values := url.Values{} - for _, svalue := range svalueList { - values.Add("var-"+name, svalue) + expr = strings.ReplaceAll(expr, d.String(), p.variable) + + // Parse as float64 + f, err := strconv.ParseFloat(p.value, 64) + if err != nil { + return "", fmt.Errorf("failed to parse float64: %s when reverting expanded variable: %s", p.value, p.variable) } - return values.Encode(), nil - default: - return svalue, nil + expr = strings.ReplaceAll(expr, fmt.Sprint(f), p.variable) + } else { + expr = strings.ReplaceAll(expr, p.value, p.variable) } } + return expr, nil } -func removeVariableByName(name string, variables []Template) []Template { - vars := make([]Template, 0, len(variables)) - for _, v := range variables { - if v.Name == name { - continue +// Should not replace variables inside double quotes +func RegexpExpandVariables(s string) string { + // check if string starts with a double quote + if s[0:1] == `"` { + return s + } + + if strings.Contains(s, ":") { + // check if variable is __from or __to with advanced formatting + if strings.HasPrefix(trimVariableSyntax(s), from) || strings.HasPrefix(trimVariableSyntax(s), to) { + if strings.Count(s, ":") > 2 { + // Should not replace variables with more than 2 colons returning the original string, promql parser will handle the error. + return s + } + return createPlaceholder(s, valTypeEpoch) + } + // check if variable contains more than 1 colon + if strings.Count(s, ":") > 1 { + // Should not replace variables with more than 1 colon returning the original string, promql parser will handle the error. + return s } - vars = append(vars, v) } - return vars + return createPlaceholder(s, valTypeString) } -func variableSampleValue(s string, variables []Template) (string, error) { - var name, kind, format string - parts := strings.Split(s, ":") - switch len(parts) { - case 1: - // No format - name = s - case 2: - // Could be __from:date, variable:csv, ... - name = parts[0] - format = parts[1] - case 3: - // Could be __from:date:iso, ... - name = parts[0] - kind = parts[1] - format = parts[2] - default: - return "", fmt.Errorf("unknown variable format: %s", s) - } - // If it is part of the globals, return a string representation of a sample value - if value, ok := globalVariables[name]; ok { - return stringValue(name, value, kind, format) - } - // If it is an auto interval variable, replace with a sample value of 10s - if strings.HasPrefix(name, "__auto_interval") { - return "10s", nil - } - // If it is a template variable and we have a value, we use it - for _, v := range variables { - if v.Name != name { - continue +// getPlaceholder returns placeholder for a provided variable or value +func getPlaceholder(variable string, value string) *placeholder { + switch { + case variable != "" && value != "": + if p, ok := placeholderByVariable[variable]; ok { + if p.value == value { + return p + } } - // if it has a current value, use it - c, err := v.Current.Get() - if err != nil { - return "", err + case variable != "": + if p, ok := placeholderByVariable[variable]; ok { + return p } - if c.Value != "" { - // Recursively expand, without the current variable to avoid infinite recursion - return expandVariables(c.Value, removeVariableByName(name, variables)) + case value != "": + if p, ok := placeholderByValue[value]; ok { + return p } - // If it has options, use the first option - if len(v.Options) > 0 { - // Recursively expand, without the current variable to avoid infinite recursion - o, err := v.Options[0].Get() - if err != nil { - return "", err + } + return nil +} + +// assignPlaceholder assigns a placeholder to a variable it ensures both placeholderByVariable and placeholderByValue are updated +func assignPlaceholder(placeholder placeholder) error { + if placeholder.variable == "" || placeholder.value == "" { + return fmt.Errorf("variable and value must not be empty") + } + // Check if variable and value combination already exists + if getPlaceholder(placeholder.variable, placeholder.value) != nil { + return nil + } + // check if value already exists but with a different variable + p := getPlaceholder("", placeholder.value) + if p != nil { + if p.variable != placeholder.variable { + return fmt.Errorf("value %s already assigned to variable %s", placeholder.value, p.variable) + } + } + // check if variable already exists but with a different value + p = getPlaceholder(placeholder.variable, "") + if p != nil { + if p.value != placeholder.value { + return fmt.Errorf("variable %s already assigned to value %s", placeholder.variable, p.value) + } + } + // add placeholder to placeholderByVariable + placeholderByVariable[placeholder.variable] = &placeholder + // add placeholder to placeholderByValue + placeholderByValue[placeholder.value] = &placeholder + return nil +} + +func getValueType(value string) valType { + // value might be provided as an integer, so we need to check if it can be parsed as an integer and then add s to the end) + if _, err := strconv.Atoi(value); err == nil { + value = value + "s" + } + // check if variable is a time range + if _, err := model.ParseDuration(value); err == nil { + return valTypeTimeRange + } + // check if variable is epoch, this is used for promql @ modifier + if _, err := strconv.ParseFloat(value, 64); err == nil { + return valTypeEpoch + } + // naive check if variable is a matcher + if strings.Contains(value, "=") && strings.Contains(value, "\"") { + return valTypeMatcher + } + // default to string + return valTypeString +} + +// createPlaceholder returns a placeholder for a variable. +func createPlaceholder(variable string, valType valType) string { + // check if variable already has a placeholder + if p := getPlaceholder(variable, ""); p != nil { + return p.value + } + // create placeholder + counter := 0 + var value string + for { + if valType == valTypeTimeRange { + // Using magicTimeRange as a seed for the placeholder + timeRange := magicTimeRange + counter + value = strconv.Itoa(timeRange) + } + if valType == valTypeEpoch { + // Using magicEpoch as a seed for the placeholder + epoch := magicEpoch + float64(counter) + // trim epoch to 3 decimal places since that is the precision used in prometheus + value = fmt.Sprintf("%.3f", epoch) + } + if valType == valTypeMatcher { + part := fmt.Sprintf("%s_%s_%d", magicString, trimVariableSyntax(variable), counter) + value = fmt.Sprintf(`%s="%s"`, part, part) + } + if valType == valTypeString { + value = fmt.Sprintf("%s_%s_%d", magicString, trimVariableSyntax(variable), counter) + } + + if _, ok := placeholderByValue[value]; !ok { + err := assignPlaceholder(placeholder{variable: variable, valType: valType, value: value}) + if err == nil { + return value } - return expandVariables(o.Value, removeVariableByName(name, variables)) + } + counter++ + if counter > 10000 { + // this should never happen... but just in case... lets panic... + panic("createPlaceholder: counter > 10000 - this should never happen :(") } } - // Assume variable type is a string - return stringValue(name, name, kind, format) } -var variableRegexp = regexp.MustCompile( - strings.Join([]string{ - `\$([[:word:]]+)`, // $var syntax - `\$\{([^}]+)\}`, // ${var} syntax - `\[\[([^\[\]]+)\]\]`, // [[var]] syntax - }, "|"), -) +// Helper func to remove the variable syntax from a string +func trimVariableSyntax(s string) string { + s = strings.TrimPrefix(s, "[[") + s = strings.TrimPrefix(s, "${") + s = strings.TrimPrefix(s, "$") -func expandVariables(expr string, variables []Template) (string, error) { - parts := strings.Split(expr, "\"") - for i, part := range parts { - if i%2 == 1 { - // Inside a double quote string, just add it - continue - } - - // Accumulator to store the processed submatches - var subparts []string - // Cursor indicates where we are in the part being processed - cursor := 0 - for _, v := range variableRegexp.FindAllStringSubmatchIndex(part, -1) { - // Add all until match starts - subparts = append(subparts, part[cursor:v[0]]) - // Iterate on all the subgroups and find the one that matched - for j := 2; j < len(v); j += 2 { - if v[j] < 0 { - continue - } - // Replace the match with sample value - val, err := variableSampleValue(part[v[j]:v[j+1]], variables) - if err != nil { - return "", err - } - subparts = append(subparts, val) + s = strings.TrimSuffix(s, "]]") + s = strings.TrimSuffix(s, "}") + + // replace all ":" with "_" + s = strings.ReplaceAll(s, ":", "_") + + return s +} + +// Helper func to check if string has variable syntax +func checkVariableSyntax(s string) bool { + return strings.Contains(s, "$") || strings.Contains(s, "[[") || strings.Contains(s, "{") +} + +// Helper func to get the value of a template variable +func getTemplateVariableValue(v Template) string { + var value string + // do not handle error + c, _ := v.Current.Get() + // check if variable has a value + if c.Value == "" { + if len(v.Options) > 0 { + // Do not handle error + o, _ := v.Options[0].Get() + if o.Value != "" { + value = o.Value + } + } + } else { + value = c.Value + } + // check value for variable syntax + if checkVariableSyntax(value) { + // lazy way of dealing with __auto_interval... + if strings.HasPrefix(trimVariableSyntax(value), "__auto_interval") { + // This will result in a placeholder with type timeRange + value = "9001s" + } else { + // try to expand variable + varValue := getPlaceholder(value, "") + if varValue != nil { + value = varValue.value } - // Move the start cursor at the end of the current match - cursor = v[1] } - // Add rest of the string - subparts = append(subparts, parts[i][cursor:]) - // Merge all back into the parts - parts[i] = strings.Join(subparts, "") } - return strings.Join(parts, "\""), nil + return value } diff --git a/lint/variables_test.go b/lint/variables_test.go index 1f0e080..495b461 100644 --- a/lint/variables_test.go +++ b/lint/variables_test.go @@ -20,128 +20,133 @@ func TestVariableExpansion(t *testing.T) { expr: "up{job=~\"$job\"}", result: "up{job=~\"$job\"}", }, - // https://grafana.com/docs/grafana/latest/variables/syntax/ + // https: //grafana.com/docs/grafana/latest/variables/syntax/ { desc: "Should replace variables in metric name", expr: "up$var{job=~\"$job\"}", - result: "upvar{job=~\"$job\"}", + result: "upbgludgvy_var_0{job=~\"$job\"}", }, { desc: "Should replace global rate/range variables", - expr: "rate(metric{}[$__rate_interval])", - result: "rate(metric{}[8869990787ms])", + expr: "rate(metric{}[11277964])", + result: "rate(metric{}[11277964])", }, { desc: "Should support ${...} syntax", expr: "rate(metric{}[${__rate_interval}])", - result: "rate(metric{}[8869990787ms])", + result: "rate(metric{}[11277965])", }, { desc: "Should support [[...]] syntax", expr: "rate(metric{}[[[__rate_interval]]])", - result: "rate(metric{}[8869990787ms])", + result: "rate(metric{}[11277966])", }, // https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/ { desc: "Should support ${__user.id}", expr: "sum(http_requests_total{method=\"GET\"} @ ${__user.id})", - result: "sum(http_requests_total{method=\"GET\"} @ 42)", + result: "sum(http_requests_total{method=\"GET\"} @ 1294671549264.000)", }, { desc: "Should support $__from/$__to", expr: "sum(http_requests_total{method=\"GET\"} @ $__from)", - result: "sum(http_requests_total{method=\"GET\"} @ 1594671549254)", + result: "sum(http_requests_total{method=\"GET\"} @ 1294671549254.000)", }, { desc: "Should support $__from/$__to with formatting option (unix seconds)", - expr: "sum(http_requests_total{method=\"GET\"} @ ${__from:date:seconds}000)", - result: "sum(http_requests_total{method=\"GET\"} @ 1594671549000)", + expr: "sum(http_requests_total{method=\"GET\"} @ ${__from:date:seconds})", + result: "sum(http_requests_total{method=\"GET\"} @ 1294671549266.000)", }, { desc: "Should support $__from/$__to with formatting option (iso default)", expr: "sum(http_requests_total{method=\"GET\"} @ ${__from:date})", - result: "sum(http_requests_total{method=\"GET\"} @ 2020-07-13T20:19:09Z)", + result: "sum(http_requests_total{method=\"GET\"} @ 1294671549267.000)", }, { desc: "Should support $__from/$__to with formatting option (iso)", expr: "sum(http_requests_total{method=\"GET\"} @ ${__from:date:iso})", - result: "sum(http_requests_total{method=\"GET\"} @ 2020-07-13T20:19:09Z)", + result: "sum(http_requests_total{method=\"GET\"} @ 1294671549268.000)", }, { - desc: "Should not support $__from/$__to with momentjs formatting option (iso)", - expr: "sum(http_requests_total{method=\"GET\"} @ ${__from:date:YYYY-MM})", - err: fmt.Errorf("Unsupported momentjs time format: YYYY-MM"), + desc: "Should not support $__from/$__to with momentjs formatting option (iso)", + expr: "sum(http_requests_total{method=\"GET\"} @ ${__from:date:YYYY-MM})", + result: "sum(http_requests_total{method=\"GET\"} @ 1294671549269.000)", }, // https://grafana.com/docs/grafana/latest/variables/advanced-variable-format-options/ { desc: "Should support ${variable:csv} syntax", expr: "max by(${variable:csv}) (rate(cpu{}[$__rate_interval]))", - result: "max by(variable,variable,variable) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_csv_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:doublequote} syntax", expr: "max by(${variable:doublequote}) (rate(cpu{}[$__rate_interval]))", - result: "max by(\"variable\",\"variable\",\"variable\") (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_doublequote_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:glob} syntax", expr: "max by(${variable:glob}) (rate(cpu{}[$__rate_interval]))", - result: "max by({variable,variable,variable}) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_glob_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:json} syntax", expr: "max by(${variable:json}) (rate(cpu{}[$__rate_interval]))", - result: "max by([\"variable\",\"variable\",\"variable\"]) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_json_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:lucene} syntax", expr: "max by(${variable:lucene}) (rate(cpu{}[$__rate_interval]))", - result: "max by((\"variable\" OR \"variable\" OR \"variable\")) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_lucene_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:percentencode} syntax", expr: "max by(${variable:percentencode}) (rate(cpu{}[$__rate_interval]))", - result: "max by(variable%2Cvariable%2Cvariable) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_percentencode_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:pipe} syntax", expr: "max by(${variable:pipe}) (rate(cpu{}[$__rate_interval]))", - result: "max by(variable|variable|variable) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_pipe_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:raw} syntax", expr: "max by(${variable:raw}) (rate(cpu{}[$__rate_interval]))", - result: "max by(variable,variable,variable) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_raw_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:regex} syntax", expr: "max by(${variable:regex}) (rate(cpu{}[$__rate_interval]))", - result: "max by(variable|variable|variable) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_regex_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:singlequote} syntax", expr: "max by(${variable:singlequote}) (rate(cpu{}[$__rate_interval]))", - result: "max by('variable','variable','variable') (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_singlequote_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:sqlstring} syntax", expr: "max by(${variable:sqlstring}) (rate(cpu{}[$__rate_interval]))", - result: "max by('variable','variable','variable') (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_sqlstring_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:text} syntax", expr: "max by(${variable:text}) (rate(cpu{}[$__rate_interval]))", - result: "max by(variable + variable + variable) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_text_0) (rate(cpu{}[11277964]))", }, { desc: "Should support ${variable:queryparam} syntax", expr: "max by(${variable:queryparam}) (rate(cpu{}[$__rate_interval]))", - result: "max by(var-variable=variable&var-variable=variable&var-variable=variable) (rate(cpu{}[8869990787ms]))", + result: "max by(bgludgvy_variable_queryparam_0) (rate(cpu{}[11277964]))", + }, + { + desc: "Should support using variables for multiplication", + expr: "sum(rate(foo[$__rate_interval])) * $__range_s", + result: "sum(rate(foo[11277964])) * 11277976", }, { desc: "Should return an error for unknown syntax", expr: "max by(${a:b:c:d}) (rate(cpu{}[$__rate_interval]))", - err: fmt.Errorf("unknown variable format: a:b:c:d"), + err: fmt.Errorf("failed to parse expression: max by(${a:b:c:d}) (rate(cpu{}[11277964]))"), }, { desc: "Should replace variables present in the templating", @@ -155,21 +160,31 @@ func TestVariableExpansion(t *testing.T) { }, }, }, + { + Name: "sampling", + Options: []RawTemplateValue{ + map[string]interface{}{ + "value": "1h", + }, + }, + }, { Name: "resolution", Options: []RawTemplateValue{ map[string]interface{}{ "value": "5m", }, - }}, + }, + }, { Name: "var", Type: "query", Current: map[string]interface{}{ "value": "value", - }}, + }, + }, }, - result: "max by(value) (rate(cpu{}[4h:5m]))", + result: "max by(bgludgvy_var_0) (rate(cpu{}[11277982:11277988]))", }, { desc: "Should recursively replace variables", @@ -177,7 +192,7 @@ func TestVariableExpansion(t *testing.T) { variables: []Template{ {Name: "interval", Current: map[string]interface{}{"value": "$__auto_interval_interval"}}, }, - result: "sum (rate(cpu{}[10s]))", + result: "sum (rate(cpu{}[11277982]))", }, { desc: "Should support plain $__auto_interval, generated by grafonnet-lib (https://github.com/grafana/grafonnet-lib/blob/master/grafonnet/template.libsonnet#L100)", @@ -185,15 +200,7 @@ func TestVariableExpansion(t *testing.T) { variables: []Template{ {Name: "interval", Current: map[string]interface{}{"value": "$__auto_interval"}}, }, - result: "sum (rate(cpu{}[10s]))", - }, - { - desc: "Should recursively replace variables, but not run into an infinite loop", - expr: "sum (rate(cpu{}[$interval]))", - variables: []Template{ - {Name: "interval", Current: map[string]interface{}{"value": "$interval"}}, - }, - result: "sum (rate(cpu{}[interval]))", + result: "sum (rate(cpu{}[11277982]))", }, } { s, err := expandVariables(tc.expr, tc.variables) @@ -201,3 +208,243 @@ func TestVariableExpansion(t *testing.T) { require.Equal(t, tc.result, s, tc.desc) } } + +func TestReverseVariableExpansion(t *testing.T) { + placeholderByValue = map[string]*placeholder{ + "11277964": {variable: "$__rate_interval", valType: 1, value: "11277964"}, + "11277965": {variable: "${__rate_interval}", valType: 1, value: "11277965"}, + "11277966": {variable: "[[__rate_interval]]", valType: 1, value: "11277966"}, + "11277967": {variable: "$__interval", valType: 1, value: "11277967"}, + "11277968": {variable: "${__interval}", valType: 1, value: "11277968"}, + "11277969": {variable: "[[__interval]]", valType: 1, value: "11277969"}, + "11277970": {variable: "$__interval_ms", valType: 1, value: "11277970"}, + "11277971": {variable: "${__interval_ms}", valType: 1, value: "11277971"}, + "11277972": {variable: "[[__interval_ms]]", valType: 1, value: "11277972"}, + "11277973": {variable: "$__range_ms", valType: 1, value: "11277973"}, + "11277974": {variable: "${__range_ms}", valType: 1, value: "11277974"}, + "11277975": {variable: "[[__range_ms]]", valType: 1, value: "11277975"}, + "11277976": {variable: "$__range_s", valType: 1, value: "11277976"}, + "11277977": {variable: "${__range_s}", valType: 1, value: "11277977"}, + "11277978": {variable: "[[__range_s]]", valType: 1, value: "11277978"}, + "11277979": {variable: "$__range", valType: 1, value: "11277979"}, + "11277980": {variable: "${__range}", valType: 1, value: "11277980"}, + "11277981": {variable: "[[__range]]", valType: 1, value: "11277981"}, + "11277982": {variable: "$interval", valType: 1, value: "11277982"}, + "11277983": {variable: "${interval}", valType: 1, value: "11277983"}, + "11277984": {variable: "[[interval]]", valType: 1, value: "11277984"}, + "11277985": {variable: "$sampling", valType: 1, value: "11277985"}, + "11277986": {variable: "${sampling}", valType: 1, value: "11277986"}, + "11277987": {variable: "[[sampling]]", valType: 1, value: "11277987"}, + "11277988": {variable: "$resolution", valType: 1, value: "11277988"}, + "11277989": {variable: "${resolution}", valType: 1, value: "11277989"}, + "11277990": {variable: "[[resolution]]", valType: 1, value: "11277990"}, + "1294671549254.000": {variable: "$__from", valType: 2, value: "1294671549254.000"}, + "1294671549255.000": {variable: "${__from}", valType: 2, value: "1294671549255.000"}, + "1294671549256.000": {variable: "[[__from]]", valType: 2, value: "1294671549256.000"}, + "1294671549257.000": {variable: "$__to", valType: 2, value: "1294671549257.000"}, + "1294671549258.000": {variable: "${__to}", valType: 2, value: "1294671549258.000"}, + "1294671549259.000": {variable: "[[__to]]", valType: 2, value: "1294671549259.000"}, + "1294671549260.000": {variable: "$__org", valType: 2, value: "1294671549260.000"}, + "1294671549261.000": {variable: "${__org}", valType: 2, value: "1294671549261.000"}, + "1294671549262.000": {variable: "[[__org]]", valType: 2, value: "1294671549262.000"}, + "1294671549263.000": {variable: "$__user.id", valType: 2, value: "1294671549263.000"}, + "1294671549264.000": {variable: "${__user.id}", valType: 2, value: "1294671549264.000"}, + "1294671549265.000": {variable: "[[__user.id]]", valType: 2, value: "1294671549265.000"}, + "1294671549266.000": {variable: "${__from:date:seconds}", valType: 2, value: "1294671549266.000"}, + "1294671549267.000": {variable: "${__from:date}", valType: 2, value: "1294671549267.000"}, + "1294671549268.000": {variable: "${__from:date:iso}", valType: 2, value: "1294671549268.000"}, + "1294671549269.000": {variable: "${__from:date:YYYY-MM}", valType: 2, value: "1294671549269.000"}, + "bgludgvy___dashboard_0": {variable: "$__dashboard", valType: 0, value: "bgludgvy___dashboard_0"}, + "bgludgvy___dashboard_1": {variable: "${__dashboard}", valType: 0, value: "bgludgvy___dashboard_1"}, + "bgludgvy___dashboard_2": {variable: "[[__dashboard]]", valType: 0, value: "bgludgvy___dashboard_2"}, + "bgludgvy___name_0": {variable: "$__name", valType: 0, value: "bgludgvy___name_0"}, + "bgludgvy___name_1": {variable: "${__name}", valType: 0, value: "bgludgvy___name_1"}, + "bgludgvy___name_2": {variable: "[[__name]]", valType: 0, value: "bgludgvy___name_2"}, + "bgludgvy___org.name_0": {variable: "$__org.name", valType: 0, value: "bgludgvy___org.name_0"}, + "bgludgvy___org.name_1": {variable: "${__org.name}", valType: 0, value: "bgludgvy___org.name_1"}, + "bgludgvy___org.name_2": {variable: "[[__org.name]]", valType: 0, value: "bgludgvy___org.name_2"}, + "bgludgvy___timeFilter_0": {variable: "$__timeFilter", valType: 0, value: "bgludgvy___timeFilter_0"}, + "bgludgvy___timeFilter_1": {variable: "${__timeFilter}", valType: 0, value: "bgludgvy___timeFilter_1"}, + "bgludgvy___timeFilter_2": {variable: "[[__timeFilter]]", valType: 0, value: "bgludgvy___timeFilter_2"}, + "bgludgvy___user.email_0": {variable: "$__user.email", valType: 0, value: "bgludgvy___user.email_0"}, + "bgludgvy___user.email_1": {variable: "${__user.email}", valType: 0, value: "bgludgvy___user.email_1"}, + "bgludgvy___user.email_2": {variable: "[[__user.email]]", valType: 0, value: "bgludgvy___user.email_2"}, + "bgludgvy___user.login_0": {variable: "$__user.login", valType: 0, value: "bgludgvy___user.login_0"}, + "bgludgvy___user.login_1": {variable: "${__user.login}", valType: 0, value: "bgludgvy___user.login_1"}, + "bgludgvy___user.login_2": {variable: "[[__user.login]]", valType: 0, value: "bgludgvy___user.login_2"}, + "bgludgvy_namespaces_0": {variable: "$namespaces", valType: 0, value: "bgludgvy_namespaces_0"}, + "bgludgvy_namespaces_1": {variable: "${namespaces}", valType: 0, value: "bgludgvy_namespaces_1"}, + "bgludgvy_namespaces_2": {variable: "[[namespaces]]", valType: 0, value: "bgludgvy_namespaces_2"}, + "bgludgvy_timeFilter_0": {variable: "$timeFilter", valType: 0, value: "bgludgvy_timeFilter_0"}, + "bgludgvy_timeFilter_1": {variable: "${timeFilter}", valType: 0, value: "bgludgvy_timeFilter_1"}, + "bgludgvy_timeFilter_2": {variable: "[[timeFilter]]", valType: 0, value: "bgludgvy_timeFilter_2"}, + "bgludgvy_var_0": {variable: "$var", valType: 0, value: "bgludgvy_var_0"}, + "bgludgvy_var_1": {variable: "${var}", valType: 0, value: "bgludgvy_var_1"}, + "bgludgvy_var_2": {variable: "[[var]]", valType: 0, value: "bgludgvy_var_2"}, + "bgludgvy_variable_csv_0": {variable: "${variable:csv}", valType: 0, value: "bgludgvy_variable_csv_0"}, + "bgludgvy_variable_doublequote_0": {variable: "${variable:doublequote}", valType: 0, value: "bgludgvy_variable_doublequote_0"}, + "bgludgvy_variable_glob_0": {variable: "${variable:glob}", valType: 0, value: "bgludgvy_variable_glob_0"}, + "bgludgvy_variable_json_0": {variable: "${variable:json}", valType: 0, value: "bgludgvy_variable_json_0"}, + "bgludgvy_variable_lucene_0": {variable: "${variable:lucene}", valType: 0, value: "bgludgvy_variable_lucene_0"}, + "bgludgvy_variable_percentencode_0": {variable: "${variable:percentencode}", valType: 0, value: "bgludgvy_variable_percentencode_0"}, + "bgludgvy_variable_pipe_0": {variable: "${variable:pipe}", valType: 0, value: "bgludgvy_variable_pipe_0"}, + "bgludgvy_variable_queryparam_0": {variable: "${variable:queryparam}", valType: 0, value: "bgludgvy_variable_queryparam_0"}, + "bgludgvy_variable_raw_0": {variable: "${variable:raw}", valType: 0, value: "bgludgvy_variable_raw_0"}, + "bgludgvy_variable_regex_0": {variable: "${variable:regex}", valType: 0, value: "bgludgvy_variable_regex_0"}, + "bgludgvy_variable_singlequote_0": {variable: "${variable:singlequote}", valType: 0, value: "bgludgvy_variable_singlequote_0"}, + "bgludgvy_variable_sqlstring_0": {variable: "${variable:sqlstring}", valType: 0, value: "bgludgvy_variable_sqlstring_0"}, + "bgludgvy_variable_text_0": {variable: "${variable:text}", valType: 0, value: "bgludgvy_variable_text_0"}, + } + for _, tc := range []struct { + desc string + expr string + result string + }{ + { + desc: "Should not replace variables in quoted strings", + expr: "up{job=~\"$job\"}", + result: "up{job=~\"$job\"}", + }, + // https: //grafana.com/docs/grafana/latest/variables/syntax/ + { + desc: "Should replace variables in metric name", + expr: "upbgludgvy_var_0{job=~\"$job\"}", + result: "up$var{job=~\"$job\"}", + }, + { + desc: "Should replace global rate/range variables", + expr: "rate(metric{}[130d12h46m4s])", + result: "rate(metric{}[$__rate_interval])", + }, + { + desc: "Should support ${...} syntax", + expr: "rate(metric{}[130d12h46m5s])", + result: "rate(metric{}[${__rate_interval}])", + }, + { + desc: "Should support [[...]] syntax", + expr: "rate(metric{}[130d12h46m6s])", + result: "rate(metric{}[[[__rate_interval]]])", + }, + // https://grafana.com/docs/grafana/latest/variables/variable-types/global-variables/ + { + desc: "Should support ${__user.id}", + expr: "sum(http_requests_total{method=\"GET\"} @ 1294671549264.000)", + result: "sum(http_requests_total{method=\"GET\"} @ ${__user.id})", + }, + { + desc: "Should support $__from/$__to", + expr: "sum(http_requests_total{method=\"GET\"} @ 1294671549254.000)", + result: "sum(http_requests_total{method=\"GET\"} @ $__from)", + }, + { + desc: "Should support $__from/$__to with formatting option (unix seconds)", + expr: "sum(http_requests_total{method=\"GET\"} @ 1294671549266.000)", + result: "sum(http_requests_total{method=\"GET\"} @ ${__from:date:seconds})", + }, + { + desc: "Should support $__from/$__to with formatting option (iso default)", + expr: "sum(http_requests_total{method=\"GET\"} @ 1294671549267.000)", + result: "sum(http_requests_total{method=\"GET\"} @ ${__from:date})", + }, + { + desc: "Should support $__from/$__to with formatting option (iso)", + expr: "sum(http_requests_total{method=\"GET\"} @ 1294671549268.000)", + result: "sum(http_requests_total{method=\"GET\"} @ ${__from:date:iso})", + }, + { + desc: "Should not support $__from/$__to with momentjs formatting option (iso)", + expr: "sum(http_requests_total{method=\"GET\"} @ 1294671549269.000)", + result: "sum(http_requests_total{method=\"GET\"} @ ${__from:date:YYYY-MM})", + }, + // https://grafana.com/docs/grafana/latest/variables/advanced-variable-format-options/ + { + desc: "Should support ${variable:csv} syntax", + expr: "max by(bgludgvy_variable_csv_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:csv}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:doublequote} syntax", + expr: "max by(bgludgvy_variable_doublequote_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:doublequote}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:glob} syntax", + expr: "max by(bgludgvy_variable_glob_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:glob}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:json} syntax", + expr: "max by(bgludgvy_variable_json_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:json}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:lucene} syntax", + expr: "max by(bgludgvy_variable_lucene_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:lucene}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:percentencode} syntax", + expr: "max by(bgludgvy_variable_percentencode_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:percentencode}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:pipe} syntax", + expr: "max by(bgludgvy_variable_pipe_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:pipe}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:raw} syntax", + expr: "max by(bgludgvy_variable_raw_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:raw}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:regex} syntax", + expr: "max by(bgludgvy_variable_regex_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:regex}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:singlequote} syntax", + expr: "max by(bgludgvy_variable_singlequote_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:singlequote}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:sqlstring} syntax", + expr: "max by(bgludgvy_variable_sqlstring_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:sqlstring}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:text} syntax", + expr: "max by(bgludgvy_variable_text_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:text}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should support ${variable:queryparam} syntax", + expr: "max by(bgludgvy_variable_queryparam_0) (rate(cpu{}[130d12h46m4s]))", + result: "max by(${variable:queryparam}) (rate(cpu{}[$__rate_interval]))", + }, + { + desc: "Should replace variables present in the templating", + expr: "max by(bgludgvy_var_0) (rate(cpu{}[130d12h46m22s:130d12h46m28s]))", + result: "max by($var) (rate(cpu{}[$interval:$resolution]))", + }, + { + desc: "Should support using variables for multiplication", + expr: "sum(rate(foo[130d12h46m4s])) * 11277976", + result: "sum(rate(foo[$__rate_interval])) * $__range_s", + }, + { + desc: "Should recursively replace variables", + expr: "sum (rate(cpu{}[130d12h46m22s]))", + result: "sum (rate(cpu{}[$interval]))", + }, + { + desc: "Should support plain $__auto_interval, generated by grafonnet-lib (https://github.com/grafana/grafonnet-lib/blob/master/grafonnet/template.libsonnet#L100)", + expr: "sum (rate(cpu{}[130d12h46m22s]))", + result: "sum (rate(cpu{}[$interval]))", + }, + } { + s, _ := revertExpandedVariables(tc.expr) + require.Equal(t, tc.result, s, tc.desc) + } +} diff --git a/main.go b/main.go index d85e7a3..20f0b1f 100644 --- a/main.go +++ b/main.go @@ -14,11 +14,14 @@ import ( "github.com/grafana/dashboard-linter/lint" ) -var lintStrictFlag bool -var lintVerboseFlag bool -var lintAutofixFlag bool -var lintReadFromStdIn bool -var lintConfigFlag string +var ( + lintStrictFlag bool + lintVerboseFlag bool + lintAutofixFlag bool + lintReadFromStdIn bool + lintConfigFlag string + ExperimentalFlag bool +) // lintCmd represents the lint command var lintCmd = &cobra.Command{ @@ -69,7 +72,7 @@ var lintCmd = &cobra.Command{ config.Verbose = lintVerboseFlag config.Autofix = lintAutofixFlag - rules := lint.NewRuleSet() + rules := lint.NewRuleSet(ExperimentalFlag, config.RuleSettings) results, err := rules.Lint([]lint.Dashboard{dashboard}) if err != nil { return fmt.Errorf("failed to lint dashboard: %v", err) @@ -119,9 +122,11 @@ var rulesCmd = &cobra.Command{ Short: "Print documentation about each lint rule.", SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - rules := lint.NewRuleSet() + rules := lint.NewRuleSet(ExperimentalFlag, lint.ConfigurationRuleSettings{}) + fmt.Fprintf(os.Stdout, "%-40s %-15s %s\n", "Rule Name", "Stability", "Description") + fmt.Fprintf(os.Stdout, "%-40s %-15s %s\n", "---------", "---------", "-----------") for _, rule := range rules.Rules() { - fmt.Fprintf(os.Stdout, "* `%s` - %s\n", rule.Name(), rule.Description()) + fmt.Fprintf(os.Stdout, "%-40s %-15s %s\n", rule.Name(), rule.Stability(), rule.Description()) } return nil }, @@ -129,7 +134,6 @@ var rulesCmd = &cobra.Command{ func init() { rootCmd.AddCommand(lintCmd) - rootCmd.AddCommand(rulesCmd) lintCmd.Flags().BoolVar( &lintStrictFlag, "strict", @@ -161,6 +165,19 @@ func init() { false, "read from stdin", ) + lintCmd.Flags().BoolVar( + &ExperimentalFlag, + "experimental", + false, + "enable experimental rules", + ) + rootCmd.AddCommand(rulesCmd) + rulesCmd.Flags().BoolVar( + &ExperimentalFlag, + "experimental", + false, + "enable experimental rules", + ) } var rootCmd = &cobra.Command{