Skip to content

Commit e49d943

Browse files
Maria Ntallahoegaardencamilamacedo86
committed
Support mixed case Kind
The implementation * uses the same validation logic that runs against `Kind` in apimachinery; i.e. allows any `Kind` value that, when converted to lowercase, is a valid DNS-1035 label. * additionally requires that `Kind`s start with an uppercase character, to ensure they map to exported types. Co-Authored-By: Hannes Hörl <[email protected]> Co-Authored-By: Camila Macedo <[email protected]>
1 parent c6e1ee1 commit e49d943

File tree

3 files changed

+64
-20
lines changed

3 files changed

+64
-20
lines changed

pkg/model/resource/options.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,17 @@ func (opts *Options) Validate() error {
126126
"version must match %s (was %s)", versionPattern, opts.Version)
127127
}
128128

129-
// Check if the Kind is PascalCase
130-
if opts.Kind != flect.Pascalize(opts.Kind) {
131-
return fmt.Errorf("kind must be PascalCase (expected %s was %s)", flect.Pascalize(opts.Kind), opts.Kind)
129+
validationErrors := []string{}
130+
131+
// require Kind to start with an uppercase character
132+
if string(opts.Kind[0]) == strings.ToLower(string(opts.Kind[0])) {
133+
validationErrors = append(validationErrors, "Kind must start with an uppercase character")
134+
}
135+
136+
validationErrors = append(validationErrors, isDNS1035Label(strings.ToLower(opts.Kind))...)
137+
138+
if len(validationErrors) != 0 {
139+
return fmt.Errorf("Invalid Kind: %#v", validationErrors)
132140
}
133141

134142
// TODO: validate plural strings if provided

pkg/model/resource/options_test.go

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package resource_test
22

33
import (
4+
"strings"
5+
46
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/ginkgo/extensions/table"
58
. "github.com/onsi/gomega"
69

710
. "sigs.k8s.io/kubebuilder/pkg/model/resource"
@@ -73,24 +76,38 @@ var _ = Describe("Resource Options", func() {
7376
Expect(options.Validate().Error()).To(ContainSubstring("kind cannot be empty"))
7477
})
7578

76-
It("should fail if the Kind is not pascal cased", func() {
77-
// Base case
78-
options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"}
79-
Expect(options.Validate()).To(Succeed())
80-
81-
// Can't detect this case :(
82-
options = &Options{Group: "crew", Version: "v1", Kind: "Firstmate"}
83-
Expect(options.Validate()).To(Succeed())
84-
85-
options = &Options{Group: "crew", Version: "v1", Kind: "firstMate"}
86-
Expect(options.Validate()).NotTo(Succeed())
87-
Expect(options.Validate().Error()).To(ContainSubstring(
88-
`kind must be PascalCase (expected FirstMate was firstMate)`))
79+
DescribeTable("valid Kind values-according to core Kubernetes",
80+
func(kind string) {
81+
options := &Options{Group: "crew", Kind: kind, Version: "v1"}
82+
Expect(options.Validate()).To(Succeed())
83+
},
84+
Entry("should pass validation if Kind is camelcase", "FirstMate"),
85+
Entry("should pass validation if Kind has more than one caps at the start", "FIRSTMate"),
86+
)
87+
88+
It("should fail if Kind is too long", func() {
89+
kind := strings.Repeat("a", 64)
90+
91+
options := &Options{Group: "crew", Kind: kind, Version: "v1"}
92+
err := options.Validate()
93+
Expect(err).To(MatchError(ContainSubstring("must be no more than 63 characters")))
94+
})
8995

90-
options = &Options{Group: "crew", Version: "v1", Kind: "firstmate"}
91-
Expect(options.Validate()).NotTo(Succeed())
92-
Expect(options.Validate().Error()).To(ContainSubstring(
93-
`kind must be PascalCase (expected Firstmate was firstmate)`))
96+
DescribeTable("invalid Kind values-according to core Kubernetes",
97+
func(kind string) {
98+
options := &Options{Group: "crew", Kind: kind, Version: "v1"}
99+
Expect(options.Validate()).To(MatchError(
100+
ContainSubstring("a DNS-1035 label must consist of lower case alphanumeric characters")))
101+
},
102+
Entry("should fail validation if Kind contains whitespaces", "Something withSpaces"),
103+
Entry("should fail validation if Kind ends in -", "KindEndingIn-"),
104+
Entry("should fail validation if Kind starts with number", "0ValidityKind"),
105+
)
106+
107+
It("should fail if Kind starts with a lowercase character", func() {
108+
options := &Options{Group: "crew", Kind: "lOWERCASESTART", Version: "v1"}
109+
err := options.Validate()
110+
Expect(err).To(MatchError(ContainSubstring("Kind must start with an uppercase character")))
94111
})
95112
})
96113
})

pkg/model/resource/validation.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,17 @@ const (
3333

3434
// dns1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123)
3535
dns1123SubdomainMaxLength int = 253
36+
37+
dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
38+
dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', " +
39+
"start with an alphabetic character, and end with an alphanumeric character"
40+
41+
// DNS1035LabelMaxLength is a label's max length in DNS (RFC 1035)
42+
dns1035LabelMaxLength int = 63
3643
)
3744

3845
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
46+
var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$")
3947

4048
// IsDNS1123Subdomain tests for a string that conforms to the definition of a
4149
// subdomain in DNS (RFC 1123).
@@ -50,6 +58,17 @@ func IsDNS1123Subdomain(value string) []string {
5058
return errs
5159
}
5260

61+
func isDNS1035Label(value string) []string {
62+
var errs []string
63+
if len(value) > dns1035LabelMaxLength {
64+
errs = append(errs, maxLenError(dns1035LabelMaxLength))
65+
}
66+
if !dns1035LabelRegexp.MatchString(value) {
67+
errs = append(errs, regexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123"))
68+
}
69+
return errs
70+
}
71+
5372
// MaxLenError returns a string explanation of a "string too long" validation
5473
// failure.
5574
func maxLenError(length int) string {

0 commit comments

Comments
 (0)