From 469af614b023a5a970f1155ad32b221f04e7086c Mon Sep 17 00:00:00 2001 From: Irbe Krumina Date: Thu, 11 Jan 2024 20:02:03 +0000 Subject: [PATCH] cmd/k8s-operator: fix base truncating for extra long Service names (#10825) cmd/k8s-operator: fix base truncating for extra long Service names StatefulSet names for ingress/egress proxies are calculated using Kubernetes name generator and the parent resource name as a base. The name generator also cuts the base, but has a higher max cap. This commit fixes a bug where, if we get a shortened base back from the generator, we cut off too little as the base that we have cut will be passed into the generator again, which will then itself cut less because the base is shorter- so we end up with a too long name again. Updates tailscale/tailscale#10807 Co-authored-by: Maisem Ali Signed-off-by: Irbe Krumina --- cmd/k8s-operator/sts.go | 19 +++++++-------- cmd/k8s-operator/sts_test.go | 45 +++++++++++++++--------------------- 2 files changed, 28 insertions(+), 36 deletions(-) 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 + } } }