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/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 @@
- 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
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 1945cce..d0d9557 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,expressionCanBeEvaluated,expressionIsWellFormatted
+ - 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) {