m/node: allow specifying node labels during cluster bootstrap

We also drive-by refactor ProvideBootstrapData to take a structure
instead of a bunch of unnamed arguments.

Change-Id: I8d876fd726fa87420789513540b20f523994d801
Reviewed-on: https://review.monogon.dev/c/monogon/+/3103
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/node/core/cluster/BUILD.bazel b/metropolis/node/core/cluster/BUILD.bazel
index e002a31..4da6c76 100644
--- a/metropolis/node/core/cluster/BUILD.bazel
+++ b/metropolis/node/core/cluster/BUILD.bazel
@@ -11,6 +11,7 @@
     importpath = "source.monogon.dev/metropolis/node/core/cluster",
     visibility = ["//metropolis/node/core:__subpackages__"],
     deps = [
+        "//metropolis/node",
         "//metropolis/node/core/curator",
         "//metropolis/node/core/curator/proto/api",
         "//metropolis/node/core/identity",
diff --git a/metropolis/node/core/cluster/cluster_bootstrap.go b/metropolis/node/core/cluster/cluster_bootstrap.go
index f328815..3e5f745 100644
--- a/metropolis/node/core/cluster/cluster_bootstrap.go
+++ b/metropolis/node/core/cluster/cluster_bootstrap.go
@@ -26,8 +26,10 @@
 
 	"google.golang.org/protobuf/proto"
 
+	common "source.monogon.dev/metropolis/node"
 	"source.monogon.dev/metropolis/node/core/curator"
 	"source.monogon.dev/metropolis/node/core/identity"
+	"source.monogon.dev/metropolis/node/core/roleserve"
 	"source.monogon.dev/metropolis/pkg/supervisor"
 
 	apb "source.monogon.dev/metropolis/proto/api"
@@ -132,7 +134,38 @@
 	}
 	supervisor.Logger(ctx).Infof("Saved bootstrapped node's credentials.")
 
-	m.roleServer.ProvideBootstrapData(priv, ownerKey, cuk, nuk, jpriv, cc, tpmUsage)
+	labels := make(map[string]string)
+	if l := bootstrap.Labels; l != nil {
+		if nlabels := len(l.Pairs); nlabels > common.MaxLabelsPerNode {
+			supervisor.Logger(ctx).Warningf("Too many labels (%d, limit %d), truncating...", nlabels, common.MaxLabelsPerNode)
+			l.Pairs = l.Pairs[:common.MaxLabelsPerNode]
+		}
+		for _, pair := range l.Pairs {
+			if err := common.ValidateLabel(pair.Key); err != nil {
+				supervisor.Logger(ctx).Warningf("Skipping label %q/%q: key invalid: %v", pair.Key, pair.Value, err)
+				continue
+			}
+			if err := common.ValidateLabel(pair.Value); err != nil {
+				supervisor.Logger(ctx).Warningf("Skipping label %q/%q: value invalid: %v", pair.Key, pair.Value, err)
+				continue
+			}
+			if _, ok := labels[pair.Key]; ok {
+				supervisor.Logger(ctx).Warningf("Label %q/%q: repeated key, overwriting previous value", pair.Key, pair.Value)
+			}
+			labels[pair.Key] = pair.Value
+		}
+	}
+
+	bd := roleserve.BootstrapData{}
+	bd.Node.PrivateKey = priv
+	bd.Node.ClusterUnlockKey = cuk
+	bd.Node.NodeUnlockKey = nuk
+	bd.Node.JoinKey = jpriv
+	bd.Node.TPMUsage = tpmUsage
+	bd.Node.Labels = labels
+	bd.Cluster.InitialOwnerKey = ownerKey
+	bd.Cluster.Configuration = cc
+	m.roleServer.ProvideBootstrapData(&bd)
 
 	supervisor.Signal(ctx, supervisor.SignalHealthy)
 	supervisor.Signal(ctx, supervisor.SignalDone)