m/c/metroctl: implement the "describe" command

This implements metroctl's "describe".

cmd.TerminateIfFound was adjusted to meet new test needs.

Change-Id: If86f35bc648d99396e7d5be48ab459d6b13334ce
Reviewed-on: https://review.monogon.dev/c/monogon/+/850
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/cli/metroctl/describe.go b/metropolis/cli/metroctl/describe.go
new file mode 100644
index 0000000..3426b5d
--- /dev/null
+++ b/metropolis/cli/metroctl/describe.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+	"context"
+	"log"
+
+	"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 describeCmd = &cobra.Command{
+	Short:   "Describes cluster nodes.",
+	Use:     "describe [node-id] [--filter] [--output] [--format]",
+	Example: "metroctl node describe metropolis-c556e31c3fa2bf0a36e9ccb9fd5d6056",
+	Run:     doDescribe,
+	Args:    cobra.ArbitraryArgs,
+}
+
+func init() {
+	nodeCmd.AddCommand(describeCmd)
+}
+
+func printNodes(of func(*encoder, *apb.Node) error, nodes []*apb.Node, args []string) {
+	// 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
+		}
+	}
+
+	enc := newOutputEncoder()
+	defer enc.close()
+
+	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
+			}
+		}
+
+		if err := of(enc, n); err != nil {
+			log.Fatalf("While listing nodes: %v", err)
+		}
+	}
+}
+
+func doDescribe(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)
+	}
+
+	of := func(enc *encoder, n *apb.Node) error {
+		return enc.writeNode(n)
+	}
+	printNodes(of, nodes, args)
+}