github.com/kubeshop/testkube@v1.17.23/pkg/telemetry/telemetry.go (about) 1 package telemetry 2 3 import ( 4 "context" 5 "net/http" 6 "os" 7 "runtime" 8 "sync" 9 10 "strings" 11 12 "github.com/spf13/cobra" 13 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 15 "github.com/kubeshop/testkube/cmd/kubectl-testkube/commands/common" 16 "github.com/kubeshop/testkube/cmd/kubectl-testkube/config" 17 httpclient "github.com/kubeshop/testkube/pkg/http" 18 "github.com/kubeshop/testkube/pkg/k8sclient" 19 "github.com/kubeshop/testkube/pkg/log" 20 "github.com/kubeshop/testkube/pkg/utils/text" 21 ) 22 23 var ( 24 client = httpclient.NewClient() 25 senders = map[string]Sender{ 26 "google": GoogleAnalyticsSender, 27 "segmentio": SegmentioSender, 28 } 29 ) 30 31 type Sender func(client *http.Client, payload Payload) (out string, err error) 32 33 // SendServerStartEvent will send event to GA 34 func SendServerStartEvent(clusterId, version string) (string, error) { 35 payload := NewAPIPayload(clusterId, "testkube_api_start", version, "localhost", GetClusterType()) 36 return sendData(senders, payload) 37 } 38 39 // SendCmdEvent will send CLI event to GA 40 func SendCmdEvent(cmd *cobra.Command, version string) (string, error) { 41 // get all sub-commands passed to cli 42 command := strings.TrimPrefix(cmd.CommandPath(), "kubectl-testkube ") 43 if command == "" { 44 command = "root" 45 } 46 47 payload := NewCLIPayload(getCurrentContext(), getUserID(cmd), command, version, "cli_command_execution", GetClusterType()) 48 return sendData(senders, payload) 49 } 50 51 func SendCmdErrorEvent(cmd *cobra.Command, version, errType string, errorStackTrace string) (string, error) { 52 return SendCmdErrorEventWithLicense(cmd, version, errType, errorStackTrace, "") 53 } 54 55 // SendCmdErrorEventWithLicense will send CLI error event with license 56 func SendCmdErrorEventWithLicense(cmd *cobra.Command, version, errType, errorStackTrace, license string) (string, error) { 57 58 // get all sub-commands passed to cli 59 command := strings.TrimPrefix(cmd.CommandPath(), "kubectl-testkube ") 60 if command == "" { 61 command = "root" 62 } 63 64 command += "_error" 65 machineID := GetMachineID() 66 payload := Payload{ 67 ClientID: machineID, 68 UserID: machineID, 69 Events: []Event{ 70 { 71 Name: text.GAEventName(command), 72 Params: Params{ 73 EventCount: 1, 74 EventCategory: "cli_command_execution", 75 AppVersion: version, 76 AppName: "kubectl-testkube", 77 MachineID: machineID, 78 OperatingSystem: runtime.GOOS, 79 Architecture: runtime.GOARCH, 80 Context: getCurrentContext(), 81 ClusterType: GetClusterType(), 82 ErrorType: errType, 83 ErrorStackTrace: errorStackTrace, 84 License: license, 85 }, 86 }}, 87 } 88 89 return sendData(senders, payload) 90 } 91 92 func SendCmdAttemptEvent(cmd *cobra.Command, version string) (string, error) { 93 // TODO pass error 94 payload := NewCLIPayload(getCurrentContext(), getUserID(cmd), getCommand(cmd), version, "cli_command_execution", GetClusterType()) 95 return sendData(senders, payload) 96 } 97 98 // SendCmdAttempWithLicenseEvent will send CLI command attempt event with license 99 func SendCmdAttempWithLicenseEvent(cmd *cobra.Command, version, license string) (string, error) { 100 payload := NewCLIWithLicensePayload(getCurrentContext(), getUserID(cmd), getCommand(cmd), version, "cli_command_execution", GetClusterType(), license) 101 return sendData(senders, payload) 102 } 103 104 // SendCmdInitEvent will send CLI event to GA 105 func SendCmdInitEvent(cmd *cobra.Command, version string) (string, error) { 106 payload := NewCLIPayload(getCurrentContext(), getUserID(cmd), "init", version, "cli_command_execution", GetClusterType()) 107 return sendData(senders, payload) 108 } 109 110 // SendHeartbeatEvent will send CLI event to GA 111 func SendHeartbeatEvent(host, version, clusterId string) (string, error) { 112 payload := NewAPIPayload(clusterId, "testkube_api_heartbeat", version, host, GetClusterType()) 113 return sendData(senders, payload) 114 } 115 116 // SendCreateEvent will send API create event for Test or Test suite to GA 117 func SendCreateEvent(event string, params CreateParams) (string, error) { 118 payload := NewCreatePayload(event, GetClusterType(), params) 119 return sendData(senders, payload) 120 } 121 122 // SendRunEvent will send API run event for Test, or Test suite to GA 123 func SendRunEvent(event string, params RunParams) (string, error) { 124 payload := NewRunPayload(event, GetClusterType(), params) 125 return sendData(senders, payload) 126 } 127 128 // SendCreateWorkflowEvent will send API create event for Test workflows to GA 129 func SendCreateWorkflowEvent(event string, params CreateWorkflowParams) (string, error) { 130 payload := NewCreateWorkflowPayload(event, GetClusterType(), params) 131 return sendData(senders, payload) 132 } 133 134 // SendRunWorkflowEvent will send API run event for Test workflows to GA 135 func SendRunWorkflowEvent(event string, params RunWorkflowParams) (string, error) { 136 payload := NewRunWorkflowPayload(event, GetClusterType(), params) 137 return sendData(senders, payload) 138 } 139 140 // sendData sends data to all telemetry storages in parallel and syncs sending 141 func sendData(senders map[string]Sender, payload Payload) (out string, err error) { 142 var wg sync.WaitGroup 143 wg.Add(len(senders)) 144 for name, sender := range senders { 145 go func(sender Sender, name string) { 146 defer wg.Done() 147 o, err := sender(client, payload) 148 if err != nil { 149 log.DefaultLogger.Debugw("sending telemetry data error", "payload", payload, "error", err.Error()) 150 return 151 } 152 log.DefaultLogger.Debugw("sending telemetry data", "payload", payload, "output", o, "sender", name) 153 }(sender, name) 154 } 155 156 wg.Wait() 157 158 return out, nil 159 } 160 161 func getCommand(cmd *cobra.Command) string { 162 command := strings.TrimPrefix(cmd.CommandPath(), "kubectl-testkube ") 163 if command == "" { 164 command = "root" 165 } 166 167 command += "_attempt" 168 return command 169 } 170 171 func getCurrentContext() RunContext { 172 data, err := config.Load() 173 if err != nil { 174 return RunContext{ 175 Type: "invalid-context", 176 } 177 } 178 return RunContext{ 179 Type: string(data.ContextType), 180 OrganizationId: data.CloudContext.OrganizationId, 181 EnvironmentId: data.CloudContext.EnvironmentId, 182 } 183 } 184 185 func getUserID(cmd *cobra.Command) string { 186 id := "command-cli-user" 187 client, _, err := common.GetClient(cmd) 188 if err == nil && client != nil { 189 info, err := client.GetServerInfo() 190 if err == nil && info.ClusterId != "" { 191 id = info.ClusterId 192 } 193 } 194 data, err := config.Load() 195 if err != nil || data.CloudContext.EnvironmentId == "" { 196 return id 197 } 198 return data.CloudContext.EnvironmentId 199 } 200 201 func GetClusterType() string { 202 203 clientset, err := k8sclient.ConnectToK8s() 204 if err != nil { 205 log.DefaultLogger.Debugw("Creating k8s clientset", err) 206 return "unidentified" 207 } 208 209 pods, err := clientset.CoreV1().Pods("kube-system").List(context.Background(), v1.ListOptions{}) 210 if err != nil { 211 log.DefaultLogger.Debugw("Getting pods from kube-system namespace", err) 212 return "unidentified" 213 } 214 215 // Loop through the pods and check if their name contains the search string. 216 for _, pod := range pods.Items { 217 if strings.Contains(pod.Name, "-kind-") || strings.Contains(pod.Name, "kindnet") { 218 return "kind" 219 } 220 if strings.Contains(pod.Name, "-minikube") { 221 return "minikube" 222 } 223 if strings.Contains(pod.Name, "docker-desktop") { 224 return "docker-desktop" 225 } 226 if strings.Contains(pod.Name, "gke-") || strings.Contains(pod.Name, "-gke-") { 227 return "gke" 228 } 229 if strings.Contains(pod.Name, "aws-") || strings.Contains(pod.Name, "-aws-") { 230 return "eks" 231 } 232 if strings.Contains(pod.Name, "azure-") || strings.Contains(pod.Name, "-azuredisk-") || strings.Contains(pod.Name, "-azurefile-") { 233 return "aks" 234 } 235 if strings.Contains(pod.Name, "openshift") || strings.Contains(pod.Name, "oc-") { 236 return "openshift" 237 } 238 if strings.Contains(pod.Name, "k3d-") { 239 return "k3d" 240 } 241 if strings.Contains(pod.Name, "k3s-") { 242 return "k3s" 243 } 244 if strings.Contains(pod.Name, "microk8s-") { 245 return "microk8s" 246 } 247 } 248 249 return "others" 250 } 251 252 func GetCliRunContext() string { 253 if value, ok := os.LookupEnv("GITHUB_ACTIONS"); ok { 254 if value == "true" { 255 return "github-actions" 256 } 257 } 258 259 if _, ok := os.LookupEnv("TF_BUILD"); ok { 260 return "azure-pipelines" 261 } 262 263 if _, ok := os.LookupEnv("JENKINS_URL"); ok { 264 return "jenkins" 265 } 266 267 if _, ok := os.LookupEnv("JENKINS_HOME"); ok { 268 return "jenkins" 269 } 270 271 if _, ok := os.LookupEnv("CIRCLECI"); ok { 272 return "circleci" 273 } 274 275 if _, ok := os.LookupEnv("GITLAB_CI"); ok { 276 return "gitlab-ci" 277 } 278 279 if _, ok := os.LookupEnv("BUILDKITE"); ok { 280 return "buildkite" 281 } 282 283 if _, ok := os.LookupEnv("TRAVIS"); ok { 284 return "travis-ci" 285 } 286 287 if _, ok := os.LookupEnv("AIRFLOW_HOME"); ok { 288 return "airflow" 289 } 290 291 if _, ok := os.LookupEnv("TEAMCITY_VERSION"); ok { 292 return "teamcity" 293 } 294 295 if _, ok := os.LookupEnv("GO_PIPELINE_NAME"); ok { 296 return "gocd" 297 } 298 299 if _, ok := os.LookupEnv("SEMAPHORE"); ok { 300 return "semaphore-ci" 301 } 302 303 if _, ok := os.LookupEnv("BITBUCKET_BUILD_NUMBER"); ok { 304 return "bitbucket-pipelines" 305 } 306 307 if _, ok := os.LookupEnv("DRONE"); ok { 308 return "drone" 309 } 310 311 return "others|local" 312 }