From 7eee9577d99d61ad3a32daf18fd04bb14935b862 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 14 Nov 2024 11:03:44 +0100 Subject: [PATCH 01/18] Improve the cmd: get clusters to show the cluster and nodes and their internal IP. Created a help function to create globally a kube client. #445 Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 53 +++++++++++++++++++++++++++++++++++++---- pkg/cmd/get/root.go | 1 - pkg/cmd/helpers/k8s.go | 40 +++++++++++++++++++++++++++++++ pkg/cmd/root.go | 1 + 4 files changed, 90 insertions(+), 5 deletions(-) create mode 100644 pkg/cmd/helpers/k8s.go diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index 01708c8d..842b349d 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -1,10 +1,14 @@ package get import ( + "context" "fmt" - "github.com/cnoe-io/idpbuilder/pkg/cmd/helpers" + "github.com/cnoe-io/idpbuilder/pkg/kind" + "github.com/cnoe-io/idpbuilder/pkg/util" "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "os" "sigs.k8s.io/kind/pkg/cluster" ) @@ -16,19 +20,60 @@ var ClustersCmd = &cobra.Command{ PreRunE: preClustersE, } +var kubeCfgPath string + func preClustersE(cmd *cobra.Command, args []string) error { return helpers.SetLogger() } func list(cmd *cobra.Command, args []string) error { - provider := cluster.NewProvider(cluster.ProviderWithDocker()) + logger := helpers.CmdLogger + + detectOpt, err := util.DetectKindNodeProvider() + if err != nil { + logger.Error(err, "failed to detect the provider.") + os.Exit(1) + } + + kubeConfig, err := helpers.GetKubeConfig() + if err != nil { + logger.Error(err, "failed to create the kube config.") + os.Exit(1) + } + + cli, err := helpers.GetKubeClient(kubeConfig) + if err != nil { + logger.Error(err, "failed to create the kube client.") + os.Exit(1) + } + + provider := cluster.NewProvider(cluster.ProviderWithLogger(kind.KindLoggerFromLogr(&logger)), detectOpt) clusters, err := provider.List() if err != nil { - return fmt.Errorf("failed to list clusters: %w", err) + logger.Error(err, "failed to list clusters.") } for _, c := range clusters { - fmt.Println(c) + fmt.Printf("Cluster: %s\n", c) + var nodeList corev1.NodeList + err := cli.List(context.TODO(), &nodeList) + if err != nil { + logger.Error(err, "failed to list nodes for cluster: %s", c) + } + for _, node := range nodeList.Items { + nodeName := node.Name + fmt.Printf(" Node: %s\n", nodeName) + + for _, addr := range node.Status.Addresses { + switch addr.Type { + case corev1.NodeInternalIP: + fmt.Printf(" Internal IP: %s\n", addr.Address) + case corev1.NodeExternalIP: + fmt.Printf(" External IP: %s\n", addr.Address) + } + } + fmt.Println("----------") + } } return nil } diff --git a/pkg/cmd/get/root.go b/pkg/cmd/get/root.go index 1e329010..a8aacd9f 100644 --- a/pkg/cmd/get/root.go +++ b/pkg/cmd/get/root.go @@ -2,7 +2,6 @@ package get import ( "fmt" - "github.com/spf13/cobra" ) diff --git a/pkg/cmd/helpers/k8s.go b/pkg/cmd/helpers/k8s.go new file mode 100644 index 00000000..cc6907c5 --- /dev/null +++ b/pkg/cmd/helpers/k8s.go @@ -0,0 +1,40 @@ +package helpers + +import ( + "fmt" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" + "path/filepath" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ( + KubeConfigPath string + scheme *runtime.Scheme +) + +func GetKubeConfigPath() string { + if KubeConfigPath == "" { + return filepath.Join(homedir.HomeDir(), ".kube", "config") + } else { + return KubeConfigPath + } +} + +func GetKubeConfig() (*rest.Config, error) { + kubeConfig, err := clientcmd.BuildConfigFromFlags("", GetKubeConfigPath()) + if err != nil { + return nil, fmt.Errorf("Error building kubeconfig: %w", err) + } + return kubeConfig, nil +} + +func GetKubeClient(kubeConfig *rest.Config) (client.Client, error) { + kubeClient, err := client.New(kubeConfig, client.Options{Scheme: scheme}) + if err != nil { + return nil, fmt.Errorf("Error creating kubernetes client: %w", err) + } + return kubeClient, nil +} diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 32d97ab4..12afffc7 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -21,6 +21,7 @@ var rootCmd = &cobra.Command{ func init() { rootCmd.PersistentFlags().StringVarP(&helpers.LogLevel, "log-level", "l", "info", helpers.LogLevelMsg) rootCmd.PersistentFlags().BoolVar(&helpers.ColoredOutput, "color", false, helpers.ColoredOutputMsg) + rootCmd.PersistentFlags().StringVarP(&helpers.KubeConfigPath, "kubePath", "", "", "kube config file Path.") rootCmd.AddCommand(create.CreateCmd) rootCmd.AddCommand(get.GetCmd) rootCmd.AddCommand(delete.DeleteCmd) From 6e05aece3aaf7ab6b4057a1a49e953b4a7f55083 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 14 Nov 2024 11:36:47 +0100 Subject: [PATCH 02/18] Show the kube API server URL, if TLS is checked. #445 Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 32 ++++++++++++++++++++++++++++---- pkg/cmd/helpers/k8s.go | 10 ++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index 842b349d..f2fa0948 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -8,6 +8,7 @@ import ( "github.com/cnoe-io/idpbuilder/pkg/util" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/clientcmd/api" "os" "sigs.k8s.io/kind/pkg/cluster" ) @@ -26,6 +27,12 @@ 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 { logger := helpers.CmdLogger @@ -47,19 +54,36 @@ func list(cmd *cobra.Command, args []string) error { os.Exit(1) } + // 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() if err != nil { logger.Error(err, "failed to list clusters.") } - for _, c := range clusters { - fmt.Printf("Cluster: %s\n", c) + for _, cluster := range clusters { + fmt.Printf("Cluster: %s\n", cluster) + + config, err := helpers.LoadKubeConfig() + if err != nil { + logger.Error(err, "failed to load the kube config.") + } + + // Search about the idp cluster within the kubeconfig file + cluster, found := findClusterByName(config, "kind-"+cluster) + if !found { + fmt.Printf("Cluster %q not found\n", cluster) + } else { + fmt.Printf("URL of the kube API server: %s\n", cluster.Server) + fmt.Printf("TLS Verify: %t\n", cluster.InsecureSkipTLSVerify) + } + var nodeList corev1.NodeList - err := cli.List(context.TODO(), &nodeList) + err = cli.List(context.TODO(), &nodeList) if err != nil { - logger.Error(err, "failed to list nodes for cluster: %s", c) + logger.Error(err, "failed to list nodes for cluster: %s", cluster) } + for _, node := range nodeList.Items { nodeName := node.Name fmt.Printf(" Node: %s\n", nodeName) diff --git a/pkg/cmd/helpers/k8s.go b/pkg/cmd/helpers/k8s.go index cc6907c5..f371b865 100644 --- a/pkg/cmd/helpers/k8s.go +++ b/pkg/cmd/helpers/k8s.go @@ -5,6 +5,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" "k8s.io/client-go/util/homedir" "path/filepath" "sigs.k8s.io/controller-runtime/pkg/client" @@ -23,6 +24,15 @@ func GetKubeConfigPath() string { } } +func LoadKubeConfig() (*api.Config, error) { + config, err := clientcmd.LoadFromFile(GetKubeConfigPath()) + if err != nil { + return nil, fmt.Errorf("Failed to load kubeconfig file: %w", err) + } else { + return config, nil + } +} + func GetKubeConfig() (*rest.Config, error) { kubeConfig, err := clientcmd.BuildConfigFromFlags("", GetKubeConfigPath()) if err != nil { From aca1830ca5abf37568090c3da429568b70f51147 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 14 Nov 2024 12:57:45 +0100 Subject: [PATCH 03/18] Printing the node capacity and allocated resources Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 100 +++++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index f2fa0948..b5ebddd3 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -8,9 +8,12 @@ import ( "github.com/cnoe-io/idpbuilder/pkg/util" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" "k8s.io/client-go/tools/clientcmd/api" "os" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/kind/pkg/cluster" + "strings" ) var ClustersCmd = &cobra.Command{ @@ -69,7 +72,7 @@ func list(cmd *cobra.Command, args []string) error { logger.Error(err, "failed to load the kube config.") } - // Search about the idp cluster within the kubeconfig file + // Search about the idp cluster within the kubeconfig file and show information cluster, found := findClusterByName(config, "kind-"+cluster) if !found { fmt.Printf("Cluster %q not found\n", cluster) @@ -77,27 +80,92 @@ func list(cmd *cobra.Command, args []string) error { fmt.Printf("URL of the kube API server: %s\n", cluster.Server) fmt.Printf("TLS Verify: %t\n", cluster.InsecureSkipTLSVerify) } + fmt.Println("----------------------------------------") + } + + // Let's check what the current node reports + var nodeList corev1.NodeList + err = cli.List(context.TODO(), &nodeList) + if err != nil { + logger.Error(err, "failed to list nodes for the current kube cluster.") + } + + for _, node := range nodeList.Items { + nodeName := node.Name + fmt.Printf("Node: %s\n", nodeName) + + for _, addr := range node.Status.Addresses { + switch addr.Type { + case corev1.NodeInternalIP: + fmt.Printf("Internal IP: %s\n", addr.Address) + case corev1.NodeExternalIP: + fmt.Printf("External IP: %s\n", addr.Address) + } + } + + // Show node capacity + fmt.Printf("Capacity of the node: \n") + printFormattedResourceList(node.Status.Capacity) - var nodeList corev1.NodeList - err = cli.List(context.TODO(), &nodeList) + // Show node allocated resources + err = printAllocatedResources(context.Background(), cli, node.Name) if err != nil { - logger.Error(err, "failed to list nodes for cluster: %s", cluster) + logger.Error(err, "Failed to get the node's allocated resources.") } + fmt.Println("--------------------") + } + + return nil +} + +func printFormattedResourceList(resources corev1.ResourceList) { + // Define the fixed width for the resource name column (adjust as needed) + nameWidth := 20 - for _, node := range nodeList.Items { - nodeName := node.Name - fmt.Printf(" Node: %s\n", nodeName) - - for _, addr := range node.Status.Addresses { - switch addr.Type { - case corev1.NodeInternalIP: - fmt.Printf(" Internal IP: %s\n", addr.Address) - case corev1.NodeExternalIP: - fmt.Printf(" External IP: %s\n", addr.Address) - } + 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()) + } + } +} + +func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeName string) 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) + } + + // Initialize counters for CPU and memory requests + totalCPU := resource.NewQuantity(0, resource.DecimalSI) + totalMemory := resource.NewQuantity(0, resource.BinarySI) + + // Sum up CPU and memory requests from each container in each pod + for _, pod := range podList.Items { + for _, container := range pod.Spec.Containers { + if reqCPU, found := container.Resources.Requests[corev1.ResourceCPU]; found { + totalCPU.Add(reqCPU) + } + if reqMemory, found := container.Resources.Requests[corev1.ResourceMemory]; found { + totalMemory.Add(reqMemory) } - fmt.Println("----------") } } + + // 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()) + return nil } From 0dad33144a83a104c9bcb9d460e70c150818ee98 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 14 Nov 2024 13:10:12 +0100 Subject: [PATCH 04/18] Fix fmt.Printf error due to wrong var used Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index b5ebddd3..d4aeb89a 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -24,8 +24,6 @@ var ClustersCmd = &cobra.Command{ PreRunE: preClustersE, } -var kubeCfgPath string - func preClustersE(cmd *cobra.Command, args []string) error { return helpers.SetLogger() } @@ -73,12 +71,12 @@ func list(cmd *cobra.Command, args []string) error { } // Search about the idp cluster within the kubeconfig file and show information - cluster, found := findClusterByName(config, "kind-"+cluster) + c, found := findClusterByName(config, "kind-"+cluster) if !found { - fmt.Printf("Cluster %q not found\n", cluster) + fmt.Printf("Cluster not found: %s\n", cluster) } else { - fmt.Printf("URL of the kube API server: %s\n", cluster.Server) - fmt.Printf("TLS Verify: %t\n", cluster.InsecureSkipTLSVerify) + fmt.Printf("URL of the kube API server: %s\n", c.Server) + fmt.Printf("TLS Verify: %t\n", c.InsecureSkipTLSVerify) } fmt.Println("----------------------------------------") } From 7cab4bfd5234a1f05a75eacb2c1086dac9d429cf Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 14 Nov 2024 14:46:10 +0100 Subject: [PATCH 05/18] Print the external port of the ingress controller of the current ctx Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index d4aeb89a..2e9f5820 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/clientcmd/api" "os" "sigs.k8s.io/controller-runtime/pkg/client" @@ -78,9 +79,22 @@ func list(cmd *cobra.Command, args []string) error { fmt.Printf("URL of the kube API server: %s\n", c.Server) fmt.Printf("TLS Verify: %t\n", c.InsecureSkipTLSVerify) } + fmt.Println("----------------------------------------") } + // Print the external port that users can access using the ingress nginx proxy + service := corev1.Service{} + namespacedName := types.NamespacedName{ + Name: "ingress-nginx-controller", + Namespace: "ingress-nginx", + } + err = cli.Get(context.TODO(), namespacedName, &service) + if err != nil { + logger.Error(err, "failed to get the ingress service on the cluster.") + } + fmt.Printf("External Port: %d\n", findExternalHTTPSPort(service)) + // Let's check what the current node reports var nodeList corev1.NodeList err = cli.List(context.TODO(), &nodeList) @@ -167,3 +181,14 @@ func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeN return nil } + +func findExternalHTTPSPort(service corev1.Service) int32 { + var targetPort corev1.ServicePort + for _, port := range service.Spec.Ports { + if port.Name != "" && strings.HasPrefix(port.Name, "https-") { + targetPort = port + break + } + } + return targetPort.Port +} From 0207f16454be8d60ed779adee031ba681395fa1a Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 14 Nov 2024 16:04:17 +0100 Subject: [PATCH 06/18] Populate a list of kuybe clients; one per cluster/context Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 151 +++++++++++++++++++++++++++------------- 1 file changed, 103 insertions(+), 48 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index 2e9f5820..3ad74d07 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -10,13 +10,20 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" "os" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/kind/pkg/cluster" + "slices" "strings" ) +// ClusterManager holds the clients for the different idpbuilder clusters +type ClusterManager struct { + clients map[string]client.Client // map of cluster name to client +} + var ClustersCmd = &cobra.Command{ Use: "clusters", Short: "Get idp clusters", @@ -50,12 +57,19 @@ func list(cmd *cobra.Command, args []string) error { os.Exit(1) } - cli, err := helpers.GetKubeClient(kubeConfig) + // TODO: Check if we need it or not like also if the new code handle the kubeconfig path passed as parameter + _, err = helpers.GetKubeClient(kubeConfig) if err != nil { logger.Error(err, "failed to create the kube client.") os.Exit(1) } + config, err := helpers.LoadKubeConfig() + if err != nil { + logger.Error(err, "failed to load the kube config.") + os.Exit(1) + } + // 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() @@ -63,14 +77,13 @@ func list(cmd *cobra.Command, args []string) error { logger.Error(err, "failed to list clusters.") } + // 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) - config, err := helpers.LoadKubeConfig() - if err != nil { - logger.Error(err, "failed to load the kube config.") - } - // Search about the idp cluster within the kubeconfig file and show information c, found := findClusterByName(config, "kind-"+cluster) if !found { @@ -78,53 +91,56 @@ func list(cmd *cobra.Command, args []string) error { } else { fmt.Printf("URL of the kube API server: %s\n", c.Server) fmt.Printf("TLS Verify: %t\n", c.InsecureSkipTLSVerify) - } - - fmt.Println("----------------------------------------") - } - - // Print the external port that users can access using the ingress nginx proxy - service := corev1.Service{} - namespacedName := types.NamespacedName{ - Name: "ingress-nginx-controller", - Namespace: "ingress-nginx", - } - err = cli.Get(context.TODO(), namespacedName, &service) - if err != nil { - logger.Error(err, "failed to get the ingress service on the cluster.") - } - fmt.Printf("External Port: %d\n", findExternalHTTPSPort(service)) - - // Let's check what the current node reports - var nodeList corev1.NodeList - err = cli.List(context.TODO(), &nodeList) - if err != nil { - logger.Error(err, "failed to list nodes for the current kube cluster.") - } - - for _, node := range nodeList.Items { - nodeName := node.Name - fmt.Printf("Node: %s\n", nodeName) - for _, addr := range node.Status.Addresses { - switch addr.Type { - case corev1.NodeInternalIP: - fmt.Printf("Internal IP: %s\n", addr.Address) - case corev1.NodeExternalIP: - fmt.Printf("External IP: %s\n", addr.Address) + cli, err := GetClientForCluster(manager, cluster) + if err != nil { + logger.Error(err, "failed to get the cluster/context for the cluster: %s.", cluster) } - } + // Print the external port that users can access using the ingress nginx proxy + service := corev1.Service{} + namespacedName := types.NamespacedName{ + Name: "ingress-nginx-controller", + Namespace: "ingress-nginx", + } + err = cli.Get(context.TODO(), namespacedName, &service) + if err != nil { + logger.Error(err, "failed to get the ingress service on the cluster.") + } + fmt.Printf("External Port: %d", findExternalHTTPSPort(service)) - // Show node capacity - fmt.Printf("Capacity of the node: \n") - printFormattedResourceList(node.Status.Capacity) + // Let's check what the current node reports + var nodeList corev1.NodeList + err = cli.List(context.TODO(), &nodeList) + if err != nil { + logger.Error(err, "failed to list nodes for the current kube cluster.") + } - // Show node allocated resources - err = printAllocatedResources(context.Background(), cli, node.Name) - if err != nil { - logger.Error(err, "Failed to get the node's allocated resources.") + for _, node := range nodeList.Items { + nodeName := node.Name + fmt.Printf("\n\n") + fmt.Printf("Node: %s\n", nodeName) + + for _, addr := range node.Status.Addresses { + switch addr.Type { + case corev1.NodeInternalIP: + fmt.Printf("Internal IP: %s\n", addr.Address) + case corev1.NodeExternalIP: + fmt.Printf("External IP: %s\n", addr.Address) + } + } + + // Show node capacity + fmt.Printf("Capacity of the node: \n") + printFormattedResourceList(node.Status.Capacity) + + // Show node allocated resources + err = printAllocatedResources(context.Background(), cli, node.Name) + if err != nil { + logger.Error(err, "Failed to get the node's allocated resources.") + } + } } - fmt.Println("--------------------") + fmt.Println("----------------------------------------") } return nil @@ -192,3 +208,42 @@ func findExternalHTTPSPort(service corev1.Service) int32 { } return targetPort.Port } + +// 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] + if !exists { + return nil, fmt.Errorf("no client found for cluster %q", clusterName) + } + return cl, nil +} + +func CreateKubeClientForEachIDPCluster(config *api.Config, clusterList []string) (*ClusterManager, error) { + // Initialize the ClusterManager with a map of kube Client + manager := &ClusterManager{ + clients: make(map[string]client.Client), + } + + for contextName := range config.Contexts { + // Check if the kubconfig contains the cluster name + // We remove the prefix "kind-" to find the cluster name from the slice + if slices.Contains(clusterList, contextName[5:]) { + cfg, err := clientcmd.NewNonInteractiveClientConfig(*config, contextName, &clientcmd.ConfigOverrides{}, nil).ClientConfig() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to build client for context %q: %v\n", contextName, err) + continue + } + + cl, err := client.New(cfg, client.Options{}) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create client for context %q: %v\n", contextName, err) + continue + } + + manager.clients[contextName] = cl + // fmt.Printf("Client created for context %q\n", contextName) + } + + } + return manager, nil +} From d7c124732efa914c43763612c74f7b072e5a149d Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 14 Nov 2024 16:32:46 +0100 Subject: [PATCH 07/18] Added a function to get the internal port of the kube API Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 64 ++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index 3ad74d07..3749b00c 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -89,24 +89,29 @@ func list(cmd *cobra.Command, args []string) error { if !found { fmt.Printf("Cluster not found: %s\n", cluster) } else { - fmt.Printf("URL of the kube API server: %s\n", c.Server) - fmt.Printf("TLS Verify: %t\n", c.InsecureSkipTLSVerify) - cli, err := GetClientForCluster(manager, cluster) if err != nil { logger.Error(err, "failed to get the cluster/context for the cluster: %s.", cluster) } - // Print the external port that users can access using the ingress nginx proxy - service := corev1.Service{} - namespacedName := types.NamespacedName{ - Name: "ingress-nginx-controller", - Namespace: "ingress-nginx", + + // Print the external port mounted on the container and available also as ingress host port + targetPort, err := findExternalHTTPSPort(cli) + if err != nil { + logger.Error(err, "failed to get the kubernetes ingress service.") + } else { + fmt.Printf("External Ingress Port: %d\n", targetPort) } - err = cli.Get(context.TODO(), namespacedName, &service) + + fmt.Printf("Host URL of the kube API server: %s\n", c.Server) + //fmt.Printf("TLS Verify: %t\n", c.InsecureSkipTLSVerify) + + // Print the internal port running the Kuber API service + targetPort, err = findInternalKubeApiPort(cli) if err != nil { - logger.Error(err, "failed to get the ingress service on the cluster.") + logger.Error(err, "failed to get the kubernetes default service.") + } else { + fmt.Printf("Internal Kubernetes API Port: %d\n", targetPort) } - fmt.Printf("External Port: %d", findExternalHTTPSPort(service)) // Let's check what the current node reports var nodeList corev1.NodeList @@ -117,7 +122,7 @@ func list(cmd *cobra.Command, args []string) error { for _, node := range nodeList.Items { nodeName := node.Name - fmt.Printf("\n\n") + fmt.Printf("\n") fmt.Printf("Node: %s\n", nodeName) for _, addr := range node.Status.Addresses { @@ -198,7 +203,17 @@ func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeN return nil } -func findExternalHTTPSPort(service corev1.Service) int32 { +func findExternalHTTPSPort(cli client.Client) (int32, error) { + service := corev1.Service{} + namespacedName := types.NamespacedName{ + Name: "ingress-nginx-controller", + Namespace: "ingress-nginx", + } + err := cli.Get(context.TODO(), namespacedName, &service) + if err != nil { + return 0, fmt.Errorf("failed to get the ingress service on the cluster. %w", err) + } + var targetPort corev1.ServicePort for _, port := range service.Spec.Ports { if port.Name != "" && strings.HasPrefix(port.Name, "https-") { @@ -206,7 +221,28 @@ func findExternalHTTPSPort(service corev1.Service) int32 { break } } - return targetPort.Port + return targetPort.Port, nil +} + +func findInternalKubeApiPort(cli client.Client) (int32, error) { + service := corev1.Service{} + namespacedName := types.NamespacedName{ + Name: "kubernetes", + Namespace: "default", + } + err := cli.Get(context.TODO(), namespacedName, &service) + if err != nil { + return 0, fmt.Errorf("failed to get the kubernetes default service on the cluster. %w", err) + } + + var targetPort corev1.ServicePort + for _, port := range service.Spec.Ports { + if port.Name != "" && strings.HasPrefix(port.Name, "https") { + targetPort = port + break + } + } + return targetPort.TargetPort.IntVal, nil } // GetClientForCluster returns the client for the specified cluster/context name From 3a41ab4c34b15c9b28c88cf4de87b9b4536ac1ad Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 15 Nov 2024 12:13:01 +0100 Subject: [PATCH 08/18] Refactor the code to display the data using go cli-runtime table Signed-off-by: cmoulliard --- go.mod | 3 + go.sum | 7 ++ pkg/cmd/get/clusters.go | 164 ++++++++++++++++++++++++++++++---------- 3 files changed, 136 insertions(+), 38 deletions(-) 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] From 717388bc767763a676b1d539ce62c7cf241f70bf Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 15 Nov 2024 13:57:40 +0100 Subject: [PATCH 09/18] Review log messages Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index d50bedcf..2e89c133 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -125,12 +125,13 @@ func populateClusterList() ([]Cluster, error) { // Search about the idp cluster within the kubeconfig file and show information c, found := findClusterByName(config, "kind-"+cluster) if !found { - fmt.Printf("Cluster not found: %s\n", cluster) + logger.Error(nil, fmt.Sprintf("Cluster not found: %s within kube config file\n", cluster)) } else { cli, err := GetClientForCluster(manager, cluster) if err != nil { - logger.Error(err, "failed to get the cluster/context for the cluster: %s.", cluster) + logger.Error(err, fmt.Sprintf("failed to get the context for the cluster: %s.", cluster)) } + logger.V(1).Info(fmt.Sprintf("Got the context for the cluster: %s.", cluster)) // Print the external port mounted on the container and available also as ingress host port targetPort, err := findExternalHTTPSPort(cli) @@ -215,11 +216,12 @@ func generateClusterTable(clusterTable []Cluster) metav1.Table { } func printTable(opts printers.PrintOptions, table metav1.Table) { + logger := helpers.CmdLogger out := bytes.NewBuffer([]byte{}) printer := printers.NewTablePrinter(opts) err := printer.PrintObj(&table, out) if err != nil { - fmt.Println("Error:", err) + logger.Error(err, "failed to print the table.") return } fmt.Println(out.String()) @@ -254,7 +256,7 @@ func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeN // 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 Allocated{}, fmt.Errorf("failed to list pods on node %s: %w", nodeName, err) + return Allocated{}, fmt.Errorf("failed to list pods on node %s.", nodeName) } // Initialize counters for CPU and memory requests @@ -354,18 +356,17 @@ func CreateKubeClientForEachIDPCluster(config *api.Config, clusterList []string) if slices.Contains(clusterList, contextName[5:]) { cfg, err := clientcmd.NewNonInteractiveClientConfig(*config, contextName, &clientcmd.ConfigOverrides{}, nil).ClientConfig() if err != nil { - fmt.Fprintf(os.Stderr, "Failed to build client for context %q: %v\n", contextName, err) + fmt.Errorf("Failed to build client for context %s.", contextName) continue } cl, err := client.New(cfg, client.Options{}) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to create client for context %q: %v\n", contextName, err) + fmt.Fprintf(os.Stderr, "Failed to create client for context %s", contextName) continue } manager.clients[contextName] = cl - // fmt.Printf("Client created for context %q\n", contextName) } } From 05cb831f21530523e9d0bb75a296ffe1feefd07e Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 15 Nov 2024 13:58:03 +0100 Subject: [PATCH 10/18] Review log messages Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index 2e89c133..e001ee7d 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -362,7 +362,7 @@ func CreateKubeClientForEachIDPCluster(config *api.Config, clusterList []string) cl, err := client.New(cfg, client.Options{}) if err != nil { - fmt.Fprintf(os.Stderr, "Failed to create client for context %s", contextName) + fmt.Errorf("Failed to create client for context %s", contextName) continue } From 489bd45f921f82b311c4c49e0bfbe01c668432f8 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 15 Nov 2024 15:29:04 +0100 Subject: [PATCH 11/18] Return the error instead of logging it. Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index e001ee7d..f77119c1 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -15,7 +15,6 @@ import ( "k8s.io/cli-runtime/pkg/printers" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd/api" - "os" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/kind/pkg/cluster" "slices" @@ -83,27 +82,26 @@ func populateClusterList() ([]Cluster, error) { detectOpt, err := util.DetectKindNodeProvider() if err != nil { - logger.Error(err, "failed to detect the provider.") - os.Exit(1) + //logger.Error(err, "failed to detect the provider.") + return nil, err } kubeConfig, err := helpers.GetKubeConfig() if err != nil { - logger.Error(err, "failed to create the kube config.") - os.Exit(1) + return nil, err } // TODO: Check if we need it or not like also if the new code handle the kubeconfig path passed as parameter _, err = helpers.GetKubeClient(kubeConfig) if err != nil { - logger.Error(err, "failed to create the kube client.") - os.Exit(1) + //logger.Error(err, "failed to create the kube client.") + return nil, err } config, err := helpers.LoadKubeConfig() if err != nil { - logger.Error(err, "failed to load the kube config.") - os.Exit(1) + //logger.Error(err, "failed to load the kube config.") + return nil, err } // Create an empty array of clusters to collect the information @@ -113,7 +111,8 @@ func populateClusterList() ([]Cluster, error) { provider := cluster.NewProvider(cluster.ProviderWithLogger(kind.KindLoggerFromLogr(&logger)), detectOpt) clusters, err := provider.List() if err != nil { - logger.Error(err, "failed to list clusters.") + //logger.Error(err, "failed to list clusters.") + return nil, err } // Populate a list of Kube client for each cluster/context matching a idpbuilder cluster @@ -125,18 +124,21 @@ func populateClusterList() ([]Cluster, error) { // Search about the idp cluster within the kubeconfig file and show information c, found := findClusterByName(config, "kind-"+cluster) if !found { - logger.Error(nil, fmt.Sprintf("Cluster not found: %s within kube config file\n", cluster)) + //logger.Error(nil, fmt.Sprintf("Cluster not found: %s within kube config file\n", cluster)) + return nil, err } else { cli, err := GetClientForCluster(manager, cluster) if err != nil { - logger.Error(err, fmt.Sprintf("failed to get the context for the cluster: %s.", cluster)) + //logger.Error(err, fmt.Sprintf("failed to get the context for the cluster: %s.", cluster)) + return nil, err } logger.V(1).Info(fmt.Sprintf("Got the context for the cluster: %s.", cluster)) // Print the external port mounted on the container and available also as ingress host port targetPort, err := findExternalHTTPSPort(cli) if err != nil { - logger.Error(err, "failed to get the kubernetes ingress service.") + //logger.Error(err, "failed to get the kubernetes ingress service.") + return nil, err } else { aCluster.ExternalPort = targetPort } @@ -147,7 +149,8 @@ func populateClusterList() ([]Cluster, error) { // Print the internal port running the Kuber API service kubeApiPort, err := findInternalKubeApiPort(cli) if err != nil { - logger.Error(err, "failed to get the kubernetes default service.") + //logger.Error(err, "failed to get the kubernetes default service.") + return nil, err } else { aCluster.KubePort = kubeApiPort } @@ -156,7 +159,8 @@ func populateClusterList() ([]Cluster, error) { var nodeList corev1.NodeList err = cli.List(context.TODO(), &nodeList) if err != nil { - logger.Error(err, "failed to list nodes for the current kube cluster.") + //logger.Error(err, "failed to list nodes for the current kube cluster.") + return nil, err } for _, node := range nodeList.Items { @@ -180,7 +184,8 @@ func populateClusterList() ([]Cluster, error) { // Get Node Allocated resources allocated, err := printAllocatedResources(context.Background(), cli, node.Name) if err != nil { - logger.Error(err, "failed to get the allocated resources.") + //logger.Error(err, "failed to get the allocated resources.") + return nil, err } aNode.Allocated = allocated } From 1f99c4811c6fc4271b990baa4b88d07272fe6461 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 22 Nov 2024 12:28:40 +0100 Subject: [PATCH 12/18] Rename flag from kubePath to kubeconfig Signed-off-by: cmoulliard --- pkg/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 12afffc7..ba13b848 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -21,7 +21,7 @@ var rootCmd = &cobra.Command{ func init() { rootCmd.PersistentFlags().StringVarP(&helpers.LogLevel, "log-level", "l", "info", helpers.LogLevelMsg) rootCmd.PersistentFlags().BoolVar(&helpers.ColoredOutput, "color", false, helpers.ColoredOutputMsg) - rootCmd.PersistentFlags().StringVarP(&helpers.KubeConfigPath, "kubePath", "", "", "kube config file Path.") + rootCmd.PersistentFlags().StringVarP(&helpers.KubeConfigPath, "kubeconfig", "", "", "kube config file Path.") rootCmd.AddCommand(create.CreateCmd) rootCmd.AddCommand(get.GetCmd) rootCmd.AddCommand(delete.DeleteCmd) From 031e81b48ed3d485e3115db0872920c3caf151cc Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 22 Nov 2024 12:34:32 +0100 Subject: [PATCH 13/18] We continue even if the idpbuilder cluster is not found part of the kubeconfig file BUT we log a message Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index f77119c1..5e52cc4a 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -125,7 +125,7 @@ func populateClusterList() ([]Cluster, error) { c, found := findClusterByName(config, "kind-"+cluster) if !found { //logger.Error(nil, fmt.Sprintf("Cluster not found: %s within kube config file\n", cluster)) - return nil, err + logger.Info(fmt.Sprintf("Cluster not found: %s within kube config file\n", cluster)) } else { cli, err := GetClientForCluster(manager, cluster) if err != nil { @@ -361,14 +361,12 @@ func CreateKubeClientForEachIDPCluster(config *api.Config, clusterList []string) if slices.Contains(clusterList, contextName[5:]) { cfg, err := clientcmd.NewNonInteractiveClientConfig(*config, contextName, &clientcmd.ConfigOverrides{}, nil).ClientConfig() if err != nil { - fmt.Errorf("Failed to build client for context %s.", contextName) - continue + return nil, fmt.Errorf("Failed to build client for context %s.", contextName) } cl, err := client.New(cfg, client.Options{}) if err != nil { - fmt.Errorf("Failed to create client for context %s", contextName) - continue + return nil, fmt.Errorf("failed to create client for context %s", contextName) } manager.clients[contextName] = cl From 68bc257513e7d0bd1be9578a860bbed1356264f9 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 22 Nov 2024 12:53:28 +0100 Subject: [PATCH 14/18] Remove populate function as non needed and look it up in map directly Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index 5e52cc4a..1a5da7dd 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -179,7 +179,17 @@ func populateClusterList() ([]Cluster, error) { } // Get Node capacity - aNode.Capacity = populateNodeCapacity(node.Status.Capacity) + resources := node.Status.Capacity + + memory := resources[corev1.ResourceMemory] + cpu := resources[corev1.ResourceCPU] + pods := resources[corev1.ResourcePods] + + aNode.Capacity = Capacity{ + Memory: float64(memory.Value()) / (1024 * 1024 * 1024), + Cpu: cpu.Value(), + Pods: pods.Value(), + } // Get Node Allocated resources allocated, err := printAllocatedResources(context.Background(), cli, node.Name) @@ -232,31 +242,6 @@ func printTable(opts printers.PrintOptions, table metav1.Table) { 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 { - memoryInBytes := quantity.Value() // .Value() gives the value in bytes - memoryInGB := float64(memoryInBytes) / (1024 * 1024 * 1024) // Convert to GB - 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) (Allocated, error) { // List all pods on the specified node var podList corev1.PodList From 9fc54e25aaee639a239217c4c5242153936763b5 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 28 Nov 2024 16:56:54 +0100 Subject: [PATCH 15/18] Remove comments. Handle the error. Add a new column to show the list of nodes Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index 1a5da7dd..6adf8fa0 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -82,7 +82,6 @@ func populateClusterList() ([]Cluster, error) { detectOpt, err := util.DetectKindNodeProvider() if err != nil { - //logger.Error(err, "failed to detect the provider.") return nil, err } @@ -94,7 +93,6 @@ func populateClusterList() ([]Cluster, error) { // TODO: Check if we need it or not like also if the new code handle the kubeconfig path passed as parameter _, err = helpers.GetKubeClient(kubeConfig) if err != nil { - //logger.Error(err, "failed to create the kube client.") return nil, err } @@ -111,12 +109,14 @@ func populateClusterList() ([]Cluster, error) { provider := cluster.NewProvider(cluster.ProviderWithLogger(kind.KindLoggerFromLogr(&logger)), detectOpt) clusters, err := provider.List() if err != nil { - //logger.Error(err, "failed to list clusters.") return nil, err } - // Populate a list of Kube client for each cluster/context matching a idpbuilder cluster - manager, _ := CreateKubeClientForEachIDPCluster(config, clusters) + // Populate a list of Kube client for each cluster/context matching an idpbuilder cluster + manager, err := CreateKubeClientForEachIDPCluster(config, clusters) + if err != nil { + return nil, err + } for _, cluster := range clusters { aCluster := Cluster{Name: cluster} @@ -124,12 +124,10 @@ func populateClusterList() ([]Cluster, error) { // Search about the idp cluster within the kubeconfig file and show information c, found := findClusterByName(config, "kind-"+cluster) if !found { - //logger.Error(nil, fmt.Sprintf("Cluster not found: %s within kube config file\n", cluster)) logger.Info(fmt.Sprintf("Cluster not found: %s within kube config file\n", cluster)) } else { cli, err := GetClientForCluster(manager, cluster) if err != nil { - //logger.Error(err, fmt.Sprintf("failed to get the context for the cluster: %s.", cluster)) return nil, err } logger.V(1).Info(fmt.Sprintf("Got the context for the cluster: %s.", cluster)) @@ -137,7 +135,6 @@ func populateClusterList() ([]Cluster, error) { // Print the external port mounted on the container and available also as ingress host port targetPort, err := findExternalHTTPSPort(cli) if err != nil { - //logger.Error(err, "failed to get the kubernetes ingress service.") return nil, err } else { aCluster.ExternalPort = targetPort @@ -146,10 +143,9 @@ func populateClusterList() ([]Cluster, error) { aCluster.URLKubeApi = c.Server aCluster.TlsCheck = c.InsecureSkipTLSVerify - // Print the internal port running the Kuber API service + // Print the internal port running the Kube API service kubeApiPort, err := findInternalKubeApiPort(cli) if err != nil { - //logger.Error(err, "failed to get the kubernetes default service.") return nil, err } else { aCluster.KubePort = kubeApiPort @@ -159,7 +155,6 @@ func populateClusterList() ([]Cluster, error) { var nodeList corev1.NodeList err = cli.List(context.TODO(), &nodeList) if err != nil { - //logger.Error(err, "failed to list nodes for the current kube cluster.") return nil, err } @@ -194,11 +189,13 @@ func populateClusterList() ([]Cluster, error) { // Get Node Allocated resources allocated, err := printAllocatedResources(context.Background(), cli, node.Name) if err != nil { - //logger.Error(err, "failed to get the allocated resources.") return nil, err } aNode.Allocated = allocated + + aCluster.Nodes = append(aCluster.Nodes, aNode) } + } clusterList = append(clusterList, aCluster) } @@ -214,6 +211,7 @@ func generateClusterTable(clusterTable []Cluster) metav1.Table { {Name: "Kube-Api", Type: "string"}, {Name: "TLS", Type: "string"}, {Name: "Kube-Port", Type: "string"}, + {Name: "Nodes", Type: "string"}, } for _, cluster := range clusterTable { row := metav1.TableRow{ @@ -223,6 +221,7 @@ func generateClusterTable(clusterTable []Cluster) metav1.Table { cluster.URLKubeApi, cluster.TlsCheck, cluster.KubePort, + generateNodeData(cluster.Nodes), }, } table.Rows = append(table.Rows, row) @@ -242,6 +241,17 @@ func printTable(opts printers.PrintOptions, table metav1.Table) { fmt.Println(out.String()) } +func generateNodeData(nodes []Node) string { + var result string + for i, aNode := range nodes { + result += aNode.Name + if i < len(nodes)-1 { + result += "," + } + } + return result +} + func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeName string) (Allocated, error) { // List all pods on the specified node var podList corev1.PodList @@ -265,10 +275,6 @@ func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeN } } - // Display the total allocated resources - //fmt.Printf(" CPU Requests: %s\n", totalCPU.String()) - //fmt.Printf(" Memory Requests: %s\n", totalMemory.String()) - allocated := Allocated{ Memory: totalMemory.String(), Cpu: totalCPU.String(), From dffda70c34eaa7517ebc052a0a706c25a8847b8c Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 28 Nov 2024 17:03:56 +0100 Subject: [PATCH 16/18] Moving the kubeconfig flag from root to get command Signed-off-by: cmoulliard --- pkg/cmd/get/root.go | 2 ++ pkg/cmd/root.go | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/get/root.go b/pkg/cmd/get/root.go index a8aacd9f..c767b8c4 100644 --- a/pkg/cmd/get/root.go +++ b/pkg/cmd/get/root.go @@ -2,6 +2,7 @@ package get import ( "fmt" + "github.com/cnoe-io/idpbuilder/pkg/cmd/helpers" "github.com/spf13/cobra" ) @@ -22,6 +23,7 @@ func init() { GetCmd.AddCommand(SecretsCmd) GetCmd.PersistentFlags().StringSliceVarP(&packages, "packages", "p", []string{}, "names of packages.") GetCmd.PersistentFlags().StringVarP(&outputFormat, "output", "o", "", "Output format. json or yaml.") + GetCmd.PersistentFlags().StringVarP(&helpers.KubeConfigPath, "kubeconfig", "", "", "kube config file Path.") } func exportE(cmd *cobra.Command, args []string) error { diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index ba13b848..32d97ab4 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -21,7 +21,6 @@ var rootCmd = &cobra.Command{ func init() { rootCmd.PersistentFlags().StringVarP(&helpers.LogLevel, "log-level", "l", "info", helpers.LogLevelMsg) rootCmd.PersistentFlags().BoolVar(&helpers.ColoredOutput, "color", false, helpers.ColoredOutputMsg) - rootCmd.PersistentFlags().StringVarP(&helpers.KubeConfigPath, "kubeconfig", "", "", "kube config file Path.") rootCmd.AddCommand(create.CreateCmd) rootCmd.AddCommand(get.GetCmd) rootCmd.AddCommand(delete.DeleteCmd) From f1d03fad873357377241d9cbd5088b6d3b8b275e Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 28 Nov 2024 18:56:35 +0100 Subject: [PATCH 17/18] Grab the LocalBuild resource Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index 6adf8fa0..d84856cf 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "github.com/cnoe-io/idpbuilder/api/v1alpha1" "github.com/cnoe-io/idpbuilder/pkg/cmd/helpers" "github.com/cnoe-io/idpbuilder/pkg/kind" "github.com/cnoe-io/idpbuilder/pkg/util" @@ -133,7 +134,7 @@ func populateClusterList() ([]Cluster, error) { logger.V(1).Info(fmt.Sprintf("Got the context for the cluster: %s.", cluster)) // Print the external port mounted on the container and available also as ingress host port - targetPort, err := findExternalHTTPSPort(cli) + targetPort, err := findExternalHTTPSPort(cli, cluster) if err != nil { return nil, err } else { @@ -283,7 +284,7 @@ func printAllocatedResources(ctx context.Context, k8sClient client.Client, nodeN return allocated, nil } -func findExternalHTTPSPort(cli client.Client) (int32, error) { +func findExternalHTTPSPort(cli client.Client, clusterName string) (int32, error) { service := corev1.Service{} namespacedName := types.NamespacedName{ Name: "ingress-nginx-controller", @@ -294,9 +295,20 @@ func findExternalHTTPSPort(cli client.Client) (int32, error) { return 0, fmt.Errorf("failed to get the ingress service on the cluster. %w", err) } + localBuild := v1alpha1.Localbuild{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + }, + } + err = cli.Get(context.TODO(), client.ObjectKeyFromObject(&localBuild), &localBuild) + if err != nil { + return 0, fmt.Errorf("failed to get the localbuild on the cluster. %w", err) + } + var targetPort corev1.ServicePort + protocol := localBuild.Spec.BuildCustomization.Protocol + "-" for _, port := range service.Spec.Ports { - if port.Name != "" && strings.HasPrefix(port.Name, "https-") { + if port.Name != "" && strings.HasPrefix(port.Name, protocol) { targetPort = port break } From 8f4810c0aa298628c60fa7c0eab6c732f1ed94fb Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 28 Nov 2024 19:04:25 +0100 Subject: [PATCH 18/18] Adding the missing scheme to the clients created Signed-off-by: cmoulliard --- pkg/cmd/get/clusters.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/get/clusters.go b/pkg/cmd/get/clusters.go index d84856cf..60586070 100644 --- a/pkg/cmd/get/clusters.go +++ b/pkg/cmd/get/clusters.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/cnoe-io/idpbuilder/api/v1alpha1" "github.com/cnoe-io/idpbuilder/pkg/cmd/helpers" + "github.com/cnoe-io/idpbuilder/pkg/k8s" "github.com/cnoe-io/idpbuilder/pkg/kind" "github.com/cnoe-io/idpbuilder/pkg/util" "github.com/spf13/cobra" @@ -367,7 +368,7 @@ func CreateKubeClientForEachIDPCluster(config *api.Config, clusterList []string) return nil, fmt.Errorf("Failed to build client for context %s.", contextName) } - cl, err := client.New(cfg, client.Options{}) + cl, err := client.New(cfg, client.Options{Scheme: k8s.GetScheme()}) if err != nil { return nil, fmt.Errorf("failed to create client for context %s", contextName) }