Skip to content

Commit 1ada22b

Browse files
committed
Merge branch 'feat/monitor-event' into 'master'
feat(QOV-964): Implement automated kube-event monitoring See merge request qovery/backend/k8s-event-logger!4
2 parents 3e8a12d + 30517a4 commit 1ada22b

File tree

4 files changed

+227
-17
lines changed

4 files changed

+227
-17
lines changed

.gitlab-ci.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,3 @@ build-image:
3232
when: manual
3333
tags:
3434
- vm-250mcpu-1gmem-0g
35-
only:
36-
- master

go.mod

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ module github.com/max-rocket-internet/k8s-event-logger
33
go 1.23.0
44

55
require (
6+
github.com/prometheus/client_golang v1.22.0
67
k8s.io/api v0.31.0
78
k8s.io/apimachinery v0.31.0
89
k8s.io/client-go v0.31.0
910
)
1011

1112
require (
13+
github.com/beorn7/perks v1.0.1 // indirect
14+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1215
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1316
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
1417
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
@@ -19,25 +22,29 @@ require (
1922
github.com/gogo/protobuf v1.3.2 // indirect
2023
github.com/golang/protobuf v1.5.4 // indirect
2124
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
22-
github.com/google/go-cmp v0.6.0 // indirect
25+
github.com/google/go-cmp v0.7.0 // indirect
2326
github.com/google/gofuzz v1.2.0 // indirect
2427
github.com/google/uuid v1.6.0 // indirect
28+
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
2529
github.com/imdario/mergo v0.3.16 // indirect
2630
github.com/josharian/intern v1.0.0 // indirect
2731
github.com/json-iterator/go v1.1.12 // indirect
2832
github.com/mailru/easyjson v0.7.7 // indirect
2933
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
3034
github.com/modern-go/reflect2 v1.0.2 // indirect
3135
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
36+
github.com/prometheus/client_model v0.6.1 // indirect
37+
github.com/prometheus/common v0.62.0 // indirect
38+
github.com/prometheus/procfs v0.15.1 // indirect
3239
github.com/spf13/pflag v1.0.5 // indirect
3340
github.com/x448/float16 v0.8.4 // indirect
3441
golang.org/x/net v0.38.0 // indirect
35-
golang.org/x/oauth2 v0.22.0 // indirect
42+
golang.org/x/oauth2 v0.24.0 // indirect
3643
golang.org/x/sys v0.31.0 // indirect
3744
golang.org/x/term v0.30.0 // indirect
3845
golang.org/x/text v0.23.0 // indirect
3946
golang.org/x/time v0.6.0 // indirect
40-
google.golang.org/protobuf v1.34.2 // indirect
47+
google.golang.org/protobuf v1.36.5 // indirect
4148
gopkg.in/inf.v0 v0.9.1 // indirect
4249
gopkg.in/yaml.v2 v2.4.0 // indirect
4350
gopkg.in/yaml.v3 v3.0.1 // indirect

go.sum

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
2+
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
3+
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
4+
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
15
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
26
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
37
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -23,15 +27,17 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6
2327
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU=
2428
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M=
2529
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
26-
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
27-
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
30+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
31+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
2832
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
2933
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
3034
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
3135
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k=
3236
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
3337
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
3438
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
39+
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
40+
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
3541
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
3642
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
3743
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -40,10 +46,14 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
4046
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
4147
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
4248
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
49+
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
50+
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
4351
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
4452
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
4553
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
4654
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
55+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
56+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
4757
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
4858
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
4959
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -57,17 +67,27 @@ github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA
5767
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
5868
github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
5969
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
70+
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
71+
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
6072
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
6173
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
6274
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
75+
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
76+
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
77+
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
78+
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
79+
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
80+
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
81+
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
82+
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
6383
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
6484
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
6585
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
6686
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
6787
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6888
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
69-
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
70-
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
89+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
90+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7191
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
7292
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
7393
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -83,8 +103,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
83103
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
84104
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
85105
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
86-
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
87-
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
106+
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
107+
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
88108
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
89109
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
90110
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -111,11 +131,13 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
111131
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
112132
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
113133
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
114-
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
115-
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
134+
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
135+
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
116136
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
117137
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
118138
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
139+
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
140+
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
119141
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
120142
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
121143
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

main.go

Lines changed: 187 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,58 @@
11
package main
22

33
import (
4+
"context"
45
"encoding/json"
56
"flag"
7+
"github.com/hashicorp/golang-lru/v2/expirable"
8+
"github.com/prometheus/client_golang/prometheus/promauto"
9+
"k8s.io/apimachinery/pkg/api/meta"
10+
"k8s.io/apimachinery/pkg/runtime/schema"
11+
"k8s.io/client-go/discovery"
12+
"k8s.io/client-go/dynamic"
13+
"k8s.io/client-go/rest"
14+
"k8s.io/client-go/restmapper"
615
"log"
16+
"net/http"
717
"os"
18+
"time"
19+
20+
"github.com/prometheus/client_golang/prometheus"
21+
"github.com/prometheus/client_golang/prometheus/promhttp"
822

923
corev1 "k8s.io/api/core/v1"
1024
"k8s.io/apimachinery/pkg/fields"
1125
"k8s.io/client-go/kubernetes"
1226
"k8s.io/client-go/tools/cache"
1327
"k8s.io/client-go/tools/clientcmd"
28+
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
"k8s.io/client-go/discovery/cached/memory"
31+
32+
"github.com/hashicorp/golang-lru/v2"
1433
)
1534

1635
var (
17-
ignoreNormal = flag.Bool("ignore-normal", false, "ignore events of type 'Normal' to reduce noise")
18-
ignoreUpdate = flag.Bool("ignore-update", true, "ignore update of events")
36+
ignoreNormal = flag.Bool("ignore-normal", false, "ignore events of type 'Normal' to reduce noise")
37+
ignoreUpdate = flag.Bool("ignore-update", true, "ignore update of events")
38+
metricsEnabled = flag.Bool("metrics-enabled", false, "expose Prometheus metrics on :8080/metrics")
39+
)
40+
41+
var (
42+
eventsTotal = promauto.NewCounterVec(
43+
prometheus.CounterOpts{
44+
Name: "k8s_event_logger_q_k8s_events_total",
45+
Help: "Total number of Kubernetes events processed",
46+
},
47+
[]string{"type", "reason", "object_kind", "qovery_project_id", "qovery_environment_id", "qovery_service_id"},
48+
)
49+
eventsHit = promauto.NewCounterVec(
50+
prometheus.CounterOpts{
51+
Name: "k8s_event_logger_q_cache_event_total",
52+
Help: "Cache hit/miss events for object label fetching",
53+
},
54+
[]string{"cache_type"},
55+
)
1956
)
2057

2158
func main() {
@@ -24,6 +61,16 @@ func main() {
2461
loggerApplication := log.New(os.Stderr, "", log.LstdFlags)
2562
loggerEvent := log.New(os.Stdout, "", 0)
2663

64+
if *metricsEnabled {
65+
go func() {
66+
http.Handle("/metrics", promhttp.Handler())
67+
loggerApplication.Printf("Prometheus endpoint listening on :8080/metrics")
68+
if err := http.ListenAndServe(":8080", nil); err != nil {
69+
loggerApplication.Fatalf("metrics server: %v", err)
70+
}
71+
}()
72+
}
73+
2774
// Using First sample from https://pkg.go.dev/k8s.io/client-go/tools/clientcmd to automatically deal with environment variables and default file paths
2875

2976
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
@@ -39,6 +86,11 @@ func main() {
3986
loggerApplication.Panicln(err.Error())
4087
}
4188

89+
fetcher, err := NewObjectLabelFetcher(config, 10_000, 30*time.Minute)
90+
if err != nil {
91+
loggerApplication.Fatalf("failed to build label fetcher: %v", err)
92+
}
93+
4294
// Note that this *should* automatically sanitize sensitive fields
4395
loggerApplication.Println("Using configuration:", config.String())
4496

@@ -59,16 +111,20 @@ func main() {
59111
0,
60112
cache.ResourceEventHandlerFuncs{
61113
AddFunc: func(obj interface{}) {
62-
if *ignoreNormal && obj.(*corev1.Event).Type == corev1.EventTypeNormal {
114+
evt := obj.(*corev1.Event)
115+
if *ignoreNormal && evt.Type == corev1.EventTypeNormal {
63116
return
64117
}
65118
logEvent(obj, loggerEvent)
119+
recordMetric(evt, fetcher)
66120
},
67121
UpdateFunc: func(oldObj, newObj interface{}) {
68-
if *ignoreUpdate || (*ignoreNormal && newObj.(*corev1.Event).Type == corev1.EventTypeNormal) {
122+
evt := newObj.(*corev1.Event)
123+
if *ignoreUpdate || (*ignoreNormal && evt.Type == corev1.EventTypeNormal) {
69124
return
70125
}
71126
logEvent(newObj, loggerEvent)
127+
recordMetric(evt, fetcher)
72128
},
73129
},
74130
)
@@ -83,3 +139,130 @@ func logEvent(obj interface{}, logger *log.Logger) {
83139
j, _ := json.Marshal(obj)
84140
logger.Printf("%s\n", string(j))
85141
}
142+
143+
func recordMetric(evt *corev1.Event, fetcher *ObjectLabelFetcher) {
144+
if !*metricsEnabled {
145+
return
146+
}
147+
148+
qoveryProjectId := ""
149+
qoveryEnvId := ""
150+
qoveryServiceId := ""
151+
152+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
153+
defer cancel()
154+
155+
labels, err := fetcher.LabelsForEvent(ctx, evt)
156+
if err == nil {
157+
qoveryProjectId = labels["qovery.com/project-id"]
158+
qoveryEnvId = labels["qovery.com/environment-id"]
159+
qoveryServiceId = labels["qovery.com/service-id"]
160+
}
161+
162+
eventsTotal.
163+
WithLabelValues(evt.Type, evt.Reason, evt.InvolvedObject.Kind, qoveryProjectId, qoveryEnvId, qoveryServiceId).
164+
Inc()
165+
}
166+
167+
type cacheKey string
168+
169+
func keyFromEvent(evt *corev1.Event) cacheKey {
170+
return cacheKey(evt.InvolvedObject.UID)
171+
}
172+
173+
type ObjectLabelFetcher struct {
174+
dynClient dynamic.Interface
175+
mapper meta.RESTMapper
176+
cache labelCache[cacheKey, map[string]string]
177+
}
178+
179+
type labelCache[K comparable, V any] interface {
180+
Get(K) (V, bool)
181+
Add(K, V) bool
182+
}
183+
184+
func NewObjectLabelFetcher(
185+
cfg *rest.Config,
186+
maxEntries int,
187+
ttl time.Duration,
188+
) (*ObjectLabelFetcher, error) {
189+
dynClient, err := dynamic.NewForConfig(cfg)
190+
if err != nil {
191+
return nil, err
192+
}
193+
194+
disco, err := discovery.NewDiscoveryClientForConfig(cfg)
195+
if err != nil {
196+
return nil, err
197+
}
198+
199+
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(disco))
200+
201+
// ----- creating the LRU cache (with or without TTL) -----
202+
var c labelCache[cacheKey, map[string]string]
203+
if ttl > 0 {
204+
c = expirable.NewLRU[cacheKey, map[string]string](maxEntries, nil, ttl)
205+
} else {
206+
lruCache, err := lru.New[cacheKey, map[string]string](maxEntries)
207+
if err != nil {
208+
return nil, err
209+
}
210+
c = lruCache
211+
}
212+
213+
return &ObjectLabelFetcher{
214+
dynClient: dynClient,
215+
mapper: mapper,
216+
cache: c,
217+
}, nil
218+
}
219+
220+
func (f *ObjectLabelFetcher) LabelsForEvent(ctx context.Context, evt *corev1.Event) (map[string]string, error) {
221+
key := keyFromEvent(evt)
222+
223+
// Fast-path: cache hit
224+
if lbls, ok := f.cache.Get(key); ok {
225+
eventsHit.WithLabelValues("hit").Inc()
226+
return lbls, nil
227+
}
228+
eventsHit.WithLabelValues("miss").Inc()
229+
230+
// Resolve the GroupVersionKind from the Event.
231+
gvk := schema.FromAPIVersionAndKind(evt.InvolvedObject.APIVersion, evt.InvolvedObject.Kind)
232+
233+
// Translate GVK ➜ GroupVersionResource via the RESTMapper.
234+
mapping, err := f.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
235+
if err != nil {
236+
return nil, err
237+
}
238+
239+
// Pick the correct dynamic ResourceInterface (namespaced or cluster-wide).
240+
var dr dynamic.ResourceInterface
241+
if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
242+
ns := evt.InvolvedObject.Namespace
243+
if ns == "" {
244+
ns = evt.Namespace
245+
}
246+
dr = f.dynClient.Resource(mapping.Resource).Namespace(ns)
247+
} else {
248+
dr = f.dynClient.Resource(mapping.Resource)
249+
}
250+
251+
// Retrieve the actual object (no need to unmarshal into a typed struct).
252+
obj, err := dr.Get(ctx, evt.InvolvedObject.Name, metav1.GetOptions{})
253+
if err != nil {
254+
return nil, err
255+
}
256+
257+
// Use the meta.Accessor helper to read generic metadata, including labels.
258+
accessor, err := meta.Accessor(obj)
259+
if err != nil {
260+
return nil, err
261+
}
262+
labels := accessor.GetLabels()
263+
264+
// Store in cache (eviction handled automatically)
265+
f.cache.Add(key, labels)
266+
267+
return labels, nil
268+
}

0 commit comments

Comments
 (0)