Skip to content

Commit

Permalink
Merge pull request #1974 from Adirio/split-resource-validation
Browse files Browse the repository at this point in the history
⚠ Resource validation
  • Loading branch information
k8s-ci-robot committed Feb 2, 2021
2 parents 564a6e9 + db966c6 commit 90344af
Show file tree
Hide file tree
Showing 17 changed files with 390 additions and 458 deletions.
10 changes: 10 additions & 0 deletions pkg/model/resource/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ type API struct {
Namespaced bool `json:"namespaced,omitempty"`
}

// Validate checks that the API is valid.
func (api API) Validate() error {
// Validate the CRD version
if err := validateAPIVersion(api.CRDVersion); err != nil {
return fmt.Errorf("invalid CRD version: %w", err)
}

return nil
}

// Copy returns a deep copy of the API that can be safely modified without affecting the original.
func (api API) Copy() API {
// As this function doesn't use a pointer receiver, api is already a shallow copy.
Expand Down
13 changes: 13 additions & 0 deletions pkg/model/resource/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ import (

//nolint:dupl
var _ = Describe("API", func() {
Context("Validate", func() {
It("should succeed for a valid API", func() {
Expect(API{CRDVersion: v1}.Validate()).To(Succeed())
})

DescribeTable("should fail for invalid APIs",
func(api API) { Expect(api.Validate()).NotTo(Succeed()) },
// Ensure that the rest of the fields are valid to check each part
Entry("empty CRD version", API{}),
Entry("invalid CRD version", API{CRDVersion: "1"}),
)
})

Context("Update", func() {
var api, other API

Expand Down
40 changes: 40 additions & 0 deletions pkg/model/resource/gvk.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ package resource

import (
"fmt"
"regexp"
"strings"

"sigs.k8s.io/kubebuilder/v3/pkg/internal/validation"
)

const (
versionPattern = "^v\\d+(?:alpha\\d+|beta\\d+)?$"
)

var (
versionRegex = regexp.MustCompile(versionPattern)
)

// GVK stores the Group - Version - Kind triplet that uniquely identifies a resource.
Expand All @@ -29,6 +41,34 @@ type GVK struct {
Kind string `json:"kind"`
}

// Validate checks that the GVK is valid.
func (gvk GVK) Validate() error {
// Check if the qualified group has a valid DNS1123 subdomain value
if err := validation.IsDNS1123Subdomain(gvk.QualifiedGroup()); err != nil {
// NOTE: IsDNS1123Subdomain returns a slice of strings instead of an error, so no wrapping
return fmt.Errorf("either Group or Domain is invalid: %s", err)
}

// Check if the version follows the valid pattern
if !versionRegex.MatchString(gvk.Version) {
return fmt.Errorf("Version must match %s (was %s)", versionPattern, gvk.Version)
}

// Check if kind has a valid DNS1035 label value
if errors := validation.IsDNS1035Label(strings.ToLower(gvk.Kind)); len(errors) != 0 {
// NOTE: IsDNS1035Label returns a slice of strings instead of an error, so no wrapping
return fmt.Errorf("invalid Kind: %#v", errors)
}

// Require kind to start with an uppercase character
// NOTE: previous validation already fails for empty strings, gvk.Kind[0] will not panic
if string(gvk.Kind[0]) == strings.ToLower(string(gvk.Kind[0])) {
return fmt.Errorf("invalid Kind: must start with an uppercase character")
}

return nil
}

// QualifiedGroup returns the fully qualified group name with the available information.
func (gvk GVK) QualifiedGroup() string {
switch "" {
Expand Down
41 changes: 37 additions & 4 deletions pkg/model/resource/gvk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package resource

import (
"strings"

. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
Expand All @@ -30,19 +32,50 @@ var _ = Describe("GVK", func() {
kind = "Kind"
)

var gvk = GVK{Group: group, Domain: domain, Version: version, Kind: kind}

Context("Validate", func() {
It("should succeed for a valid GVK", func() {
Expect(gvk.Validate()).To(Succeed())
})

DescribeTable("should fail for invalid GVKs",
func(gvk GVK) { Expect(gvk.Validate()).NotTo(Succeed()) },
// Ensure that the rest of the fields are valid to check each part
Entry("Group (uppercase)", GVK{Group: "Group", Domain: domain, Version: version, Kind: kind}),
Entry("Group (non-alpha characters)", GVK{Group: "_*?", Domain: domain, Version: version, Kind: kind}),
Entry("Domain (uppercase)", GVK{Group: group, Domain: "Domain", Version: version, Kind: kind}),
Entry("Domain (non-alpha characters)", GVK{Group: group, Domain: "_*?", Version: version, Kind: kind}),
Entry("Group and Domain (empty)", GVK{Group: "", Domain: "", Version: version, Kind: kind}),
Entry("Version (empty)", GVK{Group: group, Domain: domain, Version: "", Kind: kind}),
Entry("Version (no v prefix)", GVK{Group: group, Domain: domain, Version: "1", Kind: kind}),
Entry("Version (wrong prefix)", GVK{Group: group, Domain: domain, Version: "a1", Kind: kind}),
Entry("Version (unstable no v prefix)", GVK{Group: group, Domain: domain, Version: "1beta1", Kind: kind}),
Entry("Version (unstable no alpha/beta number)",
GVK{Group: group, Domain: domain, Version: "v1beta", Kind: kind}),
Entry("Version (multiple unstable)",
GVK{Group: group, Domain: domain, Version: "v1beta1alpha1", Kind: kind}),
Entry("Kind (empty)", GVK{Group: group, Domain: domain, Version: version, Kind: ""}),
Entry("Kind (whitespaces)", GVK{Group: group, Domain: domain, Version: version, Kind: "Ki nd"}),
Entry("Kind (lowercase)", GVK{Group: group, Domain: domain, Version: version, Kind: "kind"}),
Entry("Kind (starts with number)", GVK{Group: group, Domain: domain, Version: version, Kind: "1Kind"}),
Entry("Kind (ends with `-`)", GVK{Group: group, Domain: domain, Version: version, Kind: "Kind-"}),
Entry("Kind (non-alpha characters)", GVK{Group: group, Domain: domain, Version: version, Kind: "_*?"}),
Entry("Kind (too long)",
GVK{Group: group, Domain: domain, Version: version, Kind: strings.Repeat("a", 64)}),
)
})

Context("QualifiedGroup", func() {
DescribeTable("should return the correct string",
func(gvk GVK, qualifiedGroup string) { Expect(gvk.QualifiedGroup()).To(Equal(qualifiedGroup)) },
Entry("fully qualified resource", GVK{Group: group, Domain: domain, Version: version, Kind: kind},
group+"."+domain),
Entry("fully qualified resource", gvk, group+"."+domain),
Entry("empty group name", GVK{Domain: domain, Version: version, Kind: kind}, domain),
Entry("empty domain", GVK{Group: group, Version: version, Kind: kind}, group),
)
})

Context("IsEqualTo", func() {
var gvk = GVK{Group: group, Domain: domain, Version: version, Kind: kind}

It("should return true for the same resource", func() {
Expect(gvk.IsEqualTo(GVK{Group: group, Domain: domain, Version: version, Kind: kind})).To(BeTrue())
})
Expand Down
34 changes: 34 additions & 0 deletions pkg/model/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package resource
import (
"fmt"
"strings"

"sigs.k8s.io/kubebuilder/v3/pkg/internal/validation"
)

// Resource contains the information required to scaffold files for a resource.
Expand All @@ -42,6 +44,38 @@ type Resource struct {
Webhooks *Webhooks `json:"webhooks,omitempty"`
}

// Validate checks that the Resource is valid.
func (r Resource) Validate() error {
// Validate the GVK
if err := r.GVK.Validate(); err != nil {
return err
}

// Validate the Plural
// NOTE: IsDNS1035Label returns a slice of strings instead of an error, so no wrapping
if errors := validation.IsDNS1035Label(r.Plural); len(errors) != 0 {
return fmt.Errorf("invalid Plural: %#v", errors)
}

// TODO: validate the path

// Validate the API
if r.API != nil && !r.API.IsEmpty() {
if err := r.API.Validate(); err != nil {
return fmt.Errorf("invalid API: %w", err)
}
}

// Validate the Webhooks
if r.Webhooks != nil && !r.Webhooks.IsEmpty() {
if err := r.Webhooks.Validate(); err != nil {
return fmt.Errorf("invalid Webhooks: %w", err)
}
}

return nil
}

// PackageName returns a name valid to be used por go packages.
func (r Resource) PackageName() string {
if r.Group == "" {
Expand Down
Loading

0 comments on commit 90344af

Please sign in to comment.