From a4812217ba6e187f60ac3f32f8194862c85a0565 Mon Sep 17 00:00:00 2001 From: zoumo Date: Tue, 9 Dec 2025 21:35:05 +0800 Subject: [PATCH] refactor: name generator interface --- controller/history/revision.go | 9 ++-- controller/names/dns1035.go | 54 +------------------- controller/names/dns1035_test.go | 8 +-- controller/names/dns1123.go | 52 +++---------------- controller/names/dns1123_test.go | 2 +- controller/names/generator.go | 85 ++++++++++++++++++++++++++++++++ 6 files changed, 102 insertions(+), 108 deletions(-) create mode 100644 controller/names/generator.go diff --git a/controller/history/revision.go b/controller/history/revision.go index d295d800..e9edf2d6 100644 --- a/controller/history/revision.go +++ b/controller/history/revision.go @@ -22,6 +22,7 @@ import ( "fmt" "hash" "hash/fnv" + "maps" "sort" "strconv" "sync" @@ -52,7 +53,7 @@ const ( // If the length of final name is greater than 63 bytes, the prefix is truncated to allow for a name // that is no larger than 63 bytes. func ControllerRevisionName(prefix, hash string) string { - return names.GenerateDNS1035Label(prefix, hash) + return names.DNS1035LabelGenerator.GenerateName(prefix, hash) } // HashControllerRevision hashes the contents of revision's Data using FNV hashing. If probe is not nil, the byte value @@ -62,7 +63,7 @@ func HashControllerRevision(revision *apps.ControllerRevision, probe *int32) str } var hashPool = sync.Pool{ - New: func() interface{} { + New: func() any { return fnv.New32() }, } @@ -169,9 +170,7 @@ func NewControllerRevision(parent metav1.Object, collisionCount *int32, ) (*apps.ControllerRevision, error) { labelMap := make(map[string]string) - for k, v := range templateLabels { - labelMap[k] = v - } + maps.Copy(labelMap, templateLabels) blockOwnerDeletion := true isController := true cr := &apps.ControllerRevision{ diff --git a/controller/names/dns1035.go b/controller/names/dns1035.go index a6632a41..3ba72675 100644 --- a/controller/names/dns1035.go +++ b/controller/names/dns1035.go @@ -22,32 +22,8 @@ import ( "k8s.io/apimachinery/pkg/util/validation" ) -const ( - // copy from k8s.io/apiserver/pkg/storage/names - randomLength = 5 - MaxGeneratedNameLength = validation.DNS1035LabelMaxLength - randomLength -) - -// GenerateDNS1035Label generates a valid DNS label (compliant with RFC 1035). -// The result is usually combined by the base and uniqueName, such as "base-uniqueName". -// And all "." will be replaced with "-". If the generated name is too long, the suffix -// of base will be truncated to ensure the final name is shorter than 63 characters. -// Usually: -// - base is the name of workload, such as "deployment", "statefulset", "daemonset". -// - uniqueName is a random string, such as "12345" or ordinal index. -func GenerateDNS1035Label(base, uniqueName string) string { - return generateDNS1035LabelByMaxLength(base, uniqueName, validation.DNS1035LabelMaxLength) -} - -// GenerateDNS1035LabelByMaxLength generates a valid DNS label (compliant with RFC 1035) -// limited by the specified maximum length. -func GenerateDNS1035LabelByMaxLength(base, uniqueName string, maxLength int) string { - return generateDNS1035LabelByMaxLength(base, uniqueName, maxLength) -} - -func generateDNS1035LabelByMaxLength(base, unique string, maxLength int) string { - return genericNameGenerator(base, unique, maxLength, validation.DNS1035LabelMaxLength, fixDNS1035Label) -} +// DNS1035LabelGenerator generates a valid DNS label (compliant with RFC 1035). +var DNS1035LabelGenerator = newNameGenerator(validation.DNS1035LabelMaxLength, fixDNS1035Label) func fixDNS1035Label(label string) string { // Convert to lowercase @@ -82,29 +58,3 @@ func fixDNS1035Label(label string) string { result := builder.String() return strings.TrimRight(result, "-") } - -func genericNameGenerator(base, unique string, maxLength, maxLimit int, fixFn func(string) string) string { - if maxLength <= 0 { - return "" - } - if maxLength > maxLimit { - maxLength = maxLimit - } - - result := unique - if len(result) >= maxLength { - result = result[:maxLength] - } - - maxPrefixLength := maxLength - len(result) - if maxPrefixLength > 0 { - // add prefix - if len(base) > maxPrefixLength-1 { - base = base[:maxPrefixLength-1] - } - - result = base + "-" + result - } - - return fixFn(result) -} diff --git a/controller/names/dns1035_test.go b/controller/names/dns1035_test.go index 7e1a6b74..75cd3f8f 100644 --- a/controller/names/dns1035_test.go +++ b/controller/names/dns1035_test.go @@ -183,28 +183,28 @@ func (s *dns1035TestSuite) Test_GenerateDNS1035Label() { name: "replace invalid characters to '-'", base: "test.#@_1", unique: "0", - maxLength: MaxGeneratedNameLength, + maxLength: 63, want: "test----1-0", }, { name: "to lower case", base: "TEST.1", unique: "0", - maxLength: MaxGeneratedNameLength, + maxLength: 63, want: "test-1-0", }, { name: "to lower case", base: "test-1", unique: "!@#$%^&*()_+", - maxLength: MaxGeneratedNameLength, + maxLength: 63, want: "test-1", }, } for _, tt := range tests { s.Run(tt.name, func() { - got := GenerateDNS1035LabelByMaxLength(tt.base, tt.unique, tt.maxLength) + got := DNS1035LabelGenerator.GenerateNameWithMaxLength(tt.base, tt.unique, tt.maxLength) s.Equal(tt.want, got) if len(got) > 0 { s.Empty(validation.IsDNS1035Label(got), "should be a valid DNS1035 label") diff --git a/controller/names/dns1123.go b/controller/names/dns1123.go index 172ce85e..a24864e4 100644 --- a/controller/names/dns1123.go +++ b/controller/names/dns1123.go @@ -22,49 +22,12 @@ import ( "k8s.io/apimachinery/pkg/util/validation" ) -// GenerateDNS1123Label generates a valid DNS label (compliant with RFC 1123). -// The result is usually combined by the base and uniqueName, such as "base-uniqueName". -// If the generated name is too long, the suffix of base will be truncated to ensure the -// final name is shorter than 63 characters. -// -// Usually: -// - base is the name of workload, such as "deployment", "statefulset", "daemonset". -// - uniqueName is a random string, such as "12345" or ordinal index. -func GenerateDNS1123Label(base, unique string) string { - return genereateDNS1123LabelByMaxLength(base, unique, validation.DNS1123LabelMaxLength) -} - -// GenerateDNS1123LabelByMaxLength generates a valid DNS label (compliant with RFC 1123) -// limited by the specified maximum length. -func GenereateDNS1123LabelByMaxLength(base, unique string, maxLength int) string { - return genereateDNS1123LabelByMaxLength(base, unique, maxLength) -} - -func genereateDNS1123LabelByMaxLength(base, unique string, maxLength int) string { - return genericNameGenerator(base, unique, maxLength, validation.DNS1123LabelMaxLength, fixDNS1123Label) -} - -// GenerateDNS1123Subdomain generates a valid DNS subdomain (compliant with RFC 1123). -// The result is usually combined by the base and uniqueName, such as "base-uniqueName". -// If the generated name is too long, the suffix of base will be truncated to ensure the -// final name is shorter than 253 characters. -// -// Usually: -// - base is the name of workload, such as "deployment", "statefulset", "daemonset". -// - uniqueName is a random string, such as "12345" or ordinal index. -func GenerateDNS1123Subdomain(base, unique string) string { - return generateDNS1123SubdomainByMaxLength(base, unique, validation.DNS1123SubdomainMaxLength) -} - -// GenerateDNS1123SubdomainByMaxLength generates a valid DNS subdomain (compliant with RFC 1123) -// limited by the specified maximum length. -func GenerateDNS1123SubdomainByMaxLength(base, unique string, maxLength int) string { - return generateDNS1123SubdomainByMaxLength(base, unique, maxLength) -} - -func generateDNS1123SubdomainByMaxLength(base, unique string, maxLength int) string { - return genericNameGenerator(base, unique, maxLength, validation.DNS1123SubdomainMaxLength, fixDNS1123Subdomain) -} +var ( + // DNS1123LabelGenerator generates a valid DNS label (compliant with RFC 1123). + DNS1123LabelGenerator = newNameGenerator(validation.DNS1123LabelMaxLength, fixDNS1123Label) + // DNS1123SubdomainGenerator generates a valid DNS subdomain (compliant with RFC 1123). + DNS1123SubdomainGenerator = newNameGenerator(validation.DNS1123SubdomainMaxLength, fixDNS1123Subdomain) +) func fixDNS1123Label(label string) string { // Convert to lowercase @@ -76,7 +39,6 @@ func fixDNS1123Label(label string) string { // Process each character in the label for i := 0; i < len(label); i++ { c := label[i] - if firstChar { // First character must be alphanumeric if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') { @@ -86,7 +48,6 @@ func fixDNS1123Label(label string) string { // Skip non-alphanumeric characters at the beginning continue } - // Subsequent characters: allow alphanumeric and dash if (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' { builder.WriteByte(c) @@ -97,7 +58,6 @@ func fixDNS1123Label(label string) string { } result := builder.String() - return strings.TrimRight(result, "-") } diff --git a/controller/names/dns1123_test.go b/controller/names/dns1123_test.go index 8eaefc1c..84cca554 100644 --- a/controller/names/dns1123_test.go +++ b/controller/names/dns1123_test.go @@ -291,7 +291,7 @@ func (s *dns1123TestSuite) TestGenerateDNS1123SubdomainByMaxLength() { for _, tc := range testCases { s.Run(tc.name, func() { - result := generateDNS1123SubdomainByMaxLength(tc.base, tc.unique, tc.maxLength) + result := DNS1123SubdomainGenerator.GenerateNameWithMaxLength(tc.base, tc.unique, tc.maxLength) s.Equal(tc.expected, result) if len(result) > 0 { s.Empty(validation.IsDNS1123Subdomain(result)) diff --git a/controller/names/generator.go b/controller/names/generator.go new file mode 100644 index 00000000..f18a7dd0 --- /dev/null +++ b/controller/names/generator.go @@ -0,0 +1,85 @@ +/* +Copyright 2025 The KusionStack Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package names + +// NameGenerator is an interface for generating names for resources. +type NameGenerator interface { + // GenerateName generates a valid name. + // The result is usually combined by the base and uniqueName, such as "base-uniqueName". + // The generator may have a default max length limit. If the generated name is too long, + // it will be truncated to ensure the final name is shorter than the limit. + // + // Usually: + // - base is the name of workload, such as "deployment", "statefulset", "daemonset". + // - uniqueName is a random string, such as "12345" or ordinal index. + GenerateName(base, unique string) string + // GenerateNameWithMaxLength generates a valid name with max length limit. + // The result is usually combined by the base and uniqueName, such as "base-uniqueName". + // If the generated name is too long, the suffix of base will be truncated to ensure the + // final name is shorter than maxLength. + // + // Usually: + // - base is the name of workload, such as "deployment", "statefulset", "daemonset". + // - uniqueName is a random string, such as "12345" or ordinal index. + GenerateNameWithMaxLength(base, unique string, maxLength int) string +} + +type formatFn func(string) string + +// nameGenerator is an implementation of NameGenerator. +type nameGenerator struct { + formatFixFn formatFn + maxLangthLimit int +} + +func newNameGenerator(lengthLimit int, fixFn formatFn) NameGenerator { + return &nameGenerator{ + maxLangthLimit: lengthLimit, + formatFixFn: fixFn, + } +} + +// GenerateName generates a valid name. +func (g *nameGenerator) GenerateName(base, unique string) string { + return g.GenerateNameWithMaxLength(base, unique, g.maxLangthLimit) +} + +// GenerateNameWithMaxLength generates a valid name with max length limit. +func (g *nameGenerator) GenerateNameWithMaxLength(base, unique string, maxLength int) string { + if maxLength <= 0 { + return "" + } + if maxLength > g.maxLangthLimit { + maxLength = g.maxLangthLimit + } + + result := unique + if len(result) >= maxLength { + result = result[:maxLength] + } + + maxPrefixLength := maxLength - len(result) + if maxPrefixLength > 0 { + // add prefix + if len(base) > maxPrefixLength-1 { + base = base[:maxPrefixLength-1] + } + + result = base + "-" + result + } + + return g.formatFixFn(result) +}