diff --git a/cmd/main.go b/cmd/main.go index 7dc5d59d92e..ab81dcf63fe 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -34,6 +34,7 @@ import ( deployimagev1alpha1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/deploy-image/v1alpha1" golangv2 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2" golangv3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3" + golangv4 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4" grafanav1alpha1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/optional/grafana/v1alpha" ) @@ -47,7 +48,7 @@ func main() { // Bundle plugin which built the golang projects scaffold by Kubebuilder go/v3 with kustomize alpha-v2 gov4Bundle, _ := plugin.NewBundle(golang.DefaultNameQualifier, plugin.Version{Number: 4, Stage: stage.Alpha}, kustomizecommonv2alpha.Plugin{}, - golangv3.Plugin{}, + golangv4.Plugin{}, ) fs := machinery.Filesystem{ @@ -64,6 +65,7 @@ func main() { cli.WithPlugins( golangv2.Plugin{}, golangv3.Plugin{}, + golangv4.Plugin{}, gov3Bundle, gov4Bundle, &kustomizecommonv1.Plugin{}, diff --git a/docs/book/src/plugins/available-plugins.md b/docs/book/src/plugins/available-plugins.md index ba7744d0d36..fd700d3e68f 100644 --- a/docs/book/src/plugins/available-plugins.md +++ b/docs/book/src/plugins/available-plugins.md @@ -2,17 +2,18 @@ This section describes the plugins supported and shipped in with the Kubebuilder project. -| Plugin | Key | Description | -| ---------------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [go.kubebuilder.io/v2 - (Deprecated)](go-v2-plugin.md) | `go/v2` | Golang plugin responsible for scaffolding the legacy layout provided with Kubebuilder CLI >= `2.0.0` and < `3.0.0`. | -| [go.kubebuilder.io/v3 - (Default scaffold with Kubebuilder init)](go-v3-plugin.md) | `go/v3` | Default scaffold used for creating a project when no plugin(s) are provided. Responsible for scaffolding Golang projects and its configurations. | -| [go.kubebuilder.io/v4-alpha - (Add Apple Sillicom Support)](go-v4-plugin.md) | `go/v4` | Scaffold composite by `base.go.kubebuilder.io/v3` and [kustomize.common.kubebuilder.io/v2-alpha](kustomize-v2-alpha.md). Responsible for scaffolding Golang projects and its configurations. | -| [declarative.go.kubebuilder.io/v1](declarative-v1.md) | `declarative/v1` | Optional plugin used to scaffold APIs/controllers using the [kubebuilder-declarative-pattern][kubebuilder-declarative-pattern] project. | -| [kustomize.common.kubebuilder.io/v1](kustomize-v1.md) | `kustomize/v1` | Responsible for scaffold all manifests to configure the projects with [kustomize(v3)][kustomize]. (create and update the `config/` directory). This plugin is used in the composition to create the plugin (`go/v3`). | -| [kustomize.common.kubebuilder.io/v2-alpha](kustomize-v2-alpha.md) | `kustomize/v2-alpha` | It has the same purpose of `kustomize/v1`. However, it works with [kustomize][kustomize] version `v4` and addresses the required changes for future kustomize configurations. It will probably be used with the future `go/v4-alpha` plugin. | -| `base.go.kubebuilder.io/v3` | `base/v3` | Responsible for scaffold all files which specific requires Golang. This plugin is used in the composition to create the plugin (`go/v3`) | -| [grafana.kubebuilder.io/v1-alpha](grafana-v1-alpha.md) | `grafana/v1-alpha` | Optional helper plugin which can be used to scaffold Grafana Manifests Dashboards for the default metrics which are exported by controller-runtime. | -| [deploy-image.go.kubebuilder.io/v1-alpha](deploy-image-plugin-v1-alpha) | `deploy-image/v1-alpha` | Optional helper plugin which can be used to scaffold APIs and controller with code implementation to Deploy and Manage an Operand(image). | +| Plugin | Key | Description | +|------------------------------------------------------------------------------------|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [go.kubebuilder.io/v2 - (Deprecated)](go-v2-plugin.md) | `go/v2` | Golang plugin responsible for scaffolding the legacy layout provided with Kubebuilder CLI >= `2.0.0` and < `3.0.0`. | +| [go.kubebuilder.io/v3 - (Default scaffold with Kubebuilder init)](go-v3-plugin.md) | `go/v3` | Default scaffold used for creating a project when no plugin(s) are provided. Responsible for scaffolding Golang projects and its configurations. | +| [go.kubebuilder.io/v4-alpha - (Add Apple Sillicom Support)](go-v4-plugin.md) | `go/v4` | Scaffold composite by `base.go.kubebuilder.io/v3` and [kustomize.common.kubebuilder.io/v2-alpha](kustomize-v2-alpha.md). Responsible for scaffolding Golang projects and its configurations. | +| [declarative.go.kubebuilder.io/v1](declarative-v1.md) | `declarative/v1` | Optional plugin used to scaffold APIs/controllers using the [kubebuilder-declarative-pattern][kubebuilder-declarative-pattern] project. | +| [kustomize.common.kubebuilder.io/v1](kustomize-v1.md) | `kustomize/v1` | Responsible for scaffold all manifests to configure the projects with [kustomize(v3)][kustomize]. (create and update the `config/` directory). This plugin is used in the composition to create the plugin (`go/v3`). | +| [kustomize.common.kubebuilder.io/v2-alpha](kustomize-v2-alpha.md) | `kustomize/v2-alpha` | It has the same purpose of `kustomize/v1`. However, it works with [kustomize][kustomize] version `v4` and addresses the required changes for future kustomize configurations. It will probably be used with the future `go/v4-alpha` plugin. | +| `base.go.kubebuilder.io/v3` | `base/v3` | Responsible for scaffold all files which specific requires Golang. This plugin is used in the composition to create the plugin (`go/v3`) | +| `base.go.kubebuilder.io/v4` | `base/v4` | Responsible for scaffold all files which specific requires Golang. This plugin is used in the composition to create the plugin (`go/v4`) | +| [grafana.kubebuilder.io/v1-alpha](grafana-v1-alpha.md) | `grafana/v1-alpha` | Optional helper plugin which can be used to scaffold Grafana Manifests Dashboards for the default metrics which are exported by controller-runtime. | +| [deploy-image.go.kubebuilder.io/v1-alpha](deploy-image-plugin-v1-alpha) | `deploy-image/v1-alpha` | Optional helper plugin which can be used to scaffold APIs and controller with code implementation to Deploy and Manage an Operand(image). | > Note: **ALPHA** plugins can introduce breaking changes. For further info see [Plugins Versioning](./plugins/plugins-versioning.md). diff --git a/docs/book/src/plugins/go-v4-plugin.md b/docs/book/src/plugins/go-v4-plugin.md index 235b5b13c2d..80b54294327 100644 --- a/docs/book/src/plugins/go-v4-plugin.md +++ b/docs/book/src/plugins/go-v4-plugin.md @@ -1,7 +1,7 @@ # go/v4-alpha (go.kubebuilder.io/v4-alpha) Kubebuilder will scaffold using the go/v4-alpha plugin only if specified when initializing the project. -This plugin is a composition of the plugins ` kustomize.common.kubebuilder.io/v2-alpha` and `base.go.kubebuilder.io/v3`. +This plugin is a composition of the plugins ` kustomize.common.kubebuilder.io/v2-alpha` and `base.go.kubebuilder.io/v4`. It scaffolds a project template that helps in constructing sets of [controllers][controller-runtime]. It scaffolds boilerplate code to create and design controllers. @@ -21,6 +21,7 @@ under the [testdata][testdata] directory on the root directory of the Kubebuilde - If you are looking to experiment with the future default scaffold that will be provided by Kubebuilder CLI - If your local environment is Apple Silicon (`darwin/arm64`) - If you are looking to use [kubernetes-sigs/kustomize][kustomize] v4 +- If you are looking for the new default layout where the "apis" and "controllers" are scaffold under the `pkg/` dir. ## How to use it ? diff --git a/pkg/model/resource/utils.go b/pkg/model/resource/utils.go index a71ad71fb1e..9be06be1168 100644 --- a/pkg/model/resource/utils.go +++ b/pkg/model/resource/utils.go @@ -50,6 +50,17 @@ func safeImport(unsafe string) string { // APIPackagePath returns the default path func APIPackagePath(repo, group, version string, multiGroup bool) string { + if multiGroup { + if group != "" { + return path.Join(repo, "pkg", "apis", group, version) + } + return path.Join(repo, "pkg", "apis", version) + } + return path.Join(repo, "pkg", "api", version) +} + +// APIPackagePathLegacy returns the default path +func APIPackagePathLegacy(repo, group, version string, multiGroup bool) string { if multiGroup { if group != "" { return path.Join(repo, "apis", group, version) diff --git a/pkg/plugin/util/helpers.go b/pkg/plugin/util/helpers.go index db252d63d8d..87d953f268b 100644 --- a/pkg/plugin/util/helpers.go +++ b/pkg/plugin/util/helpers.go @@ -20,16 +20,19 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/config" ) +// Deprecated: go/v4 no longer supports v1beta1 option // HasDifferentCRDVersion returns true if any other CRD version is tracked in the project configuration. func HasDifferentCRDVersion(config config.Config, crdVersion string) bool { return hasDifferentAPIVersion(config.ListCRDVersions(), crdVersion) } +// Deprecated: go/v4 no longer supports v1beta1 option // HasDifferentWebhookVersion returns true if any other webhook version is tracked in the project configuration. func HasDifferentWebhookVersion(config config.Config, webhookVersion string) bool { return hasDifferentAPIVersion(config.ListWebhookVersions(), webhookVersion) } +// Deprecated: go/v4 no longer supports v1beta1 option func hasDifferentAPIVersion(versions []string, version string) bool { return !(len(versions) == 0 || (len(versions) == 1 && versions[0] == version)) } diff --git a/pkg/plugin/util/util.go b/pkg/plugin/util/util.go index 71d800980e0..927ceb2d473 100644 --- a/pkg/plugin/util/util.go +++ b/pkg/plugin/util/util.go @@ -191,6 +191,25 @@ func EnsureExistAndReplace(input, match, replace string) (string, error) { return strings.Replace(input, match, replace, -1), nil } +func HasFragment(path, target string) (bool, error) { + _, err := os.Stat(path) + if err != nil { + return false, err + } + + // false positive + // nolint:gosec + b, err := os.ReadFile(path) + if err != nil { + return false, err + } + + if !strings.Contains(string(b), target) { + return false, nil + } + return true, nil +} + // ReplaceInFile replaces all instances of old with new in the file at path. func ReplaceInFile(path, old, new string) error { info, err := os.Stat(path) diff --git a/pkg/plugins/golang/declarative/v1/init.go b/pkg/plugins/golang/declarative/v1/init.go index 220d785aba2..bb786becfde 100644 --- a/pkg/plugins/golang/declarative/v1/init.go +++ b/pkg/plugins/golang/declarative/v1/init.go @@ -18,9 +18,7 @@ package v1 import ( "fmt" - "os" "path/filepath" - "strings" "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" @@ -41,48 +39,38 @@ func (p *initSubcommand) InjectConfig(c config.Config) error { } func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { - err := updateDockerfile() - if err != nil { - return err - } - return nil + return updateDockerfile() } // updateDockerfile will add channels staging required for declarative plugin func updateDockerfile() error { fmt.Println("updating Dockerfile to add channels/ directory in the image") - managerFile := filepath.Join("Dockerfile") + dokerfile := filepath.Join("Dockerfile") - // nolint:lll - err := insertCodeIfDoesNotExist(managerFile, - "COPY controllers/ controllers/", - "\n# https://github.com/kubernetes-sigs/kubebuilder-declarative-pattern/blob/master/docs/addon/walkthrough/README.md#adding-a-manifest\n# Stage channels and make readable\nCOPY channels/ /channels/\nRUN chmod -R a+rx /channels/") + isLegacyLayout, err := util.HasFragment(dokerfile, "COPY controllers/ controllers/") if err != nil { return err } - err = insertCodeIfDoesNotExist(managerFile, - "COPY --from=builder /workspace/manager .", - "\n# copy channels\nCOPY --from=builder /channels /channels\n") - if err != nil { - return err + hasChannels, _ := util.HasFragment(dokerfile, "COPY channels/ /channels/") + if !hasChannels { + if isLegacyLayout { + // nolint:lll + err := util.InsertCode(dokerfile, "COPY controllers/ controllers/", + "\n# https://github.com/kubernetes-sigs/kubebuilder-declarative-pattern/blob/master/docs/addon/walkthrough/README.md#adding-a-manifest\n# Stage channels and make readable\nCOPY channels/ /channels/\nRUN chmod -R a+rx /channels/") + if err != nil { + return err + } + } else { + // nolint:lll + err := util.InsertCode(dokerfile, + "COPY pkg/controllers/ pkg/controllers/", + "\n# https://github.com/kubernetes-sigs/kubebuilder-declarative-pattern/blob/master/docs/addon/walkthrough/README.md#adding-a-manifest\n# Stage channels and make readable\nCOPY channels/ /channels/\nRUN chmod -R a+rx /channels/") + if err != nil { + return err + } + } } - return nil -} -// insertCodeIfDoesNotExist insert code if it does not already exists -func insertCodeIfDoesNotExist(filename, target, code string) error { - // false positive - // nolint:gosec - contents, err := os.ReadFile(filename) - if err != nil { - return err - } - - idx := strings.Index(string(contents), code) - if idx != -1 { - return nil - } - - return util.InsertCode(filename, target, code) + return nil } diff --git a/pkg/plugins/golang/declarative/v1/scaffolds/internal/templates/controller.go b/pkg/plugins/golang/declarative/v1/scaffolds/internal/templates/controller.go index f0b32456748..38f41ac3b43 100644 --- a/pkg/plugins/golang/declarative/v1/scaffolds/internal/templates/controller.go +++ b/pkg/plugins/golang/declarative/v1/scaffolds/internal/templates/controller.go @@ -17,6 +17,7 @@ limitations under the License. package templates import ( + "fmt" "path/filepath" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" @@ -43,6 +44,7 @@ func (f *Controller) SetTemplateDefaults() error { } } f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) f.TemplateBody = controllerTemplate diff --git a/pkg/plugins/golang/declarative/v1/scaffolds/internal/templates/types.go b/pkg/plugins/golang/declarative/v1/scaffolds/internal/templates/types.go index 4a3b063ba05..195caf02722 100644 --- a/pkg/plugins/golang/declarative/v1/scaffolds/internal/templates/types.go +++ b/pkg/plugins/golang/declarative/v1/scaffolds/internal/templates/types.go @@ -45,6 +45,7 @@ func (f *Types) SetTemplateDefaults() error { } } f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) f.TemplateBody = typesTemplate diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/api.go b/pkg/plugins/golang/deploy-image/v1alpha1/api.go index a0e6ee27a07..d9007583b2d 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/api.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/api.go @@ -20,10 +20,10 @@ import ( "errors" "fmt" "os" - - goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" + "strings" "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" + goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" @@ -152,7 +152,14 @@ func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { p.options.DoAPI = true p.options.DoController = true p.options.Namespaced = true - p.options.UpdateResource(p.resource, p.config) + + isLegacyLayout := false + for _, pluginKey := range p.config.GetPluginChain() { + if strings.Contains(pluginKey, "go.kubebuilder.io/v3") { + isLegacyLayout = true + } + } + p.options.UpdateResource(p.resource, p.config, isLegacyLayout) if err := p.resource.Validate(); err != nil { return err @@ -165,12 +172,14 @@ func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { } // Check CRDVersion against all other CRDVersions in p.config for compatibility. + // nolint:staticcheck if util.HasDifferentCRDVersion(p.config, p.resource.API.CRDVersion) { return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q", p.resource.API.CRDVersion) } // Check CRDVersion against all other CRDVersions in p.config for compatibility. + // nolint:staticcheck if util.HasDifferentCRDVersion(p.config, p.resource.API.CRDVersion) { return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q", p.resource.API.CRDVersion) diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go index 882c079ffac..f74369c7913 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/api.go @@ -29,10 +29,12 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" "sigs.k8s.io/kubebuilder/v3/pkg/plugins" kustomizev1scaffolds "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v1/scaffolds" + kustomizev2scaffolds "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v2-alpha/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/api" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/config/samples" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers" golangv3scaffolds "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" + golangv4scaffolds "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds" ) var _ plugins.Scaffolder = &apiScaffolder{} @@ -75,7 +77,14 @@ func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) { func (s *apiScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - if err := s.scaffoldCreateAPIFromPlugins(); err != nil { + isLegacyLayout := false + for _, pluginKey := range s.config.GetPluginChain() { + if strings.Contains(pluginKey, "go.kubebuilder.io/v3") { + isLegacyLayout = true + } + } + + if err := s.scaffoldCreateAPIFromPlugins(isLegacyLayout); err != nil { return err } @@ -104,17 +113,33 @@ func (s *apiScaffolder) Scaffold() error { return fmt.Errorf("error updating config/samples: %v", err) } - controller := &controllers.Controller{ - ControllerRuntimeVersion: golangv3scaffolds.ControllerRuntimeVersion, - } - if err := scaffold.Execute( - controller, - ); err != nil { - return fmt.Errorf("error scaffolding controller: %v", err) - } + if isLegacyLayout { + controller := &controllers.Controller{ + ControllerRuntimeVersion: golangv3scaffolds.ControllerRuntimeVersion, + } + if err := scaffold.Execute( + controller, + ); err != nil { + return fmt.Errorf("error scaffolding controller: %v", err) + } + + if err := s.updateControllerCode(*controller); err != nil { + return fmt.Errorf("error updating controller: %v", err) + } - if err := s.updateControllerCode(*controller); err != nil { - return fmt.Errorf("error updating controller: %v", err) + } else { + controller := &controllers.Controller{ + ControllerRuntimeVersion: golangv4scaffolds.ControllerRuntimeVersion, + IsLegacyLayout: false, + } + if err := scaffold.Execute( + controller, + ); err != nil { + return fmt.Errorf("error scaffolding controller: %v", err) + } + if err := s.updateControllerCode(*controller); err != nil { + return fmt.Errorf("error updating controller: %v", err) + } } if err := s.updateMainByAddingEventRecorder(); err != nil { @@ -122,7 +147,7 @@ func (s *apiScaffolder) Scaffold() error { } if err := scaffold.Execute( - &controllers.ControllerTest{Port: s.port}, + &controllers.ControllerTest{Port: s.port, IsLegacyLayout: isLegacyLayout}, ); err != nil { return fmt.Errorf("error creating controllers/**_controller_test.go: %v", err) } @@ -157,12 +182,12 @@ func (s *apiScaffolder) addEnvVarIntoManager() error { // scaffoldCreateAPIFromPlugins will reuse the code from the kustomize and base golang // plugins to do the default scaffolds which an API is created -func (s *apiScaffolder) scaffoldCreateAPIFromPlugins() error { - if err := s.scaffoldCreateAPIFromGolang(); err != nil { +func (s *apiScaffolder) scaffoldCreateAPIFromPlugins(isLegacyLayout bool) error { + if err := s.scaffoldCreateAPIFromGolang(isLegacyLayout); err != nil { return fmt.Errorf("error scaffolding golang files for the new API: %v", err) } - if err := s.scaffoldCreateAPIFromKustomize(); err != nil { + if err := s.scaffoldCreateAPIFromKustomize(isLegacyLayout); err != nil { return fmt.Errorf("error scaffolding kustomize manifests for the new API: %v", err) } return nil @@ -266,32 +291,47 @@ func (s *apiScaffolder) updateControllerCode(controller controllers.Controller) return nil } -func (s *apiScaffolder) scaffoldCreateAPIFromKustomize() error { - // Now we need call the kustomize/v1 plugin to do its scaffolds when we create a new API - // todo: when we have the go/v4-alpha plugin we will also need to check what is the plugin used - // in the Project layout to know if we should use kustomize/v1 OR kustomize/v2-alpha - kustomizeV1Scaffolder := kustomizev1scaffolds.NewAPIScaffolder( - s.config, - s.resource, - true, - ) - kustomizeV1Scaffolder.InjectFS(s.fs) - - if err := kustomizeV1Scaffolder.Scaffold(); err != nil { - return fmt.Errorf("error scaffolding kustomize files for the APIs: %v", err) +func (s *apiScaffolder) scaffoldCreateAPIFromKustomize(isLegacyLayout bool) error { + if isLegacyLayout { + kustomizeV1Scaffolder := kustomizev1scaffolds.NewAPIScaffolder( + s.config, + s.resource, + true, + ) + kustomizeV1Scaffolder.InjectFS(s.fs) + + if err := kustomizeV1Scaffolder.Scaffold(); err != nil { + return fmt.Errorf("error scaffolding kustomize files for the APIs: %v", err) + } + } else { + kustomizeV2Scaffolder := kustomizev2scaffolds.NewAPIScaffolder( + s.config, + s.resource, + true, + ) + kustomizeV2Scaffolder.InjectFS(s.fs) + + if err := kustomizeV2Scaffolder.Scaffold(); err != nil { + return fmt.Errorf("error scaffolding kustomize files for the APIs: %v", err) + } } + return nil } -func (s *apiScaffolder) scaffoldCreateAPIFromGolang() error { - // Now we need call the kustomize/v1 plugin to do its scaffolds when we create a new API - // todo: when we have the go/v4-alpha plugin we will also need to check what is the plugin used - // in the Project layout to know if we should use kustomize/v1 OR kustomize/v2-alpha +func (s *apiScaffolder) scaffoldCreateAPIFromGolang(isLegacyLayout bool) error { + if isLegacyLayout { + golangV3Scaffolder := golangv3scaffolds.NewAPIScaffolder(s.config, + s.resource, true) + golangV3Scaffolder.InjectFS(s.fs) + return golangV3Scaffolder.Scaffold() + } else { + golangV4Scaffolder := golangv4scaffolds.NewAPIScaffolder(s.config, + s.resource, true) + golangV4Scaffolder.InjectFS(s.fs) + return golangV4Scaffolder.Scaffold() + } - golangV3Scaffolder := golangv3scaffolds.NewAPIScaffolder(s.config, - s.resource, true) - golangV3Scaffolder.InjectFS(s.fs) - return golangV3Scaffolder.Scaffold() } const containerTemplate = `Containers: []corev1.Container{{ diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/config/samples/crd_sample.go b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/config/samples/crd_sample.go index 785248139d9..9c682b98982 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/config/samples/crd_sample.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/config/samples/crd_sample.go @@ -14,6 +14,7 @@ limitations under the License. package samples import ( + "fmt" "path/filepath" "sigs.k8s.io/kubebuilder/v3/pkg/machinery" @@ -36,6 +37,7 @@ func (f *CRDSample) SetTemplateDefaults() error { f.Path = filepath.Join("config", "samples", "%[group]_%[version]_%[kind].yaml") } f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) f.IfExistsAction = machinery.OverwriteFile diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go index 81eb5b489cc..e3ba984bcf7 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller-test.go @@ -33,20 +33,29 @@ type ControllerTest struct { machinery.BoilerplateMixin machinery.ResourceMixin - Image string - Port string + Port string + IsLegacyLayout bool } // SetTemplateDefaults implements file.Template func (f *ControllerTest) SetTemplateDefaults() error { if f.Path == "" { if f.MultiGroup && f.Resource.Group != "" { - f.Path = filepath.Join("controllers", "%[group]", "%[kind]_controller_test.go") + if f.IsLegacyLayout { + f.Path = filepath.Join("controllers", "%[group]", "%[kind]_controller_test.go") + } else { + f.Path = filepath.Join("pkg", "controllers", "%[group]", "%[kind]_controller_test.go") + } } else { - f.Path = filepath.Join("controllers", "%[kind]_controller_test.go") + if f.IsLegacyLayout { + f.Path = filepath.Join("controllers", "%[kind]_controller_test.go") + } else { + f.Path = filepath.Join("pkg", "controllers", "%[kind]_controller_test.go") + } } } f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) fmt.Println("creating import for %", f.Resource.Path) f.TemplateBody = controllerTestTemplate diff --git a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go index 4eac67bd723..4f1023be20e 100644 --- a/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/deploy-image/v1alpha1/scaffolds/internal/templates/controllers/controller.go @@ -35,18 +35,28 @@ type Controller struct { machinery.ProjectNameMixin ControllerRuntimeVersion string + IsLegacyLayout bool } // SetTemplateDefaults implements file.Template func (f *Controller) SetTemplateDefaults() error { if f.Path == "" { if f.MultiGroup && f.Resource.Group != "" { - f.Path = filepath.Join("controllers", "%[group]", "%[kind]_controller.go") + if f.IsLegacyLayout { + f.Path = filepath.Join("controllers", "%[group]", "%[kind]_controller.go") + } else { + f.Path = filepath.Join("pkg", "controllers", "%[group]", "%[kind]_controller.go") + } } else { - f.Path = filepath.Join("controllers", "%[kind]_controller.go") + if f.IsLegacyLayout { + f.Path = filepath.Join("controllers", "%[kind]_controller.go") + } else { + f.Path = filepath.Join("pkg", "controllers", "%[kind]_controller.go") + } } } f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) fmt.Println("creating import for %", f.Resource.Path) f.TemplateBody = controllerTemplate diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index b6a7338ee93..168412bbf33 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -74,17 +74,26 @@ type Options struct { } // UpdateResource updates the provided resource with the options -func (opts Options) UpdateResource(res *resource.Resource, c config.Config) { +func (opts Options) UpdateResource(res *resource.Resource, c config.Config, isLegacyLayout bool) { if opts.Plural != "" { res.Plural = opts.Plural } if opts.DoAPI { - res.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup()) - res.API = &resource.API{ - CRDVersion: opts.CRDVersion, - Namespaced: opts.Namespaced, + if isLegacyLayout { + res.Path = resource.APIPackagePathLegacy(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup()) + res.API = &resource.API{ + CRDVersion: opts.CRDVersion, + Namespaced: opts.Namespaced, + } + } else { + res.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup()) + res.API = &resource.API{ + CRDVersion: opts.CRDVersion, + Namespaced: opts.Namespaced, + } } + } if opts.DoController { @@ -92,7 +101,11 @@ func (opts Options) UpdateResource(res *resource.Resource, c config.Config) { } if opts.DoDefaulting || opts.DoValidation || opts.DoConversion { - res.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup()) + if isLegacyLayout { + res.Path = resource.APIPackagePathLegacy(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup()) + } else { + res.Path = resource.APIPackagePath(c.GetRepository(), res.Group, res.Version, c.IsMultiGroup()) + } res.Webhooks.WebhookVersion = opts.WebhookVersion if opts.DoDefaulting { res.Webhooks.Defaulting = true diff --git a/pkg/plugins/golang/options_test.go b/pkg/plugins/golang/options_test.go index df87ee20748..4433fdd81eb 100644 --- a/pkg/plugins/golang/options_test.go +++ b/pkg/plugins/golang/options_test.go @@ -17,10 +17,9 @@ limitations under the License. package golang import ( - "path" - . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "path" "sigs.k8s.io/kubebuilder/v3/pkg/config" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" @@ -68,7 +67,7 @@ var _ = Describe("Options", func() { Webhooks: &resource.Webhooks{}, } - options.UpdateResource(&res, cfg) + options.UpdateResource(&res, cfg, false) Expect(res.Validate()).To(Succeed()) Expect(res.GVK.IsEqualTo(gvk)).To(BeTrue()) if options.Plural != "" { @@ -77,9 +76,9 @@ var _ = Describe("Options", func() { if options.DoAPI || options.DoDefaulting || options.DoValidation || options.DoConversion { if multiGroup { Expect(res.Path).To(Equal( - path.Join(cfg.GetRepository(), "apis", gvk.Group, gvk.Version))) + path.Join(cfg.GetRepository(), "pkg", "apis", gvk.Group, gvk.Version))) } else { - Expect(res.Path).To(Equal(path.Join(cfg.GetRepository(), "api", gvk.Version))) + Expect(res.Path).To(Equal(path.Join(cfg.GetRepository(), "pkg", "api", gvk.Version))) } } else { // Core-resources have a path despite not having an API/Webhook but they are not tested here @@ -139,7 +138,7 @@ var _ = Describe("Options", func() { Webhooks: &resource.Webhooks{}, } - options.UpdateResource(&res, cfg) + options.UpdateResource(&res, cfg, false) Expect(res.Validate()).To(Succeed()) Expect(res.Path).To(Equal(path.Join("k8s.io", "api", group, version))) @@ -179,7 +178,7 @@ var _ = Describe("Options", func() { Webhooks: &resource.Webhooks{}, } - options.UpdateResource(&res, cfg) + options.UpdateResource(&res, cfg, false) Expect(res.Validate()).To(Succeed()) Expect(res.Path).To(Equal(path.Join("k8s.io", "api", group, version))) diff --git a/pkg/plugins/golang/v2/api.go b/pkg/plugins/golang/v2/api.go index 88ccd3d188b..6323be9ad9c 100644 --- a/pkg/plugins/golang/v2/api.go +++ b/pkg/plugins/golang/v2/api.go @@ -129,7 +129,7 @@ func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { p.options.DoController = util.YesNo(reader) } - p.options.UpdateResource(p.resource, p.config) + p.options.UpdateResource(p.resource, p.config, true) if err := p.resource.Validate(); err != nil { return err diff --git a/pkg/plugins/golang/v2/webhook.go b/pkg/plugins/golang/v2/webhook.go index 02a7ae91eab..a873b3ce755 100644 --- a/pkg/plugins/golang/v2/webhook.go +++ b/pkg/plugins/golang/v2/webhook.go @@ -85,7 +85,7 @@ func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { return fmt.Errorf("group cannot be empty") } - p.options.UpdateResource(p.resource, p.config) + p.options.UpdateResource(p.resource, p.config, true) if err := p.resource.Validate(); err != nil { return err diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index 30b81ca7f30..582002c640f 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -141,7 +141,7 @@ func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { p.options.DoController = util.YesNo(reader) } - p.options.UpdateResource(p.resource, p.config) + p.options.UpdateResource(p.resource, p.config, true) if err := p.resource.Validate(); err != nil { return err @@ -161,6 +161,7 @@ func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { } // Check CRDVersion against all other CRDVersions in p.config for compatibility. + // nolint:staticcheck if util.HasDifferentCRDVersion(p.config, p.resource.API.CRDVersion) { return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q", p.resource.API.CRDVersion) diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go index 1baba22aaba..1899bd0240b 100644 --- a/pkg/plugins/golang/v3/webhook.go +++ b/pkg/plugins/golang/v3/webhook.go @@ -95,7 +95,7 @@ func (p *createWebhookSubcommand) InjectConfig(c config.Config) error { func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { p.resource = res - p.options.UpdateResource(p.resource, p.config) + p.options.UpdateResource(p.resource, p.config, true) if err := p.resource.Validate(); err != nil { return err diff --git a/pkg/plugins/golang/v4/api.go b/pkg/plugins/golang/v4/api.go new file mode 100644 index 00000000000..eb0fe1725cc --- /dev/null +++ b/pkg/plugins/golang/v4/api.go @@ -0,0 +1,191 @@ +/* +Copyright 2022 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 v4 + +import ( + "bufio" + "errors" + "fmt" + "os" + + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" + goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds" +) + +const ( + // defaultCRDVersion is the default CRD API version to scaffold. + defaultCRDVersion = "v1" +) + +// DefaultMainPath is default file path of main.go +const DefaultMainPath = "main.go" + +var _ plugin.CreateAPISubcommand = &createAPISubcommand{} + +type createAPISubcommand struct { + config config.Config + + options *goPlugin.Options + + resource *resource.Resource + + // Check if we have to scaffold resource and/or controller + resourceFlag *pflag.Flag + controllerFlag *pflag.Flag + + // force indicates that the resource should be created even if it already exists + force bool + + // runMake indicates whether to run make or not after scaffolding APIs + runMake bool +} + +func (p *createAPISubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `Scaffold a Kubernetes API by writing a Resource definition and/or a Controller. + +If information about whether the resource and controller should be scaffolded +was not explicitly provided, it will prompt the user if they should be. + +After the scaffold is written, the dependencies will be updated and +make generate will be run. +` + subcmdMeta.Examples = fmt.Sprintf(` # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate + %[1]s create api --group ship --version v1beta1 --kind Frigate + + # Edit the API Scheme + nano pkg/api/v1beta1/frigate_types.go + + # Edit the Controller + nano pkg/controllers/frigate/frigate_controller.go + + # Edit the Controller Test + nano pkg/controllers/frigate/frigate_controller_test.go + + # Generate the manifests + make manifests + + # Install CRDs into the Kubernetes cluster using kubectl apply + make install + + # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config + make run +`, cliMeta.CommandName) +} + +func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { + fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files") + + fs.BoolVar(&p.force, "force", false, + "attempt to create resource even if it already exists") + + p.options = &goPlugin.Options{} + + fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form") + + fs.BoolVar(&p.options.DoAPI, "resource", true, + "if set, generate the resource without prompting the user") + p.resourceFlag = fs.Lookup("resource") + fs.BoolVar(&p.options.Namespaced, "namespaced", true, "resource is namespaced") + + fs.BoolVar(&p.options.DoController, "controller", true, + "if set, generate the controller without prompting the user") + p.controllerFlag = fs.Lookup("controller") +} + +func (p *createAPISubcommand) InjectConfig(c config.Config) error { + p.config = c + // go/v4 no longer supports v1beta1 option + p.options.CRDVersion = defaultCRDVersion + return nil +} + +func (p *createAPISubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + + // TODO: re-evaluate whether y/n input still makes sense. We should probably always + // scaffold the resource and controller. + // Ask for API and Controller if not specified + reader := bufio.NewReader(os.Stdin) + if !p.resourceFlag.Changed { + fmt.Println("Create Resource [y/n]") + p.options.DoAPI = util.YesNo(reader) + } + if !p.controllerFlag.Changed { + fmt.Println("Create Controller [y/n]") + p.options.DoController = util.YesNo(reader) + } + + p.options.UpdateResource(p.resource, p.config, false) + + if err := p.resource.Validate(); err != nil { + return err + } + + // In case we want to scaffold a resource API we need to do some checks + if p.options.DoAPI { + // Check that resource doesn't have the API scaffolded or flag force was set + if r, err := p.config.GetResource(p.resource.GVK); err == nil && r.HasAPI() && !p.force { + return errors.New("API resource already exists") + } + + // Check that the provided group can be added to the project + if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.resource.Group) { + return fmt.Errorf("multiple groups are not allowed by default, " + + "to enable multi-group visit https://kubebuilder.io/migration/multi-group.html") + } + } + + return nil +} + +func (p *createAPISubcommand) PreScaffold(machinery.Filesystem) error { + // check if main.go is present in the root directory + if _, err := os.Stat(DefaultMainPath); os.IsNotExist(err) { + return fmt.Errorf("%s file should present in the root directory", DefaultMainPath) + } + + return nil +} + +func (p *createAPISubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewAPIScaffolder(p.config, *p.resource, p.force) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() +} + +func (p *createAPISubcommand) PostScaffold() error { + err := util.RunCmd("Update dependencies", "go", "mod", "tidy") + if err != nil { + return err + } + if p.runMake && p.resource.HasAPI() { + err = util.RunCmd("Running make", "make", "generate") + if err != nil { + return err + } + fmt.Print("Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:\n$ make manifests\n") + } + + return nil +} diff --git a/pkg/plugins/golang/v4/edit.go b/pkg/plugins/golang/v4/edit.go new file mode 100644 index 00000000000..438c016815b --- /dev/null +++ b/pkg/plugins/golang/v4/edit.go @@ -0,0 +1,65 @@ +/* +Copyright 2022 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 v4 + +import ( + "fmt" + + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds" +) + +var _ plugin.EditSubcommand = &editSubcommand{} + +type editSubcommand struct { + config config.Config + + multigroup bool +} + +func (p *editSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + subcmdMeta.Description = `This command will edit the project configuration. +Features supported: + - Toggle between single or multi group projects. +` + subcmdMeta.Examples = fmt.Sprintf(` # Enable the multigroup layout + %[1]s edit --multigroup + + # Disable the multigroup layout + %[1]s edit --multigroup=false +`, cliMeta.CommandName) +} + +func (p *editSubcommand) BindFlags(fs *pflag.FlagSet) { + fs.BoolVar(&p.multigroup, "multigroup", false, "enable or disable multigroup layout") +} + +func (p *editSubcommand) InjectConfig(c config.Config) error { + p.config = c + + return nil +} + +func (p *editSubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewEditScaffolder(p.config, p.multigroup) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() +} diff --git a/pkg/plugins/golang/v4/init.go b/pkg/plugins/golang/v4/init.go new file mode 100644 index 00000000000..21927ec0fe4 --- /dev/null +++ b/pkg/plugins/golang/v4/init.go @@ -0,0 +1,209 @@ +/* +Copyright 2022 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 v4 + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "unicode" + + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds" +) + +// Variables and function to check Go version requirements. +var ( + goVerMin = golang.MustParse("go1.19.0") + goVerMax = golang.MustParse("go2.0alpha1") +) + +var _ plugin.InitSubcommand = &initSubcommand{} + +type initSubcommand struct { + config config.Config + // For help text. + commandName string + + // boilerplate options + license string + owner string + + // go config options + repo string + + // flags + fetchDeps bool + skipGoVersionCheck bool +} + +func (p *initSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + p.commandName = cliMeta.CommandName + + subcmdMeta.Description = `Initialize a new project including the following files: + - a "go.mod" with project dependencies + - a "PROJECT" file that stores project configuration + - a "Makefile" with several useful make targets for the project + - several YAML files for project deployment under the "config" directory + - a "main.go" file that creates the manager that will run the project controllers +` + subcmdMeta.Examples = fmt.Sprintf(` # Initialize a new project with your domain and name in copyright + %[1]s init --plugins go/v3 --domain example.org --owner "Your name" + + # Initialize a new project defining a specific project version + %[1]s init --plugins go/v3 --project-version 3 +`, cliMeta.CommandName) +} + +func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { + fs.BoolVar(&p.skipGoVersionCheck, "skip-go-version-check", + false, "if specified, skip checking the Go version") + + // dependency args + fs.BoolVar(&p.fetchDeps, "fetch-deps", true, "ensure dependencies are downloaded") + + // boilerplate args + fs.StringVar(&p.license, "license", "apache2", + "license to use to boilerplate, may be one of 'apache2', 'none'") + fs.StringVar(&p.owner, "owner", "", "owner to add to the copyright") + + // project args + fs.StringVar(&p.repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+ + "defaults to the go package of the current working directory.") +} + +func (p *initSubcommand) InjectConfig(c config.Config) error { + p.config = c + + // Try to guess repository if flag is not set. + if p.repo == "" { + repoPath, err := golang.FindCurrentRepo() + if err != nil { + return fmt.Errorf("error finding current repository: %v", err) + } + p.repo = repoPath + } + + return p.config.SetRepository(p.repo) +} + +func (p *initSubcommand) PreScaffold(machinery.Filesystem) error { + // Ensure Go version is in the allowed range if check not turned off. + if !p.skipGoVersionCheck { + if err := golang.ValidateGoVersion(goVerMin, goVerMax); err != nil { + return err + } + } + + // Check if the current directory has not files or directories which does not allow to init the project + return checkDir() +} + +func (p *initSubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewInitScaffolder(p.config, p.license, p.owner) + scaffolder.InjectFS(fs) + err := scaffolder.Scaffold() + if err != nil { + return err + } + + if !p.fetchDeps { + fmt.Println("Skipping fetching dependencies.") + return nil + } + + // Ensure that we are pinning controller-runtime version + // xref: https://github.com/kubernetes-sigs/kubebuilder/issues/997 + err = util.RunCmd("Get controller runtime", "go", "get", + "sigs.k8s.io/controller-runtime@"+scaffolds.ControllerRuntimeVersion) + if err != nil { + return err + } + + return nil +} + +func (p *initSubcommand) PostScaffold() error { + err := util.RunCmd("Update dependencies", "go", "mod", "tidy") + if err != nil { + return err + } + + fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName) + return nil +} + +// checkDir will return error if the current directory has files which are not allowed. +// Note that, it is expected that the directory to scaffold the project is cleaned. +// Otherwise, it might face issues to do the scaffold. +func checkDir() error { + err := filepath.Walk(".", + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + // Allow directory trees starting with '.' + if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." { + return filepath.SkipDir + } + // Allow files starting with '.' + if strings.HasPrefix(info.Name(), ".") { + return nil + } + // Allow files ending with '.md' extension + if strings.HasSuffix(info.Name(), ".md") && !info.IsDir() { + return nil + } + // Allow capitalized files except PROJECT + isCapitalized := true + for _, l := range info.Name() { + if !unicode.IsUpper(l) { + isCapitalized = false + break + } + } + if isCapitalized && info.Name() != "PROJECT" { + return nil + } + // Allow files in the following list + allowedFiles := []string{ + "go.mod", // user might run `go mod init` instead of providing the `--flag` at init + "go.sum", // auto-generated file related to go.mod + } + for _, allowedFile := range allowedFiles { + if info.Name() == allowedFile { + return nil + } + } + // Do not allow any other file + return fmt.Errorf( + "target directory is not empty (only %s, files and directories with the prefix \".\", "+ + "files with the suffix \".md\" or capitalized files name are allowed); "+ + "found existing file %q", strings.Join(allowedFiles, ", "), path) + }) + if err != nil { + return err + } + return nil +} diff --git a/pkg/plugins/golang/v4/plugin.go b/pkg/plugins/golang/v4/plugin.go new file mode 100644 index 00000000000..f8b60a9ab4a --- /dev/null +++ b/pkg/plugins/golang/v4/plugin.go @@ -0,0 +1,64 @@ +/* +Copyright 2022 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 v4 + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" +) + +const pluginName = "base." + golang.DefaultNameQualifier + +var ( + pluginVersion = plugin.Version{Number: 4} + supportedProjectVersions = []config.Version{cfgv3.Version} +) + +var _ plugin.Full = Plugin{} + +// Plugin implements the plugin.Full interface +type Plugin struct { + initSubcommand + createAPISubcommand + createWebhookSubcommand + editSubcommand +} + +// Name returns the name of the plugin +func (Plugin) Name() string { return pluginName } + +// Version returns the version of the plugin +func (Plugin) Version() plugin.Version { return pluginVersion } + +// SupportedProjectVersions returns an array with all project versions supported by the plugin +func (Plugin) SupportedProjectVersions() []config.Version { return supportedProjectVersions } + +// GetInitSubcommand will return the subcommand which is responsible for initializing and common scaffolding +func (p Plugin) GetInitSubcommand() plugin.InitSubcommand { return &p.initSubcommand } + +// GetCreateAPISubcommand will return the subcommand which is responsible for scaffolding apis +func (p Plugin) GetCreateAPISubcommand() plugin.CreateAPISubcommand { return &p.createAPISubcommand } + +// GetCreateWebhookSubcommand will return the subcommand which is responsible for scaffolding webhooks +func (p Plugin) GetCreateWebhookSubcommand() plugin.CreateWebhookSubcommand { + return &p.createWebhookSubcommand +} + +// GetEditSubcommand will return the subcommand which is responsible for editing the scaffold of the project +func (p Plugin) GetEditSubcommand() plugin.EditSubcommand { return &p.editSubcommand } diff --git a/pkg/plugins/golang/v4/scaffolds/api.go b/pkg/plugins/golang/v4/scaffolds/api.go new file mode 100644 index 00000000000..69a5a02748d --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/api.go @@ -0,0 +1,113 @@ +/* +Copyright 2022 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 scaffolds + +import ( + "fmt" + + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/api" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/hack" +) + +var _ plugins.Scaffolder = &apiScaffolder{} + +// apiScaffolder contains configuration for generating scaffolding for Go type +// representing the API and controller that implements the behavior for the API. +type apiScaffolder struct { + config config.Config + resource resource.Resource + + // fs is the filesystem that will be used by the scaffolder + fs machinery.Filesystem + + // force indicates whether to scaffold controller files even if it exists or not + force bool +} + +// NewAPIScaffolder returns a new Scaffolder for API/controller creation operations +func NewAPIScaffolder(config config.Config, res resource.Resource, force bool) plugins.Scaffolder { + return &apiScaffolder{ + config: config, + resource: res, + force: force, + } +} + +// InjectFS implements cmdutil.Scaffolder +func (s *apiScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder +func (s *apiScaffolder) Scaffold() error { + fmt.Println("Writing scaffold for you to edit...") + + // Load the boilerplate + boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) + if err != nil { + return fmt.Errorf("error scaffolding API/controller: unable to load boilerplate: %w", err) + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), + ) + + // Keep track of these values before the update + doAPI := s.resource.HasAPI() + doController := s.resource.HasController() + + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } + + if doAPI { + if err := scaffold.Execute( + &api.Types{Force: s.force}, + &api.Group{}, + ); err != nil { + return fmt.Errorf("error scaffolding APIs: %v", err) + } + } + + if doController { + if err := scaffold.Execute( + &controllers.SuiteTest{Force: s.force}, + &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force}, + ); err != nil { + return fmt.Errorf("error scaffolding controller: %v", err) + } + } + + if err := scaffold.Execute( + &templates.MainUpdater{WireResource: doAPI, WireController: doController}, + ); err != nil { + return fmt.Errorf("error updating main.go: %v", err) + } + + return nil +} diff --git a/pkg/plugins/golang/v4/scaffolds/doc.go b/pkg/plugins/golang/v4/scaffolds/doc.go new file mode 100644 index 00000000000..81757e9692b --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2022 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 scaffolds contains libraries for scaffolding code to use with controller-runtime +package scaffolds diff --git a/pkg/plugins/golang/v4/scaffolds/edit.go b/pkg/plugins/golang/v4/scaffolds/edit.go new file mode 100644 index 00000000000..82f770415d2 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/edit.go @@ -0,0 +1,102 @@ +/* +Copyright 2022 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 scaffolds + +import ( + "fmt" + "strings" + + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" +) + +var _ plugins.Scaffolder = &editScaffolder{} + +type editScaffolder struct { + config config.Config + multigroup bool + + // fs is the filesystem that will be used by the scaffolder + fs machinery.Filesystem +} + +// NewEditScaffolder returns a new Scaffolder for configuration edit operations +func NewEditScaffolder(config config.Config, multigroup bool) plugins.Scaffolder { + return &editScaffolder{ + config: config, + multigroup: multigroup, + } +} + +// InjectFS implements cmdutil.Scaffolder +func (s *editScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder +func (s *editScaffolder) Scaffold() error { + filename := "Dockerfile" + bs, err := afero.ReadFile(s.fs.FS, filename) + if err != nil { + return err + } + str := string(bs) + + // update dockerfile + if s.multigroup { + str, err = ensureExistAndReplace( + str, + "COPY pkg/api/ pkg/api/", + `COPY pkg/apis/ pkg/apis/`) + + } else { + str, err = ensureExistAndReplace( + str, + "COPY pkg/apis/ pkg/apis/", + `COPY pkg/api/ pkg/api/`) + } + + // Ignore the error encountered, if the file is already in desired format. + if err != nil && s.multigroup != s.config.IsMultiGroup() { + return err + } + + if s.multigroup { + _ = s.config.SetMultiGroup() + } else { + _ = s.config.ClearMultiGroup() + } + + // Check if the str is not empty, because when the file is already in desired format it will return empty string + // because there is nothing to replace. + if str != "" { + // TODO: instead of writing it directly, we should use the scaffolding machinery for consistency + return afero.WriteFile(s.fs.FS, filename, []byte(str), 0644) + } + + return nil +} + +func ensureExistAndReplace(input, match, replace string) (string, error) { + if !strings.Contains(input, match) { + return "", fmt.Errorf("can't find %q", match) + } + return strings.Replace(input, match, replace, -1), nil +} diff --git a/pkg/plugins/golang/v4/scaffolds/init.go b/pkg/plugins/golang/v4/scaffolds/init.go new file mode 100644 index 00000000000..37a220e8996 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/init.go @@ -0,0 +1,136 @@ +/* +Copyright 2022 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 scaffolds + +import ( + "fmt" + + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" + kustomizecommonv1 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v1" + kustomizecommonv2alpha "sigs.k8s.io/kubebuilder/v3/pkg/plugins/common/kustomize/v2-alpha" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/hack" +) + +const ( + // ControllerRuntimeVersion is the kubernetes-sigs/controller-runtime version to be used in the project + ControllerRuntimeVersion = "v0.13.0" + // ControllerToolsVersion is the kubernetes-sigs/controller-tools version to be used in the project + ControllerToolsVersion = "v0.10.0" + + imageName = "controller:latest" +) + +var _ plugins.Scaffolder = &initScaffolder{} + +var kustomizeVersion string + +type initScaffolder struct { + config config.Config + boilerplatePath string + license string + owner string + + // fs is the filesystem that will be used by the scaffolder + fs machinery.Filesystem +} + +// NewInitScaffolder returns a new Scaffolder for project initialization operations +func NewInitScaffolder(config config.Config, license, owner string) plugins.Scaffolder { + return &initScaffolder{ + config: config, + boilerplatePath: hack.DefaultBoilerplatePath, + license: license, + owner: owner, + } +} + +// InjectFS implements cmdutil.Scaffolder +func (s *initScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder +func (s *initScaffolder) Scaffold() error { + fmt.Println("Writing scaffold for you to edit...") + + // Initialize the machinery.Scaffold that will write the boilerplate file to disk + // The boilerplate file needs to be scaffolded as a separate step as it is going to + // be used by the rest of the files, even those scaffolded in this command call. + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + ) + + bpFile := &hack.Boilerplate{ + License: s.license, + Owner: s.owner, + } + bpFile.Path = s.boilerplatePath + if err := scaffold.Execute(bpFile); err != nil { + return err + } + + boilerplate, err := afero.ReadFile(s.fs.FS, s.boilerplatePath) + if err != nil { + return err + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold = machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + ) + + // If the KustomizeV2 was used to do the scaffold then + // we need to ensure that we use its supported Kustomize Version + // in order to support it + kustomizeVersion = kustomizecommonv1.KustomizeVersion + kustomizev2 := kustomizecommonv2alpha.Plugin{} + gov4alpha := "go.kubebuilder.io/v4-alpha" + pluginKeyForKustomizeV2 := plugin.KeyFor(kustomizev2) + + for _, pluginKey := range s.config.GetPluginChain() { + if pluginKey == pluginKeyForKustomizeV2 || pluginKey == gov4alpha { + kustomizeVersion = kustomizecommonv2alpha.KustomizeVersion + break + } + } + + return scaffold.Execute( + &templates.Main{}, + &templates.GoMod{ + ControllerRuntimeVersion: ControllerRuntimeVersion, + }, + &templates.GitIgnore{}, + &templates.Makefile{ + Image: imageName, + BoilerplatePath: s.boilerplatePath, + ControllerToolsVersion: ControllerToolsVersion, + KustomizeVersion: kustomizeVersion, + ControllerRuntimeVersion: ControllerRuntimeVersion, + }, + &templates.Dockerfile{}, + &templates.DockerIgnore{}, + &templates.Readme{}, + ) +} diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go new file mode 100644 index 00000000000..8fd1c7640ca --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/group.go @@ -0,0 +1,80 @@ +/* +Copyright 2022 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 api + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Group{} + +// Group scaffolds the file that defines the registration methods for a certain group and version +type Group struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin +} + +// SetTemplateDefaults implements file.Template +func (f *Group) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + if f.Resource.Group != "" { + f.Path = filepath.Join("pkg", "apis", "%[group]", "%[version]", "groupversion_info.go") + } else { + f.Path = filepath.Join("pkg", "apis", "%[version]", "groupversion_info.go") + } + } else { + f.Path = filepath.Join("pkg", "api", "%[version]", "groupversion_info.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + f.TemplateBody = groupTemplate + + return nil +} + +//nolint:lll +const groupTemplate = `{{ .Boilerplate }} + +// Package {{ .Resource.Version }} contains API Schema definitions for the {{ .Resource.Group }} {{ .Resource.Version }} API group +//+kubebuilder:object:generate=true +//+groupName={{ .Resource.QualifiedGroup }} +package {{ .Resource.Version }} + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "{{ .Resource.QualifiedGroup }}", Version: "{{ .Resource.Version }}"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go new file mode 100644 index 00000000000..5513793212f --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/types.go @@ -0,0 +1,124 @@ +/* +Copyright 2022 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 api + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Types{} + +// Types scaffolds the file that defines the schema for a CRD +// nolint:maligned +type Types struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *Types) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + if f.Resource.Group != "" { + f.Path = filepath.Join("pkg", "apis", "%[group]", "%[version]", "%[kind]_types.go") + } else { + f.Path = filepath.Join("pkg", "apis", "%[version]", "%[kind]_types.go") + } + } else { + f.Path = filepath.Join("pkg", "api", "%[version]", "%[kind]_types.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + f.TemplateBody = typesTemplate + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } else { + f.IfExistsAction = machinery.Error + } + + return nil +} + +const typesTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Version }} + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }} +type {{ .Resource.Kind }}Spec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of {{ .Resource.Kind }}. Edit {{ lower .Resource.Kind }}_types.go to remove/update + Foo string ` + "`" + `json:"foo,omitempty"` + "`" + ` +} + +// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }} +type {{ .Resource.Kind }}Status struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +{{- if and (not .Resource.API.Namespaced) (not .Resource.IsRegularPlural) }} +//+kubebuilder:resource:path={{ .Resource.Plural }},scope=Cluster +{{- else if not .Resource.API.Namespaced }} +//+kubebuilder:resource:scope=Cluster +{{- else if not .Resource.IsRegularPlural }} +//+kubebuilder:resource:path={{ .Resource.Plural }} +{{- end }} + +// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API +type {{ .Resource.Kind }} struct { + metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` + metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` + + Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec,omitempty"` + "`" + ` + Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty"` + "`" + ` +} + +//+kubebuilder:object:root=true + +// {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }} +type {{ .Resource.Kind }}List struct { + metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` + metav1.ListMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` + Items []{{ .Resource.Kind }} ` + "`" + `json:"items"` + "`" + ` +} + +func init() { + SchemeBuilder.Register(&{{ .Resource.Kind }}{}, &{{ .Resource.Kind }}List{}) +} +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go new file mode 100644 index 00000000000..8030c309b38 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook.go @@ -0,0 +1,156 @@ +/* +Copyright 2022 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 api + +import ( + "fmt" + "path/filepath" + "strings" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Webhook{} + +// Webhook scaffolds the file that defines a webhook for a CRD or a builtin resource +type Webhook struct { // nolint:maligned + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + // Is the Group domain for the Resource replacing '.' with '-' + QualifiedGroupWithDash string + + // Define value for AdmissionReviewVersions marker + AdmissionReviewVersions string + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *Webhook) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + if f.Resource.Group != "" { + f.Path = filepath.Join("pkg", "apis", "%[group]", "%[version]", "%[kind]_webhook.go") + } else { + f.Path = filepath.Join("pkg", "apis", "%[version]", "%[kind]_webhook.go") + } + } else { + f.Path = filepath.Join("pkg", "api", "%[version]", "%[kind]_webhook.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + webhookTemplate := webhookTemplate + if f.Resource.HasDefaultingWebhook() { + webhookTemplate = webhookTemplate + defaultingWebhookTemplate + } + if f.Resource.HasValidationWebhook() { + webhookTemplate = webhookTemplate + validatingWebhookTemplate + } + f.TemplateBody = webhookTemplate + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } else { + f.IfExistsAction = machinery.Error + } + + f.AdmissionReviewVersions = "v1" + f.QualifiedGroupWithDash = strings.Replace(f.Resource.QualifiedGroup(), ".", "-", -1) + + return nil +} + +const ( + webhookTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Version }} + +import ( + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + {{- if .Resource.HasValidationWebhook }} + "k8s.io/apimachinery/pkg/runtime" + {{- end }} + {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }} + "sigs.k8s.io/controller-runtime/pkg/webhook" + {{- end }} +) + +// log is for logging in this package. +var {{ lower .Resource.Kind }}log = logf.Log.WithName("{{ lower .Resource.Kind }}-resource") + +func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +` + + //nolint:lll + defaultingWebhookTemplate = ` +//+kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/mutate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=true,failurePolicy=fail,sideEffects=None,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }} + +var _ webhook.Defaulter = &{{ .Resource.Kind }}{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *{{ .Resource.Kind }}) Default() { + {{ lower .Resource.Kind }}log.Info("default", "name", r.Name) + + // TODO(user): fill in your defaulting logic. +} +` + + //nolint:lll + validatingWebhookTemplate = ` +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//+kubebuilder:webhook:{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .Resource.Webhooks.WebhookVersion }}{{"}"}},{{ end }}path=/validate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=false,failurePolicy=fail,sideEffects=None,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={{ .AdmissionReviewVersions }} + +var _ webhook.Validator = &{{ .Resource.Kind }}{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *{{ .Resource.Kind }}) ValidateCreate() error { + {{ lower .Resource.Kind }}log.Info("validate create", "name", r.Name) + + // TODO(user): fill in your validation logic upon object creation. + return nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *{{ .Resource.Kind }}) ValidateUpdate(old runtime.Object) error { + {{ lower .Resource.Kind }}log.Info("validate update", "name", r.Name) + + // TODO(user): fill in your validation logic upon object update. + return nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *{{ .Resource.Kind }}) ValidateDelete() error { + {{ lower .Resource.Kind }}log.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil +} +` +) diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go new file mode 100644 index 00000000000..7503c93322b --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/api/webhook_suitetest.go @@ -0,0 +1,246 @@ +/* +Copyright 2022 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 api + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &WebhookSuite{} +var _ machinery.Inserter = &WebhookSuite{} + +// WebhookSuite scaffolds the file that sets up the webhook tests +type WebhookSuite struct { //nolint:maligned + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + // todo: currently is not possible to know if an API was or not scaffolded. We can fix it when #1826 be addressed + WireResource bool + + // BaseDirectoryRelativePath define the Path for the base directory when it is multigroup + BaseDirectoryRelativePath string +} + +// SetTemplateDefaults implements file.Template +func (f *WebhookSuite) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + if f.Resource.Group != "" { + f.Path = filepath.Join("pkg", "apis", "%[group]", "%[version]", "webhook_suite_test.go") + } else { + f.Path = filepath.Join("pkg", "apis", "%[version]", "webhook_suite_test.go") + } + } else { + f.Path = filepath.Join("pkg", "api", "%[version]", "webhook_suite_test.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + f.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplate, + machinery.NewMarkerFor(f.Path, importMarker), + admissionImportAlias, + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, addWebhookManagerMarker), + "%s", + "%d", + ) + + // If is multigroup the path needs to be ../../.. since it has the group dir. + f.BaseDirectoryRelativePath = `"..", ".."` + if f.MultiGroup && f.Resource.Group != "" { + f.BaseDirectoryRelativePath = `"..", "..",".."` + } + + return nil +} + +const ( + admissionImportAlias = "admissionv1" + admissionPath = "k8s.io/api/admission/v1" + importMarker = "imports" + addWebhookManagerMarker = "webhook" + addSchemeMarker = "scheme" +) + +// GetMarkers implements file.Inserter +func (f *WebhookSuite) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, addWebhookManagerMarker), + } +} + +const ( + apiImportCodeFragment = `%s "%s" +` + + addWebhookManagerCodeFragment = `err = (&%s{}).SetupWebhookWithManager(mgr) +Expect(err).NotTo(HaveOccurred()) + +` +) + +// GetCodeFragments implements file.Inserter +func (f *WebhookSuite) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 3) + + // Generate import code fragments + imports := make([]string, 0) + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, admissionImportAlias, admissionPath)) + + // Generate add scheme code fragments + addScheme := make([]string, 0) + + // Generate add webhookManager code fragments + addWebhookManager := make([]string, 0) + addWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragment, f.Resource.Kind)) + + // Only store code fragments in the map if the slices are non-empty + if len(addWebhookManager) != 0 { + fragments[machinery.NewMarkerFor(f.Path, addWebhookManagerMarker)] = addWebhookManager + } + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports + } + if len(addScheme) != 0 { + fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme + } + + return fragments +} + +const webhookTestSuiteTemplate = `{{ .Boilerplate }} + +package {{ .Resource.Version }} + +import ( + "context" + "path/filepath" + "testing" + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + %s + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Webhook Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "crd", "bases")}, + ErrorIfCRDPathMissing: {{ .WireResource }}, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "webhook")}, + }, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = %s.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + %s + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).NotTo(HaveOccurred()) + + %s + + go func() { + defer GinkgoRecover() + err = mgr.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := fmt.Sprintf("%s:%s", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + +}) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go new file mode 100644 index 00000000000..e0c0325057b --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller.go @@ -0,0 +1,119 @@ +/* +Copyright 2022 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 controllers + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Controller{} + +// Controller scaffolds the file that defines the controller for a CRD or a builtin resource +// nolint:maligned +type Controller struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + ControllerRuntimeVersion string + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *Controller) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("pkg", "controllers", "%[group]", "%[kind]_controller.go") + } else { + f.Path = filepath.Join("pkg", "controllers", "%[kind]_controller.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + f.TemplateBody = controllerTemplate + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } else { + f.IfExistsAction = machinery.Error + } + + return nil +} + +//nolint:lll +const controllerTemplate = `{{ .Boilerplate }} + +package {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ else }}controllers{{ end }} + +import ( + "context" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + {{ if not (isEmptyStr .Resource.Path) -}} + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" + {{- end }} +) + +// {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object +type {{ .Resource.Kind }}Reconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch +//+kubebuilder:rbac:groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }}/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 {{ .Resource.Kind }} 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@{{ .ControllerRuntimeVersion }}/pkg/reconcile +func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + {{ 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 + // For(). + {{- end }} + Complete(r) +} +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go new file mode 100644 index 00000000000..a05a0519f8f --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -0,0 +1,190 @@ +/* +Copyright 2022 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 controllers + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &SuiteTest{} +var _ machinery.Inserter = &SuiteTest{} + +// SuiteTest scaffolds the file that sets up the controller tests +// nolint:maligned +type SuiteTest struct { + machinery.TemplateMixin + machinery.MultiGroupMixin + machinery.BoilerplateMixin + machinery.ResourceMixin + + // CRDDirectoryRelativePath define the Path for the CRD + CRDDirectoryRelativePath string + + Force bool +} + +// SetTemplateDefaults implements file.Template +func (f *SuiteTest) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup && f.Resource.Group != "" { + f.Path = filepath.Join("pkg", "controllers", "%[group]", "suite_test.go") + } else { + f.Path = filepath.Join("pkg", "controllers", "suite_test.go") + } + } + + f.Path = f.Resource.Replacer().Replace(f.Path) + fmt.Println(f.Path) + + f.TemplateBody = fmt.Sprintf(controllerSuiteTestTemplate, + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + ) + + // If is multigroup the path needs to be ../../ since it has + // the group dir. + f.CRDDirectoryRelativePath = `".."` + if f.MultiGroup && f.Resource.Group != "" { + f.CRDDirectoryRelativePath = `"..", ".."` + } + + if f.Force { + f.IfExistsAction = machinery.OverwriteFile + } + + return nil +} + +const ( + importMarker = "imports" + addSchemeMarker = "scheme" +) + +// GetMarkers implements file.Inserter +func (f *SuiteTest) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + } +} + +const ( + apiImportCodeFragment = `%s "%s" +` + addschemeCodeFragment = `err = %s.AddToScheme(scheme.Scheme) +Expect(err).NotTo(HaveOccurred()) + +` +) + +// GetCodeFragments implements file.Inserter +func (f *SuiteTest) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 2) + + // Generate import code fragments + imports := make([]string, 0) + 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.Path != "" { + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) + } + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(f.Path, importMarker)] = imports + } + if len(addScheme) != 0 { + fragments[machinery.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme + } + + return fragments +} + +const controllerSuiteTestTemplate = `{{ .Boilerplate }} + +{{if and .MultiGroup .Resource.Group }} +package {{ .Resource.PackageName }} +{{else}} +package controllers +{{end}} + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + %s +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join({{ .CRDDirectoryRelativePath }}, "config", "crd", "bases")}, + ErrorIfCRDPathMissing: {{ .Resource.HasAPI }}, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + %s + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go new file mode 100644 index 00000000000..a345cc37d41 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerfile.go @@ -0,0 +1,74 @@ +/* +Copyright 2022 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 templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Dockerfile{} + +// Dockerfile scaffolds a file that defines the containerized build process +type Dockerfile struct { + machinery.TemplateMixin +} + +// SetTemplateDefaults implements file.Template +func (f *Dockerfile) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "Dockerfile" + } + + f.TemplateBody = dockerfileTemplate + + return nil +} + +const dockerfileTemplate = `# Build the manager binary +FROM golang:1.19 as builder +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY main.go main.go +COPY pkg/api/ pkg/api/ +COPY pkg/controllers/ pkg/controllers/ + +# Build +# the GOARCH has not a default value to allow the binary be built according to the host where the command +# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO +# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, +# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/manager . +USER 65532:65532 + +ENTRYPOINT ["/manager"] +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go new file mode 100644 index 00000000000..85f2d3f0f3e --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/dockerignore.go @@ -0,0 +1,45 @@ +/* +Copyright 2022 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 templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &DockerIgnore{} + +// DockerIgnore scaffolds a file that defines which files should be ignored by the containerized build process +type DockerIgnore struct { + machinery.TemplateMixin +} + +// SetTemplateDefaults implements file.Template +func (f *DockerIgnore) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = ".dockerignore" + } + + f.TemplateBody = dockerignorefileTemplate + + return nil +} + +const dockerignorefileTemplate = `# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore build and test binaries. +bin/ +testbin/ +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go new file mode 100644 index 00000000000..89931a4d111 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/gitignore.go @@ -0,0 +1,67 @@ +/* +Copyright 2022 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 templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &GitIgnore{} + +// GitIgnore scaffolds a file that defines which files should be ignored by git +type GitIgnore struct { + machinery.TemplateMixin +} + +// SetTemplateDefaults implements file.Template +func (f *GitIgnore) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = ".gitignore" + } + + f.TemplateBody = gitignoreTemplate + + return nil +} + +const gitignoreTemplate = ` +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin +testbin/* +Dockerfile.cross + +# Test binary, build with ` + "`go test -c`" + ` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Kubernetes Generated files - skip generated files, except for vendored files + +!vendor/**/zz_generated.* + +# editor and IDE paraphernalia +.idea +*.swp +*.swo +*~ +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go new file mode 100644 index 00000000000..c0fc6ec90e2 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/gomod.go @@ -0,0 +1,54 @@ +/* +Copyright 2022 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 templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &GoMod{} + +// GoMod scaffolds a file that defines the project dependencies +type GoMod struct { + machinery.TemplateMixin + machinery.RepositoryMixin + + ControllerRuntimeVersion string +} + +// SetTemplateDefaults implements file.Template +func (f *GoMod) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "go.mod" + } + + f.TemplateBody = goModTemplate + + f.IfExistsAction = machinery.OverwriteFile + + return nil +} + +const goModTemplate = ` +module {{ .Repo }} + +go 1.19 + +require ( + sigs.k8s.io/controller-runtime {{ .ControllerRuntimeVersion }} +) +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go new file mode 100644 index 00000000000..9907d615d65 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/hack/boilerplate.go @@ -0,0 +1,125 @@ +/* +Copyright 2022 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 hack + +import ( + "fmt" + "path/filepath" + "time" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +// DefaultBoilerplatePath is the default path to the boilerplate file +var DefaultBoilerplatePath = filepath.Join("hack", "boilerplate.go.txt") + +var _ machinery.Template = &Boilerplate{} + +// Boilerplate scaffolds a file that defines the common header for the rest of the files +type Boilerplate struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + + // License is the License type to write + License string + + // Licenses maps License types to their actual string + Licenses map[string]string + + // Owner is the copyright owner - e.g. "The Kubernetes Authors" + Owner string + + // Year is the copyright year + Year string +} + +// Validate implements file.RequiresValidation +func (f Boilerplate) Validate() error { + if f.License == "" { + // A default license will be set later + } else if _, found := knownLicenses[f.License]; found { + // One of the know licenses + } else if _, found := f.Licenses[f.License]; found { + // A map containing the requested license was also provided + } else { + return fmt.Errorf("unknown specified license %s", f.License) + } + + return nil +} + +// SetTemplateDefaults implements file.Template +func (f *Boilerplate) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = DefaultBoilerplatePath + } + + if f.License == "" { + f.License = "apache2" + } + + if f.Licenses == nil { + f.Licenses = make(map[string]string, len(knownLicenses)) + } + + for key, value := range knownLicenses { + if _, hasLicense := f.Licenses[key]; !hasLicense { + f.Licenses[key] = value + } + } + + if f.Year == "" { + f.Year = fmt.Sprintf("%v", time.Now().Year()) + } + + // Boilerplate given + if len(f.Boilerplate) > 0 { + f.TemplateBody = f.Boilerplate + return nil + } + + f.TemplateBody = boilerplateTemplate + + return nil +} + +const boilerplateTemplate = `/* +{{ if .Owner -}} +Copyright {{ .Year }} {{ .Owner }}. +{{- else -}} +Copyright {{ .Year }}. +{{- end }} +{{ index .Licenses .License }}*/` + +var knownLicenses = map[string]string{ + "apache2": apache2, + "none": "", +} + +const apache2 = ` +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. +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go new file mode 100644 index 00000000000..6989200637b --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/main.go @@ -0,0 +1,295 @@ +/* +Copyright 2022 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 templates + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +const defaultMainPath = "main.go" + +var _ machinery.Template = &Main{} + +// Main scaffolds a file that defines the controller manager entry point +type Main struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.DomainMixin + machinery.RepositoryMixin + machinery.ComponentConfigMixin +} + +// SetTemplateDefaults implements file.Template +func (f *Main) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = filepath.Join(defaultMainPath) + } + + f.TemplateBody = fmt.Sprintf(mainTemplate, + machinery.NewMarkerFor(f.Path, importMarker), + machinery.NewMarkerFor(f.Path, addSchemeMarker), + machinery.NewMarkerFor(f.Path, setupMarker), + ) + + return nil +} + +var _ machinery.Inserter = &MainUpdater{} + +// MainUpdater updates main.go to run Controllers +type MainUpdater struct { //nolint:maligned + machinery.RepositoryMixin + machinery.MultiGroupMixin + machinery.ResourceMixin + + // Flags to indicate which parts need to be included when updating the file + WireResource, WireController, WireWebhook bool +} + +// GetPath implements file.Builder +func (*MainUpdater) GetPath() string { + return defaultMainPath +} + +// GetIfExistsAction implements file.Builder +func (*MainUpdater) GetIfExistsAction() machinery.IfExistsAction { + return machinery.OverwriteFile +} + +const ( + importMarker = "imports" + addSchemeMarker = "scheme" + setupMarker = "builder" +) + +// GetMarkers implements file.Inserter +func (f *MainUpdater) GetMarkers() []machinery.Marker { + return []machinery.Marker{ + machinery.NewMarkerFor(defaultMainPath, importMarker), + machinery.NewMarkerFor(defaultMainPath, addSchemeMarker), + machinery.NewMarkerFor(defaultMainPath, setupMarker), + } +} + +const ( + apiImportCodeFragment = `%s "%s" +` + controllerImportCodeFragment = `"%s/pkg/controllers" +` + multiGroupControllerImportCodeFragment = `%scontrollers "%s/pkg/controllers/%s" +` + addschemeCodeFragment = `utilruntime.Must(%s.AddToScheme(scheme)) +` + reconcilerSetupCodeFragment = `if err = (&controllers.%sReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "%s") + os.Exit(1) + } +` + multiGroupReconcilerSetupCodeFragment = `if err = (&%scontrollers.%sReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "%s") + os.Exit(1) + } +` + webhookSetupCodeFragment = `if err = (&%s.%s{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "%s") + os.Exit(1) + } +` +) + +// GetCodeFragments implements file.Inserter +func (f *MainUpdater) GetCodeFragments() machinery.CodeFragmentsMap { + fragments := make(machinery.CodeFragmentsMap, 3) + + // If resource is not being provided we are creating the file, not updating it + if f.Resource == nil { + return fragments + } + + // Generate import code fragments + imports := make([]string, 0) + if f.WireResource { + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) + } + + if f.WireController { + if !f.MultiGroup || f.Resource.Group == "" { + imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo)) + } else { + imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment, + f.Resource.PackageName(), f.Repo, f.Resource.Group)) + } + } + + // Generate add scheme code fragments + addScheme := make([]string, 0) + if f.WireResource { + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) + } + + // Generate setup code fragments + setup := make([]string, 0) + if f.WireController { + if !f.MultiGroup || f.Resource.Group == "" { + setup = append(setup, fmt.Sprintf(reconcilerSetupCodeFragment, + f.Resource.Kind, f.Resource.Kind)) + } else { + setup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment, + f.Resource.PackageName(), f.Resource.Kind, f.Resource.Kind)) + } + } + if f.WireWebhook { + setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, + f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) + } + + // Only store code fragments in the map if the slices are non-empty + if len(imports) != 0 { + fragments[machinery.NewMarkerFor(defaultMainPath, importMarker)] = imports + } + if len(addScheme) != 0 { + fragments[machinery.NewMarkerFor(defaultMainPath, addSchemeMarker)] = addScheme + } + if len(setup) != 0 { + fragments[machinery.NewMarkerFor(defaultMainPath, setupMarker)] = setup + } + + return fragments +} + +var mainTemplate = `{{ .Boilerplate }} + +package main + +import ( + "flag" + "os" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/healthz" + %s +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + %s +} + +func main() { +{{- if not .ComponentConfig }} + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. " + + "Enabling this will ensure there is only one active controller manager.") +{{- else }} + var configFile string + flag.StringVar(&configFile, "config", "", + "The controller will load its initial configuration from this file. " + + "Omit this flag to use the default configuration values. " + + "Command-line flags override configuration from this file.") +{{- end }} + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + +{{ if not .ComponentConfig }} + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: metricsAddr, + Port: 9443, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "{{ hashFNV .Repo }}.{{ .Domain }}", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) +{{- else }} + var err error + options := ctrl.Options{Scheme: scheme} + if configFile != "" { + options, err = options.AndFrom(ctrl.ConfigFile().AtPath(configFile)) + if err != nil { + setupLog.Error(err, "unable to load the config file") + os.Exit(1) + } + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options) +{{- end }} + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + %s + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go new file mode 100644 index 00000000000..4aedf7fb6d9 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/makefile.go @@ -0,0 +1,212 @@ +/* +Copyright 2022 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 templates + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Makefile{} + +// Makefile scaffolds a file that defines project management CLI commands +type Makefile struct { + machinery.TemplateMixin + machinery.ComponentConfigMixin + machinery.ProjectNameMixin + + // Image is controller manager image name + Image string + // BoilerplatePath is the path to the boilerplate file + BoilerplatePath string + // Controller tools version to use in the project + ControllerToolsVersion string + // Kustomize version to use in the project + KustomizeVersion string + // ControllerRuntimeVersion version to be used to download the envtest setup script + ControllerRuntimeVersion string +} + +// SetTemplateDefaults implements file.Template +func (f *Makefile) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "Makefile" + } + + f.TemplateBody = makefileTemplate + + f.IfExistsAction = machinery.Error + + if f.Image == "" { + f.Image = "controller:latest" + } + + return nil +} + +//nolint:lll +const makefileTemplate = ` +# Image URL to use all building/pushing image targets +IMG ?= {{ .Image }} +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.25.0 + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +.PHONY: all +all: build + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: manifests +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..." + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: test +test: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out + +##@ Build + +.PHONY: build +build: generate fmt vet ## Build manager binary. + go build -o bin/manager main.go + +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + go run ./main.go + +# If you wish built the manager image targeting other platforms you can use the --platform flag. +# (i.e. docker build --platform linux/arm64 ). However, you must enable docker buildKit for it. +# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +.PHONY: docker-build +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/ +# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> than the export will fail) +# To properly provided solutions that supports more than one platform you should use this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: test ## Build and push docker image for the manager for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - docker buildx create --name project-v3-builder + docker buildx use project-v3-builder + - docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross + - docker buildx rm project-v3-builder + rm Dockerfile.cross + +##@ Deployment + +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | kubectl apply -f - + +.PHONY: undeploy +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +##@ Build Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +KUSTOMIZE ?= $(LOCALBIN)/kustomize +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +ENVTEST ?= $(LOCALBIN)/setup-envtest + +## Tool Versions +KUSTOMIZE_VERSION ?= {{ .KustomizeVersion }} +CONTROLLER_TOOLS_VERSION ?= {{ .ControllerToolsVersion }} + +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. +$(KUSTOMIZE): $(LOCALBIN) + test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kustomize/kustomize/v4@$(KUSTOMIZE_VERSION) + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. +$(CONTROLLER_GEN): $(LOCALBIN) + test -s $(LOCALBIN)/controller-gen || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) + +.PHONY: envtest +envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. +$(ENVTEST): $(LOCALBIN) + test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest +` diff --git a/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go new file mode 100644 index 00000000000..64d541bf170 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/internal/templates/readme.go @@ -0,0 +1,129 @@ +/* +Copyright 2022 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 templates + +import ( + "fmt" + "strings" + + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" +) + +var _ machinery.Template = &Readme{} + +// Readme scaffolds a README.md file +type Readme struct { + machinery.TemplateMixin + machinery.BoilerplateMixin + machinery.ProjectNameMixin + + License string +} + +// SetTemplateDefaults implements file.Template +func (f *Readme) SetTemplateDefaults() error { + if f.Path == "" { + f.Path = "README.md" + } + + f.License = strings.Replace( + strings.Replace(f.Boilerplate, "/*", "", 1), + "*/", "", 1) + + f.TemplateBody = fmt.Sprintf(readmeFileTemplate, + codeFence("kubectl apply -f config/samples/"), + codeFence("make docker-build docker-push IMG=/{{ .ProjectName }}:tag"), + codeFence("make deploy IMG=/{{ .ProjectName }}:tag"), + codeFence("make uninstall"), + codeFence("make undeploy"), + codeFence("make install"), + codeFence("make run"), + codeFence("make manifests")) + + return nil +} + +//nolint:lll +const readmeFileTemplate = `# {{ .ProjectName }} +// TODO(user): Add simple overview of use/purpose + +## Description +// TODO(user): An in-depth paragraph about your project and overview of use + +## Getting Started +You’ll need a Kubernetes cluster to run against. You can use [KIND](https://sigs.k8s.io/kind) to get a local cluster for testing, or run against a remote cluster. +**Note:** Your controller will automatically use the current context in your kubeconfig file (i.e. whatever cluster ` + "`kubectl cluster-info`" + ` shows). + +### Running on the cluster +1. Install Instances of Custom Resources: + +%s + +2. Build and push your image to the location specified by ` + "`IMG`" + `: + +%s + +3. Deploy the controller to the cluster with the image specified by ` + "`IMG`" + `: + +%s + +### Uninstall CRDs +To delete the CRDs from the cluster: + +%s + +### Undeploy controller +UnDeploy the controller to the cluster: + +%s + +## Contributing +// TODO(user): Add detailed information on how you would like others to contribute to this project + +### How it works +This project aims to follow the Kubernetes [Operator pattern](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) + +It uses [Controllers](https://kubernetes.io/docs/concepts/architecture/controller/) +which provides a reconcile function responsible for synchronizing resources untile the desired state is reached on the cluster + +### Test It Out +1. Install the CRDs into the cluster: + +%s + +2. Run your controller (this will run in the foreground, so switch to a new terminal if you want to leave it running): + +%s + +**NOTE:** You can also run this in one step by running: ` + "`make install run`" + ` + +### Modifying the API definitions +If you are editing the API definitions, generate the manifests such as CRs or CRDs using: + +%s + +**NOTE:** Run ` + "`make --help`" + ` for more information on all potential ` + "`make`" + ` targets + +More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html) + +## License +{{ .License }} +` + +func codeFence(code string) string { + return "```sh" + "\n" + code + "\n" + "```" +} diff --git a/pkg/plugins/golang/v4/scaffolds/webhook.go b/pkg/plugins/golang/v4/scaffolds/webhook.go new file mode 100644 index 00000000000..029f75ee5e5 --- /dev/null +++ b/pkg/plugins/golang/v4/scaffolds/webhook.go @@ -0,0 +1,108 @@ +/* +Copyright 2022 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 scaffolds + +import ( + "fmt" + + "github.com/spf13/afero" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/api" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds/internal/templates/hack" +) + +var _ plugins.Scaffolder = &webhookScaffolder{} + +type webhookScaffolder struct { + config config.Config + resource resource.Resource + + // fs is the filesystem that will be used by the scaffolder + fs machinery.Filesystem + + // force indicates whether to scaffold controller files even if it exists or not + force bool +} + +// NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations +func NewWebhookScaffolder(config config.Config, resource resource.Resource, force bool) plugins.Scaffolder { + return &webhookScaffolder{ + config: config, + resource: resource, + force: force, + } +} + +// InjectFS implements cmdutil.Scaffolder +func (s *webhookScaffolder) InjectFS(fs machinery.Filesystem) { + s.fs = fs +} + +// Scaffold implements cmdutil.Scaffolder +func (s *webhookScaffolder) Scaffold() error { + fmt.Println("Writing scaffold for you to edit...") + + // Load the boilerplate + boilerplate, err := afero.ReadFile(s.fs.FS, hack.DefaultBoilerplatePath) + if err != nil { + return fmt.Errorf("error scaffolding webhook: unable to load boilerplate: %w", err) + } + + // Initialize the machinery.Scaffold that will write the files to disk + scaffold := machinery.NewScaffold(s.fs, + machinery.WithConfig(s.config), + machinery.WithBoilerplate(string(boilerplate)), + machinery.WithResource(&s.resource), + ) + + // Keep track of these values before the update + doDefaulting := s.resource.HasDefaultingWebhook() + doValidation := s.resource.HasValidationWebhook() + doConversion := s.resource.HasConversionWebhook() + + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } + + if err := scaffold.Execute( + &api.Webhook{Force: s.force}, + &templates.MainUpdater{WireWebhook: true}, + ); err != nil { + return err + } + + if doConversion { + fmt.Println(`Webhook server has been set up for you. +You need to implement the conversion.Hub and conversion.Convertible interfaces for your CRD types.`) + } + + // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest. + if doDefaulting || doValidation { + if err := scaffold.Execute( + &api.WebhookSuite{}, + ); err != nil { + return err + } + } + + return nil +} diff --git a/pkg/plugins/golang/v4/webhook.go b/pkg/plugins/golang/v4/webhook.go new file mode 100644 index 00000000000..c720eebf5af --- /dev/null +++ b/pkg/plugins/golang/v4/webhook.go @@ -0,0 +1,133 @@ +/* +Copyright 2022 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 v4 + +import ( + "fmt" + + "github.com/spf13/pflag" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + pluginutil "sigs.k8s.io/kubebuilder/v3/pkg/plugin/util" + goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v4/scaffolds" +) + +// defaultWebhookVersion is the default mutating/validating webhook config API version to scaffold. +const defaultWebhookVersion = "v1" + +var _ plugin.CreateWebhookSubcommand = &createWebhookSubcommand{} + +type createWebhookSubcommand struct { + config config.Config + // For help text. + commandName string + + options *goPlugin.Options + + resource *resource.Resource + + // force indicates that the resource should be created even if it already exists + force bool +} + +func (p *createWebhookSubcommand) UpdateMetadata(cliMeta plugin.CLIMetadata, subcmdMeta *plugin.SubcommandMetadata) { + p.commandName = cliMeta.CommandName + + subcmdMeta.Description = `Scaffold a webhook for an API resource. You can choose to scaffold defaulting, +validating and/or conversion webhooks. +` + subcmdMeta.Examples = fmt.Sprintf(` # Create defaulting and validating webhooks for Group: ship, Version: v1beta1 + # and Kind: Frigate + %[1]s create webhook --group ship --version v1beta1 --kind Frigate --defaulting --programmatic-validation + + # Create conversion webhook for Group: ship, Version: v1beta1 + # and Kind: Frigate + %[1]s create webhook --group ship --version v1beta1 --kind Frigate --conversion +`, cliMeta.CommandName) +} + +func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { + p.options = &goPlugin.Options{} + + fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form") + + fs.BoolVar(&p.options.DoDefaulting, "defaulting", false, + "if set, scaffold the defaulting webhook") + fs.BoolVar(&p.options.DoValidation, "programmatic-validation", false, + "if set, scaffold the validating webhook") + fs.BoolVar(&p.options.DoConversion, "conversion", false, + "if set, scaffold the conversion webhook") + + fs.BoolVar(&p.force, "force", false, + "attempt to create resource even if it already exists") +} + +func (p *createWebhookSubcommand) InjectConfig(c config.Config) error { + p.config = c + // go/v4 no longer supports v1beta1 option + p.options.WebhookVersion = defaultWebhookVersion + return nil +} + +func (p *createWebhookSubcommand) InjectResource(res *resource.Resource) error { + p.resource = res + + p.options.UpdateResource(p.resource, p.config, false) + + if err := p.resource.Validate(); err != nil { + return err + } + + if !p.resource.HasDefaultingWebhook() && !p.resource.HasValidationWebhook() && !p.resource.HasConversionWebhook() { + return fmt.Errorf("%s create webhook requires at least one of --defaulting,"+ + " --programmatic-validation and --conversion to be true", p.commandName) + } + + // check if resource exist to create webhook + 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 fmt.Errorf("webhook resource already exists") + } + + return nil +} + +func (p *createWebhookSubcommand) Scaffold(fs machinery.Filesystem) error { + scaffolder := scaffolds.NewWebhookScaffolder(p.config, *p.resource, p.force) + scaffolder.InjectFS(fs) + return scaffolder.Scaffold() +} + +func (p *createWebhookSubcommand) PostScaffold() error { + err := pluginutil.RunCmd("Update dependencies", "go", "mod", "tidy") + if err != nil { + return err + } + + err = pluginutil.RunCmd("Running make", "make", "generate") + if err != nil { + return err + } + fmt.Print("Next: implement your new Webhook and generate the manifests with:\n$ make manifests\n") + + return nil +} diff --git a/testdata/project-v3-addon-and-grafana/Dockerfile b/testdata/project-v3-addon-and-grafana/Dockerfile index c4733c56e1b..35222c26d4f 100644 --- a/testdata/project-v3-addon-and-grafana/Dockerfile +++ b/testdata/project-v3-addon-and-grafana/Dockerfile @@ -32,9 +32,6 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o ma FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/manager . -# copy channels -COPY --from=builder /channels /channels - USER 65532:65532 ENTRYPOINT ["/manager"] diff --git a/testdata/project-v3-with-deploy-image/controllers/busybox_controller.go b/testdata/project-v3-with-deploy-image/controllers/busybox_controller.go index 287af79026d..d2e2faeaba0 100644 --- a/testdata/project-v3-with-deploy-image/controllers/busybox_controller.go +++ b/testdata/project-v3-with-deploy-image/controllers/busybox_controller.go @@ -18,417 +18,45 @@ package controllers import ( "context" - "fmt" - "os" - "strings" - "time" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v3-with-deploy-image/api/v1alpha1" ) -const busyboxFinalizer = "example.com.testproject.org/finalizer" - -// Definitions to manage status conditions -const ( - // typeAvailableBusybox represents the status of the Deployment reconciliation - typeAvailableBusybox = "Available" - // typeDegradedBusybox represents the status used when the custom resource is deleted and the finalizer operations are must to occur. - typeDegradedBusybox = "Degraded" -) - // BusyboxReconciler reconciles a Busybox object type BusyboxReconciler struct { client.Client - Scheme *runtime.Scheme - Recorder record.EventRecorder + Scheme *runtime.Scheme } -// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen -// when the command is executed. -// To know more about markers see: https://book.kubebuilder.io/reference/markers.html - //+kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes/status,verbs=get;update;patch //+kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes/finalizers,verbs=update -//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch -//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. - -// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator -// pattern you will create Controllers which provide a reconcile function -// responsible for synchronizing resources until the desired state is reached on the cluster. -// Breaking this recommendation goes against the design principles of controller-runtime. -// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention. -// For further info: -// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/ -// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/ +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Busybox 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.13.0/pkg/reconcile func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := log.FromContext(ctx) - - // Fetch the Busybox instance - // The purpose is check if the Custom Resource for the Kind Busybox - // is applied on the cluster if not we return nil to stop the reconciliation - busybox := &examplecomv1alpha1.Busybox{} - err := r.Get(ctx, req.NamespacedName, busybox) - if err != nil { - if apierrors.IsNotFound(err) { - // If the custom resource is not found then, it usually means that it was deleted or not created - // In this way, we will stop the reconciliation - log.Info("busybox resource not found. Ignoring since object must be deleted") - return ctrl.Result{}, nil - } - // Error reading the object - requeue the request. - log.Error(err, "Failed to get busybox") - return ctrl.Result{}, err - } - - // Let's just set the status as Unknown when no status are available - if busybox.Status.Conditions == nil || len(busybox.Status.Conditions) == 0 { - meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) - if err = r.Status().Update(ctx, busybox); err != nil { - log.Error(err, "Failed to update Busybox status") - return ctrl.Result{}, err - } - - // Let's re-fetch the busybox Custom Resource after update the status - // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply - // your changes to the latest version and try again" which would re-trigger the reconciliation - // if we try to update it again in the following operations - if err := r.Get(ctx, req.NamespacedName, busybox); err != nil { - log.Error(err, "Failed to re-fetch busybox") - return ctrl.Result{}, err - } - } - - // Let's add a finalizer. Then, we can define some operations which should - // occurs before the custom resource to be deleted. - // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers - if !controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) { - log.Info("Adding Finalizer for Busybox") - if ok := controllerutil.AddFinalizer(busybox, busyboxFinalizer); !ok { - log.Error(err, "Failed to add finalizer into the custom resource") - return ctrl.Result{Requeue: true}, nil - } - - if err = r.Update(ctx, busybox); err != nil { - log.Error(err, "Failed to update custom resource to add finalizer") - return ctrl.Result{}, err - } - } - - // Check if the Busybox instance is marked to be deleted, which is - // indicated by the deletion timestamp being set. - isBusyboxMarkedToBeDeleted := busybox.GetDeletionTimestamp() != nil - if isBusyboxMarkedToBeDeleted { - if controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) { - log.Info("Performing Finalizer Operations for Busybox before delete CR") - - // Let's add here an status "Downgrade" to define that this resource begin its process to be terminated. - meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeDegradedBusybox, - Status: metav1.ConditionUnknown, Reason: "Finalizing", - Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", busybox.Name)}) - - if err := r.Status().Update(ctx, busybox); err != nil { - log.Error(err, "Failed to update Busybox status") - return ctrl.Result{}, err - } - - // Perform all operations required before remove the finalizer and allow - // the Kubernetes API to remove the custom resource. - r.doFinalizerOperationsForBusybox(busybox) - - // TODO(user): If you add operations to the doFinalizerOperationsForBusybox method - // then you need to ensure that all worked fine before deleting and updating the Downgrade status - // otherwise, you should requeue here. - - // Re-fetch the busybox Custom Resource before update the status - // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply - // your changes to the latest version and try again" which would re-trigger the reconciliation - if err := r.Get(ctx, req.NamespacedName, busybox); err != nil { - log.Error(err, "Failed to re-fetch busybox") - return ctrl.Result{}, err - } - - meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeDegradedBusybox, - Status: metav1.ConditionTrue, Reason: "Finalizing", - Message: fmt.Sprintf("Finalizer operations for custom resource %s name were successfully accomplished", busybox.Name)}) - - if err := r.Status().Update(ctx, busybox); err != nil { - log.Error(err, "Failed to update Busybox status") - return ctrl.Result{}, err - } - - log.Info("Removing Finalizer for Busybox after successfully perform the operations") - if ok := controllerutil.RemoveFinalizer(busybox, busyboxFinalizer); !ok { - log.Error(err, "Failed to remove finalizer for Busybox") - return ctrl.Result{Requeue: true}, nil - } - - if err := r.Update(ctx, busybox); err != nil { - log.Error(err, "Failed to remove finalizer for Busybox") - return ctrl.Result{}, err - } - } - return ctrl.Result{}, nil - } - - // Check if the deployment already exists, if not create a new one - found := &appsv1.Deployment{} - err = r.Get(ctx, types.NamespacedName{Name: busybox.Name, Namespace: busybox.Namespace}, found) - if err != nil && apierrors.IsNotFound(err) { - // Define a new deployment - dep, err := r.deploymentForBusybox(busybox) - if err != nil { - log.Error(err, "Failed to define new Deployment resource for Busybox") - - // The following implementation will update the status - meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, - Status: metav1.ConditionFalse, Reason: "Reconciling", - Message: fmt.Sprintf("Failed to create Deployment for the custom resource (%s): (%s)", busybox.Name, err)}) - - if err := r.Status().Update(ctx, busybox); err != nil { - log.Error(err, "Failed to update Busybox status") - return ctrl.Result{}, err - } + _ = log.FromContext(ctx) - return ctrl.Result{}, err - } - - log.Info("Creating a new Deployment", - "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) - if err = r.Create(ctx, dep); err != nil { - log.Error(err, "Failed to create new Deployment", - "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) - return ctrl.Result{}, err - } - - // Deployment created successfully - // We will requeue the reconciliation so that we can ensure the state - // and move forward for the next operations - return ctrl.Result{RequeueAfter: time.Minute}, nil - } else if err != nil { - log.Error(err, "Failed to get Deployment") - // Let's return the error for the reconciliation be re-trigged again - return ctrl.Result{}, err - } - - // The CRD API is defining that the Busybox type, have a BusyboxSpec.Size field - // to set the quantity of Deployment instances is the desired state on the cluster. - // Therefore, the following code will ensure the Deployment size is the same as defined - // via the Size spec of the Custom Resource which we are reconciling. - size := busybox.Spec.Size - if *found.Spec.Replicas != size { - found.Spec.Replicas = &size - if err = r.Update(ctx, found); err != nil { - log.Error(err, "Failed to update Deployment", - "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) - - // Re-fetch the busybox Custom Resource before update the status - // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply - // your changes to the latest version and try again" which would re-trigger the reconciliation - if err := r.Get(ctx, req.NamespacedName, busybox); err != nil { - log.Error(err, "Failed to re-fetch busybox") - return ctrl.Result{}, err - } - - // The following implementation will update the status - meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, - Status: metav1.ConditionFalse, Reason: "Resizing", - Message: fmt.Sprintf("Failed to update the size for the custom resource (%s): (%s)", busybox.Name, err)}) - - if err := r.Status().Update(ctx, busybox); err != nil { - log.Error(err, "Failed to update Busybox status") - return ctrl.Result{}, err - } - - return ctrl.Result{}, err - } - - // Now, that we update the size we want to requeue the reconciliation - // so that we can ensure that we have the latest state of the resource before - // update. Also, it will help ensure the desired state on the cluster - return ctrl.Result{Requeue: true}, nil - } - - // The following implementation will update the status - meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, - Status: metav1.ConditionTrue, Reason: "Reconciling", - Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", busybox.Name, size)}) - - if err := r.Status().Update(ctx, busybox); err != nil { - log.Error(err, "Failed to update Busybox status") - return ctrl.Result{}, err - } + // TODO(user): your logic here return ctrl.Result{}, nil } -// finalizeBusybox will perform the required operations before delete the CR. -func (r *BusyboxReconciler) doFinalizerOperationsForBusybox(cr *examplecomv1alpha1.Busybox) { - // TODO(user): Add the cleanup steps that the operator - // needs to do before the CR can be deleted. Examples - // of finalizers include performing backups and deleting - // resources that are not owned by this CR, like a PVC. - - // Note: It is not recommended to use finalizers with the purpose of delete resources which are - // created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile, - // are defined as depended of the custom resource. See that we use the method ctrl.SetControllerReference. - // to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API. - // More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/ - - // The following implementation will raise an event - r.Recorder.Event(cr, "Warning", "Deleting", - fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s", - cr.Name, - cr.Namespace)) -} - -// deploymentForBusybox returns a Busybox Deployment object -func (r *BusyboxReconciler) deploymentForBusybox( - busybox *examplecomv1alpha1.Busybox) (*appsv1.Deployment, error) { - ls := labelsForBusybox(busybox.Name) - replicas := busybox.Spec.Size - - // Get the Operand image - image, err := imageForBusybox() - if err != nil { - return nil, err - } - - dep := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: busybox.Name, - Namespace: busybox.Namespace, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: ls, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: ls, - }, - Spec: corev1.PodSpec{ - // TODO(user): Uncomment the following code to configure the nodeAffinity expression - // according to the platforms which are supported by your solution. It is considered - // best practice to support multiple architectures. build your manager image using the - // makefile target docker-buildx. Also, you can use docker manifest inspect - // to check what are the platforms supported. - // More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity - //Affinity: &corev1.Affinity{ - // NodeAffinity: &corev1.NodeAffinity{ - // RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ - // NodeSelectorTerms: []corev1.NodeSelectorTerm{ - // { - // MatchExpressions: []corev1.NodeSelectorRequirement{ - // { - // Key: "kubernetes.io/arch", - // Operator: "In", - // Values: []string{"amd64", "arm64", "ppc64le", "s390x"}, - // }, - // { - // Key: "kubernetes.io/os", - // Operator: "In", - // Values: []string{"linux"}, - // }, - // }, - // }, - // }, - // }, - // }, - //}, - SecurityContext: &corev1.PodSecurityContext{ - RunAsNonRoot: &[]bool{true}[0], - // IMPORTANT: seccomProfile was introduced with Kubernetes 1.19 - // If you are looking for to produce solutions to be supported - // on lower versions you must remove this option. - SeccompProfile: &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - }, - }, - Containers: []corev1.Container{{ - Image: image, - Name: "busybox", - ImagePullPolicy: corev1.PullIfNotPresent, - // Ensure restrictive context for the container - // More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted - SecurityContext: &corev1.SecurityContext{ - RunAsNonRoot: &[]bool{true}[0], - AllowPrivilegeEscalation: &[]bool{false}[0], - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{ - "ALL", - }, - }, - }, - }}, - }, - }, - }, - } - - // Set the ownerRef for the Deployment - // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/ - if err := ctrl.SetControllerReference(busybox, dep, r.Scheme); err != nil { - return nil, err - } - return dep, nil -} - -// labelsForBusybox returns the labels for selecting the resources -// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ -func labelsForBusybox(name string) map[string]string { - var imageTag string - image, err := imageForBusybox() - if err == nil { - imageTag = strings.Split(image, ":")[1] - } - return map[string]string{"app.kubernetes.io/name": "Busybox", - "app.kubernetes.io/instance": name, - "app.kubernetes.io/version": imageTag, - "app.kubernetes.io/part-of": "project-v3-with-deploy-image", - "app.kubernetes.io/created-by": "controller-manager", - } -} - -// imageForBusybox gets the Operand image which is managed by this controller -// from the BUSYBOX_IMAGE environment variable defined in the config/manager/manager.yaml -func imageForBusybox() (string, error) { - var imageEnvVar = "BUSYBOX_IMAGE" - image, found := os.LookupEnv(imageEnvVar) - if !found { - return "", fmt.Errorf("Unable to find %s environment variable with the image", imageEnvVar) - } - return image, nil -} - // SetupWithManager sets up the controller with the Manager. -// Note that the Deployment will be also watched in order to ensure its -// desirable state on the cluster func (r *BusyboxReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&examplecomv1alpha1.Busybox{}). - Owns(&appsv1.Deployment{}). Complete(r) } diff --git a/testdata/project-v3-with-deploy-image/controllers/memcached_controller.go b/testdata/project-v3-with-deploy-image/controllers/memcached_controller.go index 4df4ae3ea4f..01097262b28 100644 --- a/testdata/project-v3-with-deploy-image/controllers/memcached_controller.go +++ b/testdata/project-v3-with-deploy-image/controllers/memcached_controller.go @@ -18,423 +18,45 @@ package controllers import ( "context" - "fmt" - "os" - "strings" - "time" - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v3-with-deploy-image/api/v1alpha1" ) -const memcachedFinalizer = "example.com.testproject.org/finalizer" - -// Definitions to manage status conditions -const ( - // typeAvailableMemcached represents the status of the Deployment reconciliation - typeAvailableMemcached = "Available" - // typeDegradedMemcached represents the status used when the custom resource is deleted and the finalizer operations are must to occur. - typeDegradedMemcached = "Degraded" -) - // MemcachedReconciler reconciles a Memcached object type MemcachedReconciler struct { client.Client - Scheme *runtime.Scheme - Recorder record.EventRecorder + Scheme *runtime.Scheme } -// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen -// when the command is executed. -// To know more about markers see: https://book.kubebuilder.io/reference/markers.html - //+kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds/status,verbs=get;update;patch //+kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds/finalizers,verbs=update -//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch -//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. - -// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator -// pattern you will create Controllers which provide a reconcile function -// responsible for synchronizing resources until the desired state is reached on the cluster. -// Breaking this recommendation goes against the design principles of controller-runtime. -// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention. -// For further info: -// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/ -// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/ +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Memcached 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.13.0/pkg/reconcile func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := log.FromContext(ctx) - - // Fetch the Memcached instance - // The purpose is check if the Custom Resource for the Kind Memcached - // is applied on the cluster if not we return nil to stop the reconciliation - memcached := &examplecomv1alpha1.Memcached{} - err := r.Get(ctx, req.NamespacedName, memcached) - if err != nil { - if apierrors.IsNotFound(err) { - // If the custom resource is not found then, it usually means that it was deleted or not created - // In this way, we will stop the reconciliation - log.Info("memcached resource not found. Ignoring since object must be deleted") - return ctrl.Result{}, nil - } - // Error reading the object - requeue the request. - log.Error(err, "Failed to get memcached") - return ctrl.Result{}, err - } - - // Let's just set the status as Unknown when no status are available - if memcached.Status.Conditions == nil || len(memcached.Status.Conditions) == 0 { - meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) - if err = r.Status().Update(ctx, memcached); err != nil { - log.Error(err, "Failed to update Memcached status") - return ctrl.Result{}, err - } - - // Let's re-fetch the memcached Custom Resource after update the status - // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply - // your changes to the latest version and try again" which would re-trigger the reconciliation - // if we try to update it again in the following operations - if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { - log.Error(err, "Failed to re-fetch memcached") - return ctrl.Result{}, err - } - } - - // Let's add a finalizer. Then, we can define some operations which should - // occurs before the custom resource to be deleted. - // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers - if !controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { - log.Info("Adding Finalizer for Memcached") - if ok := controllerutil.AddFinalizer(memcached, memcachedFinalizer); !ok { - log.Error(err, "Failed to add finalizer into the custom resource") - return ctrl.Result{Requeue: true}, nil - } - - if err = r.Update(ctx, memcached); err != nil { - log.Error(err, "Failed to update custom resource to add finalizer") - return ctrl.Result{}, err - } - } - - // Check if the Memcached instance is marked to be deleted, which is - // indicated by the deletion timestamp being set. - isMemcachedMarkedToBeDeleted := memcached.GetDeletionTimestamp() != nil - if isMemcachedMarkedToBeDeleted { - if controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { - log.Info("Performing Finalizer Operations for Memcached before delete CR") - - // Let's add here an status "Downgrade" to define that this resource begin its process to be terminated. - meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached, - Status: metav1.ConditionUnknown, Reason: "Finalizing", - Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", memcached.Name)}) - - if err := r.Status().Update(ctx, memcached); err != nil { - log.Error(err, "Failed to update Memcached status") - return ctrl.Result{}, err - } - - // Perform all operations required before remove the finalizer and allow - // the Kubernetes API to remove the custom resource. - r.doFinalizerOperationsForMemcached(memcached) - - // TODO(user): If you add operations to the doFinalizerOperationsForMemcached method - // then you need to ensure that all worked fine before deleting and updating the Downgrade status - // otherwise, you should requeue here. - - // Re-fetch the memcached Custom Resource before update the status - // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply - // your changes to the latest version and try again" which would re-trigger the reconciliation - if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { - log.Error(err, "Failed to re-fetch memcached") - return ctrl.Result{}, err - } - - meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached, - Status: metav1.ConditionTrue, Reason: "Finalizing", - Message: fmt.Sprintf("Finalizer operations for custom resource %s name were successfully accomplished", memcached.Name)}) - - if err := r.Status().Update(ctx, memcached); err != nil { - log.Error(err, "Failed to update Memcached status") - return ctrl.Result{}, err - } - - log.Info("Removing Finalizer for Memcached after successfully perform the operations") - if ok := controllerutil.RemoveFinalizer(memcached, memcachedFinalizer); !ok { - log.Error(err, "Failed to remove finalizer for Memcached") - return ctrl.Result{Requeue: true}, nil - } - - if err := r.Update(ctx, memcached); err != nil { - log.Error(err, "Failed to remove finalizer for Memcached") - return ctrl.Result{}, err - } - } - return ctrl.Result{}, nil - } - - // Check if the deployment already exists, if not create a new one - found := &appsv1.Deployment{} - err = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found) - if err != nil && apierrors.IsNotFound(err) { - // Define a new deployment - dep, err := r.deploymentForMemcached(memcached) - if err != nil { - log.Error(err, "Failed to define new Deployment resource for Memcached") - - // The following implementation will update the status - meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, - Status: metav1.ConditionFalse, Reason: "Reconciling", - Message: fmt.Sprintf("Failed to create Deployment for the custom resource (%s): (%s)", memcached.Name, err)}) - - if err := r.Status().Update(ctx, memcached); err != nil { - log.Error(err, "Failed to update Memcached status") - return ctrl.Result{}, err - } + _ = log.FromContext(ctx) - return ctrl.Result{}, err - } - - log.Info("Creating a new Deployment", - "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) - if err = r.Create(ctx, dep); err != nil { - log.Error(err, "Failed to create new Deployment", - "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) - return ctrl.Result{}, err - } - - // Deployment created successfully - // We will requeue the reconciliation so that we can ensure the state - // and move forward for the next operations - return ctrl.Result{RequeueAfter: time.Minute}, nil - } else if err != nil { - log.Error(err, "Failed to get Deployment") - // Let's return the error for the reconciliation be re-trigged again - return ctrl.Result{}, err - } - - // The CRD API is defining that the Memcached type, have a MemcachedSpec.Size field - // to set the quantity of Deployment instances is the desired state on the cluster. - // Therefore, the following code will ensure the Deployment size is the same as defined - // via the Size spec of the Custom Resource which we are reconciling. - size := memcached.Spec.Size - if *found.Spec.Replicas != size { - found.Spec.Replicas = &size - if err = r.Update(ctx, found); err != nil { - log.Error(err, "Failed to update Deployment", - "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) - - // Re-fetch the memcached Custom Resource before update the status - // so that we have the latest state of the resource on the cluster and we will avoid - // raise the issue "the object has been modified, please apply - // your changes to the latest version and try again" which would re-trigger the reconciliation - if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { - log.Error(err, "Failed to re-fetch memcached") - return ctrl.Result{}, err - } - - // The following implementation will update the status - meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, - Status: metav1.ConditionFalse, Reason: "Resizing", - Message: fmt.Sprintf("Failed to update the size for the custom resource (%s): (%s)", memcached.Name, err)}) - - if err := r.Status().Update(ctx, memcached); err != nil { - log.Error(err, "Failed to update Memcached status") - return ctrl.Result{}, err - } - - return ctrl.Result{}, err - } - - // Now, that we update the size we want to requeue the reconciliation - // so that we can ensure that we have the latest state of the resource before - // update. Also, it will help ensure the desired state on the cluster - return ctrl.Result{Requeue: true}, nil - } - - // The following implementation will update the status - meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, - Status: metav1.ConditionTrue, Reason: "Reconciling", - Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", memcached.Name, size)}) - - if err := r.Status().Update(ctx, memcached); err != nil { - log.Error(err, "Failed to update Memcached status") - return ctrl.Result{}, err - } + // TODO(user): your logic here return ctrl.Result{}, nil } -// finalizeMemcached will perform the required operations before delete the CR. -func (r *MemcachedReconciler) doFinalizerOperationsForMemcached(cr *examplecomv1alpha1.Memcached) { - // TODO(user): Add the cleanup steps that the operator - // needs to do before the CR can be deleted. Examples - // of finalizers include performing backups and deleting - // resources that are not owned by this CR, like a PVC. - - // Note: It is not recommended to use finalizers with the purpose of delete resources which are - // created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile, - // are defined as depended of the custom resource. See that we use the method ctrl.SetControllerReference. - // to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API. - // More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/ - - // The following implementation will raise an event - r.Recorder.Event(cr, "Warning", "Deleting", - fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s", - cr.Name, - cr.Namespace)) -} - -// deploymentForMemcached returns a Memcached Deployment object -func (r *MemcachedReconciler) deploymentForMemcached( - memcached *examplecomv1alpha1.Memcached) (*appsv1.Deployment, error) { - ls := labelsForMemcached(memcached.Name) - replicas := memcached.Spec.Size - - // Get the Operand image - image, err := imageForMemcached() - if err != nil { - return nil, err - } - - dep := &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: memcached.Name, - Namespace: memcached.Namespace, - }, - Spec: appsv1.DeploymentSpec{ - Replicas: &replicas, - Selector: &metav1.LabelSelector{ - MatchLabels: ls, - }, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: ls, - }, - Spec: corev1.PodSpec{ - // TODO(user): Uncomment the following code to configure the nodeAffinity expression - // according to the platforms which are supported by your solution. It is considered - // best practice to support multiple architectures. build your manager image using the - // makefile target docker-buildx. Also, you can use docker manifest inspect - // to check what are the platforms supported. - // More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity - //Affinity: &corev1.Affinity{ - // NodeAffinity: &corev1.NodeAffinity{ - // RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ - // NodeSelectorTerms: []corev1.NodeSelectorTerm{ - // { - // MatchExpressions: []corev1.NodeSelectorRequirement{ - // { - // Key: "kubernetes.io/arch", - // Operator: "In", - // Values: []string{"amd64", "arm64", "ppc64le", "s390x"}, - // }, - // { - // Key: "kubernetes.io/os", - // Operator: "In", - // Values: []string{"linux"}, - // }, - // }, - // }, - // }, - // }, - // }, - //}, - SecurityContext: &corev1.PodSecurityContext{ - RunAsNonRoot: &[]bool{true}[0], - // IMPORTANT: seccomProfile was introduced with Kubernetes 1.19 - // If you are looking for to produce solutions to be supported - // on lower versions you must remove this option. - SeccompProfile: &corev1.SeccompProfile{ - Type: corev1.SeccompProfileTypeRuntimeDefault, - }, - }, - Containers: []corev1.Container{{ - Image: image, - Name: "memcached", - ImagePullPolicy: corev1.PullIfNotPresent, - // Ensure restrictive context for the container - // More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted - SecurityContext: &corev1.SecurityContext{ - RunAsNonRoot: &[]bool{true}[0], - RunAsUser: &[]int64{1001}[0], - AllowPrivilegeEscalation: &[]bool{false}[0], - Capabilities: &corev1.Capabilities{ - Drop: []corev1.Capability{ - "ALL", - }, - }, - }, - Ports: []corev1.ContainerPort{{ - ContainerPort: memcached.Spec.ContainerPort, - Name: "memcached", - }}, - Command: []string{"memcached", "-m=64", "-o", "modern", "-v"}, - }}, - }, - }, - }, - } - - // Set the ownerRef for the Deployment - // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/ - if err := ctrl.SetControllerReference(memcached, dep, r.Scheme); err != nil { - return nil, err - } - return dep, nil -} - -// labelsForMemcached returns the labels for selecting the resources -// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ -func labelsForMemcached(name string) map[string]string { - var imageTag string - image, err := imageForMemcached() - if err == nil { - imageTag = strings.Split(image, ":")[1] - } - return map[string]string{"app.kubernetes.io/name": "Memcached", - "app.kubernetes.io/instance": name, - "app.kubernetes.io/version": imageTag, - "app.kubernetes.io/part-of": "project-v3-with-deploy-image", - "app.kubernetes.io/created-by": "controller-manager", - } -} - -// imageForMemcached gets the Operand image which is managed by this controller -// from the MEMCACHED_IMAGE environment variable defined in the config/manager/manager.yaml -func imageForMemcached() (string, error) { - var imageEnvVar = "MEMCACHED_IMAGE" - image, found := os.LookupEnv(imageEnvVar) - if !found { - return "", fmt.Errorf("Unable to find %s environment variable with the image", imageEnvVar) - } - return image, nil -} - // SetupWithManager sets up the controller with the Manager. -// Note that the Deployment will be also watched in order to ensure its -// desirable state on the cluster func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&examplecomv1alpha1.Memcached{}). - Owns(&appsv1.Deployment{}). Complete(r) } diff --git a/testdata/project-v3-with-deploy-image/pkg/controllers/busybox_controller.go b/testdata/project-v3-with-deploy-image/pkg/controllers/busybox_controller.go new file mode 100644 index 00000000000..287af79026d --- /dev/null +++ b/testdata/project-v3-with-deploy-image/pkg/controllers/busybox_controller.go @@ -0,0 +1,434 @@ +/* +Copyright 2022 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 controllers + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v3-with-deploy-image/api/v1alpha1" +) + +const busyboxFinalizer = "example.com.testproject.org/finalizer" + +// Definitions to manage status conditions +const ( + // typeAvailableBusybox represents the status of the Deployment reconciliation + typeAvailableBusybox = "Available" + // typeDegradedBusybox represents the status used when the custom resource is deleted and the finalizer operations are must to occur. + typeDegradedBusybox = "Degraded" +) + +// BusyboxReconciler reconciles a Busybox object +type BusyboxReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen +// when the command is executed. +// To know more about markers see: https://book.kubebuilder.io/reference/markers.html + +//+kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=example.com.testproject.org,resources=busyboxes/finalizers,verbs=update +//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. + +// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator +// pattern you will create Controllers which provide a reconcile function +// responsible for synchronizing resources until the desired state is reached on the cluster. +// Breaking this recommendation goes against the design principles of controller-runtime. +// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention. +// For further info: +// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/ +// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/ +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile +func (r *BusyboxReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + // Fetch the Busybox instance + // The purpose is check if the Custom Resource for the Kind Busybox + // is applied on the cluster if not we return nil to stop the reconciliation + busybox := &examplecomv1alpha1.Busybox{} + err := r.Get(ctx, req.NamespacedName, busybox) + if err != nil { + if apierrors.IsNotFound(err) { + // If the custom resource is not found then, it usually means that it was deleted or not created + // In this way, we will stop the reconciliation + log.Info("busybox resource not found. Ignoring since object must be deleted") + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + log.Error(err, "Failed to get busybox") + return ctrl.Result{}, err + } + + // Let's just set the status as Unknown when no status are available + if busybox.Status.Conditions == nil || len(busybox.Status.Conditions) == 0 { + meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) + if err = r.Status().Update(ctx, busybox); err != nil { + log.Error(err, "Failed to update Busybox status") + return ctrl.Result{}, err + } + + // Let's re-fetch the busybox Custom Resource after update the status + // so that we have the latest state of the resource on the cluster and we will avoid + // raise the issue "the object has been modified, please apply + // your changes to the latest version and try again" which would re-trigger the reconciliation + // if we try to update it again in the following operations + if err := r.Get(ctx, req.NamespacedName, busybox); err != nil { + log.Error(err, "Failed to re-fetch busybox") + return ctrl.Result{}, err + } + } + + // Let's add a finalizer. Then, we can define some operations which should + // occurs before the custom resource to be deleted. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers + if !controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) { + log.Info("Adding Finalizer for Busybox") + if ok := controllerutil.AddFinalizer(busybox, busyboxFinalizer); !ok { + log.Error(err, "Failed to add finalizer into the custom resource") + return ctrl.Result{Requeue: true}, nil + } + + if err = r.Update(ctx, busybox); err != nil { + log.Error(err, "Failed to update custom resource to add finalizer") + return ctrl.Result{}, err + } + } + + // Check if the Busybox instance is marked to be deleted, which is + // indicated by the deletion timestamp being set. + isBusyboxMarkedToBeDeleted := busybox.GetDeletionTimestamp() != nil + if isBusyboxMarkedToBeDeleted { + if controllerutil.ContainsFinalizer(busybox, busyboxFinalizer) { + log.Info("Performing Finalizer Operations for Busybox before delete CR") + + // Let's add here an status "Downgrade" to define that this resource begin its process to be terminated. + meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeDegradedBusybox, + Status: metav1.ConditionUnknown, Reason: "Finalizing", + Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", busybox.Name)}) + + if err := r.Status().Update(ctx, busybox); err != nil { + log.Error(err, "Failed to update Busybox status") + return ctrl.Result{}, err + } + + // Perform all operations required before remove the finalizer and allow + // the Kubernetes API to remove the custom resource. + r.doFinalizerOperationsForBusybox(busybox) + + // TODO(user): If you add operations to the doFinalizerOperationsForBusybox method + // then you need to ensure that all worked fine before deleting and updating the Downgrade status + // otherwise, you should requeue here. + + // Re-fetch the busybox Custom Resource before update the status + // so that we have the latest state of the resource on the cluster and we will avoid + // raise the issue "the object has been modified, please apply + // your changes to the latest version and try again" which would re-trigger the reconciliation + if err := r.Get(ctx, req.NamespacedName, busybox); err != nil { + log.Error(err, "Failed to re-fetch busybox") + return ctrl.Result{}, err + } + + meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeDegradedBusybox, + Status: metav1.ConditionTrue, Reason: "Finalizing", + Message: fmt.Sprintf("Finalizer operations for custom resource %s name were successfully accomplished", busybox.Name)}) + + if err := r.Status().Update(ctx, busybox); err != nil { + log.Error(err, "Failed to update Busybox status") + return ctrl.Result{}, err + } + + log.Info("Removing Finalizer for Busybox after successfully perform the operations") + if ok := controllerutil.RemoveFinalizer(busybox, busyboxFinalizer); !ok { + log.Error(err, "Failed to remove finalizer for Busybox") + return ctrl.Result{Requeue: true}, nil + } + + if err := r.Update(ctx, busybox); err != nil { + log.Error(err, "Failed to remove finalizer for Busybox") + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil + } + + // Check if the deployment already exists, if not create a new one + found := &appsv1.Deployment{} + err = r.Get(ctx, types.NamespacedName{Name: busybox.Name, Namespace: busybox.Namespace}, found) + if err != nil && apierrors.IsNotFound(err) { + // Define a new deployment + dep, err := r.deploymentForBusybox(busybox) + if err != nil { + log.Error(err, "Failed to define new Deployment resource for Busybox") + + // The following implementation will update the status + meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, + Status: metav1.ConditionFalse, Reason: "Reconciling", + Message: fmt.Sprintf("Failed to create Deployment for the custom resource (%s): (%s)", busybox.Name, err)}) + + if err := r.Status().Update(ctx, busybox); err != nil { + log.Error(err, "Failed to update Busybox status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, err + } + + log.Info("Creating a new Deployment", + "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + if err = r.Create(ctx, dep); err != nil { + log.Error(err, "Failed to create new Deployment", + "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + return ctrl.Result{}, err + } + + // Deployment created successfully + // We will requeue the reconciliation so that we can ensure the state + // and move forward for the next operations + return ctrl.Result{RequeueAfter: time.Minute}, nil + } else if err != nil { + log.Error(err, "Failed to get Deployment") + // Let's return the error for the reconciliation be re-trigged again + return ctrl.Result{}, err + } + + // The CRD API is defining that the Busybox type, have a BusyboxSpec.Size field + // to set the quantity of Deployment instances is the desired state on the cluster. + // Therefore, the following code will ensure the Deployment size is the same as defined + // via the Size spec of the Custom Resource which we are reconciling. + size := busybox.Spec.Size + if *found.Spec.Replicas != size { + found.Spec.Replicas = &size + if err = r.Update(ctx, found); err != nil { + log.Error(err, "Failed to update Deployment", + "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) + + // Re-fetch the busybox Custom Resource before update the status + // so that we have the latest state of the resource on the cluster and we will avoid + // raise the issue "the object has been modified, please apply + // your changes to the latest version and try again" which would re-trigger the reconciliation + if err := r.Get(ctx, req.NamespacedName, busybox); err != nil { + log.Error(err, "Failed to re-fetch busybox") + return ctrl.Result{}, err + } + + // The following implementation will update the status + meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, + Status: metav1.ConditionFalse, Reason: "Resizing", + Message: fmt.Sprintf("Failed to update the size for the custom resource (%s): (%s)", busybox.Name, err)}) + + if err := r.Status().Update(ctx, busybox); err != nil { + log.Error(err, "Failed to update Busybox status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, err + } + + // Now, that we update the size we want to requeue the reconciliation + // so that we can ensure that we have the latest state of the resource before + // update. Also, it will help ensure the desired state on the cluster + return ctrl.Result{Requeue: true}, nil + } + + // The following implementation will update the status + meta.SetStatusCondition(&busybox.Status.Conditions, metav1.Condition{Type: typeAvailableBusybox, + Status: metav1.ConditionTrue, Reason: "Reconciling", + Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", busybox.Name, size)}) + + if err := r.Status().Update(ctx, busybox); err != nil { + log.Error(err, "Failed to update Busybox status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// finalizeBusybox will perform the required operations before delete the CR. +func (r *BusyboxReconciler) doFinalizerOperationsForBusybox(cr *examplecomv1alpha1.Busybox) { + // TODO(user): Add the cleanup steps that the operator + // needs to do before the CR can be deleted. Examples + // of finalizers include performing backups and deleting + // resources that are not owned by this CR, like a PVC. + + // Note: It is not recommended to use finalizers with the purpose of delete resources which are + // created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile, + // are defined as depended of the custom resource. See that we use the method ctrl.SetControllerReference. + // to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API. + // More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/ + + // The following implementation will raise an event + r.Recorder.Event(cr, "Warning", "Deleting", + fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s", + cr.Name, + cr.Namespace)) +} + +// deploymentForBusybox returns a Busybox Deployment object +func (r *BusyboxReconciler) deploymentForBusybox( + busybox *examplecomv1alpha1.Busybox) (*appsv1.Deployment, error) { + ls := labelsForBusybox(busybox.Name) + replicas := busybox.Spec.Size + + // Get the Operand image + image, err := imageForBusybox() + if err != nil { + return nil, err + } + + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: busybox.Name, + Namespace: busybox.Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: ls, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: ls, + }, + Spec: corev1.PodSpec{ + // TODO(user): Uncomment the following code to configure the nodeAffinity expression + // according to the platforms which are supported by your solution. It is considered + // best practice to support multiple architectures. build your manager image using the + // makefile target docker-buildx. Also, you can use docker manifest inspect + // to check what are the platforms supported. + // More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + //Affinity: &corev1.Affinity{ + // NodeAffinity: &corev1.NodeAffinity{ + // RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + // NodeSelectorTerms: []corev1.NodeSelectorTerm{ + // { + // MatchExpressions: []corev1.NodeSelectorRequirement{ + // { + // Key: "kubernetes.io/arch", + // Operator: "In", + // Values: []string{"amd64", "arm64", "ppc64le", "s390x"}, + // }, + // { + // Key: "kubernetes.io/os", + // Operator: "In", + // Values: []string{"linux"}, + // }, + // }, + // }, + // }, + // }, + // }, + //}, + SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: &[]bool{true}[0], + // IMPORTANT: seccomProfile was introduced with Kubernetes 1.19 + // If you are looking for to produce solutions to be supported + // on lower versions you must remove this option. + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + }, + Containers: []corev1.Container{{ + Image: image, + Name: "busybox", + ImagePullPolicy: corev1.PullIfNotPresent, + // Ensure restrictive context for the container + // More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted + SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: &[]bool{true}[0], + AllowPrivilegeEscalation: &[]bool{false}[0], + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + }, + }}, + }, + }, + }, + } + + // Set the ownerRef for the Deployment + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/ + if err := ctrl.SetControllerReference(busybox, dep, r.Scheme); err != nil { + return nil, err + } + return dep, nil +} + +// labelsForBusybox returns the labels for selecting the resources +// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +func labelsForBusybox(name string) map[string]string { + var imageTag string + image, err := imageForBusybox() + if err == nil { + imageTag = strings.Split(image, ":")[1] + } + return map[string]string{"app.kubernetes.io/name": "Busybox", + "app.kubernetes.io/instance": name, + "app.kubernetes.io/version": imageTag, + "app.kubernetes.io/part-of": "project-v3-with-deploy-image", + "app.kubernetes.io/created-by": "controller-manager", + } +} + +// imageForBusybox gets the Operand image which is managed by this controller +// from the BUSYBOX_IMAGE environment variable defined in the config/manager/manager.yaml +func imageForBusybox() (string, error) { + var imageEnvVar = "BUSYBOX_IMAGE" + image, found := os.LookupEnv(imageEnvVar) + if !found { + return "", fmt.Errorf("Unable to find %s environment variable with the image", imageEnvVar) + } + return image, nil +} + +// SetupWithManager sets up the controller with the Manager. +// Note that the Deployment will be also watched in order to ensure its +// desirable state on the cluster +func (r *BusyboxReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&examplecomv1alpha1.Busybox{}). + Owns(&appsv1.Deployment{}). + Complete(r) +} diff --git a/testdata/project-v3-with-deploy-image/pkg/controllers/memcached_controller.go b/testdata/project-v3-with-deploy-image/pkg/controllers/memcached_controller.go new file mode 100644 index 00000000000..4df4ae3ea4f --- /dev/null +++ b/testdata/project-v3-with-deploy-image/pkg/controllers/memcached_controller.go @@ -0,0 +1,440 @@ +/* +Copyright 2022 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 controllers + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v3-with-deploy-image/api/v1alpha1" +) + +const memcachedFinalizer = "example.com.testproject.org/finalizer" + +// Definitions to manage status conditions +const ( + // typeAvailableMemcached represents the status of the Deployment reconciliation + typeAvailableMemcached = "Available" + // typeDegradedMemcached represents the status used when the custom resource is deleted and the finalizer operations are must to occur. + typeDegradedMemcached = "Degraded" +) + +// MemcachedReconciler reconciles a Memcached object +type MemcachedReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// The following markers are used to generate the rules permissions (RBAC) on config/rbac using controller-gen +// when the command is executed. +// To know more about markers see: https://book.kubebuilder.io/reference/markers.html + +//+kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=example.com.testproject.org,resources=memcacheds/finalizers,verbs=update +//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. + +// It is essential for the controller's reconciliation loop to be idempotent. By following the Operator +// pattern you will create Controllers which provide a reconcile function +// responsible for synchronizing resources until the desired state is reached on the cluster. +// Breaking this recommendation goes against the design principles of controller-runtime. +// and may lead to unforeseen consequences such as resources becoming stuck and requiring manual intervention. +// For further info: +// - About Operator Pattern: https://kubernetes.io/docs/concepts/extend-kubernetes/operator/ +// - About Controllers: https://kubernetes.io/docs/concepts/architecture/controller/ +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.0/pkg/reconcile +func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + // Fetch the Memcached instance + // The purpose is check if the Custom Resource for the Kind Memcached + // is applied on the cluster if not we return nil to stop the reconciliation + memcached := &examplecomv1alpha1.Memcached{} + err := r.Get(ctx, req.NamespacedName, memcached) + if err != nil { + if apierrors.IsNotFound(err) { + // If the custom resource is not found then, it usually means that it was deleted or not created + // In this way, we will stop the reconciliation + log.Info("memcached resource not found. Ignoring since object must be deleted") + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + log.Error(err, "Failed to get memcached") + return ctrl.Result{}, err + } + + // Let's just set the status as Unknown when no status are available + if memcached.Status.Conditions == nil || len(memcached.Status.Conditions) == 0 { + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, Status: metav1.ConditionUnknown, Reason: "Reconciling", Message: "Starting reconciliation"}) + if err = r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + // Let's re-fetch the memcached Custom Resource after update the status + // so that we have the latest state of the resource on the cluster and we will avoid + // raise the issue "the object has been modified, please apply + // your changes to the latest version and try again" which would re-trigger the reconciliation + // if we try to update it again in the following operations + if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { + log.Error(err, "Failed to re-fetch memcached") + return ctrl.Result{}, err + } + } + + // Let's add a finalizer. Then, we can define some operations which should + // occurs before the custom resource to be deleted. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers + if !controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { + log.Info("Adding Finalizer for Memcached") + if ok := controllerutil.AddFinalizer(memcached, memcachedFinalizer); !ok { + log.Error(err, "Failed to add finalizer into the custom resource") + return ctrl.Result{Requeue: true}, nil + } + + if err = r.Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update custom resource to add finalizer") + return ctrl.Result{}, err + } + } + + // Check if the Memcached instance is marked to be deleted, which is + // indicated by the deletion timestamp being set. + isMemcachedMarkedToBeDeleted := memcached.GetDeletionTimestamp() != nil + if isMemcachedMarkedToBeDeleted { + if controllerutil.ContainsFinalizer(memcached, memcachedFinalizer) { + log.Info("Performing Finalizer Operations for Memcached before delete CR") + + // Let's add here an status "Downgrade" to define that this resource begin its process to be terminated. + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached, + Status: metav1.ConditionUnknown, Reason: "Finalizing", + Message: fmt.Sprintf("Performing finalizer operations for the custom resource: %s ", memcached.Name)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + // Perform all operations required before remove the finalizer and allow + // the Kubernetes API to remove the custom resource. + r.doFinalizerOperationsForMemcached(memcached) + + // TODO(user): If you add operations to the doFinalizerOperationsForMemcached method + // then you need to ensure that all worked fine before deleting and updating the Downgrade status + // otherwise, you should requeue here. + + // Re-fetch the memcached Custom Resource before update the status + // so that we have the latest state of the resource on the cluster and we will avoid + // raise the issue "the object has been modified, please apply + // your changes to the latest version and try again" which would re-trigger the reconciliation + if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { + log.Error(err, "Failed to re-fetch memcached") + return ctrl.Result{}, err + } + + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeDegradedMemcached, + Status: metav1.ConditionTrue, Reason: "Finalizing", + Message: fmt.Sprintf("Finalizer operations for custom resource %s name were successfully accomplished", memcached.Name)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + log.Info("Removing Finalizer for Memcached after successfully perform the operations") + if ok := controllerutil.RemoveFinalizer(memcached, memcachedFinalizer); !ok { + log.Error(err, "Failed to remove finalizer for Memcached") + return ctrl.Result{Requeue: true}, nil + } + + if err := r.Update(ctx, memcached); err != nil { + log.Error(err, "Failed to remove finalizer for Memcached") + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil + } + + // Check if the deployment already exists, if not create a new one + found := &appsv1.Deployment{} + err = r.Get(ctx, types.NamespacedName{Name: memcached.Name, Namespace: memcached.Namespace}, found) + if err != nil && apierrors.IsNotFound(err) { + // Define a new deployment + dep, err := r.deploymentForMemcached(memcached) + if err != nil { + log.Error(err, "Failed to define new Deployment resource for Memcached") + + // The following implementation will update the status + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, + Status: metav1.ConditionFalse, Reason: "Reconciling", + Message: fmt.Sprintf("Failed to create Deployment for the custom resource (%s): (%s)", memcached.Name, err)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, err + } + + log.Info("Creating a new Deployment", + "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + if err = r.Create(ctx, dep); err != nil { + log.Error(err, "Failed to create new Deployment", + "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + return ctrl.Result{}, err + } + + // Deployment created successfully + // We will requeue the reconciliation so that we can ensure the state + // and move forward for the next operations + return ctrl.Result{RequeueAfter: time.Minute}, nil + } else if err != nil { + log.Error(err, "Failed to get Deployment") + // Let's return the error for the reconciliation be re-trigged again + return ctrl.Result{}, err + } + + // The CRD API is defining that the Memcached type, have a MemcachedSpec.Size field + // to set the quantity of Deployment instances is the desired state on the cluster. + // Therefore, the following code will ensure the Deployment size is the same as defined + // via the Size spec of the Custom Resource which we are reconciling. + size := memcached.Spec.Size + if *found.Spec.Replicas != size { + found.Spec.Replicas = &size + if err = r.Update(ctx, found); err != nil { + log.Error(err, "Failed to update Deployment", + "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) + + // Re-fetch the memcached Custom Resource before update the status + // so that we have the latest state of the resource on the cluster and we will avoid + // raise the issue "the object has been modified, please apply + // your changes to the latest version and try again" which would re-trigger the reconciliation + if err := r.Get(ctx, req.NamespacedName, memcached); err != nil { + log.Error(err, "Failed to re-fetch memcached") + return ctrl.Result{}, err + } + + // The following implementation will update the status + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, + Status: metav1.ConditionFalse, Reason: "Resizing", + Message: fmt.Sprintf("Failed to update the size for the custom resource (%s): (%s)", memcached.Name, err)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, err + } + + // Now, that we update the size we want to requeue the reconciliation + // so that we can ensure that we have the latest state of the resource before + // update. Also, it will help ensure the desired state on the cluster + return ctrl.Result{Requeue: true}, nil + } + + // The following implementation will update the status + meta.SetStatusCondition(&memcached.Status.Conditions, metav1.Condition{Type: typeAvailableMemcached, + Status: metav1.ConditionTrue, Reason: "Reconciling", + Message: fmt.Sprintf("Deployment for custom resource (%s) with %d replicas created successfully", memcached.Name, size)}) + + if err := r.Status().Update(ctx, memcached); err != nil { + log.Error(err, "Failed to update Memcached status") + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +// finalizeMemcached will perform the required operations before delete the CR. +func (r *MemcachedReconciler) doFinalizerOperationsForMemcached(cr *examplecomv1alpha1.Memcached) { + // TODO(user): Add the cleanup steps that the operator + // needs to do before the CR can be deleted. Examples + // of finalizers include performing backups and deleting + // resources that are not owned by this CR, like a PVC. + + // Note: It is not recommended to use finalizers with the purpose of delete resources which are + // created and managed in the reconciliation. These ones, such as the Deployment created on this reconcile, + // are defined as depended of the custom resource. See that we use the method ctrl.SetControllerReference. + // to set the ownerRef which means that the Deployment will be deleted by the Kubernetes API. + // More info: https://kubernetes.io/docs/tasks/administer-cluster/use-cascading-deletion/ + + // The following implementation will raise an event + r.Recorder.Event(cr, "Warning", "Deleting", + fmt.Sprintf("Custom Resource %s is being deleted from the namespace %s", + cr.Name, + cr.Namespace)) +} + +// deploymentForMemcached returns a Memcached Deployment object +func (r *MemcachedReconciler) deploymentForMemcached( + memcached *examplecomv1alpha1.Memcached) (*appsv1.Deployment, error) { + ls := labelsForMemcached(memcached.Name) + replicas := memcached.Spec.Size + + // Get the Operand image + image, err := imageForMemcached() + if err != nil { + return nil, err + } + + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: memcached.Name, + Namespace: memcached.Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: ls, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: ls, + }, + Spec: corev1.PodSpec{ + // TODO(user): Uncomment the following code to configure the nodeAffinity expression + // according to the platforms which are supported by your solution. It is considered + // best practice to support multiple architectures. build your manager image using the + // makefile target docker-buildx. Also, you can use docker manifest inspect + // to check what are the platforms supported. + // More info: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity + //Affinity: &corev1.Affinity{ + // NodeAffinity: &corev1.NodeAffinity{ + // RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ + // NodeSelectorTerms: []corev1.NodeSelectorTerm{ + // { + // MatchExpressions: []corev1.NodeSelectorRequirement{ + // { + // Key: "kubernetes.io/arch", + // Operator: "In", + // Values: []string{"amd64", "arm64", "ppc64le", "s390x"}, + // }, + // { + // Key: "kubernetes.io/os", + // Operator: "In", + // Values: []string{"linux"}, + // }, + // }, + // }, + // }, + // }, + // }, + //}, + SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: &[]bool{true}[0], + // IMPORTANT: seccomProfile was introduced with Kubernetes 1.19 + // If you are looking for to produce solutions to be supported + // on lower versions you must remove this option. + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + }, + Containers: []corev1.Container{{ + Image: image, + Name: "memcached", + ImagePullPolicy: corev1.PullIfNotPresent, + // Ensure restrictive context for the container + // More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted + SecurityContext: &corev1.SecurityContext{ + RunAsNonRoot: &[]bool{true}[0], + RunAsUser: &[]int64{1001}[0], + AllowPrivilegeEscalation: &[]bool{false}[0], + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{ + "ALL", + }, + }, + }, + Ports: []corev1.ContainerPort{{ + ContainerPort: memcached.Spec.ContainerPort, + Name: "memcached", + }}, + Command: []string{"memcached", "-m=64", "-o", "modern", "-v"}, + }}, + }, + }, + }, + } + + // Set the ownerRef for the Deployment + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/ + if err := ctrl.SetControllerReference(memcached, dep, r.Scheme); err != nil { + return nil, err + } + return dep, nil +} + +// labelsForMemcached returns the labels for selecting the resources +// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +func labelsForMemcached(name string) map[string]string { + var imageTag string + image, err := imageForMemcached() + if err == nil { + imageTag = strings.Split(image, ":")[1] + } + return map[string]string{"app.kubernetes.io/name": "Memcached", + "app.kubernetes.io/instance": name, + "app.kubernetes.io/version": imageTag, + "app.kubernetes.io/part-of": "project-v3-with-deploy-image", + "app.kubernetes.io/created-by": "controller-manager", + } +} + +// imageForMemcached gets the Operand image which is managed by this controller +// from the MEMCACHED_IMAGE environment variable defined in the config/manager/manager.yaml +func imageForMemcached() (string, error) { + var imageEnvVar = "MEMCACHED_IMAGE" + image, found := os.LookupEnv(imageEnvVar) + if !found { + return "", fmt.Errorf("Unable to find %s environment variable with the image", imageEnvVar) + } + return image, nil +} + +// SetupWithManager sets up the controller with the Manager. +// Note that the Deployment will be also watched in order to ensure its +// desirable state on the cluster +func (r *MemcachedReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&examplecomv1alpha1.Memcached{}). + Owns(&appsv1.Deployment{}). + Complete(r) +} diff --git a/testdata/project-v4-addon-and-grafana/Dockerfile b/testdata/project-v4-addon-and-grafana/Dockerfile index c4733c56e1b..c7dd59c19d5 100644 --- a/testdata/project-v4-addon-and-grafana/Dockerfile +++ b/testdata/project-v4-addon-and-grafana/Dockerfile @@ -13,8 +13,8 @@ RUN go mod download # Copy the go source COPY main.go main.go -COPY api/ api/ -COPY controllers/ controllers/ +COPY pkg/api/ pkg/api/ +COPY pkg/controllers/ pkg/controllers/ # https://github.com/kubernetes-sigs/kubebuilder-declarative-pattern/blob/master/docs/addon/walkthrough/README.md#adding-a-manifest # Stage channels and make readable COPY channels/ /channels/ @@ -32,9 +32,6 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o ma FROM gcr.io/distroless/static:nonroot WORKDIR / COPY --from=builder /workspace/manager . -# copy channels -COPY --from=builder /channels /channels - USER 65532:65532 ENTRYPOINT ["/manager"] diff --git a/testdata/project-v4-addon-and-grafana/Makefile b/testdata/project-v4-addon-and-grafana/Makefile index 48ac825553e..da9a937ec24 100644 --- a/testdata/project-v4-addon-and-grafana/Makefile +++ b/testdata/project-v4-addon-and-grafana/Makefile @@ -135,11 +135,10 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest KUSTOMIZE_VERSION ?= v4.5.5 CONTROLLER_TOOLS_VERSION ?= v0.10.0 -KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. $(KUSTOMIZE): $(LOCALBIN) - test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kustomize/kustomize/v4@$(KUSTOMIZE_VERSION) .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. diff --git a/testdata/project-v4-addon-and-grafana/PROJECT b/testdata/project-v4-addon-and-grafana/PROJECT index 118981e558b..04d7992b08c 100644 --- a/testdata/project-v4-addon-and-grafana/PROJECT +++ b/testdata/project-v4-addon-and-grafana/PROJECT @@ -29,7 +29,7 @@ resources: domain: testproject.org group: crew kind: Captain - path: sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/api/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1 version: v1 - api: crdVersion: v1 @@ -38,7 +38,7 @@ resources: domain: testproject.org group: crew kind: FirstMate - path: sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/api/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1 version: v1 - api: crdVersion: v1 @@ -46,6 +46,6 @@ resources: domain: testproject.org group: crew kind: Admiral - path: sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/api/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1 version: v1 version: "3" diff --git a/testdata/project-v4-addon-and-grafana/api/v1/zz_generated.deepcopy.go b/testdata/project-v4-addon-and-grafana/api/v1/zz_generated.deepcopy.go index d7d62dbdb1c..6f814698f1a 100644 --- a/testdata/project-v4-addon-and-grafana/api/v1/zz_generated.deepcopy.go +++ b/testdata/project-v4-addon-and-grafana/api/v1/zz_generated.deepcopy.go @@ -84,39 +84,6 @@ func (in *AdmiralList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdmiralSpec) DeepCopyInto(out *AdmiralSpec) { - *out = *in - out.CommonSpec = in.CommonSpec - in.PatchSpec.DeepCopyInto(&out.PatchSpec) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralSpec. -func (in *AdmiralSpec) DeepCopy() *AdmiralSpec { - if in == nil { - return nil - } - out := new(AdmiralSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AdmiralStatus) DeepCopyInto(out *AdmiralStatus) { - *out = *in - in.CommonStatus.DeepCopyInto(&out.CommonStatus) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralStatus. -func (in *AdmiralStatus) DeepCopy() *AdmiralStatus { - if in == nil { - return nil - } - out := new(AdmiralStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Captain) DeepCopyInto(out *Captain) { *out = *in @@ -176,39 +143,6 @@ func (in *CaptainList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CaptainSpec) DeepCopyInto(out *CaptainSpec) { - *out = *in - out.CommonSpec = in.CommonSpec - in.PatchSpec.DeepCopyInto(&out.PatchSpec) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainSpec. -func (in *CaptainSpec) DeepCopy() *CaptainSpec { - if in == nil { - return nil - } - out := new(CaptainSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CaptainStatus) DeepCopyInto(out *CaptainStatus) { - *out = *in - in.CommonStatus.DeepCopyInto(&out.CommonStatus) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainStatus. -func (in *CaptainStatus) DeepCopy() *CaptainStatus { - if in == nil { - return nil - } - out := new(CaptainStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *FirstMate) DeepCopyInto(out *FirstMate) { *out = *in @@ -267,36 +201,3 @@ func (in *FirstMateList) DeepCopyObject() runtime.Object { } return nil } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FirstMateSpec) DeepCopyInto(out *FirstMateSpec) { - *out = *in - out.CommonSpec = in.CommonSpec - in.PatchSpec.DeepCopyInto(&out.PatchSpec) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateSpec. -func (in *FirstMateSpec) DeepCopy() *FirstMateSpec { - if in == nil { - return nil - } - out := new(FirstMateSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FirstMateStatus) DeepCopyInto(out *FirstMateStatus) { - *out = *in - in.CommonStatus.DeepCopyInto(&out.CommonStatus) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateStatus. -func (in *FirstMateStatus) DeepCopy() *FirstMateStatus { - if in == nil { - return nil - } - out := new(FirstMateStatus) - in.DeepCopyInto(out) - return out -} diff --git a/testdata/project-v4-addon-and-grafana/config/crd/bases/_.yaml b/testdata/project-v4-addon-and-grafana/config/crd/bases/_.yaml new file mode 100644 index 00000000000..25f6032cf97 --- /dev/null +++ b/testdata/project-v4-addon-and-grafana/config/crd/bases/_.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null +spec: + group: "" + names: + kind: "" + plural: "" + scope: "" + versions: null diff --git a/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_admirals.yaml b/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_admirals.yaml index 21f1ac8d4fe..cb3c595b0ab 100644 --- a/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_admirals.yaml +++ b/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_admirals.yaml @@ -35,33 +35,13 @@ spec: spec: description: AdmiralSpec defines the desired state of Admiral properties: - channel: - description: 'Channel specifies a channel that can be used to resolve - a specific addon, eg: stable It will be ignored if Version is specified' - type: string - patches: - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - version: - description: Version specifies the exact addon version to be deployed, - eg 1.2.3 It should not be specified if Channel is specified + foo: + description: Foo is an example field of Admiral. Edit admiral_types.go + to remove/update type: string type: object status: description: AdmiralStatus defines the observed state of Admiral - properties: - errors: - items: - type: string - type: array - healthy: - type: boolean - phase: - type: string - required: - - healthy type: object type: object served: true diff --git a/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_captains.yaml b/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_captains.yaml index d622a9bab29..69ab9ccffcb 100644 --- a/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_captains.yaml +++ b/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_captains.yaml @@ -35,33 +35,13 @@ spec: spec: description: CaptainSpec defines the desired state of Captain properties: - channel: - description: 'Channel specifies a channel that can be used to resolve - a specific addon, eg: stable It will be ignored if Version is specified' - type: string - patches: - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - version: - description: Version specifies the exact addon version to be deployed, - eg 1.2.3 It should not be specified if Channel is specified + foo: + description: Foo is an example field of Captain. Edit captain_types.go + to remove/update type: string type: object status: description: CaptainStatus defines the observed state of Captain - properties: - errors: - items: - type: string - type: array - healthy: - type: boolean - phase: - type: string - required: - - healthy type: object type: object served: true diff --git a/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_firstmates.yaml b/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_firstmates.yaml index 0799cde8419..e1d9bb02b2b 100644 --- a/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_firstmates.yaml +++ b/testdata/project-v4-addon-and-grafana/config/crd/bases/crew.testproject.org_firstmates.yaml @@ -35,33 +35,13 @@ spec: spec: description: FirstMateSpec defines the desired state of FirstMate properties: - channel: - description: 'Channel specifies a channel that can be used to resolve - a specific addon, eg: stable It will be ignored if Version is specified' - type: string - patches: - items: - type: object - x-kubernetes-preserve-unknown-fields: true - type: array - version: - description: Version specifies the exact addon version to be deployed, - eg 1.2.3 It should not be specified if Channel is specified + foo: + description: Foo is an example field of FirstMate. Edit firstmate_types.go + to remove/update type: string type: object status: description: FirstMateStatus defines the observed state of FirstMate - properties: - errors: - items: - type: string - type: array - healthy: - type: boolean - phase: - type: string - required: - - healthy type: object type: object served: true diff --git a/testdata/project-v4-addon-and-grafana/config/rbac/role.yaml b/testdata/project-v4-addon-and-grafana/config/rbac/role.yaml index 2ea68d8593b..c2f5699b347 100644 --- a/testdata/project-v4-addon-and-grafana/config/rbac/role.yaml +++ b/testdata/project-v4-addon-and-grafana/config/rbac/role.yaml @@ -17,6 +17,12 @@ rules: - patch - update - watch +- apiGroups: + - crew.testproject.org + resources: + - admirals/finalizers + verbs: + - update - apiGroups: - crew.testproject.org resources: @@ -37,6 +43,12 @@ rules: - patch - update - watch +- apiGroups: + - crew.testproject.org + resources: + - captains/finalizers + verbs: + - update - apiGroups: - crew.testproject.org resources: @@ -57,6 +69,12 @@ rules: - patch - update - watch +- apiGroups: + - crew.testproject.org + resources: + - firstmates/finalizers + verbs: + - update - apiGroups: - crew.testproject.org resources: diff --git a/testdata/project-v4-addon-and-grafana/controllers/admiral_controller.go b/testdata/project-v4-addon-and-grafana/controllers/admiral_controller.go index 486745e8b14..de8675935b2 100644 --- a/testdata/project-v4-addon-and-grafana/controllers/admiral_controller.go +++ b/testdata/project-v4-addon-and-grafana/controllers/admiral_controller.go @@ -29,7 +29,7 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1" ) var _ reconcile.Reconciler = &AdmiralReconciler{} diff --git a/testdata/project-v4-addon-and-grafana/controllers/captain_controller.go b/testdata/project-v4-addon-and-grafana/controllers/captain_controller.go index a79120a2545..a28e1e8a44a 100644 --- a/testdata/project-v4-addon-and-grafana/controllers/captain_controller.go +++ b/testdata/project-v4-addon-and-grafana/controllers/captain_controller.go @@ -29,7 +29,7 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1" ) var _ reconcile.Reconciler = &CaptainReconciler{} diff --git a/testdata/project-v4-addon-and-grafana/controllers/firstmate_controller.go b/testdata/project-v4-addon-and-grafana/controllers/firstmate_controller.go index 3f9db6ee19f..e5f4a20352a 100644 --- a/testdata/project-v4-addon-and-grafana/controllers/firstmate_controller.go +++ b/testdata/project-v4-addon-and-grafana/controllers/firstmate_controller.go @@ -29,7 +29,7 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1" ) var _ reconcile.Reconciler = &FirstMateReconciler{} diff --git a/testdata/project-v4-addon-and-grafana/main.go b/testdata/project-v4-addon-and-grafana/main.go index 6428f01e493..458cb172992 100644 --- a/testdata/project-v4-addon-and-grafana/main.go +++ b/testdata/project-v4-addon-and-grafana/main.go @@ -31,8 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/api/v1" - "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/controllers" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1" + "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/controllers" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-config/api/v1/admiral_types.go b/testdata/project-v4-addon-and-grafana/pkg/api/v1/admiral_types.go similarity index 100% rename from testdata/project-v4-config/api/v1/admiral_types.go rename to testdata/project-v4-addon-and-grafana/pkg/api/v1/admiral_types.go diff --git a/testdata/project-v4-config/api/v1/captain_types.go b/testdata/project-v4-addon-and-grafana/pkg/api/v1/captain_types.go similarity index 100% rename from testdata/project-v4-config/api/v1/captain_types.go rename to testdata/project-v4-addon-and-grafana/pkg/api/v1/captain_types.go diff --git a/testdata/project-v4-config/api/v1/firstmate_types.go b/testdata/project-v4-addon-and-grafana/pkg/api/v1/firstmate_types.go similarity index 100% rename from testdata/project-v4-config/api/v1/firstmate_types.go rename to testdata/project-v4-addon-and-grafana/pkg/api/v1/firstmate_types.go diff --git a/testdata/project-v4-addon-and-grafana/api/v1/groupversion_info.go b/testdata/project-v4-addon-and-grafana/pkg/api/v1/groupversion_info.go similarity index 100% rename from testdata/project-v4-addon-and-grafana/api/v1/groupversion_info.go rename to testdata/project-v4-addon-and-grafana/pkg/api/v1/groupversion_info.go diff --git a/testdata/project-v4-addon-and-grafana/pkg/api/v1/zz_generated.deepcopy.go b/testdata/project-v4-addon-and-grafana/pkg/api/v1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..100ca1a28a2 --- /dev/null +++ b/testdata/project-v4-addon-and-grafana/pkg/api/v1/zz_generated.deepcopy.go @@ -0,0 +1,293 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2022 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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Admiral) DeepCopyInto(out *Admiral) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Admiral. +func (in *Admiral) DeepCopy() *Admiral { + if in == nil { + return nil + } + out := new(Admiral) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Admiral) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmiralList) DeepCopyInto(out *AdmiralList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Admiral, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralList. +func (in *AdmiralList) DeepCopy() *AdmiralList { + if in == nil { + return nil + } + out := new(AdmiralList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AdmiralList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmiralSpec) DeepCopyInto(out *AdmiralSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralSpec. +func (in *AdmiralSpec) DeepCopy() *AdmiralSpec { + if in == nil { + return nil + } + out := new(AdmiralSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AdmiralStatus) DeepCopyInto(out *AdmiralStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmiralStatus. +func (in *AdmiralStatus) DeepCopy() *AdmiralStatus { + if in == nil { + return nil + } + out := new(AdmiralStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Captain) DeepCopyInto(out *Captain) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Captain. +func (in *Captain) DeepCopy() *Captain { + if in == nil { + return nil + } + out := new(Captain) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Captain) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CaptainList) DeepCopyInto(out *CaptainList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Captain, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainList. +func (in *CaptainList) DeepCopy() *CaptainList { + if in == nil { + return nil + } + out := new(CaptainList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CaptainList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CaptainSpec) DeepCopyInto(out *CaptainSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainSpec. +func (in *CaptainSpec) DeepCopy() *CaptainSpec { + if in == nil { + return nil + } + out := new(CaptainSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CaptainStatus) DeepCopyInto(out *CaptainStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CaptainStatus. +func (in *CaptainStatus) DeepCopy() *CaptainStatus { + if in == nil { + return nil + } + out := new(CaptainStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirstMate) DeepCopyInto(out *FirstMate) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMate. +func (in *FirstMate) DeepCopy() *FirstMate { + if in == nil { + return nil + } + out := new(FirstMate) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FirstMate) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirstMateList) DeepCopyInto(out *FirstMateList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]FirstMate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateList. +func (in *FirstMateList) DeepCopy() *FirstMateList { + if in == nil { + return nil + } + out := new(FirstMateList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *FirstMateList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirstMateSpec) DeepCopyInto(out *FirstMateSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateSpec. +func (in *FirstMateSpec) DeepCopy() *FirstMateSpec { + if in == nil { + return nil + } + out := new(FirstMateSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FirstMateStatus) DeepCopyInto(out *FirstMateStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FirstMateStatus. +func (in *FirstMateStatus) DeepCopy() *FirstMateStatus { + if in == nil { + return nil + } + out := new(FirstMateStatus) + in.DeepCopyInto(out) + return out +} diff --git a/testdata/project-v4-addon-and-grafana/pkg/controllers/admiral_controller.go b/testdata/project-v4-addon-and-grafana/pkg/controllers/admiral_controller.go new file mode 100644 index 00000000000..b1bc5468fae --- /dev/null +++ b/testdata/project-v4-addon-and-grafana/pkg/controllers/admiral_controller.go @@ -0,0 +1,62 @@ +/* +Copyright 2022 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 controllers + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1" +) + +// AdmiralReconciler reconciles a Admiral object +type AdmiralReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=crew.testproject.org,resources=admirals,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=crew.testproject.org,resources=admirals/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=crew.testproject.org,resources=admirals/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 Admiral 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.13.0/pkg/reconcile +func (r *AdmiralReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *AdmiralReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&crewv1.Admiral{}). + Complete(r) +} diff --git a/testdata/project-v4-addon-and-grafana/pkg/controllers/captain_controller.go b/testdata/project-v4-addon-and-grafana/pkg/controllers/captain_controller.go new file mode 100644 index 00000000000..0ce3e46c824 --- /dev/null +++ b/testdata/project-v4-addon-and-grafana/pkg/controllers/captain_controller.go @@ -0,0 +1,62 @@ +/* +Copyright 2022 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 controllers + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1" +) + +// CaptainReconciler reconciles a Captain object +type CaptainReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=crew.testproject.org,resources=captains,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=crew.testproject.org,resources=captains/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=crew.testproject.org,resources=captains/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 Captain 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.13.0/pkg/reconcile +func (r *CaptainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *CaptainReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&crewv1.Captain{}). + Complete(r) +} diff --git a/testdata/project-v4-addon-and-grafana/pkg/controllers/firstmate_controller.go b/testdata/project-v4-addon-and-grafana/pkg/controllers/firstmate_controller.go new file mode 100644 index 00000000000..5a7ff3a4184 --- /dev/null +++ b/testdata/project-v4-addon-and-grafana/pkg/controllers/firstmate_controller.go @@ -0,0 +1,62 @@ +/* +Copyright 2022 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 controllers + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1" +) + +// FirstMateReconciler reconciles a FirstMate object +type FirstMateReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=crew.testproject.org,resources=firstmates,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=crew.testproject.org,resources=firstmates/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=crew.testproject.org,resources=firstmates/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 FirstMate 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.13.0/pkg/reconcile +func (r *FirstMateReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *FirstMateReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&crewv1.FirstMate{}). + Complete(r) +} diff --git a/testdata/project-v4-addon-and-grafana/controllers/suite_test.go b/testdata/project-v4-addon-and-grafana/pkg/controllers/suite_test.go similarity index 99% rename from testdata/project-v4-addon-and-grafana/controllers/suite_test.go rename to testdata/project-v4-addon-and-grafana/pkg/controllers/suite_test.go index 50f381691c3..61cb5f7538a 100644 --- a/testdata/project-v4-addon-and-grafana/controllers/suite_test.go +++ b/testdata/project-v4-addon-and-grafana/pkg/controllers/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-addon-and-grafana/pkg/api/v1" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-config/Dockerfile b/testdata/project-v4-config/Dockerfile index 8f9cca18eb6..727b965b7c4 100644 --- a/testdata/project-v4-config/Dockerfile +++ b/testdata/project-v4-config/Dockerfile @@ -13,8 +13,8 @@ RUN go mod download # Copy the go source COPY main.go main.go -COPY api/ api/ -COPY controllers/ controllers/ +COPY pkg/api/ pkg/api/ +COPY pkg/controllers/ pkg/controllers/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/testdata/project-v4-config/Makefile b/testdata/project-v4-config/Makefile index 48ac825553e..da9a937ec24 100644 --- a/testdata/project-v4-config/Makefile +++ b/testdata/project-v4-config/Makefile @@ -135,11 +135,10 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest KUSTOMIZE_VERSION ?= v4.5.5 CONTROLLER_TOOLS_VERSION ?= v0.10.0 -KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. $(KUSTOMIZE): $(LOCALBIN) - test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kustomize/kustomize/v4@$(KUSTOMIZE_VERSION) .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. diff --git a/testdata/project-v4-config/PROJECT b/testdata/project-v4-config/PROJECT index 92f6555562e..04183123cdb 100644 --- a/testdata/project-v4-config/PROJECT +++ b/testdata/project-v4-config/PROJECT @@ -12,7 +12,7 @@ resources: domain: testproject.org group: crew kind: Captain - path: sigs.k8s.io/kubebuilder/testdata/project-v4-config/api/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-config/pkg/api/v1 version: v1 webhooks: defaulting: true @@ -25,7 +25,7 @@ resources: domain: testproject.org group: crew kind: FirstMate - path: sigs.k8s.io/kubebuilder/testdata/project-v4-config/api/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-config/pkg/api/v1 version: v1 webhooks: conversion: true @@ -36,7 +36,7 @@ resources: domain: testproject.org group: crew kind: Admiral - path: sigs.k8s.io/kubebuilder/testdata/project-v4-config/api/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-config/pkg/api/v1 version: v1 webhooks: defaulting: true diff --git a/testdata/project-v4-config/main.go b/testdata/project-v4-config/main.go index 296c9d8af21..fe05132dadf 100644 --- a/testdata/project-v4-config/main.go +++ b/testdata/project-v4-config/main.go @@ -31,8 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/api/v1" - "sigs.k8s.io/kubebuilder/testdata/project-v4-config/controllers" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/pkg/api/v1" + "sigs.k8s.io/kubebuilder/testdata/project-v4-config/pkg/controllers" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-config/pkg/api/v1/admiral_types.go b/testdata/project-v4-config/pkg/api/v1/admiral_types.go new file mode 100644 index 00000000000..55d2b767089 --- /dev/null +++ b/testdata/project-v4-config/pkg/api/v1/admiral_types.go @@ -0,0 +1,65 @@ +/* +Copyright 2022 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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// AdmiralSpec defines the desired state of Admiral +type AdmiralSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of Admiral. Edit admiral_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// AdmiralStatus defines the observed state of Admiral +type AdmiralStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster + +// Admiral is the Schema for the admirals API +type Admiral struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AdmiralSpec `json:"spec,omitempty"` + Status AdmiralStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// AdmiralList contains a list of Admiral +type AdmiralList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Admiral `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Admiral{}, &AdmiralList{}) +} diff --git a/testdata/project-v4-config/api/v1/admiral_webhook.go b/testdata/project-v4-config/pkg/api/v1/admiral_webhook.go similarity index 100% rename from testdata/project-v4-config/api/v1/admiral_webhook.go rename to testdata/project-v4-config/pkg/api/v1/admiral_webhook.go diff --git a/testdata/project-v4-multigroup/apis/crew/v1/captain_types.go b/testdata/project-v4-config/pkg/api/v1/captain_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/crew/v1/captain_types.go rename to testdata/project-v4-config/pkg/api/v1/captain_types.go diff --git a/testdata/project-v4-config/api/v1/captain_webhook.go b/testdata/project-v4-config/pkg/api/v1/captain_webhook.go similarity index 100% rename from testdata/project-v4-config/api/v1/captain_webhook.go rename to testdata/project-v4-config/pkg/api/v1/captain_webhook.go diff --git a/testdata/project-v4/api/v1/firstmate_types.go b/testdata/project-v4-config/pkg/api/v1/firstmate_types.go similarity index 100% rename from testdata/project-v4/api/v1/firstmate_types.go rename to testdata/project-v4-config/pkg/api/v1/firstmate_types.go diff --git a/testdata/project-v4-config/api/v1/firstmate_webhook.go b/testdata/project-v4-config/pkg/api/v1/firstmate_webhook.go similarity index 100% rename from testdata/project-v4-config/api/v1/firstmate_webhook.go rename to testdata/project-v4-config/pkg/api/v1/firstmate_webhook.go diff --git a/testdata/project-v4-config/api/v1/groupversion_info.go b/testdata/project-v4-config/pkg/api/v1/groupversion_info.go similarity index 100% rename from testdata/project-v4-config/api/v1/groupversion_info.go rename to testdata/project-v4-config/pkg/api/v1/groupversion_info.go diff --git a/testdata/project-v4-config/api/v1/webhook_suite_test.go b/testdata/project-v4-config/pkg/api/v1/webhook_suite_test.go similarity index 97% rename from testdata/project-v4-config/api/v1/webhook_suite_test.go rename to testdata/project-v4-config/pkg/api/v1/webhook_suite_test.go index 06549f198e6..91b9bf273c6 100644 --- a/testdata/project-v4-config/api/v1/webhook_suite_test.go +++ b/testdata/project-v4-config/pkg/api/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-config/api/v1/zz_generated.deepcopy.go b/testdata/project-v4-config/pkg/api/v1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-config/api/v1/zz_generated.deepcopy.go rename to testdata/project-v4-config/pkg/api/v1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-config/controllers/admiral_controller.go b/testdata/project-v4-config/pkg/controllers/admiral_controller.go similarity index 96% rename from testdata/project-v4-config/controllers/admiral_controller.go rename to testdata/project-v4-config/pkg/controllers/admiral_controller.go index 1e510efee0f..047e932c5cc 100644 --- a/testdata/project-v4-config/controllers/admiral_controller.go +++ b/testdata/project-v4-config/pkg/controllers/admiral_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/pkg/api/v1" ) // AdmiralReconciler reconciles a Admiral object diff --git a/testdata/project-v4-config/controllers/captain_controller.go b/testdata/project-v4-config/pkg/controllers/captain_controller.go similarity index 96% rename from testdata/project-v4-config/controllers/captain_controller.go rename to testdata/project-v4-config/pkg/controllers/captain_controller.go index 6a9b4000cb3..33dad803f2c 100644 --- a/testdata/project-v4-config/controllers/captain_controller.go +++ b/testdata/project-v4-config/pkg/controllers/captain_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/pkg/api/v1" ) // CaptainReconciler reconciles a Captain object diff --git a/testdata/project-v4-config/controllers/firstmate_controller.go b/testdata/project-v4-config/pkg/controllers/firstmate_controller.go similarity index 96% rename from testdata/project-v4-config/controllers/firstmate_controller.go rename to testdata/project-v4-config/pkg/controllers/firstmate_controller.go index 88848bb1763..cd7289adcf5 100644 --- a/testdata/project-v4-config/controllers/firstmate_controller.go +++ b/testdata/project-v4-config/pkg/controllers/firstmate_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/pkg/api/v1" ) // FirstMateReconciler reconciles a FirstMate object diff --git a/testdata/project-v4-config/controllers/laker_controller.go b/testdata/project-v4-config/pkg/controllers/laker_controller.go similarity index 100% rename from testdata/project-v4-config/controllers/laker_controller.go rename to testdata/project-v4-config/pkg/controllers/laker_controller.go diff --git a/testdata/project-v4-config/controllers/suite_test.go b/testdata/project-v4-config/pkg/controllers/suite_test.go similarity index 96% rename from testdata/project-v4-config/controllers/suite_test.go rename to testdata/project-v4-config/pkg/controllers/suite_test.go index 1c61e38a95d..604f617e142 100644 --- a/testdata/project-v4-config/controllers/suite_test.go +++ b/testdata/project-v4-config/pkg/controllers/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-config/pkg/api/v1" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-multigroup/Dockerfile b/testdata/project-v4-multigroup/Dockerfile index 8e7dcf7a007..5daa1b114df 100644 --- a/testdata/project-v4-multigroup/Dockerfile +++ b/testdata/project-v4-multigroup/Dockerfile @@ -13,8 +13,8 @@ RUN go mod download # Copy the go source COPY main.go main.go -COPY apis/ apis/ -COPY controllers/ controllers/ +COPY pkg/apis/ pkg/apis/ +COPY pkg/controllers/ pkg/controllers/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/testdata/project-v4-multigroup/Makefile b/testdata/project-v4-multigroup/Makefile index 48ac825553e..da9a937ec24 100644 --- a/testdata/project-v4-multigroup/Makefile +++ b/testdata/project-v4-multigroup/Makefile @@ -135,11 +135,10 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest KUSTOMIZE_VERSION ?= v4.5.5 CONTROLLER_TOOLS_VERSION ?= v0.10.0 -KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. $(KUSTOMIZE): $(LOCALBIN) - test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kustomize/kustomize/v4@$(KUSTOMIZE_VERSION) .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. diff --git a/testdata/project-v4-multigroup/PROJECT b/testdata/project-v4-multigroup/PROJECT index ef652e90d9c..98bc02a0114 100644 --- a/testdata/project-v4-multigroup/PROJECT +++ b/testdata/project-v4-multigroup/PROJECT @@ -12,7 +12,7 @@ resources: domain: testproject.org group: crew kind: Captain - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/crew/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/crew/v1 version: v1 webhooks: defaulting: true @@ -25,7 +25,7 @@ resources: domain: testproject.org group: ship kind: Frigate - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v1beta1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v1beta1 version: v1beta1 webhooks: conversion: true @@ -36,7 +36,7 @@ resources: domain: testproject.org group: ship kind: Destroyer - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v1 version: v1 webhooks: defaulting: true @@ -47,7 +47,7 @@ resources: domain: testproject.org group: ship kind: Cruiser - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v2alpha1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1 version: v2alpha1 webhooks: validation: true @@ -59,7 +59,7 @@ resources: domain: testproject.org group: sea-creatures kind: Kraken - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/sea-creatures/v1beta1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1 version: v1beta1 - api: crdVersion: v1 @@ -68,7 +68,7 @@ resources: domain: testproject.org group: sea-creatures kind: Leviathan - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/sea-creatures/v1beta2 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2 version: v1beta2 - api: crdVersion: v1 @@ -77,7 +77,7 @@ resources: domain: testproject.org group: foo.policy kind: HealthCheckPolicy - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/foo.policy/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/foo.policy/v1 version: v1 - controller: true group: apps @@ -91,7 +91,7 @@ resources: domain: testproject.org group: foo kind: Bar - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/foo/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/foo/v1 version: v1 - api: crdVersion: v1 @@ -100,7 +100,7 @@ resources: domain: testproject.org group: fiz kind: Bar - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/fiz/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/fiz/v1 version: v1 - api: crdVersion: v1 @@ -108,7 +108,7 @@ resources: controller: true domain: testproject.org kind: Lakers - path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/v1 version: v1 webhooks: defaulting: true diff --git a/testdata/project-v4-multigroup/main.go b/testdata/project-v4-multigroup/main.go index fb53924ca8f..4586aa98091 100644 --- a/testdata/project-v4-multigroup/main.go +++ b/testdata/project-v4-multigroup/main.go @@ -31,24 +31,24 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/crew/v1" - fizv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/fiz/v1" - foopolicyv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/foo.policy/v1" - foov1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/foo/v1" - seacreaturesv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/sea-creatures/v1beta1" - seacreaturesv1beta2 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/sea-creatures/v1beta2" - shipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v1" - shipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v1beta1" - shipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v2alpha1" - testprojectorgv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/v1" - "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/controllers" - appscontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/controllers/apps" - crewcontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/controllers/crew" - fizcontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/controllers/fiz" - foocontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/controllers/foo" - foopolicycontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/controllers/foo.policy" - seacreaturescontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/controllers/sea-creatures" - shipcontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/controllers/ship" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/crew/v1" + fizv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/fiz/v1" + foopolicyv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/foo.policy/v1" + foov1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/foo/v1" + seacreaturesv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1" + seacreaturesv1beta2 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2" + shipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v1" + shipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v1beta1" + shipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1" + testprojectorgv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/v1" + "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/controllers" + appscontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/controllers/apps" + crewcontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/controllers/crew" + fizcontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/controllers/fiz" + foocontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/controllers/foo" + foopolicycontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/controllers/foo.policy" + seacreaturescontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/controllers/sea-creatures" + shipcontrollers "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/controllers/ship" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4/api/v1/captain_types.go b/testdata/project-v4-multigroup/pkg/apis/crew/v1/captain_types.go similarity index 100% rename from testdata/project-v4/api/v1/captain_types.go rename to testdata/project-v4-multigroup/pkg/apis/crew/v1/captain_types.go diff --git a/testdata/project-v4-multigroup/apis/crew/v1/captain_webhook.go b/testdata/project-v4-multigroup/pkg/apis/crew/v1/captain_webhook.go similarity index 100% rename from testdata/project-v4-multigroup/apis/crew/v1/captain_webhook.go rename to testdata/project-v4-multigroup/pkg/apis/crew/v1/captain_webhook.go diff --git a/testdata/project-v4-multigroup/apis/crew/v1/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/crew/v1/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/crew/v1/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/crew/v1/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/crew/v1/webhook_suite_test.go b/testdata/project-v4-multigroup/pkg/apis/crew/v1/webhook_suite_test.go similarity index 97% rename from testdata/project-v4-multigroup/apis/crew/v1/webhook_suite_test.go rename to testdata/project-v4-multigroup/pkg/apis/crew/v1/webhook_suite_test.go index 41c8c57de00..95d604fbf9e 100644 --- a/testdata/project-v4-multigroup/apis/crew/v1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/pkg/apis/crew/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-multigroup/apis/crew/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/crew/v1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/crew/v1/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/crew/v1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/apis/fiz/v1/bar_types.go b/testdata/project-v4-multigroup/pkg/apis/fiz/v1/bar_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/fiz/v1/bar_types.go rename to testdata/project-v4-multigroup/pkg/apis/fiz/v1/bar_types.go diff --git a/testdata/project-v4-multigroup/apis/fiz/v1/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/fiz/v1/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/fiz/v1/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/fiz/v1/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/fiz/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/fiz/v1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/fiz/v1/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/fiz/v1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/apis/foo.policy/v1/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/foo.policy/v1/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/foo.policy/v1/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/foo.policy/v1/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/foo.policy/v1/healthcheckpolicy_types.go b/testdata/project-v4-multigroup/pkg/apis/foo.policy/v1/healthcheckpolicy_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/foo.policy/v1/healthcheckpolicy_types.go rename to testdata/project-v4-multigroup/pkg/apis/foo.policy/v1/healthcheckpolicy_types.go diff --git a/testdata/project-v4-multigroup/apis/foo.policy/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/foo.policy/v1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/foo.policy/v1/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/foo.policy/v1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/apis/foo/v1/bar_types.go b/testdata/project-v4-multigroup/pkg/apis/foo/v1/bar_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/foo/v1/bar_types.go rename to testdata/project-v4-multigroup/pkg/apis/foo/v1/bar_types.go diff --git a/testdata/project-v4-multigroup/apis/foo/v1/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/foo/v1/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/foo/v1/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/foo/v1/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/foo/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/foo/v1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/foo/v1/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/foo/v1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/apis/sea-creatures/v1beta1/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/sea-creatures/v1beta1/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/sea-creatures/v1beta1/kraken_types.go b/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1/kraken_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/sea-creatures/v1beta1/kraken_types.go rename to testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1/kraken_types.go diff --git a/testdata/project-v4-multigroup/apis/sea-creatures/v1beta1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/sea-creatures/v1beta1/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/apis/sea-creatures/v1beta2/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/sea-creatures/v1beta2/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/sea-creatures/v1beta2/leviathan_types.go b/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2/leviathan_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/sea-creatures/v1beta2/leviathan_types.go rename to testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2/leviathan_types.go diff --git a/testdata/project-v4-multigroup/apis/sea-creatures/v1beta2/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/sea-creatures/v1beta2/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/apis/ship/v1/destroyer_types.go b/testdata/project-v4-multigroup/pkg/apis/ship/v1/destroyer_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v1/destroyer_types.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v1/destroyer_types.go diff --git a/testdata/project-v4-multigroup/apis/ship/v1/destroyer_webhook.go b/testdata/project-v4-multigroup/pkg/apis/ship/v1/destroyer_webhook.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v1/destroyer_webhook.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v1/destroyer_webhook.go diff --git a/testdata/project-v4-multigroup/apis/ship/v1/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/ship/v1/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v1/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v1/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/ship/v1/webhook_suite_test.go b/testdata/project-v4-multigroup/pkg/apis/ship/v1/webhook_suite_test.go similarity index 97% rename from testdata/project-v4-multigroup/apis/ship/v1/webhook_suite_test.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v1/webhook_suite_test.go index af22a1df03c..b4228bbd462 100644 --- a/testdata/project-v4-multigroup/apis/ship/v1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/pkg/apis/ship/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-multigroup/apis/ship/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/ship/v1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v1/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/apis/ship/v1beta1/frigate_types.go b/testdata/project-v4-multigroup/pkg/apis/ship/v1beta1/frigate_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v1beta1/frigate_types.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v1beta1/frigate_types.go diff --git a/testdata/project-v4-multigroup/apis/ship/v1beta1/frigate_webhook.go b/testdata/project-v4-multigroup/pkg/apis/ship/v1beta1/frigate_webhook.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v1beta1/frigate_webhook.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v1beta1/frigate_webhook.go diff --git a/testdata/project-v4-multigroup/apis/ship/v1beta1/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/ship/v1beta1/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v1beta1/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v1beta1/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/ship/v1beta1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/ship/v1beta1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v1beta1/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v1beta1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/apis/ship/v2alpha1/cruiser_types.go b/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/cruiser_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v2alpha1/cruiser_types.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/cruiser_types.go diff --git a/testdata/project-v4-multigroup/apis/ship/v2alpha1/cruiser_webhook.go b/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/cruiser_webhook.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v2alpha1/cruiser_webhook.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/cruiser_webhook.go diff --git a/testdata/project-v4-multigroup/apis/ship/v2alpha1/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v2alpha1/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/ship/v2alpha1/webhook_suite_test.go b/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/webhook_suite_test.go similarity index 97% rename from testdata/project-v4-multigroup/apis/ship/v2alpha1/webhook_suite_test.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/webhook_suite_test.go index 23748cbe171..b31858834bf 100644 --- a/testdata/project-v4-multigroup/apis/ship/v2alpha1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-multigroup/apis/ship/v2alpha1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/ship/v2alpha1/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/apis/v1/groupversion_info.go b/testdata/project-v4-multigroup/pkg/apis/v1/groupversion_info.go similarity index 100% rename from testdata/project-v4-multigroup/apis/v1/groupversion_info.go rename to testdata/project-v4-multigroup/pkg/apis/v1/groupversion_info.go diff --git a/testdata/project-v4-multigroup/apis/v1/lakers_types.go b/testdata/project-v4-multigroup/pkg/apis/v1/lakers_types.go similarity index 100% rename from testdata/project-v4-multigroup/apis/v1/lakers_types.go rename to testdata/project-v4-multigroup/pkg/apis/v1/lakers_types.go diff --git a/testdata/project-v4-multigroup/apis/v1/lakers_webhook.go b/testdata/project-v4-multigroup/pkg/apis/v1/lakers_webhook.go similarity index 100% rename from testdata/project-v4-multigroup/apis/v1/lakers_webhook.go rename to testdata/project-v4-multigroup/pkg/apis/v1/lakers_webhook.go diff --git a/testdata/project-v4-multigroup/apis/v1/webhook_suite_test.go b/testdata/project-v4-multigroup/pkg/apis/v1/webhook_suite_test.go similarity index 97% rename from testdata/project-v4-multigroup/apis/v1/webhook_suite_test.go rename to testdata/project-v4-multigroup/pkg/apis/v1/webhook_suite_test.go index 0b451a74262..1af25ebb4d0 100644 --- a/testdata/project-v4-multigroup/apis/v1/webhook_suite_test.go +++ b/testdata/project-v4-multigroup/pkg/apis/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-multigroup/apis/v1/zz_generated.deepcopy.go b/testdata/project-v4-multigroup/pkg/apis/v1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4-multigroup/apis/v1/zz_generated.deepcopy.go rename to testdata/project-v4-multigroup/pkg/apis/v1/zz_generated.deepcopy.go diff --git a/testdata/project-v4-multigroup/controllers/apps/deployment_controller.go b/testdata/project-v4-multigroup/pkg/controllers/apps/deployment_controller.go similarity index 100% rename from testdata/project-v4-multigroup/controllers/apps/deployment_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/apps/deployment_controller.go diff --git a/testdata/project-v4-multigroup/controllers/apps/suite_test.go b/testdata/project-v4-multigroup/pkg/controllers/apps/suite_test.go similarity index 100% rename from testdata/project-v4-multigroup/controllers/apps/suite_test.go rename to testdata/project-v4-multigroup/pkg/controllers/apps/suite_test.go diff --git a/testdata/project-v4-multigroup/controllers/crew/captain_controller.go b/testdata/project-v4-multigroup/pkg/controllers/crew/captain_controller.go similarity index 99% rename from testdata/project-v4-multigroup/controllers/crew/captain_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/crew/captain_controller.go index 329a36d8e7f..445299d7383 100644 --- a/testdata/project-v4-multigroup/controllers/crew/captain_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/crew/captain_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/crew/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/crew/v1" ) // CaptainReconciler reconciles a Captain object diff --git a/testdata/project-v4-multigroup/controllers/crew/suite_test.go b/testdata/project-v4-multigroup/pkg/controllers/crew/suite_test.go similarity index 99% rename from testdata/project-v4-multigroup/controllers/crew/suite_test.go rename to testdata/project-v4-multigroup/pkg/controllers/crew/suite_test.go index 686e7eb5a0e..a63ef89810b 100644 --- a/testdata/project-v4-multigroup/controllers/crew/suite_test.go +++ b/testdata/project-v4-multigroup/pkg/controllers/crew/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/crew/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/crew/v1" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-multigroup/controllers/fiz/bar_controller.go b/testdata/project-v4-multigroup/pkg/controllers/fiz/bar_controller.go similarity index 96% rename from testdata/project-v4-multigroup/controllers/fiz/bar_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/fiz/bar_controller.go index b7fe0b159c0..6c6fe75155d 100644 --- a/testdata/project-v4-multigroup/controllers/fiz/bar_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/fiz/bar_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - fizv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/fiz/v1" + fizv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/fiz/v1" ) // BarReconciler reconciles a Bar object diff --git a/testdata/project-v4-multigroup/controllers/fiz/suite_test.go b/testdata/project-v4-multigroup/pkg/controllers/fiz/suite_test.go similarity index 96% rename from testdata/project-v4-multigroup/controllers/fiz/suite_test.go rename to testdata/project-v4-multigroup/pkg/controllers/fiz/suite_test.go index 0615d928f66..7b9d69f0f18 100644 --- a/testdata/project-v4-multigroup/controllers/fiz/suite_test.go +++ b/testdata/project-v4-multigroup/pkg/controllers/fiz/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - fizv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/fiz/v1" + fizv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/fiz/v1" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-multigroup/controllers/foo.policy/healthcheckpolicy_controller.go b/testdata/project-v4-multigroup/pkg/controllers/foo.policy/healthcheckpolicy_controller.go similarity index 98% rename from testdata/project-v4-multigroup/controllers/foo.policy/healthcheckpolicy_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/foo.policy/healthcheckpolicy_controller.go index 322f8024808..1409d1220a9 100644 --- a/testdata/project-v4-multigroup/controllers/foo.policy/healthcheckpolicy_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/foo.policy/healthcheckpolicy_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - foopolicyv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/foo.policy/v1" + foopolicyv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/foo.policy/v1" ) // HealthCheckPolicyReconciler reconciles a HealthCheckPolicy object diff --git a/testdata/project-v4-multigroup/controllers/foo.policy/suite_test.go b/testdata/project-v4-multigroup/pkg/controllers/foo.policy/suite_test.go similarity index 98% rename from testdata/project-v4-multigroup/controllers/foo.policy/suite_test.go rename to testdata/project-v4-multigroup/pkg/controllers/foo.policy/suite_test.go index 213de495121..21dd1951681 100644 --- a/testdata/project-v4-multigroup/controllers/foo.policy/suite_test.go +++ b/testdata/project-v4-multigroup/pkg/controllers/foo.policy/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - foopolicyv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/foo.policy/v1" + foopolicyv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/foo.policy/v1" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-multigroup/controllers/foo/bar_controller.go b/testdata/project-v4-multigroup/pkg/controllers/foo/bar_controller.go similarity index 96% rename from testdata/project-v4-multigroup/controllers/foo/bar_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/foo/bar_controller.go index 0733ca97d7d..96467a7420d 100644 --- a/testdata/project-v4-multigroup/controllers/foo/bar_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/foo/bar_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - foov1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/foo/v1" + foov1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/foo/v1" ) // BarReconciler reconciles a Bar object diff --git a/testdata/project-v4-multigroup/controllers/foo/suite_test.go b/testdata/project-v4-multigroup/pkg/controllers/foo/suite_test.go similarity index 96% rename from testdata/project-v4-multigroup/controllers/foo/suite_test.go rename to testdata/project-v4-multigroup/pkg/controllers/foo/suite_test.go index d38918fbd67..d596853b4d2 100644 --- a/testdata/project-v4-multigroup/controllers/foo/suite_test.go +++ b/testdata/project-v4-multigroup/pkg/controllers/foo/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - foov1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/foo/v1" + foov1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/foo/v1" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-multigroup/controllers/lakers_controller.go b/testdata/project-v4-multigroup/pkg/controllers/lakers_controller.go similarity index 98% rename from testdata/project-v4-multigroup/controllers/lakers_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/lakers_controller.go index 2d68e1787dd..5483f38e275 100644 --- a/testdata/project-v4-multigroup/controllers/lakers_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/lakers_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - testprojectorgv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/v1" + testprojectorgv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/v1" ) // LakersReconciler reconciles a Lakers object diff --git a/testdata/project-v4-multigroup/controllers/sea-creatures/kraken_controller.go b/testdata/project-v4-multigroup/pkg/controllers/sea-creatures/kraken_controller.go similarity index 98% rename from testdata/project-v4-multigroup/controllers/sea-creatures/kraken_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/sea-creatures/kraken_controller.go index ba0c62e00a2..0fc44b98dbb 100644 --- a/testdata/project-v4-multigroup/controllers/sea-creatures/kraken_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/sea-creatures/kraken_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - seacreaturesv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/sea-creatures/v1beta1" + seacreaturesv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1" ) // KrakenReconciler reconciles a Kraken object diff --git a/testdata/project-v4-multigroup/controllers/sea-creatures/leviathan_controller.go b/testdata/project-v4-multigroup/pkg/controllers/sea-creatures/leviathan_controller.go similarity index 98% rename from testdata/project-v4-multigroup/controllers/sea-creatures/leviathan_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/sea-creatures/leviathan_controller.go index 42faec534af..7f8307e08f2 100644 --- a/testdata/project-v4-multigroup/controllers/sea-creatures/leviathan_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/sea-creatures/leviathan_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - seacreaturesv1beta2 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/sea-creatures/v1beta2" + seacreaturesv1beta2 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2" ) // LeviathanReconciler reconciles a Leviathan object diff --git a/testdata/project-v4-multigroup/controllers/sea-creatures/suite_test.go b/testdata/project-v4-multigroup/pkg/controllers/sea-creatures/suite_test.go similarity index 96% rename from testdata/project-v4-multigroup/controllers/sea-creatures/suite_test.go rename to testdata/project-v4-multigroup/pkg/controllers/sea-creatures/suite_test.go index 968ad7e2587..8462c02a517 100644 --- a/testdata/project-v4-multigroup/controllers/sea-creatures/suite_test.go +++ b/testdata/project-v4-multigroup/pkg/controllers/sea-creatures/suite_test.go @@ -30,8 +30,8 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - seacreaturesv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/sea-creatures/v1beta1" - seacreaturesv1beta2 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/sea-creatures/v1beta2" + seacreaturesv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta1" + seacreaturesv1beta2 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/sea-creatures/v1beta2" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-multigroup/controllers/ship/cruiser_controller.go b/testdata/project-v4-multigroup/pkg/controllers/ship/cruiser_controller.go similarity index 98% rename from testdata/project-v4-multigroup/controllers/ship/cruiser_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/ship/cruiser_controller.go index 7d42e43d68b..56793372f41 100644 --- a/testdata/project-v4-multigroup/controllers/ship/cruiser_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/ship/cruiser_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - shipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v2alpha1" + shipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1" ) // CruiserReconciler reconciles a Cruiser object diff --git a/testdata/project-v4-multigroup/controllers/ship/destroyer_controller.go b/testdata/project-v4-multigroup/pkg/controllers/ship/destroyer_controller.go similarity index 99% rename from testdata/project-v4-multigroup/controllers/ship/destroyer_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/ship/destroyer_controller.go index c945783157d..d9a715138b9 100644 --- a/testdata/project-v4-multigroup/controllers/ship/destroyer_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/ship/destroyer_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - shipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v1" + shipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v1" ) // DestroyerReconciler reconciles a Destroyer object diff --git a/testdata/project-v4-multigroup/controllers/ship/frigate_controller.go b/testdata/project-v4-multigroup/pkg/controllers/ship/frigate_controller.go similarity index 98% rename from testdata/project-v4-multigroup/controllers/ship/frigate_controller.go rename to testdata/project-v4-multigroup/pkg/controllers/ship/frigate_controller.go index 11dad23d5cc..4d8d93c4811 100644 --- a/testdata/project-v4-multigroup/controllers/ship/frigate_controller.go +++ b/testdata/project-v4-multigroup/pkg/controllers/ship/frigate_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - shipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v1beta1" + shipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v1beta1" ) // FrigateReconciler reconciles a Frigate object diff --git a/testdata/project-v4-multigroup/controllers/ship/suite_test.go b/testdata/project-v4-multigroup/pkg/controllers/ship/suite_test.go similarity index 97% rename from testdata/project-v4-multigroup/controllers/ship/suite_test.go rename to testdata/project-v4-multigroup/pkg/controllers/ship/suite_test.go index 1f3d1f1bfc4..dccfd099f00 100644 --- a/testdata/project-v4-multigroup/controllers/ship/suite_test.go +++ b/testdata/project-v4-multigroup/pkg/controllers/ship/suite_test.go @@ -30,9 +30,9 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - shipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v1" - shipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v1beta1" - shipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/ship/v2alpha1" + shipv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v1" + shipv1beta1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v1beta1" + shipv2alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/ship/v2alpha1" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-multigroup/controllers/suite_test.go b/testdata/project-v4-multigroup/pkg/controllers/suite_test.go similarity index 98% rename from testdata/project-v4-multigroup/controllers/suite_test.go rename to testdata/project-v4-multigroup/pkg/controllers/suite_test.go index 2cc3e726507..0247f6c2c14 100644 --- a/testdata/project-v4-multigroup/controllers/suite_test.go +++ b/testdata/project-v4-multigroup/pkg/controllers/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - testprojectorgv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/apis/v1" + testprojectorgv1 "sigs.k8s.io/kubebuilder/testdata/project-v4-multigroup/pkg/apis/v1" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-with-deploy-image/Dockerfile b/testdata/project-v4-with-deploy-image/Dockerfile index 8f9cca18eb6..727b965b7c4 100644 --- a/testdata/project-v4-with-deploy-image/Dockerfile +++ b/testdata/project-v4-with-deploy-image/Dockerfile @@ -13,8 +13,8 @@ RUN go mod download # Copy the go source COPY main.go main.go -COPY api/ api/ -COPY controllers/ controllers/ +COPY pkg/api/ pkg/api/ +COPY pkg/controllers/ pkg/controllers/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/testdata/project-v4-with-deploy-image/Makefile b/testdata/project-v4-with-deploy-image/Makefile index 48ac825553e..da9a937ec24 100644 --- a/testdata/project-v4-with-deploy-image/Makefile +++ b/testdata/project-v4-with-deploy-image/Makefile @@ -135,11 +135,10 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest KUSTOMIZE_VERSION ?= v4.5.5 CONTROLLER_TOOLS_VERSION ?= v0.10.0 -KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. $(KUSTOMIZE): $(LOCALBIN) - test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kustomize/kustomize/v4@$(KUSTOMIZE_VERSION) .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. diff --git a/testdata/project-v4-with-deploy-image/PROJECT b/testdata/project-v4-with-deploy-image/PROJECT index ae6a0353e0a..bcd0bc5fd1e 100644 --- a/testdata/project-v4-with-deploy-image/PROJECT +++ b/testdata/project-v4-with-deploy-image/PROJECT @@ -29,7 +29,7 @@ resources: domain: testproject.org group: example.com kind: Memcached - path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1 version: v1alpha1 webhooks: validation: true @@ -41,6 +41,6 @@ resources: domain: testproject.org group: example.com kind: Busybox - path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1 version: v1alpha1 version: "3" diff --git a/testdata/project-v4-with-deploy-image/api/v1alpha1/zz_generated.deepcopy.go b/testdata/project-v4-with-deploy-image/api/v1alpha1/zz_generated.deepcopy.go index 264d519dd7f..64436886942 100644 --- a/testdata/project-v4-with-deploy-image/api/v1alpha1/zz_generated.deepcopy.go +++ b/testdata/project-v4-with-deploy-image/api/v1alpha1/zz_generated.deepcopy.go @@ -22,8 +22,7 @@ limitations under the License. package v1alpha1 import ( - "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -85,43 +84,6 @@ func (in *BusyboxList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BusyboxSpec) DeepCopyInto(out *BusyboxSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxSpec. -func (in *BusyboxSpec) DeepCopy() *BusyboxSpec { - if in == nil { - return nil - } - out := new(BusyboxSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BusyboxStatus) DeepCopyInto(out *BusyboxStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxStatus. -func (in *BusyboxStatus) DeepCopy() *BusyboxStatus { - if in == nil { - return nil - } - out := new(BusyboxStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Memcached) DeepCopyInto(out *Memcached) { *out = *in @@ -180,40 +142,3 @@ func (in *MemcachedList) DeepCopyObject() runtime.Object { } return nil } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MemcachedSpec) DeepCopyInto(out *MemcachedSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec. -func (in *MemcachedSpec) DeepCopy() *MemcachedSpec { - if in == nil { - return nil - } - out := new(MemcachedSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *MemcachedStatus) DeepCopyInto(out *MemcachedStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]v1.Condition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedStatus. -func (in *MemcachedStatus) DeepCopy() *MemcachedStatus { - if in == nil { - return nil - } - out := new(MemcachedStatus) - in.DeepCopyInto(out) - return out -} diff --git a/testdata/project-v4-with-deploy-image/config/crd/bases/_.yaml b/testdata/project-v4-with-deploy-image/config/crd/bases/_.yaml new file mode 100644 index 00000000000..25f6032cf97 --- /dev/null +++ b/testdata/project-v4-with-deploy-image/config/crd/bases/_.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.10.0 + creationTimestamp: null +spec: + group: "" + names: + kind: "" + plural: "" + scope: "" + versions: null diff --git a/testdata/project-v4-with-deploy-image/config/crd/bases/example.com.testproject.org_busyboxes.yaml b/testdata/project-v4-with-deploy-image/config/crd/bases/example.com.testproject.org_busyboxes.yaml index cf97e148058..ccc8faf4242 100644 --- a/testdata/project-v4-with-deploy-image/config/crd/bases/example.com.testproject.org_busyboxes.yaml +++ b/testdata/project-v4-with-deploy-image/config/crd/bases/example.com.testproject.org_busyboxes.yaml @@ -35,86 +35,13 @@ spec: spec: description: BusyboxSpec defines the desired state of Busybox properties: - size: - description: 'Size defines the number of Busybox instances The following - markers will use OpenAPI v3 schema to validate the value More info: - https://book.kubebuilder.io/reference/markers/crd-validation.html' - format: int32 - maximum: 3 - minimum: 1 - type: integer + foo: + description: Foo is an example field of Busybox. Edit busybox_types.go + to remove/update + type: string type: object status: description: BusyboxStatus defines the observed state of Busybox - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array type: object type: object served: true diff --git a/testdata/project-v4-with-deploy-image/config/crd/bases/example.com.testproject.org_memcacheds.yaml b/testdata/project-v4-with-deploy-image/config/crd/bases/example.com.testproject.org_memcacheds.yaml index 4e1cbc4b9fe..877b8bcf9a8 100644 --- a/testdata/project-v4-with-deploy-image/config/crd/bases/example.com.testproject.org_memcacheds.yaml +++ b/testdata/project-v4-with-deploy-image/config/crd/bases/example.com.testproject.org_memcacheds.yaml @@ -35,91 +35,13 @@ spec: spec: description: MemcachedSpec defines the desired state of Memcached properties: - containerPort: - description: Port defines the port that will be used to init the container - with the image - format: int32 - type: integer - size: - description: 'Size defines the number of Memcached instances The following - markers will use OpenAPI v3 schema to validate the value More info: - https://book.kubebuilder.io/reference/markers/crd-validation.html' - format: int32 - maximum: 3 - minimum: 1 - type: integer + foo: + description: Foo is an example field of Memcached. Edit memcached_types.go + to remove/update + type: string type: object status: description: MemcachedStatus defines the observed state of Memcached - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array type: object type: object served: true diff --git a/testdata/project-v4-with-deploy-image/config/crd/patches/cainjection_in_busyboxes.yaml b/testdata/project-v4-with-deploy-image/config/crd/patches/cainjection_in_busyboxes.yaml index 6b2cff3c811..5f6b0384f48 100644 --- a/testdata/project-v4-with-deploy-image/config/crd/patches/cainjection_in_busyboxes.yaml +++ b/testdata/project-v4-with-deploy-image/config/crd/patches/cainjection_in_busyboxes.yaml @@ -3,5 +3,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME name: busyboxes.example.com.testproject.org diff --git a/testdata/project-v4-with-deploy-image/config/crd/patches/cainjection_in_memcacheds.yaml b/testdata/project-v4-with-deploy-image/config/crd/patches/cainjection_in_memcacheds.yaml index 7f5b50acfba..5b9e839364d 100644 --- a/testdata/project-v4-with-deploy-image/config/crd/patches/cainjection_in_memcacheds.yaml +++ b/testdata/project-v4-with-deploy-image/config/crd/patches/cainjection_in_memcacheds.yaml @@ -3,5 +3,5 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME name: memcacheds.example.com.testproject.org diff --git a/testdata/project-v4-with-deploy-image/main.go b/testdata/project-v4-with-deploy-image/main.go index 59d643f1727..656cbe5a4c4 100644 --- a/testdata/project-v4-with-deploy-image/main.go +++ b/testdata/project-v4-with-deploy-image/main.go @@ -31,8 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1" - "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/controllers" + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1" + "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/pkg/controllers" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/busybox_types.go b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/busybox_types.go new file mode 100644 index 00000000000..5acb2597f6c --- /dev/null +++ b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/busybox_types.go @@ -0,0 +1,64 @@ +/* +Copyright 2022 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// BusyboxSpec defines the desired state of Busybox +type BusyboxSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of Busybox. Edit busybox_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// BusyboxStatus defines the observed state of Busybox +type BusyboxStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Busybox is the Schema for the busyboxes API +type Busybox struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BusyboxSpec `json:"spec,omitempty"` + Status BusyboxStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// BusyboxList contains a list of Busybox +type BusyboxList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Busybox `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Busybox{}, &BusyboxList{}) +} diff --git a/testdata/project-v4-with-deploy-image/api/v1alpha1/groupversion_info.go b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/groupversion_info.go similarity index 100% rename from testdata/project-v4-with-deploy-image/api/v1alpha1/groupversion_info.go rename to testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/groupversion_info.go diff --git a/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/memcached_types.go b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/memcached_types.go new file mode 100644 index 00000000000..c14e4643bc2 --- /dev/null +++ b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/memcached_types.go @@ -0,0 +1,64 @@ +/* +Copyright 2022 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 v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// MemcachedSpec defines the desired state of Memcached +type MemcachedSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of Memcached. Edit memcached_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// MemcachedStatus defines the observed state of Memcached +type MemcachedStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Memcached is the Schema for the memcacheds API +type Memcached struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec MemcachedSpec `json:"spec,omitempty"` + Status MemcachedStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// MemcachedList contains a list of Memcached +type MemcachedList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Memcached `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Memcached{}, &MemcachedList{}) +} diff --git a/testdata/project-v4-with-deploy-image/api/v1alpha1/memcached_webhook.go b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/memcached_webhook.go similarity index 100% rename from testdata/project-v4-with-deploy-image/api/v1alpha1/memcached_webhook.go rename to testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/memcached_webhook.go diff --git a/testdata/project-v4-with-deploy-image/api/v1alpha1/webhook_suite_test.go b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/webhook_suite_test.go similarity index 97% rename from testdata/project-v4-with-deploy-image/api/v1alpha1/webhook_suite_test.go rename to testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/webhook_suite_test.go index 91bbf3d07d5..9e3757a42f3 100644 --- a/testdata/project-v4-with-deploy-image/api/v1alpha1/webhook_suite_test.go +++ b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/zz_generated.deepcopy.go b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..d7d55728ce3 --- /dev/null +++ b/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,204 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 2022 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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Busybox) DeepCopyInto(out *Busybox) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Busybox. +func (in *Busybox) DeepCopy() *Busybox { + if in == nil { + return nil + } + out := new(Busybox) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Busybox) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BusyboxList) DeepCopyInto(out *BusyboxList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Busybox, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxList. +func (in *BusyboxList) DeepCopy() *BusyboxList { + if in == nil { + return nil + } + out := new(BusyboxList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BusyboxList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BusyboxSpec) DeepCopyInto(out *BusyboxSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxSpec. +func (in *BusyboxSpec) DeepCopy() *BusyboxSpec { + if in == nil { + return nil + } + out := new(BusyboxSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BusyboxStatus) DeepCopyInto(out *BusyboxStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BusyboxStatus. +func (in *BusyboxStatus) DeepCopy() *BusyboxStatus { + if in == nil { + return nil + } + out := new(BusyboxStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Memcached) DeepCopyInto(out *Memcached) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Memcached. +func (in *Memcached) DeepCopy() *Memcached { + if in == nil { + return nil + } + out := new(Memcached) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Memcached) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedList) DeepCopyInto(out *MemcachedList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Memcached, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedList. +func (in *MemcachedList) DeepCopy() *MemcachedList { + if in == nil { + return nil + } + out := new(MemcachedList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *MemcachedList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedSpec) DeepCopyInto(out *MemcachedSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedSpec. +func (in *MemcachedSpec) DeepCopy() *MemcachedSpec { + if in == nil { + return nil + } + out := new(MemcachedSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MemcachedStatus) DeepCopyInto(out *MemcachedStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MemcachedStatus. +func (in *MemcachedStatus) DeepCopy() *MemcachedStatus { + if in == nil { + return nil + } + out := new(MemcachedStatus) + in.DeepCopyInto(out) + return out +} diff --git a/testdata/project-v4-with-deploy-image/controllers/busybox_controller.go b/testdata/project-v4-with-deploy-image/pkg/controllers/busybox_controller.go similarity index 99% rename from testdata/project-v4-with-deploy-image/controllers/busybox_controller.go rename to testdata/project-v4-with-deploy-image/pkg/controllers/busybox_controller.go index 28a2d64c93f..9775b84b691 100644 --- a/testdata/project-v4-with-deploy-image/controllers/busybox_controller.go +++ b/testdata/project-v4-with-deploy-image/pkg/controllers/busybox_controller.go @@ -36,7 +36,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" - examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1" + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1" ) const busyboxFinalizer = "example.com.testproject.org/finalizer" diff --git a/testdata/project-v4-with-deploy-image/controllers/busybox_controller_test.go b/testdata/project-v4-with-deploy-image/pkg/controllers/busybox_controller_test.go similarity index 99% rename from testdata/project-v4-with-deploy-image/controllers/busybox_controller_test.go rename to testdata/project-v4-with-deploy-image/pkg/controllers/busybox_controller_test.go index 655aaaac694..1189ae5bcfd 100644 --- a/testdata/project-v4-with-deploy-image/controllers/busybox_controller_test.go +++ b/testdata/project-v4-with-deploy-image/pkg/controllers/busybox_controller_test.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/reconcile" - examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1" + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1" ) var _ = Describe("Busybox controller", func() { diff --git a/testdata/project-v4-with-deploy-image/controllers/memcached_controller.go b/testdata/project-v4-with-deploy-image/pkg/controllers/memcached_controller.go similarity index 99% rename from testdata/project-v4-with-deploy-image/controllers/memcached_controller.go rename to testdata/project-v4-with-deploy-image/pkg/controllers/memcached_controller.go index 1b3afade2b5..c386cae381f 100644 --- a/testdata/project-v4-with-deploy-image/controllers/memcached_controller.go +++ b/testdata/project-v4-with-deploy-image/pkg/controllers/memcached_controller.go @@ -36,7 +36,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" - examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1" + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1" ) const memcachedFinalizer = "example.com.testproject.org/finalizer" diff --git a/testdata/project-v4-with-deploy-image/controllers/memcached_controller_test.go b/testdata/project-v4-with-deploy-image/pkg/controllers/memcached_controller_test.go similarity index 99% rename from testdata/project-v4-with-deploy-image/controllers/memcached_controller_test.go rename to testdata/project-v4-with-deploy-image/pkg/controllers/memcached_controller_test.go index cbedf08c7e7..97dd4582f30 100644 --- a/testdata/project-v4-with-deploy-image/controllers/memcached_controller_test.go +++ b/testdata/project-v4-with-deploy-image/pkg/controllers/memcached_controller_test.go @@ -31,7 +31,7 @@ import ( "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/reconcile" - examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1" + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1" ) var _ = Describe("Memcached controller", func() { diff --git a/testdata/project-v4-with-deploy-image/controllers/suite_test.go b/testdata/project-v4-with-deploy-image/pkg/controllers/suite_test.go similarity index 98% rename from testdata/project-v4-with-deploy-image/controllers/suite_test.go rename to testdata/project-v4-with-deploy-image/pkg/controllers/suite_test.go index d98a393008f..d50021a3a44 100644 --- a/testdata/project-v4-with-deploy-image/controllers/suite_test.go +++ b/testdata/project-v4-with-deploy-image/pkg/controllers/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/api/v1alpha1" + examplecomv1alpha1 "sigs.k8s.io/kubebuilder/testdata/project-v4-with-deploy-image/pkg/api/v1alpha1" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4/Dockerfile b/testdata/project-v4/Dockerfile index 8f9cca18eb6..727b965b7c4 100644 --- a/testdata/project-v4/Dockerfile +++ b/testdata/project-v4/Dockerfile @@ -13,8 +13,8 @@ RUN go mod download # Copy the go source COPY main.go main.go -COPY api/ api/ -COPY controllers/ controllers/ +COPY pkg/api/ pkg/api/ +COPY pkg/controllers/ pkg/controllers/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/testdata/project-v4/Makefile b/testdata/project-v4/Makefile index 48ac825553e..da9a937ec24 100644 --- a/testdata/project-v4/Makefile +++ b/testdata/project-v4/Makefile @@ -135,11 +135,10 @@ ENVTEST ?= $(LOCALBIN)/setup-envtest KUSTOMIZE_VERSION ?= v4.5.5 CONTROLLER_TOOLS_VERSION ?= v0.10.0 -KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. $(KUSTOMIZE): $(LOCALBIN) - test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + test -s $(LOCALBIN)/kustomize || GOBIN=$(LOCALBIN) go install sigs.k8s.io/kustomize/kustomize/v4@$(KUSTOMIZE_VERSION) .PHONY: controller-gen controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. diff --git a/testdata/project-v4/PROJECT b/testdata/project-v4/PROJECT index 2fdc4c243f0..460bf5d0348 100644 --- a/testdata/project-v4/PROJECT +++ b/testdata/project-v4/PROJECT @@ -11,7 +11,7 @@ resources: domain: testproject.org group: crew kind: Captain - path: sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4/pkg/api/v1 version: v1 webhooks: defaulting: true @@ -24,7 +24,7 @@ resources: domain: testproject.org group: crew kind: FirstMate - path: sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4/pkg/api/v1 version: v1 webhooks: conversion: true @@ -35,7 +35,7 @@ resources: domain: testproject.org group: crew kind: Admiral - path: sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1 + path: sigs.k8s.io/kubebuilder/testdata/project-v4/pkg/api/v1 plural: admirales version: v1 webhooks: diff --git a/testdata/project-v4/main.go b/testdata/project-v4/main.go index 1c44a24f4dc..56d01cf15e1 100644 --- a/testdata/project-v4/main.go +++ b/testdata/project-v4/main.go @@ -31,8 +31,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" - "sigs.k8s.io/kubebuilder/testdata/project-v4/controllers" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/pkg/api/v1" + "sigs.k8s.io/kubebuilder/testdata/project-v4/pkg/controllers" //+kubebuilder:scaffold:imports ) diff --git a/testdata/project-v4/api/v1/admiral_types.go b/testdata/project-v4/pkg/api/v1/admiral_types.go similarity index 100% rename from testdata/project-v4/api/v1/admiral_types.go rename to testdata/project-v4/pkg/api/v1/admiral_types.go diff --git a/testdata/project-v4/api/v1/admiral_webhook.go b/testdata/project-v4/pkg/api/v1/admiral_webhook.go similarity index 100% rename from testdata/project-v4/api/v1/admiral_webhook.go rename to testdata/project-v4/pkg/api/v1/admiral_webhook.go diff --git a/testdata/project-v4/pkg/api/v1/captain_types.go b/testdata/project-v4/pkg/api/v1/captain_types.go new file mode 100644 index 00000000000..e81debb719c --- /dev/null +++ b/testdata/project-v4/pkg/api/v1/captain_types.go @@ -0,0 +1,64 @@ +/* +Copyright 2022 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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// CaptainSpec defines the desired state of Captain +type CaptainSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of Captain. Edit captain_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// CaptainStatus defines the observed state of Captain +type CaptainStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Captain is the Schema for the captains API +type Captain struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec CaptainSpec `json:"spec,omitempty"` + Status CaptainStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// CaptainList contains a list of Captain +type CaptainList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Captain `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Captain{}, &CaptainList{}) +} diff --git a/testdata/project-v4/api/v1/captain_webhook.go b/testdata/project-v4/pkg/api/v1/captain_webhook.go similarity index 100% rename from testdata/project-v4/api/v1/captain_webhook.go rename to testdata/project-v4/pkg/api/v1/captain_webhook.go diff --git a/testdata/project-v4/pkg/api/v1/firstmate_types.go b/testdata/project-v4/pkg/api/v1/firstmate_types.go new file mode 100644 index 00000000000..7515759833a --- /dev/null +++ b/testdata/project-v4/pkg/api/v1/firstmate_types.go @@ -0,0 +1,64 @@ +/* +Copyright 2022 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 v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// FirstMateSpec defines the desired state of FirstMate +type FirstMateSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of FirstMate. Edit firstmate_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// FirstMateStatus defines the observed state of FirstMate +type FirstMateStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// FirstMate is the Schema for the firstmates API +type FirstMate struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec FirstMateSpec `json:"spec,omitempty"` + Status FirstMateStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// FirstMateList contains a list of FirstMate +type FirstMateList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []FirstMate `json:"items"` +} + +func init() { + SchemeBuilder.Register(&FirstMate{}, &FirstMateList{}) +} diff --git a/testdata/project-v4/api/v1/firstmate_webhook.go b/testdata/project-v4/pkg/api/v1/firstmate_webhook.go similarity index 100% rename from testdata/project-v4/api/v1/firstmate_webhook.go rename to testdata/project-v4/pkg/api/v1/firstmate_webhook.go diff --git a/testdata/project-v4/api/v1/groupversion_info.go b/testdata/project-v4/pkg/api/v1/groupversion_info.go similarity index 100% rename from testdata/project-v4/api/v1/groupversion_info.go rename to testdata/project-v4/pkg/api/v1/groupversion_info.go diff --git a/testdata/project-v4/api/v1/webhook_suite_test.go b/testdata/project-v4/pkg/api/v1/webhook_suite_test.go similarity index 97% rename from testdata/project-v4/api/v1/webhook_suite_test.go rename to testdata/project-v4/pkg/api/v1/webhook_suite_test.go index 06549f198e6..91b9bf273c6 100644 --- a/testdata/project-v4/api/v1/webhook_suite_test.go +++ b/testdata/project-v4/pkg/api/v1/webhook_suite_test.go @@ -28,7 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" + admissionv1 "k8s.io/api/admission/v1" //+kubebuilder:scaffold:imports "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -78,7 +78,7 @@ var _ = BeforeSuite(func() { err = AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = admissionv1beta1.AddToScheme(scheme) + err = admissionv1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/testdata/project-v4/api/v1/zz_generated.deepcopy.go b/testdata/project-v4/pkg/api/v1/zz_generated.deepcopy.go similarity index 100% rename from testdata/project-v4/api/v1/zz_generated.deepcopy.go rename to testdata/project-v4/pkg/api/v1/zz_generated.deepcopy.go diff --git a/testdata/project-v4/controllers/admiral_controller.go b/testdata/project-v4/pkg/controllers/admiral_controller.go similarity index 97% rename from testdata/project-v4/controllers/admiral_controller.go rename to testdata/project-v4/pkg/controllers/admiral_controller.go index a006a9b759f..28a9a7027c0 100644 --- a/testdata/project-v4/controllers/admiral_controller.go +++ b/testdata/project-v4/pkg/controllers/admiral_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/pkg/api/v1" ) // AdmiralReconciler reconciles a Admiral object diff --git a/testdata/project-v4/controllers/captain_controller.go b/testdata/project-v4/pkg/controllers/captain_controller.go similarity index 97% rename from testdata/project-v4/controllers/captain_controller.go rename to testdata/project-v4/pkg/controllers/captain_controller.go index b1b931c9fb7..779988d0157 100644 --- a/testdata/project-v4/controllers/captain_controller.go +++ b/testdata/project-v4/pkg/controllers/captain_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/pkg/api/v1" ) // CaptainReconciler reconciles a Captain object diff --git a/testdata/project-v4/controllers/firstmate_controller.go b/testdata/project-v4/pkg/controllers/firstmate_controller.go similarity index 97% rename from testdata/project-v4/controllers/firstmate_controller.go rename to testdata/project-v4/pkg/controllers/firstmate_controller.go index db43f1b5fbc..e13c97220a8 100644 --- a/testdata/project-v4/controllers/firstmate_controller.go +++ b/testdata/project-v4/pkg/controllers/firstmate_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/pkg/api/v1" ) // FirstMateReconciler reconciles a FirstMate object diff --git a/testdata/project-v4/controllers/laker_controller.go b/testdata/project-v4/pkg/controllers/laker_controller.go similarity index 100% rename from testdata/project-v4/controllers/laker_controller.go rename to testdata/project-v4/pkg/controllers/laker_controller.go diff --git a/testdata/project-v4/controllers/suite_test.go b/testdata/project-v4/pkg/controllers/suite_test.go similarity index 97% rename from testdata/project-v4/controllers/suite_test.go rename to testdata/project-v4/pkg/controllers/suite_test.go index d17fdb2d4ad..9418337e001 100644 --- a/testdata/project-v4/controllers/suite_test.go +++ b/testdata/project-v4/pkg/controllers/suite_test.go @@ -30,7 +30,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v4/pkg/api/v1" //+kubebuilder:scaffold:imports )