diff --git a/cmd/k8s-operator/sts.go b/cmd/k8s-operator/sts.go index 6284f2acc..c4ad2cf1c 100644 --- a/cmd/k8s-operator/sts.go +++ b/cmd/k8s-operator/sts.go @@ -214,18 +214,19 @@ func (a *tailscaleSTSReconciler) Cleanup(ctx context.Context, logger *zap.Sugare // generation will NOT result in a StatefulSet name longer than 52 chars. // This is done because of https://github.com/kubernetes/kubernetes/issues/64023. func statefulSetNameBase(parent string) string { - base := fmt.Sprintf("ts-%s-", parent) - - // Calculate what length name GenerateName returns for this base. generator := names.SimpleNameGenerator - generatedName := generator.GenerateName(base) - - if excess := len(generatedName) - maxStatefulSetNameLength; excess > 0 { - base = base[:len(base)-excess-1] // take extra char off to make space for hyphen - base = base + "-" // re-instate hyphen + for { + generatedName := generator.GenerateName(base) + excess := len(generatedName) - maxStatefulSetNameLength + if excess <= 0 { + return base + } + base = base[:len(base)-1-excess] // cut off the excess chars + if !strings.HasSuffix(base, "-") { // dash may have been cut by the generator + base = base + "-" + } } - return base } func (a *tailscaleSTSReconciler) reconcileHeadlessService(ctx context.Context, logger *zap.SugaredLogger, sts *tailscaleSTSConfig) (*corev1.Service, error) { diff --git a/cmd/k8s-operator/sts_test.go b/cmd/k8s-operator/sts_test.go index 43d57d649..a31775ee0 100644 --- a/cmd/k8s-operator/sts_test.go +++ b/cmd/k8s-operator/sts_test.go @@ -6,6 +6,9 @@ package main import ( + "fmt" + "regexp" + "strings" "testing" ) @@ -19,32 +22,20 @@ // https://github.com/kubernetes/kubernetes/blob/v1.28.4/staging/src/k8s.io/apiserver/pkg/storage/names/generate.go#L45. // https://github.com/kubernetes/kubernetes/pull/116430 func Test_statefulSetNameBase(t *testing.T) { - tests := []struct { - name string - in string - out string - }{ - { - name: "43 chars", - in: "oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9xb", - out: "ts-oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9xb-", - }, - { - name: "44 chars", - in: "oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9xbo", - out: "ts-oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9xb-", - }, - { - name: "42 chars", - in: "oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9x", - out: "ts-oidhexl9o832hcbhyg4uz6o0s7u9uae54h5k8ofs9x-", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := statefulSetNameBase(tt.in); got != tt.out { - t.Errorf("stsNamePrefix(%s) = %q, want %s", tt.in, got, tt.out) - } - }) + // Service name lengths can be 1 - 63 chars, be paranoid and test them all. + var b strings.Builder + for b.Len() < 63 { + if _, err := b.WriteString("a"); err != nil { + t.Fatalf("error writing to string builder: %v", err) + } + baseLength := len(b.String()) + if baseLength > 43 { + baseLength = 43 // currently 43 is the max base length + } + wantsNameR := regexp.MustCompile(`^ts-a{` + fmt.Sprint(baseLength) + `}-$`) // to match a string like ts-aaaa- + gotName := statefulSetNameBase(b.String()) + if !wantsNameR.MatchString(gotName) { + t.Fatalf("expected string %s to match regex %s ", gotName, wantsNameR.String()) // fatal rather than error as this test is called 63 times + } } }