Skip to content

Commit

Permalink
Use Resource model in the config file instead of having a separate Re…
Browse files Browse the repository at this point in the history
…sourceData model

Signed-off-by: Adrian Orive <[email protected]>
  • Loading branch information
Adirio committed Dec 4, 2020
1 parent 6d37b49 commit c85aa06
Show file tree
Hide file tree
Showing 36 changed files with 1,019 additions and 776 deletions.
126 changes: 75 additions & 51 deletions pkg/model/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"strings"

"sigs.k8s.io/yaml"

"sigs.k8s.io/kubebuilder/v2/pkg/model/resource"
)

// Scaffolding versions
Expand All @@ -44,8 +46,7 @@ type Config struct {
ProjectName string `json:"projectName,omitempty"`

// Resources tracks scaffolded resources in the project
// This info is tracked only in project with version 2
Resources []GVK `json:"resources,omitempty"`
Resources []*resource.Resource `json:"resources,omitempty"`

// Multigroup tracks if the project has more than one group
MultiGroup bool `json:"multigroup,omitempty"`
Expand Down Expand Up @@ -78,32 +79,33 @@ func (c Config) IsV3() bool {
return c.Version == Version3Alpha
}

// HasResource returns true if API resource is already tracked
func (c Config) HasResource(target GVK) bool {
// Return true if the target resource is found in the tracked resources
// GetResource returns the requested resource if it is already tracked
func (c Config) GetResource(target resource.GVK) *resource.Resource {
// Return the target resource if it is found in the tracked resources
for _, r := range c.Resources {
if r.isEqualTo(target) {
return true
if r.GVK().IsEqualTo(target) {
return r
}
}

// Return false otherwise
return false
// Return nil otherwise
return nil
}

// UpdateResources either adds gvk to the tracked set or, if the resource already exists,
// updates the the equivalent resource in the set.
func (c *Config) UpdateResources(gvk GVK) {
// UpdateResources adds the resource to the tracked ones or updates it as needed
func (c *Config) UpdateResources(res *resource.Resource) *resource.Resource {
gvk := res.GVK()
// If the resource already exists, update it.
for i, r := range c.Resources {
if r.isEqualTo(gvk) {
c.Resources[i].merge(gvk)
return
if r.GVK().IsEqualTo(gvk) {
c.Resources[i].Update(res)
return c.Resources[i]
}
}

// The resource does not exist, append the resource to the tracked ones.
c.Resources = append(c.Resources, gvk)
c.Resources = append(c.Resources, res)
return res
}

// HasGroup returns true if group is already tracked
Expand Down Expand Up @@ -136,9 +138,13 @@ func (c Config) resourceAPIVersionCompatible(verType, version string) bool {
var currVersion string
switch verType {
case "crd":
currVersion = res.CRDVersion
if res.API != nil {
currVersion = res.API.Version
}
case "webhook":
currVersion = res.WebhookVersion
if res.Webhooks != nil {
currVersion = res.Webhooks.Version
}
}
if currVersion != "" && version != currVersion {
return false
Expand All @@ -147,41 +153,44 @@ func (c Config) resourceAPIVersionCompatible(verType, version string) bool {
return true
}

// GVK contains information about scaffolded resources
type GVK struct {
Group string `json:"group,omitempty"`
Version string `json:"version,omitempty"`
Kind string `json:"kind,omitempty"`

// CRDVersion holds the CustomResourceDefinition API version used for the GVK.
CRDVersion string `json:"crdVersion,omitempty"`
// WebhookVersion holds the {Validating,Mutating}WebhookConfiguration API version used for the GVK.
WebhookVersion string `json:"webhookVersion,omitempty"`
}

// isEqualTo compares it with another resource
func (r GVK) isEqualTo(other GVK) bool {
return r.Group == other.Group &&
r.Version == other.Version &&
r.Kind == other.Kind
}

// merge combines fields of two GVKs that have matching group, version, and kind,
// favoring the receiver's values.
func (r *GVK) merge(other GVK) {
if r.CRDVersion == "" && other.CRDVersion != "" {
r.CRDVersion = other.CRDVersion
}
if r.WebhookVersion == "" && other.WebhookVersion != "" {
r.WebhookVersion = other.WebhookVersion
}
}

// Marshal returns the bytes of c.
func (c Config) Marshal() ([]byte, error) {
// Ignore extra fields at first.
cfg := c
cfg.Plugins = nil

// Ignore some fields if v2
if cfg.IsV2() {
for i := range cfg.Resources {
cfg.Resources[i].Domain = ""
cfg.Resources[i].Plural = ""
cfg.Resources[i].Path = ""
cfg.Resources[i].API = nil
cfg.Resources[i].Controller = false
cfg.Resources[i].Webhooks = nil
}
}

// Simplify some fields
for i, r := range cfg.Resources {
// If the plural is regular, omit it
if r.Plural == resource.RegularPlural(r.Kind) {
cfg.Resources[i].Plural = ""
}
// If the path is the default location, omit it
if r.Path == resource.LocalPath(cfg.Repo, r.Group, r.Version, cfg.MultiGroup) {
cfg.Resources[i].Path = ""
}
// If API is empty, omit it (prevents `api: {}`)
if r.API != nil && r.API.IsEmpty() {
cfg.Resources[i].API = nil
}
// If Webhooks is empty, omit it (prevents `webhooks: {}`)
if r.Webhooks != nil && r.Webhooks.IsEmpty() {
cfg.Resources[i].Webhooks = nil
}
}

content, err := yaml.Marshal(cfg)
if err != nil {
return nil, fmt.Errorf("error marshalling project configuration: %v", err)
Expand Down Expand Up @@ -209,16 +218,31 @@ func (c Config) Marshal() ([]byte, error) {
return content, nil
}

// Unmarshal unmarshals the bytes of a Config into c.
// Unmarshal unmarshalls the bytes of a Config into c.
func (c *Config) Unmarshal(b []byte) error {
if err := yaml.UnmarshalStrict(b, c); err != nil {
return fmt.Errorf("error unmarshalling project configuration: %v", err)
}

// Project versions < v3 do not support a plugins field.
if !c.IsV3() {
// Restore some omitted values
for i, r := range c.Resources {
if r.Plural == "" {
c.Resources[i].Plural = resource.RegularPlural(r.Kind)
}
if r.Path == "" {
c.Resources[i].Path = resource.LocalPath(c.Repo, r.Group, r.Version, c.MultiGroup)
}
}

// Project version v2 does not support a plugins field.
if c.IsV2() {
// Only the default domain is allowed
for i := range c.Resources {
c.Resources[i].Domain = c.Domain
}
c.Plugins = nil
}

return nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/model/config/config_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
. "github.com/onsi/gomega"
)

func TestCLI(t *testing.T) {
func TestConfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Config Suite")
}
110 changes: 67 additions & 43 deletions pkg/model/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package config
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"sigs.k8s.io/kubebuilder/v2/pkg/model/resource"
)

const v1beta1 = "v1beta1"
Expand Down Expand Up @@ -169,89 +171,111 @@ var _ = Describe("Resource Version Compatibility", func() {

var (
c *Config
gvk1, gvk2 GVK
res1, res2 *resource.Resource

defaultVersion = "v1"
)

BeforeEach(func() {
c = &Config{}
gvk1 = GVK{Group: "example", Version: "v1", Kind: "TestKind"}
gvk2 = GVK{Group: "example", Version: "v1", Kind: "TestKind2"}
res1 = &resource.Resource{Group: "example", Domain: "test.io", Version: "v1", Kind: "TestKind"}
res2 = &resource.Resource{Group: "example", Domain: "test.io", Version: "v1", Kind: "TestKind2"}
})

Context("resourceAPIVersionCompatible", func() {
It("returns true for a list of empty resources", func() {
Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeTrue())
Expect(c.IsCRDVersionCompatible(defaultVersion)).To(BeTrue())
Expect(c.IsWebhookVersionCompatible(defaultVersion)).To(BeTrue())
})

It("returns true for one resource with an empty version", func() {
c.Resources = []GVK{gvk1}
Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeTrue())
c.Resources = []*resource.Resource{res1}
Expect(c.IsCRDVersionCompatible(defaultVersion)).To(BeTrue())
Expect(c.IsWebhookVersionCompatible(defaultVersion)).To(BeTrue())
})

It("returns true for one resource with matching version", func() {
gvk1.CRDVersion = defaultVersion
c.Resources = []GVK{gvk1}
Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeTrue())
res1.API = &resource.API{Version: defaultVersion}
res1.Webhooks = &resource.Webhooks{Version: defaultVersion}
c.Resources = []*resource.Resource{res1}
Expect(c.IsCRDVersionCompatible(defaultVersion)).To(BeTrue())
Expect(c.IsWebhookVersionCompatible(defaultVersion)).To(BeTrue())
})

It("returns true for two resources with matching versions", func() {
gvk1.CRDVersion = defaultVersion
gvk2.CRDVersion = defaultVersion
c.Resources = []GVK{gvk1, gvk2}
Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeTrue())
res1.API = &resource.API{Version: defaultVersion}
res1.Webhooks = &resource.Webhooks{Version: defaultVersion}
res2.API = &resource.API{Version: defaultVersion}
res2.Webhooks = &resource.Webhooks{Version: defaultVersion}
c.Resources = []*resource.Resource{res1, res2}
Expect(c.IsCRDVersionCompatible(defaultVersion)).To(BeTrue())
Expect(c.IsWebhookVersionCompatible(defaultVersion)).To(BeTrue())
})

It("returns false for one resource with a non-matching version", func() {
gvk1.CRDVersion = v1beta1
c.Resources = []GVK{gvk1}
Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeFalse())
})
It("returns false for two resources containing a non-matching version", func() {
gvk1.CRDVersion = v1beta1
gvk2.CRDVersion = defaultVersion
c.Resources = []GVK{gvk1, gvk2}
Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeFalse())
res1.API = &resource.API{Version: v1beta1}
res1.Webhooks = &resource.Webhooks{Version: v1beta1}
c.Resources = []*resource.Resource{res1}
Expect(c.IsCRDVersionCompatible(defaultVersion)).To(BeFalse())
Expect(c.IsWebhookVersionCompatible(defaultVersion)).To(BeFalse())
})

It("returns false for two resources containing a non-matching version (webhooks)", func() {
gvk1.WebhookVersion = v1beta1
gvk2.WebhookVersion = defaultVersion
c.Resources = []GVK{gvk1, gvk2}
Expect(c.resourceAPIVersionCompatible("webhook", defaultVersion)).To(BeFalse())
It("returns false for two resources containing a non-matching version", func() {
res1.API = &resource.API{Version: v1beta1}
res1.Webhooks = &resource.Webhooks{Version: v1beta1}
res2.API = &resource.API{Version: defaultVersion}
res2.Webhooks = &resource.Webhooks{Version: defaultVersion}
c.Resources = []*resource.Resource{res1, res2}
Expect(c.IsCRDVersionCompatible(defaultVersion)).To(BeFalse())
Expect(c.IsWebhookVersionCompatible(defaultVersion)).To(BeFalse())
})
})
})

var _ = Describe("Config", func() {
var (
c *Config
gvk1, gvk2 GVK
res1, res2 *resource.Resource
)

BeforeEach(func() {
c = &Config{}
gvk1 = GVK{Group: "example", Version: "v1", Kind: "TestKind"}
gvk2 = GVK{Group: "example", Version: "v1", Kind: "TestKind2"}
res1 = &resource.Resource{Group: "example", Domain: "test.io", Version: "v1", Kind: "TestKind"}
res2 = &resource.Resource{Group: "example", Domain: "test.io", Version: "v1", Kind: "TestKind2"}
})

Context("UpdateResource", func() {
It("Adds a non-existing resource", func() {
c.UpdateResources(gvk1)
Expect(c.Resources).To(Equal([]GVK{gvk1}))
c.UpdateResources(res1)
Expect(c.Resources).To(Equal([]*resource.Resource{res1}))
// Update again to ensure idempotency.
c.UpdateResources(gvk1)
Expect(c.Resources).To(Equal([]GVK{gvk1}))
c.UpdateResources(res1)
Expect(c.Resources).To(Equal([]*resource.Resource{res1}))
})
It("Updates an existing resource", func() {
c.UpdateResources(gvk1)
gvk := GVK{Group: gvk1.Group, Version: gvk1.Version, Kind: gvk1.Kind, CRDVersion: "v1"}
c.UpdateResources(gvk)
Expect(c.Resources).To(Equal([]GVK{gvk}))
c.UpdateResources(res1)
res := &resource.Resource{
Group: res1.Group,
Domain: res1.Domain,
Version: res1.Version,
Kind: res1.Kind,
API: &resource.API{Version: "v1"},
}
c.UpdateResources(res)
Expect(c.Resources).To(Equal([]*resource.Resource{res}))
})
It("Updates an existing resource with more than one resource present", func() {
c.UpdateResources(gvk1)
c.UpdateResources(gvk2)
gvk := GVK{Group: gvk1.Group, Version: gvk1.Version, Kind: gvk1.Kind, CRDVersion: "v1"}
c.UpdateResources(gvk)
Expect(c.Resources).To(Equal([]GVK{gvk, gvk2}))
c.UpdateResources(res1)
c.UpdateResources(res2)
res := &resource.Resource{
Group: res1.Group,
Domain: res1.Domain,
Version: res1.Version,
Kind: res1.Kind,
API: &resource.API{Version: "v1"},
}
c.UpdateResources(res)
Expect(c.Resources).To(Equal([]*resource.Resource{res, res2}))
})
})
})
29 changes: 29 additions & 0 deletions pkg/model/model_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package model

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestModel(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Model Suite")
}
Loading

0 comments on commit c85aa06

Please sign in to comment.