diff --git a/go.mod b/go.mod index e12a07164..381821642 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/chainguard-dev/git-urls v1.0.2 github.com/elazarl/goproxy v1.8.1 github.com/fluxcd/cli-utils v0.37.2-flux.1 + github.com/fluxcd/kustomize-controller/api v1.8.0 github.com/fluxcd/notification-controller/api v1.8.0 github.com/fluxcd/pkg/apis/event v0.25.0 github.com/fluxcd/pkg/apis/meta v1.26.0 @@ -41,11 +42,11 @@ require ( github.com/slok/go-http-metrics v0.13.0 github.com/spf13/pflag v1.0.10 gitlab.com/gitlab-org/api/client-go v1.33.0 - go.opentelemetry.io/otel v1.40.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 - go.opentelemetry.io/otel/sdk v1.40.0 - go.opentelemetry.io/otel/trace v1.40.0 + go.opentelemetry.io/otel v1.42.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 + go.opentelemetry.io/otel/sdk v1.42.0 + go.opentelemetry.io/otel/trace v1.42.0 golang.org/x/oauth2 v0.35.0 golang.org/x/text v0.34.0 google.golang.org/api v0.266.0 @@ -135,7 +136,7 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -175,25 +176,25 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.42.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.47.0 // indirect + golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect + golang.org/x/net v0.51.0 // indirect golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/term v0.40.0 // indirect golang.org/x/time v0.14.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect + google.golang.org/grpc v1.79.2 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index e0c50bfc8..6db427851 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6 github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc= @@ -117,12 +117,12 @@ github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= -github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= @@ -135,6 +135,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0= github.com/fluxcd/cli-utils v0.37.2-flux.1/go.mod h1:LcWSu1NYET8d8U7O326RhEm5JkQXCMK6ITu4G1CT02c= +github.com/fluxcd/kustomize-controller/api v1.8.0 h1:NqDgjqUwotXaHhvd5z46xOhe1O/NPzycXExbuHRmt38= +github.com/fluxcd/kustomize-controller/api v1.8.0/go.mod h1:+ZJB/dIGbnSzZ5J/kiJ8n0USmLNAjfeZU6Xfra0oMZA= github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4= github.com/fluxcd/pkg/apis/event v0.25.0 h1:zdwytvDhG+fk+Ywl5DOtv7TklkrVgM21WHm1f+YhleE= @@ -282,8 +284,8 @@ github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= @@ -428,20 +430,20 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -457,8 +459,8 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= @@ -474,8 +476,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= @@ -492,11 +494,11 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= @@ -524,17 +526,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 h1:VQZ/yAbAtjkHgH80teYd2em3xtIkkHd7ZhqfH2N9CsM= google.golang.org/genproto v0.0.0-20260128011058-8636f8732409/go.mod h1:rxKD3IEILWEu3P44seeNOAwZN4SaoKaQ/2eTg4mM6EM= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= -google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 h1:Jr5R2J6F6qWyzINc+4AM8t5pfUz6beZpHp678GNrMbE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0= +google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= 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= diff --git a/internal/notifier/factory.go b/internal/notifier/factory.go index 6b2dfabc0..1ee54078e 100644 --- a/internal/notifier/factory.go +++ b/internal/notifier/factory.go @@ -383,5 +383,5 @@ func otelNotifierFunc(opts notifierOptions) (Interface, error) { if opts.Token == "" && opts.Password != "" { opts.Token = opts.Password } - return NewOTLPTracer(opts.Context, opts.URL, opts.ProxyURL, opts.Headers, opts.TLSConfig, opts.Username, opts.Token) + return NewOTLPTracer(opts.Context, opts.TokenClient, opts.URL, opts.ProxyURL, opts.Headers, opts.TLSConfig, opts.Username, opts.Token) } diff --git a/internal/notifier/otel.go b/internal/notifier/otel.go index c83dc0dc5..db4664453 100644 --- a/internal/notifier/otel.go +++ b/internal/notifier/otel.go @@ -25,6 +25,7 @@ import ( "net/http" "net/url" "slices" + "time" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" @@ -32,14 +33,18 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.34.0" + semconv "go.opentelemetry.io/otel/semconv/v1.40.0" "go.opentelemetry.io/otel/trace" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" - + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" apiv1beta3 "github.com/fluxcd/notification-controller/api/v1beta3" + eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1" ) // Context key @@ -56,9 +61,43 @@ func GetAlertMetadata(ctx context.Context) (metav1.ObjectMeta, bool) { type OTLPTracer struct { tracerExporter *otlptrace.Exporter + kubeClient client.Client +} + +// IDGenerator generates deterministic IDs for Flux objects +type IDGenerator struct { + UID string + revision string +} + +// Context keys for IDGenerator +type ObjUID struct{} +type ObjRevision struct{} + +func (g *IDGenerator) NewIDs(ctx context.Context) (trace.TraceID, trace.SpanID) { + objectUID, _ := ctx.Value(ObjUID{}).(string) + objectRevision, _ := ctx.Value(ObjRevision{}).(string) + + var traceID trace.TraceID + var spanID trace.SpanID + + // TraceID from alert (UID + Root Source) + traceIDBytes := generateID(g.UID, g.revision) + copy(traceID[:], traceIDBytes[:16]) + + // SpanID from object + spanIDBytes := generateID(objectUID, objectRevision) + copy(spanID[:], spanIDBytes[:8]) + + return traceID, spanID +} + +func (g *IDGenerator) NewSpanID(ctx context.Context, traceID trace.TraceID) trace.SpanID { + _, spanID := g.NewIDs(ctx) + return spanID } -func NewOTLPTracer(ctx context.Context, urlStr string, proxyURL string, headers map[string]string, tlsConfig *tls.Config, username string, password string) (*OTLPTracer, error) { +func NewOTLPTracer(ctx context.Context, kubeClient client.Client, urlStr string, proxyURL string, headers map[string]string, tlsConfig *tls.Config, username string, password string) (*OTLPTracer, error) { // Set up OTLP exporter options httpOptions := []otlptracehttp.Option{ otlptracehttp.WithEndpointURL(urlStr), @@ -107,6 +146,7 @@ func NewOTLPTracer(ctx context.Context, urlStr string, proxyURL string, headers log.FromContext(ctx).V(1).Info("Successfully created OTEL tracerExporter") return &OTLPTracer{ tracerExporter: exporter, + kubeClient: kubeClient, }, nil } @@ -124,42 +164,64 @@ func (t *OTLPTracer) Post(ctx context.Context, event eventv1.Event) error { return fmt.Errorf("alert metadata not found in context") } - // Extract revision from event metadata - revision := getRevision(event.Metadata) + kind := event.InvolvedObject.Kind - // Create TraceProvider - tp := sdktrace.NewTracerProvider( - sdktrace.WithSpanProcessor(sdktrace.NewSimpleSpanProcessor(t.tracerExporter)), - sdktrace.WithResource( - resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceName(fmt.Sprintf("%s/%s", alert.GetNamespace(), alert.GetName())), - semconv.ServiceNamespace(alert.GetNamespace()), - semconv.ServiceInstanceID(string(alert.GetUID())), - ), - ), - ) + // Skip if it's HelmRepository kind object (no considered as main source for tracing) + if kind == "HelmRepository" { + logger.Info("OTEL notification skipped", "alert", alert.Namespace, alert.Name) + return nil + } - // Generate traceID - logger.V(1).Info("Generating trace IDs", "alertUID", string(alert.UID), "revision", revision) - var traceID trace.TraceID - traceIDStr := generateID(string(alert.UID), revision) - copy(traceID[:], traceIDStr[:16]) + // Check if the object is part of a Kustomization + generators := t.getKustFromLabels(ctx, event.InvolvedObject) + parentRevision := getRevision(event) + + var parentUID string = "" + var revision string = "" + + revision = parentRevision + if len(generators) > 0 { + // Object is under a Kustomization + root := generators[len(generators)-1] + revision = root.revision + + if isSource(kind) { + // Source: parent is immediate Kustomization + parentUID = generators[0].UID + parentRevision = generators[0].revision + } else if parentRevision == revision { + // Non-source: parent is root Kustomization + parentUID = root.UID + } else { + // Non-source with different revision: use object's direct source + parentUID = t.getParentUID(ctx, event) + } + } else if !isSource(kind) { + // No Kustomization parent + parentUID = t.getParentUID(ctx, event) + } - // Determine span relationship based on Flux object hierarchy - var spanCtx context.Context = createSpanContext(ctx, event, traceID) + logger.Info("Generating trace IDs", "alertUID", string(alert.UID), "revision", revision) - // Skip if it's HelmRepository kind object (no considered as main source for tracing) - if event.InvolvedObject.Kind != "HelmRepository" { - logger.Info("Processing OTEL notification", "alert", alert.Name) + // Create TraceProvider + provider := t.createProvider(alert, revision) + defer func() { + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := provider.Shutdown(shutdownCtx); err != nil { + logger.Error(err, "Failed to shutdown tracer provider") + } + }() - } else { - logger.Info("OTEL notification skipped", "alert", alert.Name) - return nil - } + // Determine span relationship based on Flux object hierarchy + spanCtx := createSpanContext(ctx, event, + &IDGenerator{UID: string(alert.UID), revision: revision}, // Root + &IDGenerator{UID: parentUID, revision: parentRevision}, // Parent + ) // Create single span with proper attributes - span := processSpan(tp, spanCtx, event) + ctx, span := processSpan(provider, spanCtx, event) + // Set status based on event severity if event.Severity == eventv1.EventSeverityError { span.SetStatus(codes.Error, event.Message) @@ -177,26 +239,202 @@ func (t *OTLPTracer) Post(ctx context.Context, event eventv1.Event) error { return nil } -func createSpanContext(ctx context.Context, event eventv1.Event, traceID trace.TraceID) context.Context { +func (t *OTLPTracer) createProvider(alert metav1.ObjectMeta, revision string) *sdktrace.TracerProvider { + return sdktrace.NewTracerProvider( + sdktrace.WithBatcher(t.tracerExporter), + sdktrace.WithResource( + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName(fmt.Sprintf("%s/%s", alert.GetNamespace(), alert.GetName())), + semconv.ServiceNamespace(alert.GetNamespace()), + semconv.ServiceInstanceID(string(alert.GetUID())), + ), + ), + sdktrace.WithIDGenerator(&IDGenerator{ + UID: string(alert.GetUID()), + revision: revision, + }), + ) +} + +func (t *OTLPTracer) getParentUID(ctx context.Context, event eventv1.Event) string { + obj, err := t.getObject(ctx, event.InvolvedObject) + if err != nil { + return "" + } + kind := event.InvolvedObject.Kind + var name string = "" + var namespace string = "" + + // HelmRelease: check chartRef for HelmChart parent + if chartRef, found, _ := unstructured.NestedMap(obj.Object, "spec", "chartRef"); found { + kind, _ = chartRef["kind"].(string) + name, _ = chartRef["name"].(string) + namespace, _ = chartRef["namespace"].(string) + if namespace == "" { + namespace = event.InvolvedObject.Namespace + } + } - spanContext := trace.NewSpanContext(trace.SpanContextConfig{ - TraceID: traceID, - TraceFlags: trace.FlagsSampled, - Remote: true, + // Check sourceRef (for GitRepo>Kust, HelmRepo>HelmChart, OCIRepo>HelmRelease, etc) + if sourceRef, found, _ := unstructured.NestedMap(obj.Object, "spec", "sourceRef"); found { + kind, _ = sourceRef["kind"].(string) + if kind == "HelmRepository" { + kind = "HelmChart" + } + name, _ = sourceRef["name"].(string) + namespace, _ = sourceRef["namespace"].(string) + if namespace == "" { + namespace = event.InvolvedObject.Namespace + } + } + + if name != "" { + if meta, err := t.getObjectPartialMetadata(ctx, corev1.ObjectReference{ + APIVersion: "source.toolkit.fluxcd.io/v1", + Kind: kind, + Name: name, + Namespace: namespace, + }); err == nil { + return string(meta.GetUID()) + } + } + + return "" +} + +func (t *OTLPTracer) getKustFromLabels(ctx context.Context, objRef corev1.ObjectReference) []*IDGenerator { + logger := log.FromContext(ctx).V(1) + var generators []*IDGenerator + + ksName, ksNamespace := t.retrieveKustLabels(ctx, objRef) + if ksName == "" || ksNamespace == "" { + return generators + } + + kustRef := corev1.ObjectReference{ + APIVersion: kustomizev1.GroupVersion.String(), + Kind: kustomizev1.KustomizationKind, + Name: ksName, + Namespace: ksNamespace, + } + + kust, err := t.getObject(ctx, kustRef) + if err != nil { + logger.Info("Failed to get Kustomization", "error", err) + return generators + } + + revision := extractRevision(kust) + uid := string(kust.GetUID()) + + // Add current Kustomization to generators list + generators = append(generators, &IDGenerator{ + UID: uid, + revision: revision, }) - // Root spans: Sources that start the deployment flow - if isSource(kind) { - return trace.ContextWithSpanContext(context.Background(), - spanContext.WithTraceFlags(spanContext.TraceFlags())) + + // Recursive call: check if this Kustomization is managed by another one + parentGenerators := t.getKustFromLabels(ctx, kustRef) + if len(parentGenerators) > 0 { + generators = append(generators, parentGenerators...) } - // Child spans: Everything else inherits from the same trace - return trace.ContextWithSpanContext(ctx, - spanContext.WithTraceFlags(spanContext.TraceFlags())) + // This is the root Kustomization + logger.Info("Got Parent Kustomization", "uid", uid, "revision", revision) + + return generators } -func processSpan(tracerProvider *sdktrace.TracerProvider, ctx context.Context, event eventv1.Event) trace.Span { +func (t *OTLPTracer) retrieveKustLabels(ctx context.Context, objRef corev1.ObjectReference) (string, string) { + logger := log.FromContext(ctx).V(1) + objMeta, err := t.getObjectPartialMetadata(ctx, objRef) + if err != nil { + logger.Info("Failed to get object metadata", "error", err) + return "", "" + } + + labels := objMeta.GetLabels() + ksName := labels["kustomize.toolkit.fluxcd.io/name"] + ksNamespace := labels["kustomize.toolkit.fluxcd.io/namespace"] + + if ksName == "" || ksNamespace == "" { + logger.Info("No Kustomization labels found under", "objRefName", objRef.Name, "objRefNamespace", objRef.Namespace) + return ksName, ksNamespace + } + + logger.Info("Found Kustomization labels", "ksName", ksName, "ksNamespace", ksNamespace) + + return ksName, ksNamespace +} + +func (t *OTLPTracer) getObject(ctx context.Context, objRef corev1.ObjectReference) (*unstructured.Unstructured, error) { + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(schema.FromAPIVersionAndKind(objRef.APIVersion, objRef.Kind)) + + if err := t.kubeClient.Get(ctx, client.ObjectKey{ + Namespace: objRef.Namespace, + Name: objRef.Name, + }, obj); err != nil { + return nil, err + } + + return obj, nil +} + +func (t *OTLPTracer) getObjectPartialMetadata(ctx context.Context, objRef corev1.ObjectReference) (*metav1.PartialObjectMetadata, error) { + meta := &metav1.PartialObjectMetadata{} + meta.SetGroupVersionKind(schema.FromAPIVersionAndKind(objRef.APIVersion, objRef.Kind)) + + if err := t.kubeClient.Get(ctx, client.ObjectKey{ + Namespace: objRef.Namespace, + Name: objRef.Name, + }, meta); err != nil { + return nil, err + } + + return meta, nil +} + +func extractRevision(obj *unstructured.Unstructured) string { + if rev, found, _ := unstructured.NestedString(obj.Object, "status", "lastAppliedRevision"); found { + return rev + } + if rev, found, _ := unstructured.NestedString(obj.Object, "status", "artifact", "revision"); found { + return rev + } + return "unknown" +} + +func createSpanContext(ctx context.Context, event eventv1.Event, rootSpan *IDGenerator, parentSpan *IDGenerator) context.Context { + + // No parent and non-source object + if parentSpan.UID != "" { + var traceID trace.TraceID + var spanID trace.SpanID + + parentSpanID := generateID(parentSpan.UID, parentSpan.revision) + traceIDStr := generateID(rootSpan.UID, rootSpan.revision) + copy(traceID[:], traceIDStr[:16]) + copy(spanID[:], parentSpanID[:8]) + + parentSpanCtx := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: traceID, + SpanID: spanID, + TraceFlags: trace.FlagsSampled, + }) + ctx = trace.ContextWithSpanContext(ctx, parentSpanCtx) + } + + // Set current object's UID and revision for span generation + ctx = context.WithValue(ctx, ObjUID{}, string(event.InvolvedObject.UID)) + ctx = context.WithValue(ctx, ObjRevision{}, getRevision(event)) + + return ctx +} + +func processSpan(tracerProvider *sdktrace.TracerProvider, ctx context.Context, event eventv1.Event) (context.Context, trace.Span) { // Build span attributes including metadata eventAttrs := []attribute.KeyValue{ attribute.String("object.uid", string(event.InvolvedObject.UID)), @@ -213,29 +451,30 @@ func processSpan(tracerProvider *sdktrace.TracerProvider, ctx context.Context, e // Create tracer and start tracing spanName := fmt.Sprintf("%s: %s/%s", event.InvolvedObject.Kind, event.InvolvedObject.Namespace, event.InvolvedObject.Name) tracer := tracerProvider.Tracer("flux:notification-controller") - _, span := tracer.Start(ctx, spanName, + return tracer.Start(ctx, spanName, trace.WithAttributes(eventAttrs...), - trace.WithTimestamp(event.Timestamp.Time)) - - return span + trace.WithTimestamp(event.Timestamp.Time), + ) } // Build the revision ID based on the event metadata -func getRevision(eventMetadata map[string]string) string { - var revision string = "unknown" +func getRevision(event eventv1.Event) string { + revision, hasRev := event.GetRevision() + if !hasRev { + return "unknown" + } // OCIRepositories does populate the following metadata // which it's the same revision as some other sources // @ -> @: - ociDigest, hasOCI := eventMetadata["oci-digest"] - appVersion, hasApp := eventMetadata["app-version"] + ociDigest, hasOCI := event.Metadata["oci-digest"] + appVersion, hasApp := event.Metadata["app-version"] - if hasOCI && hasApp { - revision = appVersion + "@" + ociDigest - } else if rev, hasRev := eventMetadata["revision"]; hasRev { + if rev, hasRev := event.Metadata["source-revision"]; hasRev { revision = rev + } else if hasOCI && hasApp { + revision = appVersion + "@" + ociDigest } - return revision } diff --git a/internal/notifier/otel_test.go b/internal/notifier/otel_test.go index 9b433b490..d0c194495 100644 --- a/internal/notifier/otel_test.go +++ b/internal/notifier/otel_test.go @@ -23,8 +23,11 @@ import ( "testing" "time" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "github.com/fluxcd/pkg/apis/event/v1beta1" ) @@ -67,8 +70,12 @@ func TestOTEL_Post(t *testing.T) { UID: "test-alert-uid", } ctx := WithAlertMetadata(context.Background(), *alertMetadata) + // Create mock client with Kustomization scheme + scheme := runtime.NewScheme() + _ = kustomizev1.AddToScheme(scheme) + mockClient := fake.NewClientBuilder().WithScheme(scheme).Build() - otelTrace, err := NewOTLPTracer(ctx, ts.URL, "", nil, nil, "", "") + otelTrace, err := NewOTLPTracer(ctx, mockClient, ts.URL, "", nil, nil, "", "") g.Expect(err).ToNot(HaveOccurred()) err = otelTrace.Post(ctx, tt.event())