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/format.go b/metropolis/cli/metroctl/format.go
index aaf7984..d2c5ddf 100644
--- a/metropolis/cli/metroctl/format.go
+++ b/metropolis/cli/metroctl/format.go
@@ -8,6 +8,7 @@
 
 	"source.monogon.dev/metropolis/node/core/identity"
 	apb "source.monogon.dev/metropolis/proto/api"
+	cpb "source.monogon.dev/metropolis/proto/common"
 )
 
 type encoder struct {
@@ -20,6 +21,45 @@
 	return err
 }
 
+func (e *encoder) writeNode(n *apb.Node) error {
+	id := identity.NodeID(n.Pubkey)
+	if _, err := fmt.Fprintf(e.out, "%s", id); err != nil {
+		return err
+	}
+
+	state := cpb.NodeState_name[int32(n.State)]
+	if _, err := fmt.Fprintf(e.out, "\t%s", state); err != nil {
+		return err
+	}
+
+	addr := n.Status.ExternalAddress
+	if _, err := fmt.Fprintf(e.out, "\t%s", addr); err != nil {
+		return err
+	}
+
+	health := apb.Node_Health_name[int32(n.Health)]
+	if _, err := fmt.Fprintf(e.out, "\t%s", health); err != nil {
+		return err
+	}
+
+	var roles string
+	if n.Roles.KubernetesWorker != nil {
+		roles += "KubernetesWorker"
+	}
+	if n.Roles.ConsensusMember != nil {
+		roles += ",ConsensusMember"
+	}
+	if _, err := fmt.Fprintf(e.out, "\t%s", roles); err != nil {
+		return err
+	}
+
+	tshs := n.TimeSinceHeartbeat.GetSeconds()
+	if _, err := fmt.Fprintf(e.out, "\t%ds\n", tshs); err != nil {
+		return err
+	}
+	return nil
+}
+
 func (e *encoder) close() error {
 	if e.out != os.Stdout {
 		return e.out.Close()