Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure resource names are always K8s compliant #4244

Merged
merged 2 commits into from
Sep 9, 2024
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
53 changes: 52 additions & 1 deletion v2/cmd/asoctl/internal/importing/importable_arm_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -556,6 +560,53 @@ 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 {

mapped, isMapped := safeResourceNameMappings[r]

switch {
case unicode.IsLetter(r):
// Transform letters to lowercase
buffer = append(buffer, unicode.ToLower(r))

case unicode.IsNumber(r):
// Keep numbers as they are
buffer = append(buffer, r)

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)

case unicode.IsSpace(r):
// Convert all kinds of spaces to hyphens
buffer = append(buffer, '-')

default:
// Skip other characters
}
}

result := string(buffer)
result = safeResourceNameRegex.ReplaceAllString(result, "-")
return result
}

type childReference struct {
ID string `json:"id,omitempty"`
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
}
}
Loading