sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/model/resource/resource_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package resource
    18  
    19  import (
    20  	. "github.com/onsi/ginkgo/v2"
    21  	. "github.com/onsi/gomega"
    22  )
    23  
    24  //nolint:dupl
    25  var _ = Describe("Resource", func() {
    26  	const (
    27  		group   = "group"
    28  		domain  = "test.io"
    29  		version = "v1"
    30  		kind    = "Kind"
    31  		plural  = "kinds"
    32  		v1beta1 = "v1beta1"
    33  	)
    34  
    35  	var (
    36  		gvk = GVK{
    37  			Group:   group,
    38  			Domain:  domain,
    39  			Version: version,
    40  			Kind:    kind,
    41  		}
    42  		res = Resource{
    43  			GVK:    gvk,
    44  			Plural: plural,
    45  		}
    46  	)
    47  
    48  	Context("Validate", func() {
    49  		It("should succeed for a valid Resource", func() {
    50  			Expect(res.Validate()).To(Succeed())
    51  		})
    52  
    53  		DescribeTable("should fail for invalid Resources",
    54  			func(res Resource) { Expect(res.Validate()).NotTo(Succeed()) },
    55  			// Ensure that the rest of the fields are valid to check each part
    56  			Entry("invalid GVK", Resource{GVK: GVK{}, Plural: "plural"}),
    57  			Entry("invalid Plural", Resource{GVK: gvk, Plural: "Plural"}),
    58  			Entry("invalid API", Resource{GVK: gvk, Plural: "plural", API: &API{CRDVersion: "1"}}),
    59  			Entry("invalid Webhooks", Resource{GVK: gvk, Plural: "plural", Webhooks: &Webhooks{WebhookVersion: "1"}}),
    60  		)
    61  	})
    62  
    63  	Context("compound field", func() {
    64  		const (
    65  			safeDomain    = "testio"
    66  			groupVersion  = group + version
    67  			domainVersion = safeDomain + version
    68  			safeGroup     = "mygroup"
    69  			safeAlias     = safeGroup + version
    70  		)
    71  
    72  		var (
    73  			resNoGroup = Resource{
    74  				GVK: GVK{
    75  					// Empty group
    76  					Domain:  domain,
    77  					Version: version,
    78  					Kind:    kind,
    79  				},
    80  			}
    81  			resNoDomain = Resource{
    82  				GVK: GVK{
    83  					Group: group,
    84  					// Empty domain
    85  					Version: version,
    86  					Kind:    kind,
    87  				},
    88  			}
    89  			resHyphenGroup = Resource{
    90  				GVK: GVK{
    91  					Group:   "my-group",
    92  					Domain:  domain,
    93  					Version: version,
    94  					Kind:    kind,
    95  				},
    96  			}
    97  			resDotGroup = Resource{
    98  				GVK: GVK{
    99  					Group:   "my.group",
   100  					Domain:  domain,
   101  					Version: version,
   102  					Kind:    kind,
   103  				},
   104  			}
   105  		)
   106  
   107  		DescribeTable("PackageName should return the correct string",
   108  			func(res Resource, packageName string) { Expect(res.PackageName()).To(Equal(packageName)) },
   109  			Entry("fully qualified resource", res, group),
   110  			Entry("empty group name", resNoGroup, safeDomain),
   111  			Entry("empty domain", resNoDomain, group),
   112  			Entry("hyphen-containing group", resHyphenGroup, safeGroup),
   113  			Entry("dot-containing group", resDotGroup, safeGroup),
   114  		)
   115  
   116  		DescribeTable("ImportAlias",
   117  			func(res Resource, importAlias string) { Expect(res.ImportAlias()).To(Equal(importAlias)) },
   118  			Entry("fully qualified resource", res, groupVersion),
   119  			Entry("empty group name", resNoGroup, domainVersion),
   120  			Entry("empty domain", resNoDomain, groupVersion),
   121  			Entry("hyphen-containing group", resHyphenGroup, safeAlias),
   122  			Entry("dot-containing group", resDotGroup, safeAlias),
   123  		)
   124  	})
   125  
   126  	Context("part check", func() {
   127  		Context("HasAPI", func() {
   128  			It("should return true if the API is scaffolded", func() {
   129  				Expect(Resource{API: &API{CRDVersion: "v1"}}.HasAPI()).To(BeTrue())
   130  			})
   131  
   132  			DescribeTable("should return false if the API is not scaffolded",
   133  				func(res Resource) { Expect(res.HasAPI()).To(BeFalse()) },
   134  				Entry("nil API", Resource{API: nil}),
   135  				Entry("empty CRD version", Resource{API: &API{}}),
   136  			)
   137  		})
   138  
   139  		Context("HasController", func() {
   140  			It("should return true if the controller is scaffolded", func() {
   141  				Expect(Resource{Controller: true}.HasController()).To(BeTrue())
   142  			})
   143  
   144  			It("should return false if the controller is not scaffolded", func() {
   145  				Expect(Resource{Controller: false}.HasController()).To(BeFalse())
   146  			})
   147  		})
   148  
   149  		Context("HasDefaultingWebhook", func() {
   150  			It("should return true if the defaulting webhook is scaffolded", func() {
   151  				Expect(Resource{Webhooks: &Webhooks{Defaulting: true}}.HasDefaultingWebhook()).To(BeTrue())
   152  			})
   153  
   154  			DescribeTable("should return false if the defaulting webhook is not scaffolded",
   155  				func(res Resource) { Expect(res.HasDefaultingWebhook()).To(BeFalse()) },
   156  				Entry("nil webhooks", Resource{Webhooks: nil}),
   157  				Entry("no defaulting", Resource{Webhooks: &Webhooks{Defaulting: false}}),
   158  			)
   159  		})
   160  
   161  		Context("HasValidationWebhook", func() {
   162  			It("should return true if the validation webhook is scaffolded", func() {
   163  				Expect(Resource{Webhooks: &Webhooks{Validation: true}}.HasValidationWebhook()).To(BeTrue())
   164  			})
   165  
   166  			DescribeTable("should return false if the validation webhook is not scaffolded",
   167  				func(res Resource) { Expect(res.HasValidationWebhook()).To(BeFalse()) },
   168  				Entry("nil webhooks", Resource{Webhooks: nil}),
   169  				Entry("no validation", Resource{Webhooks: &Webhooks{Validation: false}}),
   170  			)
   171  		})
   172  
   173  		Context("HasConversionWebhook", func() {
   174  			It("should return true if the conversion webhook is scaffolded", func() {
   175  				Expect(Resource{Webhooks: &Webhooks{Conversion: true}}.HasConversionWebhook()).To(BeTrue())
   176  			})
   177  
   178  			DescribeTable("should return false if the conversion webhook is not scaffolded",
   179  				func(res Resource) { Expect(res.HasConversionWebhook()).To(BeFalse()) },
   180  				Entry("nil webhooks", Resource{Webhooks: nil}),
   181  				Entry("no conversion", Resource{Webhooks: &Webhooks{Conversion: false}}),
   182  			)
   183  		})
   184  
   185  		Context("IsRegularPlural", func() {
   186  			It("should return true if the regular plural form is used", func() {
   187  				Expect(res.IsRegularPlural()).To(BeTrue())
   188  			})
   189  
   190  			It("should return false if an irregular plural form is used", func() {
   191  				Expect(Resource{GVK: gvk, Plural: "types"}.IsRegularPlural()).To(BeFalse())
   192  			})
   193  		})
   194  	})
   195  
   196  	Context("Copy", func() {
   197  		const (
   198  			path           = "api/v1"
   199  			crdVersion     = "v1"
   200  			webhookVersion = "v1"
   201  		)
   202  
   203  		res := Resource{
   204  			GVK:    gvk,
   205  			Plural: plural,
   206  			Path:   path,
   207  			API: &API{
   208  				CRDVersion: crdVersion,
   209  				Namespaced: true,
   210  			},
   211  			Controller: true,
   212  			Webhooks: &Webhooks{
   213  				WebhookVersion: webhookVersion,
   214  				Defaulting:     true,
   215  				Validation:     true,
   216  				Conversion:     true,
   217  			},
   218  		}
   219  
   220  		It("should return an exact copy", func() {
   221  			other := res.Copy()
   222  			Expect(other.Group).To(Equal(res.Group))
   223  			Expect(other.Domain).To(Equal(res.Domain))
   224  			Expect(other.Version).To(Equal(res.Version))
   225  			Expect(other.Kind).To(Equal(res.Kind))
   226  			Expect(other.Plural).To(Equal(res.Plural))
   227  			Expect(other.Path).To(Equal(res.Path))
   228  			Expect(other.API).NotTo(BeNil())
   229  			Expect(other.API.CRDVersion).To(Equal(res.API.CRDVersion))
   230  			Expect(other.API.Namespaced).To(Equal(res.API.Namespaced))
   231  			Expect(other.Controller).To(Equal(res.Controller))
   232  			Expect(other.Webhooks).NotTo(BeNil())
   233  			Expect(other.Webhooks.WebhookVersion).To(Equal(res.Webhooks.WebhookVersion))
   234  			Expect(other.Webhooks.Defaulting).To(Equal(res.Webhooks.Defaulting))
   235  			Expect(other.Webhooks.Validation).To(Equal(res.Webhooks.Validation))
   236  			Expect(other.Webhooks.Conversion).To(Equal(res.Webhooks.Conversion))
   237  		})
   238  
   239  		It("modifying the copy should not affect the original", func() {
   240  			other := res.Copy()
   241  			other.Group = "group2"
   242  			other.Domain = "other.domain"
   243  			other.Version = "v2"
   244  			other.Kind = "kind2"
   245  			other.Plural = "kind2s"
   246  			other.Path = "api/v2"
   247  			other.API.CRDVersion = v1beta1
   248  			other.API.Namespaced = false
   249  			other.API = nil // Change fields before changing pointer
   250  			other.Controller = false
   251  			other.Webhooks.WebhookVersion = v1beta1
   252  			other.Webhooks.Defaulting = false
   253  			other.Webhooks.Validation = false
   254  			other.Webhooks.Conversion = false
   255  			other.Webhooks = nil // Change fields before changing pointer
   256  
   257  			Expect(res.Group).To(Equal(group))
   258  			Expect(res.Domain).To(Equal(domain))
   259  			Expect(res.Version).To(Equal(version))
   260  			Expect(res.Kind).To(Equal(kind))
   261  			Expect(res.Plural).To(Equal(plural))
   262  			Expect(res.Path).To(Equal(path))
   263  			Expect(res.API).NotTo(BeNil())
   264  			Expect(res.API.CRDVersion).To(Equal(crdVersion))
   265  			Expect(res.API.Namespaced).To(BeTrue())
   266  			Expect(res.Controller).To(BeTrue())
   267  			Expect(res.Webhooks).NotTo(BeNil())
   268  			Expect(res.Webhooks.WebhookVersion).To(Equal(webhookVersion))
   269  			Expect(res.Webhooks.Defaulting).To(BeTrue())
   270  			Expect(res.Webhooks.Validation).To(BeTrue())
   271  			Expect(res.Webhooks.Conversion).To(BeTrue())
   272  		})
   273  	})
   274  
   275  	Context("Update", func() {
   276  		var r, other Resource
   277  
   278  		It("should fail for nil objects", func() {
   279  			var nilResource *Resource
   280  			Expect(nilResource.Update(other)).NotTo(Succeed())
   281  		})
   282  
   283  		It("should fail for different GVKs", func() {
   284  			r = Resource{GVK: gvk}
   285  			other = Resource{
   286  				GVK: GVK{
   287  					Group:   group,
   288  					Domain:  domain,
   289  					Version: version,
   290  					Kind:    "OtherKind",
   291  				},
   292  			}
   293  			Expect(r.Update(other)).NotTo(Succeed())
   294  		})
   295  
   296  		It("should fail for different Plurals", func() {
   297  			r = Resource{
   298  				GVK:    gvk,
   299  				Plural: plural,
   300  			}
   301  			other = Resource{
   302  				GVK:    gvk,
   303  				Plural: "types",
   304  			}
   305  			Expect(r.Update(other)).NotTo(Succeed())
   306  		})
   307  
   308  		It("should work for a new path", func() {
   309  			const path = "api/v1"
   310  			r = Resource{GVK: gvk}
   311  			other = Resource{
   312  				GVK:  gvk,
   313  				Path: path,
   314  			}
   315  			Expect(r.Update(other)).To(Succeed())
   316  			Expect(r.Path).To(Equal(path))
   317  		})
   318  
   319  		It("should fail for different Paths", func() {
   320  			r = Resource{
   321  				GVK:  gvk,
   322  				Path: "api/v1",
   323  			}
   324  			other = Resource{
   325  				GVK:  gvk,
   326  				Path: "apis/group/v1",
   327  			}
   328  			Expect(r.Update(other)).NotTo(Succeed())
   329  		})
   330  
   331  		Context("API", func() {
   332  			It("should work with nil APIs", func() {
   333  				r = Resource{GVK: gvk}
   334  				other = Resource{
   335  					GVK: gvk,
   336  					API: &API{CRDVersion: v1},
   337  				}
   338  				Expect(r.Update(other)).To(Succeed())
   339  				Expect(r.API).NotTo(BeNil())
   340  				Expect(r.API.CRDVersion).To(Equal(v1))
   341  			})
   342  
   343  			It("should fail if API.Update fails", func() {
   344  				r = Resource{
   345  					GVK: gvk,
   346  					API: &API{CRDVersion: v1},
   347  				}
   348  				other = Resource{
   349  					GVK: gvk,
   350  					API: &API{CRDVersion: v1beta1},
   351  				}
   352  				Expect(r.Update(other)).NotTo(Succeed())
   353  			})
   354  
   355  			// The rest of the cases are tested in API.Update
   356  		})
   357  
   358  		Context("Controller", func() {
   359  			It("should set the controller flag if provided and not previously set", func() {
   360  				r = Resource{GVK: gvk}
   361  				other = Resource{
   362  					GVK:        gvk,
   363  					Controller: true,
   364  				}
   365  				Expect(r.Update(other)).To(Succeed())
   366  				Expect(r.Controller).To(BeTrue())
   367  			})
   368  
   369  			It("should keep the controller flag if previously set", func() {
   370  				r = Resource{
   371  					GVK:        gvk,
   372  					Controller: true,
   373  				}
   374  
   375  				By("not providing it")
   376  				other = Resource{GVK: gvk}
   377  				Expect(r.Update(other)).To(Succeed())
   378  				Expect(r.Controller).To(BeTrue())
   379  
   380  				By("providing it")
   381  				other = Resource{
   382  					GVK:        gvk,
   383  					Controller: true,
   384  				}
   385  				Expect(r.Update(other)).To(Succeed())
   386  				Expect(r.Controller).To(BeTrue())
   387  			})
   388  
   389  			It("should not set the controller flag if not provided and not previously set", func() {
   390  				r = Resource{GVK: gvk}
   391  				other = Resource{GVK: gvk}
   392  				Expect(r.Update(other)).To(Succeed())
   393  				Expect(r.Controller).To(BeFalse())
   394  			})
   395  		})
   396  
   397  		Context("Webhooks", func() {
   398  			It("should work with nil Webhooks", func() {
   399  				r = Resource{GVK: gvk}
   400  				other = Resource{
   401  					GVK:      gvk,
   402  					Webhooks: &Webhooks{WebhookVersion: v1},
   403  				}
   404  				Expect(r.Update(other)).To(Succeed())
   405  				Expect(r.Webhooks).NotTo(BeNil())
   406  				Expect(r.Webhooks.WebhookVersion).To(Equal(v1))
   407  			})
   408  
   409  			It("should fail if Webhooks.Update fails", func() {
   410  				r = Resource{
   411  					GVK:      gvk,
   412  					Webhooks: &Webhooks{WebhookVersion: v1},
   413  				}
   414  				other = Resource{
   415  					GVK:      gvk,
   416  					Webhooks: &Webhooks{WebhookVersion: v1beta1},
   417  				}
   418  				Expect(r.Update(other)).NotTo(Succeed())
   419  			})
   420  
   421  			// The rest of the cases are tested in Webhooks.Update
   422  		})
   423  	})
   424  
   425  	Context("Replacer", func() {
   426  		replacer := res.Replacer()
   427  
   428  		DescribeTable("should replace the following strings",
   429  			func(pattern, result string) { Expect(replacer.Replace(pattern)).To(Equal(result)) },
   430  			Entry("no pattern", "version", "version"),
   431  			Entry("pattern `%[group]`", "%[group]", res.Group),
   432  			Entry("pattern `%[version]`", "%[version]", res.Version),
   433  			Entry("pattern `%[kind]`", "%[kind]", "kind"),
   434  			Entry("pattern `%[plural]`", "%[plural]", res.Plural),
   435  			Entry("pattern `%[package-name]`", "%[package-name]", res.PackageName()),
   436  		)
   437  	})
   438  })