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/table_node.go b/metropolis/cli/metroctl/table_node.go
new file mode 100644
index 0000000..e58ad32
--- /dev/null
+++ b/metropolis/cli/metroctl/table_node.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"source.monogon.dev/metropolis/node/core/identity"
+	apb "source.monogon.dev/metropolis/proto/api"
+)
+
+func nodeEntry(n *apb.Node) entry {
+	res := entry{}
+
+	res.add("node id", identity.NodeID(n.Pubkey))
+	state := n.State.String()
+	state = strings.ReplaceAll(state, "NODE_STATE_", "")
+	res.add("state", state)
+	res.add("address", n.Status.ExternalAddress)
+	res.add("health", n.Health.String())
+
+	var roles []string
+	if n.Roles.ConsensusMember != nil {
+		roles = append(roles, "ConsensusMember")
+	}
+	if n.Roles.KubernetesController != nil {
+		roles = append(roles, "KubernetesController")
+	}
+	if n.Roles.KubernetesWorker != nil {
+		roles = append(roles, "KubernetesWorker")
+	}
+	sort.Strings(roles)
+	res.add("roles", strings.Join(roles, ","))
+
+	tshs := n.TimeSinceHeartbeat.GetSeconds()
+	res.add("heartbeat", fmt.Sprintf("%ds", tshs))
+
+	return res
+}