From 2fdce409e157a44fbb584fcc90495dec00d370e9 Mon Sep 17 00:00:00 2001 From: Martin Chodur Date: Thu, 31 Oct 2024 23:37:51 +0100 Subject: [PATCH 1/2] feat: add support for UTF-8 and new UTF-8 related validators fixes https://github.com/FUSAKLA/promruval/issues/88 Signed-off-by: Martin Chodur --- CHANGELOG.md | 4 + docs/default_validation.yaml | 2 + docs/validations.md | 11 +++ examples/rules/rules.yaml | 7 ++ examples/validation.yaml | 1 + go.mod | 6 +- go.sum | 43 ++++++++--- pkg/validator/config.go | 2 + pkg/validator/others.go | 88 ++++++++++++++++++++++ pkg/validator/promql_expression_helpers.go | 3 + pkg/validator/validator_test.go | 15 ++++ 11 files changed, 169 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1be9866..67cf78b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +- Change: :warning: Enable UTF-8 support in metic and label names by default. To disallow usage of UTF-8 characters in metric and label names use the new validator [`doesNotUseUTF8`](./docs/validations.md#doesnotuseutf8). +- Added: new validator [`doesNotUseUTF8`](./docs/validations.md#doesnotuseemoji) to check if the metric and label names do not use UTF-8 characters. +- Added: new validator [`doesNotUseEmoji`](./docs/validations.md#doesnotuseemoji) to check if the metric and label names or values does not contain emojis 💩. +- Changed: Upgraded Prometheus dependencies to v2.55.0 ## [3.5.0] - 2024-10-31 - Added: New validation `expressionDoesNotUseClassicHistogramBucketOperations` to avoid queries fragile because of the classic histogram bucket operations. diff --git a/docs/default_validation.yaml b/docs/default_validation.yaml index 9ef2722..1c26a04 100644 --- a/docs/default_validation.yaml +++ b/docs/default_validation.yaml @@ -40,3 +40,5 @@ validationRules: - type: expressionDoesNotUseOlderDataThan params: limit: "4w" + - type: doesNotUseUTF8 + - type: doesNotUseEmoji diff --git a/docs/validations.md b/docs/validations.md index 1f44395..5946b8b 100644 --- a/docs/validations.md +++ b/docs/validations.md @@ -45,6 +45,8 @@ All the supported validations are listed here. The validations are grouped by th - [`logQlExpressionUsesRangeAggregation`](#logqlexpressionusesrangeaggregation) - [Other](#other) - [`hasSourceTenantsForMetrics`](#hassourcetenantsformetrics) + - [`doesNotUseEmoji`](#doesnotuseemoji) + - [`doesNotUseUTF8`](#doesnotuseutf8) - [Alert validators](#alert-validators) - [Labels](#labels-1) - [`validateLabelTemplates`](#validatelabeltemplates) @@ -428,6 +430,15 @@ params: # description: "Metrics from Kafka" ``` +#### `doesNotUseEmoji` + +Fails if the rule contains any emoji characters (expression, label names/values, recording rule metric name, ...). + +#### `doesNotUseUTF8` + +Fails if the rule contains any label names or metric names which does not follow the legacy non UTF-8 character set. +See the UTF-8 support proposal [here](https://github.com/prometheus/proposals/blob/main/proposals/2023-08-21-utf8.md). + ## Alert validators Validators that can be used on `Alert` scope. diff --git a/examples/rules/rules.yaml b/examples/rules/rules.yaml index 1945cce..3b2a24a 100644 --- a/examples/rules/rules.yaml +++ b/examples/rules/rules.yaml @@ -67,3 +67,10 @@ groups: annotations: title: test alert playbook: http://foo.bar/nonexisting/playbook + + # ignore_validations: hasLabels,expressionUsesExistingLabels,hasLabels,hasAnyOfAnnotations,hasAnnotations + - name: testUTF8 + limit: 10 + rules: + - alert: test + expr: sum by ("foo.bar") (up) diff --git a/examples/validation.yaml b/examples/validation.yaml index 7216b54..e32cb29 100644 --- a/examples/validation.yaml +++ b/examples/validation.yaml @@ -105,6 +105,7 @@ validationRules: showExpectedForm: true skipExpressionsWithComments: true - type: expressionDoesNotUseClassicHistogramBucketOperations + - type: doesNotUseEmoji - name: check-recording-rules scope: Recording rule diff --git a/go.mod b/go.mod index 7e5ff8b..df8c2d6 100644 --- a/go.mod +++ b/go.mod @@ -78,6 +78,7 @@ require ( github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pires/go-proxyproto v0.7.0 // indirect github.com/prometheus/exporter-toolkit v0.13.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/sercand/kuberesolver/v5 v5.1.1 // indirect @@ -106,8 +107,6 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect google.golang.org/grpc v1.67.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/client-go v0.30.2 // indirect - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) @@ -118,6 +117,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dennwc/varint v1.0.0 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/forPelevin/gomoji v1.2.0 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.2 // indirect @@ -134,7 +134,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/prometheus/prometheus v0.54.1 + github.com/prometheus/prometheus v0.55.0 go.opentelemetry.io/otel v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect go.opentelemetry.io/otel/trace v1.30.0 // indirect diff --git a/go.sum b/go.sum index 2547f4e..3f58de7 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,15 @@ +cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= +cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= +cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 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/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.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= @@ -45,8 +52,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -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/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/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= @@ -94,6 +101,8 @@ github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLg github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/forPelevin/gomoji v1.2.0 h1:9k4WVSSkE1ARO/BWywxgEUBvR/jMnao6EZzrql5nxJ8= +github.com/forPelevin/gomoji v1.2.0/go.mod h1:8+Z3KNGkdslmeGZBC3tCrwMrcPy5GRzAD+gL9NAwMXg= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= @@ -127,6 +136,8 @@ github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci 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/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +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/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= @@ -146,8 +157,12 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g= github.com/google/go-jsonnet v0.20.0/go.mod h1:VbgWF9JX7ztlv770x/TolZNGGFfiHEVx9G6ca2eUmeA= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= 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.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grafana/dskit v0.0.0-20241002104024-b69ac1b95024 h1:RflXv1xMlnqP0zr1apM3lJ0BK5SwvEOGLv980V+oGJ0= @@ -343,8 +358,10 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 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/prometheus/prometheus v0.55.0 h1:ITinOi1zr3HemoVWHf679PfRRmpxZOcR4nEvsze6eB0= +github.com/prometheus/prometheus v0.55.0/go.mod h1:GGS7QlWKCqCbcEzWsVahYIfQwiGhcExkarHyLJTsv6I= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -400,8 +417,12 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSf go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/collector/pdata v1.16.0 h1:g02K8jlRnmQ7TQDuXpdgVL6vIxIVqr5Gbb1qIR27rto= go.opentelemetry.io/collector/pdata v1.16.0/go.mod h1:YZZJIt2ehxosYf/Y1pbvexjNWsIGNNrzzlCTO9jC1F4= +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.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= @@ -517,6 +538,8 @@ gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJ gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.195.0 h1:Ude4N8FvTKnnQJHU48RFI40jOBgIrL8Zqr3/QeST6yU= +google.golang.org/api v0.195.0/go.mod h1:DOGRWuv3P8TU8Lnz7uQc4hyNqrBpMtD9ppW3wBJurgc= google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA= google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE= @@ -546,10 +569,10 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= -k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= -k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= +k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= +k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= diff --git a/pkg/validator/config.go b/pkg/validator/config.go index 76b472f..a97750a 100644 --- a/pkg/validator/config.go +++ b/pkg/validator/config.go @@ -46,6 +46,8 @@ var registeredUniversalRuleValidators = map[string]validatorCreator{ // Other "hasSourceTenantsForMetrics": newHasSourceTenantsForMetrics, + "doesNotUseEmoji": newDoesNotUseEmoji, + "doesNotUseUTF8": newDoesNotUseUTF8, } var registeredRecordingRuleValidators = map[string]validatorCreator{ diff --git a/pkg/validator/others.go b/pkg/validator/others.go index a2dd14b..481b1c0 100644 --- a/pkg/validator/others.go +++ b/pkg/validator/others.go @@ -5,8 +5,10 @@ import ( "regexp" "strings" + "github.com/forPelevin/gomoji" "github.com/fusakla/promruval/v3/pkg/prometheus" "github.com/fusakla/promruval/v3/pkg/unmarshaler" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/rulefmt" "golang.org/x/exp/slices" "gopkg.in/yaml.v3" @@ -108,3 +110,89 @@ func (h hasSourceTenantsForMetrics) Validate(group unmarshaler.RuleGroup, rule r } return errs } + +func newDoesNotUseEmoji(_ yaml.Node) (Validator, error) { + return &doesNotUseEmoji{}, nil +} + +type doesNotUseEmoji struct{} + +func (h doesNotUseEmoji) String() string { + return "fails if any rule uses an emoji in metric name or labels" +} + +func (h doesNotUseEmoji) Validate(_ unmarshaler.RuleGroup, rule rulefmt.Rule, _ *prometheus.Client) []error { + var errs []error + for k, v := range rule.Labels { + if gomoji.ContainsEmoji(k) { + errs = append(errs, fmt.Errorf("rule uses label named `%s` with emoji", k)) + } + if gomoji.ContainsEmoji(v) { + errs = append(errs, fmt.Errorf("rule uses label with value `%s` containing emoji", v)) + } + } + usedMetrics, err := getExpressionMetrics(rule.Expr) + if err != nil { + errs = append(errs, err) + return errs + } + if gomoji.ContainsEmoji(rule.Record) { + errs = append(errs, fmt.Errorf("recording rule metric name contains emoji `%s`", rule.Record)) + } + for _, m := range usedMetrics { + if gomoji.ContainsEmoji(m.Name) { + errs = append(errs, fmt.Errorf("rule uses metric `%s` with emoji", m.Name)) + } + if m.VectorSelector != nil { + for _, l := range m.VectorSelector.LabelMatchers { + if gomoji.ContainsEmoji(l.Name) { + errs = append(errs, fmt.Errorf("expression uses label `%s` with emoji", l.Value)) + } + if gomoji.ContainsEmoji(l.Value) { + errs = append(errs, fmt.Errorf("expression uses label with value `%s` containing emoji", l.Value)) + } + } + } + } + return errs +} + +func newDoesNotUseUTF8(_ yaml.Node) (Validator, error) { + return &doesNotUseEmoji{}, nil +} + +type doesNotUseUTF8 struct{} + +func (h doesNotUseUTF8) String() string { + return "fails if any rule uses UTF-8 characters in metric name or labels" +} + +func (h doesNotUseUTF8) Validate(_ unmarshaler.RuleGroup, rule rulefmt.Rule, _ *prometheus.Client) []error { + var errs []error + for k := range rule.Labels { + if !model.LabelName(k).IsValidLegacy() { + errs = append(errs, fmt.Errorf("rule uses label named `%s` with UTF-8 characters", k)) + } + } + usedMetrics, err := getExpressionMetrics(rule.Expr) + if err != nil { + errs = append(errs, err) + return errs + } + if rule.Record != "" && !model.IsValidLegacyMetricName(rule.Record) { + errs = append(errs, fmt.Errorf("recording rule metric name contains UTF-8 characters `%s`", rule.Record)) + } + for _, m := range usedMetrics { + if !model.IsValidLegacyMetricName(m.Name) { + errs = append(errs, fmt.Errorf("rule uses metric `%s` with UTF-8 characters", m.Name)) + } + if m.VectorSelector != nil { + for _, l := range m.VectorSelector.LabelMatchers { + if !model.LabelName(l.Name).IsValidLegacy() { + errs = append(errs, fmt.Errorf("expression uses label `%s` with UTF-8 characters", l.Value)) + } + } + } + } + return errs +} diff --git a/pkg/validator/promql_expression_helpers.go b/pkg/validator/promql_expression_helpers.go index ced96ac..9155e4d 100644 --- a/pkg/validator/promql_expression_helpers.go +++ b/pkg/validator/promql_expression_helpers.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" + "github.com/prometheus/common/model" "github.com/prometheus/prometheus/model/labels" "github.com/prometheus/prometheus/promql/parser" "golang.org/x/exp/slices" @@ -12,6 +13,8 @@ import ( func init() { // Enable experimental functions in promql parser. parser.EnableExperimentalFunctions = true + // Enable UTF-8 support + model.NameValidationScheme = model.UTF8Validation } const metricNameLabel = "__name__" diff --git a/pkg/validator/validator_test.go b/pkg/validator/validator_test.go index 2b40d1d..de15403 100644 --- a/pkg/validator/validator_test.go +++ b/pkg/validator/validator_test.go @@ -328,6 +328,21 @@ var testCases = []struct { {name: "expressionDoesNotUseOperationsBetweenClassicHistogramBuckets_valid", validator: expressionDoesNotUseOperationsBetweenClassicHistogramBuckets{}, rule: rulefmt.Rule{Expr: `foo_bucket{le="+Inf"} - bar_bucket{le="1"}`}, expectedErrors: 0}, {name: "expressionDoesNotUseOperationsBetweenClassicHistogramBuckets_invalid", validator: expressionDoesNotUseOperationsBetweenClassicHistogramBuckets{}, rule: rulefmt.Rule{Expr: `request_duration_seconds_bucket{le="+Inf"} - ignoring(le) request_duration_seconds_bucket{le="1"}`}, expectedErrors: 1}, {name: "expressionDoesNotUseOperationsBetweenClassicHistogramBuckets_complicated_valid", validator: expressionDoesNotUseOperationsBetweenClassicHistogramBuckets{}, rule: rulefmt.Rule{Expr: `(request_duration_seconds_bucket{app="foo", le="+Inf"} * up{app="foo"}) - ignoring(le) request_duration_seconds_bucket{le="1"}`}, expectedErrors: 0}, + + // doesNotUseEmoji + {name: "doesNotUseEmoji_valid", validator: doesNotUseEmoji{}, rule: rulefmt.Rule{Expr: `foo_bar{emoji="foo"}`}, expectedErrors: 0}, + {name: "doesNotUseEmoji_expr_label_value_poop", validator: doesNotUseEmoji{}, rule: rulefmt.Rule{Expr: `foo_bar{emoji="💩"}`}, expectedErrors: 1}, + {name: "doesNotUseEmoji_expr_label_name_poop", validator: doesNotUseEmoji{}, rule: rulefmt.Rule{Expr: `foo_bar{"💩"="foo"}`}, expectedErrors: 1}, + {name: "doesNotUseEmoji_rule_label_value_poop", validator: doesNotUseEmoji{}, rule: rulefmt.Rule{Expr: "1", Labels: map[string]string{"foo": "💩"}}, expectedErrors: 1}, + {name: "doesNotUseEmoji_rule_label_name_poop", validator: doesNotUseEmoji{}, rule: rulefmt.Rule{Expr: "1", Labels: map[string]string{"💩": "foo"}}, expectedErrors: 1}, + {name: "doesNotUseEmoji_rule_record_poop", validator: doesNotUseEmoji{}, rule: rulefmt.Rule{Record: "foo:💩:bar", Expr: "1"}, expectedErrors: 1}, + + // doesNotUseUTF8 + {name: "doesNotUseUTF8_valid", validator: doesNotUseUTF8{}, rule: rulefmt.Rule{Expr: `foo_bar{foo="bar"}`}, expectedErrors: 0}, + {name: "doesNotUseUTF8_invalid_label_name", validator: doesNotUseUTF8{}, rule: rulefmt.Rule{Expr: `foo_bar{"žluťoulinký"="bar"}`}, expectedErrors: 1}, + {name: "doesNotUseUTF8_invalid_metric_name", validator: doesNotUseUTF8{}, rule: rulefmt.Rule{Expr: `{"žluťoulinký",foo="bar"}`}, expectedErrors: 1}, + {name: "doesNotUseUTF8_invalid_rule_label_name", validator: doesNotUseUTF8{}, rule: rulefmt.Rule{Expr: `1`, Labels: map[string]string{"žluťoulinký": "foo"}}, expectedErrors: 1}, + {name: "doesNotUseUTF8_invalid_recorded_metric_name", validator: doesNotUseUTF8{}, rule: rulefmt.Rule{Expr: `1`, Record: "foo:žluťoulinký:bar"}, expectedErrors: 1}, } func Test(t *testing.T) { From 1030bcdf5f98393e6a204845c8408e04ffb92e57 Mon Sep 17 00:00:00 2001 From: Martin Chodur Date: Thu, 31 Oct 2024 23:45:28 +0100 Subject: [PATCH 2/2] fix: e2e tests Signed-off-by: Martin Chodur --- examples/human_readable.html | 1 + examples/human_readable.md | 1 + examples/rules/rules.yaml | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/human_readable.html b/examples/human_readable.html index fd200b5..7bbe23b 100644 --- a/examples/human_readable.html +++ b/examples/human_readable.html @@ -56,6 +56,7 @@

