-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #179 from vshn/fix/invalid_object_names
Escape Object names before applying
- Loading branch information
Showing
3 changed files
with
176 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package runtime | ||
|
||
import ( | ||
"crypto/sha1" | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/crossplane/function-sdk-go/resource/composed" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
// escapeK8sNames will figure out what naming scheme applies to the given object | ||
// and tries to find the correct naming scheme for it. | ||
// Then it will escape the name accordingly. | ||
func escapeK8sNames(obj client.Object) { | ||
kind, _, err := composed.Scheme.ObjectKinds(obj) | ||
if err != nil { | ||
obj.SetName(escapeDNS1123(obj.GetName(), false)) | ||
} | ||
|
||
switch kind[0].Kind { | ||
case "Pod": | ||
obj.SetName(escapeDNS1123Label(obj.GetName())) | ||
case "Namespace": | ||
obj.SetName(escapeDNS1123Label(obj.GetName())) | ||
case "Service": | ||
obj.SetName(escapeDNS1123Label(obj.GetName())) | ||
case "Role": | ||
obj.SetName(escapeDNS1123(obj.GetName(), true)) | ||
case "ClusterRole": | ||
obj.SetName(escapeDNS1123(obj.GetName(), true)) | ||
case "RoleBinding": | ||
obj.SetName(escapeDNS1123(obj.GetName(), true)) | ||
case "ClusterRoleBinding": | ||
obj.SetName(escapeDNS1123(obj.GetName(), true)) | ||
default: | ||
obj.SetName(escapeDNS1123(obj.GetName(), false)) | ||
} | ||
} | ||
|
||
// escapeDNS1123Label does the same as escapeDNS1123 but also limit to 63 chars | ||
func escapeDNS1123Label(name string) string { | ||
name = escapeDNS1123(name, false) | ||
if len(name) > 63 { | ||
suffix := hashString(name) | ||
name = name[:58] + suffix | ||
} | ||
return name | ||
} | ||
|
||
// escapeDNS1123 will always return a string that conforms to K8s' DNS subdomain naming scheme | ||
// contain no more than 253 characters | ||
// contain only lowercase alphanumeric characters, '-' or '.' | ||
// start with an alphanumeric character | ||
// end with an alphanumeric character | ||
// We also remove any '.' here, so that it can be used as a base function for escaping | ||
// label names as well. | ||
func escapeDNS1123(name string, allowColons bool) string { | ||
escapeNameStart := regexp.MustCompile("^[^a-zA-Z0-9]+") | ||
escapeNameEnd := regexp.MustCompile("[^a-zA-Z0-9]+$") | ||
escapeExpression := regexp.MustCompile("[^a-zA-Z0-9]+") | ||
dns1123LabelRegexColons := "^[a-z0-9]([-a-z0-9:]*[a-z0-9])?$" | ||
dns1123LabelRegexp := regexp.MustCompile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") | ||
|
||
if allowColons { | ||
dns1123LabelRegexp = regexp.MustCompile(dns1123LabelRegexColons) | ||
} | ||
|
||
if dns1123LabelRegexp.MatchString(name) && len(name) <= 253 { | ||
return name | ||
} | ||
|
||
escapedName := strings.ToLower(name) | ||
|
||
escapedName = escapeExpression.ReplaceAllString(escapedName, "-") | ||
escapedName = escapeNameStart.ReplaceAllString(escapedName, "") | ||
escapedName = escapeNameEnd.ReplaceAllString(escapedName, "") | ||
|
||
if len(escapedName) > 253 { | ||
suffix := hashString(escapedName) | ||
escapedName = escapedName[:248] + suffix | ||
} | ||
|
||
return escapedName | ||
} | ||
|
||
// hashString returns the first 4 symbols of the sha1 hash, prefixed with a `-` | ||
func hashString(name string) string { | ||
hasher := sha1.New() | ||
hasher.Write([]byte(name)) | ||
return "-" + fmt.Sprintf("%x", hasher.Sum(nil))[:4] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package runtime | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestServiceRuntime_escapeK8sNames(t *testing.T) { | ||
|
||
type args struct { | ||
name string | ||
allowColon bool | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
}{ | ||
{ | ||
name: "GivenInvalidString_ThenExpectValidString", | ||
args: args{ | ||
name: "Hello_World", | ||
}, | ||
want: "hello-world", | ||
}, | ||
{ | ||
name: "GivenValidString_ThenExpectValidString", | ||
args: args{ | ||
name: "hello-world", | ||
}, | ||
want: "hello-world", | ||
}, | ||
{ | ||
name: "GivenStringStartingWithUnderscore_ThenExpectValidString", | ||
args: args{ | ||
name: "_Hello_World", | ||
}, | ||
want: "hello-world", | ||
}, | ||
{ | ||
name: "GivenMoreInvalidCharacters_ThenExpectValidString", | ||
args: args{ | ||
name: "+*ç%&/()=?b+*ç%&/()=?", | ||
}, | ||
want: "b", | ||
}, | ||
{ | ||
name: "GivenVerylongName_ThenExpectValidString", | ||
args: args{ | ||
name: "AVeryLongStringWhichShouldBetrimmedToItaSensiblesize1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012", | ||
}, | ||
want: "averylongstringwhichshouldbetrimmedtoitasensiblesize1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456-fdbe", | ||
}, | ||
{ | ||
name: "GivenNameWithColon_ThenExpectValisString", | ||
args: args{ | ||
name: "test:colon", | ||
allowColon: true, | ||
}, | ||
want: "test:colon", | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got := escapeDNS1123(tt.args.name, tt.args.allowColon) | ||
assert.Equal(t, tt.want, got) | ||
}) | ||
} | ||
} |