From 5ebf4c0b5f37be6e2aee642927ea1e76d102a2b8 Mon Sep 17 00:00:00 2001 From: Benedikt Iltisberger Date: Thu, 12 Oct 2023 15:32:54 +0200 Subject: [PATCH] fix: introduced system check. --- .vscode/launch.json | 14 ++- cmd/system.go | 99 +++++++++++++++ dtos/cluster-status.go | 16 +-- dtos/kubernetes-provider.go | 30 ++++- dtos/nodeStatsDto.go | 24 ++-- go.mod | 2 +- go.sum | 2 + kubernetes/deployment.go | 14 +++ kubernetes/nodes.go | 55 +++++++-- kubernetes/utils.go | 236 +++++++++++++++++++++++++++++++++++- utils/utils.go | 29 +++++ 11 files changed, 484 insertions(+), 37 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 7bd674b..2603ff2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -122,12 +122,22 @@ "console": "integratedTerminal" }, { - "name": "test", + "name": "cluster info", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "main.go", + "args": ["cluster", "info"], + "asRoot": false, + "console": "integratedTerminal" + }, + { + "name": "system check", "type": "go", "request": "launch", "mode": "debug", "program": "main.go", - "args": ["system", "test"], + "args": ["system", "check"], "asRoot": false, "console": "integratedTerminal" } diff --git a/cmd/system.go b/cmd/system.go index 4208977..c5a6115 100644 --- a/cmd/system.go +++ b/cmd/system.go @@ -6,7 +6,9 @@ package cmd import ( "fmt" "os" + "strings" + "github.com/jedib0t/go-pretty/v6/table" "github.com/mogenius/punq/kubernetes" "github.com/mogenius/punq/utils" @@ -34,6 +36,86 @@ var resetConfig = &cobra.Command{ }, } +var checkCmd = &cobra.Command{ + Use: "check", + Short: "Check the system for all required components and offer healing.", + Run: func(cmd *cobra.Command, args []string) { + // check internet access + // check for kubectl + // check kubernetes version + // check for ingresscontroller + // check for metrics server + // check for helm + // check for cluster provider + // check for api versions + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Check", "Status", "Message"}) + // check internet access + inetResult, inetErr := utils.CheckInternetAccess() + t.AppendRow( + table.Row{"Internet Access", StatusEmoji(inetResult), StatusMessage(inetErr, "Check your internet connection.", "Internet access works.")}, + ) + t.AppendSeparator() + + // check for kubectl + kubectlResult, kubectlOutput, kubectlErr := utils.IsKubectlInstalled() + t.AppendRow( + table.Row{"kubectl", StatusEmoji(kubectlResult), StatusMessage(kubectlErr, "Plase install kubectl (https://kubernetes.io/docs/tasks/tools/) on your system to proceed.", kubectlOutput)}, + ) + t.AppendSeparator() + + // check kubernetes version + kubernetesVersion := kubernetes.KubernetesVersion(nil) + kubernetesVersionResult := kubernetesVersion != nil + t.AppendRow( + table.Row{"Kubernetes Version", StatusEmoji(kubernetesVersionResult), StatusMessage(kubectlErr, "Cannot determin version of kubernetes.", fmt.Sprintf("Version: %s\nPlatform: %s", kubernetesVersion.String(), kubernetesVersion.Platform))}, + ) + t.AppendSeparator() + + // check for ingresscontroller + ingressType, ingressTypeErr := kubernetes.DetermineIngressControllerType(nil) + t.AppendRow( + table.Row{"Ingress Controller", StatusEmoji(ingressTypeErr == nil), StatusMessage(ingressTypeErr, "Cannot determin ingress controller type.", ingressType.String())}, + ) + t.AppendSeparator() + + // check for metrics server + metricsResult, metricsVersion, metricsErr := kubernetes.IsMetricsServerAvailable(nil) + t.AppendRow( + table.Row{"Metrics Server", StatusEmoji(metricsResult), StatusMessage(metricsErr, "kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml\nNote: Running docker-desktop? Please add '- --kubelet-insecure-tls' to the args sction in the deployment of metrics-server.", metricsVersion)}, + ) + t.AppendSeparator() + + // check for helm + helmResult, helmOutput, helmErr := utils.IsHelmInstalled() + t.AppendRow( + table.Row{"Helm", StatusEmoji(helmResult), StatusMessage(helmErr, "Plase install helm (https://helm.sh/docs/intro/install/) on your system to proceed.", helmOutput)}, + ) + t.AppendSeparator() + + // check cluster provider + clusterProvOutput, clusterProvErr := kubernetes.GuessClusterProvider(nil) + t.AppendRow( + table.Row{"Cluster Provider", StatusEmoji(clusterProvErr == nil), StatusMessage(clusterProvErr, "We could not determine the provider of this cluster.", string(clusterProvOutput))}, + ) + t.AppendSeparator() + + // API Versions + apiVerResult, apiVerErr := kubernetes.ApiVersions(nil) + apiVersStr := "" + for _, entry := range apiVerResult { + apiVersStr += fmt.Sprintf("%s\n", entry) + } + apiVersStr = strings.TrimRight(apiVersStr, "\n\r") + t.AppendRow( + table.Row{"API Versions", StatusEmoji(len(apiVerResult) > 0), StatusMessage(apiVerErr, "Cannot determin API versions.", apiVersStr)}, + ) + t.Render() + }, +} + var infoCmd = &cobra.Command{ Use: "info", Short: "Print information and exit.", @@ -61,4 +143,21 @@ func init() { systemCmd.AddCommand(resetConfig) systemCmd.AddCommand(infoCmd) systemCmd.AddCommand(ingressControllerCmd) + systemCmd.AddCommand(checkCmd) +} + +// UTILS + +func StatusEmoji(works bool) string { + if works { + return "✅" + } + return "❌" +} + +func StatusMessage(err error, solution string, successMsg string) string { + if err != nil { + return fmt.Sprintf("Error: %s\nSolution: %s", err.Error(), solution) + } + return successMsg } diff --git a/dtos/cluster-status.go b/dtos/cluster-status.go index 7eefaf8..20958de 100644 --- a/dtos/cluster-status.go +++ b/dtos/cluster-status.go @@ -5,10 +5,10 @@ import "time" type ClusterStatusDto struct { ClusterName string `json:"clusterName"` Pods int `json:"pods"` - CpuInMilliCores int `json:"cpu"` - CpuLimitInMilliCores int `json:"cpuLimit"` - MemoryInBytes int64 `json:"memoryInBytes"` - MemoryLimitInBytes int64 `json:"memoryLimitInBytes"` + PodCpuUsageInMilliCores int `json:"podCpuUsageInMilliCores"` + PodCpuLimitInMilliCores int `json:"podCpuLimitInMilliCores"` + PodMemoryUsageInBytes int64 `json:"podMemoryUsageInBytes"` + PodMemoryLimitInBytes int64 `json:"podMemoryLimitInBytes"` EphemeralStorageLimitInBytes int64 `json:"ephemeralStorageLimitInBytes"` CurrentTime string `json:"currentTime"` KubernetesVersion string `json:"kubernetesVersion"` @@ -19,10 +19,10 @@ func ClusterStatusDtoExmapleData() ClusterStatusDto { return ClusterStatusDto{ ClusterName: "clusterName", Pods: 1, - CpuInMilliCores: 1, - CpuLimitInMilliCores: 1, - MemoryInBytes: 123, - MemoryLimitInBytes: 1456, + PodCpuUsageInMilliCores: 1, + PodCpuLimitInMilliCores: 1, + PodMemoryUsageInBytes: 123, + PodMemoryLimitInBytes: 1234, EphemeralStorageLimitInBytes: 166, CurrentTime: time.Now().Format(time.RFC3339), KubernetesVersion: "v1.27.2", diff --git a/dtos/kubernetes-provider.go b/dtos/kubernetes-provider.go index fd5fe09..2d32842 100644 --- a/dtos/kubernetes-provider.go +++ b/dtos/kubernetes-provider.go @@ -21,7 +21,7 @@ const ( ACK KubernetesProvider = "ACK" // Alibaba Cloud Kubernetes OKE KubernetesProvider = "OKE" // Oracle Cloud Kubernetes OTC KubernetesProvider = "OTC" // Telekom cloud - OPEN_SHIFT KubernetesProvider = "OPEN_SHIFT" // Telekom cloud + OPEN_SHIFT KubernetesProvider = "OPEN_SHIFT" // RED HAT OpenShift GKE_ON_PREM KubernetesProvider = "GKE_ON_PREM" // Google Kubernetes Engine On-Prem RKE KubernetesProvider = "RKE" // Rancher Kubernetes Engine KUBEADM KubernetesProvider = "KUBEADM" // Kubeadm @@ -36,6 +36,20 @@ const ( STACKIT KubernetesProvider = "SKE" // STACKIT Kubernetes Engine (SKE) IONOS KubernetesProvider = "IONOS" // IONOS Cloud Managed SCALEWAY KubernetesProvider = "SCALEWAY" // scaleway + VMWARE KubernetesProvider = "VMWARE" // VMware Tanzu Kubernetes Grid Integrated Edition + MICROK8S KubernetesProvider = "MICROK8S" // MicroK8s + CIVO KubernetesProvider = "CIVO" // Civo Kubernetes + GIANTSWARM KubernetesProvider = "GIANTSWARM" // Giant Swarm Kubernetes + OVHCLOUD KubernetesProvider = "OVHCLOUD" // OVHCloud Kubernetes + GARDENER KubernetesProvider = "GARDENER" // SAP Gardener Kubernetes + HUAWEI KubernetesProvider = "HUAWEI" // Huawei Cloud Kubernetes + NIRMATA KubernetesProvider = "NIRMATA" // Nirmata Kubernetes + PF9 KubernetesProvider = "PF9" // Platform9 Kubernetes + NKS KubernetesProvider = "NKS" // Netapp Kubernetes Service + APPSCODE KubernetesProvider = "APPSCODE" // AppsCode Kubernetes + LOFT KubernetesProvider = "LOFT" // Loft Kubernetes + SPECTROCLOUD KubernetesProvider = "SPECTROCLOUD" // Spectro Cloud Kubernetes + DIAMANTI KubernetesProvider = "DIAMANTI" // Diamanti Kubernetes ) var ALL_PROVIDER []string = []string{ @@ -72,4 +86,18 @@ var ALL_PROVIDER []string = []string{ string(STACKIT), string(IONOS), string(SCALEWAY), + string(VMWARE), + string(MICROK8S), + string(CIVO), + string(GIANTSWARM), + string(OVHCLOUD), + string(GARDENER), + string(HUAWEI), + string(NIRMATA), + string(PF9), + string(NKS), + string(APPSCODE), + string(LOFT), + string(SPECTROCLOUD), + string(DIAMANTI), } diff --git a/dtos/nodeStatsDto.go b/dtos/nodeStatsDto.go index 2ccaefd..f74172e 100644 --- a/dtos/nodeStatsDto.go +++ b/dtos/nodeStatsDto.go @@ -7,16 +7,18 @@ import ( ) type NodeStat struct { - Name string `json:"name" validate:"required"` - MaschineId string `json:"maschineId" validate:"required"` - Cpus int64 `json:"cpus" validate:"required"` - MemoryInBytes int64 `json:"memoryInBytes" validate:"required"` - EphemeralInBytes int64 `json:"ephemeralInBytes" validate:"required"` - MaxPods int64 `json:"maxPods" validate:"required"` - KubletVersion string `json:"kubletVersion" validate:"required"` - OsType string `json:"osType" validate:"required"` - OsImage string `json:"osImage" validate:"required"` - Architecture string `json:"architecture" validate:"required"` + Name string `json:"name" validate:"required"` + MaschineId string `json:"maschineId" validate:"required"` + CpuInCores int64 `json:"cpuInCores" validate:"required"` + CpuInCoresUtilized float64 `json:"cpuInCoresUtilized" validate:"required"` + MemoryInBytes int64 `json:"memoryInBytes" validate:"required"` + MemoryInBytesUtilized int64 `json:"memoryInBytesUtilized" validate:"required"` + EphemeralInBytes int64 `json:"ephemeralInBytes" validate:"required"` + MaxPods int64 `json:"maxPods" validate:"required"` + KubletVersion string `json:"kubletVersion" validate:"required"` + OsType string `json:"osType" validate:"required"` + OsImage string `json:"osImage" validate:"required"` + Architecture string `json:"architecture" validate:"required"` } func (o *NodeStat) PrintPretty() { @@ -26,7 +28,7 @@ func (o *NodeStat) PrintPretty() { o.OsImage, o.OsType, o.Architecture, - o.Cpus, + o.CpuInCores, utils.BytesToHumanReadable(o.MemoryInBytes), utils.BytesToHumanReadable(o.EphemeralInBytes), o.MaxPods, diff --git a/go.mod b/go.mod index 884b304..057b9f8 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/ivanpirog/coloredcobra v1.0.1 github.com/jaevor/go-nanoid v1.3.0 github.com/jedib0t/go-pretty v4.3.0+incompatible - github.com/jedib0t/go-pretty/v6 v6.4.6 + github.com/jedib0t/go-pretty/v6 v6.4.8 github.com/json-iterator/go v1.1.12 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/spf13/cobra v1.7.0 diff --git a/go.sum b/go.sum index c60d945..1e34334 100644 --- a/go.sum +++ b/go.sum @@ -136,6 +136,8 @@ github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PH github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= +github.com/jedib0t/go-pretty/v6 v6.4.8 h1:HiNzyMSEpsBaduKhmK+CwcpulEeBrTmxutz4oX/oWkg= +github.com/jedib0t/go-pretty/v6 v6.4.8/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= diff --git a/kubernetes/deployment.go b/kubernetes/deployment.go index 0901514..fce8c2a 100644 --- a/kubernetes/deployment.go +++ b/kubernetes/deployment.go @@ -32,6 +32,20 @@ func AllDeployments(namespaceName string, contextId *string) []v1.Deployment { return result } +func AllDeploymentsIncludeIgnored(namespaceName string, contextId *string) []v1.Deployment { + provider, err := NewKubeProvider(contextId) + if err != nil { + return []v1.Deployment{} + } + deploymentList, err := provider.ClientSet.AppsV1().Deployments(namespaceName).List(context.TODO(), metav1.ListOptions{}) + if err != nil { + logger.Log.Errorf("AllDeployments ERROR: %s", err.Error()) + return deploymentList.Items + } + + return deploymentList.Items +} + func AllK8sDeployments(namespaceName string, contextId *string) utils.K8sWorkloadResult { result := []v1.Deployment{} diff --git a/kubernetes/nodes.go b/kubernetes/nodes.go index ec027c3..2ffd53a 100644 --- a/kubernetes/nodes.go +++ b/kubernetes/nodes.go @@ -12,29 +12,64 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1metrics "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) func GetNodeStats(contextId *string) []dtos.NodeStat { result := []dtos.NodeStat{} nodes := ListNodes(contextId) + nodeMetrics := ListNodeMetricss(contextId) for index, node := range nodes { + + utilizedCores := float64(0) + utilizedMemory := int64(0) + if len(nodeMetrics) > 0 { + // Find the corresponding node metrics + var nodeMetric *v1metrics.NodeMetrics + for _, nm := range nodeMetrics { + if nm.Name == node.Name { + nodeMetric = &nm + break + } + } + + // CPU + cpuUsageDec := nodeMetric.Usage.Cpu().AsDec() + cpuUsage, works := cpuUsageDec.Unscaled() + if !works { + logger.Log.Errorf("Failed to get CPU usage for node %s", node.Name) + } + if cpuUsage == 0 { + cpuUsage = 1 + } + utilizedCores = float64(cpuUsage) / 1000000000 + + // Memory + utilizedMemory, works = nodeMetric.Usage.Memory().AsInt64() + if !works { + logger.Log.Errorf("Failed to get MEMORY usage for node %s", node.Name) + } + } + mem, _ := node.Status.Capacity.Memory().AsInt64() cpu, _ := node.Status.Capacity.Cpu().AsInt64() maxPods, _ := node.Status.Capacity.Pods().AsInt64() ephemeral, _ := node.Status.Capacity.StorageEphemeral().AsInt64() nodeStat := dtos.NodeStat{ - Name: fmt.Sprintf("Node-%d", index+1), - MaschineId: node.Status.NodeInfo.MachineID, - Cpus: cpu, - MemoryInBytes: mem, - EphemeralInBytes: ephemeral, - MaxPods: maxPods, - KubletVersion: node.Status.NodeInfo.KubeletVersion, - OsType: node.Status.NodeInfo.OperatingSystem, - OsImage: node.Status.NodeInfo.OSImage, - Architecture: node.Status.NodeInfo.Architecture, + Name: fmt.Sprintf("Node-%d", index+1), + MaschineId: node.Status.NodeInfo.MachineID, + CpuInCores: cpu, + CpuInCoresUtilized: utilizedCores, + MemoryInBytes: mem, + MemoryInBytesUtilized: utilizedMemory, + EphemeralInBytes: ephemeral, + MaxPods: maxPods, + KubletVersion: node.Status.NodeInfo.KubeletVersion, + OsType: node.Status.NodeInfo.OperatingSystem, + OsImage: node.Status.NodeInfo.OSImage, + Architecture: node.Status.NodeInfo.Architecture, } result = append(result, nodeStat) //nodeStat.PrintPretty() diff --git a/kubernetes/utils.go b/kubernetes/utils.go index 14f2362..5081d6a 100644 --- a/kubernetes/utils.go +++ b/kubernetes/utils.go @@ -5,10 +5,13 @@ import ( "fmt" "os" "reflect" + "sort" "strings" "time" + "k8s.io/apimachinery/pkg/runtime/schema" version2 "k8s.io/apimachinery/pkg/version" + v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" "github.com/jedib0t/go-pretty/table" "github.com/mogenius/punq/version" @@ -23,6 +26,7 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/dynamic" "k8s.io/client-go/tools/clientcmd" ) @@ -298,10 +302,10 @@ func ClusterStatus(contextId *string) dtos.ClusterStatusDto { return dtos.ClusterStatusDto{ ClusterName: utils.CONFIG.Kubernetes.ClusterName, Pods: len(result), - CpuInMilliCores: int(cpu), - CpuLimitInMilliCores: int(cpuLimit), - MemoryInBytes: memory, - MemoryLimitInBytes: memoryLimit, + PodCpuUsageInMilliCores: int(cpu), + PodCpuLimitInMilliCores: int(cpuLimit), + PodMemoryUsageInBytes: memory, + PodMemoryLimitInBytes: memoryLimit, EphemeralStorageLimitInBytes: ephemeralStorageLimit, KubernetesVersion: kubernetesVersion, Platform: platform, @@ -360,6 +364,21 @@ func ListNodes(contextId *string) []v1.Node { return nodeMetricsList.Items } +func ListNodeMetricss(contextId *string) []v1beta1.NodeMetrics { + provider, err := NewKubeProviderMetrics(contextId) + if provider == nil || err != nil { + logger.Log.Errorf("ListNodeMetricss ERROR: %s", err.Error()) + return []v1beta1.NodeMetrics{} + } + + nodeMetricsList, err := provider.ClientSet.MetricsV1beta1().NodeMetricses().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + logger.Log.Errorf("ListNodeMetrics ERROR: %s", err.Error()) + return []v1beta1.NodeMetrics{} + } + return nodeMetricsList.Items +} + func podStats(pods map[string]v1.Pod, contextId *string) ([]structs.Stats, error) { provider, err := NewKubeProviderMetrics(contextId) if provider == nil || err != nil { @@ -537,3 +556,212 @@ func DetermineIngressControllerType(contextId *string) (IngressType, error) { return UNKNOWN, fmt.Errorf("unknown ingress controller: %s", unknownController) } + +func IsMetricsServerAvailable(contextId *string) (bool, string, error) { + // kube-system would be the right namespace but if somebody installed it in another namespace we want to find it + deployments := AllDeploymentsIncludeIgnored("", contextId) + + for _, deployment := range deployments { + for key, label := range deployment.Labels { + if key == "k8s-app" && label == "metrics-server" { + if deployment.Status.UnavailableReplicas > 0 { + return false, "", fmt.Errorf("metrics-server installed but not running") + } + return true, deployment.Spec.Template.Spec.Containers[0].Image, nil + } + } + } + + return false, "", fmt.Errorf("no metrics-server found") +} + +func ApiVersions(contextId *string) ([]string, error) { + result := []string{} + + provider, err := NewKubeProvider(contextId) + if provider == nil || err != nil { + return result, err + } + + groupResources, err := provider.ClientSet.DiscoveryClient.ServerPreferredResources() + if err != nil { + fmt.Printf("Error fetching API GroupResources: %v\n", err) + return result, err + } + + for _, groupList := range groupResources { + result = append(result, groupList.GroupVersion) + } + + sort.Slice(result, func(i, j int) bool { + return result[i] < result[j] + }) + + return result, nil +} + +func GuessClusterProvider(contextId *string) (dtos.KubernetesProvider, error) { + provider, err := NewKubeProvider(contextId) + if provider == nil || err != nil { + return dtos.SELF_HOSTED, err + } + + nodes, err := provider.ClientSet.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return dtos.SELF_HOSTED, err + } + + for _, node := range nodes.Items { + labels := node.GetLabels() + + if LabelsContain(labels, "eks.amazonaws.com/capacity") { + return dtos.EKS, nil + } else if LabelsContain(labels, "docker-desktop") { + return dtos.DOCKER_DESKTOP, nil + } else if LabelsContain(labels, "kubernetes.azure.com/role") { + return dtos.AKS, nil + } else if LabelsContain(labels, "cloud.google.com/gke-nodepool") { + return dtos.GKE, nil + } else if LabelsContain(labels, "k3s.io/hostname") { + return dtos.K3S, nil + } else if LabelsContain(labels, "ibm-cloud.kubernetes.io/worker-version") { + return dtos.IBM, nil + } else if LabelsContain(labels, "doks.digitalocean.com/node-id") { + return dtos.DOKS, nil + } else if LabelsContain(labels, "oke.oraclecloud.com/node-pool") { + return dtos.OKE, nil + } else if LabelsContain(labels, "ack.aliyun.com") { + return dtos.ACK, nil + } else if LabelsContain(labels, "node-role.kubernetes.io/master") && LabelsContain(labels, "node.openshift.io/os_id") { + return dtos.OPEN_SHIFT, nil + } else if LabelsContain(labels, "vmware-system-vmware.io/role") { + return dtos.VMWARE, nil + } else if LabelsContain(labels, "io.rancher.os/hostname") { + return dtos.RKE, nil + } else if LabelsContain(labels, "linode-lke/") { + return dtos.LINODE, nil + } else if LabelsContain(labels, "scaleway-kapsule/") { + return dtos.SCALEWAY, nil + } else if LabelsContain(labels, "microk8s.io/cluster") { + return dtos.MICROK8S, nil + } else if strings.ToLower(node.Name) == "minikube" { + return dtos.MINIKUBE, nil + } else if LabelsContain(labels, "io.k8s.sigs.kind/role") { + return dtos.KIND, nil + } else if LabelsContain(labels, "civo/") { + return dtos.CIVO, nil + } else if LabelsContain(labels, "giantswarm.io/") { + return dtos.GIANTSWARM, nil + } else if LabelsContain(labels, "ovhcloud/") { + return dtos.OVHCLOUD, nil + } else if LabelsContain(labels, "gardener.cloud/role") { + return dtos.GARDENER, nil + } else if LabelsContain(labels, "cce.huawei.com") { + return dtos.HUAWEI, nil + } else if LabelsContain(labels, "nirmata.io") { + return dtos.NIRMATA, nil + } else if LabelsContain(labels, "platform9.com/role") { + return dtos.PF9, nil + } else if LabelsContain(labels, "nks.netapp.io") { + return dtos.NKS, nil + } else if LabelsContain(labels, "appscode.com") { + return dtos.APPSCODE, nil + } else if LabelsContain(labels, "loft.sh") { + return dtos.LOFT, nil + } else if LabelsContain(labels, "spectrocloud.com") { + return dtos.SPECTROCLOUD, nil + } else if LabelsContain(labels, "diamanti.com") { + return dtos.DIAMANTI, nil + } else if strings.HasPrefix(strings.ToLower(node.Name), "k3d-") { + return dtos.K3D, nil + } else if LabelsContain(labels, "cloud.google.com/gke-on-prem") { + return dtos.GKE_ON_PREM, nil + } else if LabelsContain(labels, "rke.cattle.io") { + return dtos.RKE, nil + } + } + + fmt.Println("This cluster's provider is unknown or it might be self-managed.") + return dtos.SELF_HOSTED, nil +} + +func LabelsContain(labels map[string]string, str string) bool { + // Keys + if _, ok := labels[strings.ToLower(str)]; ok { + return true + } + // Values + for _, label := range labels { + if strings.EqualFold(label, str) { + return true + } + } + return false +} + +func AllResourcesFrom(namespace string, contextId *string) ([]interface{}, error) { + ignoredResources := []string{ + "events.k8s.io/v1", + "events.k8s.io/v1beta1", + "metrics.k8s.io/v1beta1", + "discovery.k8s.io/v1", + } + + result := []interface{}{} + + provider, err := NewKubeProvider(contextId) + if provider == nil || err != nil { + return result, err + } + + // Get a list of all resource types in the cluster + resourceList, err := provider.ClientSet.Discovery().ServerPreferredResources() + if err != nil { + return result, err + } + + // Iterate over each resource type and backup all resources in the namespace + for _, resource := range resourceList { + if utils.Contains(ignoredResources, resource.GroupVersion) { + continue + } + gv, _ := schema.ParseGroupVersion(resource.GroupVersion) + if len(resource.APIResources) <= 0 { + continue + } + + for _, aApiResource := range resource.APIResources { + if !aApiResource.Namespaced { + continue + } + + resourceId := schema.GroupVersionResource{ + Group: gv.Group, + Version: gv.Version, + Resource: aApiResource.Name, + } + // Get the REST client for this resource type + restClient := dynamic.New(provider.ClientSet.RESTClient()).Resource(resourceId).Namespace(namespace) + + // Get a list of all resources of this type in the namespace + list, err := restClient.List(context.Background(), metav1.ListOptions{}) + if err != nil { + logger.Log.Error("%s: %s", resourceId.Resource, err.Error()) + continue + } + + // Iterate over each resource and write it to a file + for _, obj := range list.Items { + logger.Log.Noticef("(SUCCESS) %s: %s/%s", resourceId.Resource, obj.GetNamespace(), obj.GetName()) + obj.SetManagedFields(nil) + delete(obj.Object, "status") + obj.SetUID("") + obj.SetResourceVersion("") + obj.SetCreationTimestamp(metav1.Time{}) + + result = append(result, obj.Object) + } + } + } + return result, nil +} diff --git a/utils/utils.go b/utils/utils.go index 1704115..fd75fad 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -3,6 +3,7 @@ package utils import ( "bufio" "bytes" + "context" "crypto/sha256" "encoding/gob" "encoding/hex" @@ -343,3 +344,31 @@ func parseIPs(ips []string) ([]net.IP, error) { } return parsed, nil } + +func CheckInternetAccess() (bool, error) { + // Create a custom resolver + r := &net.Resolver{ + PreferGo: true, + Dial: func(ctx context.Context, network, address string) (net.Conn, error) { + d := net.Dialer{} + return d.DialContext(ctx, "udp", "1.1.1.1:53") + }, + } + + // Attempt to resolve a known domain + // If it succeeds, it means we have internet access + _, err := r.LookupHost(context.Background(), "mogenius.com") + return err == nil, err +} + +func IsKubectlInstalled() (bool, string, error) { + cmd := exec.Command("kubectl", "version") + output, err := cmd.CombinedOutput() + return err == nil, strings.TrimRight(string(output), "\n\r"), err +} + +func IsHelmInstalled() (bool, string, error) { + cmd := exec.Command("helm", "version", "--short") + output, err := cmd.CombinedOutput() + return err == nil, strings.TrimRight(string(output), "\n\r"), err +}