diff --git a/generate_testdata.sh b/generate_testdata.sh index f66b16df5d9..692d7b05528 100755 --- a/generate_testdata.sh +++ b/generate_testdata.sh @@ -98,7 +98,7 @@ scaffold_test_project() { $kb create api --group foo.policy --version v1 --kind HealthCheckPolicy --controller=true --resource=true --make=false - $kb create api --group apps --version v1 --kind Pod --controller=true --resource=false --make=false + $kb create api --group apps --version v1 --kind Deployment --controller=true --resource=false --make=false if [ $project == "project-v3-multigroup" ]; then $kb create api --version v1 --kind Lakers --controller=true --resource=true --make=false diff --git a/pkg/model/file/funcmap.go b/pkg/model/file/funcmap.go index 95d921c189a..a0a9432427b 100644 --- a/pkg/model/file/funcmap.go +++ b/pkg/model/file/funcmap.go @@ -26,12 +26,18 @@ import ( // DefaultFuncMap returns the default template.FuncMap for rendering the template. func DefaultFuncMap() template.FuncMap { return template.FuncMap{ - "title": strings.Title, - "lower": strings.ToLower, - "hashFNV": hashFNV, + "title": strings.Title, + "lower": strings.ToLower, + "isEmptyStr": isEmptyString, + "hashFNV": hashFNV, } } +// isEmptyString returns whether the string is empty +func isEmptyString(s string) bool { + return s == "" +} + // hashFNV will generate a random string useful for generating a unique string func hashFNV(s string) (string, error) { hasher := fnv.New32a() diff --git a/pkg/model/resource/resource.go b/pkg/model/resource/resource.go index 869b4a7e4d0..f5a36ba567c 100644 --- a/pkg/model/resource/resource.go +++ b/pkg/model/resource/resource.go @@ -155,8 +155,12 @@ func (r *Resource) Update(other Resource) error { return fmt.Errorf("unable to update Resource with another with non-matching Plural") } - if r.Path != other.Path { - return fmt.Errorf("unable to update Resource with another with non-matching Path") + if other.Path != "" && r.Path != other.Path { + if r.Path == "" { + r.Path = other.Path + } else { + return fmt.Errorf("unable to update Resource with another with non-matching Path") + } } // Update API. diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index ab2a2e0c96b..892217da7ca 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -137,7 +137,6 @@ func (opts Options) GVK() resource.GVK { func (opts Options) NewResource(c newconfig.Config) resource.Resource { res := resource.Resource{ GVK: opts.GVK(), - Path: resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()), Controller: opts.DoController, } @@ -149,6 +148,7 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { } if opts.DoAPI { + res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) res.API = &resource.API{ CRDVersion: opts.CRDVersion, Namespaced: opts.Namespaced, @@ -159,6 +159,7 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { } if opts.DoDefaulting || opts.DoValidation || opts.DoConversion { + res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) res.Webhooks = &resource.Webhooks{ WebhookVersion: opts.WebhookVersion, Defaulting: opts.DoDefaulting, @@ -177,7 +178,9 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { // - In any other case, default to => project resource // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath if !opts.DoAPI { - if !c.HasResource(opts.GVK()) { + loadedRes, err := c.GetResource(opts.GVK()) + alreadyHasAPI := err == nil && loadedRes.HasAPI() + if !alreadyHasAPI { if domain, found := coreGroups[opts.Group]; found { res.Domain = domain res.Path = path.Join("k8s.io", "api", opts.Group, opts.Version) diff --git a/pkg/plugins/golang/options_test.go b/pkg/plugins/golang/options_test.go index 983313fb111..8faa101d975 100644 --- a/pkg/plugins/golang/options_test.go +++ b/pkg/plugins/golang/options_test.go @@ -62,6 +62,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -70,19 +72,36 @@ var _ = Describe("Options", func() { Expect(resource.Domain).To(Equal(options.Domain)) Expect(resource.Version).To(Equal(options.Version)) Expect(resource.Kind).To(Equal(options.Kind)) - if multiGroup { - Expect(resource.Path).To(Equal( - path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + Expect(resource.API).NotTo(BeNil()) + if options.DoAPI || options.DoDefaulting || options.DoValidation || options.DoConversion { + if multiGroup { + Expect(resource.Path).To(Equal( + path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + } else { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + } } else { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + // Core-resources have a path despite not having an API/Webhook but they are not tested here + Expect(resource.Path).To(Equal("")) + } + if options.DoAPI { + Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) + Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) + Expect(resource.API.IsEmpty()).To(BeFalse()) + } else { + Expect(resource.API.IsEmpty()).To(BeTrue()) } - Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) - Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) Expect(resource.Controller).To(Equal(options.DoController)) - Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) - Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) - Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) - Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.Webhooks).NotTo(BeNil()) + if options.DoDefaulting || options.DoValidation || options.DoConversion { + Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) + Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) + Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) + Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.Webhooks.IsEmpty()).To(BeFalse()) + } else { + Expect(resource.Webhooks.IsEmpty()).To(BeTrue()) + } Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) Expect(resource.PackageName()).To(Equal(options.Group)) Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) @@ -130,6 +149,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -150,6 +171,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -168,12 +191,15 @@ var _ = Describe("Options", func() { Domain: "test.io", Version: "v1", Kind: "FirstMate", + DoAPI: true, // Scaffold the API so that the path is saved } Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -201,6 +227,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -222,12 +250,15 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) Expect(resource.Validate()).To(Succeed()) Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.API.CRDVersion).To(Equal("")) + Expect(resource.API).NotTo(BeNil()) + Expect(resource.API.IsEmpty()).To(BeTrue()) Expect(resource.QualifiedGroup()).To(Equal(qualified)) } }, @@ -242,12 +273,15 @@ var _ = Describe("Options", func() { Domain: "test.io", Version: "v1", Kind: "FirstMate", + DoAPI: true, // Scaffold the API so that the path is saved } Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) diff --git a/pkg/plugins/golang/v2/api.go b/pkg/plugins/golang/v2/api.go index 0c224a07d4a..bdca3497512 100644 --- a/pkg/plugins/golang/v2/api.go +++ b/pkg/plugins/golang/v2/api.go @@ -154,9 +154,9 @@ func (p *createAPISubcommand) Validate() error { } // In case we want to scaffold a resource API we need to do some checks - if p.resource.HasAPI() { - // Check that resource doesn't exist or flag force was set - if !p.force && p.config.HasResource(p.resource.GVK) { + if p.options.DoAPI { + // Check that resource doesn't have the API scaffolded or flag force was set + if res, err := p.config.GetResource(p.resource.GVK); err == nil && res.HasAPI() && !p.force { return errors.New("API resource already exists") } diff --git a/pkg/plugins/golang/v2/options.go b/pkg/plugins/golang/v2/options.go index 4b3613fe295..52f4410b838 100644 --- a/pkg/plugins/golang/v2/options.go +++ b/pkg/plugins/golang/v2/options.go @@ -22,6 +22,7 @@ import ( "strings" newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) @@ -137,7 +138,6 @@ func (opts Options) GVK() resource.GVK { func (opts Options) NewResource(c newconfig.Config) resource.Resource { res := resource.Resource{ GVK: opts.GVK(), - Path: resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()), Controller: opts.DoController, } @@ -149,6 +149,7 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { } if opts.DoAPI { + res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) res.API = &resource.API{ CRDVersion: opts.CRDVersion, Namespaced: opts.Namespaced, @@ -159,6 +160,7 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { } if opts.DoDefaulting || opts.DoValidation || opts.DoConversion { + res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) res.Webhooks = &resource.Webhooks{ WebhookVersion: opts.WebhookVersion, Defaulting: opts.DoDefaulting, @@ -177,7 +179,14 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { // - In any other case, default to => project resource // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath if !opts.DoAPI { - if !c.HasResource(opts.GVK()) { + var alreadyHasAPI bool + if c.GetVersion().Compare(cfgv2.Version) == 0 { + alreadyHasAPI = c.HasResource(opts.GVK()) + } else { + loadedRes, err := c.GetResource(opts.GVK()) + alreadyHasAPI = err == nil && loadedRes.HasAPI() + } + if !alreadyHasAPI { if domain, found := coreGroups[opts.Group]; found { res.Domain = domain res.Path = path.Join("k8s.io", "api", opts.Group, opts.Version) diff --git a/pkg/plugins/golang/v2/options_test.go b/pkg/plugins/golang/v2/options_test.go index e4997e684a7..692a262e5ca 100644 --- a/pkg/plugins/golang/v2/options_test.go +++ b/pkg/plugins/golang/v2/options_test.go @@ -61,6 +61,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -69,19 +71,36 @@ var _ = Describe("Options", func() { Expect(resource.Domain).To(Equal(options.Domain)) Expect(resource.Version).To(Equal(options.Version)) Expect(resource.Kind).To(Equal(options.Kind)) - if multiGroup { - Expect(resource.Path).To(Equal( - path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + Expect(resource.API).NotTo(BeNil()) + if options.DoAPI || options.DoDefaulting || options.DoValidation || options.DoConversion { + if multiGroup { + Expect(resource.Path).To(Equal( + path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + } else { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + } } else { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + // Core-resources have a path despite not having an API/Webhook but they are not tested here + Expect(resource.Path).To(Equal("")) + } + if options.DoAPI { + Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) + Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) + Expect(resource.API.IsEmpty()).To(BeFalse()) + } else { + Expect(resource.API.IsEmpty()).To(BeTrue()) } - Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) - Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) Expect(resource.Controller).To(Equal(options.DoController)) - Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) - Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) - Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) - Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.Webhooks).NotTo(BeNil()) + if options.DoDefaulting || options.DoValidation || options.DoConversion { + Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) + Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) + Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) + Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.Webhooks.IsEmpty()).To(BeFalse()) + } else { + Expect(resource.Webhooks.IsEmpty()).To(BeTrue()) + } Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) Expect(resource.PackageName()).To(Equal(options.Group)) Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) @@ -129,6 +148,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -149,6 +170,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -167,12 +190,15 @@ var _ = Describe("Options", func() { Domain: "test.io", Version: "v1", Kind: "FirstMate", + DoAPI: true, // Scaffold the API so that the path is saved } Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -200,6 +226,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -221,12 +249,15 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) Expect(resource.Validate()).To(Succeed()) Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.API.CRDVersion).To(Equal("")) + Expect(resource.API).NotTo(BeNil()) + Expect(resource.API.IsEmpty()).To(BeTrue()) Expect(resource.QualifiedGroup()).To(Equal(qualified)) } }, diff --git a/pkg/plugins/golang/v2/scaffolds/api.go b/pkg/plugins/golang/v2/scaffolds/api.go index e4abc4d9ae6..353aac0e5f0 100644 --- a/pkg/plugins/golang/v2/scaffolds/api.go +++ b/pkg/plugins/golang/v2/scaffolds/api.go @@ -20,6 +20,7 @@ import ( "fmt" "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/model" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" @@ -89,11 +90,19 @@ func (s *apiScaffolder) scaffold() error { doAPI := s.resource.HasAPI() doController := s.resource.HasController() - if doAPI { - + // Project version v2 only tracked GVK triplets of each resource. + // As they were only tracked when the API was scaffolded, the presence of a + // resource in the config file was used in webhook creation to verify that + // the API had been scaffolded previously. From project version v3 onwards + // this information is stored in the API field of the resource, so we can + // update the resources except for project version 2 when no API was scaffolded. + if doAPI || s.config.GetVersion().Compare(cfgv2.Version) == 1 { if err := s.config.UpdateResource(s.resource); err != nil { return fmt.Errorf("error updating resource: %w", err) } + } + + if doAPI { if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go index b338e3b151f..ee059001455 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go @@ -72,7 +72,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - {{ if .Resource.HasAPI -}} + {{ if not (isEmptyStr .Resource.Path) -}} {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" {{- end }} ) @@ -108,7 +108,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(req ctrl.Request) (ctrl.Resul // SetupWithManager sets up the controller with the Manager. func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - {{ if .Resource.HasAPI -}} + {{ if not (isEmptyStr .Resource.Path) -}} For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}). {{- else -}} // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go index 34e1b418aa9..61bd1d584d7 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -98,13 +98,13 @@ func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { // Generate import code fragments imports := make([]string, 0) - if f.Resource.HasAPI() { + if f.Resource.Path != "" { imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } // Generate add scheme code fragments addScheme := make([]string, 0) - if f.Resource.HasAPI() { + if f.Resource.Path != "" { addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) } diff --git a/pkg/plugins/golang/v2/webhook.go b/pkg/plugins/golang/v2/webhook.go index 2c4b2f8bb7e..735c48c68df 100644 --- a/pkg/plugins/golang/v2/webhook.go +++ b/pkg/plugins/golang/v2/webhook.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/pflag" newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" @@ -104,8 +105,16 @@ func (p *createWebhookSubcommand) Validate() error { } // check if resource exist to create webhook - if !p.config.HasResource(p.resource.GVK) { - return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) + if p.config.GetVersion().Compare(cfgv2.Version) == 0 { + if !p.config.HasResource(p.resource.GVK) { + return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) + } + } else { + if r, err := p.config.GetResource(p.resource.GVK); err != nil { + return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) + } else if r.Webhooks != nil && !r.Webhooks.IsEmpty() { + return fmt.Errorf("webhook resource already exists") + } } return nil diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index 98a6462e40b..f71bc7e5e4f 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -176,8 +176,8 @@ func (p *createAPISubcommand) Validate() error { } // In case we want to scaffold a resource API we need to do some checks - if p.resource.HasAPI() { - // Check that resource doesn't exist or flag force was set + if p.options.DoAPI { + // Check that resource doesn't have the API scaffolded or flag force was set if res, err := p.config.GetResource(p.resource.GVK); err == nil && res.HasAPI() && !p.force { return errors.New("API resource already exists") } diff --git a/pkg/plugins/golang/v3/scaffolds/api.go b/pkg/plugins/golang/v3/scaffolds/api.go index 0265d705b60..1d9698cafc3 100644 --- a/pkg/plugins/golang/v3/scaffolds/api.go +++ b/pkg/plugins/golang/v3/scaffolds/api.go @@ -86,11 +86,11 @@ func (s *apiScaffolder) scaffold() error { doAPI := s.resource.HasAPI() doController := s.resource.HasController() - if doAPI { + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } - if err := s.config.UpdateResource(s.resource); err != nil { - return fmt.Errorf("error updating resource: %w", err) - } + if doAPI { if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go index a70eed4714d..779edb04b45 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go @@ -72,7 +72,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - {{ if .Resource.HasAPI -}} + {{ if not (isEmptyStr .Resource.Path) -}} {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" {{- end }} ) @@ -108,7 +108,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl // SetupWithManager sets up the controller with the Manager. func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - {{ if .Resource.HasAPI -}} + {{ if not (isEmptyStr .Resource.Path) -}} For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}). {{- else -}} // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go index d8159a93c3e..d6e6ac95af5 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -98,13 +98,13 @@ func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { // Generate import code fragments imports := make([]string, 0) - if f.Resource.HasAPI() { + if f.Resource.Path != "" { imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } // Generate add scheme code fragments addScheme := make([]string, 0) - if f.Resource.HasAPI() { + if f.Resource.Path != "" { addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) } diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go index 0139225b878..7c81b017fae 100644 --- a/pkg/plugins/golang/v3/webhook.go +++ b/pkg/plugins/golang/v3/webhook.go @@ -17,7 +17,6 @@ limitations under the License. package v3 import ( - "errors" "fmt" "io/ioutil" "path/filepath" @@ -119,7 +118,7 @@ func (p *createWebhookSubcommand) Validate() error { if r, err := p.config.GetResource(p.resource.GVK); err != nil { return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) } else if r.Webhooks != nil && !r.Webhooks.IsEmpty() && !p.force { - return errors.New("webhook resource already exists") + return fmt.Errorf("webhook resource already exists") } if !p.config.IsWebhookVersionCompatible(p.resource.Webhooks.WebhookVersion) { diff --git a/testdata/project-v2-multigroup/config/rbac/role.yaml b/testdata/project-v2-multigroup/config/rbac/role.yaml index aef6d389cee..7ae49be3caa 100644 --- a/testdata/project-v2-multigroup/config/rbac/role.yaml +++ b/testdata/project-v2-multigroup/config/rbac/role.yaml @@ -9,7 +9,7 @@ rules: - apiGroups: - apps resources: - - pods + - deployments verbs: - create - delete @@ -21,7 +21,7 @@ rules: - apiGroups: - apps resources: - - pods/status + - deployments/status verbs: - get - patch diff --git a/testdata/project-v2-multigroup/controllers/apps/pod_controller.go b/testdata/project-v2-multigroup/controllers/apps/deployment_controller.go similarity index 70% rename from testdata/project-v2-multigroup/controllers/apps/pod_controller.go rename to testdata/project-v2-multigroup/controllers/apps/deployment_controller.go index 7cd866d048e..cf176817191 100644 --- a/testdata/project-v2-multigroup/controllers/apps/pod_controller.go +++ b/testdata/project-v2-multigroup/controllers/apps/deployment_controller.go @@ -20,33 +20,34 @@ import ( "context" "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) -// PodReconciler reconciles a Pod object -type PodReconciler struct { +// DeploymentReconciler reconciles a Deployment object +type DeploymentReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme } -//+kubebuilder:rbac:groups=apps,resources=pods,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=apps,resources=pods/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by -// the Pod object against the actual cluster state, and then +// the Deployment object against the actual cluster state, and then // perform operations to make the cluster state reflect the state specified by // the user. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile -func (r *PodReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { +func (r *DeploymentReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() - _ = r.Log.WithValues("pod", req.NamespacedName) + _ = r.Log.WithValues("deployment", req.NamespacedName) // your logic here @@ -54,9 +55,8 @@ func (r *PodReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } // SetupWithManager sets up the controller with the Manager. -func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument - // For(). + For(&appsv1.Deployment{}). Complete(r) } diff --git a/testdata/project-v2-multigroup/controllers/apps/suite_test.go b/testdata/project-v2-multigroup/controllers/apps/suite_test.go index d4431b5013f..9c3d92e900b 100644 --- a/testdata/project-v2-multigroup/controllers/apps/suite_test.go +++ b/testdata/project-v2-multigroup/controllers/apps/suite_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -60,6 +61,9 @@ var _ = BeforeSuite(func(done Done) { Expect(err).ToNot(HaveOccurred()) Expect(cfg).ToNot(BeNil()) + err = appsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/testdata/project-v2-multigroup/go.mod b/testdata/project-v2-multigroup/go.mod index 2aeef58e0be..d0ffad76e06 100644 --- a/testdata/project-v2-multigroup/go.mod +++ b/testdata/project-v2-multigroup/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-logr/logr v0.1.0 github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.1 + k8s.io/api v0.18.6 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 sigs.k8s.io/controller-runtime v0.6.4 diff --git a/testdata/project-v2-multigroup/main.go b/testdata/project-v2-multigroup/main.go index 60aead1c253..9d8addc71ec 100644 --- a/testdata/project-v2-multigroup/main.go +++ b/testdata/project-v2-multigroup/main.go @@ -155,12 +155,12 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "HealthCheckPolicy") os.Exit(1) } - if err = (&appscontroller.PodReconciler{ + if err = (&appscontroller.DeploymentReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Pod"), + Log: ctrl.Log.WithName("controllers").WithName("Deployment"), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Pod") + setupLog.Error(err, "unable to create controller", "controller", "Deployment") os.Exit(1) } //+kubebuilder:scaffold:builder diff --git a/testdata/project-v3-config/PROJECT b/testdata/project-v3-config/PROJECT index 144702d87c3..804ab898289 100644 --- a/testdata/project-v3-config/PROJECT +++ b/testdata/project-v3-config/PROJECT @@ -40,4 +40,9 @@ resources: webhooks: defaulting: true webhookVersion: v1 +- controller: true + domain: testproject.org + group: crew + kind: Laker + version: v1 version: "3" diff --git a/testdata/project-v3-multigroup/PROJECT b/testdata/project-v3-multigroup/PROJECT index 30a6a0ddfa7..4d23fb47636 100644 --- a/testdata/project-v3-multigroup/PROJECT +++ b/testdata/project-v3-multigroup/PROJECT @@ -78,6 +78,11 @@ resources: kind: HealthCheckPolicy path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/foo.policy/v1 version: v1 +- controller: true + group: apps + kind: Deployment + path: k8s.io/api/apps/v1 + version: v1 - api: crdVersion: v1 namespaced: true diff --git a/testdata/project-v3-multigroup/config/rbac/role.yaml b/testdata/project-v3-multigroup/config/rbac/role.yaml index cd19ad8560a..5009fea150c 100644 --- a/testdata/project-v3-multigroup/config/rbac/role.yaml +++ b/testdata/project-v3-multigroup/config/rbac/role.yaml @@ -9,7 +9,7 @@ rules: - apiGroups: - apps resources: - - pods + - deployments verbs: - create - delete @@ -21,13 +21,13 @@ rules: - apiGroups: - apps resources: - - pods/finalizers + - deployments/finalizers verbs: - update - apiGroups: - apps resources: - - pods/status + - deployments/status verbs: - get - patch diff --git a/testdata/project-v3-multigroup/controllers/apps/pod_controller.go b/testdata/project-v3-multigroup/controllers/apps/deployment_controller.go similarity index 66% rename from testdata/project-v3-multigroup/controllers/apps/pod_controller.go rename to testdata/project-v3-multigroup/controllers/apps/deployment_controller.go index 8bfb4ed0dea..a18548c0177 100644 --- a/testdata/project-v3-multigroup/controllers/apps/pod_controller.go +++ b/testdata/project-v3-multigroup/controllers/apps/deployment_controller.go @@ -20,33 +20,34 @@ import ( "context" "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) -// PodReconciler reconciles a Pod object -type PodReconciler struct { +// DeploymentReconciler reconciles a Deployment object +type DeploymentReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme } -//+kubebuilder:rbac:groups=apps,resources=pods,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=apps,resources=pods/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=apps,resources=pods/finalizers,verbs=update +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=apps,resources=deployments/finalizers,verbs=update // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by -// the Pod object against the actual cluster state, and then +// the Deployment object against the actual cluster state, and then // perform operations to make the cluster state reflect the state specified by // the user. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile -func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = r.Log.WithValues("pod", req.NamespacedName) +func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = r.Log.WithValues("deployment", req.NamespacedName) // your logic here @@ -54,9 +55,8 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R } // SetupWithManager sets up the controller with the Manager. -func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument - // For(). + For(&appsv1.Deployment{}). Complete(r) } diff --git a/testdata/project-v3-multigroup/controllers/apps/suite_test.go b/testdata/project-v3-multigroup/controllers/apps/suite_test.go index 8dedd70d4ac..d0c244d0aed 100644 --- a/testdata/project-v3-multigroup/controllers/apps/suite_test.go +++ b/testdata/project-v3-multigroup/controllers/apps/suite_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -60,6 +61,9 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) + err = appsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/testdata/project-v3-multigroup/main.go b/testdata/project-v3-multigroup/main.go index d79bb749908..5707341bd7f 100644 --- a/testdata/project-v3-multigroup/main.go +++ b/testdata/project-v3-multigroup/main.go @@ -169,12 +169,12 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "HealthCheckPolicy") os.Exit(1) } - if err = (&appscontrollers.PodReconciler{ + if err = (&appscontrollers.DeploymentReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("apps").WithName("Pod"), + Log: ctrl.Log.WithName("controllers").WithName("apps").WithName("Deployment"), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Pod") + setupLog.Error(err, "unable to create controller", "controller", "Deployment") os.Exit(1) } if err = (&controllers.LakersReconciler{ diff --git a/testdata/project-v3/PROJECT b/testdata/project-v3/PROJECT index b30361d9899..8dde5b76ffe 100644 --- a/testdata/project-v3/PROJECT +++ b/testdata/project-v3/PROJECT @@ -40,4 +40,9 @@ resources: webhooks: defaulting: true webhookVersion: v1 +- controller: true + domain: testproject.org + group: crew + kind: Laker + version: v1 version: "3"