diff --git a/.golangci.yml b/.golangci.yml index c4cde2f..08ae0c5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,7 +3,7 @@ linters-settings: sections: - standard - default - - prefix(github.com/clastix/kamaji) + - prefix(github.com/clastix/kamaji/) goheader: template: |- Copyright 2022 Clastix Labs diff --git a/cmd/manager/cmd.go b/cmd/manager/cmd.go index b85c462..3a60911 100644 --- a/cmd/manager/cmd.go +++ b/cmd/manager/cmd.go @@ -7,10 +7,12 @@ import ( "flag" "fmt" "io" + "net/http" "os" goRuntime "runtime" "time" + telemetryclient "github.com/clastix/kamaji-telemetry/pkg/client" "github.com/spf13/cobra" "github.com/spf13/viper" "k8s.io/apimachinery/pkg/runtime" @@ -53,6 +55,7 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command { webhookCABundle []byte migrateJobImage string maxConcurrentReconciles int + disableTelemetry bool webhookCAPath string ) @@ -95,8 +98,14 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command { setupLog.Info(fmt.Sprintf("Build date: %s", internal.BuildTime)) setupLog.Info(fmt.Sprintf("Go Version: %s", goRuntime.Version())) setupLog.Info(fmt.Sprintf("Go OS/Arch: %s/%s", goRuntime.GOOS, goRuntime.GOARCH)) + setupLog.Info(fmt.Sprintf("Telemetry enabled: %t", !disableTelemetry)) - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + telemetryClient := telemetryclient.New(http.Client{Timeout: 5 * time.Second}, "https://telemetry.clastix.io") + if disableTelemetry { + telemetryClient = telemetryclient.NewNewOp() + } + + ctrlOpts := ctrl.Options{ Scheme: scheme, Metrics: metricsserver.Options{ BindAddress: metricsBindAddress, @@ -113,7 +122,9 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command { return cache.New(config, opts) }, - }) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrlOpts) if err != nil { setupLog.Error(err, "unable to start manager") @@ -152,6 +163,29 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command { return err } + k8sVersion, versionErr := cmdutils.KubernetesVersion(mgr.GetConfig()) + if versionErr != nil { + setupLog.Error(err, "unable to get kubernetes version") + + k8sVersion = "Unknown" + } + + if !disableTelemetry { + err = mgr.Add(&controllers.TelemetryController{ + Client: mgr.GetClient(), + KubernetesVersion: k8sVersion, + KamajiVersion: internal.GitTag, + TelemetryClient: telemetryClient, + LeaderElectionNamespace: ctrlOpts.LeaderElectionNamespace, + LeaderElectionID: ctrlOpts.LeaderElectionID, + }) + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", "TelemetryController") + + return err + } + } + if err = (&controllers.CertificateLifecycle{Channel: certChannel}).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "CertificateLifecycle") @@ -196,6 +230,14 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command { }, handlers.TenantControlPlaneServiceCIDR{}, }, + routes.TenantControlPlaneTelemetry{}: { + handlers.TenantControlPlaneTelemetry{ + Enabled: !disableTelemetry, + TelemetryClient: telemetryClient, + KamajiVersion: internal.GitTag, + KubernetesVersion: k8sVersion, + }, + }, routes.DataStoreValidate{}: { handlers.DataStoreValidation{Client: mgr.GetClient()}, }, @@ -265,6 +307,7 @@ func NewCmd(scheme *runtime.Scheme) *cobra.Command { cmd.Flags().StringVar(&webhookCAPath, "webhook-ca-path", "/tmp/k8s-webhook-server/serving-certs/ca.crt", "Path to the Manager webhook server CA, required for the TenantControlPlane migration jobs.") cmd.Flags().DurationVar(&controllerReconcileTimeout, "controller-reconcile-timeout", 30*time.Second, "The reconciliation request timeout before the controller withdraw the external resource calls, such as dealing with the Datastore, or the Tenant Control Plane API endpoint.") cmd.Flags().DurationVar(&cacheResyncPeriod, "cache-resync-period", 10*time.Hour, "The controller-runtime.Manager cache resync period.") + cmd.Flags().BoolVar(&disableTelemetry, "disable-telemetry", false, "Disable the analytics traces collection.") cobra.OnInitialize(func() { viper.AutomaticEnv() diff --git a/cmd/utils/k8s_version.go b/cmd/utils/k8s_version.go new file mode 100644 index 0000000..eec4b05 --- /dev/null +++ b/cmd/utils/k8s_version.go @@ -0,0 +1,24 @@ +// Copyright 2022 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "github.com/pkg/errors" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +func KubernetesVersion(config *rest.Config) (string, error) { + cs, csErr := kubernetes.NewForConfig(config) + if csErr != nil { + return "", errors.Wrap(csErr, "cannot create kubernetes clientset") + } + + sv, svErr := cs.ServerVersion() + if svErr != nil { + return "", errors.Wrap(svErr, "cannot get Kubernetes version") + } + + return sv.GitVersion, nil +} diff --git a/controllers/telemetry_controller.go b/controllers/telemetry_controller.go new file mode 100644 index 0000000..806d333 --- /dev/null +++ b/controllers/telemetry_controller.go @@ -0,0 +1,120 @@ +// Copyright 2022 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package controllers + +import ( + "context" + "time" + + "github.com/clastix/kamaji-telemetry/api" + telemetry "github.com/clastix/kamaji-telemetry/pkg/client" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" +) + +type TelemetryController struct { + Client client.Client + TelemetryClient telemetry.Client + LeaderElectionNamespace string + LeaderElectionID string + KamajiVersion string + KubernetesVersion string +} + +func (m *TelemetryController) retrieveControllerUID(ctx context.Context) (string, error) { + var defaultSvc corev1.Service + if err := m.Client.Get(ctx, types.NamespacedName{Name: "kubernetes", Namespace: "default"}, &defaultSvc); err != nil { + return "", errors.Wrap(err, "cannot start the telemetry controller") + } + + return string(defaultSvc.UID), nil +} + +func (m *TelemetryController) Start(ctx context.Context) error { + logger := log.FromContext(ctx) + + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + + uid, err := m.retrieveControllerUID(ctx) + if err != nil { + logger.Error(err, "cannot retrieve controller UID") + + return err + } + // First run to avoid waiting 5 minutes + go m.collectStats(ctx, uid) + + for { + select { + case <-ctx.Done(): + return nil + case <-ticker.C: + go m.collectStats(ctx, uid) + } + } +} + +func (m *TelemetryController) collectStats(ctx context.Context, uid string) { + logger := log.FromContext(ctx) + + stats := api.Stats{ + UUID: uid, + KamajiVersion: m.KamajiVersion, + KubernetesVersion: m.KubernetesVersion, + TenantControlPlanes: api.TenantControlPlane{}, + Datastores: api.Datastores{}, + } + + var tcpList kamajiv1alpha1.TenantControlPlaneList + if err := m.Client.List(ctx, &tcpList); err != nil { + logger.Error(err, "cannot list TenantControlPlane") + + return + } + + for _, tcp := range tcpList.Items { + switch { + case tcp.Spec.ControlPlane.Deployment.Replicas == nil || *tcp.Spec.ControlPlane.Deployment.Replicas == 0: + stats.TenantControlPlanes.Sleeping++ + case tcp.Status.Kubernetes.Version.Status != nil && *tcp.Status.Kubernetes.Version.Status == kamajiv1alpha1.VersionNotReady: + stats.TenantControlPlanes.NotReady++ + case tcp.Status.Kubernetes.Version.Status != nil && *tcp.Status.Kubernetes.Version.Status == kamajiv1alpha1.VersionUpgrading: + stats.TenantControlPlanes.Upgrading++ + default: + stats.TenantControlPlanes.Running++ + } + } + + var datastoreList kamajiv1alpha1.DataStoreList + if err := m.Client.List(ctx, &datastoreList); err != nil { + logger.Error(err, "cannot list DataStores") + + return + } + + for _, ds := range datastoreList.Items { + switch ds.Spec.Driver { + case kamajiv1alpha1.EtcdDriver: + stats.Datastores.Etcd.ShardCount++ + stats.Datastores.Etcd.UsedByCount += len(ds.Status.UsedBy) + case kamajiv1alpha1.KinePostgreSQLDriver: + stats.Datastores.PostgreSQL.ShardCount++ + stats.Datastores.PostgreSQL.UsedByCount += len(ds.Status.UsedBy) + case kamajiv1alpha1.KineMySQLDriver: + stats.Datastores.MySQL.ShardCount++ + stats.Datastores.MySQL.UsedByCount += len(ds.Status.UsedBy) + case kamajiv1alpha1.KineNatsDriver: + stats.Datastores.NATS.ShardCount++ + stats.Datastores.NATS.UsedByCount += len(ds.Status.UsedBy) + } + } + + m.TelemetryClient.PushStats(ctx, stats) +} diff --git a/go.mod b/go.mod index 927719f..31f1240 100644 --- a/go.mod +++ b/go.mod @@ -7,18 +7,19 @@ toolchain go1.22.1 require ( github.com/JamesStewy/go-mysqldump v0.2.2 github.com/blang/semver v3.5.1+incompatible - github.com/go-logr/logr v1.4.1 + github.com/clastix/kamaji-telemetry v1.0.0 + github.com/go-logr/logr v1.4.2 github.com/go-pg/pg/v10 v10.10.6 github.com/go-sql-driver/mysql v1.6.0 github.com/google/go-cmp v0.6.0 - github.com/google/uuid v1.3.0 + github.com/google/uuid v1.6.0 github.com/json-iterator/go v1.1.12 github.com/juju/mutex/v2 v2.0.0 github.com/nats-io/nats.go v1.34.1 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.32.0 github.com/pkg/errors v0.9.1 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.10.1 github.com/testcontainers/testcontainers-go v0.13.0 @@ -35,7 +36,7 @@ require ( k8s.io/kubelet v0.0.0 k8s.io/kubernetes v1.30.2 k8s.io/utils v0.0.0-20230726121419-3b25d923346b - sigs.k8s.io/controller-runtime v0.17.1-0.20240416095710-67b27f27e514 + sigs.k8s.io/controller-runtime v0.18.4 ) require ( diff --git a/go.sum b/go.sum index b38987f..b22dbe4 100644 --- a/go.sum +++ b/go.sum @@ -861,6 +861,8 @@ github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLI github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/clastix/kamaji-telemetry v1.0.0 h1:/s7TVsyQpunD+cBKIaWmZ1yCXXYXgf4uQ4TeXio4moY= +github.com/clastix/kamaji-telemetry v1.0.0/go.mod h1:yhK/I0qEmKQw4mtEZRUnQfjsbbrIZtuR/XXISBrrETU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -992,6 +994,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -1106,8 +1109,9 @@ github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= @@ -1250,8 +1254,9 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -1275,8 +1280,9 @@ github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -1692,8 +1698,9 @@ github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKv github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -2851,8 +2858,8 @@ rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0/go.mod h1:z7+wmGM2dfIiLRfrC6jb5kV2Mq/sK1ZP303cxzkV5Y4= -sigs.k8s.io/controller-runtime v0.17.1-0.20240416095710-67b27f27e514 h1:jHkHzzQ/HXBagSiTfTnfGVdPhE7O0jC7QRXDZ9MK4Xg= -sigs.k8s.io/controller-runtime v0.17.1-0.20240416095710-67b27f27e514/go.mod h1:TLM3OvUJgcqHVBLVRlNylmfbOlOukMLFHtc6jo3EtIQ= +sigs.k8s.io/controller-runtime v0.18.4 h1:87+guW1zhvuPLh1PHybKdYFLU0YJp4FhJRmiHvm5BZw= +sigs.k8s.io/controller-runtime v0.18.4/go.mod h1:TVoGrfdpbA9VRFaRnKgk9P5/atA0pMwq+f+msb9M8Sg= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= diff --git a/internal/webhook/handlers/tcp_telemetry.go b/internal/webhook/handlers/tcp_telemetry.go new file mode 100644 index 0000000..35a8e9a --- /dev/null +++ b/internal/webhook/handlers/tcp_telemetry.go @@ -0,0 +1,97 @@ +// Copyright 2022 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package handlers + +import ( + "context" + + "github.com/clastix/kamaji-telemetry/api" + kamajitelemetry "github.com/clastix/kamaji-telemetry/pkg/client" + "k8s.io/apimachinery/pkg/runtime" + + kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" + "github.com/clastix/kamaji/internal/webhook/utils" +) + +type TenantControlPlaneTelemetry struct { + Enabled bool + TelemetryClient kamajitelemetry.Client + KamajiVersion string + KubernetesVersion string +} + +func (t TenantControlPlaneTelemetry) OnCreate(object runtime.Object) AdmissionResponse { + if t.Enabled { + tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert + + go t.TelemetryClient.PushCreate(context.Background(), api.Create{ + KamajiVersion: t.KamajiVersion, + KubernetesVersion: t.KubernetesVersion, + TenantVersion: tcp.Spec.Kubernetes.Version, + ClusterAPIOwned: func() bool { + for _, owner := range tcp.OwnerReferences { + if owner.Kind == "KamajiControlPlane" { + return true + } + } + + return false + }(), + NetworkProfile: func() string { + switch { + case tcp.Spec.ControlPlane.Ingress != nil: + return api.NetworkProfileIngress + case tcp.Spec.ControlPlane.Service.ServiceType == kamajiv1alpha1.ServiceTypeLoadBalancer: + return api.NetworkProfileLB + case tcp.Spec.ControlPlane.Service.ServiceType == kamajiv1alpha1.ServiceTypeNodePort: + return api.NetworkProfileNodePort + case tcp.Spec.ControlPlane.Service.ServiceType == kamajiv1alpha1.ServiceTypeClusterIP: + return api.NetworkProfileClusterIP + default: + return "Unknown" + } + }(), + }) + } + + return utils.NilOp() +} + +func (t TenantControlPlaneTelemetry) OnDelete(object runtime.Object) AdmissionResponse { + if t.Enabled { + tcp := object.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert + + go t.TelemetryClient.PushDelete(context.Background(), api.Delete{ + KamajiVersion: t.KamajiVersion, + KubernetesVersion: t.KubernetesVersion, + TenantVersion: tcp.Spec.Kubernetes.Version, + Status: t.extractTCPVersion(tcp.Status.Kubernetes.Version.Status), + }) + } + + return utils.NilOp() +} + +func (t TenantControlPlaneTelemetry) OnUpdate(newObject runtime.Object, prevObject runtime.Object) AdmissionResponse { + if t.Enabled { + prevTCP, newTCP := prevObject.(*kamajiv1alpha1.TenantControlPlane), newObject.(*kamajiv1alpha1.TenantControlPlane) //nolint:forcetypeassert + + go t.TelemetryClient.PushUpdate(context.Background(), api.Update{ + KamajiVersion: t.KamajiVersion, + KubernetesVersion: t.KubernetesVersion, + OldTenantVersion: prevTCP.Status.Kubernetes.Version.Version, + NewTenantVersion: newTCP.Spec.Kubernetes.Version, + }) + } + + return utils.NilOp() +} + +func (t TenantControlPlaneTelemetry) extractTCPVersion(status *kamajiv1alpha1.KubernetesVersionStatus) string { + if status == nil { + return "Unknown" + } + + return string(*status) +} diff --git a/internal/webhook/routes/tcp_telemetry.go b/internal/webhook/routes/tcp_telemetry.go new file mode 100644 index 0000000..e0821b3 --- /dev/null +++ b/internal/webhook/routes/tcp_telemetry.go @@ -0,0 +1,22 @@ +// Copyright 2022 Clastix Labs +// SPDX-License-Identifier: Apache-2.0 + +package routes + +import ( + "k8s.io/apimachinery/pkg/runtime" + + kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" +) + +//+kubebuilder:webhook:path=/telemetry,mutating=false,failurePolicy=ignore,sideEffects=None,groups=kamaji.clastix.io,resources=tenantcontrolplanes,verbs=create;update;delete,versions=v1alpha1,name=telemetry.kamaji.clastix.io,admissionReviewVersions=v1 + +type TenantControlPlaneTelemetry struct{} + +func (t TenantControlPlaneTelemetry) GetPath() string { + return "/telemetry" +} + +func (t TenantControlPlaneTelemetry) GetObject() runtime.Object { + return &kamajiv1alpha1.TenantControlPlane{} +}