check-formatting

  • All rules expression is well formatted as would promtool promql format do or similar online tool such as https://o11y.tools/promqlparser/
  • All rules expression does not do any binary operations between histogram buckets, it can be dangerous because of inconsistency in the data if sent over remote write for example
  • +
  • All rules fails if any rule uses an emoji in metric name or labels

check-recording-rules

diff --git a/examples/human_readable.md b/examples/human_readable.md index 31a221a..7ddf9da 100644 --- a/examples/human_readable.md +++ b/examples/human_readable.md @@ -41,6 +41,7 @@ Validation rules: check-formatting - All rules expression is well formatted as would `promtool promql format` do or similar online tool such as https://o11y.tools/promqlparser/ - All rules expression does not do any binary operations between histogram buckets, it can be dangerous because of inconsistency in the data if sent over remote write for example + - All rules fails if any rule uses an emoji in metric name or labels check-recording-rules - Recording rule Recorded metric name does not match regexp: ^foo_bar$ diff --git a/examples/rules/rules.yaml b/examples/rules/rules.yaml index 3b2a24a..d0d9557 100644 --- a/examples/rules/rules.yaml +++ b/examples/rules/rules.yaml @@ -68,9 +68,9 @@ groups: title: test alert playbook: http://foo.bar/nonexisting/playbook - # ignore_validations: hasLabels,expressionUsesExistingLabels,hasLabels,hasAnyOfAnnotations,hasAnnotations + # ignore_validations: hasLabels,expressionUsesExistingLabels,hasLabels,hasAnyOfAnnotations,hasAnnotations,expressionCanBeEvaluated,expressionIsWellFormatted - name: testUTF8 limit: 10 rules: - alert: test - expr: sum by ("foo.bar") (up) + expr: sum by ("foo.bar") ({"up"})