Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 41 additions & 45 deletions controller/names/dns1035.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,37 +36,59 @@ const (
// - 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)
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 {
result := generateDNS1035LabelByMaxLength(base, uniqueName, maxLength)
// remove all suffix "-"
return strings.TrimRight(result, "-")
return generateDNS1035LabelByMaxLength(base, uniqueName, maxLength)
}

// GenerateDNS1035LabelGenerateName generates a valid DNS label prefix (compliant with RFC 1035)
//
// Usually you can set the result in metadata.generateName. kube-apiserver will combine the prefix
// with a unique suffix. Currently, the suffix is a random string with length 5.
func GenerateDNS1035LabelGenerateName(base string) string {
return GenerateDNS1035LabelPrefixByMaxLength(base, MaxGeneratedNameLength)
func generateDNS1035LabelByMaxLength(base, unique string, maxLength int) string {
return genericNameGenerator(base, unique, maxLength, validation.DNS1035LabelMaxLength, fixDNS1035Label)
}

// GenerateDNS1035LabelPrefixByMaxLength generates a valid DNS label prefix (compliant with RFC 1035)
// limited by the specified maximum length.
func GenerateDNS1035LabelPrefixByMaxLength(base string, maxLength int) string {
return generateDNS1035LabelByMaxLength(base, "", maxLength)
func fixDNS1035Label(label string) string {
// Convert to lowercase
label = strings.ToLower(label)

var builder strings.Builder
firstChar := true

// Process each character in the label
for i := 0; i < len(label); i++ {
c := label[i]

if firstChar {
// First character must be letter
if c >= 'a' && c <= 'z' {
builder.WriteByte(c)
firstChar = false
}
// 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)
} else {
// Replace invalid characters with dash
builder.WriteByte('-')
}
}

result := builder.String()
return strings.TrimRight(result, "-")
}

func generateDNS1035LabelByMaxLength(base, unique string, maxLength int) string {
func genericNameGenerator(base, unique string, maxLength, maxLimit int, fixFn func(string) string) string {
if maxLength <= 0 {
return ""
}
if maxLength > validation.DNS1035LabelMaxLength {
maxLength = validation.DNS1035LabelMaxLength
if maxLength > maxLimit {
maxLength = maxLimit
}

result := unique
Expand All @@ -80,35 +102,9 @@ func generateDNS1035LabelByMaxLength(base, unique string, maxLength int) string
if len(base) > maxPrefixLength-1 {
base = base[:maxPrefixLength-1]
}

result = base + "-" + result
}

// to lower
result = strings.ToLower(result)

b := strings.Builder{}

firstLetter := false
// fix result to match DNS1035Label prefix
// 1. first letter must be [a-z]
// 2. only contain characters in [-a-z0-9]
for i := range len(result) {
if !firstLetter {
if result[i] < 'a' || result[i] > 'z' {
// DNS1035Label must start with a lowercase letter
continue
}
firstLetter = true
b.WriteByte(result[i])
continue
}
if (result[i] >= 'a' && result[i] <= 'z') || (result[i] >= '0' && result[i] <= '9') {
// write a-z, 0-9
b.WriteByte(result[i])
} else {
// change other characters to "-"
b.WriteByte('-')
}
}
return b.String()
return fixFn(result)
}
125 changes: 73 additions & 52 deletions controller/names/dns1035_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,84 +19,103 @@ package names
import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"k8s.io/apimachinery/pkg/util/validation"
)

func Test_generateDNS1035LabelPrefix(t *testing.T) {
type dns1035TestSuite struct {
suite.Suite
}

func TestDNS1035TestSuite(t *testing.T) {
suite.Run(t, new(dns1035TestSuite))
}

func (s *dns1035TestSuite) Test_fixDNS1035Label() {
tests := []struct {
name string
base string
maxLength int
want string
name string
label string
want string
}{
{
name: "maxLength > DNS1035LabelMaxLength",
base: "ab-1234567890123456789012345678901234567890123456789012345678901234567890",
maxLength: 70,
want: "ab-12345678901234567890123456789012345678901234567890123456789-",
name: "empty string",
label: "",
want: "",
},
{
name: "maxLength == DNS1035LabelMaxLength",
base: "test-123456789012345678901234567890123456789012345678901234567890",
maxLength: MaxGeneratedNameLength,
want: "test-1234567890123456789012345678901234567890123456789012-",
name: "valid lowercase",
label: "valid-label",
want: "valid-label",
},
{
name: "normal case",
base: "test-123456789012345678901234567890123456789012345678901234567890",
maxLength: 10,
want: "test-1234-",
name: "uppercase to lowercase",
label: "VALID-LABEL",
want: "valid-label",
},
{
name: "normal case",
base: "test-123456789012345678901234567890123456789012345678901234567890",
maxLength: 5,
want: "test-",
name: "invalid characters replaced with dash",
label: "invalid.label@123",
want: "invalid-label-123",
},
{
name: "normal case",
base: "test-123456789012345678901234567890123456789012345678901234567890",
maxLength: 6,
want: "test--",
name: "starting with number - skip until letter",
label: "123abc",
want: "abc",
},
{
name: "normal case",
base: "test-1",
maxLength: MaxGeneratedNameLength,
want: "test-1-",
name: "starting with special characters - skip until letter",
label: "@#$abc",
want: "abc",
},
{
name: "replace invalid characters to '-'",
base: "test.#@1",
maxLength: MaxGeneratedNameLength,
want: "test---1-",
name: "starting with dash - skip until letter",
label: "-abc",
want: "abc",
},
{
name: "maxLength == 0",
base: "test.1",
maxLength: 0,
want: "",
name: "only numbers and special characters",
label: "123@#$",
want: "",
},
{
name: "trimleft base",
base: "0-abcd",
maxLength: validation.DNS1035LabelMaxLength,
want: "abcd-",
name: "trailing dashes trimmed",
label: "label---",
want: "label",
},
{
name: "mixed case with special characters",
label: "My.Test.Label@123",
want: "my-test-label-123",
},
{
name: "single letter",
label: "A",
want: "a",
},
{
name: "starting with underscore and number",
label: "_123abc",
want: "abc",
},
{
name: "consecutive special characters become single dash",
label: "test..label@@123",
want: "test--label--123",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GenerateDNS1035LabelPrefixByMaxLength(tt.base, tt.maxLength)
assert.Equal(t, tt.want, got)
s.Run(tt.name, func() {
got := fixDNS1035Label(tt.label)
s.Equal(tt.want, got)
if len(got) > 0 {
assert.Equal(t, byte('-'), got[len(got)-1], "prefix should end with '-'")
s.Empty(validation.IsDNS1035Label(got))
}
})
}
}

func Test_GenerateDNS1035Label(t *testing.T) {
func (s *dns1035TestSuite) Test_GenerateDNS1035Label() {
tests := []struct {
name string
base string
Expand Down Expand Up @@ -184,10 +203,12 @@ func Test_GenerateDNS1035Label(t *testing.T) {
}

for _, tt := range tests {
got := GenerateDNS1035LabelByMaxLength(tt.base, tt.unique, tt.maxLength)
assert.Equal(t, tt.want, got)
if len(got) > 0 {
assert.Empty(t, validation.IsDNS1035Label(got), "should be a valid DNS1035 label")
}
s.Run(tt.name, func() {
got := GenerateDNS1035LabelByMaxLength(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")
}
})
}
}
Loading