From 1402607f33f6ca36313c0005cbea5c8bf0b008be Mon Sep 17 00:00:00 2001 From: Bevan Arps Date: Wed, 4 Sep 2024 16:32:02 +1200 Subject: [PATCH 1/2] Ensure resource names are always K8s compliant --- .../importing/importable_arm_resource.go | 48 ++++++++++++++++++- .../importing/importable_arm_resource_test.go | 48 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/v2/cmd/asoctl/internal/importing/importable_arm_resource.go b/v2/cmd/asoctl/internal/importing/importable_arm_resource.go index 34e5caf3483..c34b50c3770 100644 --- a/v2/cmd/asoctl/internal/importing/importable_arm_resource.go +++ b/v2/cmd/asoctl/internal/importing/importable_arm_resource.go @@ -10,8 +10,10 @@ import ( "fmt" "net/http" "reflect" + "regexp" "strings" "sync" + "unicode" "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" @@ -505,7 +507,9 @@ func (i *importableARMResource) SetName( n = fmt.Sprintf("%s-%s", owner.Name, name) } - importable.SetName(n) + // Sanitise the name so it's a valid Kubernetes name + safeName := safeResourceName(n) + importable.SetName(safeName) // AzureName needs to be exactly as specified in the ARM URL. // Use reflection to set it as we don't have convenient access. @@ -556,6 +560,48 @@ func (i *importableARMResource) SetOwner( } } +var safeResourceNameMappings = map[rune]rune{ + '_': '-', // underscores are replaced with hyphens + '/': '-', // slashes are replaced with hyphens + '\\': '-', // backslashes are replaced with hyphens + '%': '-', // percent signs are replaced with hyphens +} + +var safeResourceNameRegex = regexp.MustCompile("-+") + +// safeResourceName ensures the name is a valid Kubernetes name +func safeResourceName(name string) string { + buffer := make([]rune, 0, len(name)) + + for _, r := range name { + if unicode.IsLetter(r) || unicode.IsNumber(r) { + buffer = append(buffer, unicode.ToLower(r)) + continue + } + + // Discard leading special characters so that the result always starts with a letter or number + if len(buffer) == 0 { + continue + } + + if c, ok := safeResourceNameMappings[r]; ok { + buffer = append(buffer, c) + continue + } + + if unicode.IsSpace(r) { + buffer = append(buffer, '-') + continue + } + + // Otherwise skip + } + + result := string(buffer) + result = safeResourceNameRegex.ReplaceAllString(result, "-") + return result +} + type childReference struct { ID string `json:"id,omitempty"` } diff --git a/v2/cmd/asoctl/internal/importing/importable_arm_resource_test.go b/v2/cmd/asoctl/internal/importing/importable_arm_resource_test.go index e1fcc13b6b2..d486979a642 100644 --- a/v2/cmd/asoctl/internal/importing/importable_arm_resource_test.go +++ b/v2/cmd/asoctl/internal/importing/importable_arm_resource_test.go @@ -168,3 +168,51 @@ func Test_ARMResourceImporter_GroupVersionKindFromARMID(t *testing.T) { }) } } + +func Test_safeResourceName_GivenName_ReturnsExpectedResult(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + name string + expected string + }{ + "simple": { + name: "simple", + expected: "simple", + }, + "with spaces": { + name: "with spaces", + expected: "with-spaces", + }, + "with special characters": { + name: "with!@#$%^&*()_+special characters", + expected: "with-special-characters", + }, + "with underscores": { + name: "with_underscores", + expected: "with-underscores", + }, + "with multiple spaces": { + name: "with multiple spaces", + expected: "with-multiple-spaces", + }, + "with linux style paths": { + name: "/path/to/resource", + expected: "path-to-resource", + }, + "with windows style paths": { + name: "\\path\\to\\resource", + expected: "path-to-resource", + }, + } + + for name, c := range cases { + c := c + t.Run(name, func(t *testing.T) { + t.Parallel() + + g := NewGomegaWithT(t) + g.Expect(safeResourceName(c.name)).To(Equal(c.expected)) + }) + } +} From ddc0e20bda0f01e50217a17e453fbb8bba9379f7 Mon Sep 17 00:00:00 2001 From: Bevan Arps Date: Mon, 9 Sep 2024 11:17:29 +1200 Subject: [PATCH 2/2] Refactor character translation --- .../importing/importable_arm_resource.go | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/v2/cmd/asoctl/internal/importing/importable_arm_resource.go b/v2/cmd/asoctl/internal/importing/importable_arm_resource.go index c34b50c3770..ce21009b7ca 100644 --- a/v2/cmd/asoctl/internal/importing/importable_arm_resource.go +++ b/v2/cmd/asoctl/internal/importing/importable_arm_resource.go @@ -574,27 +574,32 @@ func safeResourceName(name string) string { buffer := make([]rune, 0, len(name)) for _, r := range name { - if unicode.IsLetter(r) || unicode.IsNumber(r) { + + mapped, isMapped := safeResourceNameMappings[r] + + switch { + case unicode.IsLetter(r): + // Transform letters to lowercase buffer = append(buffer, unicode.ToLower(r)) - continue - } - // Discard leading special characters so that the result always starts with a letter or number - if len(buffer) == 0 { - continue - } + case unicode.IsNumber(r): + // Keep numbers as they are + buffer = append(buffer, r) - if c, ok := safeResourceNameMappings[r]; ok { - buffer = append(buffer, c) - continue - } + case len(buffer) == 0: + // Discard leading special characters so that the result always starts with a letter or number + + case isMapped: + // Convert special characters + buffer = append(buffer, mapped) - if unicode.IsSpace(r) { + case unicode.IsSpace(r): + // Convert all kinds of spaces to hyphens buffer = append(buffer, '-') - continue - } - // Otherwise skip + default: + // Skip other characters + } } result := string(buffer)