m/cli/metroctl: refactor, use tabular layout
This lays out the files to make it more obvious what command each file
implements, and uses the newly implemented tabular formatting for
listing/describing nodes.
Change-Id: I90feeae67de0f78090dd5440cbad4cb9aa6bb6bc
Reviewed-on: https://review.monogon.dev/c/monogon/+/1392
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/cli/metroctl/cmd_node.go b/metropolis/cli/metroctl/cmd_node.go
new file mode 100644
index 0000000..97198d8
--- /dev/null
+++ b/metropolis/cli/metroctl/cmd_node.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+ "context"
+ "io"
+ "log"
+ "os"
+
+ "github.com/spf13/cobra"
+
+ "source.monogon.dev/metropolis/cli/metroctl/core"
+ clicontext "source.monogon.dev/metropolis/cli/pkg/context"
+ "source.monogon.dev/metropolis/node/core/identity"
+ apb "source.monogon.dev/metropolis/proto/api"
+)
+
+var nodeCmd = &cobra.Command{
+ Short: "Updates and queries node information.",
+ Use: "node",
+}
+
+var nodeDescribeCmd = &cobra.Command{
+ Short: "Describes cluster nodes.",
+ Use: "describe [node-id] [--filter] [--output] [--format]",
+ Example: "metroctl node describe metropolis-c556e31c3fa2bf0a36e9ccb9fd5d6056",
+ Run: func(cmd *cobra.Command, args []string) {
+ ctx := clicontext.WithInterrupt(context.Background())
+ cc := dialAuthenticated(ctx)
+ mgmt := apb.NewManagementClient(cc)
+
+ nodes, err := core.GetNodes(ctx, mgmt, flags.filter)
+ if err != nil {
+ log.Fatalf("While calling Management.GetNodes: %v", err)
+ }
+
+ printNodes(nodes, args, nil)
+ },
+ Args: cobra.ArbitraryArgs,
+}
+
+var nodeListCmd = &cobra.Command{
+ Short: "Lists cluster nodes.",
+ Use: "list [node-id] [--filter] [--output] [--format]",
+ Example: "metroctl node list --filter node.status.external_address==\"10.8.0.2\"",
+ Run: func(cmd *cobra.Command, args []string) {
+ ctx := clicontext.WithInterrupt(context.Background())
+ cc := dialAuthenticated(ctx)
+ mgmt := apb.NewManagementClient(cc)
+
+ nodes, err := core.GetNodes(ctx, mgmt, flags.filter)
+ if err != nil {
+ log.Fatalf("While calling Management.GetNodes: %v", err)
+ }
+
+ printNodes(nodes, args, map[string]bool{"node id": true})
+ },
+ Args: cobra.ArbitraryArgs,
+}
+
+func init() {
+ nodeCmd.AddCommand(nodeDescribeCmd)
+ nodeCmd.AddCommand(nodeListCmd)
+ rootCmd.AddCommand(nodeCmd)
+}
+
+func printNodes(nodes []*apb.Node, args []string, onlyColumns map[string]bool) {
+ o := io.WriteCloser(os.Stdout)
+ if flags.output != "" {
+ of, err := os.Create(flags.output)
+ if err != nil {
+ log.Fatalf("Couldn't create the output file at %s: %v", flags.output, err)
+ }
+ o = of
+ }
+
+ // Narrow down the output set to supplied node IDs, if any.
+ qids := make(map[string]bool)
+ if len(args) != 0 && args[0] != "all" {
+ for _, a := range args {
+ qids[a] = true
+ }
+ }
+
+ var t table
+ for _, n := range nodes {
+ // Filter the information we want client-side.
+ if len(qids) != 0 {
+ nid := identity.NodeID(n.Pubkey)
+ if _, e := qids[nid]; !e {
+ continue
+ }
+ }
+ t.add(nodeEntry(n))
+ }
+
+ t.print(o, onlyColumns)
+}