diff --git a/internal/cli/node/list.go b/internal/cli/node/list.go index 24d6293..50e1223 100644 --- a/internal/cli/node/list.go +++ b/internal/cli/node/list.go @@ -8,8 +8,8 @@ import ( "fmt" "strings" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/bootc-dev/bink/internal/config" "github.com/bootc-dev/bink/internal/podman" @@ -21,57 +21,65 @@ func newListCmd() *cobra.Command { cmd := &cobra.Command{ Use: "list", Short: "List cluster nodes", - Long: "List all cluster nodes (containers with k8s- prefix) and their status", + Long: "List all cluster nodes and their status", RunE: func(cmd *cobra.Command, args []string) error { - logger := logrus.New() - return runList(cmd.Context(), logger, showAll) + return runList(cmd.Context(), showAll) }, } - cmd.Flags().BoolVarP(&showAll, "all", "a", false, "Show all containers (including stopped)") + cmd.Flags().BoolVarP(&showAll, "all", "a", false, "Show all nodes (including stopped)") return cmd } -func runList(ctx context.Context, logger *logrus.Logger, showAll bool) error { +func runList(ctx context.Context, showAll bool) error { + clusterName := viper.GetString("cluster.name") + podmanClient, err := podman.NewClient() if err != nil { return fmt.Errorf("creating podman client: %w", err) } - filter := fmt.Sprintf("name=%s", config.ContainerNamePrefix) + filter := config.LabelFilter(config.LabelClusterName, clusterName) containers, err := podmanClient.ContainerList(ctx, filter) if err != nil { return fmt.Errorf("listing containers: %w", err) } - if len(containers) == 0 { - fmt.Println("No cluster nodes found") - return nil + type nodeInfo struct { + name, role, state, created string } - - fmt.Printf("Found %d cluster node(s):\n\n", len(containers)) + var nodes []nodeInfo for _, containerName := range containers { if containerName == "" { continue } - nodeName := strings.TrimPrefix(containerName, config.ContainerNamePrefix) + component, _ := podmanClient.ContainerInspect(ctx, containerName, config.LabelInspectFormat(config.LabelComponent)) + if strings.TrimSpace(component) != "" { + continue + } - state, err := podmanClient.ContainerInspect(ctx, containerName, "{{.State.Status}}") + nodeName, err := podmanClient.ContainerInspect(ctx, containerName, config.LabelInspectFormat(config.LabelNodeName)) if err != nil { - logger.Warnf("Failed to inspect %s: %v", containerName, err) - fmt.Printf(" %s (status unknown)\n", nodeName) + continue + } + nodeName = strings.TrimSpace(nodeName) + if nodeName == "" { continue } + state, _ := podmanClient.ContainerInspect(ctx, containerName, "{{.State.Status}}") state = strings.TrimSpace(state) if !showAll && state != "running" { continue } + nodeRole, _ := podmanClient.ContainerInspect(ctx, containerName, config.LabelInspectFormat(config.LabelNodeRole)) + nodeRole = strings.TrimSpace(nodeRole) + created, err := podmanClient.ContainerInspect(ctx, containerName, "{{.Created}}") if err == nil { created = strings.TrimSpace(created) @@ -82,22 +90,34 @@ func runList(ctx context.Context, logger *logrus.Logger, showAll bool) error { created = "unknown" } - statusSymbol := "" - switch state { + nodes = append(nodes, nodeInfo{name: nodeName, role: nodeRole, state: state, created: created}) + } + + if len(nodes) == 0 { + fmt.Println("No cluster nodes found") + return nil + } + + fmt.Printf("Found %d cluster node(s):\n\n", len(nodes)) + + for _, n := range nodes { + statusSymbol := "?" + switch n.state { case "running": statusSymbol = "✓" case "exited": statusSymbol = "✗" case "paused": statusSymbol = "⏸" - default: - statusSymbol = "?" } - fmt.Printf(" %s %s (status: %s, created: %s)\n", statusSymbol, nodeName, state, created) + if n.role != "" { + fmt.Printf(" %s %s (role: %s, status: %s, created: %s)\n", statusSymbol, n.name, n.role, n.state, n.created) + } else { + fmt.Printf(" %s %s (status: %s, created: %s)\n", statusSymbol, n.name, n.state, n.created) + } } fmt.Println() - return nil } diff --git a/test/integration/cluster_test.go b/test/integration/cluster_test.go index 8b8c3c8..2ac0d61 100644 --- a/test/integration/cluster_test.go +++ b/test/integration/cluster_test.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "os" + "regexp" "strings" "time" @@ -159,6 +160,15 @@ var _ = Describe("Cluster Lifecycle", func() { Expect(listOutput).To(ContainSubstring(clusterName), "cluster list should contain the cluster name") Expect(listOutput).To(ContainSubstring("1 node(s)"), "cluster list should show 1 node") + By("Verifying node list shows only the cluster node") + nodeListCmd := helpers.BinkCmd("node", "list", "--cluster-name", clusterName, "--all") + nodeListSession := helpers.RunCommand(nodeListCmd) + nodeListOutput := strings.TrimSpace(string(nodeListSession.Out.Contents())) + nodeListOutput = regexp.MustCompile(`created: [^)]*\)`).ReplaceAllString(nodeListOutput, "created:") + Expect(nodeListOutput).To(Equal(fmt.Sprintf( + "Found 1 cluster node(s):\n\n ✓ %s (role: control-plane, status: running, created:", + customNodeName))) + By("Stopping the cluster") stopCmd := helpers.BinkCmd("cluster", "stop", "--cluster-name", clusterName) stopSession := helpers.RunCommand(stopCmd) diff --git a/test/integration/multinode_test.go b/test/integration/multinode_test.go index 9b54474..d5e4251 100644 --- a/test/integration/multinode_test.go +++ b/test/integration/multinode_test.go @@ -67,6 +67,7 @@ var _ = Describe("Multi-Node Clusters", func() { listCmd := helpers.BinkCmd("node", "list", "--cluster-name", clusterName) listSession := helpers.RunCommand(listCmd) listOutput := string(listSession.Out.Contents()) + Expect(listOutput).To(ContainSubstring("Found 2 cluster node(s)")) Expect(listOutput).To(ContainSubstring(node1), "node list should contain node1") Expect(listOutput).To(ContainSubstring(node2), "node list should contain node2") @@ -224,6 +225,7 @@ var _ = Describe("Multi-Node Clusters", func() { listCmd := helpers.BinkCmd("node", "list", "--cluster-name", clusterName) listSession := helpers.RunCommand(listCmd) listOutput := string(listSession.Out.Contents()) + Expect(listOutput).To(ContainSubstring("Found 3 cluster node(s)")) Expect(listOutput).To(ContainSubstring(node1), "node list should contain node1") Expect(listOutput).To(ContainSubstring(node2), "node list should contain node2") Expect(listOutput).To(ContainSubstring(node3), "node list should contain node3")