diff --git a/go.mod b/go.mod index 7599a420..48ad92ec 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( k8s.io/api v0.30.5 k8s.io/apiextensions-apiserver v0.30.5 k8s.io/apimachinery v0.30.5 + k8s.io/cli-runtime v0.30.5 k8s.io/client-go v0.30.5 k8s.io/klog/v2 v2.120.1 sigs.k8s.io/controller-runtime v0.18.5 @@ -26,6 +27,7 @@ require ( require ( dario.cat/mergo v1.0.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect @@ -69,6 +71,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index 0c1294d1..67830686 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,8 @@ github.com/cnoe-io/argocd-api v0.0.0-20241031202925-3091d64cb3c4/go.mod h1:qItVg github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -141,6 +143,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -284,6 +288,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -357,6 +362,8 @@ k8s.io/apiextensions-apiserver v0.30.5 h1:JfXTIyzXf5+ryncbp7T/uaVjLdvkwtqoNG2vo7 k8s.io/apiextensions-apiserver v0.30.5/go.mod h1:uVLEME2UPA6UN22i+jTu66B9/0CnsjlHkId+Awo0lvs= k8s.io/apimachinery v0.30.5 h1:CQZO19GFgw4zcOjY2H+mJ3k1u1o7zFACTNCB7nu4O18= k8s.io/apimachinery v0.30.5/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/cli-runtime v0.30.5 h1:MWY6efoBVH3h0O6p2DgaQszabV5ZntHZwTHBkiz+PSI= +k8s.io/cli-runtime v0.30.5/go.mod h1:AKMWLDIJQUA5a7yEh5gmzkhpZqYpuDEVovanugfSnQk= k8s.io/client-go v0.30.5 h1:vEDSzfTz0F8TXcWVdXl+aqV7NAV8M3UvC2qnGTTCoKw= k8s.io/client-go v0.30.5/go.mod h1:/q5fHHBmhAUesOOFJACpD7VJ4e57rVtTPDOsvXrPpMk= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index 3749b00c..d50bedcf 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -1,6 +1,7 @@ package get import ( + "bytes" "context" "fmt" "github.com/cnoe-io/idpbuilder/pkg/cmd/helpers" @@ -9,7 +10,9 @@ import ( "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/cli-runtime/pkg/printers" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" "os" @@ -24,6 +27,34 @@ type ClusterManager struct { clients map[string]client.Client // map of cluster name to client } +type Cluster struct { + Name string + URLKubeApi string + KubePort int32 + TlsCheck bool + ExternalPort int32 + Nodes []Node +} + +type Node struct { + Name string + InternalIP string + ExternalIP string + Capacity Capacity + Allocated Allocated +} + +type Capacity struct { + Memory float64 + Pods int64 + Cpu int64 +} + +type Allocated struct { + Cpu string + Memory string +} + var ClustersCmd = &cobra.Command{ Use: "clusters", Short: "Get idp clusters", @@ -36,13 +67,18 @@ func preClustersE(cmd *cobra.Command, args []string) error { return helpers.SetLogger() } -// findClusterByName searches for a cluster by name in the kubeconfig -func findClusterByName(config *api.Config, name string) (*api.Cluster, bool) { - cluster, exists := config.Clusters[name] - return cluster, exists +func list(cmd *cobra.Command, args []string) error { + clusters, err := populateClusterList() + if err != nil { + return err + } else { + // Convert the list of the clusters to Table of clusters + printTable(printers.PrintOptions{}, generateClusterTable(clusters)) + return nil + } } -func list(cmd *cobra.Command, args []string) error { +func populateClusterList() ([]Cluster, error) { logger := helpers.CmdLogger detectOpt, err := util.DetectKindNodeProvider() @@ -70,6 +106,9 @@ func list(cmd *cobra.Command, args []string) error { os.Exit(1) } + // Create an empty array of clusters to collect the information + clusterList := []Cluster{} + // List the idp builder clusters according to the provider: podman or docker provider := cluster.NewProvider(cluster.ProviderWithLogger(kind.KindLoggerFromLogr(&logger)), detectOpt) clusters, err := provider.List() @@ -80,9 +119,8 @@ func list(cmd *cobra.Command, args []string) error { // Populate a list of Kube client for each cluster/context matching a idpbuilder cluster manager, _ := CreateKubeClientForEachIDPCluster(config, clusters) - fmt.Printf("\n") for _, cluster := range clusters { - fmt.Printf("Cluster: %s\n", cluster) + aCluster := Cluster{Name: cluster} // Search about the idp cluster within the kubeconfig file and show information c, found := findClusterByName(config, "kind-"+cluster) @@ -99,18 +137,18 @@ func list(cmd *cobra.Command, args []string) error { if err != nil { logger.Error(err, "failed to get the kubernetes ingress service.") } else { - fmt.Printf("External Ingress Port: %d\n", targetPort) + aCluster.ExternalPort = targetPort } - fmt.Printf("Host URL of the kube API server: %s\n", c.Server) - //fmt.Printf("TLS Verify: %t\n", c.InsecureSkipTLSVerify) + aCluster.URLKubeApi = c.Server + aCluster.TlsCheck = c.InsecureSkipTLSVerify // Print the internal port running the Kuber API service - targetPort, err = findInternalKubeApiPort(cli) + kubeApiPort, err := findInternalKubeApiPort(cli) if err != nil { logger.Error(err, "failed to get the kubernetes default service.") } else { - fmt.Printf("Internal Kubernetes API Port: %d\n", targetPort) + aCluster.KubePort = kubeApiPort } // Let's check what the current node reports @@ -122,61 +160,101 @@ func list(cmd *cobra.Command, args []string) error { for _, node := range nodeList.Items { nodeName := node.Name - fmt.Printf("\n") - fmt.Printf("Node: %s\n", nodeName) + + aNode := Node{} + aNode.Name = nodeName for _, addr := range node.Status.Addresses { switch addr.Type { case corev1.NodeInternalIP: - fmt.Printf("Internal IP: %s\n", addr.Address) + aNode.InternalIP = addr.Address case corev1.NodeExternalIP: - fmt.Printf("External IP: %s\n", addr.Address) + aNode.ExternalIP = addr.Address } } - // Show node capacity - fmt.Printf("Capacity of the node: \n") - printFormattedResourceList(node.Status.Capacity) + // Get Node capacity + aNode.Capacity = populateNodeCapacity(node.Status.Capacity) - // Show node allocated resources - err = printAllocatedResources(context.Background(), cli, node.Name) + // Get Node Allocated resources + allocated, err := printAllocatedResources(context.Background(), cli, node.Name) if err != nil { - logger.Error(err, "Failed to get the node's allocated resources.") + logger.Error(err, "failed to get the allocated resources.") } + aNode.Allocated = allocated } } - fmt.Println("----------------------------------------") + clusterList = append(clusterList, aCluster) } - return nil + return clusterList, nil +} + +func generateClusterTable(clusterTable []Cluster) metav1.Table { + table := &metav1.Table{} + table.ColumnDefinitions = []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string"}, + {Name: "External-Port", Type: "string"}, + {Name: "Kube-Api", Type: "string"}, + {Name: "TLS", Type: "string"}, + {Name: "Kube-Port", Type: "string"}, + } + for _, cluster := range clusterTable { + row := metav1.TableRow{ + Cells: []interface{}{ + cluster.Name, + cluster.ExternalPort, + cluster.URLKubeApi, + cluster.TlsCheck, + cluster.KubePort, + }, + } + table.Rows = append(table.Rows, row) + } + return *table } -func printFormattedResourceList(resources corev1.ResourceList) { - // Define the fixed width for the resource name column (adjust as needed) - nameWidth := 20 +func printTable(opts printers.PrintOptions, table metav1.Table) { + out := bytes.NewBuffer([]byte{}) + printer := printers.NewTablePrinter(opts) + err := printer.PrintObj(&table, out) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println(out.String()) +} +func populateNodeCapacity(resources corev1.ResourceList) Capacity { + capacity := Capacity{} for name, quantity := range resources { if strings.HasPrefix(string(name), "hugepages-") { continue } if name == corev1.ResourceMemory { - // Convert memory from bytes to gigabytes (GB) memoryInBytes := quantity.Value() // .Value() gives the value in bytes memoryInGB := float64(memoryInBytes) / (1024 * 1024 * 1024) // Convert to GB - fmt.Printf(" %-*s %.2f GB\n", nameWidth, name, memoryInGB) - } else { - // Format each line with the fixed name width and quantity - fmt.Printf(" %-*s %s\n", nameWidth, name, quantity.String()) + capacity.Memory = memoryInGB + } + + if name == corev1.ResourceCPU { + capacity.Cpu = quantity.Value() + } + + if name == corev1.ResourcePods { + capacity.Pods = quantity.Value() } + } + return capacity } -func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeName string) error { +func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeName string) (Allocated, error) { // List all pods on the specified node var podList corev1.PodList if err := k8sClient.List(ctx, &podList, client.MatchingFields{"spec.nodeName": nodeName}); err != nil { - return fmt.Errorf("failed to list pods on node %s: %w", nodeName, err) + return Allocated{}, fmt.Errorf("failed to list pods on node %s: %w", nodeName, err) } // Initialize counters for CPU and memory requests @@ -196,11 +274,15 @@ func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeN } // Display the total allocated resources - fmt.Printf("Allocated resources on node:\n") - fmt.Printf(" CPU Requests: %s\n", totalCPU.String()) - fmt.Printf(" Memory Requests: %s\n", totalMemory.String()) + //fmt.Printf(" CPU Requests: %s\n", totalCPU.String()) + //fmt.Printf(" Memory Requests: %s\n", totalMemory.String()) - return nil + allocated := Allocated{ + Memory: totalMemory.String(), + Cpu: totalCPU.String(), + } + + return allocated, nil } func findExternalHTTPSPort(cli client.Client) (int32, error) { @@ -245,6 +327,12 @@ func findInternalKubeApiPort(cli client.Client) (int32, error) { return targetPort.TargetPort.IntVal, nil } +// findClusterByName searches for a cluster by name in the kubeconfig +func findClusterByName(config *api.Config, name string) (*api.Cluster, bool) { + cluster, exists := config.Clusters[name] + return cluster, exists +} + // GetClientForCluster returns the client for the specified cluster/context name func GetClientForCluster(m *ClusterManager, clusterName string) (client.Client, error) { cl, exists := m.clients["kind-"+clusterName]