metropolis: support prefixes in node labels

This brings Metropolis node label semantics to be the same as Kubernetes
labels.

Change-Id: I33c321432ec01abf978bb8dfbb3cef90f75a38eb
Reviewed-on: https://review.monogon.dev/c/monogon/+/3467
Tested-by: Jenkins CI
Reviewed-by: Jan Schär <jan@monogon.tech>
diff --git a/metropolis/node/labels_test.go b/metropolis/node/labels_test.go
index 0e8b799..c8e1a86 100644
--- a/metropolis/node/labels_test.go
+++ b/metropolis/node/labels_test.go
@@ -3,9 +3,38 @@
 import (
 	"errors"
 	"testing"
+
+	"k8s.io/apimachinery/pkg/util/validation"
 )
 
-func TestValidateLabelKeyValue(t *testing.T) {
+func TestValidateLabelKey(t *testing.T) {
+	for i, te := range []struct {
+		in   string
+		want error
+	}{
+		{"foo", nil},
+		{"example.com/", ErrLabelEmpty},
+		{"foo-bar.baz_barfoo", nil},
+		{"-", ErrLabelInvalidFirstCharacter},
+		{"-invalid", ErrLabelInvalidFirstCharacter},
+		{"invalid-", ErrLabelInvalidLastCharacter},
+		{"", ErrLabelEmpty},
+		{"accordingtoallknownlawsofaviationthereisnowaythatabeeshouldbeabletofly", ErrLabelTooLong},
+		{"example.com/annotation", nil},
+		{"/annotation", ErrLabelEmptyPrefix},
+		{"_internal.example.com/annotation", ErrLabelInvalidPrefix},
+		{"./annotation", ErrLabelInvalidPrefix},
+		{"../annotation", ErrLabelInvalidPrefix},
+		{"tcp:80.example.com/annotation", ErrLabelInvalidPrefix},
+		{"github.com/monogon-dev/monogon/annotation", ErrLabelInvalidPrefix},
+	} {
+		if got := ValidateLabelKey(te.in); !errors.Is(got, te.want) {
+			t.Errorf("%d (%q): wanted %v, got %v", i, te.in, te.want, got)
+		}
+	}
+}
+
+func TestValidateLabelValue(t *testing.T) {
 	for i, te := range []struct {
 		in   string
 		want error
@@ -15,12 +44,23 @@
 		{"-", ErrLabelInvalidFirstCharacter},
 		{"-invalid", ErrLabelInvalidFirstCharacter},
 		{"invalid-", ErrLabelInvalidLastCharacter},
-		{"", ErrLabelEmpty},
+		{"", nil},
 		{"accordingtoallknownlawsofaviationthereisnowaythatabeeshouldbeabletofly", ErrLabelTooLong},
 		{"example.com/annotation", ErrLabelInvalidCharacter},
+		{"/annotation", ErrLabelInvalidFirstCharacter},
+		{"_internal.example.com/annotation", ErrLabelInvalidFirstCharacter},
+		{"./annotation", ErrLabelInvalidFirstCharacter},
+		{"../annotation", ErrLabelInvalidFirstCharacter},
+		{"tcp:80.example.com/annotation", ErrLabelInvalidCharacter},
+		{"github.com/monogon-dev/monogon/annotation", ErrLabelInvalidCharacter},
 	} {
-		if got := ValidateLabel(te.in); !errors.Is(got, te.want) {
-			t.Errorf("%d: wanted %v, got %v", i, te.want, got)
+		// Test our implementation against test cases.
+		if got := ValidateLabelValue(te.in); !errors.Is(got, te.want) {
+			t.Errorf("%d (%q): wanted %v, got %v", i, te.in, te.want, got)
+		}
+		// Validate test cases against Kubernetes.
+		if errs := validation.IsValidLabelValue(te.in); (te.want == nil) != (len(errs) == 0) {
+			t.Errorf("%d (%q): wanted %v, kubernetes implementation returned %v", i, te.in, te.want, errs)
 		}
 	}
 }