From d9db883f47707e6dd4e7c3b2df0dd2fc96438f16 Mon Sep 17 00:00:00 2001 From: HarshwardhanPatil07 Date: Mon, 24 Aug 2026 10:55:56 +0530 Subject: [PATCH 1/2] cli: fix node list to show only cluster nodes node list was using a container name prefix filter (name=k8s-) which matched all bink containers including infrastructure ones (DNS, HAProxy). It also did not scope to the requested cluster and displayed node names with the cluster prefix baked in. Switch to label-based filtering (bink.cluster-name) scoped to the active cluster, skip containers that carry a bink.component label, and read node names from the bink.node-name label. Also display the node role when available. Fixes: https://github.com/bootc-dev/bink/issues/108 Assisted-by: AI Signed-off-by: HarshwardhanPatil07 --- internal/cli/node/list.go | 64 +++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 22 deletions(-) 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 } From 4319f91ee65ab88bce0c379b3aa47e1a35891318 Mon Sep 17 00:00:00 2001 From: HarshwardhanPatil07 Date: Mon, 24 Aug 2026 10:56:01 +0530 Subject: [PATCH 2/2] test: verify node list output matches expected nodes Add node list verification to the single-node cluster test checking the exact output content (minus timestamps). Add node count assertions to the multinode tests to verify the full list. Assisted-by: AI Signed-off-by: HarshwardhanPatil07 --- test/integration/cluster_test.go | 10 ++++++++++ test/integration/multinode_test.go | 2 ++ 2 files changed, 12 insertions(+) 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")