diff --git a/CHANGELOG.md b/CHANGELOG.md index d250fcba1..526572aa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ NOTE: As semantic versioning states all 0.y.z releases can contain breaking chan ### Added - [#355](https://github.com/kobsio/kobs/pull/#355): [app] Add profile page for authenticated users, which can be customized via dashboards. +- [#361](https://github.com/kobsio/kobs/pull/#361): [app] Add tracing support for kobs via Jaeger or Zipkin. ### Fixed diff --git a/cmd/kobs/hub/hub.go b/cmd/kobs/hub/hub.go index 0e15beab5..58f6e96c2 100644 --- a/cmd/kobs/hub/hub.go +++ b/cmd/kobs/hub/hub.go @@ -15,6 +15,7 @@ import ( "github.com/kobsio/kobs/pkg/hub/watcher" "github.com/kobsio/kobs/pkg/log" "github.com/kobsio/kobs/pkg/metrics" + "github.com/kobsio/kobs/pkg/tracer" "github.com/kobsio/kobs/pkg/version" "github.com/spf13/cobra" @@ -58,6 +59,18 @@ var Cmd = &cobra.Command{ log.Info(nil, "Version information", version.Info()...) log.Info(nil, "Build context", version.BuildContext()...) + traceEnabled, _ := cmd.Flags().GetBool("trace.enabled") + traceServiceName, _ := cmd.Flags().GetString("trace.service-name") + traceProvider, _ := cmd.Flags().GetString("trace.provider") + traceAddress, _ := cmd.Flags().GetString("trace.address") + + if traceEnabled { + err := tracer.Setup(traceServiceName, traceProvider, traceAddress) + if err != nil { + log.Fatal(nil, "Could not setup tracing", zap.Error(err), zap.String("provider", traceProvider), zap.String("address", traceAddress)) + } + } + // Load the configuration for the satellite from the provided configuration file. cfg, err := config.Load(hubConfigFile) if err != nil { diff --git a/cmd/kobs/main.go b/cmd/kobs/main.go index a70ef02c8..34dc69271 100644 --- a/cmd/kobs/main.go +++ b/cmd/kobs/main.go @@ -39,6 +39,21 @@ func init() { defaultLogLevel = os.Getenv("KOBS_LOG_LEVEL") } + defaultTraceServiceName := "kobs" + if os.Getenv("KOBS_TRACE_SERVICE_NAME") != "" { + defaultTraceServiceName = os.Getenv("KOBS_TRACE_SERVICE_NAME") + } + + defaultTraceProvider := "jaeger" + if os.Getenv("KOBS_TRACE_PROVIDER") != "" { + defaultTraceProvider = os.Getenv("KOBS_TRACE_PROVIDER") + } + + defaultTraceAddress := "http://localhost:14268/api/traces" + if os.Getenv("KOBS_TRACE_ADDRESS") != "" { + defaultTraceAddress = os.Getenv("KOBS_TRACE_ADDRESS") + } + rootCmd.AddCommand(hub.Cmd) rootCmd.AddCommand(satellite.Cmd) rootCmd.AddCommand(version.Cmd) @@ -47,6 +62,10 @@ func init() { rootCmd.PersistentFlags().String("debug.password", defaultDebugPassword, "The password for the debug endpoints. The endpoints are only available when a password is provided.") rootCmd.PersistentFlags().String("log.format", defaultLogFormat, "Set the output format of the logs. Must be \"console\" or \"json\".") rootCmd.PersistentFlags().String("log.level", defaultLogLevel, "Set the log level. Must be \"debug\", \"info\", \"warn\", \"error\", \"fatal\" or \"panic\".") + rootCmd.PersistentFlags().Bool("trace.enabled", false, "Enable / disable tracing.") + rootCmd.PersistentFlags().String("trace.service-name", defaultTraceServiceName, "The service name which should be used for tracing.") + rootCmd.PersistentFlags().String("trace.provider", defaultTraceProvider, "Set the trace exporter which should be used. Must be \"jaeger\" or \"zipkin\".") + rootCmd.PersistentFlags().String("trace.address", defaultTraceAddress, "Set the address of the Jaeger or Zipkin instance.") } func main() { diff --git a/cmd/kobs/satellite/satellite.go b/cmd/kobs/satellite/satellite.go index 9b2a08773..ee1863a52 100644 --- a/cmd/kobs/satellite/satellite.go +++ b/cmd/kobs/satellite/satellite.go @@ -11,6 +11,7 @@ import ( "github.com/kobsio/kobs/pkg/metrics" "github.com/kobsio/kobs/pkg/satellite" "github.com/kobsio/kobs/pkg/satellite/plugins" + "github.com/kobsio/kobs/pkg/tracer" "github.com/kobsio/kobs/pkg/version" "github.com/spf13/cobra" @@ -43,6 +44,18 @@ var Cmd = &cobra.Command{ log.Info(nil, "Version information", version.Info()...) log.Info(nil, "Build context", version.BuildContext()...) + traceEnabled, _ := cmd.Flags().GetBool("trace.enabled") + traceServiceName, _ := cmd.Flags().GetString("trace.service-name") + traceProvider, _ := cmd.Flags().GetString("trace.provider") + traceAddress, _ := cmd.Flags().GetString("trace.address") + + if traceEnabled { + err := tracer.Setup(traceServiceName, traceProvider, traceAddress) + if err != nil { + log.Fatal(nil, "Could not setup tracing", zap.Error(err), zap.String("provider", traceProvider), zap.String("address", traceAddress)) + } + } + // Load the configuration for the satellite from the provided configuration file. cfg, err := config.Load(satelliteConfigFile) if err != nil { diff --git a/deploy/helm/hub/Chart.yaml b/deploy/helm/hub/Chart.yaml index a8ed532f9..c86b62d6c 100644 --- a/deploy/helm/hub/Chart.yaml +++ b/deploy/helm/hub/Chart.yaml @@ -4,5 +4,5 @@ description: Kubernetes Observability Platform type: application home: https://kobs.io icon: https://kobs.io/assets/images/logo.svg -version: 0.14.4 +version: 0.15.0 appVersion: v0.8.0 diff --git a/deploy/helm/hub/templates/deployment.yaml b/deploy/helm/hub/templates/deployment.yaml index b4e729f77..df3d89a1a 100644 --- a/deploy/helm/hub/templates/deployment.yaml +++ b/deploy/helm/hub/templates/deployment.yaml @@ -34,6 +34,10 @@ spec: - hub - --log.format={{ .Values.hub.settings.logFormat }} - --log.level={{ .Values.hub.settings.logLevel }} + - --trace.enabled={{ .Values.hub.settings.traceEnabled }} + - --trace.service-name={{ .Values.hub.settings.traceServiceName }} + - --trace.provider={{ .Values.hub.settings.traceProvider }} + - --trace.address={{ .Values.hub.settings.traceAddress }} - --hub.mode={{ .Values.hub.settings.mode }} - --hub.store.driver={{ .Values.hub.settings.store.driver }} - --hub.store.uri={{ .Values.hub.settings.store.uri }} diff --git a/deploy/helm/hub/values.yaml b/deploy/helm/hub/values.yaml index f192e8a74..be59a0935 100644 --- a/deploy/helm/hub/values.yaml +++ b/deploy/helm/hub/values.yaml @@ -119,6 +119,12 @@ hub: settings: logFormat: console logLevel: info + + traceEnabled: false + traceServiceName: hub + traceProvider: jaeger + traceAddress: http://localhost:14268/api/traces + mode: default store: driver: bolt diff --git a/deploy/helm/satellite/Chart.yaml b/deploy/helm/satellite/Chart.yaml index a115891c5..36eef8a73 100644 --- a/deploy/helm/satellite/Chart.yaml +++ b/deploy/helm/satellite/Chart.yaml @@ -4,5 +4,5 @@ description: Kubernetes Observability Platform type: application home: https://kobs.io icon: https://kobs.io/assets/images/logo.svg -version: 0.14.4 +version: 0.15.0 appVersion: v0.8.0 diff --git a/deploy/helm/satellite/templates/deployment.yaml b/deploy/helm/satellite/templates/deployment.yaml index 0ded3792e..8005f302e 100644 --- a/deploy/helm/satellite/templates/deployment.yaml +++ b/deploy/helm/satellite/templates/deployment.yaml @@ -35,6 +35,10 @@ spec: - satellite - --log.format={{ .Values.satellite.settings.logFormat }} - --log.level={{ .Values.satellite.settings.logLevel }} + - --trace.enabled={{ .Values.satellite.settings.traceEnabled }} + - --trace.service-name={{ .Values.satellite.settings.traceServiceName }} + - --trace.provider={{ .Values.satellite.settings.traceProvider }} + - --trace.address={{ .Values.satellite.settings.traceAddress }} {{- with .Values.env }} env: {{- toYaml . | nindent 12 }} diff --git a/deploy/helm/satellite/values.yaml b/deploy/helm/satellite/values.yaml index 9a9fad911..c32d54c0a 100644 --- a/deploy/helm/satellite/values.yaml +++ b/deploy/helm/satellite/values.yaml @@ -122,6 +122,11 @@ satellite: logFormat: console logLevel: info + traceEnabled: false + traceServiceName: hub + traceProvider: jaeger + traceAddress: http://localhost:14268/api/traces + ## Set the content of the config.yaml file, which is used by kobs. The configuration file is used to specify the ## cluster providers and the configuration for the plugins. ## diff --git a/go.mod b/go.mod index 7fd14b657..37d61a3d9 100644 --- a/go.mod +++ b/go.mod @@ -33,9 +33,16 @@ require ( github.com/prometheus/client_golang v1.12.0 github.com/prometheus/common v0.32.1 github.com/spf13/cobra v1.2.1 - github.com/stretchr/testify v1.7.0 + github.com/stretchr/testify v1.7.1 github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a go.etcd.io/bbolt v1.3.6 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0 + go.opentelemetry.io/contrib/propagators/b3 v1.7.0 + go.opentelemetry.io/otel v1.7.0 + go.opentelemetry.io/otel/exporters/jaeger v1.7.0 + go.opentelemetry.io/otel/exporters/zipkin v1.7.0 + go.opentelemetry.io/otel/sdk v1.7.0 + go.opentelemetry.io/otel/trace v1.7.0 go.uber.org/zap v1.20.0 k8s.io/api v0.23.2 k8s.io/apiextensions-apiserver v0.23.2 @@ -65,14 +72,16 @@ require ( github.com/dimchansky/utfbom v1.1.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/felixge/httpsnoop v1.0.2 // indirect github.com/fluxcd/pkg/apis/kustomize v0.2.0 // indirect github.com/fluxcd/pkg/runtime v0.12.2 // indirect - github.com/go-logr/logr v1.2.2 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/googleapis v1.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.6 // indirect + github.com/google/go-cmp v0.5.7 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/googleapis/gnostic v0.5.5 // indirect @@ -85,9 +94,8 @@ require ( github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jaegertracing/jaeger v1.15.1 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.13.5 // indirect + github.com/klauspost/compress v1.13.6 // indirect github.com/klauspost/cpuid v1.3.1 // indirect - github.com/kr/pretty v0.2.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/minio/md5-simd v1.1.0 // indirect @@ -100,6 +108,7 @@ require ( github.com/nitishm/engarde v0.1.1 // indirect github.com/openshift/api v0.0.0-20200221181648-8ce0047d664f // indirect github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/openzipkin/zipkin-go v0.4.0 // indirect github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect @@ -112,6 +121,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.2.0 // indirect github.com/vjeantet/grok v1.0.0 // indirect + go.opentelemetry.io/otel/metric v0.30.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 // indirect @@ -124,9 +134,8 @@ require ( golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2 // indirect - google.golang.org/grpc v1.40.0 // indirect + google.golang.org/grpc v1.41.0 // indirect google.golang.org/protobuf v1.27.1 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/square/go-jose.v2 v2.5.1 // indirect diff --git a/go.sum b/go.sum index 75031b9c6..a9de178cc 100644 --- a/go.sum +++ b/go.sum @@ -105,7 +105,9 @@ github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBK github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -160,6 +162,7 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= @@ -196,6 +199,7 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= @@ -211,6 +215,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -219,6 +224,8 @@ github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= +github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/helm-controller/api v0.13.0 h1:f9SwsHjqbWfeHMEtpr9wfdbMm0HQ2dL8bVayp2QyPxs= github.com/fluxcd/helm-controller/api v0.13.0/go.mod h1:zWmzV0s2SU4rEIGLPTt+dsaMs40OsNQgSgOATgJmxB0= github.com/fluxcd/kustomize-controller/api v0.18.0 h1:dG0LKTY5EsFP3MC+y9uxnqJHkB0/Q9iq61H6CHsyJXo= @@ -234,8 +241,10 @@ github.com/fluxcd/pkg/runtime v0.12.2 h1:4iOpx2j/w15kNemDOnZrF6ugJ/rhSmRu7aI+xn2 github.com/fluxcd/pkg/runtime v0.12.2/go.mod h1:tuWdqpWPhgjQvYrSnojdZ4plyU8DRU1NDzsfOhnzl2g= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= @@ -263,8 +272,11 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2 h1:ahHml/yUpnlb96Rp8HCvtYVPY8ZYpxq3g7UYchIYwbs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= @@ -333,6 +345,7 @@ github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= @@ -349,8 +362,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= @@ -389,6 +402,8 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -422,6 +437,7 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= @@ -445,6 +461,12 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jaegertracing/jaeger v1.15.1 h1:7QzNAXq+4ko9GtCjozDNAp2uonoABu+B2Rk94hjQcp4= github.com/jaegertracing/jaeger v1.15.1/go.mod h1:LUWPSnzNPGRubM8pk0inANGitpiMOOxihXx0+53llXI= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= @@ -472,8 +494,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.5 h1:9O69jUPDcsT9fEm74W92rZL9FQY7rCdaXVneq+yyzl4= -github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= @@ -579,6 +601,7 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -587,6 +610,7 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/openshift/api v0.0.0-20200221181648-8ce0047d664f h1:ATPK7UhEwglONJc8qGsq41TbPk0XA4Kpm7XZZ3mlhAY= @@ -600,6 +624,8 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.4.0 h1:CtfRrOVZtbDj8rt1WXjklw0kqqJQwICrCKmlfUuBUUw= +github.com/openzipkin/zipkin-go v0.4.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.12 h1:csZ/8Xl4JrOp3YGqa+ZvUhheZKD0pjMQwVoZaaCNFNo= github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.12/go.mod h1:4OjcxgwdXzezqytxN534MooNmrxRD50geWZxTD7845s= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= @@ -611,8 +637,9 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -664,7 +691,9 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rabbitmq/amqp091-go v1.1.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -725,8 +754,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a h1:oIi7H/bwFUYKYhzKbHc+3MvHRWqhQwXVB4LweLMiVy0= github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a/go.mod h1:iSvujNDmpZ6eQX+bg/0X3lF7LEmZ8N77g2a/J/+Zt2U= @@ -737,8 +767,12 @@ github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/vjeantet/grok v1.0.0 h1:uxMqatJP6MOFXsj6C1tZBnqqAThQEeqnizUZ48gSJQQ= github.com/vjeantet/grok v1.0.0/go.mod h1:/FWYEVYekkm+2VjcFmO9PufDU5FgXHUz9oy2EGqmQBo= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -772,14 +806,30 @@ go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0 h1:mac9BKRqwaX6zxHPDe3pvmWpwuuIM0vuXv2juCnQevE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.32.0/go.mod h1:5eCOqeGphOyz6TsY3ZDNjE33SM/TFAK3RGuCL2naTgY= +go.opentelemetry.io/contrib/propagators/b3 v1.7.0 h1:oRAenUhj+GFttfIp3gj7HYVzBhPOHgq/dWPDSmLCXSY= +go.opentelemetry.io/contrib/propagators/b3 v1.7.0/go.mod h1:gXx7AhL4xXCF42gpm9dQvdohoDa2qeyEx4eIIxqK+h4= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM= +go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= +go.opentelemetry.io/otel/exporters/jaeger v1.7.0 h1:wXgjiRldljksZkZrldGVe6XrG9u3kYDyQmkZwmm5dI0= +go.opentelemetry.io/otel/exporters/jaeger v1.7.0/go.mod h1:PwQAOqBgqbLQRKlj466DuD2qyMjbtcPpfPfj+AqbSBs= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/exporters/zipkin v1.7.0 h1:X0FZj+kaIdLi29UiyrEGDhRTYsEXj9GdEW5Y39UQFEE= +go.opentelemetry.io/otel/exporters/zipkin v1.7.0/go.mod h1:9YBXeOMFLQGwNEjsxMRiWPGoJX83usGMhbCmxUbNe5I= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/metric v0.30.0 h1:Hs8eQZ8aQgs0U49diZoaS6Uaxw3+bBE3lcMUKBFIk3c= +go.opentelemetry.io/otel/metric v0.30.0/go.mod h1:/ShZ7+TS4dHzDFmfi1kSXMhMVubNoP0oIaBp70J6UXU= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0= +go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o= +go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -812,8 +862,10 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88 h1:Tgea0cVUD0ivh5ADBX4WwuI12DUd2to3nCYe2eayMIw= @@ -907,6 +959,7 @@ golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= @@ -1003,6 +1056,7 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1218,8 +1272,9 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= 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/pkg/hub/api/applications/applications.go b/pkg/hub/api/applications/applications.go index 3b926f507..8f2d9e758 100644 --- a/pkg/hub/api/applications/applications.go +++ b/pkg/hub/api/applications/applications.go @@ -1,6 +1,7 @@ package applications import ( + "fmt" "net/http" "strconv" @@ -12,18 +13,26 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" "go.uber.org/zap" ) type Router struct { *chi.Mux storeClient store.Client + tracer trace.Tracer } func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { - user, err := authContext.GetUser(r.Context()) + ctx, span := router.tracer.Start(r.Context(), "getApplications") + defer span.End() + + user, err := authContext.GetUser(ctx) if err != nil { - log.Warn(r.Context(), "The user is not authorized to access the applications", zap.Error(err)) + log.Warn(ctx, "The user is not authorized to access the applications", zap.Error(err)) errresponse.Render(w, r, err, http.StatusUnauthorized, "You are not authorized to access the applications") return } @@ -38,16 +47,30 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { limit := r.URL.Query().Get("limit") offset := r.URL.Query().Get("offset") + span.SetAttributes(attribute.Key("teams").StringSlice(teams)) + span.SetAttributes(attribute.Key("all").String(all)) + span.SetAttributes(attribute.Key("clusterIDs").StringSlice(clusterIDs)) + span.SetAttributes(attribute.Key("namespaceIDs").StringSlice(namespaceIDs)) + span.SetAttributes(attribute.Key("tags").StringSlice(tags)) + span.SetAttributes(attribute.Key("searchTerm").String(searchTerm)) + span.SetAttributes(attribute.Key("external").String(external)) + span.SetAttributes(attribute.Key("limit").String(limit)) + span.SetAttributes(attribute.Key("offset").String(offset)) + parsedLimit, err := strconv.Atoi(limit) if err != nil { - log.Error(r.Context(), "Could not parse limit parameter", zap.Error(err)) + log.Error(ctx, "Could not parse limit parameter", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusBadRequest, "Could not parse limit parameter") return } parsedOffset, err := strconv.Atoi(offset) if err != nil { - log.Error(r.Context(), "Could not parse offset parameter", zap.Error(err)) + log.Error(ctx, "Could not parse offset parameter", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusBadRequest, "Could not parse offset parameter") return } @@ -55,7 +78,9 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { parsedAll, _ := strconv.ParseBool(all) if parsedAll == true || (len(user.Teams) == 1 && user.Teams[0] == "*") { if !user.HasApplicationAccess("", "", "", []string{""}) { - log.Warn(r.Context(), "The user is not authorized to view all applications", zap.Error(err)) + log.Warn(ctx, "The user is not authorized to view all applications") + span.RecordError(fmt.Errorf("user is not authorized to view all applications")) + span.SetStatus(codes.Error, "user is not authorized to view all applications") errresponse.Render(w, r, nil, http.StatusForbidden, "You are not allowed to view all applications") return } @@ -63,9 +88,11 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { teams = nil } - applications, err := router.storeClient.GetApplicationsByFilter(r.Context(), teams, clusterIDs, namespaceIDs, tags, searchTerm, external, parsedLimit, parsedOffset) + applications, err := router.storeClient.GetApplicationsByFilter(ctx, teams, clusterIDs, namespaceIDs, tags, searchTerm, external, parsedLimit, parsedOffset) if err != nil { - log.Error(r.Context(), "Could not get applications", zap.Error(err)) + log.Error(ctx, "Could not get applications", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get applications") return } @@ -74,9 +101,14 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { } func (router *Router) getApplicationsCount(w http.ResponseWriter, r *http.Request) { - user, err := authContext.GetUser(r.Context()) + ctx, span := router.tracer.Start(r.Context(), "getApplicationsCount") + defer span.End() + + user, err := authContext.GetUser(ctx) if err != nil { - log.Warn(r.Context(), "The user is not authorized to access the applications", zap.Error(err)) + log.Warn(ctx, "The user is not authorized to access the applications", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusUnauthorized, "You are not authorized to access the applications") return } @@ -89,10 +121,20 @@ func (router *Router) getApplicationsCount(w http.ResponseWriter, r *http.Reques searchTerm := r.URL.Query().Get("searchTerm") external := r.URL.Query().Get("external") + span.SetAttributes(attribute.Key("teams").StringSlice(teams)) + span.SetAttributes(attribute.Key("all").String(all)) + span.SetAttributes(attribute.Key("clusterIDs").StringSlice(clusterIDs)) + span.SetAttributes(attribute.Key("namespaceIDs").StringSlice(namespaceIDs)) + span.SetAttributes(attribute.Key("tags").StringSlice(tags)) + span.SetAttributes(attribute.Key("searchTerm").String(searchTerm)) + span.SetAttributes(attribute.Key("external").String(external)) + parsedAll, _ := strconv.ParseBool(all) if parsedAll == true || (len(user.Teams) == 1 && user.Teams[0] == "*") { if !user.HasApplicationAccess("", "", "", []string{""}) { - log.Warn(r.Context(), "The user is not authorized to view all applications", zap.Error(err)) + log.Warn(ctx, "The user is not authorized to view all applications") + span.RecordError(fmt.Errorf("user is not authorized to view all applications")) + span.SetStatus(codes.Error, "user is not authorized to view all applications") errresponse.Render(w, r, nil, http.StatusForbidden, "You are not allowed to view all applications") return } @@ -100,9 +142,11 @@ func (router *Router) getApplicationsCount(w http.ResponseWriter, r *http.Reques teams = nil } - count, err := router.storeClient.GetApplicationsByFilterCount(r.Context(), teams, clusterIDs, namespaceIDs, tags, searchTerm, external) + count, err := router.storeClient.GetApplicationsByFilterCount(ctx, teams, clusterIDs, namespaceIDs, tags, searchTerm, external) if err != nil { - log.Error(r.Context(), "Could not get applications count", zap.Error(err)) + log.Error(ctx, "Could not get applications count", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get applications count") return } @@ -115,9 +159,14 @@ func (router *Router) getApplicationsCount(w http.ResponseWriter, r *http.Reques } func (router *Router) getTags(w http.ResponseWriter, r *http.Request) { - tags, err := router.storeClient.GetTags(r.Context()) + ctx, span := router.tracer.Start(r.Context(), "getTags") + defer span.End() + + tags, err := router.storeClient.GetTags(ctx) if err != nil { - log.Error(r.Context(), "Could not get tags", zap.Error(err)) + log.Error(ctx, "Could not get tags", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get tags") return } @@ -126,30 +175,43 @@ func (router *Router) getTags(w http.ResponseWriter, r *http.Request) { } func (router *Router) getApplication(w http.ResponseWriter, r *http.Request) { - user, err := authContext.GetUser(r.Context()) + ctx, span := router.tracer.Start(r.Context(), "getApplication") + defer span.End() + + user, err := authContext.GetUser(ctx) if err != nil { - log.Warn(r.Context(), "The user is not authorized to access the application", zap.Error(err)) + log.Warn(ctx, "The user is not authorized to access the application", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusUnauthorized, "You are not authorized to access the application") return } id := r.URL.Query().Get("id") - application, err := router.storeClient.GetApplicationByID(r.Context(), id) + span.SetAttributes(attribute.Key("id").String(id)) + + application, err := router.storeClient.GetApplicationByID(ctx, id) if err != nil { - log.Error(r.Context(), "Could not get application", zap.Error(err), zap.String("id", id)) + log.Error(ctx, "Could not get application", zap.Error(err), zap.String("id", id)) errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get application") + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return } if application == nil { - log.Error(r.Context(), "Application was not found", zap.Error(err), zap.String("id", id)) + log.Error(ctx, "Application was not found", zap.Error(err), zap.String("id", id)) + span.RecordError(fmt.Errorf("application was not found")) + span.SetStatus(codes.Error, "application was not found") errresponse.Render(w, r, err, http.StatusBadRequest, "Application was not found") return } if !user.HasApplicationAccess(application.Satellite, application.Cluster, application.Namespace, application.Teams) { - log.Warn(r.Context(), "The user is not authorized to view the application", zap.Error(err), zap.String("id", id)) + log.Warn(ctx, "The user is not authorized to view the application", zap.String("id", id)) + span.RecordError(fmt.Errorf("user is not authorized to view the application")) + span.SetStatus(codes.Error, "user is not authorized to view the application") errresponse.Render(w, r, nil, http.StatusForbidden, "You are not allowed to view the application") return } @@ -158,9 +220,14 @@ func (router *Router) getApplication(w http.ResponseWriter, r *http.Request) { } func (router *Router) getApplicationsByTeam(w http.ResponseWriter, r *http.Request) { - user, err := authContext.GetUser(r.Context()) + ctx, span := router.tracer.Start(r.Context(), "getApplications") + defer span.End() + + user, err := authContext.GetUser(ctx) if err != nil { - log.Warn(r.Context(), "The user is not authorized to access the applications", zap.Error(err)) + log.Warn(ctx, "The user is not authorized to access the applications", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusUnauthorized, "You are not authorized to access the applications") return } @@ -169,36 +236,50 @@ func (router *Router) getApplicationsByTeam(w http.ResponseWriter, r *http.Reque limit := r.URL.Query().Get("limit") offset := r.URL.Query().Get("offset") + span.SetAttributes(attribute.Key("team").String(team)) + span.SetAttributes(attribute.Key("limit").String(limit)) + span.SetAttributes(attribute.Key("offset").String(offset)) + parsedLimit, err := strconv.Atoi(limit) if err != nil { - log.Error(r.Context(), "Could not parse limit parameter", zap.Error(err)) + log.Error(ctx, "Could not parse limit parameter", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusBadRequest, "Could not parse limit parameter") return } parsedOffset, err := strconv.Atoi(offset) if err != nil { - log.Error(r.Context(), "Could not parse offset parameter", zap.Error(err)) + log.Error(ctx, "Could not parse offset parameter", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusBadRequest, "Could not parse offset parameter") return } if !user.HasTeamAccess(team) && !user.HasApplicationAccess("", "", "", []string{""}) { - log.Warn(r.Context(), "The user is not authorized to view the applications", zap.Error(err), zap.String("team", team)) + log.Warn(ctx, "The user is not authorized to view the applications", zap.String("team", team)) + span.RecordError(fmt.Errorf("user is not authorized to view all applications")) + span.SetStatus(codes.Error, "user is not authorized to view all applications") errresponse.Render(w, r, nil, http.StatusForbidden, "You are not allowed to view the applications of this team") return } - applications, err := router.storeClient.GetApplicationsByFilter(r.Context(), []string{team}, nil, nil, nil, "", "include", parsedLimit, parsedOffset) + applications, err := router.storeClient.GetApplicationsByFilter(ctx, []string{team}, nil, nil, nil, "", "include", parsedLimit, parsedOffset) if err != nil { - log.Error(r.Context(), "Could not get applications", zap.Error(err)) + log.Error(ctx, "Could not get applications", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get applications") return } - count, err := router.storeClient.GetApplicationsByFilterCount(r.Context(), []string{team}, nil, nil, nil, "", "include") + count, err := router.storeClient.GetApplicationsByFilterCount(ctx, []string{team}, nil, nil, nil, "", "include") if err != nil { - log.Error(r.Context(), "Could not get applications", zap.Error(err)) + log.Error(ctx, "Could not get applications", zap.Error(err)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) errresponse.Render(w, r, err, http.StatusInternalServerError, "Could not get applications") return } @@ -218,6 +299,7 @@ func Mount(storeClient store.Client) chi.Router { router := Router{ chi.NewRouter(), storeClient, + otel.Tracer("applications"), } router.Get("/", router.getApplications) diff --git a/pkg/hub/api/applications/applications_test.go b/pkg/hub/api/applications/applications_test.go index b552ca2b3..fae920704 100644 --- a/pkg/hub/api/applications/applications_test.go +++ b/pkg/hub/api/applications/applications_test.go @@ -11,6 +11,7 @@ import ( "github.com/kobsio/kobs/pkg/hub/store" applicationv1 "github.com/kobsio/kobs/pkg/kube/apis/application/v1" userv1 "github.com/kobsio/kobs/pkg/kube/apis/user/v1" + "go.opentelemetry.io/otel" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/mock" @@ -108,7 +109,7 @@ func TestGetApplications(t *testing.T) { mockStoreClient := &store.MockClient{} tt.prepare(t, mockStoreClient) - router := Router{chi.NewRouter(), mockStoreClient} + router := Router{chi.NewRouter(), mockStoreClient, otel.Tracer("applications")} req, _ := http.NewRequest(http.MethodGet, tt.url, nil) rctx := chi.NewRouteContext() @@ -189,7 +190,7 @@ func TestGetApplicationsCount(t *testing.T) { mockStoreClient := &store.MockClient{} tt.prepare(t, mockStoreClient) - router := Router{chi.NewRouter(), mockStoreClient} + router := Router{chi.NewRouter(), mockStoreClient, otel.Tracer("applications")} req, _ := http.NewRequest(http.MethodGet, tt.url, nil) rctx := chi.NewRouteContext() @@ -243,7 +244,7 @@ func TestGetTags(t *testing.T) { mockStoreClient := &store.MockClient{} tt.prepare(t, mockStoreClient) - router := Router{chi.NewRouter(), mockStoreClient} + router := Router{chi.NewRouter(), mockStoreClient, otel.Tracer("applications")} req, _ := http.NewRequest(http.MethodGet, tt.url, nil) rctx := chi.NewRouteContext() @@ -337,7 +338,7 @@ func TestGetApplication(t *testing.T) { mockStoreClient := &store.MockClient{} tt.prepare(t, mockStoreClient) - router := Router{chi.NewRouter(), mockStoreClient} + router := Router{chi.NewRouter(), mockStoreClient, otel.Tracer("applications")} req, _ := http.NewRequest(http.MethodGet, tt.url, nil) rctx := chi.NewRouteContext() @@ -464,7 +465,7 @@ func TestGetApplicationsByTeam(t *testing.T) { mockStoreClient := &store.MockClient{} tt.prepare(t, mockStoreClient) - router := Router{chi.NewRouter(), mockStoreClient} + router := Router{chi.NewRouter(), mockStoreClient, otel.Tracer("applications")} req, _ := http.NewRequest(http.MethodGet, tt.url, nil) rctx := chi.NewRouteContext() diff --git a/pkg/hub/api/plugins/plugins.go b/pkg/hub/api/plugins/plugins.go index 9a374443d..2ec05701b 100644 --- a/pkg/hub/api/plugins/plugins.go +++ b/pkg/hub/api/plugins/plugins.go @@ -17,7 +17,6 @@ import ( // Router implements the router for the resources plugin, which can be registered in the router for our rest api. type Router struct { *chi.Mux - httpClient *http.Client satellitesClient satellites.Client storeClient store.Client } @@ -81,7 +80,6 @@ func (router *Router) proxyPlugins(w http.ResponseWriter, r *http.Request) { func Mount(satellitesClient satellites.Client, storeClient store.Client) chi.Router { router := Router{ chi.NewRouter(), - &http.Client{}, satellitesClient, storeClient, } diff --git a/pkg/hub/hub.go b/pkg/hub/hub.go index 8832a6e23..540861439 100644 --- a/pkg/hub/hub.go +++ b/pkg/hub/hub.go @@ -19,6 +19,7 @@ import ( "github.com/kobsio/kobs/pkg/log" "github.com/kobsio/kobs/pkg/middleware/debug" "github.com/kobsio/kobs/pkg/middleware/httplog" + "github.com/kobsio/kobs/pkg/middleware/httptracer" "github.com/kobsio/kobs/pkg/middleware/metrics" "github.com/go-chi/chi/v5" @@ -89,6 +90,7 @@ func New(config api.Config, debugUsername, debugPassword, hubAddress string, aut r.Use(middleware.Recoverer) r.Use(middleware.URLFormat) r.Use(userauth.Handler(authEnabled, authHeaderUser, authHeaderTeams, authSessionToken, authSessionInterval, storeClient)) + r.Use(httptracer.Handler("hub")) r.Use(metrics.Metrics) r.Use(httplog.Logger) r.Use(render.SetContentType(render.ContentTypeJSON)) diff --git a/pkg/hub/satellites/satellite/request.go b/pkg/hub/satellites/satellite/request.go index be315b6ef..fd3e08d2a 100644 --- a/pkg/hub/satellites/satellite/request.go +++ b/pkg/hub/satellites/satellite/request.go @@ -8,6 +8,9 @@ import ( authContext "github.com/kobsio/kobs/pkg/hub/middleware/userauth/context" "github.com/kobsio/kobs/pkg/middleware/errresponse" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" ) // doRequest runs a http request against the given url with the given client. It decodes the returned result in the @@ -20,6 +23,8 @@ func doRequest[T any](ctx context.Context, user *authContext.User, client *http. return result, err } + otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header)) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) if user != nil { req.Header.Set("x-kobs-user", user.ToString()) diff --git a/pkg/hub/satellites/satellite/satellite.go b/pkg/hub/satellites/satellite/satellite.go index 354214865..53d0f3d84 100644 --- a/pkg/hub/satellites/satellite/satellite.go +++ b/pkg/hub/satellites/satellite/satellite.go @@ -18,6 +18,12 @@ import ( "github.com/kobsio/kobs/pkg/middleware/errresponse" "github.com/kobsio/kobs/pkg/satellite/plugins/plugin" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" "go.uber.org/zap" ) @@ -45,6 +51,7 @@ type client struct { config Config httpClient *http.Client proxyURL *url.URL + tracer trace.Tracer } func (c *client) GetName() string { @@ -52,48 +59,144 @@ func (c *client) GetName() string { } func (c *client) GetPlugins(ctx context.Context) ([]plugin.Instance, error) { - return doRequest[[]plugin.Instance](ctx, nil, c.httpClient, c.config.Address+"/api/plugins", c.config.Token) + ctx, span := c.tracer.Start(ctx, "satellite.GetPlugins") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + + res, err := doRequest[[]plugin.Instance](ctx, nil, c.httpClient, c.config.Address+"/api/plugins", c.config.Token) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return res, err } func (c *client) GetClusters(ctx context.Context) ([]string, error) { - return doRequest[[]string](ctx, nil, c.httpClient, c.config.Address+"/api/clusters", c.config.Token) + ctx, span := c.tracer.Start(ctx, "satellite.GetClusters") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + + res, err := doRequest[[]string](ctx, nil, c.httpClient, c.config.Address+"/api/clusters", c.config.Token) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return res, err } func (c *client) GetNamespaces(ctx context.Context) (map[string][]string, error) { - return doRequest[map[string][]string](ctx, nil, c.httpClient, c.config.Address+"/api/clusters/namespaces", c.config.Token) + + ctx, span := c.tracer.Start(ctx, "satellite.GetNamespaces") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + + res, err := doRequest[map[string][]string](ctx, nil, c.httpClient, c.config.Address+"/api/clusters/namespaces", c.config.Token) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return res, err } func (c *client) GetCRDs(ctx context.Context) ([]cluster.CRD, error) { - return doRequest[[]cluster.CRD](ctx, nil, c.httpClient, c.config.Address+"/api/clusters/crds", c.config.Token) + ctx, span := c.tracer.Start(ctx, "satellite.GetCRDs") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + + res, err := doRequest[[]cluster.CRD](ctx, nil, c.httpClient, c.config.Address+"/api/clusters/crds", c.config.Token) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return res, err } func (c *client) GetApplications(ctx context.Context) ([]applicationv1.ApplicationSpec, error) { - return doRequest[[]applicationv1.ApplicationSpec](ctx, nil, c.httpClient, c.config.Address+"/api/applications", c.config.Token) + ctx, span := c.tracer.Start(ctx, "satellite.GetApplications") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + + res, err := doRequest[[]applicationv1.ApplicationSpec](ctx, nil, c.httpClient, c.config.Address+"/api/applications", c.config.Token) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return res, err } func (c *client) GetDashboards(ctx context.Context) ([]dashboardv1.DashboardSpec, error) { - return doRequest[[]dashboardv1.DashboardSpec](ctx, nil, c.httpClient, c.config.Address+"/api/dashboards", c.config.Token) + ctx, span := c.tracer.Start(ctx, "satellite.GetDashboards") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + + res, err := doRequest[[]dashboardv1.DashboardSpec](ctx, nil, c.httpClient, c.config.Address+"/api/dashboards", c.config.Token) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return res, err } func (c *client) GetTeams(ctx context.Context) ([]teamv1.TeamSpec, error) { - return doRequest[[]teamv1.TeamSpec](ctx, nil, c.httpClient, c.config.Address+"/api/teams", c.config.Token) + ctx, span := c.tracer.Start(ctx, "satellite.GetTeams") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + + res, err := doRequest[[]teamv1.TeamSpec](ctx, nil, c.httpClient, c.config.Address+"/api/teams", c.config.Token) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return res, err } func (c *client) GetUsers(ctx context.Context) ([]userv1.UserSpec, error) { - return doRequest[[]userv1.UserSpec](ctx, nil, c.httpClient, c.config.Address+"/api/users", c.config.Token) + ctx, span := c.tracer.Start(ctx, "satellite.GetUsers") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + + res, err := doRequest[[]userv1.UserSpec](ctx, nil, c.httpClient, c.config.Address+"/api/users", c.config.Token) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return res, err } func (c *client) GetResources(ctx context.Context, user *authContext.User, cluster, namespace, name, resource, path, paramName, param string) (map[string]any, error) { - return doRequest[map[string]any](ctx, user, c.httpClient, fmt.Sprintf("%s/api/resources?cluster=%s&namespace=%s&name=%s&resource=%s&path=%s¶mName=%s¶m=%s", c.config.Address, cluster, namespace, name, resource, path, paramName, param), c.config.Token) + ctx, span := c.tracer.Start(ctx, "satellite.GetResources") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + + res, err := doRequest[map[string]any](ctx, user, c.httpClient, fmt.Sprintf("%s/api/resources?cluster=%s&namespace=%s&name=%s&resource=%s&path=%s¶mName=%s¶m=%s", c.config.Address, cluster, namespace, name, resource, path, paramName, param), c.config.Token) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + } + + return res, err } func (c *client) Proxy(w http.ResponseWriter, r *http.Request) { + ctx, span := c.tracer.Start(r.Context(), "satellite.Proxy") + span.SetAttributes(attribute.Key("satellite").String(c.config.Name)) + defer span.End() + proxy := httputil.NewSingleHostReverseProxy(c.proxyURL) proxy.FlushInterval = -1 originalDirector := proxy.Director proxy.Director = func(req *http.Request) { originalDirector(req) + otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header)) req.Host = req.URL.Host req.Header.Set("Authorization", "Bearer "+c.config.Token) @@ -118,8 +221,10 @@ func NewClient(config Config) (Client, error) { return &client{ config: config, httpClient: &http.Client{ - Timeout: 60 * time.Second, + Transport: otelhttp.NewTransport(http.DefaultTransport), + Timeout: 60 * time.Second, }, proxyURL: proxyURL, + tracer: otel.Tracer("satellite"), }, nil } diff --git a/pkg/hub/satellites/satellite/satellite_test.go b/pkg/hub/satellites/satellite/satellite_test.go index 1d3d3638b..a1820233f 100644 --- a/pkg/hub/satellites/satellite/satellite_test.go +++ b/pkg/hub/satellites/satellite/satellite_test.go @@ -1,6 +1,7 @@ package satellite import ( + "context" "io" "net/http" "net/http/httptest" @@ -26,7 +27,7 @@ func TestGetPlugins(t *testing.T) { client, _ := NewClient(Config{Address: ts.URL}) - plugins, err := client.GetPlugins(nil) + plugins, err := client.GetPlugins(context.Background()) require.Error(t, err) require.Empty(t, plugins) } @@ -37,7 +38,7 @@ func TestGetClusters(t *testing.T) { client, _ := NewClient(Config{Address: ts.URL}) - plugins, err := client.GetClusters(nil) + plugins, err := client.GetClusters(context.Background()) require.Error(t, err) require.Empty(t, plugins) } @@ -48,7 +49,7 @@ func TestGetNamespaces(t *testing.T) { client, _ := NewClient(Config{Address: ts.URL}) - namespaces, err := client.GetNamespaces(nil) + namespaces, err := client.GetNamespaces(context.Background()) require.Error(t, err) require.Empty(t, namespaces) } @@ -59,7 +60,7 @@ func TestGetCRDs(t *testing.T) { client, _ := NewClient(Config{Address: ts.URL}) - crds, err := client.GetCRDs(nil) + crds, err := client.GetCRDs(context.Background()) require.Error(t, err) require.Empty(t, crds) } @@ -70,7 +71,7 @@ func TestGetApplications(t *testing.T) { client, _ := NewClient(Config{Address: ts.URL}) - plugins, err := client.GetApplications(nil) + plugins, err := client.GetApplications(context.Background()) require.Error(t, err) require.Empty(t, plugins) } @@ -81,7 +82,7 @@ func TestGetDashboards(t *testing.T) { client, _ := NewClient(Config{Address: ts.URL}) - plugins, err := client.GetDashboards(nil) + plugins, err := client.GetDashboards(context.Background()) require.Error(t, err) require.Empty(t, plugins) } @@ -92,7 +93,7 @@ func TestGetTeams(t *testing.T) { client, _ := NewClient(Config{Address: ts.URL}) - plugins, err := client.GetTeams(nil) + plugins, err := client.GetTeams(context.Background()) require.Error(t, err) require.Empty(t, plugins) } @@ -103,7 +104,7 @@ func TestGetUsers(t *testing.T) { client, _ := NewClient(Config{Address: ts.URL}) - plugins, err := client.GetUsers(nil) + plugins, err := client.GetUsers(context.Background()) require.Error(t, err) require.Empty(t, plugins) } @@ -114,7 +115,7 @@ func TestGetResources(t *testing.T) { client, _ := NewClient(Config{Address: ts.URL}) - plugins, err := client.GetResources(nil, nil, "", "", "", "", "", "", "") + plugins, err := client.GetResources(context.Background(), nil, "", "", "", "", "", "", "") require.Error(t, err) require.Empty(t, plugins) } diff --git a/pkg/hub/store/bolt/bolt.go b/pkg/hub/store/bolt/bolt.go index e9400d1c9..c4a9ae20a 100644 --- a/pkg/hub/store/bolt/bolt.go +++ b/pkg/hub/store/bolt/bolt.go @@ -16,10 +16,15 @@ import ( bh "github.com/timshannon/bolthold" bolt "go.etcd.io/bbolt" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" ) type client struct { - store *bh.Store + store *bh.Store + tracer trace.Tracer } func NewClient(uri string) (*client, error) { @@ -29,11 +34,17 @@ func NewClient(uri string) (*client, error) { } return &client{ - store: store, + store: store, + tracer: otel.Tracer("store"), }, nil } func (c *client) SavePlugins(ctx context.Context, satellite string, plugins []plugin.Instance) error { + _, span := c.tracer.Start(ctx, "store.SavePlugins") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("satellite").String(satellite)) + defer span.End() + updatedAt := time.Now().Unix() err := c.store.Bolt().Update(func(tx *bolt.Tx) error { @@ -51,10 +62,21 @@ func (c *client) SavePlugins(ctx context.Context, satellite string, plugins []pl return c.store.TxDeleteMatching(tx, &plugin.Instance{}, bh.Where("Satellite").Eq(satellite).And("UpdatedAt").Lt(updatedAt)) }) - return err + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil } func (c *client) SaveClusters(ctx context.Context, satellite string, clusters []string) error { + _, span := c.tracer.Start(ctx, "store.SaveClusters") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("satellite").String(satellite)) + defer span.End() + updatedAt := time.Now().Unix() err := c.store.Bolt().Update(func(tx *bolt.Tx) error { @@ -75,10 +97,21 @@ func (c *client) SaveClusters(ctx context.Context, satellite string, clusters [] return c.store.TxDeleteMatching(tx, &shared.Cluster{}, bh.Where("Satellite").Eq(satellite).And("UpdatedAt").Lt(updatedAt)) }) - return err + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil } func (c *client) SaveNamespaces(ctx context.Context, satellite string, namespaces map[string][]string) error { + _, span := c.tracer.Start(ctx, "store.SaveNamespaces") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("satellite").String(satellite)) + defer span.End() + updatedAt := time.Now().Unix() err := c.store.Bolt().Update(func(tx *bolt.Tx) error { @@ -103,10 +136,20 @@ func (c *client) SaveNamespaces(ctx context.Context, satellite string, namespace return c.store.TxDeleteMatching(tx, &shared.Namespace{}, bh.Where("Satellite").Eq(satellite).And("UpdatedAt").Lt(updatedAt)) }) - return err + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil } func (c *client) SaveCRDs(ctx context.Context, crds []cluster.CRD) error { + _, span := c.tracer.Start(ctx, "store.SaveCRDs") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + updatedAtTime := time.Now() updatedAt := updatedAtTime.Unix() @@ -123,10 +166,21 @@ func (c *client) SaveCRDs(ctx context.Context, crds []cluster.CRD) error { return c.store.TxDeleteMatching(tx, &cluster.CRD{}, bh.Where("UpdatedAt").Lt(updatedAtTime.Add(time.Duration(-72*time.Hour)).Unix())) }) - return err + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil } func (c *client) SaveApplications(ctx context.Context, satellite string, applications []applicationv1.ApplicationSpec) error { + _, span := c.tracer.Start(ctx, "store.SaveApplications") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("satellite").String(satellite)) + defer span.End() + updatedAt := time.Now().Unix() err := c.store.Bolt().Update(func(tx *bolt.Tx) error { @@ -146,10 +200,21 @@ func (c *client) SaveApplications(ctx context.Context, satellite string, applica return c.store.TxDeleteMatching(tx, &applicationv1.ApplicationSpec{}, bh.Where("Satellite").Eq(satellite).And("UpdatedAt").Lt(updatedAt)) }) - return err + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil } func (c *client) SaveDashboards(ctx context.Context, satellite string, dashboards []dashboardv1.DashboardSpec) error { + _, span := c.tracer.Start(ctx, "store.SaveDashboards") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("satellite").String(satellite)) + defer span.End() + updatedAt := time.Now().Unix() err := c.store.Bolt().Update(func(tx *bolt.Tx) error { @@ -169,10 +234,21 @@ func (c *client) SaveDashboards(ctx context.Context, satellite string, dashboard return c.store.TxDeleteMatching(tx, &dashboardv1.DashboardSpec{}, bh.Where("Satellite").Eq(satellite).And("UpdatedAt").Lt(updatedAt)) }) - return err + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil } func (c *client) SaveTeams(ctx context.Context, satellite string, teams []teamv1.TeamSpec) error { + _, span := c.tracer.Start(ctx, "store.SaveTeams") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("satellite").String(satellite)) + defer span.End() + updatedAt := time.Now().Unix() err := c.store.Bolt().Update(func(tx *bolt.Tx) error { @@ -192,10 +268,21 @@ func (c *client) SaveTeams(ctx context.Context, satellite string, teams []teamv1 return c.store.TxDeleteMatching(tx, &teamv1.TeamSpec{}, bh.Where("Satellite").Eq(satellite).And("UpdatedAt").Lt(updatedAt)) }) - return err + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil } func (c *client) SaveUsers(ctx context.Context, satellite string, users []userv1.UserSpec) error { + _, span := c.tracer.Start(ctx, "store.SaveUsers") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("satellite").String(satellite)) + defer span.End() + updatedAt := time.Now().Unix() err := c.store.Bolt().Update(func(tx *bolt.Tx) error { @@ -215,10 +302,20 @@ func (c *client) SaveUsers(ctx context.Context, satellite string, users []userv1 return c.store.TxDeleteMatching(tx, &userv1.UserSpec{}, bh.Where("Satellite").Eq(satellite).And("UpdatedAt").Lt(updatedAt)) }) - return err + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil } func (c *client) SaveTags(ctx context.Context, applications []applicationv1.ApplicationSpec) error { + _, span := c.tracer.Start(ctx, "store.SaveTags") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + updatedAtTime := time.Now() updatedAt := updatedAtTime.Unix() @@ -241,15 +338,27 @@ func (c *client) SaveTags(ctx context.Context, applications []applicationv1.Appl return c.store.TxDeleteMatching(tx, &shared.Tag{}, bh.Where("UpdatedAt").Lt(updatedAtTime.Add(time.Duration(-72*time.Hour)).Unix())) }) - return err + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return err + } + + return nil } func (c *client) GetPlugins(ctx context.Context) ([]plugin.Instance, error) { + _, span := c.tracer.Start(ctx, "store.GetPlugins") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var plugins []plugin.Instance query := &bh.Query{} err := c.store.Find(&plugins, query.SortBy("ID")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -257,11 +366,17 @@ func (c *client) GetPlugins(ctx context.Context) ([]plugin.Instance, error) { } func (c *client) GetClusters(ctx context.Context) ([]shared.Cluster, error) { + _, span := c.tracer.Start(ctx, "store.GetClusters") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var clusters []shared.Cluster query := &bh.Query{} err := c.store.Find(&clusters, query.SortBy("ID")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -269,11 +384,17 @@ func (c *client) GetClusters(ctx context.Context) ([]shared.Cluster, error) { } func (c *client) GetNamespaces(ctx context.Context) ([]shared.Namespace, error) { + _, span := c.tracer.Start(ctx, "store.GetNamespaces") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var namespaces []shared.Namespace query := &bh.Query{} err := c.store.Find(&namespaces, query.SortBy("ID")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -281,11 +402,17 @@ func (c *client) GetNamespaces(ctx context.Context) ([]shared.Namespace, error) } func (c *client) GetCRDs(ctx context.Context) ([]cluster.CRD, error) { + _, span := c.tracer.Start(ctx, "store.GetCRDs") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var crds []cluster.CRD query := &bh.Query{} err := c.store.Find(&crds, query.SortBy("ID")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -293,10 +420,17 @@ func (c *client) GetCRDs(ctx context.Context) ([]cluster.CRD, error) { } func (c *client) GetCRDByID(ctx context.Context, id string) (*cluster.CRD, error) { + _, span := c.tracer.Start(ctx, "store.GetCRDByID") + span.SetAttributes(attribute.Key("id").String(id)) + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var crd cluster.CRD err := c.store.Get(id, &crd) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -304,6 +438,11 @@ func (c *client) GetCRDByID(ctx context.Context, id string) (*cluster.CRD, error } func (c *client) GetNamespacesByClusterIDs(ctx context.Context, clusterIDs []string) ([]shared.Namespace, error) { + _, span := c.tracer.Start(ctx, "store.GetNamespacesByClusterIDs") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("clusterIDs").StringSlice(clusterIDs)) + defer span.End() + if len(clusterIDs) == 0 { return nil, nil } @@ -312,6 +451,8 @@ func (c *client) GetNamespacesByClusterIDs(ctx context.Context, clusterIDs []str err := c.store.Find(&namespaces, bh.Where("ClusterID").In(bh.Slice(clusterIDs)...).SortBy("ID").Index("ClusterID")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -319,11 +460,17 @@ func (c *client) GetNamespacesByClusterIDs(ctx context.Context, clusterIDs []str } func (c *client) GetApplications(ctx context.Context) ([]applicationv1.ApplicationSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetApplications") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var applications []applicationv1.ApplicationSpec query := &bh.Query{} err := c.store.Find(&applications, query.SortBy("ID")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -331,8 +478,22 @@ func (c *client) GetApplications(ctx context.Context) ([]applicationv1.Applicati } func (c *client) GetApplicationsByFilter(ctx context.Context, teams, clusterIDs, namespaceIDs, tags []string, searchTerm, external string, limit, offset int) ([]applicationv1.ApplicationSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetApplicationsByFilter") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("teams").StringSlice(teams)) + span.SetAttributes(attribute.Key("clusterIDs").StringSlice(clusterIDs)) + span.SetAttributes(attribute.Key("namespaceIDs").StringSlice(namespaceIDs)) + span.SetAttributes(attribute.Key("tags").StringSlice(tags)) + span.SetAttributes(attribute.Key("searchTerm").String(searchTerm)) + span.SetAttributes(attribute.Key("external").String(external)) + span.SetAttributes(attribute.Key("limit").Int(limit)) + span.SetAttributes(attribute.Key("offset").Int(offset)) + defer span.End() + searchTermRegexp, err := regexp.Compile(searchTerm) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -364,6 +525,8 @@ func (c *client) GetApplicationsByFilter(ctx context.Context, teams, clusterIDs, err = c.store.Find(&applications, query.SortBy("Name").Limit(limit).Skip(offset)) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -371,8 +534,20 @@ func (c *client) GetApplicationsByFilter(ctx context.Context, teams, clusterIDs, } func (c *client) GetApplicationsByFilterCount(ctx context.Context, teams, clusterIDs, namespaceIDs, tags []string, searchTerm, external string) (int, error) { + _, span := c.tracer.Start(ctx, "store.GetApplicationsByFilterCount") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("teams").StringSlice(teams)) + span.SetAttributes(attribute.Key("clusterIDs").StringSlice(clusterIDs)) + span.SetAttributes(attribute.Key("namespaceIDs").StringSlice(namespaceIDs)) + span.SetAttributes(attribute.Key("tags").StringSlice(tags)) + span.SetAttributes(attribute.Key("searchTerm").String(searchTerm)) + span.SetAttributes(attribute.Key("external").String(external)) + defer span.End() + searchTermRegexp, err := regexp.Compile(searchTerm) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return 0, err } @@ -402,6 +577,8 @@ func (c *client) GetApplicationsByFilterCount(ctx context.Context, teams, cluste count, err := c.store.Count(&applicationv1.ApplicationSpec{}, query) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return 0, err } @@ -409,10 +586,17 @@ func (c *client) GetApplicationsByFilterCount(ctx context.Context, teams, cluste } func (c *client) GetApplicationByID(ctx context.Context, id string) (*applicationv1.ApplicationSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetApplicationByID") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("id").String(id)) + defer span.End() + var application applicationv1.ApplicationSpec err := c.store.Get(id, &application) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -420,11 +604,17 @@ func (c *client) GetApplicationByID(ctx context.Context, id string) (*applicatio } func (c *client) GetDashboards(ctx context.Context) ([]dashboardv1.DashboardSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetDashboards") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var dashboards []dashboardv1.DashboardSpec query := &bh.Query{} err := c.store.Find(&dashboards, query.SortBy("ID")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -432,10 +622,17 @@ func (c *client) GetDashboards(ctx context.Context) ([]dashboardv1.DashboardSpec } func (c *client) GetDashboardByID(ctx context.Context, id string) (*dashboardv1.DashboardSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetDashboardByID") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("id").String(id)) + defer span.End() + var dashboard dashboardv1.DashboardSpec err := c.store.Get(id, &dashboard) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -443,11 +640,17 @@ func (c *client) GetDashboardByID(ctx context.Context, id string) (*dashboardv1. } func (c *client) GetTeams(ctx context.Context) ([]teamv1.TeamSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetTeams") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var teams []teamv1.TeamSpec query := &bh.Query{} err := c.store.Find(&teams, query.SortBy("Group")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -455,6 +658,11 @@ func (c *client) GetTeams(ctx context.Context) ([]teamv1.TeamSpec, error) { } func (c *client) GetTeamsByGroups(ctx context.Context, groups []string) ([]teamv1.TeamSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetTeamsByGroups") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("groups").StringSlice(groups)) + defer span.End() + if len(groups) == 0 { return nil, nil } @@ -463,6 +671,8 @@ func (c *client) GetTeamsByGroups(ctx context.Context, groups []string) ([]teamv err := c.store.Find(&teams, bh.Where("Group").In(bh.Slice(groups)...).SortBy("Group").Index("Group")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -470,14 +680,23 @@ func (c *client) GetTeamsByGroups(ctx context.Context, groups []string) ([]teamv } func (c *client) GetTeamByGroup(ctx context.Context, group string) (*teamv1.TeamSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetTeamByGroup") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("group").String(group)) + defer span.End() + var teams []teamv1.TeamSpec err := c.store.Find(&teams, bh.Where("Group").Eq(group).Index("Group")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } if len(teams) == 0 { + span.RecordError(fmt.Errorf("team was not found")) + span.SetStatus(codes.Error, "team was not found") return nil, fmt.Errorf("team was not found") } @@ -494,11 +713,17 @@ func (c *client) GetTeamByGroup(ctx context.Context, group string) (*teamv1.Team } func (c *client) GetUsers(ctx context.Context) ([]userv1.UserSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetUsers") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var users []userv1.UserSpec query := &bh.Query{} err := c.store.Find(&users, query.SortBy("ID")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -506,10 +731,17 @@ func (c *client) GetUsers(ctx context.Context) ([]userv1.UserSpec, error) { } func (c *client) GetUsersByEmail(ctx context.Context, email string) ([]userv1.UserSpec, error) { + _, span := c.tracer.Start(ctx, "store.GetUsersByEmail") + span.SetAttributes(attribute.Key("store").String("bolt")) + span.SetAttributes(attribute.Key("email").String(email)) + defer span.End() + var users []userv1.UserSpec err := c.store.Find(&users, bh.Where("Email").Eq(email).Index("Email")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -517,10 +749,16 @@ func (c *client) GetUsersByEmail(ctx context.Context, email string) ([]userv1.Us } func (c *client) GetTags(ctx context.Context) ([]shared.Tag, error) { + _, span := c.tracer.Start(ctx, "store.GetTags") + span.SetAttributes(attribute.Key("store").String("bolt")) + defer span.End() + var tags []shared.Tag err := c.store.Find(&tags, bh.Where("Tag").Ne("").SortBy("Tag")) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } diff --git a/pkg/hub/watcher/watcher.go b/pkg/hub/watcher/watcher.go index 249d4276c..115116859 100644 --- a/pkg/hub/watcher/watcher.go +++ b/pkg/hub/watcher/watcher.go @@ -10,6 +10,9 @@ import ( "github.com/kobsio/kobs/pkg/hub/watcher/worker" "github.com/kobsio/kobs/pkg/log" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "go.uber.org/zap" ) @@ -34,6 +37,7 @@ type client struct { workerPool worker.Pool satellitesClient satellites.Client storeClient store.Client + tracer trace.Tracer } // Watch triggers the internal watch function in the specified interval. This should be called in a new go routine. @@ -60,10 +64,17 @@ func (c *client) watch() { startTime := time.Now() ss := c.satellitesClient.GetSatellites() + ctx, span := c.tracer.Start(context.Background(), "watcher") + defer span.End() + for _, s := range ss { go func(s satellite.Client) { c.workerPool.RunTask(task(func() { - ctx, cancel := context.WithTimeout(log.ContextWithValue(context.Background(), zap.Time("startTime", startTime)), 30*time.Second) + ctx, span := c.tracer.Start(ctx, "watcher.plugins") + span.SetAttributes(attribute.Key("satellite").String(s.GetName())) + defer span.End() + + ctx, cancel := context.WithTimeout(log.ContextWithValue(ctx, zap.Time("startTime", startTime)), 30*time.Second) defer cancel() plugins, err := s.GetPlugins(ctx) @@ -84,7 +95,11 @@ func (c *client) watch() { go func(s satellite.Client) { c.workerPool.RunTask(task(func() { - ctx, cancel := context.WithTimeout(log.ContextWithValue(context.Background(), zap.Time("startTime", startTime)), 30*time.Second) + ctx, span := c.tracer.Start(ctx, "watcher.clusters") + span.SetAttributes(attribute.Key("satellite").String(s.GetName())) + defer span.End() + + ctx, cancel := context.WithTimeout(log.ContextWithValue(ctx, zap.Time("startTime", startTime)), 30*time.Second) defer cancel() clusters, err := s.GetClusters(ctx) @@ -105,7 +120,11 @@ func (c *client) watch() { go func(s satellite.Client) { c.workerPool.RunTask(task(func() { - ctx, cancel := context.WithTimeout(log.ContextWithValue(context.Background(), zap.Time("startTime", startTime)), 30*time.Second) + ctx, span := c.tracer.Start(ctx, "watcher.namespaces") + span.SetAttributes(attribute.Key("satellite").String(s.GetName())) + defer span.End() + + ctx, cancel := context.WithTimeout(log.ContextWithValue(ctx, zap.Time("startTime", startTime)), 30*time.Second) defer cancel() namespaces, err := s.GetNamespaces(ctx) @@ -126,7 +145,11 @@ func (c *client) watch() { go func(s satellite.Client) { c.workerPool.RunTask(task(func() { - ctx, cancel := context.WithTimeout(log.ContextWithValue(context.Background(), zap.Time("startTime", startTime)), 30*time.Second) + ctx, span := c.tracer.Start(ctx, "watcher.crds") + span.SetAttributes(attribute.Key("satellite").String(s.GetName())) + defer span.End() + + ctx, cancel := context.WithTimeout(log.ContextWithValue(ctx, zap.Time("startTime", startTime)), 30*time.Second) defer cancel() crds, err := s.GetCRDs(ctx) @@ -147,7 +170,11 @@ func (c *client) watch() { go func(s satellite.Client) { c.workerPool.RunTask(task(func() { - ctx, cancel := context.WithTimeout(log.ContextWithValue(context.Background(), zap.Time("startTime", startTime)), 30*time.Second) + ctx, span := c.tracer.Start(ctx, "watcher.applications") + span.SetAttributes(attribute.Key("satellite").String(s.GetName())) + defer span.End() + + ctx, cancel := context.WithTimeout(log.ContextWithValue(ctx, zap.Time("startTime", startTime)), 30*time.Second) defer cancel() applications, err := s.GetApplications(ctx) @@ -174,7 +201,11 @@ func (c *client) watch() { go func(s satellite.Client) { c.workerPool.RunTask(task(func() { - ctx, cancel := context.WithTimeout(log.ContextWithValue(context.Background(), zap.Time("startTime", startTime)), 30*time.Second) + ctx, span := c.tracer.Start(ctx, "watcher.dashboards") + span.SetAttributes(attribute.Key("satellite").String(s.GetName())) + defer span.End() + + ctx, cancel := context.WithTimeout(log.ContextWithValue(ctx, zap.Time("startTime", startTime)), 30*time.Second) defer cancel() dashboards, err := s.GetDashboards(ctx) @@ -195,7 +226,11 @@ func (c *client) watch() { go func(s satellite.Client) { c.workerPool.RunTask(task(func() { - ctx, cancel := context.WithTimeout(log.ContextWithValue(context.Background(), zap.Time("startTime", startTime)), 30*time.Second) + ctx, span := c.tracer.Start(ctx, "watcher.teams") + span.SetAttributes(attribute.Key("satellite").String(s.GetName())) + defer span.End() + + ctx, cancel := context.WithTimeout(log.ContextWithValue(ctx, zap.Time("startTime", startTime)), 30*time.Second) defer cancel() teams, err := s.GetTeams(ctx) @@ -216,7 +251,11 @@ func (c *client) watch() { go func(s satellite.Client) { c.workerPool.RunTask(task(func() { - ctx, cancel := context.WithTimeout(log.ContextWithValue(context.Background(), zap.Time("startTime", startTime)), 30*time.Second) + ctx, span := c.tracer.Start(ctx, "watcher.users") + span.SetAttributes(attribute.Key("satellite").String(s.GetName())) + defer span.End() + + ctx, cancel := context.WithTimeout(log.ContextWithValue(ctx, zap.Time("startTime", startTime)), 30*time.Second) defer cancel() users, err := s.GetUsers(ctx) @@ -250,6 +289,7 @@ func NewClient(interval time.Duration, numberOfWorker int64, satellitesClient sa workerPool: workerPool, satellitesClient: satellitesClient, storeClient: storeClient, + tracer: otel.Tracer("watcher"), } go client.watch() diff --git a/pkg/kube/clusters/cluster/cluster.go b/pkg/kube/clusters/cluster/cluster.go index 2e5d0095c..f552d2197 100644 --- a/pkg/kube/clusters/cluster/cluster.go +++ b/pkg/kube/clusters/cluster/cluster.go @@ -25,6 +25,10 @@ import ( "github.com/kobsio/kobs/pkg/log" "github.com/gorilla/websocket" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" "go.uber.org/zap" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -45,7 +49,7 @@ var ( type Client interface { GetName() string GetCRDs() []CRD - GetClient(schema *runtime.Scheme) (controllerRuntimeClient.Client, error) + GetClient(ctx context.Context, schema *runtime.Scheme) (controllerRuntimeClient.Client, error) GetNamespaces(ctx context.Context) ([]string, error) GetResources(ctx context.Context, namespace, name, path, resource, paramName, param string) ([]byte, error) DeleteResource(ctx context.Context, namespace, name, path, resource string, body []byte) error @@ -53,9 +57,9 @@ type Client interface { CreateResource(ctx context.Context, namespace, name, path, resource, subResource string, body []byte) error GetLogs(ctx context.Context, namespace, name, container, regex string, since, tail int64, previous bool) (string, error) StreamLogs(ctx context.Context, conn *websocket.Conn, namespace, name, container string, since, tail int64, follow bool) error - GetTerminal(conn *websocket.Conn, namespace, name, container, shell string) error - CopyFileFromPod(w http.ResponseWriter, namespace, name, container, srcPath string) error - CopyFileToPod(namespace, name, container string, srcFile multipart.File, destPath string) error + GetTerminal(ctx context.Context, conn *websocket.Conn, namespace, name, container, shell string) error + CopyFileFromPod(ctx context.Context, w http.ResponseWriter, namespace, name, container, srcPath string) error + CopyFileToPod(ctx context.Context, namespace, name, container string, srcFile multipart.File, destPath string) error GetApplications(ctx context.Context, namespace string) ([]applicationv1.ApplicationSpec, error) GetApplication(ctx context.Context, namespace, name string) (*applicationv1.ApplicationSpec, error) GetTeams(ctx context.Context, namespace string) ([]teamv1.TeamSpec, error) @@ -84,6 +88,7 @@ type client struct { name string server string crds []CRD + tracer trace.Tracer } // CRD is the format of a Custom Resource Definition. Each CRD must contain a path and resource, which are used for the @@ -122,16 +127,31 @@ func (c *client) GetCRDs() []CRD { } // GetClient returns a new client to perform CRUD operations on Kubernetes objects. -func (c *client) GetClient(schema *runtime.Scheme) (controllerRuntimeClient.Client, error) { - return controllerRuntimeClient.New(c.config, controllerRuntimeClient.Options{ - Scheme: schema, - }) +func (c *client) GetClient(ctx context.Context, schema *runtime.Scheme) (controllerRuntimeClient.Client, error) { + _, span := c.tracer.Start(ctx, "cluster.GetClient") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + defer span.End() + + crc, err := controllerRuntimeClient.New(c.config, controllerRuntimeClient.Options{Scheme: schema}) + if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) + return nil, err + } + + return crc, nil } // GetNamespaces returns all namespaces for the cluster. func (c *client) GetNamespaces(ctx context.Context) ([]string, error) { + ctx, span := c.tracer.Start(ctx, "cluster.GetNamespaces") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + defer span.End() + namespaceList, err := c.clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -148,11 +168,23 @@ func (c *client) GetNamespaces(ctx context.Context) ([]string, error) { // Kubernetes API path and the resource. The name is optional and can be used to get a single resource, instead of a // list of resources. func (c *client) GetResources(ctx context.Context, namespace, name, path, resource, paramName, param string) ([]byte, error) { + ctx, span := c.tracer.Start(ctx, "cluster.GetResources") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + span.SetAttributes(attribute.Key("path").String(path)) + span.SetAttributes(attribute.Key("resource").String(resource)) + span.SetAttributes(attribute.Key("paramName").String(paramName)) + span.SetAttributes(attribute.Key("param").String(param)) + defer span.End() + if name != "" { if namespace != "" { res, err := c.clientset.CoreV1().RESTClient().Get().AbsPath(path).Namespace(namespace).Resource(resource).Name(name).DoRaw(ctx) if err != nil { log.Error(ctx, "Could not get resources", zap.Error(err), zap.String("cluster", c.name), zap.String("namespace", namespace), zap.String("name", name), zap.String("path", path), zap.String("resource", resource)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -162,6 +194,8 @@ func (c *client) GetResources(ctx context.Context, namespace, name, path, resour res, err := c.clientset.CoreV1().RESTClient().Get().AbsPath(path).Resource(resource).Name(name).DoRaw(ctx) if err != nil { log.Error(ctx, "Could not get resources", zap.Error(err), zap.String("cluster", c.name), zap.String("name", name), zap.String("path", path), zap.String("resource", resource)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -171,6 +205,8 @@ func (c *client) GetResources(ctx context.Context, namespace, name, path, resour res, err := c.clientset.CoreV1().RESTClient().Get().AbsPath(path).Namespace(namespace).Resource(resource).Param(paramName, param).DoRaw(ctx) if err != nil { log.Error(ctx, "Could not get resources", zap.Error(err), zap.String("cluster", c.name), zap.String("namespace", namespace), zap.String("path", path), zap.String("resource", resource)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -180,9 +216,19 @@ func (c *client) GetResources(ctx context.Context, namespace, name, path, resour // DeleteResource can be used to delete the given resource. The resource is identified by the Kubernetes API path and // the name of the resource. func (c *client) DeleteResource(ctx context.Context, namespace, name, path, resource string, body []byte) error { + ctx, span := c.tracer.Start(ctx, "cluster.DeleteResource") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + span.SetAttributes(attribute.Key("path").String(path)) + span.SetAttributes(attribute.Key("resource").String(resource)) + defer span.End() + _, err := c.clientset.CoreV1().RESTClient().Delete().AbsPath(path).Namespace(namespace).Resource(resource).Name(name).Body(body).DoRaw(ctx) if err != nil { log.Error(ctx, "Could not delete resources", zap.Error(err), zap.String("cluster", c.name), zap.String("namespace", namespace), zap.String("path", path), zap.String("resource", resource)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -192,9 +238,19 @@ func (c *client) DeleteResource(ctx context.Context, namespace, name, path, reso // PatchResource can be used to edit the given resource. The resource is identified by the Kubernetes API path and the // name of the resource. func (c *client) PatchResource(ctx context.Context, namespace, name, path, resource string, body []byte) error { + ctx, span := c.tracer.Start(ctx, "cluster.PatchResource") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + span.SetAttributes(attribute.Key("path").String(path)) + span.SetAttributes(attribute.Key("resource").String(resource)) + defer span.End() + _, err := c.clientset.CoreV1().RESTClient().Patch(types.JSONPatchType).AbsPath(path).Namespace(namespace).Resource(resource).Name(name).Body(body).DoRaw(ctx) if err != nil { log.Error(ctx, "Could not patch resources", zap.Error(err), zap.String("cluster", c.name), zap.String("namespace", namespace), zap.String("path", path), zap.String("resource", resource)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -204,10 +260,21 @@ func (c *client) PatchResource(ctx context.Context, namespace, name, path, resou // CreateResource can be used to create the given resource. The resource is identified by the Kubernetes API path and the // name of the resource. func (c *client) CreateResource(ctx context.Context, namespace, name, path, resource, subResource string, body []byte) error { + ctx, span := c.tracer.Start(ctx, "cluster.CreateResource") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + span.SetAttributes(attribute.Key("path").String(path)) + span.SetAttributes(attribute.Key("resource").String(resource)) + span.SetAttributes(attribute.Key("subResource").String(subResource)) + defer span.End() + if name != "" && subResource != "" { _, err := c.clientset.CoreV1().RESTClient().Put().AbsPath(path).Namespace(namespace).Name(name).Resource(resource).SubResource(subResource).Body(body).DoRaw(ctx) if err != nil { log.Error(ctx, "Could not create resources", zap.Error(err), zap.String("cluster", c.name), zap.String("namespace", namespace), zap.String("name", name), zap.String("path", path), zap.String("resource", resource), zap.String("subResource", subResource)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -217,6 +284,8 @@ func (c *client) CreateResource(ctx context.Context, namespace, name, path, reso _, err := c.clientset.CoreV1().RESTClient().Post().AbsPath(path).Namespace(namespace).Resource(resource).SubResource(subResource).Body(body).DoRaw(ctx) if err != nil { log.Error(ctx, "Could not create resources", zap.Error(err), zap.String("cluster", c.name), zap.String("namespace", namespace), zap.String("path", path), zap.String("resource", resource)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -227,6 +296,17 @@ func (c *client) CreateResource(ctx context.Context, namespace, name, path, reso // name. Is is also possible to set the time since when the logs should be received and with the previous flag the logs // for the last container can be received. func (c *client) GetLogs(ctx context.Context, namespace, name, container, regex string, since, tail int64, previous bool) (string, error) { + ctx, span := c.tracer.Start(ctx, "cluster.DeleteResource") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + span.SetAttributes(attribute.Key("container").String(container)) + span.SetAttributes(attribute.Key("regex").String(regex)) + span.SetAttributes(attribute.Key("since").Int64(since)) + span.SetAttributes(attribute.Key("tail").Int64(tail)) + span.SetAttributes(attribute.Key("previous").Bool(previous)) + defer span.End() + options := &corev1.PodLogOptions{ Container: container, SinceSeconds: &since, @@ -239,6 +319,8 @@ func (c *client) GetLogs(ctx context.Context, namespace, name, container, regex res, err := c.clientset.CoreV1().Pods(namespace).GetLogs(name, options).DoRaw(ctx) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return "", err } @@ -253,6 +335,8 @@ func (c *client) GetLogs(ctx context.Context, namespace, name, container, regex reg, err := regexp.Compile(regex) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return "", err } @@ -269,6 +353,16 @@ func (c *client) GetLogs(ctx context.Context, namespace, name, container, regex // StreamLogs can be used to stream the logs of the selected Container. For that we are using the passed in WebSocket // connection an write each line returned by the Kubernetes API to this connection. func (c *client) StreamLogs(ctx context.Context, conn *websocket.Conn, namespace, name, container string, since, tail int64, follow bool) error { + ctx, span := c.tracer.Start(ctx, "cluster.StreamLogs") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + span.SetAttributes(attribute.Key("container").String(container)) + span.SetAttributes(attribute.Key("since").Int64(since)) + span.SetAttributes(attribute.Key("tail").Int64(tail)) + span.SetAttributes(attribute.Key("follow").Bool(follow)) + defer span.End() + options := &corev1.PodLogOptions{ Container: container, SinceSeconds: &since, @@ -281,6 +375,8 @@ func (c *client) StreamLogs(ctx context.Context, conn *websocket.Conn, namespace stream, err := c.clientset.CoreV1().Pods(namespace).GetLogs(name, options).Stream(ctx) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -291,6 +387,8 @@ func (c *client) StreamLogs(ctx context.Context, conn *websocket.Conn, namespace for { data, isPrefix, err := reader.ReadLine() if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -309,6 +407,8 @@ func (c *client) StreamLogs(ctx context.Context, conn *websocket.Conn, namespace for _, line := range lines { if err := conn.WriteMessage(websocket.TextMessage, []byte(line)); err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } } @@ -316,13 +416,25 @@ func (c *client) StreamLogs(ctx context.Context, conn *websocket.Conn, namespace } // GetTerminal starts a new terminal session via the given WebSocket connection. -func (c *client) GetTerminal(conn *websocket.Conn, namespace, name, container, shell string) error { +func (c *client) GetTerminal(ctx context.Context, conn *websocket.Conn, namespace, name, container, shell string) error { + ctx, span := c.tracer.Start(ctx, "cluster.GetTerminal") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + span.SetAttributes(attribute.Key("container").String(container)) + span.SetAttributes(attribute.Key("shell").String(shell)) + defer span.End() + reqURL, err := url.Parse(fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/exec?container=%s&command=%s&stdin=true&stdout=true&stderr=true&tty=true", c.config.Host, namespace, name, container, shell)) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } if !terminal.IsValidShell(shell) { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return fmt.Errorf("invalid shell %s", shell) } @@ -336,10 +448,20 @@ func (c *client) GetTerminal(conn *websocket.Conn, namespace, name, container, s } // CopyFileFromPod creates the request URL for downloading a file from the specified container. -func (c *client) CopyFileFromPod(w http.ResponseWriter, namespace, name, container, srcPath string) error { +func (c *client) CopyFileFromPod(ctx context.Context, w http.ResponseWriter, namespace, name, container, srcPath string) error { + ctx, span := c.tracer.Start(ctx, "cluster.CopyFileFromPod") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + span.SetAttributes(attribute.Key("container").String(container)) + span.SetAttributes(attribute.Key("srcPath").String(srcPath)) + defer span.End() + command := fmt.Sprintf("&command=tar&command=cf&command=-&command=%s", srcPath) reqURL, err := url.Parse(fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/exec?container=%s&stdin=true&stdout=true&stderr=true&tty=false%s", c.config.Host, namespace, name, container, command)) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -347,10 +469,20 @@ func (c *client) CopyFileFromPod(w http.ResponseWriter, namespace, name, contain } // CopyFileToPod creates the request URL for uploading a file to the specified container. -func (c *client) CopyFileToPod(namespace, name, container string, srcFile multipart.File, destPath string) error { +func (c *client) CopyFileToPod(ctx context.Context, namespace, name, container string, srcFile multipart.File, destPath string) error { + ctx, span := c.tracer.Start(ctx, "cluster.CopyFileToPod") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + span.SetAttributes(attribute.Key("container").String(container)) + span.SetAttributes(attribute.Key("destPath").String(destPath)) + defer span.End() + command := fmt.Sprintf("&command=cp&command=/dev/stdin&command=%s", destPath) reqURL, err := url.Parse(fmt.Sprintf("%s/api/v1/namespaces/%s/pods/%s/exec?container=%s&stdin=true&stdout=true&stderr=true&tty=false%s", c.config.Host, namespace, name, container, command)) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return err } @@ -360,8 +492,15 @@ func (c *client) CopyFileToPod(namespace, name, container string, srcFile multip // GetApplications returns a list of applications gor the given namespace. It also adds the cluster, namespace and // application name to the Application CR, so that this information must not be specified by the user in the CR. func (c *client) GetApplications(ctx context.Context, namespace string) ([]applicationv1.ApplicationSpec, error) { + ctx, span := c.tracer.Start(ctx, "cluster.GetApplications") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + defer span.End() + applicationsList, err := c.applicationClientset.KobsV1().Applications(namespace).List(ctx, metav1.ListOptions{}) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -379,20 +518,35 @@ func (c *client) GetApplications(ctx context.Context, namespace string) ([]appli // the cluster, namespace and name in the spec of the Application CR. This is needed, so that the user doesn't have to, // provide these fields. func (c *client) GetApplication(ctx context.Context, namespace, name string) (*applicationv1.ApplicationSpec, error) { - teamItem, err := c.applicationClientset.KobsV1().Applications(namespace).Get(ctx, name, metav1.GetOptions{}) + ctx, span := c.tracer.Start(ctx, "cluster.GetApplication") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + defer span.End() + + applicationItem, err := c.applicationClientset.KobsV1().Applications(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } - application := setApplicationDefaults(teamItem.Spec, c.name, namespace, name) + application := setApplicationDefaults(applicationItem.Spec, c.name, namespace, name) return &application, nil } // GetTeams returns a list of teams gor the given namespace. It also adds the cluster, namespace and team name to the // Team CR, so that this information must not be specified by the user in the CR. func (c *client) GetTeams(ctx context.Context, namespace string) ([]teamv1.TeamSpec, error) { + ctx, span := c.tracer.Start(ctx, "cluster.GetTeams") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + defer span.End() + teamsList, err := c.teamClientset.KobsV1().Teams(namespace).List(ctx, metav1.ListOptions{}) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -410,8 +564,16 @@ func (c *client) GetTeams(ctx context.Context, namespace string) ([]teamv1.TeamS // namespace and name in the spec of the Team CR. This is needed, so that the user doesn't have to, provide these // fields. func (c *client) GetTeam(ctx context.Context, namespace, name string) (*teamv1.TeamSpec, error) { + ctx, span := c.tracer.Start(ctx, "cluster.GetTeam") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + defer span.End() + teamItem, err := c.teamClientset.KobsV1().Teams(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -422,8 +584,15 @@ func (c *client) GetTeam(ctx context.Context, namespace, name string) (*teamv1.T // GetDashboards returns a list of dashboards gor the given namespace. It also adds the cluster, namespace and dashboard // name to the Dashboard CR, so that this information must not be specified by the user in the CR. func (c *client) GetDashboards(ctx context.Context, namespace string) ([]dashboardv1.DashboardSpec, error) { + ctx, span := c.tracer.Start(ctx, "cluster.GetDashboards") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + defer span.End() + dashboardsList, err := c.dashboardClientset.KobsV1().Dashboards(namespace).List(ctx, metav1.ListOptions{}) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -441,8 +610,16 @@ func (c *client) GetDashboards(ctx context.Context, namespace string) ([]dashboa // the cluster, namespace and name in the spec of the Dashboard CR. This is needed, so that the user doesn't have to, // provide these fields. func (c *client) GetDashboard(ctx context.Context, namespace, name string) (*dashboardv1.DashboardSpec, error) { + ctx, span := c.tracer.Start(ctx, "cluster.GetDashboard") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + defer span.End() + dashboardItem, err := c.dashboardClientset.KobsV1().Dashboards(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -453,8 +630,15 @@ func (c *client) GetDashboard(ctx context.Context, namespace, name string) (*das // GetUsers returns a list of users for the given namespace. It also adds the cluster, namespace and user name to the // User CR, so that this information must not be specified by the user in the CR. func (c *client) GetUsers(ctx context.Context, namespace string) ([]userv1.UserSpec, error) { + ctx, span := c.tracer.Start(ctx, "cluster.GetUsers") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + defer span.End() + usersList, err := c.userClientset.KobsV1().Users(namespace).List(ctx, metav1.ListOptions{}) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -472,8 +656,16 @@ func (c *client) GetUsers(ctx context.Context, namespace string) ([]userv1.UserS // namespace and name in the spec of the User CR. This is needed, so that the user doesn't have to, provide these // fields. func (c *client) GetUser(ctx context.Context, namespace, name string) (*userv1.UserSpec, error) { + ctx, span := c.tracer.Start(ctx, "cluster.GetUser") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + span.SetAttributes(attribute.Key("namespace").String(namespace)) + span.SetAttributes(attribute.Key("name").String(name)) + defer span.End() + userItem, err := c.userClientset.KobsV1().Users(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) return nil, err } @@ -485,15 +677,20 @@ func (c *client) GetUser(ctx context.Context, namespace, name string) (*userv1.U // CRD format and saved within the cluster. Since this function is only called once after a cluster was loaded, we call // it in a endless loop until it succeeds. func (c *client) loadCRDs() { + ctx, span := c.tracer.Start(context.Background(), "cluster.loadCRDs") + span.SetAttributes(attribute.Key("cluster").String(c.name)) + defer span.End() + offset := 30 for { - ctx := context.Background() log.Debug(ctx, "Load CRDs") res, err := c.clientset.CoreV1().RESTClient().Get().AbsPath("apis/apiextensions.k8s.io/v1/customresourcedefinitions").DoRaw(ctx) if err != nil { log.Error(ctx, "Could not get Custom Resource Definitions", zap.Error(err), zap.String("name", c.name)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) time.Sleep(time.Duration(offset) * time.Second) offset = offset * 2 continue @@ -504,6 +701,8 @@ func (c *client) loadCRDs() { err = json.Unmarshal(res, &crdList) if err != nil { log.Error(ctx, "Could not get unmarshal Custom Resource Definitions List", zap.Error(err), zap.String("name", c.name)) + span.RecordError(err) + span.SetStatus(codes.Error, err.Error()) time.Sleep(time.Duration(offset) * time.Second) offset = offset * 2 continue @@ -590,6 +789,7 @@ func NewClient(name, server string, restConfig *rest.Config) (Client, error) { userClientset: userClientset, name: name, server: server, + tracer: otel.Tracer("cluster"), } go c.loadCRDs() diff --git a/pkg/kube/clusters/cluster/cluster_mock.go b/pkg/kube/clusters/cluster/cluster_mock.go index c37aea000..979b9cb9b 100644 --- a/pkg/kube/clusters/cluster/cluster_mock.go +++ b/pkg/kube/clusters/cluster/cluster_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.12.1. DO NOT EDIT. +// Code generated by mockery v2.12.3. DO NOT EDIT. package cluster @@ -20,8 +20,6 @@ import ( teamv1 "github.com/kobsio/kobs/pkg/kube/apis/team/v1" - testing "testing" - userv1 "github.com/kobsio/kobs/pkg/kube/apis/user/v1" v1 "github.com/kobsio/kobs/pkg/kube/apis/application/v1" @@ -34,13 +32,13 @@ type MockClient struct { mock.Mock } -// CopyFileFromPod provides a mock function with given fields: w, namespace, name, container, srcPath -func (_m *MockClient) CopyFileFromPod(w http.ResponseWriter, namespace string, name string, container string, srcPath string) error { - ret := _m.Called(w, namespace, name, container, srcPath) +// CopyFileFromPod provides a mock function with given fields: ctx, w, namespace, name, container, srcPath +func (_m *MockClient) CopyFileFromPod(ctx context.Context, w http.ResponseWriter, namespace string, name string, container string, srcPath string) error { + ret := _m.Called(ctx, w, namespace, name, container, srcPath) var r0 error - if rf, ok := ret.Get(0).(func(http.ResponseWriter, string, string, string, string) error); ok { - r0 = rf(w, namespace, name, container, srcPath) + if rf, ok := ret.Get(0).(func(context.Context, http.ResponseWriter, string, string, string, string) error); ok { + r0 = rf(ctx, w, namespace, name, container, srcPath) } else { r0 = ret.Error(0) } @@ -48,13 +46,13 @@ func (_m *MockClient) CopyFileFromPod(w http.ResponseWriter, namespace string, n return r0 } -// CopyFileToPod provides a mock function with given fields: namespace, name, container, srcFile, destPath -func (_m *MockClient) CopyFileToPod(namespace string, name string, container string, srcFile multipart.File, destPath string) error { - ret := _m.Called(namespace, name, container, srcFile, destPath) +// CopyFileToPod provides a mock function with given fields: ctx, namespace, name, container, srcFile, destPath +func (_m *MockClient) CopyFileToPod(ctx context.Context, namespace string, name string, container string, srcFile multipart.File, destPath string) error { + ret := _m.Called(ctx, namespace, name, container, srcFile, destPath) var r0 error - if rf, ok := ret.Get(0).(func(string, string, string, multipart.File, string) error); ok { - r0 = rf(namespace, name, container, srcFile, destPath) + if rf, ok := ret.Get(0).(func(context.Context, string, string, string, multipart.File, string) error); ok { + r0 = rf(ctx, namespace, name, container, srcFile, destPath) } else { r0 = ret.Error(0) } @@ -152,13 +150,13 @@ func (_m *MockClient) GetCRDs() []CRD { return r0 } -// GetClient provides a mock function with given fields: schema -func (_m *MockClient) GetClient(schema *runtime.Scheme) (controllerRuntimeClient.Client, error) { - ret := _m.Called(schema) +// GetClient provides a mock function with given fields: ctx, schema +func (_m *MockClient) GetClient(ctx context.Context, schema *runtime.Scheme) (controllerRuntimeClient.Client, error) { + ret := _m.Called(ctx, schema) var r0 controllerRuntimeClient.Client - if rf, ok := ret.Get(0).(func(*runtime.Scheme) controllerRuntimeClient.Client); ok { - r0 = rf(schema) + if rf, ok := ret.Get(0).(func(context.Context, *runtime.Scheme) controllerRuntimeClient.Client); ok { + r0 = rf(ctx, schema) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(controllerRuntimeClient.Client) @@ -166,8 +164,8 @@ func (_m *MockClient) GetClient(schema *runtime.Scheme) (controllerRuntimeClient } var r1 error - if rf, ok := ret.Get(1).(func(*runtime.Scheme) error); ok { - r1 = rf(schema) + if rf, ok := ret.Get(1).(func(context.Context, *runtime.Scheme) error); ok { + r1 = rf(ctx, schema) } else { r1 = ret.Error(1) } @@ -348,13 +346,13 @@ func (_m *MockClient) GetTeams(ctx context.Context, namespace string) ([]teamv1. return r0, r1 } -// GetTerminal provides a mock function with given fields: conn, namespace, name, container, shell -func (_m *MockClient) GetTerminal(conn *websocket.Conn, namespace string, name string, container string, shell string) error { - ret := _m.Called(conn, namespace, name, container, shell) +// GetTerminal provides a mock function with given fields: ctx, conn, namespace, name, container, shell +func (_m *MockClient) GetTerminal(ctx context.Context, conn *websocket.Conn, namespace string, name string, container string, shell string) error { + ret := _m.Called(ctx, conn, namespace, name, container, shell) var r0 error - if rf, ok := ret.Get(0).(func(*websocket.Conn, string, string, string, string) error); ok { - r0 = rf(conn, namespace, name, container, shell) + if rf, ok := ret.Get(0).(func(context.Context, *websocket.Conn, string, string, string, string) error); ok { + r0 = rf(ctx, conn, namespace, name, container, shell) } else { r0 = ret.Error(0) } @@ -441,8 +439,13 @@ func (_m *MockClient) loadCRDs() { _m.Called() } -// NewMockClient creates a new instance of MockClient. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. -func NewMockClient(t testing.TB) *MockClient { +type NewMockClientT interface { + mock.TestingT + Cleanup(func()) +} + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockClient(t NewMockClientT) *MockClient { mock := &MockClient{} mock.Mock.Test(t) diff --git a/pkg/kube/clusters/cluster/cluster_test.go b/pkg/kube/clusters/cluster/cluster_test.go index bc2749297..88a39d252 100644 --- a/pkg/kube/clusters/cluster/cluster_test.go +++ b/pkg/kube/clusters/cluster/cluster_test.go @@ -17,6 +17,7 @@ import ( teamfake "github.com/kobsio/kobs/pkg/kube/clients/team/clientset/versioned/typed/team/v1/fake" userfakeclient "github.com/kobsio/kobs/pkg/kube/clients/user/clientset/versioned/fake" userfake "github.com/kobsio/kobs/pkg/kube/clients/user/clientset/versioned/typed/user/v1/fake" + "go.opentelemetry.io/otel" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -49,6 +50,7 @@ func TestGetNamespaces(t *testing.T) { Name: "kube-system", }, }), + tracer: otel.Tracer("cluster"), } } @@ -91,6 +93,7 @@ func TestGetApplications(t *testing.T) { Namespace: "default", }, }), + tracer: otel.Tracer("cluster"), } } @@ -128,6 +131,7 @@ func TestGetApplication(t *testing.T) { Namespace: "default", }, }), + tracer: otel.Tracer("cluster"), } } @@ -164,6 +168,7 @@ func TestGetTeams(t *testing.T) { Namespace: "default", }, }), + tracer: otel.Tracer("cluster"), } } @@ -201,6 +206,7 @@ func TestGetTeam(t *testing.T) { Namespace: "default", }, }), + tracer: otel.Tracer("cluster"), } } @@ -237,6 +243,7 @@ func TestGetDashboards(t *testing.T) { Namespace: "default", }, }), + tracer: otel.Tracer("cluster"), } } @@ -274,6 +281,7 @@ func TestGetDashboard(t *testing.T) { Namespace: "default", }, }), + tracer: otel.Tracer("cluster"), } } @@ -310,6 +318,7 @@ func TestGetUsers(t *testing.T) { Namespace: "default", }, }), + tracer: otel.Tracer("cluster"), } } @@ -347,6 +356,7 @@ func TestGetUser(t *testing.T) { Namespace: "default", }, }), + tracer: otel.Tracer("cluster"), } } diff --git a/pkg/kube/clusters/cluster/terminal/terminal.go b/pkg/kube/clusters/cluster/terminal/terminal.go index 2d38b8d4b..634f60c31 100644 --- a/pkg/kube/clusters/cluster/terminal/terminal.go +++ b/pkg/kube/clusters/cluster/terminal/terminal.go @@ -120,7 +120,7 @@ func StartProcess(config *rest.Config, reqURL *url.URL, cmd []string, ptyHandler // IsValidShell checks if the user provided shell is an allowed one. func IsValidShell(shell string) bool { - for _, validShell := range []string{"bash", "sh", "powershell", "cmd"} { + for _, validShell := range []string{"bash", "sh", "pwsh", "cmd"} { if validShell == shell { return true } diff --git a/pkg/kube/clusters/clusters.go b/pkg/kube/clusters/clusters.go index 62786506b..4bf5f22ea 100644 --- a/pkg/kube/clusters/clusters.go +++ b/pkg/kube/clusters/clusters.go @@ -1,8 +1,14 @@ package clusters import ( + "context" + "github.com/kobsio/kobs/pkg/kube/clusters/cluster" "github.com/kobsio/kobs/pkg/kube/clusters/provider" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) // Config is the configuration required to load all clusters. It takes an array of providers, which are defined in the @@ -13,23 +19,31 @@ type Config struct { // Client is the interface with all the methods to interact with all loaded Kubernetes clusters. type Client interface { - GetClusters() []cluster.Client - GetCluster(name string) cluster.Client + GetClusters(ctx context.Context) []cluster.Client + GetCluster(ctx context.Context, name string) cluster.Client } // client implements the Client interface and is used to interact with multiple Kubernetes clusters. For that it // contains a list of all cluster clients. type client struct { clusters []cluster.Client + tracer trace.Tracer } // GetClusters returns all loaded Kubernetes clusters. -func (c *client) GetClusters() []cluster.Client { +func (c *client) GetClusters(ctx context.Context) []cluster.Client { + _, span := c.tracer.Start(ctx, "clusters.GetClusters") + defer span.End() + return c.clusters } // GetCluster returns a cluster by it's name. -func (c *client) GetCluster(name string) cluster.Client { +func (c *client) GetCluster(ctx context.Context, name string) cluster.Client { + _, span := c.tracer.Start(ctx, "clusters.GetCluster") + span.SetAttributes(attribute.Key("name").String(name)) + defer span.End() + for _, cl := range c.clusters { if cl.GetName() == name { return cl @@ -60,6 +74,7 @@ func NewClient(config Config) (Client, error) { client := &client{ clusters: tmpClusters, + tracer: otel.Tracer("clusters"), } return client, nil diff --git a/pkg/kube/clusters/clusters_mock.go b/pkg/kube/clusters/clusters_mock.go index d9fce4ba2..52d7b926c 100644 --- a/pkg/kube/clusters/clusters_mock.go +++ b/pkg/kube/clusters/clusters_mock.go @@ -1,9 +1,12 @@ -// Code generated by mockery v2.9.4. DO NOT EDIT. +// Code generated by mockery v2.12.3. DO NOT EDIT. package clusters import ( + context "context" + cluster "github.com/kobsio/kobs/pkg/kube/clusters/cluster" + mock "github.com/stretchr/testify/mock" ) @@ -12,13 +15,13 @@ type MockClient struct { mock.Mock } -// GetCluster provides a mock function with given fields: name -func (_m *MockClient) GetCluster(name string) cluster.Client { - ret := _m.Called(name) +// GetCluster provides a mock function with given fields: ctx, name +func (_m *MockClient) GetCluster(ctx context.Context, name string) cluster.Client { + ret := _m.Called(ctx, name) var r0 cluster.Client - if rf, ok := ret.Get(0).(func(string) cluster.Client); ok { - r0 = rf(name) + if rf, ok := ret.Get(0).(func(context.Context, string) cluster.Client); ok { + r0 = rf(ctx, name) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(cluster.Client) @@ -28,13 +31,13 @@ func (_m *MockClient) GetCluster(name string) cluster.Client { return r0 } -// GetClusters provides a mock function with given fields: -func (_m *MockClient) GetClusters() []cluster.Client { - ret := _m.Called() +// GetClusters provides a mock function with given fields: ctx +func (_m *MockClient) GetClusters(ctx context.Context) []cluster.Client { + ret := _m.Called(ctx) var r0 []cluster.Client - if rf, ok := ret.Get(0).(func() []cluster.Client); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) []cluster.Client); ok { + r0 = rf(ctx) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]cluster.Client) @@ -43,3 +46,18 @@ func (_m *MockClient) GetClusters() []cluster.Client { return r0 } + +type NewMockClientT interface { + mock.TestingT + Cleanup(func()) +} + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockClient(t NewMockClientT) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/kube/clusters/clusters_test.go b/pkg/kube/clusters/clusters_test.go index e253a364b..a85311a44 100644 --- a/pkg/kube/clusters/clusters_test.go +++ b/pkg/kube/clusters/clusters_test.go @@ -1,10 +1,12 @@ package clusters import ( + "context" "testing" "github.com/kobsio/kobs/pkg/kube/clusters/cluster" "github.com/kobsio/kobs/pkg/kube/clusters/provider" + "go.opentelemetry.io/otel" "github.com/stretchr/testify/require" ) @@ -12,9 +14,10 @@ import ( func TestGetClusters(t *testing.T) { c := client{ clusters: []cluster.Client{}, + tracer: otel.Tracer("clusters"), } - clusters := c.GetClusters() + clusters := c.GetClusters(context.Background()) require.Empty(t, clusters) } @@ -22,15 +25,18 @@ func TestGetCluster(t *testing.T) { mockClusterClient := &cluster.MockClient{} mockClusterClient.On("GetName").Return("testname") - c := client{clusters: []cluster.Client{mockClusterClient}} + c := client{ + clusters: []cluster.Client{mockClusterClient}, + tracer: otel.Tracer("clusters"), + } t.Run("name found", func(t *testing.T) { - clusters := c.GetCluster("testname") + clusters := c.GetCluster(context.Background(), "testname") require.NotEmpty(t, clusters) }) t.Run("name not found", func(t *testing.T) { - clusters := c.GetCluster("testname1") + clusters := c.GetCluster(context.Background(), "testname1") require.Empty(t, clusters) }) } @@ -57,6 +63,6 @@ func TestNewClient(t *testing.T) { }, }) require.NoError(t, err) - require.Empty(t, c) + require.NotEmpty(t, c) }) } diff --git a/pkg/middleware/httptracer/httptracer.go b/pkg/middleware/httptracer/httptracer.go new file mode 100644 index 000000000..9426426e1 --- /dev/null +++ b/pkg/middleware/httptracer/httptracer.go @@ -0,0 +1,72 @@ +package httptracer + +import ( + "fmt" + "net/http" + "strings" + + authContext "github.com/kobsio/kobs/pkg/hub/middleware/userauth/context" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/propagation" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" + oteltrace "go.opentelemetry.io/otel/trace" +) + +type traceware struct { + serverName string + tracer oteltrace.Tracer + propagators propagation.TextMapPropagator + handler http.Handler +} + +// Handler sets up a handler to start tracing the incoming requests. The serverName parameter should describe the name +// of the (virtual) server handling the request. +func Handler(serverName string) func(next http.Handler) http.Handler { + return func(handler http.Handler) http.Handler { + return traceware{ + serverName: serverName, + tracer: otel.Tracer("http.request"), + propagators: otel.GetTextMapPropagator(), + handler: handler, + } + } +} + +// ServeHTTP implements the http.Handler interface. It does the actual tracing of the request. +func (tw traceware) ServeHTTP(w http.ResponseWriter, r *http.Request) { + ctx := tw.propagators.Extract(r.Context(), propagation.HeaderCarrier(r.Header)) + ctx, span := tw.tracer.Start(ctx, "http.request", oteltrace.WithSpanKind(oteltrace.SpanKindServer)) + defer span.End() + + r = r.WithContext(ctx) + wrw := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + tw.handler.ServeHTTP(wrw, r) + + // In go-chi/chi, full route pattern could only be extracted once the request is executed + // See: https://github.com/go-chi/chi/issues/150#issuecomment-278850733 + routeStr := strings.Join(chi.RouteContext(r.Context()).RoutePatterns, "") + attrs := semconv.HTTPAttributesFromHTTPStatusCode(wrw.Status()) + attrs = append(attrs, semconv.NetAttributesFromHTTPRequest("tcp", r)...) + attrs = append(attrs, semconv.EndUserAttributesFromHTTPRequest(r)...) + attrs = append(attrs, semconv.HTTPServerAttributesFromHTTPRequest(tw.serverName, routeStr, r)...) + attrs = append(attrs, otelhttp.WroteBytesKey.Int(wrw.BytesWritten())) + attrs = append(attrs, otelhttp.ReadBytesKey.Int64(r.ContentLength)) + + if requestID := middleware.GetReqID(ctx); requestID != "" { + attrs = append(attrs, attribute.Key("request.id").String(requestID)) + } + if user, _ := authContext.GetUser(ctx); user != nil { + attrs = append(attrs, attribute.Key("user.email").String(user.Email)) + } + + span.SetAttributes(attrs...) + span.SetName(fmt.Sprintf("%s:%s", r.Method, routeStr)) + + spanStatus, spanMessage := semconv.SpanStatusFromHTTPStatusCode(wrw.Status()) + span.SetStatus(spanStatus, spanMessage) +} diff --git a/pkg/satellite/api/applications/applications.go b/pkg/satellite/api/applications/applications.go index 8420b2de0..8f08ad330 100644 --- a/pkg/satellite/api/applications/applications.go +++ b/pkg/satellite/api/applications/applications.go @@ -26,7 +26,7 @@ func (router *Router) getApplications(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Get applications") - for _, cluster := range router.clustersClient.GetClusters() { + for _, cluster := range router.clustersClient.GetClusters(r.Context()) { application, err := cluster.GetApplications(r.Context(), "") if err != nil { log.Error(r.Context(), "Could not get applications", zap.Error(err)) diff --git a/pkg/satellite/api/applications/applications_test.go b/pkg/satellite/api/applications/applications_test.go index fb055ac0a..7ac39f080 100644 --- a/pkg/satellite/api/applications/applications_test.go +++ b/pkg/satellite/api/applications/applications_test.go @@ -48,9 +48,9 @@ func TestGetApplications(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetClusters").Return([]cluster.Client{mockClusterClient}) - mockClustersClient.On("GetCluster", "cluster1").Return(mockClusterClient) - mockClustersClient.On("GetCluster", "cluster2").Return(nil) + mockClustersClient.On("GetClusters", mock.Anything).Return([]cluster.Client{mockClusterClient}) + mockClustersClient.On("GetCluster", mock.Anything, "cluster1").Return(mockClusterClient) + mockClustersClient.On("GetCluster", mock.Anything, "cluster2").Return(nil) tt.prepare(mockClusterClient) diff --git a/pkg/satellite/api/clusters/clusters.go b/pkg/satellite/api/clusters/clusters.go index b12e47ce2..aa76c7ee9 100644 --- a/pkg/satellite/api/clusters/clusters.go +++ b/pkg/satellite/api/clusters/clusters.go @@ -23,7 +23,7 @@ type Router struct { func (router *Router) getClusters(w http.ResponseWriter, r *http.Request) { var clusters []string - for _, c := range router.clustersClient.GetClusters() { + for _, c := range router.clustersClient.GetClusters(r.Context()) { clusters = append(clusters, c.GetName()) } @@ -34,7 +34,7 @@ func (router *Router) getNamespaces(w http.ResponseWriter, r *http.Request) { var namespaces map[string][]string namespaces = make(map[string][]string) - for _, c := range router.clustersClient.GetClusters() { + for _, c := range router.clustersClient.GetClusters(r.Context()) { clusterNamespaces, err := c.GetNamespaces(r.Context()) if err != nil { log.Error(r.Context(), "Could not get namespaces", zap.Error(err)) @@ -51,7 +51,7 @@ func (router *Router) getNamespaces(w http.ResponseWriter, r *http.Request) { func (router *Router) getCRDs(w http.ResponseWriter, r *http.Request) { var crds []cluster.CRD - for _, c := range router.clustersClient.GetClusters() { + for _, c := range router.clustersClient.GetClusters(r.Context()) { crds = append(crds, c.GetCRDs()...) } diff --git a/pkg/satellite/api/clusters/clusters_test.go b/pkg/satellite/api/clusters/clusters_test.go index b80457183..22132b872 100644 --- a/pkg/satellite/api/clusters/clusters_test.go +++ b/pkg/satellite/api/clusters/clusters_test.go @@ -35,7 +35,7 @@ func TestGetClusters(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetClusters").Return([]cluster.Client{mockClusterClient}) + mockClustersClient.On("GetClusters", mock.Anything).Return([]cluster.Client{mockClusterClient}) router := Router{chi.NewRouter(), Config{}, mockClustersClient} router.Get("/clusters", router.getClusters) @@ -82,7 +82,7 @@ func TestGetNamespaces(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetClusters").Return([]cluster.Client{mockClusterClient}) + mockClustersClient.On("GetClusters", mock.Anything).Return([]cluster.Client{mockClusterClient}) tt.prepare(mockClusterClient) @@ -112,7 +112,7 @@ func TestGetCRDs(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetClusters").Return([]cluster.Client{mockClusterClient}) + mockClustersClient.On("GetClusters", mock.Anything).Return([]cluster.Client{mockClusterClient}) router := Router{ Mux: chi.NewRouter(), diff --git a/pkg/satellite/api/dashboards/dashboards.go b/pkg/satellite/api/dashboards/dashboards.go index 059bb42f1..8764ba626 100644 --- a/pkg/satellite/api/dashboards/dashboards.go +++ b/pkg/satellite/api/dashboards/dashboards.go @@ -26,7 +26,7 @@ func (router *Router) getDashboards(w http.ResponseWriter, r *http.Request) { var dashboards []v1dashboards.DashboardSpec - for _, cluster := range router.clustersClient.GetClusters() { + for _, cluster := range router.clustersClient.GetClusters(r.Context()) { dashboard, err := cluster.GetDashboards(r.Context(), "") if err != nil { log.Error(r.Context(), "Could not get dashboards", zap.Error(err)) diff --git a/pkg/satellite/api/dashboards/dashboards_test.go b/pkg/satellite/api/dashboards/dashboards_test.go index a955d02d8..be845b50e 100644 --- a/pkg/satellite/api/dashboards/dashboards_test.go +++ b/pkg/satellite/api/dashboards/dashboards_test.go @@ -48,7 +48,7 @@ func TestGetDashboards(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetClusters").Return([]cluster.Client{mockClusterClient}) + mockClustersClient.On("GetClusters", mock.Anything).Return([]cluster.Client{mockClusterClient}) tt.prepare(mockClusterClient) diff --git a/pkg/satellite/api/resources/resources.go b/pkg/satellite/api/resources/resources.go index 871802c16..71c92e21d 100644 --- a/pkg/satellite/api/resources/resources.go +++ b/pkg/satellite/api/resources/resources.go @@ -87,7 +87,7 @@ func (router *Router) getResources(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Get resources parameters", zap.String("cluster", clusterName), zap.String("namespace", namespace), zap.String("name", name), zap.String("resource", resource), zap.String("path", path), zap.String("paramName", paramName), zap.String("param", param)) - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -159,7 +159,7 @@ func (router *Router) deleteResource(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -220,7 +220,7 @@ func (router *Router) patchResource(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -277,7 +277,7 @@ func (router *Router) createResource(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -318,7 +318,7 @@ func (router *Router) getLogs(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Get logs parameters", zap.String("cluster", clusterName), zap.String("namespace", namespace), zap.String("name", name), zap.String("container", container), zap.String("regex", regex), zap.String("since", since), zap.String("previous", previous), zap.String("follow", follow)) - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -522,7 +522,7 @@ func (router *Router) getTerminal(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) msg, _ := json.Marshal(terminal.Message{ @@ -533,7 +533,7 @@ func (router *Router) getTerminal(w http.ResponseWriter, r *http.Request) { return } - err = cluster.GetTerminal(c, namespace, name, container, shell) + err = cluster.GetTerminal(r.Context(), c, namespace, name, container, shell) if err != nil { log.Error(r.Context(), "Could not create terminal", zap.Error(err)) msg, _ := json.Marshal(terminal.Message{ @@ -579,14 +579,14 @@ func (router *Router) getFile(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") return } - err = cluster.CopyFileFromPod(w, namespace, name, container, srcPath) + err = cluster.CopyFileFromPod(r.Context(), w, namespace, name, container, srcPath) if err != nil { log.Error(r.Context(), "Could not copy file", zap.Error(err)) errresponse.Render(w, r, err, http.StatusBadRequest, "Could not copy file") @@ -633,7 +633,7 @@ func (router *Router) postFile(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -642,7 +642,7 @@ func (router *Router) postFile(w http.ResponseWriter, r *http.Request) { destPath = destPath + "/" + h.Filename - err = cluster.CopyFileToPod(namespace, name, container, f, destPath) + err = cluster.CopyFileToPod(r.Context(), namespace, name, container, f, destPath) if err != nil { log.Error(r.Context(), "Could not copy file", zap.Error(err)) errresponse.Render(w, r, err, http.StatusBadRequest, "Could not copy file") diff --git a/pkg/satellite/api/teams/teams.go b/pkg/satellite/api/teams/teams.go index 6115929e2..ea8c2c54e 100644 --- a/pkg/satellite/api/teams/teams.go +++ b/pkg/satellite/api/teams/teams.go @@ -26,7 +26,7 @@ func (router *Router) getTeams(w http.ResponseWriter, r *http.Request) { var teams []teamv1.TeamSpec - for _, cluster := range router.clustersClient.GetClusters() { + for _, cluster := range router.clustersClient.GetClusters(r.Context()) { team, err := cluster.GetTeams(r.Context(), "") if err != nil { log.Error(r.Context(), "Could not get teams", zap.Error(err)) diff --git a/pkg/satellite/api/teams/teams_test.go b/pkg/satellite/api/teams/teams_test.go index fc85f3894..65dc3d97c 100644 --- a/pkg/satellite/api/teams/teams_test.go +++ b/pkg/satellite/api/teams/teams_test.go @@ -45,7 +45,7 @@ func TestGetTeams(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetClusters").Return([]cluster.Client{mockClusterClient}) + mockClustersClient.On("GetClusters", mock.Anything).Return([]cluster.Client{mockClusterClient}) tt.prepare(mockClusterClient) diff --git a/pkg/satellite/api/users/users.go b/pkg/satellite/api/users/users.go index 849b5075f..6edeeba02 100644 --- a/pkg/satellite/api/users/users.go +++ b/pkg/satellite/api/users/users.go @@ -26,7 +26,7 @@ func (router *Router) getUsers(w http.ResponseWriter, r *http.Request) { var users []userv1.UserSpec - for _, cluster := range router.clustersClient.GetClusters() { + for _, cluster := range router.clustersClient.GetClusters(r.Context()) { user, err := cluster.GetUsers(r.Context(), "") if err != nil { log.Error(r.Context(), "Could not get users") diff --git a/pkg/satellite/api/users/users_test.go b/pkg/satellite/api/users/users_test.go index 4746167e3..9db0d1257 100644 --- a/pkg/satellite/api/users/users_test.go +++ b/pkg/satellite/api/users/users_test.go @@ -44,7 +44,7 @@ func TestGetUsers(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetClusters").Return([]cluster.Client{mockClusterClient}) + mockClustersClient.On("GetClusters", mock.Anything).Return([]cluster.Client{mockClusterClient}) tt.prepare(mockClusterClient) diff --git a/pkg/satellite/plugins/plugins.go b/pkg/satellite/plugins/plugins.go index b1bfa65b0..95db0550b 100644 --- a/pkg/satellite/plugins/plugins.go +++ b/pkg/satellite/plugins/plugins.go @@ -39,6 +39,7 @@ func NewClient(pluginDir string, instances []plugin.Instance, clustersClient clu // plugins as they are provided by the user, because they can contain sensible information like usernames and // passwords. Therefore we are just returning the name, description, type and address for a plugin instance. router := chi.NewRouter() + router.Get("/", func(w http.ResponseWriter, r *http.Request) { var frontendInstances []plugin.Instance diff --git a/pkg/satellite/satellite.go b/pkg/satellite/satellite.go index 9875abf00..306b4a2ac 100644 --- a/pkg/satellite/satellite.go +++ b/pkg/satellite/satellite.go @@ -9,6 +9,7 @@ import ( "github.com/kobsio/kobs/pkg/log" "github.com/kobsio/kobs/pkg/middleware/debug" "github.com/kobsio/kobs/pkg/middleware/httplog" + "github.com/kobsio/kobs/pkg/middleware/httptracer" "github.com/kobsio/kobs/pkg/middleware/metrics" "github.com/kobsio/kobs/pkg/satellite/api" "github.com/kobsio/kobs/pkg/satellite/api/applications" @@ -91,6 +92,7 @@ func New(debugUsername, debugPassword, satelliteAddress, satelliteToken string, r.Use(middleware.URLFormat) r.Use(tokenauth.Handler(satelliteToken)) r.Use(user.Handler()) + r.Use(httptracer.Handler("satellite")) r.Use(metrics.Metrics) r.Use(httplog.Logger) r.Use(render.SetContentType(render.ContentTypeJSON)) diff --git a/pkg/tracer/tracer.go b/pkg/tracer/tracer.go new file mode 100644 index 000000000..e9d3acc0d --- /dev/null +++ b/pkg/tracer/tracer.go @@ -0,0 +1,59 @@ +package tracer + +import ( + "github.com/kobsio/kobs/pkg/version" + + "go.opentelemetry.io/contrib/propagators/b3" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/exporters/zipkin" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.10.0" +) + +// newProvider returns an OpenTelemetry TracerProvider configured to use the Jaeger or Zipkin exporter that will send +// spans to the provided url. The returned TracerProvider will also use a Resource configured with all the information +// about the application. +func newProvider(service string, providerType string, url string) (*tracesdk.TracerProvider, error) { + var exp tracesdk.SpanExporter + var err error + + if providerType == "zipkin" { + exp, err = zipkin.New(url) + if err != nil { + return nil, err + } + } else { + exp, err = jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url))) + if err != nil { + return nil, err + } + } + + return tracesdk.NewTracerProvider( + tracesdk.WithBatcher(exp), + tracesdk.WithResource(resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(service), + semconv.ServiceVersionKey.String(version.Version), + )), + ), nil +} + +// Setup is used to setup tracing for kobs. For that we are creating a new TracerProvider and register it as the global +// so any imported instrumentation will default to using it. +func Setup(service string, providerType string, url string) error { + tp, err := newProvider(service, providerType, url) + if err != nil { + return err + } + + otel.SetTracerProvider(tp) + + b3 := b3.New(b3.WithInjectEncoding(b3.B3MultipleHeader | b3.B3SingleHeader)) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, b3)) + + return nil +} diff --git a/plugins/app/src/components/resources/details/Terminal.tsx b/plugins/app/src/components/resources/details/Terminal.tsx index 0a8fb9955..daedd4244 100644 --- a/plugins/app/src/components/resources/details/Terminal.tsx +++ b/plugins/app/src/components/resources/details/Terminal.tsx @@ -86,7 +86,7 @@ const Terminal: React.FunctionComponent = ({ resourceData }: ITe const ws = useRef(null); const term = useRef(new xTerm(TERMINAL_OPTIONS)); const containers = getContainers(resourceData.props); - const shells = ['bash', 'sh', 'powershell', 'cmd']; + const shells = ['bash', 'sh', 'pwsh', 'cmd']; const [options, setOptions] = useState<{ container: string; diff --git a/plugins/plugin-elasticsearch/pkg/instance/instance.go b/plugins/plugin-elasticsearch/pkg/instance/instance.go index d1904b0b7..68314af19 100644 --- a/plugins/plugin-elasticsearch/pkg/instance/instance.go +++ b/plugins/plugin-elasticsearch/pkg/instance/instance.go @@ -12,6 +12,9 @@ import ( "github.com/kobsio/kobs/pkg/middleware/roundtripper" "github.com/mitchellh/mapstructure" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" "go.uber.org/zap" ) @@ -56,6 +59,8 @@ func (i *instance) GetLogs(ctx context.Context, query string, timeStart, timeEnd if err != nil { return nil, err } + + otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header)) req.Header.Set("Content-Type", "application/json") resp, err := i.client.Do(req) @@ -133,7 +138,7 @@ func New(name string, options map[string]interface{}) (Instance, error) { name: name, address: config.Address, client: &http.Client{ - Transport: roundTripper, + Transport: otelhttp.NewTransport(roundTripper), }, }, nil } diff --git a/plugins/plugin-flux/cmd/flux.go b/plugins/plugin-flux/cmd/flux.go index 21fb7199d..a258815df 100644 --- a/plugins/plugin-flux/cmd/flux.go +++ b/plugins/plugin-flux/cmd/flux.go @@ -34,7 +34,7 @@ func appendIfMissing(items []string, item string) []string { } func (router *Router) getClusters(w http.ResponseWriter, r *http.Request) { - clusters := router.clustersClient.GetClusters() + clusters := router.clustersClient.GetClusters(r.Context()) var clusterNames []string for _, cluster := range clusters { @@ -49,7 +49,7 @@ func (router *Router) getNamespaces(w http.ResponseWriter, r *http.Request) { clusterNames := r.URL.Query()["cluster"] for _, clusterName := range clusterNames { - tmpNamespaces, err := router.clustersClient.GetCluster(clusterName).GetNamespaces(r.Context()) + tmpNamespaces, err := router.clustersClient.GetCluster(r.Context(), clusterName).GetNamespaces(r.Context()) if err != nil { log.Error(r.Context(), "Could not get namespaces", zap.Error(err), zap.String("cluster", clusterName)) errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get namespaces") @@ -72,7 +72,7 @@ func (router *Router) sync(w http.ResponseWriter, r *http.Request) { log.Debug(r.Context(), "Sync resource", zap.String("cluster", clusterName), zap.String("namespace", namespace), zap.String("name", name), zap.String("resource", resource)) - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") diff --git a/plugins/plugin-flux/pkg/sync/sync.go b/plugins/plugin-flux/pkg/sync/sync.go index e2c4cd6a2..bddb59cb1 100644 --- a/plugins/plugin-flux/pkg/sync/sync.go +++ b/plugins/plugin-flux/pkg/sync/sync.go @@ -35,7 +35,7 @@ func doReconcileAnnotations(annotations map[string]string) { // Kustomization can be used to sync a Flux Kustomization. For that the cluster, namespace and name for the resource // must be provided. func Kustomization(ctx context.Context, clustersClient cluster.Client, namespace, name string) error { - client, err := clustersClient.GetClient(createScheme()) + client, err := clustersClient.GetClient(ctx, createScheme()) if err != nil { return fmt.Errorf("could not get client: %w", err) } @@ -62,7 +62,7 @@ func Kustomization(ctx context.Context, clustersClient cluster.Client, namespace // HelmRelease can be used to sync a Flux HelmRelease. For that the cluster, namespace and name for the resource must be // provided. func HelmRelease(ctx context.Context, clustersClient cluster.Client, namespace, name string) error { - client, err := clustersClient.GetClient(createScheme()) + client, err := clustersClient.GetClient(ctx, createScheme()) if err != nil { return fmt.Errorf("could not get client: %w", err) } diff --git a/plugins/plugin-grafana/pkg/instance/instance.go b/plugins/plugin-grafana/pkg/instance/instance.go index b28170f63..b9558439a 100644 --- a/plugins/plugin-grafana/pkg/instance/instance.go +++ b/plugins/plugin-grafana/pkg/instance/instance.go @@ -9,6 +9,7 @@ import ( "github.com/kobsio/kobs/pkg/middleware/roundtripper" "github.com/mitchellh/mapstructure" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Config is the structure of the configuration for a single Grafana instance. @@ -91,7 +92,7 @@ func New(name string, options map[string]interface{}) (Instance, error) { address: config.Address, client: &http.Client{ Timeout: 30 * time.Second, - Transport: roundTripper, + Transport: otelhttp.NewTransport(roundTripper), }, }, nil } diff --git a/plugins/plugin-grafana/pkg/instance/request.go b/plugins/plugin-grafana/pkg/instance/request.go index 8a48fee65..f7070b89f 100644 --- a/plugins/plugin-grafana/pkg/instance/request.go +++ b/plugins/plugin-grafana/pkg/instance/request.go @@ -5,6 +5,9 @@ import ( "encoding/json" "fmt" "net/http" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" ) // doRequest runs a http request against the given url with the given client. It decodes the returned result in the @@ -17,6 +20,8 @@ func doRequest[T any](ctx context.Context, client *http.Client, url string) (T, return result, err } + otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header)) + resp, err := client.Do(req) if err != nil { return result, err diff --git a/plugins/plugin-harbor/pkg/instance/instance.go b/plugins/plugin-harbor/pkg/instance/instance.go index 72c0e9241..9056bd2d4 100644 --- a/plugins/plugin-harbor/pkg/instance/instance.go +++ b/plugins/plugin-harbor/pkg/instance/instance.go @@ -7,7 +7,9 @@ import ( "net/url" "github.com/kobsio/kobs/pkg/middleware/roundtripper" + "github.com/mitchellh/mapstructure" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Config is the structure of the configuration for a single Harbor database instance. @@ -152,7 +154,7 @@ func New(name string, options map[string]interface{}) (Instance, error) { name: name, address: config.Address, client: &http.Client{ - Transport: roundTripper, + Transport: otelhttp.NewTransport(roundTripper), }, }, nil } diff --git a/plugins/plugin-harbor/pkg/instance/request.go b/plugins/plugin-harbor/pkg/instance/request.go index 7d1f0312a..93a60d16d 100644 --- a/plugins/plugin-harbor/pkg/instance/request.go +++ b/plugins/plugin-harbor/pkg/instance/request.go @@ -6,6 +6,9 @@ import ( "fmt" "net/http" "strconv" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" ) // doRequest runs a http request against the given url with the given client. It decodes the returned result in the @@ -18,6 +21,7 @@ func doRequest[T any](ctx context.Context, client *http.Client, url string) (T, return result, 0, err } + otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header)) req.Header.Set("X-Accept-Vulnerabilities", "application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0") resp, err := client.Do(req) diff --git a/plugins/plugin-helm/cmd/helm.go b/plugins/plugin-helm/cmd/helm.go index 71c25488f..ce18606ab 100644 --- a/plugins/plugin-helm/cmd/helm.go +++ b/plugins/plugin-helm/cmd/helm.go @@ -40,7 +40,7 @@ func appendIfMissing(items []string, item string) []string { } func (router *Router) getClusters(w http.ResponseWriter, r *http.Request) { - clusters := router.clustersClient.GetClusters() + clusters := router.clustersClient.GetClusters(r.Context()) var clusterNames []string for _, cluster := range clusters { @@ -55,7 +55,7 @@ func (router *Router) getNamespaces(w http.ResponseWriter, r *http.Request) { clusterNames := r.URL.Query()["cluster"] for _, clusterName := range clusterNames { - tmpNamespaces, err := router.clustersClient.GetCluster(clusterName).GetNamespaces(r.Context()) + tmpNamespaces, err := router.clustersClient.GetCluster(r.Context(), clusterName).GetNamespaces(r.Context()) if err != nil { log.Error(r.Context(), "Could not get namespaces", zap.Error(err), zap.String("cluster", clusterName)) errresponse.Render(w, r, err, http.StatusBadRequest, "Could not get namespaces") @@ -88,7 +88,7 @@ func (router *Router) getReleases(w http.ResponseWriter, r *http.Request) { var helmReleases []*client.Release for _, clusterName := range clusterNames { - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -162,7 +162,7 @@ func (router *Router) getRelease(w http.ResponseWriter, r *http.Request) { return } - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") @@ -201,7 +201,7 @@ func (router *Router) getReleaseHistory(w http.ResponseWriter, r *http.Request) return } - cluster := router.clustersClient.GetCluster(clusterName) + cluster := router.clustersClient.GetCluster(r.Context(), clusterName) if cluster == nil { log.Error(r.Context(), "Invalid cluster name", zap.String("cluster", clusterName)) errresponse.Render(w, r, nil, http.StatusBadRequest, "Invalid cluster name") diff --git a/plugins/plugin-helm/cmd/helm_test.go b/plugins/plugin-helm/cmd/helm_test.go index 699e6262c..2840d6d32 100644 --- a/plugins/plugin-helm/cmd/helm_test.go +++ b/plugins/plugin-helm/cmd/helm_test.go @@ -130,8 +130,8 @@ func TestGetReleases(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetCluster", "cluster1").Return(mockClusterClient) - mockClustersClient.On("GetCluster", "cluster2").Return(nil) + mockClustersClient.On("GetCluster", mock.Anything, "cluster1").Return(mockClusterClient) + mockClustersClient.On("GetCluster", mock.Anything, "cluster2").Return(nil) mockHelmClient := &client.MockClient{} mockHelmClient.AssertExpectations(t) @@ -237,8 +237,8 @@ func TestGetRelease(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetCluster", "cluster1").Return(mockClusterClient) - mockClustersClient.On("GetCluster", "cluster2").Return(nil) + mockClustersClient.On("GetCluster", mock.Anything, "cluster1").Return(mockClusterClient) + mockClustersClient.On("GetCluster", mock.Anything, "cluster2").Return(nil) mockHelmClient := &client.MockClient{} mockHelmClient.AssertExpectations(t) @@ -335,8 +335,8 @@ func TestGetReleaseHistory(t *testing.T) { mockClustersClient := &clusters.MockClient{} mockClustersClient.AssertExpectations(t) - mockClustersClient.On("GetCluster", "cluster1").Return(mockClusterClient) - mockClustersClient.On("GetCluster", "cluster2").Return(nil) + mockClustersClient.On("GetCluster", mock.Anything, "cluster1").Return(mockClusterClient) + mockClustersClient.On("GetCluster", mock.Anything, "cluster2").Return(nil) mockHelmClient := &client.MockClient{} mockHelmClient.AssertExpectations(t) diff --git a/plugins/plugin-jaeger/pkg/instance/instance.go b/plugins/plugin-jaeger/pkg/instance/instance.go index ec17b61b8..630e77498 100644 --- a/plugins/plugin-jaeger/pkg/instance/instance.go +++ b/plugins/plugin-jaeger/pkg/instance/instance.go @@ -8,7 +8,11 @@ import ( "net/url" "github.com/kobsio/kobs/pkg/middleware/roundtripper" + "github.com/mitchellh/mapstructure" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" ) // Config is the structure of the configuration for a single Jaeger database instance. @@ -54,6 +58,8 @@ func (i *instance) doRequest(ctx context.Context, url string) (map[string]interf return nil, err } + otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header)) + resp, err := i.client.Do(req) if err != nil { return nil, err @@ -131,7 +137,7 @@ func New(name string, options map[string]interface{}) (Instance, error) { name: name, address: config.Address, client: &http.Client{ - Transport: roundTripper, + Transport: otelhttp.NewTransport(roundTripper), }, }, nil } diff --git a/plugins/plugin-kiali/pkg/instance/instance.go b/plugins/plugin-kiali/pkg/instance/instance.go index 971404e6a..7be6461a5 100644 --- a/plugins/plugin-kiali/pkg/instance/instance.go +++ b/plugins/plugin-kiali/pkg/instance/instance.go @@ -7,9 +7,11 @@ import ( "strconv" "strings" - "github.com/kiali/kiali/models" "github.com/kobsio/kobs/pkg/middleware/roundtripper" + + "github.com/kiali/kiali/models" "github.com/mitchellh/mapstructure" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Config is the structure of the configuration for a single Harbor database instance. @@ -189,7 +191,7 @@ func New(name string, options map[string]interface{}) (Instance, error) { name: name, address: config.Address, client: &http.Client{ - Transport: roundTripper, + Transport: otelhttp.NewTransport(roundTripper), }, }, nil } diff --git a/plugins/plugin-kiali/pkg/instance/request.go b/plugins/plugin-kiali/pkg/instance/request.go index b562e4b41..a77391a27 100644 --- a/plugins/plugin-kiali/pkg/instance/request.go +++ b/plugins/plugin-kiali/pkg/instance/request.go @@ -5,6 +5,9 @@ import ( "encoding/json" "fmt" "net/http" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" ) // doRequest runs a http request against the given url with the given client. It decodes the returned result in the @@ -17,6 +20,8 @@ func doRequest[T any](ctx context.Context, client *http.Client, url string) (T, return result, err } + otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header)) + resp, err := client.Do(req) if err != nil { return result, err diff --git a/plugins/plugin-rss/cmd/rss.go b/plugins/plugin-rss/cmd/rss.go index f5c718c8c..373bdf679 100644 --- a/plugins/plugin-rss/cmd/rss.go +++ b/plugins/plugin-rss/cmd/rss.go @@ -15,6 +15,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/mmcdole/gofeed" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "go.uber.org/zap" ) @@ -92,7 +93,10 @@ func Mount(instances []plugin.Instance, clustersClient clusters.Client) (chi.Rou var rssInstances []instance.Instance for _, i := range instances { - rssInstance := instance.New(i.Name, &http.Client{Timeout: 30 * time.Second}) + rssInstance := instance.New(i.Name, &http.Client{ + Transport: otelhttp.NewTransport(http.DefaultTransport), + Timeout: 30 * time.Second, + }) rssInstances = append(rssInstances, rssInstance) } diff --git a/plugins/plugin-sonarqube/pkg/instance/instance.go b/plugins/plugin-sonarqube/pkg/instance/instance.go index 428ab8b99..a9a1ff08a 100644 --- a/plugins/plugin-sonarqube/pkg/instance/instance.go +++ b/plugins/plugin-sonarqube/pkg/instance/instance.go @@ -7,7 +7,9 @@ import ( "strings" "github.com/kobsio/kobs/pkg/middleware/roundtripper" + "github.com/mitchellh/mapstructure" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Config is the structure of the configuration for a single SonarQube instance. @@ -88,7 +90,7 @@ func New(name string, options map[string]interface{}) (Instance, error) { address: config.Address, organization: config.Organization, client: &http.Client{ - Transport: roundTripper, + Transport: otelhttp.NewTransport(roundTripper), }, metricKeys: metricKeys, }, nil