From 0a5c60475a682a4ae5fe861617dc8b2009919e9a Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Mon, 2 Nov 2020 13:44:08 +0700 Subject: [PATCH 01/38] :sparkles: Use ErrorIfCRDPathMissing in EnvTest The error message if the path to CRDs is missing is misleading. envtest.Environment has an option to throw a sensible error here if the path is wrong and for kubebuilder it would be likely we always want to upload our CRDs. --- .../scaffolds/internal/templates/api/webhook_suitetest.go | 6 +++++- .../internal/templates/controllers/controller_suitetest.go | 3 ++- testdata/project-v3-addon/controllers/suite_test.go | 3 ++- testdata/project-v3-config/api/v1/webhook_suite_test.go | 3 ++- testdata/project-v3-config/controllers/suite_test.go | 3 ++- .../apis/crew/v1/webhook_suite_test.go | 3 ++- .../apis/ship/v1/webhook_suite_test.go | 3 ++- .../apis/ship/v2alpha1/webhook_suite_test.go | 3 ++- .../project-v3-multigroup/apis/v1/webhook_suite_test.go | 3 ++- .../project-v3-multigroup/controllers/apps/suite_test.go | 3 ++- .../project-v3-multigroup/controllers/crew/suite_test.go | 3 ++- .../controllers/foo.policy/suite_test.go | 3 ++- .../controllers/sea-creatures/suite_test.go | 3 ++- .../project-v3-multigroup/controllers/ship/suite_test.go | 3 ++- testdata/project-v3-multigroup/controllers/suite_test.go | 3 ++- testdata/project-v3/api/v1/webhook_suite_test.go | 3 ++- testdata/project-v3/controllers/suite_test.go | 3 ++- 17 files changed, 37 insertions(+), 17 deletions(-) diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go index 95086eccbd3..e303acec5cf 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go @@ -17,6 +17,9 @@ type WebhookSuite struct { file.BoilerplateMixin file.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 } @@ -163,7 +166,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "crd", "bases")}, + ErrorIfCRDPathMissing: {{ .WireResource }}, WebhookInstallOptions: envtest.WebhookInstallOptions{ Paths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "webhook")}, }, diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go index 2ac30c809e3..c61fe4e9dba 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -159,7 +159,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join({{ .CRDDirectoryRelativePath }}, "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join({{ .CRDDirectoryRelativePath }}, "config", "crd", "bases")}, + ErrorIfCRDPathMissing: {{ .WireResource }}, } cfg, err := testEnv.Start() diff --git a/testdata/project-v3-addon/controllers/suite_test.go b/testdata/project-v3-addon/controllers/suite_test.go index 12204ab2d17..73ee7f13dd0 100644 --- a/testdata/project-v3-addon/controllers/suite_test.go +++ b/testdata/project-v3-addon/controllers/suite_test.go @@ -54,7 +54,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() diff --git a/testdata/project-v3-config/api/v1/webhook_suite_test.go b/testdata/project-v3-config/api/v1/webhook_suite_test.go index 541e7682ea0..1498c040c12 100644 --- a/testdata/project-v3-config/api/v1/webhook_suite_test.go +++ b/testdata/project-v3-config/api/v1/webhook_suite_test.go @@ -64,7 +64,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ Paths: []string{filepath.Join("..", "..", "config", "webhook")}, }, diff --git a/testdata/project-v3-config/controllers/suite_test.go b/testdata/project-v3-config/controllers/suite_test.go index c2fb4129bb5..9a71520d545 100644 --- a/testdata/project-v3-config/controllers/suite_test.go +++ b/testdata/project-v3-config/controllers/suite_test.go @@ -54,7 +54,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() diff --git a/testdata/project-v3-multigroup/apis/crew/v1/webhook_suite_test.go b/testdata/project-v3-multigroup/apis/crew/v1/webhook_suite_test.go index 82fe2c58557..56d8f1147d5 100644 --- a/testdata/project-v3-multigroup/apis/crew/v1/webhook_suite_test.go +++ b/testdata/project-v3-multigroup/apis/crew/v1/webhook_suite_test.go @@ -64,7 +64,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ Paths: []string{filepath.Join("..", "..", "..", "config", "webhook")}, }, diff --git a/testdata/project-v3-multigroup/apis/ship/v1/webhook_suite_test.go b/testdata/project-v3-multigroup/apis/ship/v1/webhook_suite_test.go index 85b2339cf32..226b7bf2dd9 100644 --- a/testdata/project-v3-multigroup/apis/ship/v1/webhook_suite_test.go +++ b/testdata/project-v3-multigroup/apis/ship/v1/webhook_suite_test.go @@ -64,7 +64,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ Paths: []string{filepath.Join("..", "..", "..", "config", "webhook")}, }, diff --git a/testdata/project-v3-multigroup/apis/ship/v2alpha1/webhook_suite_test.go b/testdata/project-v3-multigroup/apis/ship/v2alpha1/webhook_suite_test.go index 0bdc004d23e..d7e1eb77302 100644 --- a/testdata/project-v3-multigroup/apis/ship/v2alpha1/webhook_suite_test.go +++ b/testdata/project-v3-multigroup/apis/ship/v2alpha1/webhook_suite_test.go @@ -64,7 +64,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ Paths: []string{filepath.Join("..", "..", "..", "config", "webhook")}, }, diff --git a/testdata/project-v3-multigroup/apis/v1/webhook_suite_test.go b/testdata/project-v3-multigroup/apis/v1/webhook_suite_test.go index 3e9cfa5867b..e1612ea22ab 100644 --- a/testdata/project-v3-multigroup/apis/v1/webhook_suite_test.go +++ b/testdata/project-v3-multigroup/apis/v1/webhook_suite_test.go @@ -64,7 +64,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ Paths: []string{filepath.Join("..", "..", "config", "webhook")}, }, diff --git a/testdata/project-v3-multigroup/controllers/apps/suite_test.go b/testdata/project-v3-multigroup/controllers/apps/suite_test.go index 0097b94eae1..1a52084f221 100644 --- a/testdata/project-v3-multigroup/controllers/apps/suite_test.go +++ b/testdata/project-v3-multigroup/controllers/apps/suite_test.go @@ -52,7 +52,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, } cfg, err := testEnv.Start() diff --git a/testdata/project-v3-multigroup/controllers/crew/suite_test.go b/testdata/project-v3-multigroup/controllers/crew/suite_test.go index 27905ab3a6e..0dc36f1a534 100644 --- a/testdata/project-v3-multigroup/controllers/crew/suite_test.go +++ b/testdata/project-v3-multigroup/controllers/crew/suite_test.go @@ -54,7 +54,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() diff --git a/testdata/project-v3-multigroup/controllers/foo.policy/suite_test.go b/testdata/project-v3-multigroup/controllers/foo.policy/suite_test.go index 03d4a9adf2b..2610397938d 100644 --- a/testdata/project-v3-multigroup/controllers/foo.policy/suite_test.go +++ b/testdata/project-v3-multigroup/controllers/foo.policy/suite_test.go @@ -54,7 +54,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() diff --git a/testdata/project-v3-multigroup/controllers/sea-creatures/suite_test.go b/testdata/project-v3-multigroup/controllers/sea-creatures/suite_test.go index a49ae8ccc17..dbd072b0f72 100644 --- a/testdata/project-v3-multigroup/controllers/sea-creatures/suite_test.go +++ b/testdata/project-v3-multigroup/controllers/sea-creatures/suite_test.go @@ -55,7 +55,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() diff --git a/testdata/project-v3-multigroup/controllers/ship/suite_test.go b/testdata/project-v3-multigroup/controllers/ship/suite_test.go index da5583ed600..10ac6ba0554 100644 --- a/testdata/project-v3-multigroup/controllers/ship/suite_test.go +++ b/testdata/project-v3-multigroup/controllers/ship/suite_test.go @@ -56,7 +56,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() diff --git a/testdata/project-v3-multigroup/controllers/suite_test.go b/testdata/project-v3-multigroup/controllers/suite_test.go index 6ace66cef28..868760626b2 100644 --- a/testdata/project-v3-multigroup/controllers/suite_test.go +++ b/testdata/project-v3-multigroup/controllers/suite_test.go @@ -54,7 +54,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() diff --git a/testdata/project-v3/api/v1/webhook_suite_test.go b/testdata/project-v3/api/v1/webhook_suite_test.go index 541e7682ea0..1498c040c12 100644 --- a/testdata/project-v3/api/v1/webhook_suite_test.go +++ b/testdata/project-v3/api/v1/webhook_suite_test.go @@ -64,7 +64,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ Paths: []string{filepath.Join("..", "..", "config", "webhook")}, }, diff --git a/testdata/project-v3/controllers/suite_test.go b/testdata/project-v3/controllers/suite_test.go index 90a0d92110a..7172d0f31ee 100644 --- a/testdata/project-v3/controllers/suite_test.go +++ b/testdata/project-v3/controllers/suite_test.go @@ -54,7 +54,8 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() From 887d714c8b93b1334f43dfa522d1a32cd280e958 Mon Sep 17 00:00:00 2001 From: Camila Macedo Date: Wed, 16 Dec 2020 19:34:29 +0000 Subject: [PATCH 02/38] :fix: remove unecessary checks adds to the main.go (go/v3-alpha) --- docs/book/src/cronjob-tutorial/testdata/project/main.go | 4 ++-- docs/book/src/multiversion-tutorial/testdata/project/main.go | 4 ++-- pkg/plugins/golang/v3/scaffolds/internal/templates/main.go | 4 ++-- testdata/project-v3-addon/main.go | 4 ++-- testdata/project-v3-config/main.go | 4 ++-- testdata/project-v3-multigroup/main.go | 4 ++-- testdata/project-v3/main.go | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/book/src/cronjob-tutorial/testdata/project/main.go b/docs/book/src/cronjob-tutorial/testdata/project/main.go index 6c9cceac474..ed242d477fa 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/main.go +++ b/docs/book/src/cronjob-tutorial/testdata/project/main.go @@ -124,11 +124,11 @@ func main() { } //+kubebuilder:scaffold:builder - if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { + 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("check", healthz.Ping); err != nil { + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } diff --git a/docs/book/src/multiversion-tutorial/testdata/project/main.go b/docs/book/src/multiversion-tutorial/testdata/project/main.go index bb0b154ffdd..fe049125b97 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/main.go +++ b/docs/book/src/multiversion-tutorial/testdata/project/main.go @@ -119,11 +119,11 @@ func main() { /* */ - if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { + 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("check", healthz.Ping); err != nil { + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go index 81fdf131bcb..f7ea2bdc8ad 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go @@ -268,11 +268,11 @@ func main() { %s - if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { + 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("check", healthz.Ping); err != nil { + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } diff --git a/testdata/project-v3-addon/main.go b/testdata/project-v3-addon/main.go index 3bd682bea26..a0fbfb4feac 100644 --- a/testdata/project-v3-addon/main.go +++ b/testdata/project-v3-addon/main.go @@ -104,11 +104,11 @@ func main() { } //+kubebuilder:scaffold:builder - if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { + 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("check", healthz.Ping); err != nil { + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } diff --git a/testdata/project-v3-config/main.go b/testdata/project-v3-config/main.go index eb530b7ac27..5819afe5c52 100644 --- a/testdata/project-v3-config/main.go +++ b/testdata/project-v3-config/main.go @@ -132,11 +132,11 @@ func main() { } //+kubebuilder:scaffold:builder - if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { + 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("check", healthz.Ping); err != nil { + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } diff --git a/testdata/project-v3-multigroup/main.go b/testdata/project-v3-multigroup/main.go index b286d17c12c..2776ed41a34 100644 --- a/testdata/project-v3-multigroup/main.go +++ b/testdata/project-v3-multigroup/main.go @@ -191,11 +191,11 @@ func main() { } //+kubebuilder:scaffold:builder - if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { + 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("check", healthz.Ping); err != nil { + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } diff --git a/testdata/project-v3/main.go b/testdata/project-v3/main.go index a3d2b1f50b8..0e4fa8b076c 100644 --- a/testdata/project-v3/main.go +++ b/testdata/project-v3/main.go @@ -136,11 +136,11 @@ func main() { } //+kubebuilder:scaffold:builder - if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { + 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("check", healthz.Ping); err != nil { + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } From b1670d8382538e69c88f75e62ee90b0c808adc0d Mon Sep 17 00:00:00 2001 From: Justin SB Date: Wed, 13 Jan 2021 07:58:19 -0500 Subject: [PATCH 03/38] Remove misleading help that mentioned dep dep is no longer used by the project (we use go mod instead); remove the help test that suggests we will suggest it. --- pkg/plugins/golang/v2/init.go | 2 -- pkg/plugins/golang/v3/init.go | 2 -- 2 files changed, 4 deletions(-) diff --git a/pkg/plugins/golang/v2/init.go b/pkg/plugins/golang/v2/init.go index dd7e221f925..08b619493c3 100644 --- a/pkg/plugins/golang/v2/init.go +++ b/pkg/plugins/golang/v2/init.go @@ -63,8 +63,6 @@ Writes the following files: - a Patch file for customizing image for manager manifests - a Patch file for enabling prometheus metrics - a main.go to run - -project will prompt the user to run 'dep ensure' after writing the project files. ` ctx.Examples = fmt.Sprintf(` # Scaffold a project using the apache2 license with "The Kubernetes authors" as owners %s init --project-version=2 --domain example.org --license apache2 --owner "The Kubernetes authors" diff --git a/pkg/plugins/golang/v3/init.go b/pkg/plugins/golang/v3/init.go index b3308e0ba32..c191dce6e9d 100644 --- a/pkg/plugins/golang/v3/init.go +++ b/pkg/plugins/golang/v3/init.go @@ -64,8 +64,6 @@ Writes the following files: - a Patch file for customizing image for manager manifests - a Patch file for enabling prometheus metrics - a main.go to run - -project will prompt the user to run 'dep ensure' after writing the project files. ` ctx.Examples = fmt.Sprintf(` # Scaffold a project using the apache2 license with "The Kubernetes authors" as owners %s init --project-version=2 --domain example.org --license apache2 --owner "The Kubernetes authors" From 84b6f537c79b47b8bce5864fc4de9c02355f72b0 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Tue, 12 Jan 2021 13:57:06 +0100 Subject: [PATCH 04/38] Build the command and use it to report user errors so that the command help message is printed Signed-off-by: Adrian Orive --- pkg/cli/cli.go | 190 +++++++++++++++++++------------------------- pkg/cli/cli_test.go | 48 ++++++++--- pkg/cli/init.go | 4 - pkg/cli/root.go | 120 ++++++++++++++++++++++++++++ 4 files changed, 239 insertions(+), 123 deletions(-) create mode 100644 pkg/cli/root.go diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 8a700741f81..ad8eec92948 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -18,7 +18,6 @@ package cli import ( "fmt" - "log" "os" "strings" @@ -95,15 +94,13 @@ type cli struct { //nolint:maligned // A filtered set of plugins that should be used by command constructors. resolvedPlugins []plugin.Plugin - // Whether some generic help should be printed, i.e. if the binary - // was invoked outside of a project with incorrect flags or -h|--help. - doHelp bool - // Root command. cmd *cobra.Command } // New creates a new cli instance. +// Developer errors (e.g. not registering any plugins, extra commands with conflicting names) return an error +// while user errors (e.g. errors while parsing flags, unresolvable plugins) create a command which return the error. func New(opts ...Option) (CLI, error) { // Create the CLI. c, err := newCLI(opts...) @@ -111,35 +108,19 @@ func New(opts ...Option) (CLI, error) { return nil, err } - // Get project version and plugin keys. - if err := c.getInfo(); err != nil { - return nil, err - } - - // Resolve plugins for project version and plugin keys. - if err := c.resolve(); err != nil { - return nil, err + // Build the cmd tree. + if err := c.buildCmd(); err != nil { + c.cmd.RunE = errCmdFunc(err) + return c, nil } - // Build the root command. - c.cmd = c.buildRootCmd() - // Add extra commands injected by options. - for _, cmd := range c.extraCommands { - for _, subCmd := range c.cmd.Commands() { - if cmd.Name() == subCmd.Name() { - return nil, fmt.Errorf("command %q already exists", cmd.Name()) - } - } - c.cmd.AddCommand(cmd) + if err := c.addExtraCommands(); err != nil { + return nil, err } // Write deprecation notices after all commands have been constructed. - for _, p := range c.resolvedPlugins { - if d, isDeprecated := p.(plugin.Deprecated); isDeprecated { - fmt.Printf(noticeColor, fmt.Sprintf(deprecationFmt, d.DeprecationWarning())) - } - } + c.printDeprecationWarnings() return c, nil } @@ -166,37 +147,47 @@ func newCLI(opts ...Option) (*cli, error) { } // getInfoFromFlags obtains the project version and plugin keys from flags. -func (c *cli) getInfoFromFlags() (string, []string) { +func (c *cli) getInfoFromFlags() (string, []string, error) { // Partially parse the command line arguments - fs := pflag.NewFlagSet("base", pflag.ExitOnError) + fs := pflag.NewFlagSet("base", pflag.ContinueOnError) + + // Load the base command global flags + fs.AddFlagSet(c.cmd.PersistentFlags()) // Omit unknown flags to avoid parsing errors fs.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{UnknownFlags: true} - // Define the flags needed for plugin resolution - var projectVersion, plugins string - var help bool - fs.StringVar(&projectVersion, projectVersionFlag, "", "project version") - fs.StringVar(&plugins, pluginsFlag, "", "plugins to run") - fs.BoolVarP(&help, "help", "h", false, "help flag") + // FlagSet special cases --help and -h, so we need to create a dummy flag with these 2 values to prevent the default + // behavior (printing the usage of this FlagSet) as we want to print the usage message of the underlying command. + fs.BoolP("help", "h", false, fmt.Sprintf("help for %s", c.commandName)) // Parse the arguments - err := fs.Parse(os.Args[1:]) + if err := fs.Parse(os.Args[1:]); err != nil { + return "", []string{}, err + } - // User needs *generic* help if args are incorrect or --help is set and - // --project-version is not set. Plugin-specific help is given if a - // plugin.Context is updated, which does not require this field. - c.doHelp = err != nil || help && !fs.Lookup(projectVersionFlag).Changed + // Define the flags needed for plugin resolution + var ( + projectVersion string + plugins []string + err error + ) + // GetXxxxx methods will not yield errors because we know for certain these flags exist and types match. + projectVersion, err = fs.GetString(projectVersionFlag) + if err != nil { + return "", []string{}, err + } + plugins, err = fs.GetStringSlice(pluginsFlag) + if err != nil { + return "", []string{}, err + } - // Split the comma-separated plugins - var pluginSet []string - if plugins != "" { - for _, p := range strings.Split(plugins, ",") { - pluginSet = append(pluginSet, strings.TrimSpace(p)) - } + // Remove leading and trailing spaces + for i, key := range plugins { + plugins[i] = strings.TrimSpace(key) } - return projectVersion, pluginSet + return projectVersion, plugins, nil } // getInfoFromConfigFile obtains the project version and plugin keys from the project config file. @@ -293,14 +284,16 @@ func (c cli) resolveFlagsAndConfigFileConflicts( // getInfo obtains the project version and plugin keys resolving conflicts among flags and the project config file. func (c *cli) getInfo() error { // Get project version and plugin info from flags - flagProjectVersion, flagPlugins := c.getInfoFromFlags() + flagProjectVersion, flagPlugins, err := c.getInfoFromFlags() + if err != nil { + return err + } // Get project version and plugin info from project configuration file cfgProjectVersion, cfgPlugins, _ := getInfoFromConfigFile() // We discard the error because not being able to read a project configuration file // is not fatal for some commands. The ones that require it need to check its existence. // Resolve project version and plugin keys - var err error c.projectVersion, c.pluginKeys, err = c.resolveFlagsAndConfigFileConflicts( flagProjectVersion, cfgProjectVersion, flagPlugins, cfgPlugins, ) @@ -399,15 +392,13 @@ func (c *cli) resolve() error { return nil } -// buildRootCmd returns a root command with a subcommand tree reflecting the +// addSubcommands returns a root command with a subcommand tree reflecting the // current project's state. -func (c cli) buildRootCmd() *cobra.Command { - rootCmd := c.defaultCommand() - +func (c *cli) addSubcommands() { // kubebuilder completion // Only add completion if requested if c.completionCommand { - rootCmd.AddCommand(c.newCompletionCmd()) + c.cmd.AddCommand(c.newCompletionCmd()) } // kubebuilder create @@ -416,76 +407,61 @@ func (c cli) buildRootCmd() *cobra.Command { createCmd.AddCommand(c.newCreateAPICmd()) createCmd.AddCommand(c.newCreateWebhookCmd()) if createCmd.HasSubCommands() { - rootCmd.AddCommand(createCmd) + c.cmd.AddCommand(createCmd) } // kubebuilder edit - rootCmd.AddCommand(c.newEditCmd()) + c.cmd.AddCommand(c.newEditCmd()) // kubebuilder init - rootCmd.AddCommand(c.newInitCmd()) + c.cmd.AddCommand(c.newInitCmd()) // kubebuilder version // Only add version if a version string was provided if c.version != "" { - rootCmd.AddCommand(c.newVersionCmd()) + c.cmd.AddCommand(c.newVersionCmd()) } - - return rootCmd } -// defaultCommand returns the root command without its subcommands. -func (c cli) defaultCommand() *cobra.Command { - return &cobra.Command{ - Use: c.commandName, - Short: "Development kit for building Kubernetes extensions and tools.", - Long: fmt.Sprintf(`Development kit for building Kubernetes extensions and tools. - -Provides libraries and tools to create new projects, APIs and controllers. -Includes tools for packaging artifacts into an installer container. - -Typical project lifecycle: - -- initialize a project: - - %[1]s init --domain example.com --license apache2 --owner "The Kubernetes authors" +// buildCmd creates the underlying cobra command and stores it internally. +func (c *cli) buildCmd() error { + c.cmd = c.newRootCmd() -- create one or more a new resource APIs and add your code to them: - - %[1]s create api --group --version --kind - -Create resource will prompt the user for if it should scaffold the Resource and / or Controller. To only -scaffold a Controller for an existing Resource, select "n" for Resource. To only define -the schema for a Resource without writing a Controller, select "n" for Controller. - -After the scaffold is written, api will run make on the project. -`, - c.commandName), - Example: fmt.Sprintf(` - # Initialize your project - %[1]s init --domain example.com --license apache2 --owner "The Kubernetes authors" - - # Create a frigates API with Group: ship, Version: v1beta1 and Kind: Frigate - %[1]s create api --group ship --version v1beta1 --kind Frigate + // Get project version and plugin keys. + if err := c.getInfo(); err != nil { + return err + } - # Edit the API Scheme - nano api/v1beta1/frigate_types.go + // Resolve plugins for project version and plugin keys. + if err := c.resolve(); err != nil { + return err + } - # Edit the Controller - nano controllers/frigate_controller.go + // Add the subcommands + c.addSubcommands() - # Install CRDs into the Kubernetes cluster using kubectl apply - make install + return nil +} - # Regenerate code and run against the Kubernetes cluster configured by ~/.kube/config - make run -`, - c.commandName), - Run: func(cmd *cobra.Command, args []string) { - if err := cmd.Help(); err != nil { - log.Fatal(err) +// addExtraCommands adds the additional commands. +func (c *cli) addExtraCommands() error { + for _, cmd := range c.extraCommands { + for _, subCmd := range c.cmd.Commands() { + if cmd.Name() == subCmd.Name() { + return fmt.Errorf("command %q already exists", cmd.Name()) } - }, + } + c.cmd.AddCommand(cmd) + } + return nil +} + +// printDeprecationWarnings prints the deprecation warnings of the resolved plugins. +func (c cli) printDeprecationWarnings() { + for _, p := range c.resolvedPlugins { + if d, isDeprecated := p.(plugin.Deprecated); isDeprecated { + fmt.Printf(noticeColor, fmt.Sprintf(deprecationFmt, d.DeprecationWarning())) + } } } diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index da76194bc6d..7413458117c 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -50,6 +50,10 @@ func setFlag(flag, value string) { os.Args = append(os.Args, "subcommand", "--"+flag, value) } +func setBoolFlag(flag string) { + os.Args = append(os.Args, "subcommand", "--"+flag) +} + // nolint:unparam func setProjectVersionFlag(value string) { setFlag(projectVersionFlag, value) @@ -74,6 +78,7 @@ var _ = Describe("CLI", func() { var ( projectVersion string plugins []string + err error c *cli ) @@ -81,13 +86,15 @@ var _ = Describe("CLI", func() { var args []string BeforeEach(func() { c = &cli{} + c.cmd = c.newRootCmd() args = os.Args }) AfterEach(func() { os.Args = args }) When("no flag is set", func() { It("should success", func() { - projectVersion, plugins = c.getInfoFromFlags() + projectVersion, plugins, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) Expect(projectVersion).To(Equal("")) Expect(len(plugins)).To(Equal(0)) }) @@ -96,7 +103,8 @@ var _ = Describe("CLI", func() { When(fmt.Sprintf("--%s flag is set", projectVersionFlag), func() { It("should success", func() { setProjectVersionFlag("2") - projectVersion, plugins = c.getInfoFromFlags() + projectVersion, plugins, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) Expect(projectVersion).To(Equal("2")) Expect(len(plugins)).To(Equal(0)) }) @@ -105,21 +113,24 @@ var _ = Describe("CLI", func() { When(fmt.Sprintf("--%s flag is set", pluginsFlag), func() { It("should success using one plugin key", func() { setPluginsFlag("go/v1") - projectVersion, plugins = c.getInfoFromFlags() + projectVersion, plugins, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) Expect(projectVersion).To(Equal("")) Expect(plugins).To(Equal([]string{"go/v1"})) }) It("should success using more than one plugin key", func() { setPluginsFlag("go/v1,example/v2,test/v1") - projectVersion, plugins = c.getInfoFromFlags() + projectVersion, plugins, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) Expect(projectVersion).To(Equal("")) Expect(plugins).To(Equal([]string{"go/v1", "example/v2", "test/v1"})) }) It("should success using more than one plugin key with spaces", func() { setPluginsFlag("go/v1 , example/v2 , test/v1") - projectVersion, plugins = c.getInfoFromFlags() + projectVersion, plugins, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) Expect(projectVersion).To(Equal("")) Expect(plugins).To(Equal([]string{"go/v1", "example/v2", "test/v1"})) }) @@ -129,7 +140,8 @@ var _ = Describe("CLI", func() { It("should success using one plugin key", func() { setProjectVersionFlag("2") setPluginsFlag("go/v1") - projectVersion, plugins = c.getInfoFromFlags() + projectVersion, plugins, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) Expect(projectVersion).To(Equal("2")) Expect(plugins).To(Equal([]string{"go/v1"})) }) @@ -137,7 +149,8 @@ var _ = Describe("CLI", func() { It("should success using more than one plugin keys", func() { setProjectVersionFlag("2") setPluginsFlag("go/v1,example/v2,test/v1") - projectVersion, plugins = c.getInfoFromFlags() + projectVersion, plugins, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) Expect(projectVersion).To(Equal("2")) Expect(plugins).To(Equal([]string{"go/v1", "example/v2", "test/v1"})) }) @@ -145,7 +158,8 @@ var _ = Describe("CLI", func() { It("should success using more than one plugin keys with spaces", func() { setProjectVersionFlag("2") setPluginsFlag("go/v1 , example/v2 , test/v1") - projectVersion, plugins = c.getInfoFromFlags() + projectVersion, plugins, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) Expect(projectVersion).To(Equal("2")) Expect(plugins).To(Equal([]string{"go/v1", "example/v2", "test/v1"})) }) @@ -154,7 +168,15 @@ var _ = Describe("CLI", func() { When("additional flags are set", func() { It("should not fail", func() { setFlag("extra-flag", "extra-value") - c.getInfoFromFlags() + _, _, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) + }) + + // `--help` is not captured by the whitelist, so we need to special case it + It("should not fail for `--help`", func() { + setBoolFlag("help") + _, _, err = c.getInfoFromFlags() + Expect(err).NotTo(HaveOccurred()) }) }) }) @@ -595,6 +617,7 @@ var _ = Describe("CLI", func() { projectVersion: pluginKeys, }, } + c.cmd = c.newRootCmd() Expect(c.getInfo()).To(Succeed()) Expect(c.projectVersion).To(Equal(projectVersion)) Expect(c.pluginKeys).To(Equal(pluginKeys)) @@ -707,10 +730,11 @@ var _ = Describe("CLI", func() { BeforeEach(func() { args = os.Args }) AfterEach(func() { os.Args = args }) - It("should return an error", func() { + It("should return a CLI that returns an error", func() { setPluginsFlag("foo") - _, err = New() - Expect(err).To(HaveOccurred()) + c, err = New() + Expect(err).NotTo(HaveOccurred()) + Expect(c.Run()).NotTo(Succeed()) }) }) diff --git a/pkg/cli/init.go b/pkg/cli/init.go index 284fecee551..1822d9a0c3d 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -52,10 +52,6 @@ func (c cli) newInitCmd() *cobra.Command { fmt.Sprintf("Available plugins: (%s)", strings.Join(c.getAvailablePlugins(), ", "))) } - if c.doHelp { - return cmd - } - // Lookup the plugin for projectVersion and bind it to the command. c.bindInit(ctx, cmd) return cmd diff --git a/pkg/cli/root.go b/pkg/cli/root.go new file mode 100644 index 00000000000..6a30cc43646 --- /dev/null +++ b/pkg/cli/root.go @@ -0,0 +1,120 @@ +/* +Copyright 2021 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 cli + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +const ( + pluginKeysHeader = "Plugin keys" + projectVersionsHeader = "Supported project versions" +) + +func (c cli) newRootCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: c.commandName, + Long: `CLI tool for building Kubernetes extensions and tools. +`, + Example: c.rootExamples(), + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } + + // Global flags for all subcommands + // NOTE: the current plugin resolution doesn't allow to provide values to this flag different to those configured + // for the project, so default values need to be empty and considered when these two sources are compared. + // Another approach would be to allow users to overwrite the project configuration values. In this case, flags + // would take precedence over project configuration, which would take precedence over cli defaults. + fs := cmd.PersistentFlags() + fs.String(projectVersionFlag, "", "project version") + fs.StringSlice(pluginsFlag, nil, "plugin keys of the plugin to initialize the project with") + + return cmd +} + +// rootExamples builds the examples string for the root command +func (c cli) rootExamples() string { + str := fmt.Sprintf(`The first step is to initialize your project: + %[1]s init --project-version= --plugins= + + is a comma-separated list of plugin keys from the following table +and a supported project version for these plugins. + +%[2]s + +For more specific help for the init command of a certain plugins and project version +configuration please run: + %[1]s init --help --project-version= --plugins= +`, + c.commandName, c.getPluginTable()) + + if c.defaultProjectVersion != "" { + str += fmt.Sprintf("\nDefault project version: %s\n", c.defaultProjectVersion) + + if defaultPlugins, hasDefaultPlugins := c.defaultPlugins[c.defaultProjectVersion]; hasDefaultPlugins { + str += fmt.Sprintf("Default plugin keys: %q\n", strings.Join(defaultPlugins, ",")) + } + } + + str += fmt.Sprintf(` +After the project has been initialized, run + %[1]s --help +to obtain further info about available commands.`, + c.commandName) + + return str +} + +// getPluginTable returns an ASCII table of the available plugins and their supported project versions. +func (c cli) getPluginTable() string { + var ( + maxPluginKeyLength = len(pluginKeysHeader) + pluginKeys = make([]string, 0, len(c.plugins)) + maxProjectVersionLength = len(projectVersionsHeader) + projectVersions = make([]string, 0, len(c.plugins)) + ) + + for pluginKey, plugin := range c.plugins { + if len(pluginKey) > maxPluginKeyLength { + maxPluginKeyLength = len(pluginKey) + } + pluginKeys = append(pluginKeys, pluginKey) + supportedProjectVersions := strings.Join(plugin.SupportedProjectVersions(), ", ") + if len(supportedProjectVersions) > maxProjectVersionLength { + maxProjectVersionLength = len(supportedProjectVersions) + } + projectVersions = append(projectVersions, supportedProjectVersions) + } + + lines := make([]string, 0, len(c.plugins)+2) + lines = append(lines, fmt.Sprintf(" %[1]*[2]s | %[3]*[4]s", + maxPluginKeyLength, pluginKeysHeader, maxProjectVersionLength, projectVersionsHeader)) + lines = append(lines, strings.Repeat("-", maxPluginKeyLength+2)+"+"+ + strings.Repeat("-", maxProjectVersionLength+2)) + for i, pluginKey := range pluginKeys { + supportedProjectVersions := projectVersions[i] + lines = append(lines, fmt.Sprintf(" %[1]*[2]s | %[3]*[4]s", + maxPluginKeyLength, pluginKey, maxProjectVersionLength, supportedProjectVersions)) + } + + return strings.Join(lines, "\n") +} From 9fd31f1542b270753e16889ea9cc5c78835ea394 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 13 Jan 2021 08:24:11 -0800 Subject: [PATCH 05/38] pkg/plugins/golang/v3: upgrade kubebuilder-declarative-pattern to k8s v1.19 equivalent tag --- pkg/plugins/golang/v3/api.go | 2 +- testdata/project-v3-addon/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index d9ab19418a9..09f21c22c2b 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -41,7 +41,7 @@ const ( // KbDeclarativePatternVersion is the sigs.k8s.io/kubebuilder-declarative-pattern version // (used only to gen api with --pattern=addon) // TODO: remove this when a better solution for using addons is implemented. - KbDeclarativePatternVersion = "1cbf859290cab81ae8e73fc5caebe792280175d1" + KbDeclarativePatternVersion = "b84d99da021778217217885dd9582ed3cc879ebe" // defaultCRDVersion is the default CRD API version to scaffold. defaultCRDVersion = "v1" diff --git a/testdata/project-v3-addon/go.mod b/testdata/project-v3-addon/go.mod index 69dfbc041a8..e64120517df 100644 --- a/testdata/project-v3-addon/go.mod +++ b/testdata/project-v3-addon/go.mod @@ -9,5 +9,5 @@ require ( k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 sigs.k8s.io/controller-runtime v0.7.0 - sigs.k8s.io/kubebuilder-declarative-pattern v0.0.0-20201109180626-1cbf859290ca + sigs.k8s.io/kubebuilder-declarative-pattern v0.0.0-20210113160450-b84d99da0217 ) From c988208ddc92ebf6f9cd5102e674b1ad960f0cbe Mon Sep 17 00:00:00 2001 From: droctothorpe Date: Wed, 13 Jan 2021 21:03:57 -0500 Subject: [PATCH 06/38] Add help dialog to makefile template --- Makefile | 15 ++-- .../scaffolds/internal/templates/makefile.go | 68 +++++++++++-------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index aa00f9e4180..85ee5199d07 100644 --- a/Makefile +++ b/Makefile @@ -28,10 +28,17 @@ endif ##@ General -# The help will print out all targets with their descriptions organized bellow their categories. The categories are represented by `##@` and the target descriptions by `##`. -# The awk commands is responsable to read 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 over the usage of ANSI control characters for terminal formatting: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters -# More info over awk command: http://linuxcommand.org/lc3_adv_awk.php +# 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) diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go index 6c31eae568a..a620818d331 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go @@ -72,70 +72,78 @@ endif all: manager -# Run tests +##@ Targets + +# 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) + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests +test: generate fmt vet manifests ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/{{ .ControllerRuntimeVersion }}/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -# Build manager binary -manager: generate fmt vet +manager: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests +run: generate fmt vet manifests ## Run a controller from your host. go run ./main.go -# Install CRDs into a cluster -install: manifests kustomize +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize +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 - -# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config -undeploy: +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/default | kubectl delete -f - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen + +manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases -# Run go fmt against code -fmt: +fmt: ## Run go fmt against code. go fmt ./... -# Run go vet against code -vet: + +vet: ## Run go vet against code. go vet ./... -# Generate code -generate: controller-gen +generate: controller-gen ## Generate code. $(CONTROLLER_GEN) object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..." -# Build the docker image -docker-build: test + +docker-build: test ## Build the docker image for the controller. docker build -t ${IMG} . -# Push the docker image -docker-push: + +docker-push: ## Push the docker image for the controller. docker push ${IMG} -# Download controller-gen locally if necessary + CONTROLLER_GEN = $(shell pwd)/bin/controller-gen -controller-gen: +controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@{{ .ControllerToolsVersion }}) -# Download kustomize locally if necessary + KUSTOMIZE = $(shell pwd)/bin/kustomize -kustomize: +kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@{{ .KustomizeVersion }}) # go-get-tool will 'go get' any package $2 and install it to $1. From a7fc753c8b74a3f13736cc4ead55ac5d35462ba7 Mon Sep 17 00:00:00 2001 From: droctothorpe Date: Wed, 13 Jan 2021 23:28:35 -0500 Subject: [PATCH 07/38] Add updated testdata --- testdata/project-v3-addon/Makefile | 68 ++++++++++++++----------- testdata/project-v3-config/Makefile | 68 ++++++++++++++----------- testdata/project-v3-multigroup/Makefile | 68 ++++++++++++++----------- testdata/project-v3/Makefile | 68 ++++++++++++++----------- 4 files changed, 152 insertions(+), 120 deletions(-) diff --git a/testdata/project-v3-addon/Makefile b/testdata/project-v3-addon/Makefile index c4f78a33150..e81937b7b38 100644 --- a/testdata/project-v3-addon/Makefile +++ b/testdata/project-v3-addon/Makefile @@ -13,70 +13,78 @@ endif all: manager -# Run tests +##@ Targets + +# 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) + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests +test: generate fmt vet manifests ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -# Build manager binary -manager: generate fmt vet +manager: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests +run: generate fmt vet manifests ## Run a controller from your host. go run ./main.go -# Install CRDs into a cluster -install: manifests kustomize +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize +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 - -# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config -undeploy: +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/default | kubectl delete -f - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen + +manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases -# Run go fmt against code -fmt: +fmt: ## Run go fmt against code. go fmt ./... -# Run go vet against code -vet: + +vet: ## Run go vet against code. go vet ./... -# Generate code -generate: controller-gen +generate: controller-gen ## Generate code. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." -# Build the docker image -docker-build: test + +docker-build: test ## Build the docker image for the controller. docker build -t ${IMG} . -# Push the docker image -docker-push: + +docker-push: ## Push the docker image for the controller. docker push ${IMG} -# Download controller-gen locally if necessary + CONTROLLER_GEN = $(shell pwd)/bin/controller-gen -controller-gen: +controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) -# Download kustomize locally if necessary + KUSTOMIZE = $(shell pwd)/bin/kustomize -kustomize: +kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) # go-get-tool will 'go get' any package $2 and install it to $1. diff --git a/testdata/project-v3-config/Makefile b/testdata/project-v3-config/Makefile index c4f78a33150..e81937b7b38 100644 --- a/testdata/project-v3-config/Makefile +++ b/testdata/project-v3-config/Makefile @@ -13,70 +13,78 @@ endif all: manager -# Run tests +##@ Targets + +# 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) + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests +test: generate fmt vet manifests ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -# Build manager binary -manager: generate fmt vet +manager: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests +run: generate fmt vet manifests ## Run a controller from your host. go run ./main.go -# Install CRDs into a cluster -install: manifests kustomize +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize +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 - -# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config -undeploy: +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/default | kubectl delete -f - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen + +manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases -# Run go fmt against code -fmt: +fmt: ## Run go fmt against code. go fmt ./... -# Run go vet against code -vet: + +vet: ## Run go vet against code. go vet ./... -# Generate code -generate: controller-gen +generate: controller-gen ## Generate code. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." -# Build the docker image -docker-build: test + +docker-build: test ## Build the docker image for the controller. docker build -t ${IMG} . -# Push the docker image -docker-push: + +docker-push: ## Push the docker image for the controller. docker push ${IMG} -# Download controller-gen locally if necessary + CONTROLLER_GEN = $(shell pwd)/bin/controller-gen -controller-gen: +controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) -# Download kustomize locally if necessary + KUSTOMIZE = $(shell pwd)/bin/kustomize -kustomize: +kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) # go-get-tool will 'go get' any package $2 and install it to $1. diff --git a/testdata/project-v3-multigroup/Makefile b/testdata/project-v3-multigroup/Makefile index c4f78a33150..e81937b7b38 100644 --- a/testdata/project-v3-multigroup/Makefile +++ b/testdata/project-v3-multigroup/Makefile @@ -13,70 +13,78 @@ endif all: manager -# Run tests +##@ Targets + +# 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) + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests +test: generate fmt vet manifests ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -# Build manager binary -manager: generate fmt vet +manager: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests +run: generate fmt vet manifests ## Run a controller from your host. go run ./main.go -# Install CRDs into a cluster -install: manifests kustomize +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize +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 - -# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config -undeploy: +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/default | kubectl delete -f - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen + +manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases -# Run go fmt against code -fmt: +fmt: ## Run go fmt against code. go fmt ./... -# Run go vet against code -vet: + +vet: ## Run go vet against code. go vet ./... -# Generate code -generate: controller-gen +generate: controller-gen ## Generate code. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." -# Build the docker image -docker-build: test + +docker-build: test ## Build the docker image for the controller. docker build -t ${IMG} . -# Push the docker image -docker-push: + +docker-push: ## Push the docker image for the controller. docker push ${IMG} -# Download controller-gen locally if necessary + CONTROLLER_GEN = $(shell pwd)/bin/controller-gen -controller-gen: +controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) -# Download kustomize locally if necessary + KUSTOMIZE = $(shell pwd)/bin/kustomize -kustomize: +kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) # go-get-tool will 'go get' any package $2 and install it to $1. diff --git a/testdata/project-v3/Makefile b/testdata/project-v3/Makefile index c4f78a33150..e81937b7b38 100644 --- a/testdata/project-v3/Makefile +++ b/testdata/project-v3/Makefile @@ -13,70 +13,78 @@ endif all: manager -# Run tests +##@ Targets + +# 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) + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests +test: generate fmt vet manifests ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -# Build manager binary -manager: generate fmt vet +manager: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests +run: generate fmt vet manifests ## Run a controller from your host. go run ./main.go -# Install CRDs into a cluster -install: manifests kustomize +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize +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 - -# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config -undeploy: +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/default | kubectl delete -f - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen + +manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases -# Run go fmt against code -fmt: +fmt: ## Run go fmt against code. go fmt ./... -# Run go vet against code -vet: + +vet: ## Run go vet against code. go vet ./... -# Generate code -generate: controller-gen +generate: controller-gen ## Generate code. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." -# Build the docker image -docker-build: test + +docker-build: test ## Build the docker image for the controller. docker build -t ${IMG} . -# Push the docker image -docker-push: + +docker-push: ## Push the docker image for the controller. docker push ${IMG} -# Download controller-gen locally if necessary + CONTROLLER_GEN = $(shell pwd)/bin/controller-gen -controller-gen: +controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) -# Download kustomize locally if necessary + KUSTOMIZE = $(shell pwd)/bin/kustomize -kustomize: +kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) # go-get-tool will 'go get' any package $2 and install it to $1. From 20df76b65ea8e2521a50d5965443e1526d1a6700 Mon Sep 17 00:00:00 2001 From: Justin SB Date: Wed, 13 Jan 2021 07:44:43 -0500 Subject: [PATCH 08/38] Make directory-not-empty error more self-explanatory When I encountered the error, I had to go to the source to understand the problem. Made the message more user-focused. Also skipped printing the allowlist, both to fit into lint line-length limits and to avoid overwhelming the user. --- pkg/plugins/golang/v3/init.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/plugins/golang/v3/init.go b/pkg/plugins/golang/v3/init.go index b3308e0ba32..4dd41816a40 100644 --- a/pkg/plugins/golang/v3/init.go +++ b/pkg/plugins/golang/v3/init.go @@ -17,7 +17,6 @@ limitations under the License. package v3 import ( - "errors" "fmt" "os" "path/filepath" @@ -188,7 +187,9 @@ func checkDir() error { return err } if info.Name() != "go.mod" && !strings.HasPrefix(info.Name(), ".") { - return errors.New("only the go.mod and files with the prefix \"(.)\" are allowed before the init") + return fmt.Errorf( + "target directory is not empty (only go.mod and files with the prefix \".\" are allowed); found existing file %q", + path) } return nil }) From d1105e2a9683bfa21a0106ad6e40c80b7336195e Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Fri, 15 Jan 2021 16:08:15 -0800 Subject: [PATCH 09/38] *: upgrade kube-rbac-proxy to v0.8.0 --- .../book/src/component-config-tutorial/testdata/project/PROJECT | 2 +- .../project/config/default/manager_auth_proxy_patch.yaml | 2 +- .../project/config/default/manager_auth_proxy_patch.yaml | 2 +- .../project/config/default/manager_auth_proxy_patch.yaml | 2 +- .../templates/config/kdefault/manager_auth_proxy_patch.go | 2 +- test/e2e/v3/plugin_cluster_test.go | 2 +- test_e2e.sh | 2 -- test_e2e_local.sh | 2 -- .../config/default/manager_auth_proxy_patch.yaml | 2 +- .../config/default/manager_auth_proxy_patch.yaml | 2 +- .../config/default/manager_auth_proxy_patch.yaml | 2 +- .../project-v3/config/default/manager_auth_proxy_patch.yaml | 2 +- 12 files changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/book/src/component-config-tutorial/testdata/project/PROJECT b/docs/book/src/component-config-tutorial/testdata/project/PROJECT index 162122564fe..bcf8439f704 100644 --- a/docs/book/src/component-config-tutorial/testdata/project/PROJECT +++ b/docs/book/src/component-config-tutorial/testdata/project/PROJECT @@ -1,6 +1,6 @@ componentConfig: true domain: tutorial.kubebuilder.io -layout: go.kubebuilder.io/v3-alpha +layout: go.kubebuilder.io/v3 multigroup: true projectName: project repo: tutorial.kubebuilder.io/project diff --git a/docs/book/src/component-config-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml b/docs/book/src/component-config-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml index 37c578ab7ae..cf923e8c88a 100644 --- a/docs/book/src/component-config-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml +++ b/docs/book/src/component-config-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml @@ -10,7 +10,7 @@ spec: spec: containers: - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 args: - "--secure-listen-address=0.0.0.0:8443" - "--upstream=http://127.0.0.1:8080/" diff --git a/docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml b/docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml index 49b1f1ab36e..a224be19ea1 100644 --- a/docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml +++ b/docs/book/src/cronjob-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml @@ -10,7 +10,7 @@ spec: spec: containers: - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 args: - "--secure-listen-address=0.0.0.0:8443" - "--upstream=http://127.0.0.1:8080/" diff --git a/docs/book/src/multiversion-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml b/docs/book/src/multiversion-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml index 49b1f1ab36e..a224be19ea1 100644 --- a/docs/book/src/multiversion-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml +++ b/docs/book/src/multiversion-tutorial/testdata/project/config/default/manager_auth_proxy_patch.yaml @@ -10,7 +10,7 @@ spec: spec: containers: - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 args: - "--secure-listen-address=0.0.0.0:8443" - "--upstream=http://127.0.0.1:8080/" diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go index 8f4f21a60ee..8c16598b8af 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go @@ -55,7 +55,7 @@ spec: spec: containers: - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 args: - "--secure-listen-address=0.0.0.0:8443" - "--upstream=http://127.0.0.1:8080/" diff --git a/test/e2e/v3/plugin_cluster_test.go b/test/e2e/v3/plugin_cluster_test.go index e2e039086ed..dff4feed0fd 100644 --- a/test/e2e/v3/plugin_cluster_test.go +++ b/test/e2e/v3/plugin_cluster_test.go @@ -74,7 +74,7 @@ var _ = Describe("kubebuilder", func() { }) }) - Context("plugin go.kubebuilder.io/v3-alpha", func() { + Context("plugin go.kubebuilder.io/v3", func() { // Use cert-manager with v1 CRs. BeforeEach(func() { By("installing the cert-manager bundle") diff --git a/test_e2e.sh b/test_e2e.sh index 85fd5a41dda..51993c1037a 100755 --- a/test_e2e.sh +++ b/test_e2e.sh @@ -29,8 +29,6 @@ build_kb setup_envs source "$(pwd)/scripts/setup.sh" ${KIND_K8S_VERSION} -docker pull gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 -kind load docker-image gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 # remove running containers on exit function cleanup() { diff --git a/test_e2e_local.sh b/test_e2e_local.sh index fed4925218a..90527a2ebf7 100755 --- a/test_e2e_local.sh +++ b/test_e2e_local.sh @@ -32,8 +32,6 @@ setup_envs export KIND_CLUSTER=local-kubebuilder-e2e if ! kind get clusters | grep -q $KIND_CLUSTER ; then source "$(pwd)/scripts/setup.sh" ${KIND_K8S_VERSION} $KIND_CLUSTER - docker pull gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 - kind load --name $KIND_CLUSTER docker-image gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 fi kind export kubeconfig --kubeconfig $tmp_root/kubeconfig --name $KIND_CLUSTER diff --git a/testdata/project-v3-addon/config/default/manager_auth_proxy_patch.yaml b/testdata/project-v3-addon/config/default/manager_auth_proxy_patch.yaml index 49b1f1ab36e..a224be19ea1 100644 --- a/testdata/project-v3-addon/config/default/manager_auth_proxy_patch.yaml +++ b/testdata/project-v3-addon/config/default/manager_auth_proxy_patch.yaml @@ -10,7 +10,7 @@ spec: spec: containers: - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 args: - "--secure-listen-address=0.0.0.0:8443" - "--upstream=http://127.0.0.1:8080/" diff --git a/testdata/project-v3-config/config/default/manager_auth_proxy_patch.yaml b/testdata/project-v3-config/config/default/manager_auth_proxy_patch.yaml index 37c578ab7ae..cf923e8c88a 100644 --- a/testdata/project-v3-config/config/default/manager_auth_proxy_patch.yaml +++ b/testdata/project-v3-config/config/default/manager_auth_proxy_patch.yaml @@ -10,7 +10,7 @@ spec: spec: containers: - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 args: - "--secure-listen-address=0.0.0.0:8443" - "--upstream=http://127.0.0.1:8080/" diff --git a/testdata/project-v3-multigroup/config/default/manager_auth_proxy_patch.yaml b/testdata/project-v3-multigroup/config/default/manager_auth_proxy_patch.yaml index 49b1f1ab36e..a224be19ea1 100644 --- a/testdata/project-v3-multigroup/config/default/manager_auth_proxy_patch.yaml +++ b/testdata/project-v3-multigroup/config/default/manager_auth_proxy_patch.yaml @@ -10,7 +10,7 @@ spec: spec: containers: - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 args: - "--secure-listen-address=0.0.0.0:8443" - "--upstream=http://127.0.0.1:8080/" diff --git a/testdata/project-v3/config/default/manager_auth_proxy_patch.yaml b/testdata/project-v3/config/default/manager_auth_proxy_patch.yaml index 49b1f1ab36e..a224be19ea1 100644 --- a/testdata/project-v3/config/default/manager_auth_proxy_patch.yaml +++ b/testdata/project-v3/config/default/manager_auth_proxy_patch.yaml @@ -10,7 +10,7 @@ spec: spec: containers: - name: kube-rbac-proxy - image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.0 args: - "--secure-listen-address=0.0.0.0:8443" - "--upstream=http://127.0.0.1:8080/" From 0d938e4558a20a251f62aa6b36a80d0f03b8dda2 Mon Sep 17 00:00:00 2001 From: Camila Macedo Date: Mon, 18 Jan 2021 19:49:20 +0000 Subject: [PATCH 10/38] upgrade kubebuilder module from v2 to v3 --- cmd/main.go | 8 +++---- common.sh | 2 +- go.mod | 2 +- pkg/cli/api.go | 4 ++-- pkg/cli/cli.go | 8 +++---- pkg/cli/cli_suite_test.go | 2 +- pkg/cli/cli_test.go | 4 ++-- pkg/cli/cmd_helpers.go | 4 ++-- pkg/cli/edit.go | 4 ++-- pkg/cli/init.go | 6 ++--- pkg/cli/internal/config/config.go | 2 +- pkg/cli/internal/config/config_test.go | 2 +- pkg/cli/options.go | 4 ++-- pkg/cli/options_test.go | 2 +- pkg/cli/webhook.go | 4 ++-- pkg/model/file/interfaces.go | 2 +- pkg/model/file/mixins.go | 2 +- pkg/model/resource/options.go | 4 ++-- pkg/model/resource/options_test.go | 2 +- pkg/model/resource/resource.go | 2 +- pkg/model/resource/resource_test.go | 4 ++-- pkg/model/universe.go | 6 ++--- pkg/plugin/helpers.go | 2 +- pkg/plugin/interfaces.go | 2 +- pkg/plugins/golang/v2/api.go | 16 ++++++------- pkg/plugins/golang/v2/edit.go | 8 +++---- pkg/plugins/golang/v2/init.go | 12 +++++----- pkg/plugins/golang/v2/plugin.go | 6 ++--- pkg/plugins/golang/v2/scaffolds/api.go | 24 +++++++++---------- pkg/plugins/golang/v2/scaffolds/edit.go | 4 ++-- pkg/plugins/golang/v2/scaffolds/init.go | 24 +++++++++---------- .../scaffolds/internal/templates/api/group.go | 2 +- .../scaffolds/internal/templates/api/types.go | 2 +- .../internal/templates/api/webhook.go | 2 +- .../config/certmanager/certificate.go | 2 +- .../config/certmanager/kustomization.go | 2 +- .../config/certmanager/kustomizeconfig.go | 2 +- .../templates/config/crd/kustomization.go | 2 +- .../templates/config/crd/kustomizeconfig.go | 2 +- .../crd/patches/enablecainjection_patch.go | 2 +- .../config/crd/patches/enablewebhook_patch.go | 2 +- .../config/kdefault/enablecainection_patch.go | 2 +- .../config/kdefault/kustomization.go | 2 +- .../kdefault/manager_auth_proxy_patch.go | 2 +- .../config/kdefault/webhook_manager_patch.go | 2 +- .../templates/config/manager/config.go | 2 +- .../templates/config/manager/kustomization.go | 2 +- .../config/prometheus/kustomization.go | 2 +- .../templates/config/prometheus/monitor.go | 2 +- .../config/rbac/auth_proxy_client_role.go | 2 +- .../templates/config/rbac/auth_proxy_role.go | 2 +- .../config/rbac/auth_proxy_role_binding.go | 2 +- .../config/rbac/auth_proxy_service.go | 2 +- .../templates/config/rbac/crd_editor_role.go | 2 +- .../templates/config/rbac/crd_viewer_role.go | 2 +- .../templates/config/rbac/kustomization.go | 2 +- .../config/rbac/leader_election_role.go | 2 +- .../rbac/leader_election_role_binding.go | 2 +- .../templates/config/rbac/role_binding.go | 2 +- .../templates/config/samples/crd_sample.go | 2 +- .../templates/config/webhook/kustomization.go | 2 +- .../config/webhook/kustomizeconfig.go | 2 +- .../templates/config/webhook/service.go | 2 +- .../templates/controllers/controller.go | 2 +- .../controllers/controller_suitetest.go | 2 +- .../internal/templates/dockerfile.go | 2 +- .../scaffolds/internal/templates/gitignore.go | 2 +- .../v2/scaffolds/internal/templates/gomod.go | 2 +- .../internal/templates/hack/boilerplate.go | 2 +- .../v2/scaffolds/internal/templates/main.go | 2 +- .../scaffolds/internal/templates/makefile.go | 2 +- pkg/plugins/golang/v2/scaffolds/webhook.go | 14 +++++------ pkg/plugins/golang/v2/webhook.go | 10 ++++---- pkg/plugins/golang/v3/api.go | 16 ++++++------- pkg/plugins/golang/v3/edit.go | 8 +++---- pkg/plugins/golang/v3/init.go | 12 +++++----- pkg/plugins/golang/v3/plugin.go | 6 ++--- pkg/plugins/golang/v3/scaffolds/api.go | 24 +++++++++---------- pkg/plugins/golang/v3/scaffolds/edit.go | 4 ++-- pkg/plugins/golang/v3/scaffolds/init.go | 22 ++++++++--------- .../scaffolds/internal/templates/api/group.go | 2 +- .../scaffolds/internal/templates/api/types.go | 2 +- .../internal/templates/api/webhook.go | 2 +- .../templates/api/webhook_suitetest.go | 2 +- .../config/certmanager/certificate.go | 2 +- .../config/certmanager/kustomization.go | 2 +- .../config/certmanager/kustomizeconfig.go | 2 +- .../templates/config/crd/kustomization.go | 2 +- .../templates/config/crd/kustomizeconfig.go | 2 +- .../crd/patches/enablecainjection_patch.go | 2 +- .../config/crd/patches/enablewebhook_patch.go | 2 +- .../config/kdefault/enablecainection_patch.go | 2 +- .../config/kdefault/kustomization.go | 2 +- .../kdefault/manager_auth_proxy_patch.go | 2 +- .../config/kdefault/manager_config_patch.go | 2 +- .../config/kdefault/webhook_manager_patch.go | 2 +- .../templates/config/manager/config.go | 2 +- .../manager/controller_manager_config.go | 2 +- .../templates/config/manager/kustomization.go | 2 +- .../config/prometheus/kustomization.go | 2 +- .../templates/config/prometheus/monitor.go | 2 +- .../config/rbac/auth_proxy_client_role.go | 2 +- .../templates/config/rbac/auth_proxy_role.go | 2 +- .../config/rbac/auth_proxy_role_binding.go | 2 +- .../config/rbac/auth_proxy_service.go | 2 +- .../templates/config/rbac/crd_editor_role.go | 2 +- .../templates/config/rbac/crd_viewer_role.go | 2 +- .../templates/config/rbac/kustomization.go | 2 +- .../config/rbac/leader_election_role.go | 2 +- .../rbac/leader_election_role_binding.go | 2 +- .../templates/config/rbac/role_binding.go | 2 +- .../templates/config/samples/crd_sample.go | 2 +- .../templates/config/webhook/kustomization.go | 2 +- .../config/webhook/kustomizeconfig.go | 2 +- .../templates/config/webhook/service.go | 2 +- .../templates/controllers/controller.go | 2 +- .../controllers/controller_suitetest.go | 2 +- .../internal/templates/dockerfile.go | 2 +- .../internal/templates/dockerignore.go | 2 +- .../scaffolds/internal/templates/gitignore.go | 2 +- .../v3/scaffolds/internal/templates/gomod.go | 2 +- .../internal/templates/hack/boilerplate.go | 2 +- .../v3/scaffolds/internal/templates/main.go | 2 +- .../scaffolds/internal/templates/makefile.go | 2 +- pkg/plugins/golang/v3/scaffolds/webhook.go | 18 +++++++------- pkg/plugins/golang/v3/webhook.go | 12 +++++----- pkg/plugins/internal/machinery/errors.go | 2 +- pkg/plugins/internal/machinery/scaffold.go | 6 ++--- .../internal/machinery/scaffold_test.go | 6 ++--- plugins/addon/channel.go | 4 ++-- plugins/addon/controller.go | 4 ++-- plugins/addon/helpers.go | 4 ++-- plugins/addon/manifest.go | 4 ++-- plugins/addon/plugin.go | 2 +- plugins/addon/type.go | 4 ++-- test/e2e/v2/plugin_cluster_test.go | 2 +- test/e2e/v3/generate_test.go | 2 +- test/e2e/v3/plugin_cluster_test.go | 2 +- 138 files changed, 267 insertions(+), 267 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index bb657623976..e95a455f76b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -19,10 +19,10 @@ package main import ( "log" - "sigs.k8s.io/kubebuilder/v2/pkg/cli" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - pluginv2 "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2" - pluginv3 "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3" + "sigs.k8s.io/kubebuilder/v3/pkg/cli" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + pluginv2 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2" + pluginv3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3" ) func main() { diff --git a/common.sh b/common.sh index 1e34c98cb2a..9340312eaa0 100644 --- a/common.sh +++ b/common.sh @@ -145,7 +145,7 @@ function build_kb { opts="" else # Injects the version into the cmd/version.go file - opts=-ldflags "-X sigs.k8s.io/kubebuilder/v2/cmd.kubeBuilderVersion=$INJECT_KB_VERSION" + opts=-ldflags "-X sigs.k8s.io/kubebuilder/v3/cmd.kubeBuilderVersion=$INJECT_KB_VERSION" fi GO111MODULE=on go build $opts -o $kb_root_dir/bin/kubebuilder ./cmd diff --git a/go.mod b/go.mod index 2007bc88153..909387bf224 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module sigs.k8s.io/kubebuilder/v2 +module sigs.k8s.io/kubebuilder/v3 go 1.15 diff --git a/pkg/cli/api.go b/pkg/cli/api.go index dbc0477e27a..d40680af112 100644 --- a/pkg/cli/api.go +++ b/pkg/cli/api.go @@ -21,8 +21,8 @@ import ( "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v2/pkg/cli/internal/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) func (c cli) newCreateAPICmd() *cobra.Command { diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index ad8eec92948..f4437f4baff 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -24,10 +24,10 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - internalconfig "sigs.k8s.io/kubebuilder/v2/pkg/cli/internal/config" - "sigs.k8s.io/kubebuilder/v2/pkg/internal/validation" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + internalconfig "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) const ( diff --git a/pkg/cli/cli_suite_test.go b/pkg/cli/cli_suite_test.go index a5bb02af848..563a47fcb9b 100644 --- a/pkg/cli/cli_suite_test.go +++ b/pkg/cli/cli_suite_test.go @@ -22,7 +22,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) func TestCLI(t *testing.T) { diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index 7413458117c..6f01e97bdc5 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -25,8 +25,8 @@ import ( . "github.com/onsi/gomega" "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) func makeMockPluginsFor(projectVersion string, pluginKeys ...string) []plugin.Plugin { diff --git a/pkg/cli/cmd_helpers.go b/pkg/cli/cmd_helpers.go index b2b5c420fee..a7b51d6ee6f 100644 --- a/pkg/cli/cmd_helpers.go +++ b/pkg/cli/cmd_helpers.go @@ -21,8 +21,8 @@ import ( "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v2/pkg/cli/internal/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) // cmdErr updates a cobra command to output error information when executed diff --git a/pkg/cli/edit.go b/pkg/cli/edit.go index 102e8dea3f8..ae6df00d1c2 100644 --- a/pkg/cli/edit.go +++ b/pkg/cli/edit.go @@ -21,8 +21,8 @@ import ( "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v2/pkg/cli/internal/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) func (c cli) newEditCmd() *cobra.Command { diff --git a/pkg/cli/init.go b/pkg/cli/init.go index 1822d9a0c3d..35e0f436469 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -26,9 +26,9 @@ import ( "github.com/spf13/cobra" - internalconfig "sigs.k8s.io/kubebuilder/v2/pkg/cli/internal/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + internalconfig "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) func (c cli) newInitCmd() *cobra.Command { diff --git a/pkg/cli/internal/config/config.go b/pkg/cli/internal/config/config.go index 3deac3440ef..5e556138167 100644 --- a/pkg/cli/internal/config/config.go +++ b/pkg/cli/internal/config/config.go @@ -23,7 +23,7 @@ import ( "github.com/spf13/afero" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" ) const ( diff --git a/pkg/cli/internal/config/config_test.go b/pkg/cli/internal/config/config_test.go index 51538639a66..cdfa9caa51d 100644 --- a/pkg/cli/internal/config/config_test.go +++ b/pkg/cli/internal/config/config_test.go @@ -23,7 +23,7 @@ import ( . "github.com/onsi/gomega" "github.com/spf13/afero" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" ) var _ = Describe("Config", func() { diff --git a/pkg/cli/options.go b/pkg/cli/options.go index 219ab321ce9..6187579f100 100644 --- a/pkg/cli/options.go +++ b/pkg/cli/options.go @@ -21,8 +21,8 @@ import ( "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v2/pkg/internal/validation" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) // Option is a function that can configure the cli diff --git a/pkg/cli/options_test.go b/pkg/cli/options_test.go index 87686f73552..d24f8ab8118 100644 --- a/pkg/cli/options_test.go +++ b/pkg/cli/options_test.go @@ -23,7 +23,7 @@ import ( . "github.com/onsi/gomega" "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) var _ = Describe("CLI options", func() { diff --git a/pkg/cli/webhook.go b/pkg/cli/webhook.go index 56dd71d9188..9a61a6c4579 100644 --- a/pkg/cli/webhook.go +++ b/pkg/cli/webhook.go @@ -21,8 +21,8 @@ import ( "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v2/pkg/cli/internal/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) func (c cli) newCreateWebhookCmd() *cobra.Command { diff --git a/pkg/model/file/interfaces.go b/pkg/model/file/interfaces.go index 2469d6f3524..43470df4e60 100644 --- a/pkg/model/file/interfaces.go +++ b/pkg/model/file/interfaces.go @@ -19,7 +19,7 @@ package file import ( "text/template" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) // Builder defines the basic methods that any file builder must implement diff --git a/pkg/model/file/mixins.go b/pkg/model/file/mixins.go index eee4dd7083c..fcf1dbbbf58 100644 --- a/pkg/model/file/mixins.go +++ b/pkg/model/file/mixins.go @@ -17,7 +17,7 @@ limitations under the License. package file import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) // PathMixin provides file builders with a path field diff --git a/pkg/model/resource/options.go b/pkg/model/resource/options.go index 956ac694a63..b763a607ab2 100644 --- a/pkg/model/resource/options.go +++ b/pkg/model/resource/options.go @@ -24,8 +24,8 @@ import ( "github.com/gobuffalo/flect" - "sigs.k8s.io/kubebuilder/v2/pkg/internal/validation" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" ) const ( diff --git a/pkg/model/resource/options_test.go b/pkg/model/resource/options_test.go index e27e81ea141..65f8511a821 100644 --- a/pkg/model/resource/options_test.go +++ b/pkg/model/resource/options_test.go @@ -7,7 +7,7 @@ import ( . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - . "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" + . "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) var _ = Describe("Resource Options", func() { diff --git a/pkg/model/resource/resource.go b/pkg/model/resource/resource.go index 300a63fec43..ce3e223411d 100644 --- a/pkg/model/resource/resource.go +++ b/pkg/model/resource/resource.go @@ -20,7 +20,7 @@ import ( "fmt" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" ) // Resource contains the information required to scaffold files for a resource. diff --git a/pkg/model/resource/resource_test.go b/pkg/model/resource/resource_test.go index fa847cc19c1..b213511e371 100644 --- a/pkg/model/resource/resource_test.go +++ b/pkg/model/resource/resource_test.go @@ -22,8 +22,8 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - . "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + . "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) var _ = Describe("Resource", func() { diff --git a/pkg/model/universe.go b/pkg/model/universe.go index b00b6769585..1c148b6b350 100644 --- a/pkg/model/universe.go +++ b/pkg/model/universe.go @@ -17,9 +17,9 @@ limitations under the License. package model import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) // Universe describes the entire state of file generation diff --git a/pkg/plugin/helpers.go b/pkg/plugin/helpers.go index e2394e133e9..4dafbc78dd7 100644 --- a/pkg/plugin/helpers.go +++ b/pkg/plugin/helpers.go @@ -21,7 +21,7 @@ import ( "path" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" ) // Key returns a unique identifying string for a plugin's name and version. diff --git a/pkg/plugin/interfaces.go b/pkg/plugin/interfaces.go index 3ae7d0fd4ef..bd6420f7814 100644 --- a/pkg/plugin/interfaces.go +++ b/pkg/plugin/interfaces.go @@ -19,7 +19,7 @@ package plugin import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" ) // Plugin is an interface that defines the common base for all plugins diff --git a/pkg/plugins/golang/v2/api.go b/pkg/plugins/golang/v2/api.go index 43dc27f7861..e4ab7b8e7d4 100644 --- a/pkg/plugins/golang/v2/api.go +++ b/pkg/plugins/golang/v2/api.go @@ -27,14 +27,14 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/util" - "sigs.k8s.io/kubebuilder/v2/plugins/addon" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" + "sigs.k8s.io/kubebuilder/v3/plugins/addon" ) type createAPISubcommand struct { diff --git a/pkg/plugins/golang/v2/edit.go b/pkg/plugins/golang/v2/edit.go index 01f23520f96..43c1b134602 100644 --- a/pkg/plugins/golang/v2/edit.go +++ b/pkg/plugins/golang/v2/edit.go @@ -21,10 +21,10 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) type editSubcommand struct { diff --git a/pkg/plugins/golang/v2/init.go b/pkg/plugins/golang/v2/init.go index 08b619493c3..eb95986236a 100644 --- a/pkg/plugins/golang/v2/init.go +++ b/pkg/plugins/golang/v2/init.go @@ -24,12 +24,12 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v2/pkg/internal/validation" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/util" + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" ) type initSubcommand struct { diff --git a/pkg/plugins/golang/v2/plugin.go b/pkg/plugins/golang/v2/plugin.go index 8eb6a974cc0..d106e3f53d4 100644 --- a/pkg/plugins/golang/v2/plugin.go +++ b/pkg/plugins/golang/v2/plugin.go @@ -17,9 +17,9 @@ limitations under the License. package v2 import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" ) const pluginName = "go" + plugins.DefaultNameQualifier diff --git a/pkg/plugins/golang/v2/scaffolds/api.go b/pkg/plugins/golang/v2/scaffolds/api.go index 412a30c6f1b..02088a8b5cf 100644 --- a/pkg/plugins/golang/v2/scaffolds/api.go +++ b/pkg/plugins/golang/v2/scaffolds/api.go @@ -19,18 +19,18 @@ package scaffolds import ( "fmt" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/api" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/api" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) // KbDeclarativePattern is the sigs.k8s.io/kubebuilder-declarative-pattern version diff --git a/pkg/plugins/golang/v2/scaffolds/edit.go b/pkg/plugins/golang/v2/scaffolds/edit.go index 0d9342f097e..8dcc606488f 100644 --- a/pkg/plugins/golang/v2/scaffolds/edit.go +++ b/pkg/plugins/golang/v2/scaffolds/edit.go @@ -21,8 +21,8 @@ import ( "io/ioutil" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) var _ cmdutil.Scaffolder = &editScaffolder{} diff --git a/pkg/plugins/golang/v2/scaffolds/init.go b/pkg/plugins/golang/v2/scaffolds/init.go index 1d5cc956bdc..c1cdd657192 100644 --- a/pkg/plugins/golang/v2/scaffolds/init.go +++ b/pkg/plugins/golang/v2/scaffolds/init.go @@ -21,18 +21,18 @@ import ( "io/ioutil" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/hack" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/hack" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) const ( diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go index ee4c9872459..d35163ebba9 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go @@ -19,7 +19,7 @@ package api import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Group{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go index a1a9b588398..675f278a802 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Types{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go index 99229d5149e..13a09c390d3 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go @@ -21,7 +21,7 @@ import ( "path/filepath" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Webhook{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go index ff3871988f6..c8ced013fc8 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/certificate.go @@ -19,7 +19,7 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Certificate{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go index 7920f960ecc..7437ab18bf1 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomization.go @@ -19,7 +19,7 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go index 220e162503d..8eada694a10 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go @@ -19,7 +19,7 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &KustomizeConfig{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go index fc9de67ded7..d737c659d3b 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go index b7152035b8b..84025c65e3a 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomizeconfig.go @@ -19,7 +19,7 @@ package crd import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &KustomizeConfig{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go index b841f7937f9..09ce35d75e2 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go @@ -19,7 +19,7 @@ package patches import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &EnableCAInjectionPatch{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go index 0addc41fa7b..b1495ff09ba 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go @@ -19,7 +19,7 @@ package patches import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &EnableWebhookPatch{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go index 6cf2aa3250f..c51474d4cb5 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go @@ -19,7 +19,7 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &WebhookCAInjectionPatch{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go index e2df654a44a..1250bcfcdaf 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/kustomization.go @@ -21,7 +21,7 @@ import ( "path/filepath" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go index 313afba4917..8f8be890a7e 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go @@ -19,7 +19,7 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &ManagerAuthProxyPatch{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go index d28413b8af5..65e34c7d793 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go @@ -19,7 +19,7 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &ManagerWebhookPatch{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go index 025ee63e889..0a1bc1cdeb7 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/config.go @@ -19,7 +19,7 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Config{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go index 6b7a5aa7d37..1832137b95b 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/manager/kustomization.go @@ -19,7 +19,7 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go index ae9bd15b2c2..16b9bb4b563 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/kustomization.go @@ -19,7 +19,7 @@ package prometheus import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go index e30fb7456e0..4ee7d32c1c2 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/prometheus/monitor.go @@ -19,7 +19,7 @@ package prometheus import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Monitor{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go index dc9ee860288..2b6d77e596f 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &AuthProxyClientRole{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go index bc7581e870c..01b8413b855 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &AuthProxyRole{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go index a9410986ee1..1aaf9ebbf85 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &AuthProxyRoleBinding{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go index f842b231099..e84ec0e322c 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/auth_proxy_service.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &AuthProxyService{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go index dc3cce9fae9..c62c2cebcd4 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &CRDEditorRole{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go index 4f71ec9b70e..b89716c46ca 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &CRDViewerRole{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go index 8a101cb04ab..d8d9b41b180 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/kustomization.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go index 6ed9c8120b3..b787f441f59 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &LeaderElectionRole{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go index e0bb6afa90d..f196868a3f8 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &LeaderElectionRoleBinding{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go index 34e47662ea7..6786223d648 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/role_binding.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &RoleBinding{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go index 0cb4f4f131d..9d51be29b6c 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go @@ -19,7 +19,7 @@ package samples import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &CRDSample{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go index 3dcf19850f0..c16ee89b9f6 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomization.go @@ -19,7 +19,7 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go index 726b45fa73a..1b43d4ee825 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/kustomizeconfig.go @@ -19,7 +19,7 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &KustomizeConfig{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go index 958c121d4c1..3a2689c4d42 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/webhook/service.go @@ -19,7 +19,7 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Service{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go index 069d225ca08..61710581e27 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Controller{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go index b60188ea1c3..149828522bf 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &SuiteTest{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go index 59b0b9de44a..1d20cafef78 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/dockerfile.go @@ -17,7 +17,7 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Dockerfile{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go index 547fb7e773f..4505799177b 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/gitignore.go @@ -17,7 +17,7 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &GitIgnore{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go index 5dc9a27c921..f40e8eda54d 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/gomod.go @@ -17,7 +17,7 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &GoMod{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go index d90315272ea..561d4befbdc 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/hack/boilerplate.go @@ -21,7 +21,7 @@ import ( "path/filepath" "time" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Boilerplate{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go index 75cc49fba1d..e3dd331ad12 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) const defaultMainPath = "main.go" diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go index 61b7ce5295a..6aa8c56c6c8 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go @@ -17,7 +17,7 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Makefile{} diff --git a/pkg/plugins/golang/v2/scaffolds/webhook.go b/pkg/plugins/golang/v2/scaffolds/webhook.go index d5479894cb9..b38642ad011 100644 --- a/pkg/plugins/golang/v2/scaffolds/webhook.go +++ b/pkg/plugins/golang/v2/scaffolds/webhook.go @@ -19,13 +19,13 @@ package scaffolds import ( "fmt" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds/internal/templates/api" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/api" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) var _ cmdutil.Scaffolder = &webhookScaffolder{} diff --git a/pkg/plugins/golang/v2/webhook.go b/pkg/plugins/golang/v2/webhook.go index 7e4f3a1277a..5fbe63fd24c 100644 --- a/pkg/plugins/golang/v2/webhook.go +++ b/pkg/plugins/golang/v2/webhook.go @@ -23,11 +23,11 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v2/scaffolds" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) type createWebhookSubcommand struct { diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index 09f21c22c2b..a1795d86b66 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -27,14 +27,14 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/util" - "sigs.k8s.io/kubebuilder/v2/plugins/addon" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" + "sigs.k8s.io/kubebuilder/v3/plugins/addon" ) const ( diff --git a/pkg/plugins/golang/v3/edit.go b/pkg/plugins/golang/v3/edit.go index c22b07fbd69..b42d300f6c7 100644 --- a/pkg/plugins/golang/v3/edit.go +++ b/pkg/plugins/golang/v3/edit.go @@ -21,10 +21,10 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) type editSubcommand struct { diff --git a/pkg/plugins/golang/v3/init.go b/pkg/plugins/golang/v3/init.go index 1dc84e67f4d..7daabc47b3d 100644 --- a/pkg/plugins/golang/v3/init.go +++ b/pkg/plugins/golang/v3/init.go @@ -24,12 +24,12 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v2/pkg/internal/validation" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/util" + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" ) type initSubcommand struct { diff --git a/pkg/plugins/golang/v3/plugin.go b/pkg/plugins/golang/v3/plugin.go index eec6f9b5e7d..edbae552e3a 100644 --- a/pkg/plugins/golang/v3/plugin.go +++ b/pkg/plugins/golang/v3/plugin.go @@ -17,9 +17,9 @@ limitations under the License. package v3 import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins" ) const pluginName = "go" + plugins.DefaultNameQualifier diff --git a/pkg/plugins/golang/v3/scaffolds/api.go b/pkg/plugins/golang/v3/scaffolds/api.go index 289ee683044..9d7f798b8ad 100644 --- a/pkg/plugins/golang/v3/scaffolds/api.go +++ b/pkg/plugins/golang/v3/scaffolds/api.go @@ -19,18 +19,18 @@ package scaffolds import ( "fmt" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/api" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/api" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) var _ cmdutil.Scaffolder = &apiScaffolder{} diff --git a/pkg/plugins/golang/v3/scaffolds/edit.go b/pkg/plugins/golang/v3/scaffolds/edit.go index 08c79725061..1f210110c82 100644 --- a/pkg/plugins/golang/v3/scaffolds/edit.go +++ b/pkg/plugins/golang/v3/scaffolds/edit.go @@ -21,8 +21,8 @@ import ( "io/ioutil" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) var _ cmdutil.Scaffolder = &editScaffolder{} diff --git a/pkg/plugins/golang/v3/scaffolds/init.go b/pkg/plugins/golang/v3/scaffolds/init.go index 560db863cb0..cc5fb251aab 100644 --- a/pkg/plugins/golang/v3/scaffolds/init.go +++ b/pkg/plugins/golang/v3/scaffolds/init.go @@ -21,17 +21,17 @@ import ( "io/ioutil" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/hack" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/hack" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) const ( diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go index a184c4390de..e3ead93db6e 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go @@ -19,7 +19,7 @@ package api import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Group{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go index 42223887bca..8147a5ad5f7 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Types{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go index 7ab22f089ad..5d276c012f2 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go @@ -21,7 +21,7 @@ import ( "path/filepath" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Webhook{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go index b0b6400be89..0424fdd9a8c 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go @@ -4,7 +4,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &WebhookSuite{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/certificate.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/certificate.go index 1fb6a4c5079..a654f82951f 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/certificate.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/certificate.go @@ -19,7 +19,7 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Certificate{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomization.go index 13ba3476cd0..4210f6f133b 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomization.go @@ -19,7 +19,7 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go index d31c8fb979b..8ade67534fc 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager/kustomizeconfig.go @@ -19,7 +19,7 @@ package certmanager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &KustomizeConfig{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go index 21acdb525b1..5d0483cac9d 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go index 2aff259fdbc..700ef4df5f9 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go @@ -19,7 +19,7 @@ package crd import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) const v1 = "v1" diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go index a61e42dd3f0..df19081c768 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go @@ -19,7 +19,7 @@ package patches import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) const v1 = "v1" diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go index 2fedd028b88..9386f0a9c5d 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go @@ -19,7 +19,7 @@ package patches import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &EnableWebhookPatch{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go index 90ff233f4f8..85494a1bd61 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go @@ -19,7 +19,7 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &WebhookCAInjectionPatch{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/kustomization.go index f56e6eed87e..c7aea4259fb 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/kustomization.go @@ -19,7 +19,7 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go index 8f4f21a60ee..4c72684ac13 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_auth_proxy_patch.go @@ -19,7 +19,7 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &ManagerAuthProxyPatch{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_config_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_config_patch.go index dbdb274c381..8b32276fe7b 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_config_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/manager_config_patch.go @@ -19,7 +19,7 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &ManagerConfigPatch{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go index 566c66b8857..3d86dd4f091 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/webhook_manager_patch.go @@ -19,7 +19,7 @@ package kdefault import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &ManagerWebhookPatch{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go index 5c0eeb22766..477ee76aa7c 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go @@ -19,7 +19,7 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Config{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/controller_manager_config.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/controller_manager_config.go index 04f2c29eb75..ea18f2145a0 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/controller_manager_config.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/controller_manager_config.go @@ -19,7 +19,7 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &ControllerManagerConfig{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/kustomization.go index 07d43a4dcdf..5b3f307acfb 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/kustomization.go @@ -19,7 +19,7 @@ package manager import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/kustomization.go index 6e4f09304a3..e2cd771253a 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/kustomization.go @@ -19,7 +19,7 @@ package prometheus import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/monitor.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/monitor.go index e30fb7456e0..4ee7d32c1c2 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/monitor.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/prometheus/monitor.go @@ -19,7 +19,7 @@ package prometheus import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Monitor{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go index 379de5ce3fc..d7bfee31882 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_client_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &AuthProxyClientRole{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role.go index 42d191d482e..f5900ec2b8e 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &AuthProxyRole{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go index b524868dadf..3834de9bce4 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_role_binding.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &AuthProxyRoleBinding{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_service.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_service.go index 963a4473d80..ffd9cd2ab19 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_service.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/auth_proxy_service.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &AuthProxyService{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go index b93abbde7ed..b13fba6b7d1 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &CRDEditorRole{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go index 667e8242baa..6ef7b28a7dd 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &CRDViewerRole{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/kustomization.go index cc00346e969..0966bd6d5d9 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/kustomization.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role.go index acdef4c0291..3f85432601f 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &LeaderElectionRole{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go index fcf95ea83ac..8c67aac8a54 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/leader_election_role_binding.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &LeaderElectionRoleBinding{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/role_binding.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/role_binding.go index 9d71f5c5119..40f1bd783e1 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/role_binding.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/role_binding.go @@ -19,7 +19,7 @@ package rbac import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &RoleBinding{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go index 0e967378981..444501525e7 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go @@ -19,7 +19,7 @@ package samples import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &CRDSample{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go index e9368018799..08fede11e1d 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go @@ -19,7 +19,7 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Kustomization{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomizeconfig.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomizeconfig.go index 86a6fa5c5b4..a719ae63c46 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomizeconfig.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomizeconfig.go @@ -19,7 +19,7 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &KustomizeConfig{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/service.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/service.go index 2edae713a45..f05e6bc719a 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/service.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/service.go @@ -19,7 +19,7 @@ package webhook import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Service{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go index 3eb6a15b141..226f0e99c83 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Controller{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go index 48e64f0f973..709e81f15e2 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &SuiteTest{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go index 1a467d56995..359a0a2ebc5 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerfile.go @@ -17,7 +17,7 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Dockerfile{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go index a933b1e0a5d..7aa0150303d 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/dockerignore.go @@ -17,7 +17,7 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &DockerIgnore{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go index 08e8820c651..bbf1e92bf53 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/gitignore.go @@ -17,7 +17,7 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &GitIgnore{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go index d58861ec4f7..fdb3ce26764 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/gomod.go @@ -17,7 +17,7 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &GoMod{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go index 86150c8d150..e8a21cbe6ce 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/hack/boilerplate.go @@ -21,7 +21,7 @@ import ( "path/filepath" "time" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Boilerplate{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go index f7ea2bdc8ad..4efafcb219b 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go @@ -20,7 +20,7 @@ import ( "fmt" "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) const defaultMainPath = "main.go" diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go index a620818d331..50d9468c6c4 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go @@ -17,7 +17,7 @@ limitations under the License. package templates import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) var _ file.Template = &Makefile{} diff --git a/pkg/plugins/golang/v3/scaffolds/webhook.go b/pkg/plugins/golang/v3/scaffolds/webhook.go index 200ef52d1b2..1dea4143336 100644 --- a/pkg/plugins/golang/v3/scaffolds/webhook.go +++ b/pkg/plugins/golang/v3/scaffolds/webhook.go @@ -19,15 +19,15 @@ package scaffolds import ( "fmt" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/api" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/machinery" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/api" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/machinery" ) var _ cmdutil.Scaffolder = &webhookScaffolder{} diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go index 736dc485dcc..96456324644 100644 --- a/pkg/plugins/golang/v3/webhook.go +++ b/pkg/plugins/golang/v3/webhook.go @@ -24,12 +24,12 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v2/pkg/model/config" - "sigs.k8s.io/kubebuilder/v2/pkg/model/resource" - "sigs.k8s.io/kubebuilder/v2/pkg/plugin" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/golang/v3/scaffolds" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/util" + "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" ) // defaultWebhookVersion is the default mutating/validating webhook config API version to scaffold. diff --git a/pkg/plugins/internal/machinery/errors.go b/pkg/plugins/internal/machinery/errors.go index 31dcc101c92..faba57a1d05 100644 --- a/pkg/plugins/internal/machinery/errors.go +++ b/pkg/plugins/internal/machinery/errors.go @@ -20,7 +20,7 @@ import ( "errors" "fmt" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) // This file contains the errors returned by the scaffolding machinery diff --git a/pkg/plugins/internal/machinery/scaffold.go b/pkg/plugins/internal/machinery/scaffold.go index afb538f3199..32e5832b810 100644 --- a/pkg/plugins/internal/machinery/scaffold.go +++ b/pkg/plugins/internal/machinery/scaffold.go @@ -27,9 +27,9 @@ import ( "golang.org/x/tools/imports" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/filesystem" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/filesystem" ) var options = imports.Options{ diff --git a/pkg/plugins/internal/machinery/scaffold_test.go b/pkg/plugins/internal/machinery/scaffold_test.go index 36f46a58327..d7e2a6c35e8 100644 --- a/pkg/plugins/internal/machinery/scaffold_test.go +++ b/pkg/plugins/internal/machinery/scaffold_test.go @@ -23,9 +23,9 @@ import ( . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" - "sigs.k8s.io/kubebuilder/v2/pkg/plugins/internal/filesystem" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/filesystem" ) func TestScaffold(t *testing.T) { diff --git a/plugins/addon/channel.go b/plugins/addon/channel.go index 7daa8b95e7e..f4804e5d528 100644 --- a/plugins/addon/channel.go +++ b/plugins/addon/channel.go @@ -19,8 +19,8 @@ package addon import ( "path/filepath" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) const exampleChannel = `# Versions for the stable channel diff --git a/plugins/addon/controller.go b/plugins/addon/controller.go index f9400e0d4f4..0c70e80242f 100644 --- a/plugins/addon/controller.go +++ b/plugins/addon/controller.go @@ -4,8 +4,8 @@ import ( "path/filepath" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) // ReplaceController replaces the controller with a modified version diff --git a/plugins/addon/helpers.go b/plugins/addon/helpers.go index 2171080d270..48f91e89d8d 100644 --- a/plugins/addon/helpers.go +++ b/plugins/addon/helpers.go @@ -8,8 +8,8 @@ import ( "github.com/gobuffalo/flect" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) // This file gathers functions that are likely to be useful to other diff --git a/plugins/addon/manifest.go b/plugins/addon/manifest.go index 999708be7ba..227f1300ef1 100644 --- a/plugins/addon/manifest.go +++ b/plugins/addon/manifest.go @@ -20,8 +20,8 @@ import ( "path/filepath" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) const exampleManifestVersion = "0.0.1" diff --git a/plugins/addon/plugin.go b/plugins/addon/plugin.go index 37080aa149d..3da0363b929 100644 --- a/plugins/addon/plugin.go +++ b/plugins/addon/plugin.go @@ -1,7 +1,7 @@ package addon import ( - "sigs.k8s.io/kubebuilder/v2/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model" ) // Plugin implements model.Plugin diff --git a/plugins/addon/type.go b/plugins/addon/type.go index 1ab4749ccdb..4046a190d38 100644 --- a/plugins/addon/type.go +++ b/plugins/addon/type.go @@ -5,8 +5,8 @@ import ( "path/filepath" "strings" - "sigs.k8s.io/kubebuilder/v2/pkg/model" - "sigs.k8s.io/kubebuilder/v2/pkg/model/file" + "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) // ReplaceTypes replaces the API types with a modified version diff --git a/test/e2e/v2/plugin_cluster_test.go b/test/e2e/v2/plugin_cluster_test.go index 9fa43938606..6832259788a 100644 --- a/test/e2e/v2/plugin_cluster_test.go +++ b/test/e2e/v2/plugin_cluster_test.go @@ -27,7 +27,7 @@ import ( . "github.com/onsi/ginkgo" //nolint:golint . "github.com/onsi/gomega" //nolint:golint - "sigs.k8s.io/kubebuilder/v2/test/e2e/utils" + "sigs.k8s.io/kubebuilder/v3/test/e2e/utils" ) var _ = Describe("kubebuilder", func() { diff --git a/test/e2e/v3/generate_test.go b/test/e2e/v3/generate_test.go index 1afe1f58e06..1e24526de8b 100644 --- a/test/e2e/v3/generate_test.go +++ b/test/e2e/v3/generate_test.go @@ -25,7 +25,7 @@ import ( . "github.com/onsi/ginkgo" //nolint:golint . "github.com/onsi/gomega" //nolint:golint - "sigs.k8s.io/kubebuilder/v2/test/e2e/utils" + "sigs.k8s.io/kubebuilder/v3/test/e2e/utils" ) // GenerateV2 implements a go/v2 plugin project defined by a TestContext. diff --git a/test/e2e/v3/plugin_cluster_test.go b/test/e2e/v3/plugin_cluster_test.go index e2e039086ed..56167aabb9e 100644 --- a/test/e2e/v3/plugin_cluster_test.go +++ b/test/e2e/v3/plugin_cluster_test.go @@ -27,7 +27,7 @@ import ( . "github.com/onsi/ginkgo" //nolint:golint . "github.com/onsi/gomega" //nolint:golint - "sigs.k8s.io/kubebuilder/v2/test/e2e/utils" + "sigs.k8s.io/kubebuilder/v3/test/e2e/utils" ) var _ = Describe("kubebuilder", func() { From 8d108aa84fb126e0e347a1e3b8078cd084b71c38 Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Tue, 19 Jan 2021 16:35:12 -0800 Subject: [PATCH 11/38] test commit Signed-off-by: Eric Stroczynski --- test/e2e/v3/plugin_cluster_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/e2e/v3/plugin_cluster_test.go b/test/e2e/v3/plugin_cluster_test.go index dff4feed0fd..07cd3980dc2 100644 --- a/test/e2e/v3/plugin_cluster_test.go +++ b/test/e2e/v3/plugin_cluster_test.go @@ -155,6 +155,11 @@ func Run(kbc *utils.TestContext) { } return nil } + defer func() { + out, err := kbc.Kubectl.CommandInNamespace("describe", "all") + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + fmt.Fprintln(GinkgoWriter, out) + }() EventuallyWithOffset(1, verifyControllerUp, time.Minute, time.Second).Should(Succeed()) By("granting permissions to access the metrics") From ea75c59347181a310ca0a21b84b631309b158373 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Thu, 17 Dec 2020 09:07:41 +0100 Subject: [PATCH 12/38] Update Options and Resource models and implement Config as an interface for better project version maintanability Signed-off-by: Adrian Orive --- cmd/main.go | 9 +- pkg/cli/api.go | 2 +- pkg/cli/cli.go | 104 +-- pkg/cli/cli_suite_test.go | 17 +- pkg/cli/cli_test.go | 226 +++---- pkg/cli/edit.go | 2 +- pkg/cli/init.go | 20 +- pkg/cli/internal/config/config.go | 56 +- pkg/cli/internal/config/config_test.go | 182 +----- pkg/cli/options.go | 10 +- pkg/cli/options_test.go | 36 +- pkg/cli/root.go | 21 +- pkg/cli/webhook.go | 2 +- pkg/config/errors.go | 85 +++ pkg/config/errors_test.go | 106 +++ pkg/config/interface.go | 114 ++++ pkg/config/registry.go | 37 ++ pkg/config/resgistry_test.go | 56 ++ .../suite_test.go} | 4 +- pkg/config/v2/config.go | 268 ++++++++ pkg/config/v2/config_test.go | 348 ++++++++++ pkg/config/v3alpha/config.go | 349 ++++++++++ pkg/config/v3alpha/config_test.go | 572 +++++++++++++++++ pkg/config/version.go | 122 ++++ pkg/config/version_test.go | 168 +++++ pkg/internal/validation/project.go | 38 -- pkg/model/config/config.go | 345 ---------- pkg/model/config/config_test.go | 312 --------- pkg/model/resource/api.go | 64 ++ pkg/model/resource/api_test.go | 127 ++++ pkg/model/resource/gvk.go | 50 ++ pkg/model/resource/gvk_test.go | 58 ++ pkg/model/resource/options.go | 295 --------- pkg/model/resource/options_test.go | 215 ------- pkg/model/resource/resource.go | 134 +++- pkg/model/resource/resource_test.go | 601 ++++++++++++------ .../{resource_suite_test.go => suite_test.go} | 4 +- pkg/model/resource/utils.go | 51 ++ pkg/model/resource/utils_test.go | 61 ++ pkg/model/resource/webhooks.go | 76 +++ pkg/model/resource/webhooks_test.go | 233 +++++++ pkg/model/stage/stage.go | 109 ++++ pkg/model/stage/stage_test.go | 132 ++++ pkg/model/universe.go | 20 +- pkg/plugin/helpers.go | 10 +- pkg/plugin/interfaces.go | 8 +- pkg/plugin/version.go | 164 ++--- pkg/plugin/version_test.go | 410 +++++------- pkg/plugins/golang/options.go | 238 +++++++ pkg/plugins/golang/options_test.go | 300 +++++++++ pkg/plugins/golang/suite_test.go | 29 + pkg/plugins/golang/v2/api.go | 58 +- pkg/plugins/golang/v2/edit.go | 10 +- pkg/plugins/golang/v2/init.go | 70 +- pkg/plugins/golang/v2/options.go | 241 +++++++ pkg/plugins/golang/v2/options_test.go | 270 ++++++++ pkg/plugins/golang/v2/plugin.go | 8 +- pkg/plugins/golang/v2/scaffolds/api.go | 61 +- pkg/plugins/golang/v2/scaffolds/edit.go | 14 +- pkg/plugins/golang/v2/scaffolds/init.go | 14 +- .../scaffolds/internal/templates/api/group.go | 4 +- .../scaffolds/internal/templates/api/types.go | 2 +- .../internal/templates/api/webhook.go | 21 +- .../templates/config/crd/kustomization.go | 2 +- .../crd/patches/enablecainjection_patch.go | 2 +- .../config/crd/patches/enablewebhook_patch.go | 2 +- .../templates/config/rbac/crd_editor_role.go | 4 +- .../templates/config/rbac/crd_viewer_role.go | 4 +- .../templates/config/samples/crd_sample.go | 2 +- .../templates/controllers/controller.go | 13 +- .../controllers/controller_suitetest.go | 11 +- .../v2/scaffolds/internal/templates/main.go | 10 +- pkg/plugins/golang/v2/scaffolds/webhook.go | 41 +- pkg/plugins/golang/v2/suite_test.go | 29 + pkg/plugins/golang/v2/webhook.go | 44 +- pkg/plugins/golang/v3/api.go | 67 +- pkg/plugins/golang/v3/edit.go | 6 +- pkg/plugins/golang/v3/init.go | 55 +- pkg/plugins/golang/v3/plugin.go | 7 +- pkg/plugins/golang/v3/scaffolds/api.go | 57 +- pkg/plugins/golang/v3/scaffolds/edit.go | 14 +- pkg/plugins/golang/v3/scaffolds/init.go | 6 +- .../scaffolds/internal/templates/api/group.go | 4 +- .../scaffolds/internal/templates/api/types.go | 2 +- .../internal/templates/api/webhook.go | 27 +- .../templates/config/crd/kustomization.go | 2 +- .../templates/config/crd/kustomizeconfig.go | 18 +- .../crd/patches/enablecainjection_patch.go | 15 +- .../config/crd/patches/enablewebhook_patch.go | 15 +- .../config/kdefault/enablecainection_patch.go | 12 +- .../templates/config/rbac/crd_editor_role.go | 4 +- .../templates/config/rbac/crd_viewer_role.go | 4 +- .../templates/config/samples/crd_sample.go | 2 +- .../templates/config/webhook/kustomization.go | 10 +- .../templates/controllers/controller.go | 17 +- .../controllers/controller_suitetest.go | 15 +- .../v3/scaffolds/internal/templates/main.go | 10 +- pkg/plugins/golang/v3/scaffolds/webhook.go | 52 +- pkg/plugins/golang/v3/webhook.go | 61 +- pkg/plugins/internal/machinery/scaffold.go | 2 +- plugins/addon/controller.go | 6 +- plugins/addon/type.go | 34 +- 102 files changed, 5729 insertions(+), 2732 deletions(-) create mode 100644 pkg/config/errors.go create mode 100644 pkg/config/errors_test.go create mode 100644 pkg/config/interface.go create mode 100644 pkg/config/registry.go create mode 100644 pkg/config/resgistry_test.go rename pkg/{model/config/config_suite_test.go => config/suite_test.go} (90%) create mode 100644 pkg/config/v2/config.go create mode 100644 pkg/config/v2/config_test.go create mode 100644 pkg/config/v3alpha/config.go create mode 100644 pkg/config/v3alpha/config_test.go create mode 100644 pkg/config/version.go create mode 100644 pkg/config/version_test.go delete mode 100644 pkg/internal/validation/project.go delete mode 100644 pkg/model/config/config.go delete mode 100644 pkg/model/config/config_test.go create mode 100644 pkg/model/resource/api.go create mode 100644 pkg/model/resource/api_test.go create mode 100644 pkg/model/resource/gvk.go create mode 100644 pkg/model/resource/gvk_test.go delete mode 100644 pkg/model/resource/options.go delete mode 100644 pkg/model/resource/options_test.go rename pkg/model/resource/{resource_suite_test.go => suite_test.go} (95%) create mode 100644 pkg/model/resource/utils.go create mode 100644 pkg/model/resource/utils_test.go create mode 100644 pkg/model/resource/webhooks.go create mode 100644 pkg/model/resource/webhooks_test.go create mode 100644 pkg/model/stage/stage.go create mode 100644 pkg/model/stage/stage_test.go create mode 100644 pkg/plugins/golang/options.go create mode 100644 pkg/plugins/golang/options_test.go create mode 100644 pkg/plugins/golang/suite_test.go create mode 100644 pkg/plugins/golang/v2/options.go create mode 100644 pkg/plugins/golang/v2/options_test.go create mode 100644 pkg/plugins/golang/v2/suite_test.go diff --git a/cmd/main.go b/cmd/main.go index e95a455f76b..6f13054b4a4 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -20,7 +20,8 @@ import ( "log" "sigs.k8s.io/kubebuilder/v3/pkg/cli" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" + cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" pluginv2 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2" pluginv3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3" ) @@ -29,13 +30,13 @@ func main() { c, err := cli.New( cli.WithCommandName("kubebuilder"), cli.WithVersion(versionString()), - cli.WithDefaultProjectVersion(config.Version3Alpha), + cli.WithDefaultProjectVersion(cfgv3alpha.Version), cli.WithPlugins( &pluginv2.Plugin{}, &pluginv3.Plugin{}, ), - cli.WithDefaultPlugins(config.Version2, &pluginv2.Plugin{}), - cli.WithDefaultPlugins(config.Version3Alpha, &pluginv3.Plugin{}), + cli.WithDefaultPlugins(cfgv2.Version, &pluginv2.Plugin{}), + cli.WithDefaultPlugins(cfgv3alpha.Version, &pluginv3.Plugin{}), cli.WithCompletion, ) if err != nil { diff --git a/pkg/cli/api.go b/pkg/cli/api.go index d40680af112..c858784962f 100644 --- a/pkg/cli/api.go +++ b/pkg/cli/api.go @@ -83,7 +83,7 @@ func (c cli) bindCreateAPI(ctx plugin.Context, cmd *cobra.Command) { } subcommand := createAPIPlugin.GetCreateAPISubcommand() - subcommand.InjectConfig(&cfg.Config) + subcommand.InjectConfig(cfg.Config) subcommand.BindFlags(cmd.Flags()) subcommand.UpdateContext(&ctx) cmd.Long = ctx.Description diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index f4437f4baff..cc67383a4b9 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -25,8 +25,8 @@ import ( "github.com/spf13/pflag" internalconfig "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" - "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) @@ -74,9 +74,9 @@ type cli struct { //nolint:maligned // CLI version string. version string // Default project version in case none is provided and a config file can't be found. - defaultProjectVersion string + defaultProjectVersion config.Version // Default plugins in case none is provided and a config file can't be found. - defaultPlugins map[string][]string + defaultPlugins map[config.Version][]string // Plugins registered in the cli. plugins map[string]plugin.Plugin // Commands injected by options. @@ -87,7 +87,7 @@ type cli struct { //nolint:maligned /* Internal fields */ // Project version to scaffold. - projectVersion string + projectVersion config.Version // Plugin keys to scaffold with. pluginKeys []string @@ -131,8 +131,8 @@ func newCLI(opts ...Option) (*cli, error) { // Default cli options. c := &cli{ commandName: "kubebuilder", - defaultProjectVersion: internalconfig.DefaultVersion, - defaultPlugins: make(map[string][]string), + defaultProjectVersion: cfgv3alpha.Version, + defaultPlugins: make(map[config.Version][]string), plugins: make(map[string]plugin.Plugin), } @@ -191,15 +191,15 @@ func (c *cli) getInfoFromFlags() (string, []string, error) { } // getInfoFromConfigFile obtains the project version and plugin keys from the project config file. -func getInfoFromConfigFile() (string, []string, error) { +func getInfoFromConfigFile() (config.Version, []string, error) { // Read the project configuration file projectConfig, err := internalconfig.Read() switch { case err == nil: case os.IsNotExist(err): - return "", nil, nil + return config.Version{}, nil, nil default: - return "", nil, err + return config.Version{}, nil, err } return getInfoFromConfig(projectConfig) @@ -207,74 +207,82 @@ func getInfoFromConfigFile() (string, []string, error) { // getInfoFromConfig obtains the project version and plugin keys from the project config. // It is extracted from getInfoFromConfigFile for testing purposes. -func getInfoFromConfig(projectConfig *config.Config) (string, []string, error) { +func getInfoFromConfig(projectConfig config.Config) (config.Version, []string, error) { // Split the comma-separated plugins var pluginSet []string - if projectConfig.Layout != "" { - for _, p := range strings.Split(projectConfig.Layout, ",") { + if projectConfig.GetLayout() != "" { + for _, p := range strings.Split(projectConfig.GetLayout(), ",") { pluginSet = append(pluginSet, strings.TrimSpace(p)) } } - return projectConfig.Version, pluginSet, nil + return projectConfig.GetVersion(), pluginSet, nil } // resolveFlagsAndConfigFileConflicts checks if the provided combined input from flags and // the config file is valid and uses default values in case some info was not provided. func (c cli) resolveFlagsAndConfigFileConflicts( - flagProjectVersion, cfgProjectVersion string, + flagProjectVersionString string, + cfgProjectVersion config.Version, flagPlugins, cfgPlugins []string, -) (string, []string, error) { +) (config.Version, []string, error) { + // Parse project configuration version from flags + var flagProjectVersion config.Version + if flagProjectVersionString != "" { + if err := flagProjectVersion.Parse(flagProjectVersionString); err != nil { + return config.Version{}, nil, fmt.Errorf("unable to parse project version flag: %w", err) + } + } + // Resolve project version - var projectVersion string + var projectVersion config.Version + isFlagProjectVersionInvalid := flagProjectVersion.Validate() != nil + isCfgProjectVersionInvalid := cfgProjectVersion.Validate() != nil switch { - // If they are both blank, use the default - case flagProjectVersion == "" && cfgProjectVersion == "": + // If they are both invalid (empty is invalid), use the default + case isFlagProjectVersionInvalid && isCfgProjectVersionInvalid: projectVersion = c.defaultProjectVersion - // If they are equal doesn't matter which we choose - case flagProjectVersion == cfgProjectVersion: + // If any is invalid (empty is invalid), choose the other + case isCfgProjectVersionInvalid: projectVersion = flagProjectVersion - // If any is blank, choose the other - case cfgProjectVersion == "": - projectVersion = flagProjectVersion - case flagProjectVersion == "": + case isFlagProjectVersionInvalid: projectVersion = cfgProjectVersion - // If none is blank and they are different error out + // If they are equal doesn't matter which we choose + case flagProjectVersion.Compare(cfgProjectVersion) == 0: + projectVersion = flagProjectVersion + // If both are valid (empty is invalid) and they are different error out default: - return "", nil, fmt.Errorf("project version conflict between command line args (%s) "+ - "and project configuration file (%s)", flagProjectVersion, cfgProjectVersion) - } - // It still may be empty if default, flag and config project versions are empty - if projectVersion != "" { - // Validate the project version - if err := validation.ValidateProjectVersion(projectVersion); err != nil { - return "", nil, err - } + return config.Version{}, nil, fmt.Errorf("project version conflict between command line args (%s) "+ + "and project configuration file (%s)", flagProjectVersionString, cfgProjectVersion) } // Resolve plugins var plugins []string + isFlagPluginsEmpty := len(flagPlugins) == 0 + isCfgPluginsEmpty := len(cfgPlugins) == 0 switch { - // If they are both blank, use the default - case len(flagPlugins) == 0 && len(cfgPlugins) == 0: - plugins = c.defaultPlugins[projectVersion] + // If they are both empty, use the default + case isFlagPluginsEmpty && isCfgPluginsEmpty: + if defaults, hasDefaults := c.defaultPlugins[projectVersion]; hasDefaults { + plugins = defaults + } + // If any is empty, choose the other + case isCfgPluginsEmpty: + plugins = flagPlugins + case isFlagPluginsEmpty: + plugins = cfgPlugins // If they are equal doesn't matter which we choose case equalStringSlice(flagPlugins, cfgPlugins): plugins = flagPlugins - // If any is blank, choose the other - case len(cfgPlugins) == 0: - plugins = flagPlugins - case len(flagPlugins) == 0: - plugins = cfgPlugins - // If none is blank and they are different error out + // If none is empty and they are different error out default: - return "", nil, fmt.Errorf("plugins conflict between command line args (%v) "+ + return config.Version{}, nil, fmt.Errorf("plugins conflict between command line args (%v) "+ "and project configuration file (%v)", flagPlugins, cfgPlugins) } // Validate the plugins for _, p := range plugins { if err := plugin.ValidateKey(p); err != nil { - return "", nil, err + return config.Version{}, nil, err } } @@ -315,8 +323,8 @@ func (c *cli) resolve() error { // under no support contract. However users should be notified _why_ their plugin cannot be found. var extraErrMsg string if version != "" { - ver, err := plugin.ParseVersion(version) - if err != nil { + var ver plugin.Version + if err := ver.Parse(version); err != nil { return fmt.Errorf("error parsing input plugin version from key %q: %v", pluginKey, err) } if !ver.IsStable() { diff --git a/pkg/cli/cli_suite_test.go b/pkg/cli/cli_suite_test.go index 563a47fcb9b..72df7f64d9c 100644 --- a/pkg/cli/cli_suite_test.go +++ b/pkg/cli/cli_suite_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) @@ -39,27 +40,27 @@ var ( type mockPlugin struct { //nolint:maligned name string version plugin.Version - projectVersions []string + projectVersions []config.Version } -func newMockPlugin(name, version string, projVers ...string) plugin.Plugin { - v, err := plugin.ParseVersion(version) - if err != nil { +func newMockPlugin(name, version string, projVers ...config.Version) plugin.Plugin { + var v plugin.Version + if err := v.Parse(version); err != nil { panic(err) } return mockPlugin{name, v, projVers} } -func (p mockPlugin) Name() string { return p.name } -func (p mockPlugin) Version() plugin.Version { return p.version } -func (p mockPlugin) SupportedProjectVersions() []string { return p.projectVersions } +func (p mockPlugin) Name() string { return p.name } +func (p mockPlugin) Version() plugin.Version { return p.version } +func (p mockPlugin) SupportedProjectVersions() []config.Version { return p.projectVersions } type mockDeprecatedPlugin struct { //nolint:maligned mockPlugin deprecation string } -func newMockDeprecatedPlugin(name, version, deprecation string, projVers ...string) plugin.Plugin { +func newMockDeprecatedPlugin(name, version, deprecation string, projVers ...config.Version) plugin.Plugin { return mockDeprecatedPlugin{ mockPlugin: newMockPlugin(name, version, projVers...).(mockPlugin), deprecation: deprecation, diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index 6f01e97bdc5..61706d893cb 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -25,11 +25,13 @@ import ( . "github.com/onsi/gomega" "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" + cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) -func makeMockPluginsFor(projectVersion string, pluginKeys ...string) []plugin.Plugin { +func makeMockPluginsFor(projectVersion config.Version, pluginKeys ...string) []plugin.Plugin { plugins := make([]plugin.Plugin, 0, len(pluginKeys)) for _, key := range pluginKeys { n, v := plugin.SplitKey(key) @@ -92,7 +94,7 @@ var _ = Describe("CLI", func() { AfterEach(func() { os.Args = args }) When("no flag is set", func() { - It("should success", func() { + It("should succeed", func() { projectVersion, plugins, err = c.getInfoFromFlags() Expect(err).NotTo(HaveOccurred()) Expect(projectVersion).To(Equal("")) @@ -101,7 +103,7 @@ var _ = Describe("CLI", func() { }) When(fmt.Sprintf("--%s flag is set", projectVersionFlag), func() { - It("should success", func() { + It("should succeed", func() { setProjectVersionFlag("2") projectVersion, plugins, err = c.getInfoFromFlags() Expect(err).NotTo(HaveOccurred()) @@ -111,7 +113,7 @@ var _ = Describe("CLI", func() { }) When(fmt.Sprintf("--%s flag is set", pluginsFlag), func() { - It("should success using one plugin key", func() { + It("should succeed using one plugin key", func() { setPluginsFlag("go/v1") projectVersion, plugins, err = c.getInfoFromFlags() Expect(err).NotTo(HaveOccurred()) @@ -119,7 +121,7 @@ var _ = Describe("CLI", func() { Expect(plugins).To(Equal([]string{"go/v1"})) }) - It("should success using more than one plugin key", func() { + It("should succeed using more than one plugin key", func() { setPluginsFlag("go/v1,example/v2,test/v1") projectVersion, plugins, err = c.getInfoFromFlags() Expect(err).NotTo(HaveOccurred()) @@ -127,7 +129,7 @@ var _ = Describe("CLI", func() { Expect(plugins).To(Equal([]string{"go/v1", "example/v2", "test/v1"})) }) - It("should success using more than one plugin key with spaces", func() { + It("should succeed using more than one plugin key with spaces", func() { setPluginsFlag("go/v1 , example/v2 , test/v1") projectVersion, plugins, err = c.getInfoFromFlags() Expect(err).NotTo(HaveOccurred()) @@ -137,7 +139,7 @@ var _ = Describe("CLI", func() { }) When(fmt.Sprintf("--%s and --%s flags are set", projectVersionFlag, pluginsFlag), func() { - It("should success using one plugin key", func() { + It("should succeed using one plugin key", func() { setProjectVersionFlag("2") setPluginsFlag("go/v1") projectVersion, plugins, err = c.getInfoFromFlags() @@ -146,7 +148,7 @@ var _ = Describe("CLI", func() { Expect(plugins).To(Equal([]string{"go/v1"})) }) - It("should success using more than one plugin keys", func() { + It("should succeed using more than one plugin keys", func() { setProjectVersionFlag("2") setPluginsFlag("go/v1,example/v2,test/v1") projectVersion, plugins, err = c.getInfoFromFlags() @@ -155,7 +157,7 @@ var _ = Describe("CLI", func() { Expect(plugins).To(Equal([]string{"go/v1", "example/v2", "test/v1"})) }) - It("should success using more than one plugin keys with spaces", func() { + It("should succeed using more than one plugin keys with spaces", func() { setProjectVersionFlag("2") setPluginsFlag("go/v1 , example/v2 , test/v1") projectVersion, plugins, err = c.getInfoFromFlags() @@ -166,7 +168,7 @@ var _ = Describe("CLI", func() { }) When("additional flags are set", func() { - It("should not fail", func() { + It("should succeed", func() { setFlag("extra-flag", "extra-value") _, _, err = c.getInfoFromFlags() Expect(err).NotTo(HaveOccurred()) @@ -183,66 +185,36 @@ var _ = Describe("CLI", func() { Context("getInfoFromConfig", func() { var ( - projectConfig *config.Config - projectVersion string + projectConfig config.Config + projectVersion config.Version plugins []string err error ) - When("having version field", func() { - It("should success", func() { - projectConfig = &config.Config{ - Version: "2", - } + When("not having layout field", func() { + It("should succeed", func() { + projectConfig = cfgv2.New() projectVersion, plugins, err = getInfoFromConfig(projectConfig) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal(projectConfig.Version)) + Expect(projectVersion.Compare(projectConfig.GetVersion())).To(Equal(0)) Expect(len(plugins)).To(Equal(0)) }) }) When("having layout field", func() { - It("should success", func() { - projectConfig = &config.Config{ - Layout: "go.kubebuilder.io/v2", - } - projectVersion, plugins, err = getInfoFromConfig(projectConfig) - Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal("")) - Expect(plugins).To(Equal([]string{projectConfig.Layout})) - }) - }) - - When("having both version and layout fields", func() { - It("should success", func() { - projectConfig = &config.Config{ - Version: "3-alpha", - Layout: "go.kubebuilder.io/v2", - } - projectVersion, plugins, err = getInfoFromConfig(projectConfig) - Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal(projectConfig.Version)) - Expect(plugins).To(Equal([]string{projectConfig.Layout})) - }) - }) - - When("not having neither version nor layout fields set", func() { - It("should success", func() { - projectConfig = &config.Config{} + It("should succeed", func() { + projectConfig = cfgv3alpha.New() + Expect(projectConfig.SetLayout("go.kubebuilder.io/v2")).To(Succeed()) projectVersion, plugins, err = getInfoFromConfig(projectConfig) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal("")) - Expect(len(plugins)).To(Equal(0)) + Expect(projectVersion.Compare(projectConfig.GetVersion())).To(Equal(0)) + Expect(plugins).To(Equal([]string{projectConfig.GetLayout()})) }) }) }) Context("cli.resolveFlagsAndConfigFileConflicts", func() { const ( - projectVersion1 = "1" - projectVersion2 = "2" - projectVersion3 = "3" - pluginKey1 = "go.kubebuilder.io/v1" pluginKey2 = "go.kubebuilder.io/v2" pluginKey3 = "go.kubebuilder.io/v3" @@ -250,58 +222,62 @@ var _ = Describe("CLI", func() { var ( c *cli - projectVersion string + projectVersion config.Version plugins []string err error + + projectVersion1 = config.Version{Number: 1} + projectVersion2 = config.Version{Number: 2} + projectVersion3 = config.Version{Number: 3} ) When("having no project version set", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{} projectVersion, _, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, nil, nil, ) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal("")) + Expect(projectVersion.Compare(config.Version{})).To(Equal(0)) }) }) When("having one project version source", func() { When("having default project version set", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{ defaultProjectVersion: projectVersion1, } projectVersion, _, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, nil, nil, ) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal(projectVersion1)) + Expect(projectVersion.Compare(projectVersion1)).To(Equal(0)) }) }) When("having project version set from flags", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{} projectVersion, _, err = c.resolveFlagsAndConfigFileConflicts( - projectVersion1, - "", + projectVersion1.String(), + config.Version{}, nil, nil, ) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal(projectVersion1)) + Expect(projectVersion.Compare(projectVersion1)).To(Equal(0)) }) }) When("having project version set from config file", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{} projectVersion, _, err = c.resolveFlagsAndConfigFileConflicts( "", @@ -310,30 +286,30 @@ var _ = Describe("CLI", func() { nil, ) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal(projectVersion1)) + Expect(projectVersion.Compare(projectVersion1)).To(Equal(0)) }) }) }) When("having two project version source", func() { When("having default project version set and from flags", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{ defaultProjectVersion: projectVersion1, } projectVersion, _, err = c.resolveFlagsAndConfigFileConflicts( - projectVersion2, - "", + projectVersion2.String(), + config.Version{}, nil, nil, ) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal(projectVersion2)) + Expect(projectVersion.Compare(projectVersion2)).To(Equal(0)) }) }) When("having default project version set and from config file", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{ defaultProjectVersion: projectVersion1, } @@ -344,27 +320,27 @@ var _ = Describe("CLI", func() { nil, ) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal(projectVersion2)) + Expect(projectVersion.Compare(projectVersion2)).To(Equal(0)) }) }) When("having project version set from flags and config file", func() { - It("should success if they are the same", func() { + It("should succeed if they are the same", func() { c = &cli{} projectVersion, _, err = c.resolveFlagsAndConfigFileConflicts( - projectVersion1, + projectVersion1.String(), projectVersion1, nil, nil, ) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal(projectVersion1)) + Expect(projectVersion.Compare(projectVersion1)).To(Equal(0)) }) It("should fail if they are different", func() { c = &cli{} _, _, err = c.resolveFlagsAndConfigFileConflicts( - projectVersion1, + projectVersion1.String(), projectVersion2, nil, nil, @@ -375,18 +351,18 @@ var _ = Describe("CLI", func() { }) When("having three project version sources", func() { - It("should success if project version from flags and config file are the same", func() { + It("should succeed if project version from flags and config file are the same", func() { c = &cli{ defaultProjectVersion: projectVersion1, } projectVersion, _, err = c.resolveFlagsAndConfigFileConflicts( - projectVersion2, + projectVersion2.String(), projectVersion2, nil, nil, ) Expect(err).NotTo(HaveOccurred()) - Expect(projectVersion).To(Equal(projectVersion2)) + Expect(projectVersion.Compare(projectVersion2)).To(Equal(0)) }) It("should fail if project version from flags and config file are different", func() { @@ -394,7 +370,7 @@ var _ = Describe("CLI", func() { defaultProjectVersion: projectVersion1, } _, _, err = c.resolveFlagsAndConfigFileConflicts( - projectVersion2, + projectVersion2.String(), projectVersion3, nil, nil, @@ -405,12 +381,10 @@ var _ = Describe("CLI", func() { When("an invalid project version is set", func() { It("should fail", func() { - c = &cli{ - defaultProjectVersion: "v1", - } + c = &cli{} projectVersion, _, err = c.resolveFlagsAndConfigFileConflicts( - "", - "", + "0", + config.Version{}, nil, nil, ) @@ -419,11 +393,11 @@ var _ = Describe("CLI", func() { }) When("having no plugin keys set", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{} _, plugins, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, nil, nil, ) @@ -434,17 +408,17 @@ var _ = Describe("CLI", func() { When("having one plugin keys source", func() { When("having default plugin keys set", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{ defaultProjectVersion: projectVersion1, - defaultPlugins: map[string][]string{ + defaultPlugins: map[config.Version][]string{ projectVersion1: {pluginKey1}, projectVersion2: {pluginKey2}, }, } _, plugins, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, nil, nil, ) @@ -455,11 +429,11 @@ var _ = Describe("CLI", func() { }) When("having plugin keys set from flags", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{} _, plugins, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, []string{pluginKey1}, nil, ) @@ -470,11 +444,11 @@ var _ = Describe("CLI", func() { }) When("having plugin keys set from config file", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{} _, plugins, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, nil, []string{pluginKey1}, ) @@ -487,15 +461,15 @@ var _ = Describe("CLI", func() { When("having two plugin keys source", func() { When("having default plugin keys set and from flags", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{ - defaultPlugins: map[string][]string{ - "": {pluginKey1}, + defaultPlugins: map[config.Version][]string{ + {}: {pluginKey1}, }, } _, plugins, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, []string{pluginKey2}, nil, ) @@ -506,15 +480,15 @@ var _ = Describe("CLI", func() { }) When("having default plugin keys set and from config file", func() { - It("should success", func() { + It("should succeed", func() { c = &cli{ - defaultPlugins: map[string][]string{ - "": {pluginKey1}, + defaultPlugins: map[config.Version][]string{ + {}: {pluginKey1}, }, } _, plugins, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, nil, []string{pluginKey2}, ) @@ -525,11 +499,11 @@ var _ = Describe("CLI", func() { }) When("having plugin keys set from flags and config file", func() { - It("should success if they are the same", func() { + It("should succeed if they are the same", func() { c = &cli{} _, plugins, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, []string{pluginKey1}, []string{pluginKey1}, ) @@ -542,7 +516,7 @@ var _ = Describe("CLI", func() { c = &cli{} _, _, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, []string{pluginKey1}, []string{pluginKey2}, ) @@ -552,15 +526,15 @@ var _ = Describe("CLI", func() { }) When("having three plugin keys sources", func() { - It("should success if plugin keys from flags and config file are the same", func() { + It("should succeed if plugin keys from flags and config file are the same", func() { c = &cli{ - defaultPlugins: map[string][]string{ - "": {pluginKey1}, + defaultPlugins: map[config.Version][]string{ + {}: {pluginKey1}, }, } _, plugins, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, []string{pluginKey2}, []string{pluginKey2}, ) @@ -571,13 +545,13 @@ var _ = Describe("CLI", func() { It("should fail if plugin keys from flags and config file are different", func() { c = &cli{ - defaultPlugins: map[string][]string{ - "": {pluginKey1}, + defaultPlugins: map[config.Version][]string{ + {}: {pluginKey1}, }, } _, _, err = c.resolveFlagsAndConfigFileConflicts( "", - "", + config.Version{}, []string{pluginKey2}, []string{pluginKey3}, ) @@ -587,16 +561,11 @@ var _ = Describe("CLI", func() { When("an invalid plugin key is set", func() { It("should fail", func() { - c = &cli{ - defaultProjectVersion: projectVersion1, - defaultPlugins: map[string][]string{ - projectVersion1: {"invalid_plugin/v1"}, - }, - } + c = &cli{} _, plugins, err = c.resolveFlagsAndConfigFileConflicts( "", - "", - nil, + config.Version{}, + []string{"A"}, nil, ) Expect(err).To(HaveOccurred()) @@ -609,26 +578,27 @@ var _ = Describe("CLI", func() { // conflicts are solved appropriately. Context("cli.getInfo", func() { It("should set project version and plugin keys", func() { - projectVersion := "2" + projectVersion := config.Version{Number: 2} pluginKeys := []string{"go.kubebuilder.io/v2"} c := &cli{ defaultProjectVersion: projectVersion, - defaultPlugins: map[string][]string{ + defaultPlugins: map[config.Version][]string{ projectVersion: pluginKeys, }, } c.cmd = c.newRootCmd() Expect(c.getInfo()).To(Succeed()) - Expect(c.projectVersion).To(Equal(projectVersion)) + Expect(c.projectVersion.Compare(projectVersion)).To(Equal(0)) Expect(c.pluginKeys).To(Equal(pluginKeys)) }) }) Context("cli.resolve", func() { - const projectVersion = "2" var ( c *cli + projectVersion = config.Version{Number: 2} + pluginKeys = []string{ "foo.example.com/v1", "bar.example.com/v1", @@ -719,7 +689,7 @@ var _ = Describe("CLI", func() { When("providing an invalid option", func() { It("should return an error", func() { // An empty project version is not valid - _, err = New(WithDefaultProjectVersion("")) + _, err = New(WithDefaultProjectVersion(config.Version{})) Expect(err).To(HaveOccurred()) }) }) @@ -756,12 +726,14 @@ var _ = Describe("CLI", func() { }) When("providing deprecated plugins", func() { - It("should success and print the deprecation notice", func() { + It("should succeed and print the deprecation notice", func() { const ( - projectVersion = "2" deprecationWarning = "DEPRECATED" ) - var deprecatedPlugin = newMockDeprecatedPlugin("deprecated", "v1", deprecationWarning, projectVersion) + var ( + projectVersion = config.Version{Number: 2} + deprecatedPlugin = newMockDeprecatedPlugin("deprecated", "v1", deprecationWarning, projectVersion) + ) // Overwrite stdout to read the output and reset it afterwards r, w, _ := os.Pipe() diff --git a/pkg/cli/edit.go b/pkg/cli/edit.go index ae6df00d1c2..a5908a08364 100644 --- a/pkg/cli/edit.go +++ b/pkg/cli/edit.go @@ -83,7 +83,7 @@ func (c cli) bindEdit(ctx plugin.Context, cmd *cobra.Command) { } subcommand := editPlugin.GetEditSubcommand() - subcommand.InjectConfig(&cfg.Config) + subcommand.InjectConfig(cfg.Config) subcommand.BindFlags(cmd.Flags()) subcommand.UpdateContext(&ctx) cmd.Long = ctx.Description diff --git a/pkg/cli/init.go b/pkg/cli/init.go index 35e0f436469..92997491bc2 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -27,7 +27,8 @@ import ( "github.com/spf13/cobra" internalconfig "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) @@ -43,10 +44,10 @@ func (c cli) newInitCmd() *cobra.Command { // Register --project-version on the dynamically created command // so that it shows up in help and does not cause a parse error. - cmd.Flags().String(projectVersionFlag, c.defaultProjectVersion, + cmd.Flags().String(projectVersionFlag, c.defaultProjectVersion.String(), fmt.Sprintf("project version, possible values: (%s)", strings.Join(c.getAvailableProjectVersions(), ", "))) // The --plugins flag can only be called to init projects v2+. - if c.projectVersion != config.Version2 { + if c.projectVersion.Compare(cfgv2.Version) == 1 { cmd.Flags().StringSlice(pluginsFlag, nil, "Name and optionally version of the plugin to initialize the project with. "+ fmt.Sprintf("Available plugins: (%s)", strings.Join(c.getAvailablePlugins(), ", "))) @@ -82,7 +83,7 @@ func (c cli) getInitHelpExamples() string { } func (c cli) getAvailableProjectVersions() (projectVersions []string) { - versionSet := make(map[string]struct{}) + versionSet := make(map[config.Version]struct{}) for _, p := range c.plugins { // Only return versions of non-deprecated plugins. if _, isDeprecated := p.(plugin.Deprecated); !isDeprecated { @@ -92,7 +93,7 @@ func (c cli) getAvailableProjectVersions() (projectVersions []string) { } } for version := range versionSet { - projectVersions = append(projectVersions, strconv.Quote(version)) + projectVersions = append(projectVersions, strconv.Quote(version.String())) } sort.Strings(projectVersions) return projectVersions @@ -135,11 +136,14 @@ func (c cli) bindInit(ctx plugin.Context, cmd *cobra.Command) { return } - cfg := internalconfig.New(internalconfig.DefaultPath) - cfg.Version = c.projectVersion + cfg, err := internalconfig.New(c.projectVersion, internalconfig.DefaultPath) + if err != nil { + cmdErr(cmd, fmt.Errorf("unable to initialize the project configuration: %w", err)) + return + } subcommand := initPlugin.GetInitSubcommand() - subcommand.InjectConfig(&cfg.Config) + subcommand.InjectConfig(cfg.Config) subcommand.BindFlags(cmd.Flags()) subcommand.UpdateContext(&ctx) cmd.Long = ctx.Description diff --git a/pkg/cli/internal/config/config.go b/pkg/cli/internal/config/config.go index 5e556138167..0bcbd4ba014 100644 --- a/pkg/cli/internal/config/config.go +++ b/pkg/cli/internal/config/config.go @@ -22,16 +22,14 @@ import ( "os" "github.com/spf13/afero" + "sigs.k8s.io/yaml" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" ) const ( // DefaultPath is the default path for the configuration file DefaultPath = "PROJECT" - - // DefaultVersion is the version which will be used when the version flag is not provided - DefaultVersion = config.Version3Alpha ) func exists(fs afero.Fs, path string) (bool, error) { @@ -50,35 +48,46 @@ func exists(fs afero.Fs, path string) (bool, error) { return false, err } -func readFrom(fs afero.Fs, path string) (c config.Config, err error) { +type versionedConfig struct { + Version config.Version +} + +func readFrom(fs afero.Fs, path string) (config.Config, error) { // Read the file in, err := afero.ReadFile(fs, path) //nolint:gosec if err != nil { - return + return nil, err } - // Unmarshal the file content - if err = c.Unmarshal(in); err != nil { - return + // Check the file version + var versioned versionedConfig + if err := yaml.Unmarshal(in, &versioned); err != nil { + return nil, err + } + + // Create the config object + var c config.Config + c, err = config.New(versioned.Version) + if err != nil { + return nil, err } - // kubebuilder v1 omitted version and it is not supported, so return an error - if c.Version == "" { - return config.Config{}, fmt.Errorf("project version key `version` is empty or does not exist in %s", path) + // Unmarshal the file content + if err := c.Unmarshal(in); err != nil { + return nil, err } - return + return c, nil } // Read obtains the configuration from the default path but doesn't allow to persist changes -func Read() (*config.Config, error) { +func Read() (config.Config, error) { return ReadFrom(DefaultPath) } // ReadFrom obtains the configuration from the provided path but doesn't allow to persist changes -func ReadFrom(path string) (*config.Config, error) { - c, err := readFrom(afero.NewOsFs(), path) - return &c, err +func ReadFrom(path string) (config.Config, error) { + return readFrom(afero.NewOsFs(), path) } // Config extends model/config.Config allowing to persist changes @@ -96,15 +105,18 @@ type Config struct { } // New creates a new configuration that will be stored at the provided path -func New(path string) *Config { +func New(version config.Version, path string) (*Config, error) { + cfg, err := config.New(version) + if err != nil { + return nil, err + } + return &Config{ - Config: config.Config{ - Version: DefaultVersion, - }, + Config: cfg, path: path, mustNotExist: true, fs: afero.NewOsFs(), - } + }, nil } // Load obtains the configuration from the default path allowing to persist changes (Save method) diff --git a/pkg/cli/internal/config/config_test.go b/pkg/cli/internal/config/config_test.go index cdfa9caa51d..fe7d7ee4932 100644 --- a/pkg/cli/internal/config/config_test.go +++ b/pkg/cli/internal/config/config_test.go @@ -23,179 +23,49 @@ import ( . "github.com/onsi/gomega" "github.com/spf13/afero" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" ) var _ = Describe("Config", func() { - var ( - cfg Config - expectedConfigStr string - ) - - Context("with valid keys", func() { - It("should save correctly", func() { - - By("saving empty config") - Expect(cfg.Save()).NotTo(Succeed()) - - By("saving empty config with path") - cfg = Config{ - fs: afero.NewMemMapFs(), - path: DefaultPath, + Context("Save", func() { + It("should success for valid configs", func() { + cfg := Config{ + Config: cfgv2.New(), + fs: afero.NewMemMapFs(), + path: DefaultPath, } Expect(cfg.Save()).To(Succeed()) - cfgBytes, err := afero.ReadFile(cfg.fs, DefaultPath) - Expect(err).NotTo(HaveOccurred()) - Expect(string(cfgBytes)).To(Equal(expectedConfigStr)) - By("saving config version 2") - cfg = Config{ - Config: config.Config{ - Version: config.Version2, - Repo: "github.com/example/project", - Domain: "example.com", - }, - fs: afero.NewMemMapFs(), - path: DefaultPath, - } - expectedConfigStr = `domain: example.com -repo: github.com/example/project -version: "2" -` - Expect(cfg.Save()).To(Succeed()) - cfgBytes, err = afero.ReadFile(cfg.fs, DefaultPath) + cfgBytes, err := afero.ReadFile(cfg.fs, DefaultPath) Expect(err).NotTo(HaveOccurred()) - Expect(string(cfgBytes)).To(Equal(expectedConfigStr)) + Expect(string(cfgBytes)).To(Equal(`version: "2" +`)) + }) - By("saving config version 3-alpha with plugin config") - cfg = Config{ - Config: config.Config{ - Version: config.Version3Alpha, - Repo: "github.com/example/project", - Domain: "example.com", - Plugins: config.PluginConfigs{ - "plugin-x": map[string]interface{}{ - "data-1": "single plugin datum", - }, - "plugin-y/v1": map[string]interface{}{ - "data-1": "plugin value 1", - "data-2": "plugin value 2", - "data-3": []string{"plugin value 3", "plugin value 4"}, - }, - }, - }, - fs: afero.NewMemMapFs(), - path: DefaultPath, + It("should fail if path is not provided", func() { + cfg := Config{ + Config: cfgv2.New(), + fs: afero.NewMemMapFs(), } - expectedConfigStr = `domain: example.com -repo: github.com/example/project -version: 3-alpha -plugins: - plugin-x: - data-1: single plugin datum - plugin-y/v1: - data-1: plugin value 1 - data-2: plugin value 2 - data-3: - - plugin value 3 - - plugin value 4 -` - Expect(cfg.Save()).To(Succeed()) - cfgBytes, err = afero.ReadFile(cfg.fs, DefaultPath) - Expect(err).NotTo(HaveOccurred()) - Expect(string(cfgBytes)).To(Equal(expectedConfigStr)) + Expect(cfg.Save()).NotTo(Succeed()) }) + }) - It("should load correctly", func() { - var ( - fs afero.Fs - configStr string - expectedConfig config.Config - ) - - By("loading config version 2") - fs = afero.NewMemMapFs() - configStr = `domain: example.com + Context("readFrom", func() { + It("should success for valid configs", func() { + configStr := `domain: example.com repo: github.com/example/project version: "2"` - expectedConfig = config.Config{ - Version: config.Version2, - Repo: "github.com/example/project", - Domain: "example.com", - } - Expect(afero.WriteFile(fs, DefaultPath, []byte(configStr), os.ModePerm)).To(Succeed()) - cfg, err := readFrom(fs, DefaultPath) - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).To(Equal(expectedConfig)) + expectedConfig := cfgv2.New() + _ = expectedConfig.SetDomain("example.com") + _ = expectedConfig.SetRepository("github.com/example/project") - By("loading config version 3-alpha with plugin config") - fs = afero.NewMemMapFs() - configStr = `domain: example.com -repo: github.com/example/project -version: 3-alpha -plugins: - plugin-x: - data-1: single plugin datum - plugin-y/v1: - data-1: plugin value 1 - data-2: plugin value 2 - data-3: - - "plugin value 3" - - "plugin value 4"` - expectedConfig = config.Config{ - Version: config.Version3Alpha, - Repo: "github.com/example/project", - Domain: "example.com", - Plugins: config.PluginConfigs{ - "plugin-x": map[string]interface{}{ - "data-1": "single plugin datum", - }, - "plugin-y/v1": map[string]interface{}{ - "data-1": "plugin value 1", - "data-2": "plugin value 2", - "data-3": []interface{}{"plugin value 3", "plugin value 4"}, - }, - }, - } + fs := afero.NewMemMapFs() Expect(afero.WriteFile(fs, DefaultPath, []byte(configStr), os.ModePerm)).To(Succeed()) - cfg, err = readFrom(fs, DefaultPath) + + cfg, err := readFrom(fs, DefaultPath) Expect(err).NotTo(HaveOccurred()) Expect(cfg).To(Equal(expectedConfig)) }) }) - - Context("with invalid keys", func() { - It("should return a save error", func() { - By("saving config version 2 with plugin config") - cfg = Config{ - Config: config.Config{ - Version: config.Version2, - Repo: "github.com/example/project", - Domain: "example.com", - Plugins: config.PluginConfigs{ - "plugin-x": map[string]interface{}{ - "data-1": "single plugin datum", - }, - }, - }, - fs: afero.NewMemMapFs(), - path: DefaultPath, - } - Expect(cfg.Save()).NotTo(Succeed()) - }) - - It("should return a load error", func() { - By("loading config version 2 with plugin config") - fs := afero.NewMemMapFs() - configStr := `domain: example.com -repo: github.com/example/project -version: "2" -plugins: - plugin-x: - data-1: single plugin datum` - Expect(afero.WriteFile(fs, DefaultPath, []byte(configStr), os.ModePerm)).To(Succeed()) - _, err := readFrom(fs, DefaultPath) - Expect(err).To(HaveOccurred()) - }) - }) }) diff --git a/pkg/cli/options.go b/pkg/cli/options.go index 6187579f100..df89dd5c649 100644 --- a/pkg/cli/options.go +++ b/pkg/cli/options.go @@ -21,7 +21,7 @@ import ( "github.com/spf13/cobra" - "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) @@ -46,9 +46,9 @@ func WithVersion(version string) Option { // WithDefaultProjectVersion is an Option that sets the cli's default project version. // Setting an unknown version will result in an error. -func WithDefaultProjectVersion(version string) Option { +func WithDefaultProjectVersion(version config.Version) Option { return func(c *cli) error { - if err := validation.ValidateProjectVersion(version); err != nil { + if err := version.Validate(); err != nil { return fmt.Errorf("broken pre-set default project version %q: %v", version, err) } c.defaultProjectVersion = version @@ -57,9 +57,9 @@ func WithDefaultProjectVersion(version string) Option { } // WithDefaultPlugins is an Option that sets the cli's default plugins. -func WithDefaultPlugins(projectVersion string, plugins ...plugin.Plugin) Option { +func WithDefaultPlugins(projectVersion config.Version, plugins ...plugin.Plugin) Option { return func(c *cli) error { - if err := validation.ValidateProjectVersion(projectVersion); err != nil { + if err := projectVersion.Validate(); err != nil { return fmt.Errorf("broken pre-set project version %q for default plugins: %v", projectVersion, err) } if len(plugins) == 0 { diff --git a/pkg/cli/options_test.go b/pkg/cli/options_test.go index d24f8ab8118..88ebe1b4b2c 100644 --- a/pkg/cli/options_test.go +++ b/pkg/cli/options_test.go @@ -23,26 +23,29 @@ import ( . "github.com/onsi/gomega" "github.com/spf13/cobra" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) var _ = Describe("CLI options", func() { const ( - pluginName = "plugin" - pluginVersion = "v1" - projectVersion = "1" + pluginName = "plugin" + pluginVersion = "v1" ) var ( c *cli err error + projectVersion = config.Version{Number: 1} + p = newMockPlugin(pluginName, pluginVersion, projectVersion) np1 = newMockPlugin("Plugin", pluginVersion, projectVersion) - np2 = mockPlugin{pluginName, plugin.Version{Number: -1, Stage: plugin.StableStage}, []string{projectVersion}} + np2 = mockPlugin{pluginName, plugin.Version{Number: -1}, []config.Version{projectVersion}} np3 = newMockPlugin(pluginName, pluginVersion) - np4 = newMockPlugin(pluginName, pluginVersion, "a") + np4 = newMockPlugin(pluginName, pluginVersion, config.Version{}) ) Context("WithCommandName", func() { @@ -67,10 +70,10 @@ var _ = Describe("CLI options", func() { Context("WithDefaultProjectVersion", func() { It("should return a valid CLI", func() { - defaultProjectVersions := []string{ - "1", - "2", - "3-alpha", + defaultProjectVersions := []config.Version{ + {Number: 1}, + {Number: 2}, + {Number: 3, Stage: stage.Alpha}, } for _, defaultProjectVersion := range defaultProjectVersions { By(fmt.Sprintf("using %q", defaultProjectVersion)) @@ -82,12 +85,9 @@ var _ = Describe("CLI options", func() { }) It("should return an error", func() { - defaultProjectVersions := []string{ - "", // Empty default project version - "v1", // 'v' prefix for project version - "1alpha", // non-delimited non-stable suffix - "1.alpha", // non-stable version delimited by '.' - "1-alpha1", // number-suffixed non-stable version + defaultProjectVersions := []config.Version{ + {}, // Empty default project version + {Number: 1, Stage: stage.Stage(27)}, // Invalid stage in default project version } for _, defaultProjectVersion := range defaultProjectVersions { By(fmt.Sprintf("using %q", defaultProjectVersion)) @@ -102,12 +102,12 @@ var _ = Describe("CLI options", func() { c, err = newCLI(WithDefaultPlugins(projectVersion, p)) Expect(err).NotTo(HaveOccurred()) Expect(c).NotTo(BeNil()) - Expect(c.defaultPlugins).To(Equal(map[string][]string{projectVersion: {plugin.KeyFor(p)}})) + Expect(c.defaultPlugins).To(Equal(map[config.Version][]string{projectVersion: {plugin.KeyFor(p)}})) }) When("providing an invalid project version", func() { It("should return an error", func() { - _, err = newCLI(WithDefaultPlugins("", p)) + _, err = newCLI(WithDefaultPlugins(config.Version{}, p)) Expect(err).To(HaveOccurred()) }) }) @@ -149,7 +149,7 @@ var _ = Describe("CLI options", func() { When("providing a default plugin for an unsupported project version", func() { It("should return an error", func() { - _, err = newCLI(WithDefaultPlugins("2", p)) + _, err = newCLI(WithDefaultPlugins(config.Version{Number: 2}, p)) Expect(err).To(HaveOccurred()) }) }) diff --git a/pkg/cli/root.go b/pkg/cli/root.go index 6a30cc43646..1203700a2aa 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -67,12 +67,10 @@ configuration please run: `, c.commandName, c.getPluginTable()) - if c.defaultProjectVersion != "" { - str += fmt.Sprintf("\nDefault project version: %s\n", c.defaultProjectVersion) + str += fmt.Sprintf("\nDefault project version: %s\n", c.defaultProjectVersion) - if defaultPlugins, hasDefaultPlugins := c.defaultPlugins[c.defaultProjectVersion]; hasDefaultPlugins { - str += fmt.Sprintf("Default plugin keys: %q\n", strings.Join(defaultPlugins, ",")) - } + if defaultPlugins, hasDefaultPlugins := c.defaultPlugins[c.defaultProjectVersion]; hasDefaultPlugins { + str += fmt.Sprintf("Default plugin keys: %q\n", strings.Join(defaultPlugins, ",")) } str += fmt.Sprintf(` @@ -98,11 +96,16 @@ func (c cli) getPluginTable() string { maxPluginKeyLength = len(pluginKey) } pluginKeys = append(pluginKeys, pluginKey) - supportedProjectVersions := strings.Join(plugin.SupportedProjectVersions(), ", ") - if len(supportedProjectVersions) > maxProjectVersionLength { - maxProjectVersionLength = len(supportedProjectVersions) + supportedProjectVersions := plugin.SupportedProjectVersions() + supportedProjectVersionStrs := make([]string, 0, len(supportedProjectVersions)) + for _, version := range supportedProjectVersions { + supportedProjectVersionStrs = append(supportedProjectVersionStrs, version.String()) + } + supportedProjectVersionsStr := strings.Join(supportedProjectVersionStrs, ", ") + if len(supportedProjectVersionsStr) > maxProjectVersionLength { + maxProjectVersionLength = len(supportedProjectVersionsStr) } - projectVersions = append(projectVersions, supportedProjectVersions) + projectVersions = append(projectVersions, supportedProjectVersionsStr) } lines := make([]string, 0, len(c.plugins)+2) diff --git a/pkg/cli/webhook.go b/pkg/cli/webhook.go index 9a61a6c4579..fb2e4511a66 100644 --- a/pkg/cli/webhook.go +++ b/pkg/cli/webhook.go @@ -83,7 +83,7 @@ func (c cli) bindCreateWebhook(ctx plugin.Context, cmd *cobra.Command) { } subcommand := createWebhookPlugin.GetCreateWebhookSubcommand() - subcommand.InjectConfig(&cfg.Config) + subcommand.InjectConfig(cfg.Config) subcommand.BindFlags(cmd.Flags()) subcommand.UpdateContext(&ctx) cmd.Long = ctx.Description diff --git a/pkg/config/errors.go b/pkg/config/errors.go new file mode 100644 index 00000000000..fed0b421882 --- /dev/null +++ b/pkg/config/errors.go @@ -0,0 +1,85 @@ +/* +Copyright 2021 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 config + +import ( + "fmt" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +// UnsupportedVersionError is returned by New when a project configuration version is not supported. +type UnsupportedVersionError struct { + Version Version +} + +// Error implements error interface +func (e UnsupportedVersionError) Error() string { + return fmt.Sprintf("version %s is not supported", e.Version) +} + +// UnsupportedField is returned when a project configuration version does not support +// one of the fields as interface must be common for all the versions +type UnsupportedField struct { + Version Version + Field string +} + +// Error implements error interface +func (e UnsupportedField) Error() string { + return fmt.Sprintf("version %s does not support the %s field", e.Version, e.Field) +} + +// UnknownResource is returned by Config.GetResource when the provided GVK cannot be found +type UnknownResource struct { + GVK resource.GVK +} + +// Error implements error interface +func (e UnknownResource) Error() string { + return fmt.Sprintf("resource %v could not be found", e.GVK) +} + +// MarshalError is returned by Config.Marshal when something went wrong while marshalling to YAML +type MarshalError struct { + Err error +} + +// Error implements error interface +func (e MarshalError) Error() string { + return fmt.Sprintf("error marshalling project configuration: %v", e.Err) +} + +// Unwrap implements Wrapper interface +func (e MarshalError) Unwrap() error { + return e.Err +} + +// UnmarshalError is returned by Config.Unmarshal when something went wrong while unmarshalling from YAML +type UnmarshalError struct { + Err error +} + +// Error implements error interface +func (e UnmarshalError) Error() string { + return fmt.Sprintf("error unmarshalling project configuration: %v", e.Err) +} + +// Unwrap implements Wrapper interface +func (e UnmarshalError) Unwrap() error { + return e.Err +} diff --git a/pkg/config/errors_test.go b/pkg/config/errors_test.go new file mode 100644 index 00000000000..b998ea949e4 --- /dev/null +++ b/pkg/config/errors_test.go @@ -0,0 +1,106 @@ +/* +Copyright 2021 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 config + +import ( + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +var _ = Describe("UnsupportedVersionError", func() { + var err = UnsupportedVersionError{ + Version: Version{Number: 1}, + } + + Context("Error", func() { + It("should return the correct error message", func() { + Expect(err.Error()).To(Equal("version 1 is not supported")) + }) + }) +}) + +var _ = Describe("UnsupportedField", func() { + var err = UnsupportedField{ + Version: Version{Number: 1}, + Field: "name", + } + + Context("Error", func() { + It("should return the correct error message", func() { + Expect(err.Error()).To(Equal("version 1 does not support the name field")) + }) + }) +}) + +var _ = Describe("UnknownResource", func() { + var err = UnknownResource{ + GVK: resource.GVK{ + Group: "group", + Domain: "my.domain", + Version: "v1", + Kind: "Kind", + }, + } + + Context("Error", func() { + It("should return the correct error message", func() { + Expect(err.Error()).To(Equal("resource {group my.domain v1 Kind} could not be found")) + }) + }) +}) + +var _ = Describe("MarshalError", func() { + var ( + wrapped = fmt.Errorf("error message") + err = MarshalError{Err: wrapped} + ) + + Context("Error", func() { + It("should return the correct error message", func() { + Expect(err.Error()).To(Equal(fmt.Sprintf("error marshalling project configuration: %v", wrapped))) + }) + }) + + Context("Unwrap", func() { + It("should unwrap to the wrapped error", func() { + Expect(err.Unwrap()).To(Equal(wrapped)) + }) + }) +}) + +var _ = Describe("UnmarshalError", func() { + var ( + wrapped = fmt.Errorf("error message") + err = UnmarshalError{Err: wrapped} + ) + + Context("Error", func() { + It("should return the correct error message", func() { + Expect(err.Error()).To(Equal(fmt.Sprintf("error unmarshalling project configuration: %v", wrapped))) + }) + }) + + Context("Unwrap", func() { + It("should unwrap to the wrapped error", func() { + Expect(err.Unwrap()).To(Equal(wrapped)) + }) + }) +}) diff --git a/pkg/config/interface.go b/pkg/config/interface.go new file mode 100644 index 00000000000..aae0ece0e5f --- /dev/null +++ b/pkg/config/interface.go @@ -0,0 +1,114 @@ +/* +Copyright 2021 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 config + +import ( + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +// Config defines the interface that project configuration types must follow +type Config interface { + /* Version */ + + // GetVersion returns the current project version + GetVersion() Version + + /* String fields */ + + // GetDomain returns the project domain + GetDomain() string + // SetDomain sets the project domain + SetDomain(domain string) error + + // GetRepository returns the project repository. + GetRepository() string + // SetRepository sets the project repository + SetRepository(repository string) error + + // GetProjectName returns the project name + // This method was introduced in project version 3-alpha. + GetProjectName() string + // SetProjectName sets the project name + // This method was introduced in project version 3-alpha. + SetProjectName(name string) error + + // GetLayout returns the config layout + // This method was introduced in project version 3-alpha. + GetLayout() string + // SetLayout sets the Config layout + // This method was introduced in project version 3-alpha. + SetLayout(layout string) error + + /* Boolean fields */ + + // IsMultiGroup checks if multi-group is enabled + IsMultiGroup() bool + // SetMultiGroup enables multi-group + SetMultiGroup() error + // ClearMultiGroup disables multi-group + ClearMultiGroup() error + + // IsComponentConfig checks if component config is enabled + // This method was introduced in project version 3-alpha. + IsComponentConfig() bool + // SetComponentConfig enables component config + // This method was introduced in project version 3-alpha. + SetComponentConfig() error + // ClearComponentConfig disables component config + // This method was introduced in project version 3-alpha. + ClearComponentConfig() error + + /* Resources */ + + // ResourcesLength returns the number of tracked resources + ResourcesLength() int + // HasResource checks if the provided GVK is stored in the Config + HasResource(gvk resource.GVK) bool + // GetResource returns the stored resource matching the provided GVK + GetResource(gvk resource.GVK) (resource.Resource, error) + // GetResources returns all the stored resources + GetResources() ([]resource.Resource, error) + // AddResource adds the provided resource if it was not present, no-op if it was already present + AddResource(res resource.Resource) error + // UpdateResource adds the provided resource if it was not present, modifies it if it was already present + UpdateResource(res resource.Resource) error + + // HasGroup checks if the provided group is the same as any of the tracked resources + HasGroup(group string) bool + // IsCRDVersionCompatible returns true if crdVersion can be added to the existing set of CRD versions. + IsCRDVersionCompatible(crdVersion string) bool + // IsWebhookVersionCompatible returns true if webhookVersion can be added to the existing set of Webhook versions. + IsWebhookVersionCompatible(webhookVersion string) bool + + /* Plugins */ + + // DecodePluginConfig decodes a plugin config stored in Config into configObj, which must be a pointer. + // This method is intended to be used for custom configuration objects, which were introduced in project version + // 3-alpha. + DecodePluginConfig(key string, configObj interface{}) error + // EncodePluginConfig encodes a config object into Config by overwriting the existing object stored under key. + // This method is intended to be used for custom configuration objects, which were introduced in project version + // 3-alpha. + EncodePluginConfig(key string, configObj interface{}) error + + /* Persistence */ + + // Marshal returns the YAML representation of the Config + Marshal() ([]byte, error) + // Unmarshal loads the Config fields from its YAML representation + Unmarshal([]byte) error +} diff --git a/pkg/config/registry.go b/pkg/config/registry.go new file mode 100644 index 00000000000..b0c03f754fe --- /dev/null +++ b/pkg/config/registry.go @@ -0,0 +1,37 @@ +/* +Copyright 2021 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 config + +type constructorFunc func() Config + +var ( + registry = make(map[Version]constructorFunc) +) + +// Register allows implementations of Config to register themselves so that they can be created with New +func Register(version Version, constructor constructorFunc) { + registry[version] = constructor +} + +// New creates Config instances from the previously registered implementations through Register +func New(version Version) (Config, error) { + if constructor, exists := registry[version]; exists { + return constructor(), nil + } + + return nil, UnsupportedVersionError{Version: version} +} diff --git a/pkg/config/resgistry_test.go b/pkg/config/resgistry_test.go new file mode 100644 index 00000000000..c10fd839526 --- /dev/null +++ b/pkg/config/resgistry_test.go @@ -0,0 +1,56 @@ +/* +Copyright 2021 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 config + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("registry", func() { + var ( + version = Version{} + f = func() Config { return nil } + ) + + AfterEach(func() { + registry = make(map[Version]constructorFunc) + }) + + Context("Register", func() { + It("should register new constructors", func() { + Register(version, f) + Expect(registry).To(HaveKey(version)) + Expect(registry[version]()).To(BeNil()) + }) + }) + + Context("New", func() { + It("should use the registered constructors", func() { + registry[version] = f + result, err := New(version) + Expect(err).NotTo(HaveOccurred()) + Expect(result).To(BeNil()) + }) + + It("should fail for unregistered constructors", func() { + _, err := New(version) + Expect(err).To(HaveOccurred()) + }) + }) + +}) diff --git a/pkg/model/config/config_suite_test.go b/pkg/config/suite_test.go similarity index 90% rename from pkg/model/config/config_suite_test.go rename to pkg/config/suite_test.go index fffe2f8bb2f..c2a73ef1566 100644 --- a/pkg/model/config/config_suite_test.go +++ b/pkg/config/suite_test.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The Kubernetes Authors. +Copyright 2021 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. @@ -23,7 +23,7 @@ import ( . "github.com/onsi/gomega" ) -func TestCLI(t *testing.T) { +func TestConfig(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Config Suite") } diff --git a/pkg/config/v2/config.go b/pkg/config/v2/config.go new file mode 100644 index 00000000000..9b3812fe8cb --- /dev/null +++ b/pkg/config/v2/config.go @@ -0,0 +1,268 @@ +/* +Copyright 2021 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 v2 + +import ( + "strings" + + "sigs.k8s.io/yaml" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +// Version is the config.Version for project configuration 2 +var Version = config.Version{Number: 2} + +const apiVersion = "v1beta1" + +type cfg struct { + // Version + Version config.Version `json:"version"` + + // String fields + Domain string `json:"domain,omitempty"` + Repository string `json:"repo,omitempty"` + + // Boolean fields + MultiGroup bool `json:"multigroup,omitempty"` + + // Resources + Gvks []resource.GVK `json:"resources,omitempty"` +} + +// New returns a new config.Config +func New() config.Config { + return &cfg{Version: Version} +} + +func init() { + config.Register(Version, New) +} + +// GetVersion implements config.Config +func (c cfg) GetVersion() config.Version { + return c.Version +} + +// GetDomain implements config.Config +func (c cfg) GetDomain() string { + return c.Domain +} + +// SetDomain implements config.Config +func (c *cfg) SetDomain(domain string) error { + c.Domain = domain + return nil +} + +// GetRepository implements config.Config +func (c cfg) GetRepository() string { + return c.Repository +} + +// SetRepository implements config.Config +func (c *cfg) SetRepository(repository string) error { + c.Repository = repository + return nil +} + +// GetProjectName implements config.Config +func (c cfg) GetProjectName() string { + return "" +} + +// SetProjectName implements config.Config +func (c *cfg) SetProjectName(string) error { + return config.UnsupportedField{ + Version: Version, + Field: "project name", + } +} + +// GetLayout implements config.Config +func (c cfg) GetLayout() string { + return "" +} + +// SetLayout implements config.Config +func (c *cfg) SetLayout(string) error { + return config.UnsupportedField{ + Version: Version, + Field: "layout", + } +} + +// IsMultiGroup implements config.Config +func (c cfg) IsMultiGroup() bool { + return c.MultiGroup +} + +// SetMultiGroup implements config.Config +func (c *cfg) SetMultiGroup() error { + c.MultiGroup = true + return nil +} + +// ClearMultiGroup implements config.Config +func (c *cfg) ClearMultiGroup() error { + c.MultiGroup = false + return nil +} + +// IsComponentConfig implements config.Config +func (c cfg) IsComponentConfig() bool { + return false +} + +// SetComponentConfig implements config.Config +func (c *cfg) SetComponentConfig() error { + return config.UnsupportedField{ + Version: Version, + Field: "component config", + } +} + +// ClearComponentConfig implements config.Config +func (c *cfg) ClearComponentConfig() error { + return config.UnsupportedField{ + Version: Version, + Field: "component config", + } +} + +// ResourcesLength implements config.Config +func (c cfg) ResourcesLength() int { + return len(c.Gvks) +} + +// HasResource implements config.Config +func (c cfg) HasResource(gvk resource.GVK) bool { + gvk.Domain = "" // Version 2 does not include domain per resource + + for _, trackedGVK := range c.Gvks { + if gvk.IsEqualTo(trackedGVK) { + return true + } + } + + return false +} + +// GetResource implements config.Config +func (c cfg) GetResource(gvk resource.GVK) (resource.Resource, error) { + gvk.Domain = "" // Version 2 does not include domain per resource + + for _, trackedGVK := range c.Gvks { + if gvk.IsEqualTo(trackedGVK) { + return resource.Resource{ + GVK: trackedGVK, + }, nil + } + } + + return resource.Resource{}, config.UnknownResource{GVK: gvk} +} + +// GetResources implements config.Config +func (c cfg) GetResources() ([]resource.Resource, error) { + resources := make([]resource.Resource, 0, len(c.Gvks)) + for _, gvk := range c.Gvks { + resources = append(resources, resource.Resource{ + GVK: gvk, + }) + } + + return resources, nil +} + +// AddResource implements config.Config +func (c *cfg) AddResource(res resource.Resource) error { + // As res is passed by value it is already a shallow copy, and we are only using + // fields that do not require a deep copy, so no need to make a deep copy + + res.Domain = "" // Version 2 does not include domain per resource + + if !c.HasResource(res.GVK) { + c.Gvks = append(c.Gvks, res.GVK) + } + + return nil +} + +// UpdateResource implements config.Config +func (c *cfg) UpdateResource(res resource.Resource) error { + return c.AddResource(res) +} + +// HasGroup implements config.Config +func (c cfg) HasGroup(group string) bool { + // Return true if the target group is found in the tracked resources + for _, r := range c.Gvks { + if strings.EqualFold(group, r.Group) { + return true + } + } + + // Return false otherwise + return false +} + +// IsCRDVersionCompatible implements config.Config +func (c cfg) IsCRDVersionCompatible(crdVersion string) bool { + return crdVersion == apiVersion +} + +// IsWebhookVersionCompatible implements config.Config +func (c cfg) IsWebhookVersionCompatible(webhookVersion string) bool { + return webhookVersion == apiVersion +} + +// DecodePluginConfig implements config.Config +func (c cfg) DecodePluginConfig(string, interface{}) error { + return config.UnsupportedField{ + Version: Version, + Field: "plugins", + } +} + +// EncodePluginConfig implements config.Config +func (c cfg) EncodePluginConfig(string, interface{}) error { + return config.UnsupportedField{ + Version: Version, + Field: "plugins", + } +} + +// Marshal implements config.Config +func (c cfg) Marshal() ([]byte, error) { + content, err := yaml.Marshal(c) + if err != nil { + return nil, config.MarshalError{Err: err} + } + + return content, nil +} + +// Unmarshal implements config.Config +func (c *cfg) Unmarshal(b []byte) error { + if err := yaml.UnmarshalStrict(b, c); err != nil { + return config.UnmarshalError{Err: err} + } + + return nil +} diff --git a/pkg/config/v2/config_test.go b/pkg/config/v2/config_test.go new file mode 100644 index 00000000000..0db44a80334 --- /dev/null +++ b/pkg/config/v2/config_test.go @@ -0,0 +1,348 @@ +/* +Copyright 2021 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 v2 + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +func TestConfigV2(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Config V2 Suite") +} + +var _ = Describe("cfg", func() { + const ( + domain = "my.domain" + repo = "myrepo" + + otherDomain = "other.domain" + otherRepo = "otherrepo" + ) + + var c cfg + + BeforeEach(func() { + c = cfg{ + Version: Version, + Domain: domain, + Repository: repo, + } + }) + + Context("Version", func() { + It("GetVersion should return version 2", func() { + Expect(c.GetVersion().Compare(Version)).To(Equal(0)) + }) + }) + + Context("Domain", func() { + It("GetDomain should return the domain", func() { + Expect(c.GetDomain()).To(Equal(domain)) + }) + + It("SetDomain should set the domain", func() { + Expect(c.SetDomain(otherDomain)).To(Succeed()) + Expect(c.Domain).To(Equal(otherDomain)) + }) + }) + + Context("Repository", func() { + It("GetRepository should return the repository", func() { + Expect(c.GetRepository()).To(Equal(repo)) + }) + + It("SetRepository should set the repository", func() { + Expect(c.SetRepository(otherRepo)).To(Succeed()) + Expect(c.Repository).To(Equal(otherRepo)) + }) + }) + + Context("Name", func() { + It("GetProjectName should return an empty name", func() { + Expect(c.GetProjectName()).To(Equal("")) + }) + + It("SetProjectName should fail to set the name", func() { + Expect(c.SetProjectName("name")).NotTo(Succeed()) + }) + }) + + Context("Layout", func() { + It("GetLayout should return an empty layout", func() { + Expect(c.GetLayout()).To(Equal("")) + }) + + It("SetLayout should fail to set the layout", func() { + Expect(c.SetLayout("layout")).NotTo(Succeed()) + }) + }) + + Context("Multi group", func() { + It("IsMultiGroup should return false if not set", func() { + Expect(c.IsMultiGroup()).To(BeFalse()) + }) + + It("IsMultiGroup should return true if set", func() { + c.MultiGroup = true + Expect(c.IsMultiGroup()).To(BeTrue()) + }) + + It("SetMultiGroup should enable multi-group support", func() { + Expect(c.SetMultiGroup()).To(Succeed()) + Expect(c.MultiGroup).To(BeTrue()) + }) + + It("ClearMultiGroup should disable multi-group support", func() { + c.MultiGroup = true + Expect(c.ClearMultiGroup()).To(Succeed()) + Expect(c.MultiGroup).To(BeFalse()) + }) + }) + + Context("Component config", func() { + It("IsComponentConfig should return false", func() { + Expect(c.IsComponentConfig()).To(BeFalse()) + }) + + It("SetComponentConfig should fail to enable component config support", func() { + Expect(c.SetComponentConfig()).NotTo(Succeed()) + }) + + It("ClearComponentConfig should fail to disable component config support", func() { + Expect(c.ClearComponentConfig()).NotTo(Succeed()) + }) + }) + + Context("Resources", func() { + var res = resource.Resource{ + GVK: resource.GVK{ + Group: "group", + Version: "v1", + Kind: "Kind", + }, + } + + DescribeTable("ResourcesLength should return the number of resources", + func(n int) { + for i := 0; i < n; i++ { + c.Gvks = append(c.Gvks, res.GVK) + } + Expect(c.ResourcesLength()).To(Equal(n)) + }, + Entry("for no resources", 0), + Entry("for one resource", 1), + Entry("for several resources", 3), + ) + + It("HasResource should return false for a non-existent resource", func() { + Expect(c.HasResource(res.GVK)).To(BeFalse()) + }) + + It("HasResource should return true for an existent resource", func() { + c.Gvks = append(c.Gvks, res.GVK) + Expect(c.HasResource(res.GVK)).To(BeTrue()) + }) + + It("GetResource should fail for a non-existent resource", func() { + _, err := c.GetResource(res.GVK) + Expect(err).To(HaveOccurred()) + }) + + It("GetResource should return an existent resource", func() { + c.Gvks = append(c.Gvks, res.GVK) + r, err := c.GetResource(res.GVK) + Expect(err).NotTo(HaveOccurred()) + Expect(r.GVK.IsEqualTo(res.GVK)).To(BeTrue()) + }) + + It("GetResources should return a slice of the tracked resources", func() { + c.Gvks = append(c.Gvks, res.GVK, res.GVK, res.GVK) + resources, err := c.GetResources() + Expect(err).NotTo(HaveOccurred()) + Expect(resources).To(Equal([]resource.Resource{res, res, res})) + }) + + It("AddResource should add the provided resource if non-existent", func() { + l := len(c.Gvks) + Expect(c.AddResource(res)).To(Succeed()) + Expect(len(c.Gvks)).To(Equal(l + 1)) + Expect(c.Gvks[0].IsEqualTo(res.GVK)).To(BeTrue()) + }) + + It("AddResource should do nothing if the resource already exists", func() { + c.Gvks = append(c.Gvks, res.GVK) + l := len(c.Gvks) + Expect(c.AddResource(res)).To(Succeed()) + Expect(len(c.Gvks)).To(Equal(l)) + }) + + It("UpdateResource should add the provided resource if non-existent", func() { + l := len(c.Gvks) + Expect(c.UpdateResource(res)).To(Succeed()) + Expect(len(c.Gvks)).To(Equal(l + 1)) + Expect(c.Gvks[0].IsEqualTo(res.GVK)).To(BeTrue()) + }) + + It("UpdateResource should do nothing if the resource already exists", func() { + c.Gvks = append(c.Gvks, res.GVK) + l := len(c.Gvks) + Expect(c.UpdateResource(res)).To(Succeed()) + Expect(len(c.Gvks)).To(Equal(l)) + }) + + It("HasGroup should return false with no tracked resources", func() { + Expect(c.HasGroup(res.Group)).To(BeFalse()) + }) + + It("HasGroup should return true with tracked resources in the same group", func() { + c.Gvks = append(c.Gvks, res.GVK) + Expect(c.HasGroup(res.Group)).To(BeTrue()) + }) + + It("HasGroup should return false with tracked resources in other group", func() { + c.Gvks = append(c.Gvks, res.GVK) + Expect(c.HasGroup("other-group")).To(BeFalse()) + }) + + It("IsCRDVersionCompatible should return true for `v1beta1`", func() { + Expect(c.IsCRDVersionCompatible("v1beta1")).To(BeTrue()) + }) + + It("IsCRDVersionCompatible should return false for any other than `v1beta1`", func() { + Expect(c.IsCRDVersionCompatible("v1")).To(BeFalse()) + Expect(c.IsCRDVersionCompatible("v2")).To(BeFalse()) + }) + + It("IsWebhookVersionCompatible should return true for `v1beta1`", func() { + Expect(c.IsWebhookVersionCompatible("v1beta1")).To(BeTrue()) + }) + + It("IsWebhookVersionCompatible should return false for any other than `v1beta1`", func() { + Expect(c.IsWebhookVersionCompatible("v1")).To(BeFalse()) + Expect(c.IsWebhookVersionCompatible("v2")).To(BeFalse()) + }) + }) + + Context("Plugins", func() { + It("DecodePluginConfig should fail", func() { + Expect(c.DecodePluginConfig("", nil)).NotTo(Succeed()) + }) + + It("EncodePluginConfig should fail", func() { + Expect(c.EncodePluginConfig("", nil)).NotTo(Succeed()) + }) + }) + + Context("Persistence", func() { + var ( + // BeforeEach is called after the entries are evaluated, and therefore, c is not available + c1 = cfg{ + Version: Version, + Domain: domain, + Repository: repo, + } + c2 = cfg{ + Version: Version, + Domain: otherDomain, + Repository: otherRepo, + MultiGroup: true, + Gvks: []resource.GVK{ + {Group: "group", Version: "v1", Kind: "Kind"}, + {Group: "group", Version: "v1", Kind: "Kind2"}, + {Group: "group", Version: "v1-beta", Kind: "Kind"}, + {Group: "group2", Version: "v1", Kind: "Kind"}, + }, + } + s1 = `domain: my.domain +repo: myrepo +version: "2" +` + s2 = `domain: other.domain +multigroup: true +repo: otherrepo +resources: +- group: group + kind: Kind + version: v1 +- group: group + kind: Kind2 + version: v1 +- group: group + kind: Kind + version: v1-beta +- group: group2 + kind: Kind + version: v1 +version: "2" +` + ) + + DescribeTable("Marshal should succeed", + func(c cfg, content string) { + b, err := c.Marshal() + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(Equal(content)) + }, + Entry("for a basic configuration", c1, s1), + Entry("for a full configuration", c2, s2), + ) + + DescribeTable("Marshal should fail", + func(c cfg) { + _, err := c.Marshal() + Expect(err).To(HaveOccurred()) + }, + // TODO (coverage): add cases where yaml.Marshal returns an error + ) + + DescribeTable("Unmarshal should succeed", + func(content string, c cfg) { + var unmarshalled cfg + Expect(unmarshalled.Unmarshal([]byte(content))).To(Succeed()) + Expect(unmarshalled.Version.Compare(c.Version)).To(Equal(0)) + Expect(unmarshalled.Domain).To(Equal(c.Domain)) + Expect(unmarshalled.Repository).To(Equal(c.Repository)) + Expect(unmarshalled.MultiGroup).To(Equal(c.MultiGroup)) + Expect(unmarshalled.Gvks).To(Equal(c.Gvks)) + }, + Entry("basic", s1, c1), + Entry("full", s2, c2), + ) + + DescribeTable("Unmarshal should fail", + func(content string) { + var c cfg + Expect(c.Unmarshal([]byte(content))).NotTo(Succeed()) + }, + Entry("for unknown fields", `field: 1 +version: "2"`), + ) + }) +}) + +var _ = Describe("New", func() { + It("should return a new config for project configuration 2", func() { + Expect(New().GetVersion().Compare(Version)).To(Equal(0)) + }) +}) diff --git a/pkg/config/v3alpha/config.go b/pkg/config/v3alpha/config.go new file mode 100644 index 00000000000..72464d9ce4e --- /dev/null +++ b/pkg/config/v3alpha/config.go @@ -0,0 +1,349 @@ +/* +Copyright 2021 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 v3alpha + +import ( + "fmt" + "strings" + + "sigs.k8s.io/yaml" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" +) + +// Version is the config.Version for project configuration 3-alpha +var Version = config.Version{Number: 3, Stage: stage.Alpha} + +type cfg struct { + // Version + Version config.Version `json:"version"` + + // String fields + Domain string `json:"domain,omitempty"` + Repository string `json:"repo,omitempty"` + Name string `json:"projectName,omitempty"` + Layout string `json:"layout,omitempty"` + + // Boolean fields + MultiGroup bool `json:"multigroup,omitempty"` + ComponentConfig bool `json:"componentConfig,omitempty"` + + // Resources + Resources []resource.Resource `json:"resources,omitempty"` + + // Plugins + Plugins PluginConfigs `json:"plugins,omitempty"` +} + +// PluginConfigs holds a set of arbitrary plugin configuration objects mapped by plugin key. +// TODO: do not export this once internalconfig has merged with config +type PluginConfigs map[string]pluginConfig + +// pluginConfig is an arbitrary plugin configuration object. +type pluginConfig interface{} + +// New returns a new config.Config +func New() config.Config { + return &cfg{Version: Version} +} + +func init() { + config.Register(Version, New) +} + +// GetVersion implements config.Config +func (c cfg) GetVersion() config.Version { + return c.Version +} + +// GetDomain implements config.Config +func (c cfg) GetDomain() string { + return c.Domain +} + +// SetDomain implements config.Config +func (c *cfg) SetDomain(domain string) error { + c.Domain = domain + return nil +} + +// GetRepository implements config.Config +func (c cfg) GetRepository() string { + return c.Repository +} + +// SetRepository implements config.Config +func (c *cfg) SetRepository(repository string) error { + c.Repository = repository + return nil +} + +// GetProjectName implements config.Config +func (c cfg) GetProjectName() string { + return c.Name +} + +// SetProjectName implements config.Config +func (c *cfg) SetProjectName(name string) error { + c.Name = name + return nil +} + +// GetLayout implements config.Config +func (c cfg) GetLayout() string { + return c.Layout +} + +// SetLayout implements config.Config +func (c *cfg) SetLayout(layout string) error { + c.Layout = layout + return nil +} + +// IsMultiGroup implements config.Config +func (c cfg) IsMultiGroup() bool { + return c.MultiGroup +} + +// SetMultiGroup implements config.Config +func (c *cfg) SetMultiGroup() error { + c.MultiGroup = true + return nil +} + +// ClearMultiGroup implements config.Config +func (c *cfg) ClearMultiGroup() error { + c.MultiGroup = false + return nil +} + +// IsComponentConfig implements config.Config +func (c cfg) IsComponentConfig() bool { + return c.ComponentConfig +} + +// SetComponentConfig implements config.Config +func (c *cfg) SetComponentConfig() error { + c.ComponentConfig = true + return nil +} + +// ClearComponentConfig implements config.Config +func (c *cfg) ClearComponentConfig() error { + c.ComponentConfig = false + return nil +} + +// ResourcesLength implements config.Config +func (c cfg) ResourcesLength() int { + return len(c.Resources) +} + +// HasResource implements config.Config +func (c cfg) HasResource(gvk resource.GVK) bool { + gvk.Domain = "" // Version 3 alpha does not include domain per resource + + for _, res := range c.Resources { + if gvk.IsEqualTo(res.GVK) { + return true + } + } + + return false +} + +// GetResource implements config.Config +func (c cfg) GetResource(gvk resource.GVK) (resource.Resource, error) { + gvk.Domain = "" // Version 3 alpha does not include domain per resource + + for _, res := range c.Resources { + if gvk.IsEqualTo(res.GVK) { + return res.Copy(), nil + } + } + + return resource.Resource{}, config.UnknownResource{GVK: gvk} +} + +// GetResources implements config.Config +func (c cfg) GetResources() ([]resource.Resource, error) { + resources := make([]resource.Resource, 0, len(c.Resources)) + for _, res := range c.Resources { + resources = append(resources, res.Copy()) + } + + return resources, nil +} + +func discardNonIncludedFields(res *resource.Resource) { + res.Domain = "" // Version 3 alpha does not include domain per resource + res.Plural = "" // Version 3 alpha does not include plural forms + res.Path = "" // Version 3 alpha does not include paths + if res.API != nil { + res.API.Namespaced = false // Version 3 alpha does not include if the api was namespaced + } + res.Controller = false // Version 3 alpha does not include if the controller was scaffolded + if res.Webhooks != nil { + res.Webhooks.Defaulting = false // Version 3 alpha does not include if the defaulting webhook was scaffolded + res.Webhooks.Validation = false // Version 3 alpha does not include if the validation webhook was scaffolded + res.Webhooks.Conversion = false // Version 3 alpha does not include if the conversion webhook was scaffolded + } +} + +// AddResource implements config.Config +func (c *cfg) AddResource(res resource.Resource) error { + // As res is passed by value it is already a shallow copy, but we need to make a deep copy + res = res.Copy() + + discardNonIncludedFields(&res) // Version 3 alpha does not include several fields from the Resource model + + if !c.HasResource(res.GVK) { + c.Resources = append(c.Resources, res) + } + return nil +} + +// UpdateResource implements config.Config +func (c *cfg) UpdateResource(res resource.Resource) error { + // As res is passed by value it is already a shallow copy, but we need to make a deep copy + res = res.Copy() + + discardNonIncludedFields(&res) // Version 3 alpha does not include several fields from the Resource model + + for i, r := range c.Resources { + if res.GVK.IsEqualTo(r.GVK) { + return c.Resources[i].Update(res) + } + } + + c.Resources = append(c.Resources, res) + return nil +} + +// HasGroup implements config.Config +func (c cfg) HasGroup(group string) bool { + // Return true if the target group is found in the tracked resources + for _, r := range c.Resources { + if strings.EqualFold(group, r.Group) { + return true + } + } + + // Return false otherwise + return false +} + +// IsCRDVersionCompatible implements config.Config +func (c cfg) IsCRDVersionCompatible(crdVersion string) bool { + return c.resourceAPIVersionCompatible("crd", crdVersion) +} + +// IsWebhookVersionCompatible implements config.Config +func (c cfg) IsWebhookVersionCompatible(webhookVersion string) bool { + return c.resourceAPIVersionCompatible("webhook", webhookVersion) +} + +func (c cfg) resourceAPIVersionCompatible(verType, version string) bool { + for _, res := range c.Resources { + var currVersion string + switch verType { + case "crd": + if res.API != nil { + currVersion = res.API.CRDVersion + } + case "webhook": + if res.Webhooks != nil { + currVersion = res.Webhooks.WebhookVersion + } + } + if currVersion != "" && version != currVersion { + return false + } + } + + return true +} + +// DecodePluginConfig implements config.Config +func (c cfg) DecodePluginConfig(key string, configObj interface{}) error { + if len(c.Plugins) == 0 { + return nil + } + + // Get the object blob by key and unmarshal into the object. + if pluginConfig, hasKey := c.Plugins[key]; hasKey { + b, err := yaml.Marshal(pluginConfig) + if err != nil { + return fmt.Errorf("failed to convert extra fields object to bytes: %w", err) + } + if err := yaml.Unmarshal(b, configObj); err != nil { + return fmt.Errorf("failed to unmarshal extra fields object: %w", err) + } + } + + return nil +} + +// EncodePluginConfig will return an error if used on any project version < v3. +func (c *cfg) EncodePluginConfig(key string, configObj interface{}) error { + // Get object's bytes and set them under key in extra fields. + b, err := yaml.Marshal(configObj) + if err != nil { + return fmt.Errorf("failed to convert %T object to bytes: %s", configObj, err) + } + var fields map[string]interface{} + if err := yaml.Unmarshal(b, &fields); err != nil { + return fmt.Errorf("failed to unmarshal %T object bytes: %s", configObj, err) + } + if c.Plugins == nil { + c.Plugins = make(map[string]pluginConfig) + } + c.Plugins[key] = fields + return nil +} + +// Marshal implements config.Config +func (c cfg) Marshal() ([]byte, error) { + for i, r := range c.Resources { + // If API is empty, omit it (prevents `api: {}`). + if r.API != nil && r.API.IsEmpty() { + c.Resources[i].API = nil + } + // If Webhooks is empty, omit it (prevents `webhooks: {}`). + if r.Webhooks != nil && r.Webhooks.IsEmpty() { + c.Resources[i].Webhooks = nil + } + } + + content, err := yaml.Marshal(c) + if err != nil { + return nil, config.MarshalError{Err: err} + } + + return content, nil +} + +// Unmarshal implements config.Config +func (c *cfg) Unmarshal(b []byte) error { + if err := yaml.UnmarshalStrict(b, c); err != nil { + return config.UnmarshalError{Err: err} + } + + return nil +} diff --git a/pkg/config/v3alpha/config_test.go b/pkg/config/v3alpha/config_test.go new file mode 100644 index 00000000000..111fd98524b --- /dev/null +++ b/pkg/config/v3alpha/config_test.go @@ -0,0 +1,572 @@ +/* +Copyright 2021 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 v3alpha + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +func TestConfigV3Alpha(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Config V3-Alpha Suite") +} + +var _ = Describe("cfg", func() { + const ( + domain = "my.domain" + repo = "myrepo" + name = "ProjectName" + layout = "go.kubebuilder.io/v2" + + otherDomain = "other.domain" + otherRepo = "otherrepo" + otherName = "OtherProjectName" + otherLayout = "go.kubebuilder.io/v3-alpha" + ) + + var c cfg + + BeforeEach(func() { + c = cfg{ + Version: Version, + Domain: domain, + Repository: repo, + Name: name, + Layout: layout, + } + }) + + Context("Version", func() { + It("GetVersion should return version 3-alpha", func() { + Expect(c.GetVersion().Compare(Version)).To(Equal(0)) + }) + }) + + Context("Domain", func() { + It("GetDomain should return the domain", func() { + Expect(c.GetDomain()).To(Equal(domain)) + }) + + It("SetDomain should set the domain", func() { + Expect(c.SetDomain(otherDomain)).To(Succeed()) + Expect(c.Domain).To(Equal(otherDomain)) + }) + }) + + Context("Repository", func() { + It("GetRepository should return the repository", func() { + Expect(c.GetRepository()).To(Equal(repo)) + }) + + It("SetRepository should set the repository", func() { + Expect(c.SetRepository(otherRepo)).To(Succeed()) + Expect(c.Repository).To(Equal(otherRepo)) + }) + }) + + Context("Name", func() { + It("GetProjectName should return the name", func() { + Expect(c.GetProjectName()).To(Equal(name)) + }) + + It("SetProjectName should set the name", func() { + Expect(c.SetProjectName(otherName)).To(Succeed()) + Expect(c.Name).To(Equal(otherName)) + }) + }) + + Context("Layout", func() { + It("GetLayout should return the layout", func() { + Expect(c.GetLayout()).To(Equal(layout)) + }) + + It("SetLayout should set the layout", func() { + Expect(c.SetLayout(otherLayout)).To(Succeed()) + Expect(c.Layout).To(Equal(otherLayout)) + }) + }) + + Context("Multi group", func() { + It("IsMultiGroup should return false if not set", func() { + Expect(c.IsMultiGroup()).To(BeFalse()) + }) + + It("IsMultiGroup should return true if set", func() { + c.MultiGroup = true + Expect(c.IsMultiGroup()).To(BeTrue()) + }) + + It("SetMultiGroup should enable multi-group support", func() { + Expect(c.SetMultiGroup()).To(Succeed()) + Expect(c.MultiGroup).To(BeTrue()) + }) + + It("ClearMultiGroup should disable multi-group support", func() { + c.MultiGroup = true + Expect(c.ClearMultiGroup()).To(Succeed()) + Expect(c.MultiGroup).To(BeFalse()) + }) + }) + + Context("Component config", func() { + It("IsComponentConfig should return false if not set", func() { + Expect(c.IsComponentConfig()).To(BeFalse()) + }) + + It("IsComponentConfig should return true if set", func() { + c.ComponentConfig = true + Expect(c.IsComponentConfig()).To(BeTrue()) + }) + + It("SetComponentConfig should fail to enable component config support", func() { + Expect(c.SetComponentConfig()).To(Succeed()) + Expect(c.ComponentConfig).To(BeTrue()) + }) + + It("ClearComponentConfig should fail to disable component config support", func() { + c.ComponentConfig = false + Expect(c.ClearComponentConfig()).To(Succeed()) + Expect(c.ComponentConfig).To(BeFalse()) + }) + }) + + Context("Resources", func() { + var res = resource.Resource{ + GVK: resource.GVK{ + Group: "group", + Version: "v1", + Kind: "Kind", + }, + API: &resource.API{ + CRDVersion: "v1", + Namespaced: true, + }, + Controller: true, + Webhooks: &resource.Webhooks{ + WebhookVersion: "v1", + Defaulting: true, + Validation: true, + Conversion: true, + }, + } + + DescribeTable("ResourcesLength should return the number of resources", + func(n int) { + for i := 0; i < n; i++ { + c.Resources = append(c.Resources, res) + } + Expect(c.ResourcesLength()).To(Equal(n)) + }, + Entry("for no resources", 0), + Entry("for one resource", 1), + Entry("for several resources", 3), + ) + + It("HasResource should return false for a non-existent resource", func() { + Expect(c.HasResource(res.GVK)).To(BeFalse()) + }) + + It("HasResource should return true for an existent resource", func() { + c.Resources = append(c.Resources, res) + Expect(c.HasResource(res.GVK)).To(BeTrue()) + }) + + It("GetResource should fail for a non-existent resource", func() { + _, err := c.GetResource(res.GVK) + Expect(err).To(HaveOccurred()) + }) + + It("GetResource should return an existent resource", func() { + c.Resources = append(c.Resources, res) + r, err := c.GetResource(res.GVK) + Expect(err).NotTo(HaveOccurred()) + Expect(r.GVK.IsEqualTo(res.GVK)).To(BeTrue()) + Expect(r.API).NotTo(BeNil()) + Expect(r.API.CRDVersion).To(Equal(res.API.CRDVersion)) + Expect(r.Webhooks).NotTo(BeNil()) + Expect(r.Webhooks.WebhookVersion).To(Equal(res.Webhooks.WebhookVersion)) + }) + + It("GetResources should return a slice of the tracked resources", func() { + c.Resources = append(c.Resources, res, res, res) + resources, err := c.GetResources() + Expect(err).NotTo(HaveOccurred()) + Expect(resources).To(Equal([]resource.Resource{res, res, res})) + }) + + // Auxiliary function for AddResource and UpdateResource tests + checkResource := func(result, expected resource.Resource) { + Expect(result.GVK.IsEqualTo(expected.GVK)).To(BeTrue()) + Expect(result.API).NotTo(BeNil()) + Expect(result.API.CRDVersion).To(Equal(expected.API.CRDVersion)) + Expect(result.API.Namespaced).To(BeFalse()) + Expect(result.Controller).To(BeFalse()) + Expect(result.Webhooks).NotTo(BeNil()) + Expect(result.Webhooks.WebhookVersion).To(Equal(expected.Webhooks.WebhookVersion)) + Expect(result.Webhooks.Defaulting).To(BeFalse()) + Expect(result.Webhooks.Validation).To(BeFalse()) + Expect(result.Webhooks.Conversion).To(BeFalse()) + } + + It("AddResource should add the provided resource if non-existent", func() { + l := len(c.Resources) + Expect(c.AddResource(res)).To(Succeed()) + Expect(len(c.Resources)).To(Equal(l + 1)) + + checkResource(c.Resources[0], res) + }) + + It("AddResource should do nothing if the resource already exists", func() { + c.Resources = append(c.Resources, res) + l := len(c.Resources) + Expect(c.AddResource(res)).To(Succeed()) + Expect(len(c.Resources)).To(Equal(l)) + }) + + It("UpdateResource should add the provided resource if non-existent", func() { + l := len(c.Resources) + Expect(c.UpdateResource(res)).To(Succeed()) + Expect(len(c.Resources)).To(Equal(l + 1)) + + checkResource(c.Resources[0], res) + }) + + It("UpdateResource should update it if the resource already exists", func() { + c.Resources = append(c.Resources, resource.Resource{ + GVK: resource.GVK{ + Group: "group", + Version: "v1", + Kind: "Kind", + }, + }) + l := len(c.Resources) + Expect(c.Resources[0].GVK.IsEqualTo(res.GVK)).To(BeTrue()) + Expect(c.Resources[0].API).To(BeNil()) + Expect(c.Resources[0].Controller).To(BeFalse()) + Expect(c.Resources[0].Webhooks).To(BeNil()) + + Expect(c.UpdateResource(res)).To(Succeed()) + Expect(len(c.Resources)).To(Equal(l)) + + r := c.Resources[0] + Expect(r.GVK.IsEqualTo(res.GVK)).To(BeTrue()) + Expect(r.API).NotTo(BeNil()) + Expect(r.API.CRDVersion).To(Equal(res.API.CRDVersion)) + Expect(r.API.Namespaced).To(BeFalse()) + Expect(r.Controller).To(BeFalse()) + Expect(r.Webhooks).NotTo(BeNil()) + Expect(r.Webhooks.WebhookVersion).To(Equal(res.Webhooks.WebhookVersion)) + Expect(r.Webhooks.Defaulting).To(BeFalse()) + Expect(r.Webhooks.Validation).To(BeFalse()) + Expect(r.Webhooks.Conversion).To(BeFalse()) + }) + + It("HasGroup should return false with no tracked resources", func() { + Expect(c.HasGroup(res.Group)).To(BeFalse()) + }) + + It("HasGroup should return true with tracked resources in the same group", func() { + c.Resources = append(c.Resources, res) + Expect(c.HasGroup(res.Group)).To(BeTrue()) + }) + + It("HasGroup should return false with tracked resources in other group", func() { + c.Resources = append(c.Resources, res) + Expect(c.HasGroup("other-group")).To(BeFalse()) + }) + + It("IsCRDVersionCompatible should return true with no tracked resources", func() { + Expect(c.IsCRDVersionCompatible("v1beta1")).To(BeTrue()) + Expect(c.IsCRDVersionCompatible("v1")).To(BeTrue()) + }) + + It("IsCRDVersionCompatible should return true only for matching CRD versions of tracked resources", func() { + c.Resources = append(c.Resources, resource.Resource{ + GVK: resource.GVK{ + Group: res.Group, + Version: res.Version, + Kind: res.Kind, + }, + API: &resource.API{CRDVersion: "v1beta1"}, + }) + Expect(c.IsCRDVersionCompatible("v1beta1")).To(BeTrue()) + Expect(c.IsCRDVersionCompatible("v1")).To(BeFalse()) + Expect(c.IsCRDVersionCompatible("v2")).To(BeFalse()) + }) + + It("IsWebhookVersionCompatible should return true with no tracked resources", func() { + Expect(c.IsWebhookVersionCompatible("v1beta1")).To(BeTrue()) + Expect(c.IsWebhookVersionCompatible("v1")).To(BeTrue()) + }) + + It("IsWebhookVersionCompatible should return true only for matching webhook versions of tracked resources", func() { + c.Resources = append(c.Resources, resource.Resource{ + GVK: resource.GVK{ + Group: res.Group, + Version: res.Version, + Kind: res.Kind, + }, + Webhooks: &resource.Webhooks{WebhookVersion: "v1beta1"}, + }) + Expect(c.IsWebhookVersionCompatible("v1beta1")).To(BeTrue()) + Expect(c.IsWebhookVersionCompatible("v1")).To(BeFalse()) + Expect(c.IsWebhookVersionCompatible("v2")).To(BeFalse()) + }) + }) + + Context("Plugins", func() { + // Test plugin config. Don't want to export this config, but need it to + // be accessible by test. + type PluginConfig struct { + Data1 string `json:"data-1"` + Data2 string `json:"data-2,omitempty"` + } + + const ( + key = "plugin-x" + ) + + var ( + c0 = cfg{ + Version: Version, + Domain: domain, + Repository: repo, + Name: name, + Layout: layout, + } + c1 = cfg{ + Version: Version, + Domain: domain, + Repository: repo, + Name: name, + Layout: layout, + Plugins: PluginConfigs{ + "plugin-x": map[string]interface{}{ + "data-1": "", + }, + }, + } + c2 = cfg{ + Version: Version, + Domain: domain, + Repository: repo, + Name: name, + Layout: layout, + Plugins: PluginConfigs{ + "plugin-x": map[string]interface{}{ + "data-1": "plugin value 1", + "data-2": "plugin value 2", + }, + }, + } + pluginConfig = PluginConfig{ + Data1: "plugin value 1", + Data2: "plugin value 2", + } + ) + + DescribeTable("DecodePluginConfig should retrieve the plugin data correctly", + func(inputConfig cfg, expectedPluginConfig PluginConfig) { + var pluginConfig PluginConfig + Expect(inputConfig.DecodePluginConfig(key, &pluginConfig)).To(Succeed()) + Expect(pluginConfig).To(Equal(expectedPluginConfig)) + }, + Entry("for no plugin config object", c0, nil), + Entry("for an empty plugin config object", c1, PluginConfig{}), + Entry("for a full plugin config object", c2, pluginConfig), + // TODO (coverage): add cases where yaml.Marshal returns an error + // TODO (coverage): add cases where yaml.Unmarshal returns an error + ) + + DescribeTable("EncodePluginConfig should encode the plugin data correctly", + func(pluginConfig PluginConfig, expectedConfig cfg) { + Expect(c.EncodePluginConfig(key, pluginConfig)).To(Succeed()) + Expect(c).To(Equal(expectedConfig)) + }, + Entry("for an empty plugin config object", PluginConfig{}, c1), + Entry("for a full plugin config object", pluginConfig, c2), + // TODO (coverage): add cases where yaml.Marshal returns an error + // TODO (coverage): add cases where yaml.Unmarshal returns an error + ) + }) + + Context("Persistence", func() { + var ( + // BeforeEach is called after the entries are evaluated, and therefore, c is not available + c1 = cfg{ + Version: Version, + Domain: domain, + Repository: repo, + Name: name, + Layout: layout, + } + c2 = cfg{ + Version: Version, + Domain: otherDomain, + Repository: otherRepo, + Name: otherName, + Layout: otherLayout, + MultiGroup: true, + ComponentConfig: true, + Resources: []resource.Resource{ + { + GVK: resource.GVK{ + Group: "group", + Version: "v1", + Kind: "Kind", + }, + }, + { + GVK: resource.GVK{ + Group: "group", + Version: "v1", + Kind: "Kind2", + }, + API: &resource.API{CRDVersion: "v1"}, + Webhooks: &resource.Webhooks{WebhookVersion: "v1"}, + }, + { + GVK: resource.GVK{ + Group: "group", + Version: "v1-beta", + Kind: "Kind", + }, + API: &resource.API{}, + Webhooks: &resource.Webhooks{}, + }, + { + GVK: resource.GVK{ + Group: "group2", + Version: "v1", + Kind: "Kind", + }, + }, + }, + Plugins: PluginConfigs{ + "plugin-x": map[string]interface{}{ + "data-1": "single plugin datum", + }, + "plugin-y/v1": map[string]interface{}{ + "data-1": "plugin value 1", + "data-2": "plugin value 2", + "data-3": []string{"plugin value 3", "plugin value 4"}, + }, + }, + } + // TODO: include cases with Plural, Path, API.namespaced, Controller, Webhooks.Defaulting, + // Webhooks.Validation and Webhooks.Conversion when added + s1 = `domain: my.domain +layout: go.kubebuilder.io/v2 +projectName: ProjectName +repo: myrepo +version: 3-alpha +` + s2 = `componentConfig: true +domain: other.domain +layout: go.kubebuilder.io/v3-alpha +multigroup: true +plugins: + plugin-x: + data-1: single plugin datum + plugin-y/v1: + data-1: plugin value 1 + data-2: plugin value 2 + data-3: + - plugin value 3 + - plugin value 4 +projectName: OtherProjectName +repo: otherrepo +resources: +- group: group + kind: Kind + version: v1 +- api: + crdVersion: v1 + group: group + kind: Kind2 + version: v1 + webhooks: + webhookVersion: v1 +- group: group + kind: Kind + version: v1-beta +- group: group2 + kind: Kind + version: v1 +version: 3-alpha +` + ) + + DescribeTable("Marshal should succeed", + func(c cfg, content string) { + b, err := c.Marshal() + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(Equal(content)) + }, + Entry("for a basic configuration", c1, s1), + Entry("for a full configuration", c2, s2), + ) + + DescribeTable("Marshal should fail", + func(c cfg) { + _, err := c.Marshal() + Expect(err).To(HaveOccurred()) + }, + // TODO (coverage): add cases where yaml.Marshal returns an error + ) + + DescribeTable("Unmarshal should succeed", + func(content string, c cfg) { + var unmarshalled cfg + Expect(unmarshalled.Unmarshal([]byte(content))).To(Succeed()) + Expect(unmarshalled.Version.Compare(c.Version)).To(Equal(0)) + Expect(unmarshalled.Domain).To(Equal(c.Domain)) + Expect(unmarshalled.Repository).To(Equal(c.Repository)) + Expect(unmarshalled.Name).To(Equal(c.Name)) + Expect(unmarshalled.Layout).To(Equal(c.Layout)) + Expect(unmarshalled.MultiGroup).To(Equal(c.MultiGroup)) + Expect(unmarshalled.ComponentConfig).To(Equal(c.ComponentConfig)) + Expect(unmarshalled.Resources).To(Equal(c.Resources)) + Expect(unmarshalled.Plugins).To(HaveLen(len(c.Plugins))) + // TODO: fully test Plugins field and not on its length + }, + Entry("basic", s1, c1), + Entry("full", s2, c2), + ) + + DescribeTable("Unmarshal should fail", + func(content string) { + var c cfg + Expect(c.Unmarshal([]byte(content))).NotTo(Succeed()) + }, + Entry("for unknown fields", `field: 1 +version: 3-alpha`), + ) + }) +}) + +var _ = Describe("New", func() { + It("should return a new config for project configuration 3-alpha", func() { + Expect(New().GetVersion().Compare(Version)).To(Equal(0)) + }) +}) diff --git a/pkg/config/version.go b/pkg/config/version.go new file mode 100644 index 00000000000..4b3d4ead71c --- /dev/null +++ b/pkg/config/version.go @@ -0,0 +1,122 @@ +/* +Copyright 2021 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 config + +import ( + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" +) + +var ( + errNonPositive = errors.New("project version number must be positive") + errEmpty = errors.New("project version is empty") +) + +// Version is a project version containing a non-zero positive integer and a stage value that represents stability. +type Version struct { + // Number denotes the current version of a plugin. Two different numbers between versions + // indicate that they are incompatible. + Number int + // Stage indicates stability. + Stage stage.Stage +} + +// Parse parses version inline, assuming it adheres to format: [1-9][0-9]*(-(alpha|beta))? +func (v *Version) Parse(version string) error { + if len(version) == 0 { + return errEmpty + } + + substrings := strings.SplitN(version, "-", 2) + + var err error + if v.Number, err = strconv.Atoi(substrings[0]); err != nil { + // Lets check if the `-` belonged to a negative number + if n, err := strconv.Atoi(version); err == nil && n < 0 { + return errNonPositive + } + return err + } else if v.Number == 0 { + return errNonPositive + } + + if len(substrings) > 1 { + if err = v.Stage.Parse(substrings[1]); err != nil { + return err + } + } + + return nil +} + +// String returns the string representation of v. +func (v Version) String() string { + stageStr := v.Stage.String() + if len(stageStr) == 0 { + return fmt.Sprintf("%d", v.Number) + } + return fmt.Sprintf("%d-%s", v.Number, stageStr) +} + +// Validate ensures that the version number is positive and the stage is one of the valid stages. +func (v Version) Validate() error { + if v.Number < 1 { + return errNonPositive + } + + return v.Stage.Validate() +} + +// Compare returns -1 if v < other, 0 if v == other, and 1 if v > other. +func (v Version) Compare(other Version) int { + if v.Number > other.Number { + return 1 + } else if v.Number < other.Number { + return -1 + } + + return v.Stage.Compare(other.Stage) +} + +// IsStable returns true if v is stable. +func (v Version) IsStable() bool { + return v.Stage.IsStable() +} + +// MarshalJSON implements json.Marshaller +func (v Version) MarshalJSON() ([]byte, error) { + if err := v.Validate(); err != nil { + return []byte{}, err + } + + return json.Marshal(v.String()) +} + +// UnmarshalJSON implements json.Unmarshaller +func (v *Version) UnmarshalJSON(b []byte) error { + var str string + if err := json.Unmarshal(b, &str); err != nil { + return err + } + + return v.Parse(str) +} diff --git a/pkg/config/version_test.go b/pkg/config/version_test.go new file mode 100644 index 00000000000..23a4ec0f417 --- /dev/null +++ b/pkg/config/version_test.go @@ -0,0 +1,168 @@ +/* +Copyright 2021 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 config + +import ( + "sort" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" +) + +var _ = Describe("Version", func() { + // Parse, String and Validate are tested by MarshalJSON and UnmarshalJSON + + Context("Compare", func() { + // Test Compare() by sorting a list. + var ( + versions = []Version{ + {Number: 2, Stage: stage.Alpha}, + {Number: 44, Stage: stage.Alpha}, + {Number: 1}, + {Number: 2, Stage: stage.Beta}, + {Number: 4, Stage: stage.Beta}, + {Number: 1, Stage: stage.Alpha}, + {Number: 4}, + {Number: 44, Stage: stage.Alpha}, + {Number: 30}, + {Number: 4, Stage: stage.Alpha}, + } + + sortedVersions = []Version{ + {Number: 1, Stage: stage.Alpha}, + {Number: 1}, + {Number: 2, Stage: stage.Alpha}, + {Number: 2, Stage: stage.Beta}, + {Number: 4, Stage: stage.Alpha}, + {Number: 4, Stage: stage.Beta}, + {Number: 4}, + {Number: 30}, + {Number: 44, Stage: stage.Alpha}, + {Number: 44, Stage: stage.Alpha}, + } + ) + + It("sorts a valid list of versions correctly", func() { + sort.Slice(versions, func(i int, j int) bool { + return versions[i].Compare(versions[j]) == -1 + }) + Expect(versions).To(Equal(sortedVersions)) + }) + + }) + + Context("IsStable", func() { + DescribeTable("should return true for stable versions", + func(version Version) { Expect(version.IsStable()).To(BeTrue()) }, + Entry("for version 1", Version{Number: 1}), + Entry("for version 1 (stable)", Version{Number: 1, Stage: stage.Stable}), + Entry("for version 22", Version{Number: 22}), + Entry("for version 22 (stable)", Version{Number: 22, Stage: stage.Stable}), + ) + + DescribeTable("should return false for unstable versions", + func(version Version) { Expect(version.IsStable()).To(BeFalse()) }, + Entry("for version 1 (alpha)", Version{Number: 1, Stage: stage.Alpha}), + Entry("for version 1 (beta)", Version{Number: 1, Stage: stage.Beta}), + Entry("for version 22 (alpha)", Version{Number: 22, Stage: stage.Alpha}), + Entry("for version 22 (beta)", Version{Number: 22, Stage: stage.Beta}), + ) + }) + + Context("MarshalJSON", func() { + DescribeTable("should be marshalled appropriately", + func(version Version, str string) { + b, err := version.MarshalJSON() + Expect(err).NotTo(HaveOccurred()) + Expect(string(b)).To(Equal(str)) + }, + Entry("for version 1", Version{Number: 1}, `"1"`), + Entry("for version 1 (stable)", Version{Number: 1, Stage: stage.Stable}, `"1"`), + Entry("for version 1 (alpha)", Version{Number: 1, Stage: stage.Alpha}, `"1-alpha"`), + Entry("for version 1 (beta)", Version{Number: 1, Stage: stage.Beta}, `"1-beta"`), + Entry("for version 22", Version{Number: 22}, `"22"`), + Entry("for version 22 (stable)", Version{Number: 22, Stage: stage.Stable}, `"22"`), + Entry("for version 22 (alpha)", Version{Number: 22, Stage: stage.Alpha}, `"22-alpha"`), + Entry("for version 22 (beta)", Version{Number: 22, Stage: stage.Beta}, `"22-beta"`), + ) + + DescribeTable("should fail to be marshalled", + func(version Version) { + _, err := version.MarshalJSON() + Expect(err).To(HaveOccurred()) + }, + Entry("for version 0", Version{Number: 0}), + Entry("for version 0 (stable)", Version{Number: 0, Stage: stage.Stable}), + Entry("for version 0 (alpha)", Version{Number: 0, Stage: stage.Alpha}), + Entry("for version 0 (beta)", Version{Number: 0, Stage: stage.Beta}), + Entry("for version 0 (implicit)", Version{}), + Entry("for version 0 (stable) (implicit)", Version{Stage: stage.Stable}), + Entry("for version 0 (alpha) (implicit)", Version{Stage: stage.Alpha}), + Entry("for version 0 (beta) (implicit)", Version{Stage: stage.Beta}), + Entry("for version -1", Version{Number: -1}), + Entry("for version -1 (stable)", Version{Number: -1, Stage: stage.Stable}), + Entry("for version -1 (alpha)", Version{Number: -1, Stage: stage.Alpha}), + Entry("for version -1 (beta)", Version{Number: -1, Stage: stage.Beta}), + Entry("for invalid stage", Version{Stage: stage.Stage(34)}), + ) + }) + + Context("UnmarshalJSON", func() { + DescribeTable("should be unmarshalled appropriately", + func(str string, number int, s stage.Stage) { + var v Version + err := v.UnmarshalJSON([]byte(str)) + Expect(err).NotTo(HaveOccurred()) + Expect(v.Number).To(Equal(number)) + Expect(v.Stage).To(Equal(s)) + }, + Entry("for version string `1`", `"1"`, 1, stage.Stable), + Entry("for version string `1-alpha`", `"1-alpha"`, 1, stage.Alpha), + Entry("for version string `1-beta`", `"1-beta"`, 1, stage.Beta), + Entry("for version string `22`", `"22"`, 22, stage.Stable), + Entry("for version string `22-alpha`", `"22-alpha"`, 22, stage.Alpha), + Entry("for version string `22-beta`", `"22-beta"`, 22, stage.Beta), + ) + + DescribeTable("should fail to be unmarshalled", + func(str string) { + var v Version + err := v.UnmarshalJSON([]byte(str)) + Expect(err).To(HaveOccurred()) + }, + Entry("for empty version string", ``), + Entry("for version string ``", `""`), + Entry("for version string `0`", `"0"`), + Entry("for version string `0-alpha`", `"0-alpha"`), + Entry("for version string `0-beta`", `"0-beta"`), + Entry("for version string `-1`", `"-1"`), + Entry("for version string `-1-alpha`", `"-1-alpha"`), + Entry("for version string `-1-beta`", `"-1-beta"`), + Entry("for version string `v1`", `"v1"`), + Entry("for version string `v1-alpha`", `"v1-alpha"`), + Entry("for version string `v1-beta`", `"v1-beta"`), + Entry("for version string `1.0`", `"1.0"`), + Entry("for version string `v1.0`", `"v1.0"`), + Entry("for version string `v1.0-alpha`", `"v1.0-alpha"`), + Entry("for version string `1.0.0`", `"1.0.0"`), + Entry("for version string `1-a`", `"1-a"`), + ) + }) +}) diff --git a/pkg/internal/validation/project.go b/pkg/internal/validation/project.go deleted file mode 100644 index 7aa7eee8325..00000000000 --- a/pkg/internal/validation/project.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package validation - -import ( - "errors" - "regexp" -) - -// projectVersionFmt defines the project version format from a project config. -const projectVersionFmt string = "[1-9][0-9]*(?:-(?:alpha|beta))?" - -var projectVersionRe = regexp.MustCompile("^" + projectVersionFmt + "$") - -// ValidateProjectVersion ensures version adheres to the project version format. -func ValidateProjectVersion(version string) error { - if version == "" { - return errors.New("project version is empty") - } - if !projectVersionRe.MatchString(version) { - return errors.New(regexError("invalid value for project version", projectVersionFmt)) - } - return nil -} diff --git a/pkg/model/config/config.go b/pkg/model/config/config.go deleted file mode 100644 index f7fd95fe199..00000000000 --- a/pkg/model/config/config.go +++ /dev/null @@ -1,345 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package config - -import ( - "fmt" - "strings" - - "sigs.k8s.io/yaml" -) - -// Scaffolding versions -const ( - Version2 = "2" - Version3Alpha = "3-alpha" -) - -// Config is the unmarshalled representation of the configuration file -type Config struct { - // Version is the project version, defaults to "1" (backwards compatibility) - Version string `json:"version,omitempty"` - - // Domain is the domain associated with the project and used for API groups - Domain string `json:"domain,omitempty"` - - // Repo is the go package name of the project root - Repo string `json:"repo,omitempty"` - - // ProjectName is the name of this controller project set on initialization. - ProjectName string `json:"projectName,omitempty"` - - // Resources tracks scaffolded resources in the project - // This info is tracked only in project with version 2 - Resources []ResourceData `json:"resources,omitempty"` - - // Multigroup tracks if the project has more than one group - MultiGroup bool `json:"multigroup,omitempty"` - - // ComponentConfig tracks if the project uses a config file for configuring - // the ctrl.Manager - ComponentConfig bool `json:"componentConfig,omitempty"` - - // Layout contains a key specifying which plugin created a project. - Layout string `json:"layout,omitempty"` - - // Plugins holds plugin-specific configs mapped by plugin key. These configs should be - // encoded/decoded using EncodePluginConfig/DecodePluginConfig, respectively. - Plugins PluginConfigs `json:"plugins,omitempty"` -} - -// PluginConfigs holds a set of arbitrary plugin configuration objects mapped by plugin key. -type PluginConfigs map[string]pluginConfig - -// pluginConfig is an arbitrary plugin configuration object. -type pluginConfig interface{} - -// IsV2 returns true if it is a v2 project -func (c Config) IsV2() bool { - return c.Version == Version2 -} - -// IsV3 returns true if it is a v3 project -func (c Config) IsV3() bool { - return c.Version == Version3Alpha -} - -// GetResource returns the GKV if the resource is found -func (c Config) GetResource(target ResourceData) *ResourceData { - // Return true if the target resource is found in the tracked resources - for _, r := range c.Resources { - if r.isGVKEqualTo(target) { - return &r - } - } - return nil -} - -// UpdateResources either adds gvk to the tracked set or, if the resource already exists, -// updates the the equivalent resource in the set. -func (c *Config) UpdateResources(resource ResourceData) { - // If the resource already exists, update it. - for i, r := range c.Resources { - if r.isGVKEqualTo(resource) { - c.Resources[i].merge(resource) - return - } - } - - // The resource does not exist, append the resource to the tracked ones. - c.Resources = append(c.Resources, resource) -} - -// HasGroup returns true if group is already tracked -func (c Config) HasGroup(group string) bool { - // Return true if the target group is found in the tracked resources - for _, r := range c.Resources { - if strings.EqualFold(group, r.Group) { - return true - } - } - - // Return false otherwise - return false -} - -// HasWebhook returns true if webhook is already present -func (c Config) HasWebhook(resource ResourceData) bool { - for _, r := range c.Resources { - if r.isGVKEqualTo(resource) { - return r.Webhooks != nil - } - } - - return false -} - -// IsCRDVersionCompatible returns true if crdVersion can be added to the existing set of CRD versions. -func (c Config) IsCRDVersionCompatible(crdVersion string) bool { - return c.resourceAPIVersionCompatible("crd", crdVersion) -} - -// IsWebhookVersionCompatible returns true if webhookVersion can be added to the existing set of Webhook versions. -func (c Config) IsWebhookVersionCompatible(webhookVersion string) bool { - return c.resourceAPIVersionCompatible("webhook", webhookVersion) -} - -// resourceAPIVersionCompatible returns true if version can be added to the existing set of versions -// for a given verType. -func (c Config) resourceAPIVersionCompatible(verType, version string) bool { - for _, res := range c.Resources { - var currVersion string - switch verType { - case "crd": - if res.API != nil { - currVersion = res.API.CRDVersion - } - case "webhook": - if res.Webhooks != nil { - currVersion = res.Webhooks.WebhookVersion - } - } - if currVersion != "" && version != currVersion { - return false - } - } - return true -} - -// ResourceData contains information about scaffolded resources -type ResourceData struct { - Group string `json:"group,omitempty"` - Version string `json:"version,omitempty"` - Kind string `json:"kind,omitempty"` - - // API holds the API data - API *API `json:"api,omitempty"` - - // Webhooks holds the Webhooks data - Webhooks *Webhooks `json:"webhooks,omitempty"` -} - -// API contains information about scaffolded APIs -type API struct { - // CRDVersion holds the CustomResourceDefinition API version used for the ResourceData. - CRDVersion string `json:"crdVersion,omitempty"` -} - -// Webhooks contains information about scaffolded webhooks -type Webhooks struct { - // WebhookVersion holds the {Validating,Mutating}WebhookConfiguration API version used for the Options. - WebhookVersion string `json:"webhookVersion,omitempty"` -} - -// isGVKEqualTo compares it with another resource -func (r ResourceData) isGVKEqualTo(other ResourceData) bool { - return r.Group == other.Group && - r.Version == other.Version && - r.Kind == other.Kind -} - -// merge combines fields of two GVKs that have matching group, version, and kind, -// favoring the receiver's values. -func (r *ResourceData) merge(other ResourceData) { - if other.Webhooks != nil { - if r.Webhooks == nil { - r.Webhooks = other.Webhooks - } else { - r.Webhooks.merge(other.Webhooks) - } - } - - if other.API != nil { - if r.API == nil { - r.API = other.API - } else { - r.API.merge(other.API) - } - } -} - -// merge compares it with another webhook by setting each webhook type individually so existing values are -// not overwritten. -func (w *Webhooks) merge(other *Webhooks) { - if w.WebhookVersion == "" && other.WebhookVersion != "" { - w.WebhookVersion = other.WebhookVersion - } -} - -// merge compares it with another api by setting each api type individually so existing values are -// not overwritten. -func (a *API) merge(other *API) { - if a.CRDVersion == "" && other.CRDVersion != "" { - a.CRDVersion = other.CRDVersion - } -} - -// Marshal returns the bytes of c. -func (c Config) Marshal() ([]byte, error) { - // Ignore extra fields at first. - cfg := c - cfg.Plugins = nil - - // Ignore some fields if v2. - if cfg.IsV2() { - for i := range cfg.Resources { - cfg.Resources[i].API = nil - cfg.Resources[i].Webhooks = nil - } - } - - for i, r := range cfg.Resources { - // If API is empty, omit it (prevents `api: {}`). - if r.API != nil && r.API.CRDVersion == "" { - cfg.Resources[i].API = nil - } - // If Webhooks is empty, omit it (prevents `webhooks: {}`). - if r.Webhooks != nil && r.Webhooks.WebhookVersion == "" { - cfg.Resources[i].Webhooks = nil - } - } - - content, err := yaml.Marshal(cfg) - if err != nil { - return nil, fmt.Errorf("error marshalling project configuration: %v", err) - } - - // Empty config strings are "{}" due to the map field. - if strings.TrimSpace(string(content)) == "{}" { - content = []byte{} - } - - // Append extra fields to put them at the config's bottom. - if len(c.Plugins) != 0 { - // Unless the project version is v2 which does not support a plugins field. - if cfg.IsV2() { - return nil, fmt.Errorf("error marshalling project configuration: plugin field found for v2") - } - - pluginConfigBytes, err := yaml.Marshal(Config{Plugins: c.Plugins}) - if err != nil { - return nil, fmt.Errorf("error marshalling project configuration extra fields: %v", err) - } - content = append(content, pluginConfigBytes...) - } - - return content, nil -} - -// Unmarshal unmarshals the bytes of a Config into c. -func (c *Config) Unmarshal(b []byte) error { - if err := yaml.UnmarshalStrict(b, c); err != nil { - return fmt.Errorf("error unmarshalling project configuration: %v", err) - } - - // Project versions < v3 do not support a plugins field. - if !c.IsV3() { - c.Plugins = nil - } - return nil -} - -// EncodePluginConfig encodes a config object into c by overwriting the existing -// object stored under key. This method is intended to be used for custom -// configuration objects, which were introduced in project version 3-alpha. -// EncodePluginConfig will return an error if used on any project version < v3. -func (c *Config) EncodePluginConfig(key string, configObj interface{}) error { - // Short-circuit project versions < v3. - if !c.IsV3() { - return fmt.Errorf("project versions < v3 do not support extra fields") - } - - // Get object's bytes and set them under key in extra fields. - b, err := yaml.Marshal(configObj) - if err != nil { - return fmt.Errorf("failed to convert %T object to bytes: %s", configObj, err) - } - var fields map[string]interface{} - if err := yaml.Unmarshal(b, &fields); err != nil { - return fmt.Errorf("failed to unmarshal %T object bytes: %s", configObj, err) - } - if c.Plugins == nil { - c.Plugins = make(map[string]pluginConfig) - } - c.Plugins[key] = fields - return nil -} - -// DecodePluginConfig decodes a plugin config stored in c into configObj, which must be a pointer -// This method is intended to be used for custom configuration objects, which were introduced -// in project version 3-alpha. EncodePluginConfig will return an error if used on any project version < v3. -func (c Config) DecodePluginConfig(key string, configObj interface{}) error { - // Short-circuit project versions < v3. - if !c.IsV3() { - return fmt.Errorf("project versions < v3 do not support extra fields") - } - if len(c.Plugins) == 0 { - return nil - } - - // Get the object blob by key and unmarshal into the object. - if pluginConfig, hasKey := c.Plugins[key]; hasKey { - b, err := yaml.Marshal(pluginConfig) - if err != nil { - return fmt.Errorf("failed to convert extra fields object to bytes: %s", err) - } - if err := yaml.Unmarshal(b, configObj); err != nil { - return fmt.Errorf("failed to unmarshal extra fields object: %s", err) - } - } - return nil -} diff --git a/pkg/model/config/config_test.go b/pkg/model/config/config_test.go deleted file mode 100644 index 12009711998..00000000000 --- a/pkg/model/config/config_test.go +++ /dev/null @@ -1,312 +0,0 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package config - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -const v1beta1 = "v1beta1" - -var _ = Describe("PluginConfig", func() { - // Test plugin config. Don't want to export this config, but need it to - // be accessible by test. - type PluginConfig struct { - Data1 string `json:"data-1"` - Data2 string `json:"data-2"` - } - const defaultWebhookVersion = "v1" - - resource := ResourceData{Group: "Foo", Kind: "Baz", Version: "v1"} - resource.Webhooks = &Webhooks{defaultWebhookVersion} - - It("should return true when has the ResourceData is equals", func() { - Expect(resource.isGVKEqualTo(ResourceData{Group: "Foo", Kind: "Baz", Version: "v1"})).To(BeTrue()) - }) - - It("should return false when ResourceData is NOT equals", func() { - Expect(resource.isGVKEqualTo(ResourceData{Group: "Foo", Kind: "Baz", Version: "v2"})).To(BeFalse()) - }) - - It("IsV2 should return true when the config is V2", func() { - cfg := Config{Version: Version2} - Expect(cfg.IsV2()).To(BeTrue()) - }) - - It("IsV3 should return true when the config is V3", func() { - cfg := Config{Version: Version3Alpha} - Expect(cfg.IsV3()).To(BeTrue()) - }) - - It("should encode correctly", func() { - var ( - key = "plugin-x" - config Config - pluginConfig PluginConfig - expectedConfig Config - ) - - By("Using config version empty") - config = Config{} - pluginConfig = PluginConfig{} - Expect(config.EncodePluginConfig(key, pluginConfig)).NotTo(Succeed()) - - By("Using config version 2") - config = Config{Version: Version2} - pluginConfig = PluginConfig{} - Expect(config.EncodePluginConfig(key, pluginConfig)).NotTo(Succeed()) - - By("Using config version 2 with extra fields") - config = Config{Version: Version2} - pluginConfig = PluginConfig{ - Data1: "single plugin datum", - } - Expect(config.EncodePluginConfig(key, pluginConfig)).NotTo(Succeed()) - - By("Using config version 3-alpha") - config = Config{Version: Version3Alpha} - pluginConfig = PluginConfig{} - expectedConfig = Config{ - Version: Version3Alpha, - Plugins: PluginConfigs{ - "plugin-x": map[string]interface{}{ - "data-1": "", - "data-2": "", - }, - }, - } - Expect(config.EncodePluginConfig(key, pluginConfig)).To(Succeed()) - Expect(config).To(Equal(expectedConfig)) - - By("Using config version 3-alpha with extra fields as struct") - config = Config{Version: Version3Alpha} - pluginConfig = PluginConfig{ - "plugin value 1", - "plugin value 2", - } - expectedConfig = Config{ - Version: Version3Alpha, - Plugins: PluginConfigs{ - "plugin-x": map[string]interface{}{ - "data-1": "plugin value 1", - "data-2": "plugin value 2", - }, - }, - } - Expect(config.EncodePluginConfig(key, pluginConfig)).To(Succeed()) - Expect(config).To(Equal(expectedConfig)) - }) - - It("should decode correctly", func() { - var ( - key = "plugin-x" - config Config - pluginConfig PluginConfig - expectedPluginConfig PluginConfig - ) - - By("Using config version 2") - config = Config{Version: Version2} - pluginConfig = PluginConfig{} - Expect(config.DecodePluginConfig(key, &pluginConfig)).NotTo(Succeed()) - - By("Using config version 2 with extra fields") - config = Config{Version: Version2} - pluginConfig = PluginConfig{ - Data1: "single plugin datum", - } - Expect(config.DecodePluginConfig(key, &pluginConfig)).NotTo(Succeed()) - - By("Using empty config version 3-alpha") - config = Config{ - Version: Version3Alpha, - Plugins: PluginConfigs{ - "plugin-x": map[string]interface{}{}, - }, - } - pluginConfig = PluginConfig{} - expectedPluginConfig = PluginConfig{} - Expect(config.DecodePluginConfig(key, &pluginConfig)).To(Succeed()) - Expect(pluginConfig).To(Equal(expectedPluginConfig)) - - By("Using config version 3-alpha") - config = Config{Version: Version3Alpha} - pluginConfig = PluginConfig{} - expectedPluginConfig = PluginConfig{} - Expect(config.DecodePluginConfig(key, &pluginConfig)).To(Succeed()) - Expect(pluginConfig).To(Equal(expectedPluginConfig)) - - By("Using config version 3-alpha with extra fields as struct") - config = Config{ - Version: Version3Alpha, - Plugins: PluginConfigs{ - "plugin-x": map[string]interface{}{ - "data-1": "plugin value 1", - "data-2": "plugin value 2", - }, - }, - } - pluginConfig = PluginConfig{} - expectedPluginConfig = PluginConfig{ - "plugin value 1", - "plugin value 2", - } - Expect(config.DecodePluginConfig(key, &pluginConfig)).To(Succeed()) - Expect(pluginConfig).To(Equal(expectedPluginConfig)) - }) - - It("should Marshal and Unmarshal a plugin", func() { - By("Using config with extra fields as struct") - cfg := Config{ - Version: Version3Alpha, - Plugins: PluginConfigs{ - "plugin-x": map[string]interface{}{ - "data-1": "plugin value 1", - }, - }, - } - b, err := cfg.Marshal() - Expect(err).NotTo(HaveOccurred()) - Expect(string(b)).To(Equal("version: 3-alpha\nplugins:\n plugin-x:\n data-1: plugin value 1\n")) - Expect(cfg.Unmarshal(b)).To(Succeed()) - }) -}) - -var _ = Describe("ResourceData Version Compatibility", func() { - - var ( - c *Config - resource1, resource2 ResourceData - - defaultVersion = "v1" - ) - - BeforeEach(func() { - c = &Config{} - resource1 = ResourceData{Group: "example", Version: "v1", Kind: "TestKind"} - resource2 = ResourceData{Group: "example", Version: "v1", Kind: "TestKind2"} - }) - - Context("resourceAPIVersionCompatible", func() { - It("returns true for a list of empty resources", func() { - Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeTrue()) - Expect(c.resourceAPIVersionCompatible("webhook", defaultVersion)).To(BeTrue()) - }) - It("returns true for one resource with an empty version", func() { - c.Resources = []ResourceData{resource1} - Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeTrue()) - Expect(c.resourceAPIVersionCompatible("webhook", defaultVersion)).To(BeTrue()) - }) - It("returns true for one resource with matching version", func() { - resource1.API = &API{CRDVersion: defaultVersion} - resource1.Webhooks = &Webhooks{WebhookVersion: defaultVersion} - c.Resources = []ResourceData{resource1} - Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeTrue()) - Expect(c.resourceAPIVersionCompatible("webhook", defaultVersion)).To(BeTrue()) - }) - It("returns true for two resources with matching versions", func() { - resource1.API = &API{CRDVersion: defaultVersion} - resource1.Webhooks = &Webhooks{WebhookVersion: defaultVersion} - resource2.API = &API{CRDVersion: defaultVersion} - resource2.Webhooks = &Webhooks{WebhookVersion: defaultVersion} - c.Resources = []ResourceData{resource1, resource2} - Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeTrue()) - Expect(c.resourceAPIVersionCompatible("webhook", defaultVersion)).To(BeTrue()) - }) - It("returns false for one resource with a non-matching version", func() { - resource1.API = &API{CRDVersion: v1beta1} - resource1.Webhooks = &Webhooks{WebhookVersion: v1beta1} - c.Resources = []ResourceData{resource1} - Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeFalse()) - Expect(c.resourceAPIVersionCompatible("webhook", defaultVersion)).To(BeFalse()) - }) - It("returns false for two resources containing a non-matching version", func() { - resource1.API = &API{CRDVersion: v1beta1} - resource1.Webhooks = &Webhooks{WebhookVersion: v1beta1} - resource2.API = &API{CRDVersion: defaultVersion} - resource2.Webhooks = &Webhooks{WebhookVersion: defaultVersion} - c.Resources = []ResourceData{resource1, resource2} - Expect(c.resourceAPIVersionCompatible("crd", defaultVersion)).To(BeFalse()) - Expect(c.resourceAPIVersionCompatible("webhook", defaultVersion)).To(BeFalse()) - }) - }) -}) - -var _ = Describe("Config", func() { - var ( - c *Config - gvk1, gvk2, gvk3 ResourceData - ) - - BeforeEach(func() { - c = &Config{} - gvk1 = ResourceData{Group: "example", Version: "v1", Kind: "TestKind"} - gvk2 = ResourceData{Group: "example", Version: "v1", Kind: "TestKind2"} - gvk3 = ResourceData{Group: "example", Version: "v1", Kind: "TestKind", Webhooks: &Webhooks{WebhookVersion: v1beta1}} - }) - - Context("UpdateResource", func() { - It("Adds a non-existing resource", func() { - c.UpdateResources(gvk1) - Expect(c.Resources).To(Equal([]ResourceData{gvk1})) - // Update again to ensure idempotency. - c.UpdateResources(gvk1) - Expect(c.Resources).To(Equal([]ResourceData{gvk1})) - }) - It("Updates an existing resource", func() { - c.UpdateResources(gvk1) - resource := ResourceData{Group: gvk1.Group, Version: gvk1.Version, Kind: gvk1.Kind} - c.UpdateResources(resource) - Expect(c.Resources).To(Equal([]ResourceData{resource})) - }) - It("Updates an existing resource with more than one resource present", func() { - c.UpdateResources(gvk1) - c.UpdateResources(gvk2) - gvk := ResourceData{Group: gvk1.Group, Version: gvk1.Version, Kind: gvk1.Kind} - c.UpdateResources(gvk) - Expect(c.Resources).To(Equal([]ResourceData{gvk, gvk2})) - }) - }) - - Context("HasGroup", func() { - It("should return true when config has a resource with the group", func() { - c.UpdateResources(gvk1) - Expect(c.Resources).To(Equal([]ResourceData{gvk1})) - Expect(c.HasGroup(gvk1.Group)).To(BeTrue()) - }) - It("should return false when config has a resource with not the same group", func() { - c.UpdateResources(gvk1) - Expect(c.Resources).To(Equal([]ResourceData{gvk1})) - Expect(c.HasGroup("hasNot")).To(BeFalse()) - }) - - }) - - Context("HasWebhook", func() { - It("should return true when config has a webhook for the GVK", func() { - c.UpdateResources(gvk3) - Expect(c.Resources).To(Equal([]ResourceData{gvk3})) - Expect(c.HasWebhook(gvk3)).To(BeTrue()) - }) - It("should return false when config does not have a webhook for the GVK", func() { - c.UpdateResources(gvk1) - Expect(c.Resources).To(Equal([]ResourceData{gvk1})) - Expect(c.HasWebhook(gvk1)).To(BeFalse()) - }) - }) -}) diff --git a/pkg/model/resource/api.go b/pkg/model/resource/api.go new file mode 100644 index 00000000000..770171e04ae --- /dev/null +++ b/pkg/model/resource/api.go @@ -0,0 +1,64 @@ +/* +Copyright 2021 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 resource + +import ( + "fmt" +) + +// API contains information about scaffolded APIs +type API struct { + // CRDVersion holds the CustomResourceDefinition API version used for the resource. + CRDVersion string `json:"crdVersion,omitempty"` + + // Namespaced is true if the API is namespaced. + Namespaced bool `json:"namespaced,omitempty"` +} + +// Copy returns a deep copy of the API that can be safely modified without affecting the original. +func (api API) Copy() API { + // As this function doesn't use a pointer receiver, api is already a shallow copy. + // Any field that is a pointer, slice or map needs to be deep copied. + return api +} + +// Update combines fields of the APIs of two resources. +func (api *API) Update(other *API) error { + // If other is nil, nothing to merge + if other == nil { + return nil + } + + // Update the version. + if other.CRDVersion != "" { + if api.CRDVersion == "" { + api.CRDVersion = other.CRDVersion + } else if api.CRDVersion != other.CRDVersion { + return fmt.Errorf("CRD versions do not match") + } + } + + // Update the namespace. + api.Namespaced = api.Namespaced || other.Namespaced + + return nil +} + +// IsEmpty returns if the API's fields all contain zero-values. +func (api API) IsEmpty() bool { + return api.CRDVersion == "" && !api.Namespaced +} diff --git a/pkg/model/resource/api_test.go b/pkg/model/resource/api_test.go new file mode 100644 index 00000000000..52faad9a748 --- /dev/null +++ b/pkg/model/resource/api_test.go @@ -0,0 +1,127 @@ +/* +Copyright 2021 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 resource + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +//nolint:dupl +var _ = Describe("API", func() { + Context("Update", func() { + var api, other API + + It("should do nothing if provided a nil pointer", func() { + api = API{} + Expect(api.Update(nil)).To(Succeed()) + Expect(api.CRDVersion).To(Equal("")) + Expect(api.Namespaced).To(BeFalse()) + + api = API{ + CRDVersion: v1, + Namespaced: true, + } + Expect(api.Update(nil)).To(Succeed()) + Expect(api.CRDVersion).To(Equal(v1)) + Expect(api.Namespaced).To(BeTrue()) + }) + + Context("CRD version", func() { + It("should modify the CRD version if provided and not previously set", func() { + api = API{} + other = API{CRDVersion: v1} + Expect(api.Update(&other)).To(Succeed()) + Expect(api.CRDVersion).To(Equal(v1)) + }) + + It("should keep the CRD version if not provided", func() { + api = API{CRDVersion: v1} + other = API{} + Expect(api.Update(&other)).To(Succeed()) + Expect(api.CRDVersion).To(Equal(v1)) + }) + + It("should keep the CRD version if provided the same as previously set", func() { + api = API{CRDVersion: v1} + other = API{CRDVersion: v1} + Expect(api.Update(&other)).To(Succeed()) + Expect(api.CRDVersion).To(Equal(v1)) + }) + + It("should fail if previously set and provided CRD versions do not match", func() { + api = API{CRDVersion: v1} + other = API{CRDVersion: "v1beta1"} + Expect(api.Update(&other)).NotTo(Succeed()) + }) + }) + + Context("Namespaced", func() { + It("should set the namespace scope if provided and not previously set", func() { + api = API{} + other = API{Namespaced: true} + Expect(api.Update(&other)).To(Succeed()) + Expect(api.Namespaced).To(BeTrue()) + }) + + It("should keep the namespace scope if previously set", func() { + api = API{Namespaced: true} + + By("not providing it") + other = API{} + Expect(api.Update(&other)).To(Succeed()) + Expect(api.Namespaced).To(BeTrue()) + + By("providing it") + other = API{Namespaced: true} + Expect(api.Update(&other)).To(Succeed()) + Expect(api.Namespaced).To(BeTrue()) + }) + + It("should not set the namespace scope if not provided and not previously set", func() { + api = API{} + other = API{} + Expect(api.Update(&other)).To(Succeed()) + Expect(api.Namespaced).To(BeFalse()) + }) + }) + }) + + Context("IsEmpty", func() { + var ( + none = API{} + cluster = API{ + CRDVersion: v1, + } + namespaced = API{ + CRDVersion: v1, + Namespaced: true, + } + ) + + It("should return true fo an empty object", func() { + Expect(none.IsEmpty()).To(BeTrue()) + }) + + DescribeTable("should return false for non-empty objects", + func(api API) { Expect(api.IsEmpty()).To(BeFalse()) }, + Entry("cluster-scope", cluster), + Entry("namespace-scope", namespaced), + ) + }) +}) diff --git a/pkg/model/resource/gvk.go b/pkg/model/resource/gvk.go new file mode 100644 index 00000000000..feb8368887d --- /dev/null +++ b/pkg/model/resource/gvk.go @@ -0,0 +1,50 @@ +/* +Copyright 2021 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 resource + +import ( + "fmt" +) + +// GVK stores the Group - Version - Kind triplet that uniquely identifies a resource. +// In kubebuilder, the k8s fully qualified group is stored as Group and Domain to improve UX. +type GVK struct { + Group string `json:"group,omitempty"` + Domain string `json:"domain,omitempty"` + Version string `json:"version"` + Kind string `json:"kind"` +} + +// QualifiedGroup returns the fully qualified group name with the available information. +func (gvk GVK) QualifiedGroup() string { + switch "" { + case gvk.Domain: + return gvk.Group + case gvk.Group: + return gvk.Domain + default: + return fmt.Sprintf("%s.%s", gvk.Group, gvk.Domain) + } +} + +// IsEqualTo compares two GVK objects. +func (gvk GVK) IsEqualTo(other GVK) bool { + return gvk.Group == other.Group && + gvk.Domain == other.Domain && + gvk.Version == other.Version && + gvk.Kind == other.Kind +} diff --git a/pkg/model/resource/gvk_test.go b/pkg/model/resource/gvk_test.go new file mode 100644 index 00000000000..c3adf999561 --- /dev/null +++ b/pkg/model/resource/gvk_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2021 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 resource + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = Describe("GVK", func() { + const ( + group = "group" + domain = "my.domain" + version = "v1" + kind = "Kind" + ) + + Context("QualifiedGroup", func() { + DescribeTable("should return the correct string", + func(gvk GVK, qualifiedGroup string) { Expect(gvk.QualifiedGroup()).To(Equal(qualifiedGroup)) }, + Entry("fully qualified resource", GVK{Group: group, Domain: domain, Version: version, Kind: kind}, + group+"."+domain), + Entry("empty group name", GVK{Domain: domain, Version: version, Kind: kind}, domain), + Entry("empty domain", GVK{Group: group, Version: version, Kind: kind}, group), + ) + }) + + Context("IsEqualTo", func() { + var gvk = GVK{Group: group, Domain: domain, Version: version, Kind: kind} + + It("should return true for the same resource", func() { + Expect(gvk.IsEqualTo(GVK{Group: group, Domain: domain, Version: version, Kind: kind})).To(BeTrue()) + }) + + DescribeTable("should return false for different resources", + func(other GVK) { Expect(gvk.IsEqualTo(other)).To(BeFalse()) }, + Entry("different kind", GVK{Group: group, Domain: domain, Version: version, Kind: "Kind2"}), + Entry("different version", GVK{Group: group, Domain: domain, Version: "v2", Kind: kind}), + Entry("different domain", GVK{Group: group, Domain: "other.domain", Version: version, Kind: kind}), + Entry("different group", GVK{Group: "group2", Domain: domain, Version: version, Kind: kind}), + ) + }) +}) diff --git a/pkg/model/resource/options.go b/pkg/model/resource/options.go deleted file mode 100644 index b763a607ab2..00000000000 --- a/pkg/model/resource/options.go +++ /dev/null @@ -1,295 +0,0 @@ -/* -Copyright 2018 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 resource - -import ( - "fmt" - "path" - "regexp" - "strings" - - "github.com/gobuffalo/flect" - - "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" -) - -const ( - versionPattern = "^v\\d+(?:alpha\\d+|beta\\d+)?$" - groupRequired = "group cannot be empty" - versionRequired = "version cannot be empty" - kindRequired = "kind cannot be empty" -) - -var ( - versionRegex = regexp.MustCompile(versionPattern) - - coreGroups = map[string]string{ - "admission": "k8s.io", - "admissionregistration": "k8s.io", - "apps": "", - "auditregistration": "k8s.io", - "apiextensions": "k8s.io", - "authentication": "k8s.io", - "authorization": "k8s.io", - "autoscaling": "", - "batch": "", - "certificates": "k8s.io", - "coordination": "k8s.io", - "core": "", - "events": "k8s.io", - "extensions": "", - "imagepolicy": "k8s.io", - "networking": "k8s.io", - "node": "k8s.io", - "metrics": "k8s.io", - "policy": "", - "rbac.authorization": "k8s.io", - "scheduling": "k8s.io", - "setting": "k8s.io", - "storage": "k8s.io", - } -) - -// Options contains the information required to build a new Resource -type Options struct { - // Group is the API Group. Does not contain the domain. - Group string - - // Version is the API version. - Version string - - // Kind is the API Kind. - Kind string - - // Plural is the API Kind plural form. - // Optional - Plural string - - // Namespaced is true if the resource is namespaced. - Namespaced bool - - // API holds the api data - API config.API - - // Webhooks holds the webhooks data - Webhooks config.Webhooks -} - -// ValidateV2 verifies that V2 project has all the fields have valid values -func (opts *Options) ValidateV2() error { - // Check that the required flags did not get a flag as their value - // We can safely look for a '-' as the first char as none of the fields accepts it - // NOTE: We must do this for all the required flags first or we may output the wrong - // error as flags may seem to be missing because Cobra assigned them to another flag. - if strings.HasPrefix(opts.Group, "-") { - return fmt.Errorf(groupRequired) - } - if strings.HasPrefix(opts.Version, "-") { - return fmt.Errorf(versionRequired) - } - if strings.HasPrefix(opts.Kind, "-") { - return fmt.Errorf(kindRequired) - } - // Now we can check that all the required flags are not empty - if len(opts.Group) == 0 { - return fmt.Errorf(groupRequired) - } - if len(opts.Version) == 0 { - return fmt.Errorf(versionRequired) - } - if len(opts.Kind) == 0 { - return fmt.Errorf(kindRequired) - } - - // Check if the Group has a valid DNS1123 subdomain value - if err := validation.IsDNS1123Subdomain(opts.Group); err != nil { - return fmt.Errorf("group name is invalid: (%v)", err) - } - - // Check if the version follows the valid pattern - if !versionRegex.MatchString(opts.Version) { - return fmt.Errorf("version must match %s (was %s)", versionPattern, opts.Version) - } - - validationErrors := []string{} - - // require Kind to start with an uppercase character - if string(opts.Kind[0]) == strings.ToLower(string(opts.Kind[0])) { - validationErrors = append(validationErrors, "kind must start with an uppercase character") - } - - validationErrors = append(validationErrors, validation.IsDNS1035Label(strings.ToLower(opts.Kind))...) - - if len(validationErrors) != 0 { - return fmt.Errorf("invalid Kind: %#v", validationErrors) - } - - // TODO: validate plural strings if provided - - return nil -} - -// Validate verifies that all the fields have valid values -func (opts *Options) Validate() error { - // Check that the required flags did not get a flag as their value - // We can safely look for a '-' as the first char as none of the fields accepts it - // NOTE: We must do this for all the required flags first or we may output the wrong - // error as flags may seem to be missing because Cobra assigned them to another flag. - if strings.HasPrefix(opts.Version, "-") { - return fmt.Errorf(versionRequired) - } - if strings.HasPrefix(opts.Kind, "-") { - return fmt.Errorf(kindRequired) - } - // Now we can check that all the required flags are not empty - if len(opts.Version) == 0 { - return fmt.Errorf(versionRequired) - } - if len(opts.Kind) == 0 { - return fmt.Errorf(kindRequired) - } - - // Check if the Group has a valid DNS1123 subdomain value - if len(opts.Group) != 0 { - if err := validation.IsDNS1123Subdomain(opts.Group); err != nil { - return fmt.Errorf("group name is invalid: (%v)", err) - } - } - - // Check if the version follows the valid pattern - if !versionRegex.MatchString(opts.Version) { - return fmt.Errorf("version must match %s (was %s)", versionPattern, opts.Version) - } - - validationErrors := []string{} - - // require Kind to start with an uppercase character - if string(opts.Kind[0]) == strings.ToLower(string(opts.Kind[0])) { - validationErrors = append(validationErrors, "kind must start with an uppercase character") - } - - validationErrors = append(validationErrors, validation.IsDNS1035Label(strings.ToLower(opts.Kind))...) - - if len(validationErrors) != 0 { - return fmt.Errorf("invalid Kind: %#v", validationErrors) - } - - // Ensure apiVersions for k8s types are empty or valid. - for typ, apiVersion := range map[string]string{ - "CRD": opts.API.CRDVersion, - "Webhook": opts.Webhooks.WebhookVersion, - } { - switch apiVersion { - case "", "v1", "v1beta1": - default: - return fmt.Errorf("%s version must be one of: v1, v1beta1", typ) - } - } - - // TODO: validate plural strings if provided - - return nil -} - -// Data returns the ResourceData information to check against tracked resources in the configuration file -func (opts *Options) Data() config.ResourceData { - return config.ResourceData{ - Group: opts.Group, - Version: opts.Version, - Kind: opts.Kind, - API: &opts.API, - Webhooks: &opts.Webhooks, - } -} - -// safeImport returns a cleaned version of the provided string that can be used for imports -func (opts *Options) safeImport(unsafe string) string { - safe := unsafe - - // Remove dashes and dots - safe = strings.Replace(safe, "-", "", -1) - safe = strings.Replace(safe, ".", "", -1) - - return safe -} - -// NewResource creates a new resource from the options -func (opts *Options) NewResource(c *config.Config, doResource bool) *Resource { - res := opts.newResource() - - replacer := res.Replacer() - - pkg := replacer.Replace(path.Join(c.Repo, "api", "%[version]")) - if c.MultiGroup { - if opts.Group != "" { - pkg = replacer.Replace(path.Join(c.Repo, "apis", "%[group]", "%[version]")) - } else { - pkg = replacer.Replace(path.Join(c.Repo, "apis", "%[version]")) - } - } - domain := c.Domain - - // pkg and domain may need to be changed in case we are referring to a builtin core resource: - // - Check if we are scaffolding the resource now => project resource - // - Check if we already scaffolded the resource => project resource - // - Check if the resource group is a well-known core group => builtin core resource - // - In any other case, default to => project resource - // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath - if !doResource { - if c.GetResource(opts.Data()) == nil { - if coreDomain, found := coreGroups[opts.Group]; found { - pkg = replacer.Replace(path.Join("k8s.io", "api", "%[group]", "%[version]")) - domain = coreDomain - } - } - } - - res.Package = pkg - res.Domain = opts.Group - if domain != "" && opts.Group != "" { - res.Domain += "." + domain - } else if opts.Group == "" && !c.IsV2() { - // Empty group overrides the default values provided by newResource(). - // GroupPackageName and ImportAlias includes domain instead of group name as user provided group is empty. - res.Domain = domain - res.GroupPackageName = opts.safeImport(domain) - res.ImportAlias = opts.safeImport(domain + opts.Version) - } - - return res -} - -func (opts *Options) newResource() *Resource { - // If not provided, compute a plural for for Kind - plural := opts.Plural - if plural == "" { - plural = flect.Pluralize(strings.ToLower(opts.Kind)) - } - - return &Resource{ - Namespaced: opts.Namespaced, - Group: opts.Group, - GroupPackageName: opts.safeImport(opts.Group), - Version: opts.Version, - Kind: opts.Kind, - Plural: plural, - ImportAlias: opts.safeImport(opts.Group + opts.Version), - API: opts.API, - Webhooks: opts.Webhooks, - } -} diff --git a/pkg/model/resource/options_test.go b/pkg/model/resource/options_test.go deleted file mode 100644 index 65f8511a821..00000000000 --- a/pkg/model/resource/options_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package resource_test - -import ( - "strings" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo/extensions/table" - . "github.com/onsi/gomega" - - . "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" -) - -var _ = Describe("Resource Options", func() { - Describe("scaffolding an API", func() { - It("should succeed if the Options is valid", func() { - options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - }) - - It("should succeed if the Group is not specified for V3", func() { - options := &Options{Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - }) - - It("should fail if the Group is not all lowercase", func() { - options := &Options{Group: "Crew", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).NotTo(Succeed()) - Expect(options.Validate().Error()).To(ContainSubstring("group name is invalid: " + - "([a DNS-1123 subdomain must consist of lower case alphanumeric characters")) - }) - - It("should fail if the Group contains non-alpha characters", func() { - options := &Options{Group: "crew1*?", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).NotTo(Succeed()) - Expect(options.Validate().Error()).To(ContainSubstring("group name is invalid: " + - "([a DNS-1123 subdomain must consist of lower case alphanumeric characters")) - }) - - It("should fail if the Version is not specified", func() { - options := &Options{Group: "crew", Kind: "FirstMate"} - Expect(options.Validate()).NotTo(Succeed()) - Expect(options.Validate().Error()).To(ContainSubstring("version cannot be empty")) - }) - - //nolint:dupl - It("should fail if the Version does not match the version format", func() { - options := &Options{Group: "crew", Version: "1", Kind: "FirstMate"} - Expect(options.Validate()).NotTo(Succeed()) - Expect(options.Validate().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was 1)`)) - - options = &Options{Group: "crew", Version: "1beta1", Kind: "FirstMate"} - Expect(options.Validate()).NotTo(Succeed()) - Expect(options.Validate().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was 1beta1)`)) - - options = &Options{Group: "crew", Version: "a1beta1", Kind: "FirstMate"} - Expect(options.Validate()).NotTo(Succeed()) - Expect(options.Validate().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was a1beta1)`)) - - options = &Options{Group: "crew", Version: "v1beta", Kind: "FirstMate"} - Expect(options.Validate()).NotTo(Succeed()) - Expect(options.Validate().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was v1beta)`)) - - options = &Options{Group: "crew", Version: "v1beta1alpha1", Kind: "FirstMate"} - Expect(options.Validate()).NotTo(Succeed()) - Expect(options.Validate().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was v1beta1alpha1)`)) - }) - - It("should fail if the Kind is not specified", func() { - options := &Options{Group: "crew", Version: "v1"} - Expect(options.Validate()).NotTo(Succeed()) - Expect(options.Validate().Error()).To(ContainSubstring("kind cannot be empty")) - }) - - DescribeTable("valid Kind values-according to core Kubernetes", - func(kind string) { - options := &Options{Group: "crew", Kind: kind, Version: "v1"} - Expect(options.Validate()).To(Succeed()) - }, - Entry("should pass validation if Kind is camelcase", "FirstMate"), - Entry("should pass validation if Kind has more than one caps at the start", "FIRSTMate"), - ) - - It("should fail if Kind is too long", func() { - kind := strings.Repeat("a", 64) - - options := &Options{Group: "crew", Kind: kind, Version: "v1"} - err := options.Validate() - Expect(err).To(MatchError(ContainSubstring("must be no more than 63 characters"))) - }) - - DescribeTable("invalid Kind values-according to core Kubernetes", - func(kind string) { - options := &Options{Group: "crew", Kind: kind, Version: "v1"} - Expect(options.Validate()).To(MatchError( - ContainSubstring("a DNS-1035 label must consist of lower case alphanumeric characters"))) - }, - Entry("should fail validation if Kind contains whitespaces", "Something withSpaces"), - Entry("should fail validation if Kind ends in -", "KindEndingIn-"), - Entry("should fail validation if Kind starts with number", "0ValidityKind"), - ) - - It("should fail if Kind starts with a lowercase character", func() { - options := &Options{Group: "crew", Kind: "lOWERCASESTART", Version: "v1"} - err := options.Validate() - Expect(err).To(MatchError(ContainSubstring("kind must start with an uppercase character"))) - }) - }) - - // We are duplicating the test cases for ValidateV2 with the Validate(). This test cases will be removed when - // the V2 will no longer be supported. - Describe("scaffolding an API for V2", func() { - It("should succeed if the Options is valid for V2", func() { - options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"} - Expect(options.ValidateV2()).To(Succeed()) - }) - - It("should not succeed if the Group is not specified for V2", func() { - options := &Options{Version: "v1", Kind: "FirstMate"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring("group cannot be empty")) - }) - - It("should fail if the Group is not all lowercase for V2", func() { - options := &Options{Group: "Crew", Version: "v1", Kind: "FirstMate"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring("group name is invalid: " + - "([a DNS-1123 subdomain must consist of lower case alphanumeric characters")) - }) - - It("should fail if the Group contains non-alpha characters for V2", func() { - options := &Options{Group: "crew1*?", Version: "v1", Kind: "FirstMate"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring("group name is invalid: " + - "([a DNS-1123 subdomain must consist of lower case alphanumeric characters")) - }) - - It("should fail if the Version is not specified for V2", func() { - options := &Options{Group: "crew", Kind: "FirstMate"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring("version cannot be empty")) - }) - //nolint:dupl - It("should fail if the Version does not match the version format for V2", func() { - options := &Options{Group: "crew", Version: "1", Kind: "FirstMate"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was 1)`)) - - options = &Options{Group: "crew", Version: "1beta1", Kind: "FirstMate"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was 1beta1)`)) - - options = &Options{Group: "crew", Version: "a1beta1", Kind: "FirstMate"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was a1beta1)`)) - - options = &Options{Group: "crew", Version: "v1beta", Kind: "FirstMate"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was v1beta)`)) - - options = &Options{Group: "crew", Version: "v1beta1alpha1", Kind: "FirstMate"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring( - `version must match ^v\d+(?:alpha\d+|beta\d+)?$ (was v1beta1alpha1)`)) - }) - - It("should fail if the Kind is not specified for V2", func() { - options := &Options{Group: "crew", Version: "v1"} - Expect(options.ValidateV2()).NotTo(Succeed()) - Expect(options.ValidateV2().Error()).To(ContainSubstring("kind cannot be empty")) - }) - - DescribeTable("valid Kind values-according to core Kubernetes for V2", - func(kind string) { - options := &Options{Group: "crew", Kind: kind, Version: "v1"} - Expect(options.ValidateV2()).To(Succeed()) - }, - Entry("should pass validation if Kind is camelcase", "FirstMate"), - Entry("should pass validation if Kind has more than one caps at the start", "FIRSTMate"), - ) - - It("should fail if Kind is too long for V2", func() { - kind := strings.Repeat("a", 64) - - options := &Options{Group: "crew", Kind: kind, Version: "v1"} - err := options.ValidateV2() - Expect(err).To(MatchError(ContainSubstring("must be no more than 63 characters"))) - }) - - DescribeTable("invalid Kind values-according to core Kubernetes for V2", - func(kind string) { - options := &Options{Group: "crew", Kind: kind, Version: "v1"} - Expect(options.ValidateV2()).To(MatchError( - ContainSubstring("a DNS-1035 label must consist of lower case alphanumeric characters"))) - }, - Entry("should fail validation if Kind contains whitespaces", "Something withSpaces"), - Entry("should fail validation if Kind ends in -", "KindEndingIn-"), - Entry("should fail validation if Kind starts with number", "0ValidityKind"), - ) - - It("should fail if Kind starts with a lowercase character for V2", func() { - options := &Options{Group: "crew", Kind: "lOWERCASESTART", Version: "v1"} - err := options.ValidateV2() - Expect(err).To(MatchError(ContainSubstring("kind must start with an uppercase character"))) - }) - }) -}) diff --git a/pkg/model/resource/resource.go b/pkg/model/resource/resource.go index ce3e223411d..a9237fa7a4a 100644 --- a/pkg/model/resource/resource.go +++ b/pkg/model/resource/resource.go @@ -19,55 +19,123 @@ package resource import ( "fmt" "strings" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" ) // Resource contains the information required to scaffold files for a resource. type Resource struct { - // Group is the API Group. Does not contain the domain. - Group string `json:"group,omitempty"` + // GVK contains the resource's Group-Version-Kind triplet. + GVK `json:",inline"` + + // Plural is the resource's kind plural form. + Plural string `json:"plural,omitempty"` - // GroupPackageName is the API Group cleaned to be used as the package name. - GroupPackageName string `json:"-"` + // Path is the path to the go package where the types are defined. + Path string `json:"path,omitempty"` - // Version is the API version. - Version string `json:"version,omitempty"` + // API holds the information related to the resource API. + API *API `json:"api,omitempty"` - // Kind is the API Kind. - Kind string `json:"kind,omitempty"` + // Controller specifies if a controller has been scaffolded. + Controller bool `json:"controller,omitempty"` - // Plural is the API Kind plural form. - Plural string `json:"plural,omitempty"` + // Webhooks holds the information related to the associated webhooks. + Webhooks *Webhooks `json:"webhooks,omitempty"` +} + +// PackageName returns a name valid to be used por go packages. +func (r Resource) PackageName() string { + if r.Group == "" { + return safeImport(r.Domain) + } + + return safeImport(r.Group) +} + +// ImportAlias returns a identifier usable as an import alias for this resource. +func (r Resource) ImportAlias() string { + if r.Group == "" { + return safeImport(r.Domain + r.Version) + } - // ImportAlias is a cleaned concatenation of Group and Version. - ImportAlias string `json:"-"` + return safeImport(r.Group + r.Version) +} - // Package is the go package of the Resource. - Package string `json:"package,omitempty"` +// HasAPI returns true if the resource has an associated API. +func (r Resource) HasAPI() bool { + return r.API != nil && r.API.CRDVersion != "" +} - // Domain is the Group + "." + Domain of the Resource. - Domain string `json:"domain,omitempty"` +// HasController returns true if the resource has an associated controller. +func (r Resource) HasController() bool { + return r.Controller +} - // Namespaced is true if the resource is namespaced. - Namespaced bool `json:"namespaced,omitempty"` +// HasDefaultingWebhook returns true if the resource has an associated defaulting webhook. +func (r Resource) HasDefaultingWebhook() bool { + return r.Webhooks != nil && r.Webhooks.Defaulting +} - // API holds the the api data that is scaffolded - API config.API `json:"api,omitempty"` +// HasValidationWebhook returns true if the resource has an associated validation webhook. +func (r Resource) HasValidationWebhook() bool { + return r.Webhooks != nil && r.Webhooks.Validation +} - // Webhooks holds webhooks data that is scaffolded - Webhooks config.Webhooks `json:"webhooks,omitempty"` +// HasConversionWebhook returns true if the resource has an associated conversion webhook. +func (r Resource) HasConversionWebhook() bool { + return r.Webhooks != nil && r.Webhooks.Conversion } -// Data returns the ResourceData information to check against tracked resources in the configuration file -func (r *Resource) Data() config.ResourceData { - return config.ResourceData{ - Group: r.Group, - Version: r.Version, - Kind: r.Kind, - API: &r.API, - Webhooks: &r.Webhooks, +// Copy returns a deep copy of the Resource that can be safely modified without affecting the original. +func (r Resource) Copy() Resource { + // As this function doesn't use a pointer receiver, r is already a shallow copy. + // Any field that is a pointer, slice or map needs to be deep copied. + if r.API != nil { + api := r.API.Copy() + r.API = &api } + if r.Webhooks != nil { + webhooks := r.Webhooks.Copy() + r.Webhooks = &webhooks + } + return r +} + +// Update combines fields of two resources that have matching GVK favoring the receiver's values. +func (r *Resource) Update(other Resource) error { + // If self is nil, return an error + if r == nil { + return fmt.Errorf("unable to update a nil Resource") + } + + // Make sure we are not merging resources for different GVKs. + if !r.GVK.IsEqualTo(other.GVK) { + return fmt.Errorf("unable to update a Resource with another with non-matching GVK") + } + + // TODO: currently Plural & Path will always match. In the future, this may not be true (e.g. providing a + // --plural flag). In that case, we should yield an error in case of updating two resources with different + // values for these fields. + + // Update API. + if r.API == nil && other.API != nil { + r.API = &API{} + } + if err := r.API.Update(other.API); err != nil { + return err + } + + // Update controller. + r.Controller = r.Controller || other.Controller + + // Update Webhooks. + if r.Webhooks == nil && other.Webhooks != nil { + r.Webhooks = &Webhooks{} + } + if err := r.Webhooks.Update(other.Webhooks); err != nil { + return err + } + + return nil } func wrapKey(key string) string { @@ -79,10 +147,10 @@ func (r Resource) Replacer() *strings.Replacer { var replacements []string replacements = append(replacements, wrapKey("group"), r.Group) - replacements = append(replacements, wrapKey("group-package-name"), r.GroupPackageName) replacements = append(replacements, wrapKey("version"), r.Version) replacements = append(replacements, wrapKey("kind"), strings.ToLower(r.Kind)) replacements = append(replacements, wrapKey("plural"), strings.ToLower(r.Plural)) + replacements = append(replacements, wrapKey("package-name"), r.PackageName()) return strings.NewReplacer(replacements...) } diff --git a/pkg/model/resource/resource_test.go b/pkg/model/resource/resource_test.go index b213511e371..b0bd7fd7f8e 100644 --- a/pkg/model/resource/resource_test.go +++ b/pkg/model/resource/resource_test.go @@ -14,248 +14,429 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resource_test +package resource import ( - "path" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" - - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" - . "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) +//nolint:dupl var _ = Describe("Resource", func() { - Describe("scaffolding an API", func() { - It("should succeed if the Resource is valid", func() { - options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - - resource := options.NewResource( - &config.Config{ - Version: config.Version2, - Domain: "test.io", - Repo: "test", - }, - true, - ) - Expect(resource.Namespaced).To(Equal(options.Namespaced)) - Expect(resource.Group).To(Equal(options.Group)) - Expect(resource.GroupPackageName).To(Equal("crew")) - Expect(resource.Version).To(Equal(options.Version)) - Expect(resource.Kind).To(Equal(options.Kind)) - Expect(resource.Plural).To(Equal("firstmates")) - Expect(resource.ImportAlias).To(Equal("crewv1")) - Expect(resource.Package).To(Equal(path.Join("test", "api", "v1"))) - Expect(resource.Domain).To(Equal("crew.test.io")) - - resource = options.NewResource( - &config.Config{ - Version: config.Version2, - Domain: "test.io", - Repo: "test", - MultiGroup: true, - }, - true, - ) - Expect(resource.Namespaced).To(Equal(options.Namespaced)) - Expect(resource.Group).To(Equal(options.Group)) - Expect(resource.GroupPackageName).To(Equal("crew")) - Expect(resource.Version).To(Equal(options.Version)) - Expect(resource.Kind).To(Equal(options.Kind)) - Expect(resource.Plural).To(Equal("firstmates")) - Expect(resource.ImportAlias).To(Equal("crewv1")) - Expect(resource.Package).To(Equal(path.Join("test", "apis", "crew", "v1"))) - Expect(resource.Domain).To(Equal("crew.test.io")) - }) - - It("should default the Plural by pluralizing the Kind", func() { - singleGroupConfig := &config.Config{ - Version: config.Version2, - } - multiGroupConfig := &config.Config{ - Version: config.Version2, - MultiGroup: true, - } - - options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - - resource := options.NewResource(singleGroupConfig, true) - Expect(resource.Plural).To(Equal("firstmates")) - - resource = options.NewResource(multiGroupConfig, true) - Expect(resource.Plural).To(Equal("firstmates")) + const ( + group = "group" + domain = "test.io" + version = "v1" + kind = "Kind" + ) + + var ( + res1 = Resource{ + GVK: GVK{ + Group: group, + Domain: domain, + Version: version, + Kind: kind, + }, + } + res2 = Resource{ + GVK: GVK{ + // Empty group + Domain: domain, + Version: version, + Kind: kind, + }, + } + res3 = Resource{ + GVK: GVK{ + Group: group, + // Empty domain + Version: version, + Kind: kind, + }, + } + ) + + Context("compound field", func() { + const ( + safeDomain = "testio" + groupVersion = group + version + domainVersion = safeDomain + version + ) + + DescribeTable("PackageName should return the correct string", + func(res Resource, packageName string) { Expect(res.PackageName()).To(Equal(packageName)) }, + Entry("fully qualified resource", res1, group), + Entry("empty group name", res2, safeDomain), + Entry("empty domain", res3, group), + ) + + DescribeTable("ImportAlias", + func(res Resource, importAlias string) { Expect(res.ImportAlias()).To(Equal(importAlias)) }, + Entry("fully qualified resource", res1, groupVersion), + Entry("empty group name", res2, domainVersion), + Entry("empty domain", res3, groupVersion), + ) + }) - options = &Options{Group: "crew", Version: "v1", Kind: "Fish"} - Expect(options.Validate()).To(Succeed()) + Context("part check", func() { + Context("HasAPI", func() { + It("should return true if the API is scaffolded", func() { + Expect(Resource{API: &API{CRDVersion: "v1"}}.HasAPI()).To(BeTrue()) + }) - resource = options.NewResource(singleGroupConfig, true) - Expect(resource.Plural).To(Equal("fish")) + DescribeTable("should return false if the API is not scaffolded", + func(res Resource) { Expect(res.HasAPI()).To(BeFalse()) }, + Entry("nil API", Resource{API: nil}), + Entry("empty CRD version", Resource{API: &API{}}), + ) + }) - resource = options.NewResource(multiGroupConfig, true) - Expect(resource.Plural).To(Equal("fish")) + Context("HasController", func() { + It("should return true if the controller is scaffolded", func() { + Expect(Resource{Controller: true}.HasController()).To(BeTrue()) + }) - options = &Options{Group: "crew", Version: "v1", Kind: "Helmswoman"} - Expect(options.Validate()).To(Succeed()) + It("should return false if the controller is not scaffolded", func() { + Expect(Resource{Controller: false}.HasController()).To(BeFalse()) + }) + }) - resource = options.NewResource(singleGroupConfig, true) - Expect(resource.Plural).To(Equal("helmswomen")) + Context("HasDefaultingWebhook", func() { + It("should return true if the defaulting webhook is scaffolded", func() { + Expect(Resource{Webhooks: &Webhooks{Defaulting: true}}.HasDefaultingWebhook()).To(BeTrue()) + }) - resource = options.NewResource(multiGroupConfig, true) - Expect(resource.Plural).To(Equal("helmswomen")) + DescribeTable("should return false if the defaulting webhook is not scaffolded", + func(res Resource) { Expect(res.HasDefaultingWebhook()).To(BeFalse()) }, + Entry("nil webhooks", Resource{Webhooks: nil}), + Entry("no defaulting", Resource{Webhooks: &Webhooks{Defaulting: false}}), + ) }) - It("should keep the Plural if specified", func() { - options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate", Plural: "mates"} - Expect(options.Validate()).To(Succeed()) + Context("HasValidationWebhook", func() { + It("should return true if the validation webhook is scaffolded", func() { + Expect(Resource{Webhooks: &Webhooks{Validation: true}}.HasValidationWebhook()).To(BeTrue()) + }) - resource := options.NewResource( - &config.Config{ - Version: config.Version2, - }, - true, + DescribeTable("should return false if the validation webhook is not scaffolded", + func(res Resource) { Expect(res.HasValidationWebhook()).To(BeFalse()) }, + Entry("nil webhooks", Resource{Webhooks: nil}), + Entry("no validation", Resource{Webhooks: &Webhooks{Validation: false}}), ) - Expect(resource.Plural).To(Equal("mates")) + }) - resource = options.NewResource( - &config.Config{ - Version: config.Version2, - MultiGroup: true, - }, - true, + Context("HasConversionWebhook", func() { + It("should return true if the conversion webhook is scaffolded", func() { + Expect(Resource{Webhooks: &Webhooks{Conversion: true}}.HasConversionWebhook()).To(BeTrue()) + }) + + DescribeTable("should return false if the conversion webhook is not scaffolded", + func(res Resource) { Expect(res.HasConversionWebhook()).To(BeFalse()) }, + Entry("nil webhooks", Resource{Webhooks: nil}), + Entry("no conversion", Resource{Webhooks: &Webhooks{Conversion: false}}), ) - Expect(resource.Plural).To(Equal("mates")) }) + }) - It("should allow hyphens and dots in group names", func() { - singleGroupConfig := &config.Config{ - Version: config.Version2, - Domain: "test.io", - Repo: "test", - } - multiGroupConfig := &config.Config{ - Version: config.Version2, - Domain: "test.io", - Repo: "test", - MultiGroup: true, - } - - options := &Options{Group: "my-project", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - - resource := options.NewResource(singleGroupConfig, true) - - Expect(resource.Group).To(Equal(options.Group)) - Expect(resource.GroupPackageName).To(Equal("myproject")) - Expect(resource.ImportAlias).To(Equal("myprojectv1")) - Expect(resource.Package).To(Equal(path.Join("test", "api", "v1"))) - Expect(resource.Domain).To(Equal("my-project.test.io")) - - resource = options.NewResource(multiGroupConfig, true) - Expect(resource.Group).To(Equal(options.Group)) - Expect(resource.GroupPackageName).To(Equal("myproject")) - Expect(resource.ImportAlias).To(Equal("myprojectv1")) - Expect(resource.Package).To(Equal(path.Join("test", "apis", "my-project", "v1"))) - Expect(resource.Domain).To(Equal("my-project.test.io")) - - options = &Options{Group: "my.project", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - - resource = options.NewResource(singleGroupConfig, true) - Expect(resource.Group).To(Equal(options.Group)) - Expect(resource.GroupPackageName).To(Equal("myproject")) - Expect(resource.ImportAlias).To(Equal("myprojectv1")) - Expect(resource.Package).To(Equal(path.Join("test", "api", "v1"))) - Expect(resource.Domain).To(Equal("my.project.test.io")) - - resource = options.NewResource(multiGroupConfig, true) - Expect(resource.Group).To(Equal(options.Group)) - Expect(resource.GroupPackageName).To(Equal("myproject")) - Expect(resource.ImportAlias).To(Equal("myprojectv1")) - Expect(resource.Package).To(Equal(path.Join("test", "apis", "my.project", "v1"))) - Expect(resource.Domain).To(Equal("my.project.test.io")) + Context("Copy", func() { + const ( + plural = "kinds" + path = "api/v1" + crdVersion = "v1" + webhookVersion = "v1" + ) + + res := Resource{ + GVK: GVK{ + Group: group, + Domain: domain, + Version: version, + Kind: kind, + }, + Plural: plural, + Path: path, + API: &API{ + CRDVersion: crdVersion, + Namespaced: true, + }, + Controller: true, + Webhooks: &Webhooks{ + WebhookVersion: webhookVersion, + Defaulting: true, + Validation: true, + Conversion: true, + }, + } + + It("should return an exact copy", func() { + other := res.Copy() + Expect(other.Group).To(Equal(res.Group)) + Expect(other.Domain).To(Equal(res.Domain)) + Expect(other.Version).To(Equal(res.Version)) + Expect(other.Kind).To(Equal(res.Kind)) + Expect(other.Plural).To(Equal(res.Plural)) + Expect(other.Path).To(Equal(res.Path)) + Expect(other.API).NotTo(BeNil()) + Expect(other.API.CRDVersion).To(Equal(res.API.CRDVersion)) + Expect(other.API.Namespaced).To(Equal(res.API.Namespaced)) + Expect(other.Controller).To(Equal(res.Controller)) + Expect(other.Webhooks).NotTo(BeNil()) + Expect(other.Webhooks.WebhookVersion).To(Equal(res.Webhooks.WebhookVersion)) + Expect(other.Webhooks.Defaulting).To(Equal(res.Webhooks.Defaulting)) + Expect(other.Webhooks.Validation).To(Equal(res.Webhooks.Validation)) + Expect(other.Webhooks.Conversion).To(Equal(res.Webhooks.Conversion)) }) - It("should not append '.' if provided an empty domain", func() { - options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) + It("modifying the copy should not affect the original", func() { + other := res.Copy() + other.Group = "group2" + other.Domain = "other.domain" + other.Version = "v2" + other.Kind = "kind2" + other.Plural = "kind2s" + other.Path = "api/v2" + other.API.CRDVersion = "v1beta1" + other.API.Namespaced = false + other.API = nil // Change fields before changing pointer + other.Controller = false + other.Webhooks.WebhookVersion = "v1beta1" + other.Webhooks.Defaulting = false + other.Webhooks.Validation = false + other.Webhooks.Conversion = false + other.Webhooks = nil // Change fields before changing pointer + + Expect(res.Group).To(Equal(group)) + Expect(res.Domain).To(Equal(domain)) + Expect(res.Version).To(Equal(version)) + Expect(res.Kind).To(Equal(kind)) + Expect(res.Plural).To(Equal(plural)) + Expect(res.Path).To(Equal(path)) + Expect(res.API).NotTo(BeNil()) + Expect(res.API.CRDVersion).To(Equal(crdVersion)) + Expect(res.API.Namespaced).To(BeTrue()) + Expect(res.Controller).To(BeTrue()) + Expect(res.Webhooks).NotTo(BeNil()) + Expect(res.Webhooks.WebhookVersion).To(Equal(webhookVersion)) + Expect(res.Webhooks.Defaulting).To(BeTrue()) + Expect(res.Webhooks.Validation).To(BeTrue()) + Expect(res.Webhooks.Conversion).To(BeTrue()) + }) + }) - resource := options.NewResource( - &config.Config{ - Version: config.Version2, - }, - true, - ) - Expect(resource.Domain).To(Equal("crew")) + Context("Update", func() { + var r, other Resource - resource = options.NewResource( - &config.Config{ - Version: config.Version2, - MultiGroup: true, - }, - true, - ) - Expect(resource.Domain).To(Equal("crew")) + It("should fail for nil objects", func() { + var nilResource *Resource + Expect(nilResource.Update(other)).NotTo(Succeed()) }) - It("should use core apis", func() { - singleGroupConfig := &config.Config{ - Version: config.Version2, - Domain: "test.io", - Repo: "test", + It("should fail for different GVKs", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, } - multiGroupConfig := &config.Config{ - Version: config.Version2, - Domain: "test.io", - Repo: "test", - MultiGroup: true, + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: "OtherKind", + }, } + Expect(r.Update(other)).NotTo(Succeed()) + }) - options := &Options{Group: "apps", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - - resource := options.NewResource(singleGroupConfig, false) - Expect(resource.Package).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.Domain).To(Equal("apps")) - - resource = options.NewResource(multiGroupConfig, false) - Expect(resource.Package).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.Domain).To(Equal("apps")) - - options = &Options{Group: "authentication", Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - - resource = options.NewResource(singleGroupConfig, false) - Expect(resource.Package).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.Domain).To(Equal("authentication.k8s.io")) + Context("API", func() { + It("should work with nil APIs", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + } + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + API: &API{CRDVersion: v1}, + } + Expect(r.Update(other)).To(Succeed()) + Expect(r.API).NotTo(BeNil()) + Expect(r.API.CRDVersion).To(Equal(v1)) + }) + + It("should fail if API.Update fails", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + API: &API{CRDVersion: v1}, + } + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + API: &API{CRDVersion: "v1beta1"}, + } + Expect(r.Update(other)).NotTo(Succeed()) + }) + + // The rest of the cases are tested in API.Update + }) - resource = options.NewResource(multiGroupConfig, false) - Expect(resource.Package).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.Domain).To(Equal("authentication.k8s.io")) + Context("Controller", func() { + It("should set the controller flag if provided and not previously set", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + } + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Controller: true, + } + Expect(r.Update(other)).To(Succeed()) + Expect(r.Controller).To(BeTrue()) + }) + + It("should keep the controller flag if previously set", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Controller: true, + } + + By("not providing it") + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + } + Expect(r.Update(other)).To(Succeed()) + Expect(r.Controller).To(BeTrue()) + + By("providing it") + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Controller: true, + } + Expect(r.Update(other)).To(Succeed()) + Expect(r.Controller).To(BeTrue()) + }) + + It("should not set the controller flag if not provided and not previously set", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + } + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + } + Expect(r.Update(other)).To(Succeed()) + Expect(r.Controller).To(BeFalse()) + }) }) - It("should use domain if the group is empty for version v3", func() { - options := &Options{Version: "v1", Kind: "FirstMate"} - Expect(options.Validate()).To(Succeed()) - - resource := options.NewResource( - &config.Config{ - Version: config.Version3Alpha, - Domain: "test.io", - Repo: "test", - }, - true, - ) - Expect(resource.Namespaced).To(Equal(options.Namespaced)) - Expect(resource.Group).To(Equal("")) - Expect(resource.GroupPackageName).To(Equal("testio")) - Expect(resource.Version).To(Equal(options.Version)) - Expect(resource.Kind).To(Equal(options.Kind)) - Expect(resource.Plural).To(Equal("firstmates")) - Expect(resource.ImportAlias).To(Equal("testiov1")) - Expect(resource.Package).To(Equal(path.Join("test", "api", "v1"))) - Expect(resource.Domain).To(Equal("test.io")) + + Context("Webhooks", func() { + It("should work with nil Webhooks", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + } + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Webhooks: &Webhooks{WebhookVersion: v1}, + } + Expect(r.Update(other)).To(Succeed()) + Expect(r.Webhooks).NotTo(BeNil()) + Expect(r.Webhooks.WebhookVersion).To(Equal(v1)) + }) + + It("should fail if Webhooks.Update fails", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Webhooks: &Webhooks{WebhookVersion: v1}, + } + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Webhooks: &Webhooks{WebhookVersion: "v1beta1"}, + } + Expect(r.Update(other)).NotTo(Succeed()) + }) + + // The rest of the cases are tested in Webhooks.Update }) }) + + Context("Replacer", func() { + res := Resource{ + GVK: GVK{ + Group: group, + Domain: domain, + Version: version, + Kind: kind, + }, + Plural: "kinds", + } + replacer := res.Replacer() + + DescribeTable("should replace the following strings", + func(pattern, result string) { Expect(replacer.Replace(pattern)).To(Equal(result)) }, + Entry("no pattern", "version", "version"), + Entry("pattern `%[group]`", "%[group]", res.Group), + Entry("pattern `%[version]`", "%[version]", res.Version), + Entry("pattern `%[kind]`", "%[kind]", "kind"), + Entry("pattern `%[plural]`", "%[plural]", res.Plural), + Entry("pattern `%[package-name]`", "%[package-name]", res.PackageName()), + ) + }) }) diff --git a/pkg/model/resource/resource_suite_test.go b/pkg/model/resource/suite_test.go similarity index 95% rename from pkg/model/resource/resource_suite_test.go rename to pkg/model/resource/suite_test.go index 9f897cb6ce2..eae9ac5c2d8 100644 --- a/pkg/model/resource/resource_suite_test.go +++ b/pkg/model/resource/suite_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package resource_test +package resource import ( "testing" @@ -23,6 +23,8 @@ import ( . "github.com/onsi/gomega" ) +const v1 = "v1" + func TestResource(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Resource Suite") diff --git a/pkg/model/resource/utils.go b/pkg/model/resource/utils.go new file mode 100644 index 00000000000..5d4d4c23516 --- /dev/null +++ b/pkg/model/resource/utils.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 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 resource + +import ( + "path" + "strings" + + "github.com/gobuffalo/flect" +) + +// safeImport returns a cleaned version of the provided string that can be used for imports +func safeImport(unsafe string) string { + safe := unsafe + + // Remove dashes and dots + safe = strings.Replace(safe, "-", "", -1) + safe = strings.Replace(safe, ".", "", -1) + + return safe +} + +// APIPackagePath returns the default path +func APIPackagePath(repo, group, version string, multiGroup bool) string { + if multiGroup { + if group != "" { + return path.Join(repo, "apis", group, version) + } + return path.Join(repo, "apis", version) + } + return path.Join(repo, "api", version) +} + +// RegularPlural returns a default plural form when none was specified +func RegularPlural(singular string) string { + return flect.Pluralize(strings.ToLower(singular)) +} diff --git a/pkg/model/resource/utils_test.go b/pkg/model/resource/utils_test.go new file mode 100644 index 00000000000..229262ea8ef --- /dev/null +++ b/pkg/model/resource/utils_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2021 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 resource + +import ( + "path" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +var _ = DescribeTable("safeImport should remove unsupported characters", + func(unsafe, safe string) { Expect(safeImport(unsafe)).To(Equal(safe)) }, + Entry("no dots nor dashes", "text", "text"), + Entry("one dot", "my.domain", "mydomain"), + Entry("several dots", "example.my.domain", "examplemydomain"), + Entry("one dash", "example-text", "exampletext"), + Entry("several dashes", "other-example-text", "otherexampletext"), + Entry("both dots and dashes", "my-example.my.domain", "myexamplemydomain"), +) + +var _ = Describe("APIPackagePath", func() { + const ( + repo = "github.com/kubernetes-sigs/kubebuilder" + group = "group" + version = "v1" + ) + + DescribeTable("should work", + func(repo, group, version string, multiGroup bool, p string) { + Expect(APIPackagePath(repo, group, version, multiGroup)).To(Equal(p)) + }, + Entry("single group setup", repo, group, version, false, path.Join(repo, "api", version)), + Entry("multiple group setup", repo, group, version, true, path.Join(repo, "apis", group, version)), + Entry("multiple group setup with empty group", repo, "", version, true, path.Join(repo, "apis", version)), + ) +}) + +var _ = DescribeTable("RegularPlural should return the regular plural form", + func(singular, plural string) { Expect(RegularPlural(singular)).To(Equal(plural)) }, + Entry("basic singular", "firstmate", "firstmates"), + Entry("capitalized singular", "Firstmate", "firstmates"), + Entry("camel-cased singular", "FirstMate", "firstmates"), + Entry("irregular well-known plurals", "fish", "fish"), + Entry("irregular well-known plurals", "helmswoman", "helmswomen"), +) diff --git a/pkg/model/resource/webhooks.go b/pkg/model/resource/webhooks.go new file mode 100644 index 00000000000..1fa745bdbf3 --- /dev/null +++ b/pkg/model/resource/webhooks.go @@ -0,0 +1,76 @@ +/* +Copyright 2021 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 resource + +import ( + "fmt" +) + +// Webhooks contains information about scaffolded webhooks +type Webhooks struct { + // WebhookVersion holds the {Validating,Mutating}WebhookConfiguration API version used for the resource. + WebhookVersion string `json:"webhookVersion,omitempty"` + + // Defaulting specifies if a defaulting webhook is associated to the resource. + Defaulting bool `json:"defaulting,omitempty"` + + // Validation specifies if a validation webhook is associated to the resource. + Validation bool `json:"validation,omitempty"` + + // Conversion specifies if a conversion webhook is associated to the resource. + Conversion bool `json:"conversion,omitempty"` +} + +// Copy returns a deep copy of the API that can be safely modified without affecting the original. +func (webhooks Webhooks) Copy() Webhooks { + // As this function doesn't use a pointer receiver, webhooks is already a shallow copy. + // Any field that is a pointer, slice or map needs to be deep copied. + return webhooks +} + +// Update combines fields of the webhooks of two resources. +func (webhooks *Webhooks) Update(other *Webhooks) error { + // If other is nil, nothing to merge + if other == nil { + return nil + } + + // Update the version. + if other.WebhookVersion != "" { + if webhooks.WebhookVersion == "" { + webhooks.WebhookVersion = other.WebhookVersion + } else if webhooks.WebhookVersion != other.WebhookVersion { + return fmt.Errorf("webhook versions do not match") + } + } + + // Update defaulting. + webhooks.Defaulting = webhooks.Defaulting || other.Defaulting + + // Update validation. + webhooks.Validation = webhooks.Validation || other.Validation + + // Update conversion. + webhooks.Conversion = webhooks.Conversion || other.Conversion + + return nil +} + +// IsEmpty returns if the Webhooks' fields all contain zero-values. +func (webhooks Webhooks) IsEmpty() bool { + return webhooks.WebhookVersion == "" && !webhooks.Defaulting && !webhooks.Validation && !webhooks.Conversion +} diff --git a/pkg/model/resource/webhooks_test.go b/pkg/model/resource/webhooks_test.go new file mode 100644 index 00000000000..f8ea6858f25 --- /dev/null +++ b/pkg/model/resource/webhooks_test.go @@ -0,0 +1,233 @@ +/* +Copyright 2021 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 resource + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +//nolint:dupl +var _ = Describe("Webhooks", func() { + Context("Update", func() { + var webhook, other Webhooks + + It("should do nothing if provided a nil pointer", func() { + webhook = Webhooks{} + Expect(webhook.Update(nil)).To(Succeed()) + Expect(webhook.WebhookVersion).To(Equal("")) + Expect(webhook.Defaulting).To(BeFalse()) + Expect(webhook.Validation).To(BeFalse()) + Expect(webhook.Conversion).To(BeFalse()) + + webhook = Webhooks{ + WebhookVersion: v1, + Defaulting: true, + Validation: true, + Conversion: true, + } + Expect(webhook.Update(nil)).To(Succeed()) + Expect(webhook.WebhookVersion).To(Equal(v1)) + Expect(webhook.Defaulting).To(BeTrue()) + Expect(webhook.Validation).To(BeTrue()) + Expect(webhook.Conversion).To(BeTrue()) + }) + + Context("webhooks version", func() { + It("should modify the webhooks version if provided and not previously set", func() { + webhook = Webhooks{} + other = Webhooks{WebhookVersion: v1} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.WebhookVersion).To(Equal(v1)) + }) + + It("should keep the webhooks version if not provided", func() { + webhook = Webhooks{WebhookVersion: v1} + other = Webhooks{} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.WebhookVersion).To(Equal(v1)) + }) + + It("should keep the webhooks version if provided the same as previously set", func() { + webhook = Webhooks{WebhookVersion: v1} + other = Webhooks{WebhookVersion: v1} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.WebhookVersion).To(Equal(v1)) + }) + + It("should fail if previously set and provided webhooks versions do not match", func() { + webhook = Webhooks{WebhookVersion: v1} + other = Webhooks{WebhookVersion: "v1beta1"} + Expect(webhook.Update(&other)).NotTo(Succeed()) + }) + }) + + Context("Defaulting", func() { + It("should set the defaulting webhook if provided and not previously set", func() { + webhook = Webhooks{} + other = Webhooks{Defaulting: true} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Defaulting).To(BeTrue()) + }) + + It("should keep the defaulting webhook if previously set", func() { + webhook = Webhooks{Defaulting: true} + + By("not providing it") + other = Webhooks{} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Defaulting).To(BeTrue()) + + By("providing it") + other = Webhooks{Defaulting: true} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Defaulting).To(BeTrue()) + }) + + It("should not set the defaulting webhook if not provided and not previously set", func() { + webhook = Webhooks{} + other = Webhooks{} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Defaulting).To(BeFalse()) + }) + }) + + Context("Validation", func() { + It("should set the validation webhook if provided and not previously set", func() { + webhook = Webhooks{} + other = Webhooks{Validation: true} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Validation).To(BeTrue()) + }) + + It("should keep the validation webhook if previously set", func() { + webhook = Webhooks{Validation: true} + + By("not providing it") + other = Webhooks{} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Validation).To(BeTrue()) + + By("providing it") + other = Webhooks{Validation: true} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Validation).To(BeTrue()) + }) + + It("should not set the validation webhook if not provided and not previously set", func() { + webhook = Webhooks{} + other = Webhooks{} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Validation).To(BeFalse()) + }) + }) + + Context("Conversion", func() { + It("should set the conversion webhook if provided and not previously set", func() { + webhook = Webhooks{} + other = Webhooks{Conversion: true} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Conversion).To(BeTrue()) + }) + + It("should keep the conversion webhook if previously set", func() { + webhook = Webhooks{Conversion: true} + + By("not providing it") + other = Webhooks{} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Conversion).To(BeTrue()) + + By("providing it") + other = Webhooks{Conversion: true} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Conversion).To(BeTrue()) + }) + + It("should not set the conversion webhook if not provided and not previously set", func() { + webhook = Webhooks{} + other = Webhooks{} + Expect(webhook.Update(&other)).To(Succeed()) + Expect(webhook.Conversion).To(BeFalse()) + }) + }) + }) + + Context("IsEmpty", func() { + var ( + none = Webhooks{} + defaulting = Webhooks{ + WebhookVersion: "v1", + Defaulting: true, + Validation: false, + Conversion: false, + } + validation = Webhooks{ + WebhookVersion: "v1", + Defaulting: false, + Validation: true, + Conversion: false, + } + conversion = Webhooks{ + WebhookVersion: "v1", + Defaulting: false, + Validation: false, + Conversion: true, + } + defaultingAndValidation = Webhooks{ + WebhookVersion: "v1", + Defaulting: true, + Validation: true, + Conversion: false, + } + defaultingAndConversion = Webhooks{ + WebhookVersion: "v1", + Defaulting: true, + Validation: false, + Conversion: true, + } + validationAndConversion = Webhooks{ + WebhookVersion: "v1", + Defaulting: false, + Validation: true, + Conversion: true, + } + all = Webhooks{ + WebhookVersion: "v1", + Defaulting: true, + Validation: true, + Conversion: true, + } + ) + + It("should return true fo an empty object", func() { + Expect(none.IsEmpty()).To(BeTrue()) + }) + + DescribeTable("should return false for non-empty objects", + func(webhooks Webhooks) { Expect(webhooks.IsEmpty()).To(BeFalse()) }, + Entry("defaulting", defaulting), + Entry("validation", validation), + Entry("conversion", conversion), + Entry("defaulting and validation", defaultingAndValidation), + Entry("defaulting and conversion", defaultingAndConversion), + Entry("validation and conversion", validationAndConversion), + Entry("defaulting and validation and conversion", all), + ) + }) +}) diff --git a/pkg/model/stage/stage.go b/pkg/model/stage/stage.go new file mode 100644 index 00000000000..cf7c001aec5 --- /dev/null +++ b/pkg/model/stage/stage.go @@ -0,0 +1,109 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package stage + +import ( + "errors" +) + +var errInvalid = errors.New("invalid version stage") + +// Stage represents the stability of a version +type Stage uint8 + +// Order Stage in decreasing degree of stability for comparison purposes. +// Stable must be 0 so that it is the default Stage +const ( // The order in this const declaration will be used to order version stages except for Stable + // Stable should be used for plugins that are rarely changed in backwards-compatible ways, e.g. bug fixes. + Stable Stage = iota + // Beta should be used for plugins that may be changed in minor ways and are not expected to break between uses. + Beta Stage = iota + // Alpha should be used for plugins that are frequently changed and may break between uses. + Alpha Stage = iota +) + +const ( + alpha = "alpha" + beta = "beta" + stable = "" +) + +// ParseStage parses stage into a Stage, assuming it is one of the valid stages +func ParseStage(stage string) (Stage, error) { + var s Stage + return s, s.Parse(stage) +} + +// Parse parses stage inline, assuming it is one of the valid stages +func (s *Stage) Parse(stage string) error { + switch stage { + case alpha: + *s = Alpha + case beta: + *s = Beta + case stable: + *s = Stable + default: + return errInvalid + } + return nil +} + +// String returns the string representation of s +func (s Stage) String() string { + switch s { + case Alpha: + return alpha + case Beta: + return beta + case Stable: + return stable + default: + panic(errInvalid) + } +} + +// Validate ensures that the stage is one of the valid stages +func (s Stage) Validate() error { + switch s { + case Alpha: + case Beta: + case Stable: + default: + return errInvalid + } + + return nil +} + +// Compare returns -1 if s < other, 0 if s == other, and 1 if s > other. +func (s Stage) Compare(other Stage) int { + if s == other { + return 0 + } + + // Stage are sorted in decreasing order + if s > other { + return -1 + } + return 1 +} + +// IsStable returns whether the stage is stable or not +func (s Stage) IsStable() bool { + return s == Stable +} diff --git a/pkg/model/stage/stage_test.go b/pkg/model/stage/stage_test.go new file mode 100644 index 00000000000..561aa688c43 --- /dev/null +++ b/pkg/model/stage/stage_test.go @@ -0,0 +1,132 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package stage + +import ( + "sort" + "testing" + + g "github.com/onsi/ginkgo" // An alias is required because Context is defined elsewhere in this package. + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" +) + +func TestStage(t *testing.T) { + RegisterFailHandler(g.Fail) + g.RunSpecs(t, "Stage Suite") +} + +var _ = g.Describe("ParseStage", func() { + DescribeTable("should be correctly parsed for valid stage strings", + func(str string, stage Stage) { + s, err := ParseStage(str) + Expect(err).NotTo(HaveOccurred()) + Expect(s).To(Equal(stage)) + }, + Entry("for alpha stage", "alpha", Alpha), + Entry("for beta stage", "beta", Beta), + Entry("for stable stage", "", Stable), + ) + + DescribeTable("should error when parsing invalid stage strings", + func(str string) { + _, err := ParseStage(str) + Expect(err).To(HaveOccurred()) + }, + Entry("passing a number as the stage string", "1"), + Entry("passing `gamma` as the stage string", "gamma"), + Entry("passing a dash-prefixed stage string", "-alpha"), + ) +}) + +var _ = g.Describe("Stage", func() { + g.Context("String", func() { + DescribeTable("should return the correct string value", + func(stage Stage, str string) { Expect(stage.String()).To(Equal(str)) }, + Entry("for alpha stage", Alpha, "alpha"), + Entry("for beta stage", Beta, "beta"), + Entry("for stable stage", Stable, ""), + ) + + DescribeTable("should panic", + func(stage Stage) { Expect(func() { _ = stage.String() }).To(Panic()) }, + Entry("for stage 34", Stage(34)), + Entry("for stage 75", Stage(75)), + Entry("for stage 123", Stage(123)), + Entry("for stage 255", Stage(255)), + ) + }) + + g.Context("Validate", func() { + DescribeTable("should validate existing stages", + func(stage Stage) { Expect(stage.Validate()).To(Succeed()) }, + Entry("for alpha stage", Alpha), + Entry("for beta stage", Beta), + Entry("for stable stage", Stable), + ) + + DescribeTable("should fail for non-existing stages", + func(stage Stage) { Expect(stage.Validate()).NotTo(Succeed()) }, + Entry("for stage 34", Stage(34)), + Entry("for stage 75", Stage(75)), + Entry("for stage 123", Stage(123)), + Entry("for stage 255", Stage(255)), + ) + }) + + g.Context("Compare", func() { + // Test Stage.Compare by sorting a list + var ( + stages = []Stage{ + Stable, + Alpha, + Stable, + Beta, + Beta, + Alpha, + } + + sortedStages = []Stage{ + Alpha, + Alpha, + Beta, + Beta, + Stable, + Stable, + } + ) + + g.It("sorts stages correctly", func() { + sort.Slice(stages, func(i int, j int) bool { + return stages[i].Compare(stages[j]) == -1 + }) + Expect(stages).To(Equal(sortedStages)) + }) + }) + + g.Context("IsStable", func() { + g.It("should return true for stable stage", func() { + Expect(Stable.IsStable()).To(BeTrue()) + }) + + DescribeTable("should return false for any unstable stage", + func(stage Stage) { Expect(stage.IsStable()).To(BeFalse()) }, + Entry("for alpha stage", Alpha), + Entry("for beta stage", Beta), + ) + }) +}) diff --git a/pkg/model/universe.go b/pkg/model/universe.go index 1c148b6b350..15d597c88f7 100644 --- a/pkg/model/universe.go +++ b/pkg/model/universe.go @@ -17,7 +17,7 @@ limitations under the License. package model import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model/file" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) @@ -25,7 +25,7 @@ import ( // Universe describes the entire state of file generation type Universe struct { // Config stores the project configuration - Config *config.Config `json:"config,omitempty"` + Config config.Config `json:"config,omitempty"` // Boilerplate is the copyright comment added at the top of scaffolded files Boilerplate string `json:"boilerplate,omitempty"` @@ -53,7 +53,7 @@ func NewUniverse(options ...UniverseOption) *Universe { type UniverseOption func(*Universe) // WithConfig stores the already loaded project configuration -func WithConfig(projectConfig *config.Config) UniverseOption { +func WithConfig(projectConfig config.Config) UniverseOption { return func(universe *Universe) { universe.Config = projectConfig } @@ -83,19 +83,19 @@ func (u Universe) InjectInto(builder file.Builder) { // Inject project configuration if u.Config != nil { if builderWithDomain, hasDomain := builder.(file.HasDomain); hasDomain { - builderWithDomain.InjectDomain(u.Config.Domain) + builderWithDomain.InjectDomain(u.Config.GetDomain()) } if builderWithRepository, hasRepository := builder.(file.HasRepository); hasRepository { - builderWithRepository.InjectRepository(u.Config.Repo) + builderWithRepository.InjectRepository(u.Config.GetRepository()) + } + if builderWithProjectName, hasProjectName := builder.(file.HasProjectName); hasProjectName { + builderWithProjectName.InjectProjectName(u.Config.GetProjectName()) } if builderWithMultiGroup, hasMultiGroup := builder.(file.HasMultiGroup); hasMultiGroup { - builderWithMultiGroup.InjectMultiGroup(u.Config.MultiGroup) + builderWithMultiGroup.InjectMultiGroup(u.Config.IsMultiGroup()) } if builderWithComponentConfig, hasComponentConfig := builder.(file.HasComponentConfig); hasComponentConfig { - builderWithComponentConfig.InjectComponentConfig(u.Config.ComponentConfig) - } - if builderWithProjectName, hasProjectName := builder.(file.HasProjectName); hasProjectName { - builderWithProjectName.InjectProjectName(u.Config.ProjectName) + builderWithComponentConfig.InjectComponentConfig(u.Config.IsComponentConfig()) } } // Inject boilerplate diff --git a/pkg/plugin/helpers.go b/pkg/plugin/helpers.go index 4dafbc78dd7..3a2d43f4e4c 100644 --- a/pkg/plugin/helpers.go +++ b/pkg/plugin/helpers.go @@ -21,6 +21,7 @@ import ( "path" "strings" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" ) @@ -64,7 +65,7 @@ func Validate(p Plugin) error { return fmt.Errorf("plugin %q must support at least one project version", KeyFor(p)) } for _, projectVersion := range p.SupportedProjectVersions() { - if err := validation.ValidateProjectVersion(projectVersion); err != nil { + if err := projectVersion.Validate(); err != nil { return fmt.Errorf("plugin %q supports an invalid project version %q: %v", KeyFor(p), projectVersion, err) } } @@ -79,7 +80,8 @@ func ValidateKey(key string) error { } // CLI-set plugins do not have to contain a version. if version != "" { - if _, err := ParseVersion(version); err != nil { + var v Version + if err := v.Parse(version); err != nil { return fmt.Errorf("invalid plugin version %q: %v", version, err) } } @@ -95,9 +97,9 @@ func validateName(name string) error { } // SupportsVersion checks if a plugins supports a project version. -func SupportsVersion(p Plugin, projectVersion string) bool { +func SupportsVersion(p Plugin, projectVersion config.Version) bool { for _, version := range p.SupportedProjectVersions() { - if version == projectVersion { + if projectVersion.Compare(version) == 0 { return true } } diff --git a/pkg/plugin/interfaces.go b/pkg/plugin/interfaces.go index bd6420f7814..1c1cf2da01a 100644 --- a/pkg/plugin/interfaces.go +++ b/pkg/plugin/interfaces.go @@ -19,7 +19,7 @@ package plugin import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" ) // Plugin is an interface that defines the common base for all plugins @@ -32,9 +32,9 @@ type Plugin interface { // // NOTE: this version is different from config version. Version() Version - // SupportedProjectVersions lists all project configuration versions this plugin supports, ex. []string{"2", "3"}. + // SupportedProjectVersions lists all project configuration versions this plugin supports. // The returned slice cannot be empty. - SupportedProjectVersions() []string + SupportedProjectVersions() []config.Version } // Deprecated is an interface that defines the messages for plugins that are deprecated. @@ -56,7 +56,7 @@ type Subcommand interface { Run() error // InjectConfig passes a config to a plugin. The plugin may modify the config. // Initializing, loading, and saving the config is managed by the cli package. - InjectConfig(*config.Config) + InjectConfig(config.Config) } // Context is the runtime context for a subcommand. diff --git a/pkg/plugin/version.go b/pkg/plugin/version.go index 1196afdce1e..8f8a4bed430 100644 --- a/pkg/plugin/version.go +++ b/pkg/plugin/version.go @@ -1,3 +1,19 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + package plugin import ( @@ -5,146 +21,66 @@ import ( "fmt" "strconv" "strings" -) -var ( - errInvalidStage = errors.New("invalid version stage") - errInvalidVersion = errors.New("version number must be positive") - errEmptyPlugin = errors.New("plugin version is empty") -) - -// Stage represents the stability of a version -type Stage uint8 - -// Order Stage in decreasing degree of stability for comparison purposes. -// StableStage must be 0 so that it is the default Stage -const ( // The order in this const declaration will be used to order version stages except for StableStage - // StableStage should be used for plugins that are rarely changed in backwards-compatible ways, e.g. bug fixes. - StableStage Stage = iota - // BetaStage should be used for plugins that may be changed in minor ways and are not expected to break between uses. - BetaStage Stage = iota - // AlphaStage should be used for plugins that are frequently changed and may break between uses. - AlphaStage Stage = iota + "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" ) -const ( - alphaStage = "alpha" - betaStage = "beta" - stableStage = "" +var ( + errNegative = errors.New("plugin version number must be positive") + errEmpty = errors.New("plugin version is empty") ) -// ParseStage parses stage into a Stage, assuming it is one of the valid stages -func ParseStage(stage string) (Stage, error) { - var s Stage - return s, s.Parse(stage) -} - -// Parse parses stage inline, assuming it is one of the valid stages -func (s *Stage) Parse(stage string) error { - switch stage { - case alphaStage: - *s = AlphaStage - case betaStage: - *s = BetaStage - case stableStage: - *s = StableStage - default: - return errInvalidVersion - } - return nil -} - -// String returns the string representation of s -func (s Stage) String() string { - switch s { - case AlphaStage: - return alphaStage - case BetaStage: - return betaStage - case StableStage: - return stableStage - default: - panic(errInvalidStage) - } -} - -// Validate ensures that the stage is one of the valid stages -func (s Stage) Validate() error { - switch s { - case AlphaStage: - case BetaStage: - case StableStage: - default: - return errInvalidStage - } - - return nil -} - -// Compare returns -1 if s < other, 0 if s == other, and 1 if s > other. -func (s Stage) Compare(other Stage) int { - if s == other { - return 0 - } - - // Stage are sorted in decreasing order - if s > other { - return -1 - } - return 1 -} - -// Version is a plugin version containing a non-zero positive integer and a stage value that represents stability. +// Version is a plugin version containing a positive integer and a stage value that represents stability. type Version struct { // Number denotes the current version of a plugin. Two different numbers between versions // indicate that they are incompatible. - Number int64 + Number int // Stage indicates stability. - Stage Stage + Stage stage.Stage } -// ParseVersion parses version into a Version, assuming it adheres to format: (v)?[1-9][0-9]*(-(alpha|beta))? -func ParseVersion(version string) (Version, error) { - var v Version - return v, v.Parse(version) -} - -// Parse parses version inline, assuming it adheres to format: (v)?[1-9][0-9]*(-(alpha|beta))? -func (v *Version) Parse(version string) (err error) { +// Parse parses version inline, assuming it adheres to format: (v)?[0-9]*(-(alpha|beta))? +func (v *Version) Parse(version string) error { version = strings.TrimPrefix(version, "v") if len(version) == 0 { - return errEmptyPlugin + return errEmpty } substrings := strings.SplitN(version, "-", 2) - if v.Number, err = strconv.ParseInt(substrings[0], 10, 64); err != nil { - return - } else if v.Number < 1 { - return errInvalidVersion + var err error + if v.Number, err = strconv.Atoi(substrings[0]); err != nil { + // Lets check if the `-` belonged to a negative number + if n, err := strconv.Atoi(version); err == nil && n < 0 { + return errNegative + } + return err } - if len(substrings) == 1 { - v.Stage = StableStage - } else { - err = v.Stage.Parse(substrings[1]) + if len(substrings) > 1 { + if err = v.Stage.Parse(substrings[1]); err != nil { + return err + } } - return + + return nil } -// String returns the string representation of v +// String returns the string representation of v. func (v Version) String() string { - if len(v.Stage.String()) == 0 { + stageStr := v.Stage.String() + if len(stageStr) == 0 { return fmt.Sprintf("v%d", v.Number) } - return fmt.Sprintf("v%d-%s", v.Number, v.Stage) + return fmt.Sprintf("v%d-%s", v.Number, stageStr) } -// Validate ensures that the version number is positive and the stage is one of the valid stages +// Validate ensures that the version number is positive and the stage is one of the valid stages. func (v Version) Validate() error { if v.Number < 0 { - return errInvalidVersion + return errNegative } + return v.Stage.Validate() } @@ -161,5 +97,11 @@ func (v Version) Compare(other Version) int { // IsStable returns true if v is stable. func (v Version) IsStable() bool { - return v.Stage.Compare(StableStage) == -1 + // Plugin version 0 is not considered stable + if v.Number == 0 { + return false + } + + // Any other version than 0 depends on its stage field + return v.Stage.IsStable() } diff --git a/pkg/plugin/version_test.go b/pkg/plugin/version_test.go index 7a65f7e5531..f1512dbf7d8 100644 --- a/pkg/plugin/version_test.go +++ b/pkg/plugin/version_test.go @@ -21,282 +21,172 @@ import ( "testing" g "github.com/onsi/ginkgo" // An alias is required because Context is defined elsewhere in this package. + . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" ) -func TestCLI(t *testing.T) { +func TestPlugin(t *testing.T) { RegisterFailHandler(g.Fail) g.RunSpecs(t, "Plugin Suite") } -var _ = g.Describe("ParseStage", func() { - var ( - s Stage - err error - ) - - g.It("should be correctly parsed for valid stage strings", func() { - g.By("passing an empty stage string") - s, err = ParseStage("") - Expect(err).NotTo(HaveOccurred()) - Expect(s).To(Equal(StableStage)) - - g.By("passing `alpha` as the stage string") - s, err = ParseStage("alpha") - Expect(err).NotTo(HaveOccurred()) - Expect(s).To(Equal(AlphaStage)) - - g.By("passing `beta` as the stage string") - s, err = ParseStage("beta") - Expect(err).NotTo(HaveOccurred()) - Expect(s).To(Equal(BetaStage)) - }) - - g.It("should error when parsing invalid stage strings", func() { - g.By("passing a number as the stage string") - s, err = ParseStage("1") - Expect(err).To(HaveOccurred()) - - g.By("passing `gamma` as the stage string") - s, err = ParseStage("gamma") - Expect(err).To(HaveOccurred()) - - g.By("passing a dash-prefixed stage string") - s, err = ParseStage("-alpha") - Expect(err).To(HaveOccurred()) +var _ = g.Describe("Version", func() { + g.Context("Parse", func() { + DescribeTable("should be correctly parsed for valid version strings", + func(str string, number int, s stage.Stage) { + var v Version + Expect(v.Parse(str)).To(Succeed()) + Expect(v.Number).To(Equal(number)) + Expect(v.Stage).To(Equal(s)) + }, + Entry("for version string `0`", "0", 0, stage.Stable), + Entry("for version string `0-alpha`", "0-alpha", 0, stage.Alpha), + Entry("for version string `0-beta`", "0-beta", 0, stage.Beta), + Entry("for version string `1`", "1", 1, stage.Stable), + Entry("for version string `1-alpha`", "1-alpha", 1, stage.Alpha), + Entry("for version string `1-beta`", "1-beta", 1, stage.Beta), + Entry("for version string `v1`", "v1", 1, stage.Stable), + Entry("for version string `v1-alpha`", "v1-alpha", 1, stage.Alpha), + Entry("for version string `v1-beta`", "v1-beta", 1, stage.Beta), + Entry("for version string `22`", "22", 22, stage.Stable), + Entry("for version string `22-alpha`", "22-alpha", 22, stage.Alpha), + Entry("for version string `22-beta`", "22-beta", 22, stage.Beta), + ) + + DescribeTable("should error when parsing an invalid version string", + func(str string) { + var v Version + Expect(v.Parse(str)).NotTo(Succeed()) + }, + Entry("for version string ``", ""), + Entry("for version string `-1`", "-1"), + Entry("for version string `-1-alpha`", "-1-alpha"), + Entry("for version string `-1-beta`", "-1-beta"), + Entry("for version string `1.0`", "1.0"), + Entry("for version string `v1.0`", "v1.0"), + Entry("for version string `v1.0-alpha`", "v1.0-alpha"), + Entry("for version string `1.0.0`", "1.0.0"), + Entry("for version string `1-a`", "1-a"), + ) }) -}) - -var _ = g.Describe("Stage.String", func() { - g.It("should return the correct string value", func() { - g.By("for stable stage") - Expect(StableStage.String()).To(Equal(stableStage)) - g.By("for alpha stage") - Expect(AlphaStage.String()).To(Equal(alphaStage)) - - g.By("for beta stage") - Expect(BetaStage.String()).To(Equal(betaStage)) + g.Context("String", func() { + DescribeTable("should return the correct string value", + func(version Version, str string) { Expect(version.String()).To(Equal(str)) }, + Entry("for version 0", Version{Number: 0}, "v0"), + Entry("for version 0 (stable)", Version{Number: 0, Stage: stage.Stable}, "v0"), + Entry("for version 0 (alpha)", Version{Number: 0, Stage: stage.Alpha}, "v0-alpha"), + Entry("for version 0 (beta)", Version{Number: 0, Stage: stage.Beta}, "v0-beta"), + Entry("for version 0 (implicit)", Version{}, "v0"), + Entry("for version 0 (stable) (implicit)", Version{Stage: stage.Stable}, "v0"), + Entry("for version 0 (alpha) (implicit)", Version{Stage: stage.Alpha}, "v0-alpha"), + Entry("for version 0 (beta) (implicit)", Version{Stage: stage.Beta}, "v0-beta"), + Entry("for version 1", Version{Number: 1}, "v1"), + Entry("for version 1 (stable)", Version{Number: 1, Stage: stage.Stable}, "v1"), + Entry("for version 1 (alpha)", Version{Number: 1, Stage: stage.Alpha}, "v1-alpha"), + Entry("for version 1 (beta)", Version{Number: 1, Stage: stage.Beta}, "v1-beta"), + Entry("for version 22", Version{Number: 22}, "v22"), + Entry("for version 22 (stable)", Version{Number: 22, Stage: stage.Stable}, "v22"), + Entry("for version 22 (alpha)", Version{Number: 22, Stage: stage.Alpha}, "v22-alpha"), + Entry("for version 22 (beta)", Version{Number: 22, Stage: stage.Beta}, "v22-beta"), + ) }) -}) -var _ = g.Describe("Stage.Validate", func() { - g.It("should validate existing stages", func() { - g.By("for stable stage") - Expect(StableStage.Validate()).To(Succeed()) - - g.By("for alpha stage") - Expect(AlphaStage.Validate()).To(Succeed()) - - g.By("for beta stage") - Expect(BetaStage.Validate()).To(Succeed()) + g.Context("Validate", func() { + DescribeTable("should validate valid versions", + func(version Version) { Expect(version.Validate()).To(Succeed()) }, + Entry("for version 0", Version{Number: 0}), + Entry("for version 0 (stable)", Version{Number: 0, Stage: stage.Stable}), + Entry("for version 0 (alpha)", Version{Number: 0, Stage: stage.Alpha}), + Entry("for version 0 (beta)", Version{Number: 0, Stage: stage.Beta}), + Entry("for version 0 (implicit)", Version{}), + Entry("for version 0 (stable) (implicit)", Version{Stage: stage.Stable}), + Entry("for version 0 (alpha) (implicit)", Version{Stage: stage.Alpha}), + Entry("for version 0 (beta) (implicit)", Version{Stage: stage.Beta}), + Entry("for version 1", Version{Number: 1}), + Entry("for version 1 (stable)", Version{Number: 1, Stage: stage.Stable}), + Entry("for version 1 (alpha)", Version{Number: 1, Stage: stage.Alpha}), + Entry("for version 1 (beta)", Version{Number: 1, Stage: stage.Beta}), + Entry("for version 22", Version{Number: 22}), + Entry("for version 22 (stable)", Version{Number: 22, Stage: stage.Stable}), + Entry("for version 22 (alpha)", Version{Number: 22, Stage: stage.Alpha}), + Entry("for version 22 (beta)", Version{Number: 22, Stage: stage.Beta}), + ) + + DescribeTable("should fail for invalid versions", + func(version Version) { Expect(version.Validate()).NotTo(Succeed()) }, + Entry("for version -1", Version{Number: -1}), + Entry("for version -1 (stable)", Version{Number: -1, Stage: stage.Stable}), + Entry("for version -1 (alpha)", Version{Number: -1, Stage: stage.Alpha}), + Entry("for version -1 (beta)", Version{Number: -1, Stage: stage.Beta}), + Entry("for invalid stage", Version{Stage: stage.Stage(34)}), + ) }) - g.It("should fail for non-existing stages", func() { - Expect(Stage(34).Validate()).NotTo(Succeed()) - Expect(Stage(75).Validate()).NotTo(Succeed()) - Expect(Stage(123).Validate()).NotTo(Succeed()) - Expect(Stage(255).Validate()).NotTo(Succeed()) - }) -}) - -var _ = g.Describe("Stage.Compare", func() { - // Test Stage.Compare by sorting a list - var ( - stages = []Stage{ - StableStage, - BetaStage, - AlphaStage, - } - - sortedStages = []Stage{ - AlphaStage, - BetaStage, - StableStage, - } - ) - g.It("sorts stages correctly", func() { - sort.Slice(stages, func(i int, j int) bool { - return stages[i].Compare(stages[j]) == -1 + g.Context("Compare", func() { + // Test Compare() by sorting a list. + var ( + versions = []Version{ + {Number: 2, Stage: stage.Alpha}, + {Number: 44, Stage: stage.Alpha}, + {Number: 1}, + {Number: 2, Stage: stage.Beta}, + {Number: 4, Stage: stage.Beta}, + {Number: 1, Stage: stage.Alpha}, + {Number: 4}, + {Number: 44, Stage: stage.Alpha}, + {Number: 30}, + {Number: 4, Stage: stage.Alpha}, + } + + sortedVersions = []Version{ + {Number: 1, Stage: stage.Alpha}, + {Number: 1}, + {Number: 2, Stage: stage.Alpha}, + {Number: 2, Stage: stage.Beta}, + {Number: 4, Stage: stage.Alpha}, + {Number: 4, Stage: stage.Beta}, + {Number: 4}, + {Number: 30}, + {Number: 44, Stage: stage.Alpha}, + {Number: 44, Stage: stage.Alpha}, + } + ) + + g.It("sorts a valid list of versions correctly", func() { + sort.Slice(versions, func(i int, j int) bool { + return versions[i].Compare(versions[j]) == -1 + }) + Expect(versions).To(Equal(sortedVersions)) }) - Expect(stages).To(Equal(sortedStages)) - }) -}) -var _ = g.Describe("ParseVersion", func() { - var ( - v Version - err error - ) - - g.It("should be correctly parsed when a version is positive without a stage", func() { - g.By("passing version string 1") - v, err = ParseVersion("1") - Expect(err).NotTo(HaveOccurred()) - Expect(v.Number).To(BeNumerically("==", int64(1))) - Expect(v.Stage).To(Equal(StableStage)) - - g.By("passing version string 22") - v, err = ParseVersion("22") - Expect(err).NotTo(HaveOccurred()) - Expect(v.Number).To(BeNumerically("==", int64(22))) - Expect(v.Stage).To(Equal(StableStage)) - - g.By("passing version string v1") - v, err = ParseVersion("v1") - Expect(err).NotTo(HaveOccurred()) - Expect(v.Number).To(BeNumerically("==", int64(1))) - Expect(v.Stage).To(Equal(StableStage)) }) - g.It("should be correctly parsed when a version is positive with a stage", func() { - g.By("passing version string 1-alpha") - v, err = ParseVersion("1-alpha") - Expect(err).NotTo(HaveOccurred()) - Expect(v.Number).To(BeNumerically("==", int64(1))) - Expect(v.Stage).To(Equal(AlphaStage)) - - g.By("passing version string 1-beta") - v, err = ParseVersion("1-beta") - Expect(err).NotTo(HaveOccurred()) - Expect(v.Number).To(BeNumerically("==", int64(1))) - Expect(v.Stage).To(Equal(BetaStage)) - - g.By("passing version string v1-alpha") - v, err = ParseVersion("v1-alpha") - Expect(err).NotTo(HaveOccurred()) - Expect(v.Number).To(BeNumerically("==", int64(1))) - Expect(v.Stage).To(Equal(AlphaStage)) - - g.By("passing version string v22-alpha") - v, err = ParseVersion("v22-alpha") - Expect(err).NotTo(HaveOccurred()) - Expect(v.Number).To(BeNumerically("==", int64(22))) - Expect(v.Stage).To(Equal(AlphaStage)) + g.Context("IsStable", func() { + DescribeTable("should return true for stable versions", + func(version Version) { Expect(version.IsStable()).To(BeTrue()) }, + Entry("for version 1", Version{Number: 1}), + Entry("for version 1 (stable)", Version{Number: 1, Stage: stage.Stable}), + Entry("for version 22", Version{Number: 22}), + Entry("for version 22 (stable)", Version{Number: 22, Stage: stage.Stable}), + ) + + DescribeTable("should return false for unstable versions", + func(version Version) { Expect(version.IsStable()).To(BeFalse()) }, + Entry("for version 0", Version{Number: 0}), + Entry("for version 0 (stable)", Version{Number: 0, Stage: stage.Stable}), + Entry("for version 0 (alpha)", Version{Number: 0, Stage: stage.Alpha}), + Entry("for version 0 (beta)", Version{Number: 0, Stage: stage.Beta}), + Entry("for version 0 (implicit)", Version{}), + Entry("for version 0 (stable) (implicit)", Version{Stage: stage.Stable}), + Entry("for version 0 (alpha) (implicit)", Version{Stage: stage.Alpha}), + Entry("for version 0 (beta) (implicit)", Version{Stage: stage.Beta}), + Entry("for version 1 (alpha)", Version{Number: 1, Stage: stage.Alpha}), + Entry("for version 1 (beta)", Version{Number: 1, Stage: stage.Beta}), + Entry("for version 22 (alpha)", Version{Number: 22, Stage: stage.Alpha}), + Entry("for version 22 (beta)", Version{Number: 22, Stage: stage.Beta}), + ) }) - - g.It("should fail for invalid version strings", func() { - g.By("passing version string an empty string") - _, err = ParseVersion("") - Expect(err).To(HaveOccurred()) - - g.By("passing version string 0") - _, err = ParseVersion("0") - Expect(err).To(HaveOccurred()) - - g.By("passing a negative number version string") - _, err = ParseVersion("-1") - Expect(err).To(HaveOccurred()) - }) - - g.It("should fail validation when a version string is semver", func() { - g.By("passing version string 1.0") - _, err = ParseVersion("1.0") - Expect(err).To(HaveOccurred()) - - g.By("passing version string v1.0") - _, err = ParseVersion("v1.0") - Expect(err).To(HaveOccurred()) - - g.By("passing version string v1.0-alpha") - _, err = ParseVersion("v1.0-alpha") - Expect(err).To(HaveOccurred()) - - g.By("passing version string 1.0.0") - _, err = ParseVersion("1.0.0") - Expect(err).To(HaveOccurred()) - }) -}) - -var _ = g.Describe("Version.String", func() { - g.It("should return the correct string value", func() { - g.By("for stable version 1") - Expect(Version{Number: 1}.String()).To(Equal("v1")) - Expect(Version{Number: 1, Stage: StableStage}.String()).To(Equal("v1")) - - g.By("for stable version 22") - Expect(Version{Number: 22}.String()).To(Equal("v22")) - Expect(Version{Number: 22, Stage: StableStage}.String()).To(Equal("v22")) - - g.By("for alpha version 1") - Expect(Version{Number: 1, Stage: AlphaStage}.String()).To(Equal("v1-alpha")) - - g.By("for beta version 1") - Expect(Version{Number: 1, Stage: BetaStage}.String()).To(Equal("v1-beta")) - - g.By("for alpha version 22") - Expect(Version{Number: 22, Stage: AlphaStage}.String()).To(Equal("v22-alpha")) - }) -}) - -var _ = g.Describe("Version.Validate", func() { - g.It("should success for a positive version without a stage", func() { - g.By("passing version 0") - Expect(Version{}.Validate()).To(Succeed()) - Expect(Version{Number: 0}.Validate()).To(Succeed()) - - g.By("for version 1") - Expect(Version{Number: 1}.Validate()).To(Succeed()) - - g.By("for version 22") - Expect(Version{Number: 22}.Validate()).To(Succeed()) - }) - - g.It("should success for a positive version with a stage", func() { - g.By("for version 1 alpha") - Expect(Version{Number: 1, Stage: AlphaStage}.Validate()).To(Succeed()) - - g.By("for version 1 beta") - Expect(Version{Number: 1, Stage: BetaStage}.Validate()).To(Succeed()) - - g.By("for version 22 alpha") - Expect(Version{Number: 22, Stage: AlphaStage}.Validate()).To(Succeed()) - }) - - g.It("should fail for invalid versions", func() { - g.By("passing a negative version") - Expect(Version{Number: -1}.Validate()).NotTo(Succeed()) - - g.By("passing an invalid stage") - Expect(Version{Number: 1, Stage: Stage(173)}.Validate()).NotTo(Succeed()) - }) -}) - -var _ = g.Describe("Version.Compare", func() { - // Test Compare() by sorting a list. - var ( - versions = []Version{ - {Number: 2, Stage: AlphaStage}, - {Number: 44, Stage: AlphaStage}, - {Number: 1}, - {Number: 2, Stage: BetaStage}, - {Number: 4, Stage: BetaStage}, - {Number: 1, Stage: AlphaStage}, - {Number: 4}, - {Number: 44, Stage: AlphaStage}, - {Number: 30}, - {Number: 4, Stage: AlphaStage}, - } - - sortedVersions = []Version{ - {Number: 1, Stage: AlphaStage}, - {Number: 1}, - {Number: 2, Stage: AlphaStage}, - {Number: 2, Stage: BetaStage}, - {Number: 4, Stage: AlphaStage}, - {Number: 4, Stage: BetaStage}, - {Number: 4}, - {Number: 30}, - {Number: 44, Stage: AlphaStage}, - {Number: 44, Stage: AlphaStage}, - } - ) - - g.It("sorts a valid list of versions correctly", func() { - sort.Slice(versions, func(i int, j int) bool { - return versions[i].Compare(versions[j]) == -1 - }) - Expect(versions).To(Equal(sortedVersions)) - }) - }) diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go new file mode 100644 index 00000000000..5dd87ad4820 --- /dev/null +++ b/pkg/plugins/golang/options.go @@ -0,0 +1,238 @@ +/* +Copyright 2021 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 golang + +import ( + "fmt" + "path" + "regexp" + "strings" + + newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +const ( + versionPattern = "^v\\d+(?:alpha\\d+|beta\\d+)?$" + groupPresent = "group flag present but empty" + versionPresent = "version flag present but empty" + kindPresent = "kind flag present but empty" + versionRequired = "version cannot be empty" + kindRequired = "kind cannot be empty" +) + +var ( + versionRegex = regexp.MustCompile(versionPattern) + + coreGroups = map[string]string{ + "admission": "k8s.io", + "admissionregistration": "k8s.io", + "apps": "", + "auditregistration": "k8s.io", + "apiextensions": "k8s.io", + "authentication": "k8s.io", + "authorization": "k8s.io", + "autoscaling": "", + "batch": "", + "certificates": "k8s.io", + "coordination": "k8s.io", + "core": "", + "events": "k8s.io", + "extensions": "", + "imagepolicy": "k8s.io", + "networking": "k8s.io", + "node": "k8s.io", + "metrics": "k8s.io", + "policy": "", + "rbac.authorization": "k8s.io", + "scheduling": "k8s.io", + "setting": "k8s.io", + "storage": "k8s.io", + } +) + +// Options contains the information required to build a new resource.Resource. +type Options struct { + // Group is the resource's group. Does not contain the domain. + Group string + // Domain is the resource's domain. + Domain string + // Version is the resource's version. + Version string + // Kind is the resource's kind. + Kind string + + // Plural is the resource's kind plural form. + // Optional + Plural string + + // CRDVersion is the CustomResourceDefinition API version that will be used for the resource. + CRDVersion string + // WebhookVersion is the {Validating,Mutating}WebhookConfiguration API version that will be used for the resource. + WebhookVersion string + + // Namespaced is true if the resource should be namespaced. + Namespaced bool + + // Flags that define which parts should be scaffolded + DoAPI bool + DoController bool + DoDefaulting bool + DoValidation bool + DoConversion bool +} + +// Validate verifies that all the fields have valid values +func (opts Options) Validate() error { + // Check that the required flags did not get a flag as their value + // We can safely look for a '-' as the first char as none of the fields accepts it + // NOTE: We must do this for all the required flags first or we may output the wrong + // error as flags may seem to be missing because Cobra assigned them to another flag. + if strings.HasPrefix(opts.Group, "-") { + return fmt.Errorf(groupPresent) + } + if strings.HasPrefix(opts.Version, "-") { + return fmt.Errorf(versionPresent) + } + if strings.HasPrefix(opts.Kind, "-") { + return fmt.Errorf(kindPresent) + } + // Now we can check that all the required flags are not empty + if len(opts.Version) == 0 { + return fmt.Errorf(versionRequired) + } + if len(opts.Kind) == 0 { + return fmt.Errorf(kindRequired) + } + + // Check if the qualified group has a valid DNS1123 subdomain value + if err := validation.IsDNS1123Subdomain(opts.QualifiedGroup()); err != nil { + return fmt.Errorf("either group or domain is invalid: (%v)", err) + } + + // Check if the version follows the valid pattern + if !versionRegex.MatchString(opts.Version) { + return fmt.Errorf("version must match %s (was %s)", versionPattern, opts.Version) + } + + validationErrors := []string{} + + // Require kind to start with an uppercase character + if string(opts.Kind[0]) == strings.ToLower(string(opts.Kind[0])) { + validationErrors = append(validationErrors, "kind must start with an uppercase character") + } + + validationErrors = append(validationErrors, validation.IsDNS1035Label(strings.ToLower(opts.Kind))...) + + if len(validationErrors) != 0 { + return fmt.Errorf("invalid Kind: %#v", validationErrors) + } + + // TODO: validate plural strings if provided + + // Ensure apiVersions for k8s types are empty or valid. + for typ, apiVersion := range map[string]string{ + "CRD": opts.CRDVersion, + "Webhook": opts.WebhookVersion, + } { + switch apiVersion { + case "", "v1", "v1beta1": + default: + return fmt.Errorf("%s version must be one of: v1, v1beta1", typ) + } + } + + return nil +} + +// QualifiedGroup returns the fully qualified group name with the available information. +func (opts Options) QualifiedGroup() string { + switch "" { + case opts.Domain: + return opts.Group + case opts.Group: + return opts.Domain + default: + return fmt.Sprintf("%s.%s", opts.Group, opts.Domain) + } +} + +// GVK returns the GVK identifier of a resource. +func (opts Options) GVK() resource.GVK { + return resource.GVK{ + Group: opts.Group, + Domain: opts.Domain, + Version: opts.Version, + Kind: opts.Kind, + } +} + +// NewResource creates a new resource from the options +func (opts Options) NewResource(c newconfig.Config) resource.Resource { + res := resource.Resource{ + GVK: opts.GVK(), + Path: resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()), + Controller: opts.DoController, + } + + if opts.Plural != "" { + res.Plural = opts.Plural + } else { + // If not provided, compute a plural for Kind + res.Plural = resource.RegularPlural(opts.Kind) + } + + if opts.DoAPI { + res.API = &resource.API{ + CRDVersion: opts.CRDVersion, + Namespaced: opts.Namespaced, + } + } else { + // Make sure that the pointer is not nil to prevent pointer dereference errors + res.API = &resource.API{} + } + + if opts.DoDefaulting || opts.DoValidation || opts.DoConversion { + res.Webhooks = &resource.Webhooks{ + WebhookVersion: opts.WebhookVersion, + Defaulting: opts.DoDefaulting, + Validation: opts.DoValidation, + Conversion: opts.DoConversion, + } + } else { + // Make sure that the pointer is not nil to prevent pointer dereference errors + res.Webhooks = &resource.Webhooks{} + } + + // domain and path may need to be changed in case we are referring to a builtin core resource: + // - Check if we are scaffolding the resource now => project resource + // - Check if we already scaffolded the resource => project resource + // - Check if the resource group is a well-known core group => builtin core resource + // - In any other case, default to => project resource + // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath + if !opts.DoAPI { + if !c.HasResource(opts.GVK()) { + if domain, found := coreGroups[opts.Group]; found { + res.Domain = domain + res.Path = path.Join("k8s.io", "api", opts.Group, opts.Version) + } + } + } + + return res +} diff --git a/pkg/plugins/golang/options_test.go b/pkg/plugins/golang/options_test.go new file mode 100644 index 00000000000..27e4db5cdac --- /dev/null +++ b/pkg/plugins/golang/options_test.go @@ -0,0 +1,300 @@ +/* +Copyright 2021 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 golang + +import ( + "path" + "strings" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" +) + +var _ = Describe("Options", func() { + Context("Validate", func() { + DescribeTable("should succeed for valid options", + func(options *Options) { Expect(options.Validate()).To(Succeed()) }, + Entry("full GVK", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("missing domain", + &Options{Group: "crew", Version: "v1", Kind: "FirstMate"}), + Entry("missing group", + &Options{Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("kind with multiple initial uppercase characters", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FIRSTMate"}), + ) + + DescribeTable("should fail for invalid options", + func(options *Options) { Expect(options.Validate()).NotTo(Succeed()) }, + Entry("group flag captured another flag", + &Options{Group: "--version"}), + Entry("version flag captured another flag", + &Options{Version: "--kind"}), + Entry("kind flag captured another flag", + &Options{Kind: "--group"}), + Entry("missing group and domain", + &Options{Version: "v1", Kind: "FirstMate"}), + Entry("group with uppercase characters", + &Options{Group: "Crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("group with non-alpha characters", + &Options{Group: "crew1*?", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("missing version", + &Options{Group: "crew", Domain: "test.io", Kind: "FirstMate"}), + Entry("version without v prefix", + &Options{Group: "crew", Domain: "test.io", Version: "1", Kind: "FirstMate"}), + Entry("unstable version without v prefix", + &Options{Group: "crew", Domain: "test.io", Version: "1beta1", Kind: "FirstMate"}), + Entry("unstable version with wrong prefix", + &Options{Group: "crew", Domain: "test.io", Version: "a1beta1", Kind: "FirstMate"}), + Entry("unstable version without alpha/beta number", + &Options{Group: "crew", Domain: "test.io", Version: "v1beta", Kind: "FirstMate"}), + Entry("multiple unstable version", + &Options{Group: "crew", Domain: "test.io", Version: "v1beta1alpha1", Kind: "FirstMate"}), + Entry("missing kind", + &Options{Group: "crew", Domain: "test.io", Version: "v1"}), + Entry("kind is too long", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: strings.Repeat("a", 64)}), + Entry("kind with whitespaces", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "First Mate"}), + Entry("kind ends with `-`", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate-"}), + Entry("kind starts with a decimal character", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "1FirstMate"}), + Entry("kind starts with a lowercase character", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "firstMate"}), + Entry("Invalid CRD version", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", CRDVersion: "a"}), + Entry("Invalid webhook version", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", WebhookVersion: "a"}), + ) + }) + + Context("NewResource", func() { + var cfg config.Config + + BeforeEach(func() { + cfg = cfgv3alpha.New() + _ = cfg.SetRepository("test") + }) + + DescribeTable("should succeed if the Resource is valid", + func(options *Options) { + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Group).To(Equal(options.Group)) + Expect(resource.Domain).To(Equal(options.Domain)) + Expect(resource.Version).To(Equal(options.Version)) + Expect(resource.Kind).To(Equal(options.Kind)) + if multiGroup { + Expect(resource.Path).To(Equal( + path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + } else { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + } + Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) + Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) + Expect(resource.Controller).To(Equal(options.DoController)) + Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) + Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) + Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) + Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) + Expect(resource.PackageName()).To(Equal(options.Group)) + Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) + } + }, + Entry("basic", &Options{ + Group: "crew", + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + }), + Entry("API", &Options{ + Group: "crew", + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + DoAPI: true, + CRDVersion: "v1", + Namespaced: true, + }), + Entry("Controller", &Options{ + Group: "crew", + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + DoController: true, + }), + Entry("Webhooks", &Options{ + Group: "crew", + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + DoDefaulting: true, + DoValidation: true, + DoConversion: true, + WebhookVersion: "v1", + }), + ) + + DescribeTable("should default the Plural by pluralizing the Kind", + func(kind, plural string) { + options := &Options{Group: "crew", Version: "v1", Kind: kind} + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Plural).To(Equal(plural)) + } + }, + Entry("for `FirstMate`", "FirstMate", "firstmates"), + Entry("for `Fish`", "Fish", "fish"), + Entry("for `Helmswoman`", "Helmswoman", "helmswomen"), + ) + + DescribeTable("should keep the Plural if specified", + func(kind, plural string) { + options := &Options{Group: "crew", Version: "v1", Kind: kind, Plural: plural} + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Plural).To(Equal(plural)) + } + }, + Entry("for `FirstMate`", "FirstMate", "mates"), + Entry("for `Fish`", "Fish", "shoal"), + ) + + DescribeTable("should allow hyphens and dots in group names", + func(group, safeGroup string) { + options := &Options{ + Group: group, + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + } + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Group).To(Equal(options.Group)) + if multiGroup { + Expect(resource.Path).To(Equal( + path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + } else { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + } + Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) + Expect(resource.PackageName()).To(Equal(safeGroup)) + Expect(resource.ImportAlias()).To(Equal(safeGroup + options.Version)) + } + }, + Entry("for hyphen-containing group", "my-project", "myproject"), + Entry("for dot-containing group", "my.project", "myproject"), + ) + + It("should not append '.' if provided an empty domain", func() { + options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"} + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.QualifiedGroup()).To(Equal(options.Group)) + } + }) + + DescribeTable("should use core apis", + func(group, qualified string) { + options := &Options{ + Group: group, + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + } + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) + Expect(resource.API.CRDVersion).To(Equal("")) + Expect(resource.QualifiedGroup()).To(Equal(qualified)) + } + }, + Entry("for `apps`", "apps", "apps"), + Entry("for `authentication`", "authentication", "authentication.k8s.io"), + ) + + It("should use domain if the group is empty", func() { + safeDomain := "testio" + + options := &Options{ + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + } + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Group).To(Equal("")) + if multiGroup { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "apis", options.Version))) + } else { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + } + Expect(resource.QualifiedGroup()).To(Equal(options.Domain)) + Expect(resource.PackageName()).To(Equal(safeDomain)) + Expect(resource.ImportAlias()).To(Equal(safeDomain + options.Version)) + } + }) + }) +}) diff --git a/pkg/plugins/golang/suite_test.go b/pkg/plugins/golang/suite_test.go new file mode 100644 index 00000000000..2e805c0e98d --- /dev/null +++ b/pkg/plugins/golang/suite_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 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 golang + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestGoPlugin(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Go Plugin Suite") +} diff --git a/pkg/plugins/golang/v2/api.go b/pkg/plugins/golang/v2/api.go index e4ab7b8e7d4..6516e75af7a 100644 --- a/pkg/plugins/golang/v2/api.go +++ b/pkg/plugins/golang/v2/api.go @@ -27,9 +27,8 @@ import ( "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" @@ -38,18 +37,16 @@ import ( ) type createAPISubcommand struct { - config *config.Config + config config.Config // pattern indicates that we should use a plugin to build according to a pattern pattern string - resource *resource.Options + options *Options // Check if we have to scaffold resource and/or controller resourceFlag *pflag.Flag controllerFlag *pflag.Flag - doResource bool - doController bool // force indicates that the resource should be created even if it already exists force bool @@ -96,13 +93,6 @@ After the scaffold is written, api will run make on the project. func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.runMake, "make", true, "if true, run make after generating files") - fs.BoolVar(&p.doResource, "resource", true, - "if set, generate the resource without prompting the user") - p.resourceFlag = fs.Lookup("resource") - fs.BoolVar(&p.doController, "controller", true, - "if set, generate the controller without prompting the user") - p.controllerFlag = fs.Lookup("controller") - if os.Getenv("KUBEBUILDER_ENABLE_PLUGINS") != "" { fs.StringVar(&p.pattern, "pattern", "", "generates an API following an extension pattern (addon)") @@ -110,14 +100,26 @@ func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.force, "force", false, "attempt to create resource even if it already exists") - p.resource = &resource.Options{} - fs.StringVar(&p.resource.Kind, "kind", "", "resource Kind") - fs.StringVar(&p.resource.Group, "group", "", "resource Group") - fs.StringVar(&p.resource.Version, "version", "", "resource Version") - fs.BoolVar(&p.resource.Namespaced, "namespaced", true, "resource is namespaced") + + p.options = &Options{} + fs.StringVar(&p.options.Group, "group", "", "resource Group") + p.options.Domain = p.config.GetDomain() + fs.StringVar(&p.options.Version, "version", "", "resource Version") + fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") + // p.options.Plural can be set to specify an irregular plural form + + fs.BoolVar(&p.options.DoAPI, "resource", true, + "if set, generate the resource without prompting the user") + p.resourceFlag = fs.Lookup("resource") + p.options.CRDVersion = "v1beta1" + 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) { +func (p *createAPISubcommand) InjectConfig(c config.Config) { p.config = c } @@ -126,29 +128,29 @@ func (p *createAPISubcommand) Run() error { } func (p *createAPISubcommand) Validate() error { - if err := p.resource.ValidateV2(); err != nil { + if err := p.options.Validate(); err != nil { return err } reader := bufio.NewReader(os.Stdin) if !p.resourceFlag.Changed { fmt.Println("Create Resource [y/n]") - p.doResource = util.YesNo(reader) + p.options.DoAPI = util.YesNo(reader) } if !p.controllerFlag.Changed { fmt.Println("Create Controller [y/n]") - p.doController = util.YesNo(reader) + p.options.DoController = util.YesNo(reader) } // In case we want to scaffold a resource API we need to do some checks - if p.doResource { + if p.options.DoAPI { // Check that resource doesn't exist or flag force was set - if !p.force && p.config.GetResource(p.resource.Data()) != nil { + if !p.force && p.config.HasResource(p.options.GVK()) { return errors.New("API resource already exists") } // Check that the provided group can be added to the project - if !p.config.MultiGroup && len(p.config.Resources) != 0 && !p.config.HasGroup(p.resource.Group) { + if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.options.Group) { return fmt.Errorf("multiple groups are not allowed by default, to enable multi-group visit %s", "kubebuilder.io/migration/multi-group.html") } @@ -175,9 +177,9 @@ func (p *createAPISubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { return nil, fmt.Errorf("unknown pattern %q", p.pattern) } - // Create the actual resource from the resource options - res := p.resource.NewResource(p.config, p.doResource) - return scaffolds.NewAPIScaffolder(p.config, string(bp), res, p.doResource, p.doController, p.force, plugins), nil + // Create the resource from the options + res := p.options.NewResource(p.config) + return scaffolds.NewAPIScaffolder(p.config, string(bp), res, p.force, plugins), nil } func (p *createAPISubcommand) PostScaffold() error { diff --git a/pkg/plugins/golang/v2/edit.go b/pkg/plugins/golang/v2/edit.go index 43c1b134602..7c232a44bec 100644 --- a/pkg/plugins/golang/v2/edit.go +++ b/pkg/plugins/golang/v2/edit.go @@ -21,14 +21,14 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) type editSubcommand struct { - config *config.Config + config config.Config multigroup bool } @@ -53,11 +53,7 @@ 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) { - // v3 project configs get a 'layout' value. - if c.IsV3() { - c.Layout = plugin.KeyFor(Plugin{}) - } +func (p *editSubcommand) InjectConfig(c config.Config) { p.config = c } diff --git a/pkg/plugins/golang/v2/init.go b/pkg/plugins/golang/v2/init.go index eb95986236a..34d9d9eb2e7 100644 --- a/pkg/plugins/golang/v2/init.go +++ b/pkg/plugins/golang/v2/init.go @@ -24,8 +24,9 @@ import ( "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" @@ -33,7 +34,8 @@ import ( ) type initSubcommand struct { - config *config.Config + config config.Config + // For help text. commandName string @@ -41,6 +43,11 @@ type initSubcommand struct { license string owner string + // config options + domain string + repo string + name string + // flags fetchDeps bool skipGoVersionCheck bool @@ -85,19 +92,20 @@ func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { fs.StringVar(&p.owner, "owner", "", "owner to add to the copyright") // project args - fs.StringVar(&p.config.Repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+ + fs.StringVar(&p.domain, "domain", "my.domain", "domain for groups") + 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.") - fs.StringVar(&p.config.Domain, "domain", "my.domain", "domain for groups") - if p.config.IsV3() { - fs.StringVar(&p.config.ProjectName, "project-name", "", "name of this project") + if p.config.GetVersion().Compare(cfgv3alpha.Version) >= 0 { + fs.StringVar(&p.name, "project-name", "", "name of this project") } } -func (p *initSubcommand) InjectConfig(c *config.Config) { - // v3 project configs get a 'layout' value. - if c.IsV3() { - c.Layout = plugin.KeyFor(Plugin{}) +func (p *initSubcommand) InjectConfig(c config.Config) { + // v2+ project configs get a 'layout' value. + if c.GetVersion().Compare(cfgv3alpha.Version) >= 0 { + _ = c.SetLayout(plugin.KeyFor(Plugin{})) } + p.config = c } @@ -113,36 +121,46 @@ func (p *initSubcommand) Validate() error { } } - // Check if the project name is a valid k8s namespace (DNS 1123 label). - dir, err := os.Getwd() - if err != nil { - return fmt.Errorf("error getting current directory: %v", err) - } - projectName := strings.ToLower(filepath.Base(dir)) - if p.config.IsV3() { - if p.config.ProjectName == "" { - p.config.ProjectName = projectName - } else { - projectName = p.config.ProjectName + if p.config.GetVersion().Compare(cfgv3alpha.Version) >= 0 { + // Assign a default project name + if p.name == "" { + dir, err := os.Getwd() + if err != nil { + return fmt.Errorf("error getting current directory: %v", err) + } + p.name = strings.ToLower(filepath.Base(dir)) + } + // Check if the project name is a valid k8s namespace (DNS 1123 label). + if err := validation.IsDNS1123Label(p.name); err != nil { + return fmt.Errorf("project name (%s) is invalid: %v", p.name, err) } - } - if err := validation.IsDNS1123Label(projectName); err != nil { - return fmt.Errorf("project name (%s) is invalid: %v", projectName, err) } // Try to guess repository if flag is not set. - if p.config.Repo == "" { + if p.repo == "" { repoPath, err := util.FindCurrentRepo() if err != nil { return fmt.Errorf("error finding current repository: %v", err) } - p.config.Repo = repoPath + p.repo = repoPath } return nil } func (p *initSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { + if err := p.config.SetDomain(p.domain); err != nil { + return nil, err + } + if err := p.config.SetRepository(p.repo); err != nil { + return nil, err + } + if p.config.GetVersion().Compare(cfgv3alpha.Version) >= 0 { + if err := p.config.SetProjectName(p.name); err != nil { + return nil, err + } + } + return scaffolds.NewInitScaffolder(p.config, p.license, p.owner), nil } diff --git a/pkg/plugins/golang/v2/options.go b/pkg/plugins/golang/v2/options.go new file mode 100644 index 00000000000..692a57123c7 --- /dev/null +++ b/pkg/plugins/golang/v2/options.go @@ -0,0 +1,241 @@ +/* +Copyright 2021 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 v2 + +import ( + "fmt" + "path" + "regexp" + "strings" + + newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" +) + +const ( + v1beta1 = "v1beta1" + v1 = "v1" + + versionPattern = "^v\\d+(?:alpha\\d+|beta\\d+)?$" + groupPresent = "group flag present but empty" + versionPresent = "version flag present but empty" + kindPresent = "kind flag present but empty" + groupRequired = "group cannot be empty" + versionRequired = "version cannot be empty" + kindRequired = "kind cannot be empty" +) + +var ( + versionRegex = regexp.MustCompile(versionPattern) + + coreGroups = map[string]string{ + "admission": "k8s.io", + "admissionregistration": "k8s.io", + "apps": "", + "auditregistration": "k8s.io", + "apiextensions": "k8s.io", + "authentication": "k8s.io", + "authorization": "k8s.io", + "autoscaling": "", + "batch": "", + "certificates": "k8s.io", + "coordination": "k8s.io", + "core": "", + "events": "k8s.io", + "extensions": "", + "imagepolicy": "k8s.io", + "networking": "k8s.io", + "node": "k8s.io", + "metrics": "k8s.io", + "policy": "", + "rbac.authorization": "k8s.io", + "scheduling": "k8s.io", + "setting": "k8s.io", + "storage": "k8s.io", + } +) + +// Options contains the information required to build a new resource.Resource. +type Options struct { + // Group is the resource's group. Does not contain the domain. + Group string + // Domain is the resource's domain. + Domain string + // Version is the resource's version. + Version string + // Kind is the resource's kind. + Kind string + + // Plural is the resource's kind plural form. + // Optional + Plural string + + // CRDVersion is the CustomResourceDefinition API version that will be used for the resource. + CRDVersion string + // WebhookVersion is the {Validating,Mutating}WebhookConfiguration API version that will be used for the resource. + WebhookVersion string + + // Namespaced is true if the resource should be namespaced. + Namespaced bool + + // Flags that define which parts should be scaffolded + DoAPI bool + DoController bool + DoDefaulting bool + DoValidation bool + DoConversion bool +} + +// Validate verifies that all the fields have valid values +func (opts Options) Validate() error { + // Check that the required flags did not get a flag as their value + // We can safely look for a '-' as the first char as none of the fields accepts it + // NOTE: We must do this for all the required flags first or we may output the wrong + // error as flags may seem to be missing because Cobra assigned them to another flag. + if strings.HasPrefix(opts.Group, "-") { + return fmt.Errorf(groupPresent) + } + if strings.HasPrefix(opts.Version, "-") { + return fmt.Errorf(versionPresent) + } + if strings.HasPrefix(opts.Kind, "-") { + return fmt.Errorf(kindPresent) + } + // Now we can check that all the required flags are not empty + if len(opts.Group) == 0 { + return fmt.Errorf(groupRequired) + } + if len(opts.Version) == 0 { + return fmt.Errorf(versionRequired) + } + if len(opts.Kind) == 0 { + return fmt.Errorf(kindRequired) + } + + // Check if the qualified group has a valid DNS1123 subdomain value + if err := validation.IsDNS1123Subdomain(opts.QualifiedGroup()); err != nil { + return fmt.Errorf("either group or domain is invalid: (%v)", err) + } + + // Check if the version follows the valid pattern + if !versionRegex.MatchString(opts.Version) { + return fmt.Errorf("version must match %s (was %s)", versionPattern, opts.Version) + } + + validationErrors := []string{} + + // Require kind to start with an uppercase character + if string(opts.Kind[0]) == strings.ToLower(string(opts.Kind[0])) { + validationErrors = append(validationErrors, "kind must start with an uppercase character") + } + + validationErrors = append(validationErrors, validation.IsDNS1035Label(strings.ToLower(opts.Kind))...) + + if len(validationErrors) != 0 { + return fmt.Errorf("invalid Kind: %#v", validationErrors) + } + + // TODO: validate plural strings if provided + + // Ensure apiVersions for k8s types are empty or valid. + for typ, apiVersion := range map[string]string{ + "CRD": opts.CRDVersion, + "Webhook": opts.WebhookVersion, + } { + switch apiVersion { + case "", v1beta1, v1: + default: + return fmt.Errorf("%s version must be one of: v1, v1beta1", typ) + } + } + + return nil +} + +// QualifiedGroup returns the fully qualified group name with the available information. +func (opts Options) QualifiedGroup() string { + if opts.Domain == "" { + return opts.Group + } + return fmt.Sprintf("%s.%s", opts.Group, opts.Domain) +} + +// GVK returns the GVK identifier of a resource. +func (opts Options) GVK() resource.GVK { + return resource.GVK{ + Group: opts.Group, + Domain: opts.Domain, + Version: opts.Version, + Kind: opts.Kind, + } +} + +// NewResource creates a new resource from the options +func (opts Options) NewResource(c newconfig.Config) resource.Resource { + res := resource.Resource{ + GVK: opts.GVK(), + Path: resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()), + Controller: opts.DoController, + } + + if opts.Plural != "" { + res.Plural = opts.Plural + } else { + // If not provided, compute a plural for for Kind + res.Plural = resource.RegularPlural(opts.Kind) + } + + if opts.DoAPI { + res.API = &resource.API{ + CRDVersion: opts.CRDVersion, + Namespaced: opts.Namespaced, + } + } else { + // Make sure that the pointer is not nil to prevent pointer dereference errors + res.API = &resource.API{} + } + + if opts.DoDefaulting || opts.DoValidation || opts.DoConversion { + res.Webhooks = &resource.Webhooks{ + WebhookVersion: opts.WebhookVersion, + Defaulting: opts.DoDefaulting, + Validation: opts.DoValidation, + Conversion: opts.DoConversion, + } + } else { + // Make sure that the pointer is not nil to prevent pointer dereference errors + res.Webhooks = &resource.Webhooks{} + } + + // domain and path may need to be changed in case we are referring to a builtin core resource: + // - Check if we are scaffolding the resource now => project resource + // - Check if we already scaffolded the resource => project resource + // - Check if the resource group is a well-known core group => builtin core resource + // - In any other case, default to => project resource + // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath + if !opts.DoAPI { + if !c.HasResource(opts.GVK()) { + if domain, found := coreGroups[opts.Group]; found { + res.Domain = domain + res.Path = path.Join("k8s.io", "api", opts.Group, opts.Version) + } + } + } + + return res +} diff --git a/pkg/plugins/golang/v2/options_test.go b/pkg/plugins/golang/v2/options_test.go new file mode 100644 index 00000000000..038121e21ba --- /dev/null +++ b/pkg/plugins/golang/v2/options_test.go @@ -0,0 +1,270 @@ +/* +Copyright 2021 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 v2 + +import ( + "path" + "strings" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/extensions/table" + . "github.com/onsi/gomega" + + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" +) + +var _ = Describe("Options", func() { + Context("Validate", func() { + DescribeTable("should succeed for valid options", + func(options *Options) { Expect(options.Validate()).To(Succeed()) }, + Entry("full GVK", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("missing domain", + &Options{Group: "crew", Version: "v1", Kind: "FirstMate"}), + Entry("kind with multiple initial uppercase characters", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FIRSTMate"}), + ) + + DescribeTable("should fail for invalid options", + func(options *Options) { Expect(options.Validate()).NotTo(Succeed()) }, + Entry("group flag captured another flag", + &Options{Group: "--version"}), + Entry("version flag captured another flag", + &Options{Version: "--kind"}), + Entry("kind flag captured another flag", + &Options{Kind: "--group"}), + Entry("missing group", + &Options{Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("group with uppercase characters", + &Options{Group: "Crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("group with non-alpha characters", + &Options{Group: "crew1*?", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("missing version", + &Options{Group: "crew", Domain: "test.io", Kind: "FirstMate"}), + Entry("version without v prefix", + &Options{Group: "crew", Domain: "test.io", Version: "1", Kind: "FirstMate"}), + Entry("unstable version without v prefix", + &Options{Group: "crew", Domain: "test.io", Version: "1beta1", Kind: "FirstMate"}), + Entry("unstable version with wrong prefix", + &Options{Group: "crew", Domain: "test.io", Version: "a1beta1", Kind: "FirstMate"}), + Entry("unstable version without alpha/beta number", + &Options{Group: "crew", Domain: "test.io", Version: "v1beta", Kind: "FirstMate"}), + Entry("multiple unstable version", + &Options{Group: "crew", Domain: "test.io", Version: "v1beta1alpha1", Kind: "FirstMate"}), + Entry("missing kind", + &Options{Group: "crew", Domain: "test.io", Version: "v1"}), + Entry("kind is too long", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: strings.Repeat("a", 64)}), + Entry("kind with whitespaces", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "First Mate"}), + Entry("kind ends with `-`", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate-"}), + Entry("kind starts with a decimal character", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "1FirstMate"}), + Entry("kind starts with a lowercase character", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "firstMate"}), + Entry("Invalid CRD version", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", CRDVersion: "a"}), + Entry("Invalid webhook version", + &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", WebhookVersion: "a"}), + ) + }) + + Context("NewResource", func() { + var cfg config.Config + + BeforeEach(func() { + cfg = cfgv2.New() + _ = cfg.SetRepository("test") + }) + + DescribeTable("should succeed if the Resource is valid", + func(options *Options) { + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Group).To(Equal(options.Group)) + Expect(resource.Domain).To(Equal(options.Domain)) + Expect(resource.Version).To(Equal(options.Version)) + Expect(resource.Kind).To(Equal(options.Kind)) + if multiGroup { + Expect(resource.Path).To(Equal( + path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + } else { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + } + Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) + Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) + Expect(resource.Controller).To(Equal(options.DoController)) + Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) + Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) + Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) + Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) + Expect(resource.PackageName()).To(Equal(options.Group)) + Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) + } + }, + Entry("basic", &Options{ + Group: "crew", + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + }), + Entry("API", &Options{ + Group: "crew", + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + DoAPI: true, + CRDVersion: v1beta1, + Namespaced: true, + }), + Entry("Controller", &Options{ + Group: "crew", + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + DoController: true, + }), + Entry("Webhooks", &Options{ + Group: "crew", + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + DoDefaulting: true, + DoValidation: true, + DoConversion: true, + WebhookVersion: v1beta1, + }), + ) + + DescribeTable("should default the Plural by pluralizing the Kind", + func(kind, plural string) { + options := &Options{Group: "crew", Version: "v1", Kind: kind} + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Plural).To(Equal(plural)) + } + }, + Entry("for `FirstMate`", "FirstMate", "firstmates"), + Entry("for `Fish`", "Fish", "fish"), + Entry("for `Helmswoman`", "Helmswoman", "helmswomen"), + ) + + DescribeTable("should keep the Plural if specified", + func(kind, plural string) { + options := &Options{Group: "crew", Version: "v1", Kind: kind, Plural: plural} + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Plural).To(Equal(plural)) + } + }, + Entry("for `FirstMate`", "FirstMate", "mates"), + Entry("for `Fish`", "Fish", "shoal"), + ) + + DescribeTable("should allow hyphens and dots in group names", + func(group, safeGroup string) { + options := &Options{ + Group: group, + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + } + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Group).To(Equal(options.Group)) + if multiGroup { + Expect(resource.Path).To(Equal( + path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + } else { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + } + Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) + Expect(resource.PackageName()).To(Equal(safeGroup)) + Expect(resource.ImportAlias()).To(Equal(safeGroup + options.Version)) + } + }, + Entry("for hyphen-containing group", "my-project", "myproject"), + Entry("for dot-containing group", "my.project", "myproject"), + ) + + It("should not append '.' if provided an empty domain", func() { + options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"} + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.QualifiedGroup()).To(Equal(options.Group)) + } + }) + + DescribeTable("should use core apis", + func(group, qualified string) { + options := &Options{ + Group: group, + Domain: "test.io", + Version: "v1", + Kind: "FirstMate", + } + Expect(options.Validate()).To(Succeed()) + + for _, multiGroup := range []bool{false, true} { + if multiGroup { + Expect(cfg.SetMultiGroup()).To(Succeed()) + } + + resource := options.NewResource(cfg) + Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) + Expect(resource.API.CRDVersion).To(Equal("")) + Expect(resource.QualifiedGroup()).To(Equal(qualified)) + } + }, + Entry("for `apps`", "apps", "apps"), + Entry("for `authentication`", "authentication", "authentication.k8s.io"), + ) + }) +}) diff --git a/pkg/plugins/golang/v2/plugin.go b/pkg/plugins/golang/v2/plugin.go index d106e3f53d4..314ba74d4b4 100644 --- a/pkg/plugins/golang/v2/plugin.go +++ b/pkg/plugins/golang/v2/plugin.go @@ -17,7 +17,9 @@ limitations under the License. package v2 import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" + cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins" ) @@ -25,7 +27,7 @@ import ( const pluginName = "go" + plugins.DefaultNameQualifier var ( - supportedProjectVersions = []string{config.Version2, config.Version3Alpha} + supportedProjectVersions = []config.Version{cfgv2.Version, cfgv3alpha.Version} pluginVersion = plugin.Version{Number: 2} ) @@ -46,7 +48,7 @@ func (Plugin) Name() string { return pluginName } func (Plugin) Version() plugin.Version { return pluginVersion } // SupportedProjectVersions returns an array with all project versions supported by the plugin -func (Plugin) SupportedProjectVersions() []string { return supportedProjectVersions } +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 } diff --git a/pkg/plugins/golang/v2/scaffolds/api.go b/pkg/plugins/golang/v2/scaffolds/api.go index 02088a8b5cf..37a3c0772ee 100644 --- a/pkg/plugins/golang/v2/scaffolds/api.go +++ b/pkg/plugins/golang/v2/scaffolds/api.go @@ -19,8 +19,8 @@ package scaffolds import ( "fmt" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/api" @@ -42,61 +42,58 @@ var _ cmdutil.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 + config config.Config boilerplate string - resource *resource.Resource + resource resource.Resource + // plugins is the list of plugins we should allow to transform our generated scaffolding plugins []model.Plugin - // doResource indicates whether to scaffold API Resource or not - doResource bool - // doController indicates whether to scaffold controller files or not - doController bool + // 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, + config config.Config, boilerplate string, - res *resource.Resource, - doResource, doController, force bool, + res resource.Resource, + force bool, plugins []model.Plugin, ) cmdutil.Scaffolder { return &apiScaffolder{ - config: config, - boilerplate: boilerplate, - resource: res, - plugins: plugins, - doResource: doResource, - doController: doController, - force: force, + config: config, + boilerplate: boilerplate, + resource: res, + plugins: plugins, + force: force, } } // Scaffold implements Scaffolder func (s *apiScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - - switch { - case s.config.IsV2(), s.config.IsV3(): - return s.scaffold() - default: - return fmt.Errorf("unknown project version %v", s.config.Version) - } + return s.scaffold() } func (s *apiScaffolder) newUniverse() *model.Universe { return model.NewUniverse( model.WithConfig(s.config), model.WithBoilerplate(s.boilerplate), - model.WithResource(s.resource), + model.WithResource(&s.resource), ) } func (s *apiScaffolder) scaffold() error { - if s.doResource { - s.config.UpdateResources(s.resource.Data()) + // Keep track of these values before the update + doAPI := s.resource.HasAPI() + doController := s.resource.HasController() + + if doAPI { + + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), @@ -108,7 +105,7 @@ func (s *apiScaffolder) scaffold() error { &patches.EnableWebhookPatch{}, &patches.EnableCAInjectionPatch{}, ); err != nil { - return fmt.Errorf("error scaffolding APIs: %v", err) + return fmt.Errorf("error scaffolding APIs: %w", err) } if err := machinery.NewScaffold().Execute( @@ -121,11 +118,11 @@ func (s *apiScaffolder) scaffold() error { } - if s.doController { + if doController { if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), - &controllers.SuiteTest{WireResource: s.doResource, Force: s.force}, - &controllers.Controller{WireResource: s.doResource, Force: s.force}, + &controllers.SuiteTest{Force: s.force}, + &controllers.Controller{Force: s.force}, ); err != nil { return fmt.Errorf("error scaffolding controller: %v", err) } @@ -133,7 +130,7 @@ func (s *apiScaffolder) scaffold() error { if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), - &templates.MainUpdater{WireResource: s.doResource, WireController: s.doController}, + &templates.MainUpdater{WireResource: doAPI, WireController: doController}, ); err != nil { return fmt.Errorf("error updating main.go: %v", err) } diff --git a/pkg/plugins/golang/v2/scaffolds/edit.go b/pkg/plugins/golang/v2/scaffolds/edit.go index 8dcc606488f..4c5def9d576 100644 --- a/pkg/plugins/golang/v2/scaffolds/edit.go +++ b/pkg/plugins/golang/v2/scaffolds/edit.go @@ -21,19 +21,19 @@ import ( "io/ioutil" "strings" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) var _ cmdutil.Scaffolder = &editScaffolder{} type editScaffolder struct { - config *config.Config + config config.Config multigroup bool } // NewEditScaffolder returns a new Scaffolder for configuration edit operations -func NewEditScaffolder(config *config.Config, multigroup bool) cmdutil.Scaffolder { +func NewEditScaffolder(config config.Config, multigroup bool) cmdutil.Scaffolder { return &editScaffolder{ config: config, multigroup: multigroup, @@ -63,11 +63,15 @@ func (s *editScaffolder) Scaffold() error { } // Ignore the error encountered, if the file is already in desired format. - if err != nil && s.multigroup != s.config.MultiGroup { + if err != nil && s.multigroup != s.config.IsMultiGroup() { return err } - s.config.MultiGroup = s.multigroup + 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. diff --git a/pkg/plugins/golang/v2/scaffolds/init.go b/pkg/plugins/golang/v2/scaffolds/init.go index c1cdd657192..d0674f28395 100644 --- a/pkg/plugins/golang/v2/scaffolds/init.go +++ b/pkg/plugins/golang/v2/scaffolds/init.go @@ -21,8 +21,8 @@ import ( "io/ioutil" "path/filepath" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/certmanager" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/config/kdefault" @@ -49,14 +49,14 @@ const ( var _ cmdutil.Scaffolder = &initScaffolder{} type initScaffolder struct { - config *config.Config + config config.Config boilerplatePath string license string owner string } // NewInitScaffolder returns a new Scaffolder for project initialization operations -func NewInitScaffolder(config *config.Config, license, owner string) cmdutil.Scaffolder { +func NewInitScaffolder(config config.Config, license, owner string) cmdutil.Scaffolder { return &initScaffolder{ config: config, boilerplatePath: filepath.Join("hack", "boilerplate.go.txt"), @@ -75,13 +75,7 @@ func (s *initScaffolder) newUniverse(boilerplate string) *model.Universe { // Scaffold implements Scaffolder func (s *initScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - - switch { - case s.config.IsV2(), s.config.IsV3(): - return s.scaffold() - default: - return fmt.Errorf("unknown project version %v", s.config.Version) - } + return s.scaffold() } func (s *initScaffolder) scaffold() error { diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go index d35163ebba9..bccba858d49 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/group.go @@ -53,7 +53,7 @@ const groupTemplate = `{{ .Boilerplate }} // Package {{ .Resource.Version }} contains API Schema definitions for the {{ .Resource.Group }} {{ .Resource.Version }} API group //+kubebuilder:object:generate=true -//+groupName={{ .Resource.Domain }} +//+groupName={{ .Resource.QualifiedGroup }} package {{ .Resource.Version }} import ( @@ -63,7 +63,7 @@ import ( var ( // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "{{ .Resource.Domain }}", Version: "{{ .Resource.Version }}"} + 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} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go index 675f278a802..462830c5c6f 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go @@ -87,7 +87,7 @@ type {{ .Resource.Kind }}Status struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -{{ if not .Resource.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} +{{ if not .Resource.API.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} // {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API type {{ .Resource.Kind }} struct { diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go index 13a09c390d3..806a37d5524 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/webhook.go @@ -34,12 +34,7 @@ type Webhook struct { // nolint:maligned file.ResourceMixin // Is the Group domain for the Resource replacing '.' with '-' - GroupDomainWithDash string - - // If scaffold the defaulting webhook - Defaulting bool - // If scaffold the validating webhook - Validating bool + QualifiedGroupWithDash string } // SetTemplateDefaults implements file.Template @@ -55,17 +50,17 @@ func (f *Webhook) SetTemplateDefaults() error { fmt.Println(f.Path) webhookTemplate := webhookTemplate - if f.Defaulting { + if f.Resource.HasDefaultingWebhook() { webhookTemplate = webhookTemplate + defaultingWebhookTemplate } - if f.Validating { + if f.Resource.HasValidationWebhook() { webhookTemplate = webhookTemplate + validatingWebhookTemplate } f.TemplateBody = webhookTemplate f.IfExistsAction = file.Error - f.GroupDomainWithDash = strings.Replace(f.Resource.Domain, ".", "-", -1) + f.QualifiedGroupWithDash = strings.Replace(f.Resource.QualifiedGroup(), ".", "-", -1) return nil } @@ -78,10 +73,10 @@ package {{ .Resource.Version }} import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" - {{- if .Validating }} + {{- if .Resource.HasValidationWebhook }} "k8s.io/apimachinery/pkg/runtime" {{- end }} - {{- if or .Validating .Defaulting }} + {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }} "sigs.k8s.io/controller-runtime/pkg/webhook" {{- end }} ) @@ -100,7 +95,7 @@ func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error { //nolint:lll defaultingWebhookTemplate = ` -//+kubebuilder:webhook:path=/mutate-{{ .GroupDomainWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=true,failurePolicy=fail,groups={{ .Resource.Domain }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}.kb.io +//+kubebuilder:webhook:path=/mutate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=true,failurePolicy=fail,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}.kb.io var _ webhook.Defaulter = &{{ .Resource.Kind }}{} @@ -114,7 +109,7 @@ func (r *{{ .Resource.Kind }}) Default() { //nolint:lll validatingWebhookTemplate = ` // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -//+kubebuilder:webhook:verbs=create;update,path=/validate-{{ .GroupDomainWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=false,failurePolicy=fail,groups={{ .Resource.Domain }},resources={{ .Resource.Plural }},versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}.kb.io +//+kubebuilder:webhook:verbs=create;update,path=/validate-{{ .QualifiedGroupWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=false,failurePolicy=fail,groups={{ .Resource.QualifiedGroup }},resources={{ .Resource.Plural }},versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}.kb.io var _ webhook.Validator = &{{ .Resource.Kind }}{} diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go index d737c659d3b..fd06bdf7848 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/kustomization.go @@ -78,7 +78,7 @@ func (f *Kustomization) GetCodeFragments() file.CodeFragmentsMap { // Generate resource code fragments res := make([]string, 0) - res = append(res, fmt.Sprintf(resourceCodeFragment, f.Resource.Domain, f.Resource.Plural)) + res = append(res, fmt.Sprintf(resourceCodeFragment, f.Resource.QualifiedGroup(), f.Resource.Plural)) // Generate resource code fragments webhookPatch := make([]string, 0) diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go index 09ce35d75e2..00d03f090d1 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go @@ -49,5 +49,5 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: {{ .Resource.Plural }}.{{ .Resource.Domain }} + name: {{ .Resource.Plural }}.{{ .Resource.QualifiedGroup }} ` diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go index b1495ff09ba..8e3fc5d051a 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go @@ -47,7 +47,7 @@ const enableWebhookPatchTemplate = `# The following patch enables conversion web apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: - name: {{ .Resource.Plural }}.{{ .Resource.Domain }} + name: {{ .Resource.Plural }}.{{ .Resource.QualifiedGroup }} spec: conversion: strategy: Webhook diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go index c62c2cebcd4..3de358308bd 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_editor_role.go @@ -49,7 +49,7 @@ metadata: name: {{ lower .Resource.Kind }}-editor-role rules: - apiGroups: - - {{ .Resource.Domain }} + - {{ .Resource.QualifiedGroup }} resources: - {{ .Resource.Plural }} verbs: @@ -61,7 +61,7 @@ rules: - update - watch - apiGroups: - - {{ .Resource.Domain }} + - {{ .Resource.QualifiedGroup }} resources: - {{ .Resource.Plural }}/status verbs: diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go index b89716c46ca..5898a8fe334 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/rbac/crd_viewer_role.go @@ -49,7 +49,7 @@ metadata: name: {{ lower .Resource.Kind }}-viewer-role rules: - apiGroups: - - {{ .Resource.Domain }} + - {{ .Resource.QualifiedGroup }} resources: - {{ .Resource.Plural }} verbs: @@ -57,7 +57,7 @@ rules: - list - watch - apiGroups: - - {{ .Resource.Domain }} + - {{ .Resource.QualifiedGroup }} resources: - {{ .Resource.Plural }}/status verbs: diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go index 9d51be29b6c..b9b349ff336 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/config/samples/crd_sample.go @@ -50,7 +50,7 @@ func (f *CRDSample) SetTemplateDefaults() error { return nil } -const crdSampleTemplate = `apiVersion: {{ .Resource.Domain }}/{{ .Resource.Version }} +const crdSampleTemplate = `apiVersion: {{ .Resource.QualifiedGroup }}/{{ .Resource.Version }} kind: {{ .Resource.Kind }} metadata: name: {{ lower .Resource.Kind }}-sample diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go index 61710581e27..213b9b08561 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go @@ -33,9 +33,6 @@ type Controller struct { file.BoilerplateMixin file.ResourceMixin - // WireResource defines the api resources are generated or not. - WireResource bool - Force bool } @@ -73,8 +70,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - {{ if .WireResource -}} - {{ .Resource.ImportAlias }} "{{ .Resource.Package }}" + {{ if .Resource.HasAPI -}} + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" {{- end }} ) @@ -85,8 +82,8 @@ type {{ .Resource.Kind }}Reconciler struct { Scheme *runtime.Scheme } -//+kubebuilder:rbac:groups={{ .Resource.Domain }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups={{ .Resource.Domain }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch +//+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 func (r *{{ .Resource.Kind }}Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() @@ -99,7 +96,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(req ctrl.Request) (ctrl.Resul func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - {{ if .WireResource -}} + {{ if .Resource.HasAPI -}} For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}). {{- else -}} // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go index 149828522bf..34e1b418aa9 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -37,9 +37,6 @@ type SuiteTest struct { // CRDDirectoryRelativePath define the Path for the CRD CRDDirectoryRelativePath string - // WireResource defines the api resources are generated or not. - WireResource bool - Force bool } @@ -101,14 +98,14 @@ func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { // Generate import code fragments imports := make([]string, 0) - if f.WireResource { - imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias, f.Resource.Package)) + if f.Resource.HasAPI() { + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } // Generate add scheme code fragments addScheme := make([]string, 0) - if f.WireResource { - addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias)) + if f.Resource.HasAPI() { + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) } // Only store code fragments in the map if the slices are non-empty diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go index e3dd331ad12..e9f4b64362d 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/main.go @@ -139,7 +139,7 @@ func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { // Generate import code fragments imports := make([]string, 0) if f.WireResource { - imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias, f.Resource.Package)) + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } if f.WireController { @@ -147,14 +147,14 @@ func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo)) } else { imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment, - f.Resource.GroupPackageName, f.Repo, f.Resource.Group)) + 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)) + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) } // Generate setup code fragments @@ -165,12 +165,12 @@ func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { f.Resource.Kind, f.Resource.Kind, f.Resource.Kind)) } else { setup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment, - f.Resource.GroupPackageName, f.Resource.Kind, f.Resource.Kind, f.Resource.Kind)) + f.Resource.PackageName(), f.Resource.Kind, f.Resource.Kind, f.Resource.Kind)) } } if f.WireWebhook { setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, - f.Resource.ImportAlias, f.Resource.Kind, f.Resource.Kind)) + f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) } // Only store code fragments in the map if the slices are non-empty diff --git a/pkg/plugins/golang/v2/scaffolds/webhook.go b/pkg/plugins/golang/v2/scaffolds/webhook.go index b38642ad011..c404f571eef 100644 --- a/pkg/plugins/golang/v2/scaffolds/webhook.go +++ b/pkg/plugins/golang/v2/scaffolds/webhook.go @@ -19,8 +19,8 @@ package scaffolds import ( "fmt" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates/api" @@ -31,66 +31,51 @@ import ( var _ cmdutil.Scaffolder = &webhookScaffolder{} type webhookScaffolder struct { - config *config.Config + config config.Config boilerplate string - resource *resource.Resource - - // v2 - defaulting, validation, conversion bool + resource resource.Resource } // NewWebhookScaffolder returns a new Scaffolder for v2 webhook creation operations func NewWebhookScaffolder( - config *config.Config, + config config.Config, boilerplate string, - resource *resource.Resource, - defaulting bool, - validation bool, - conversion bool, + resource resource.Resource, ) cmdutil.Scaffolder { return &webhookScaffolder{ config: config, boilerplate: boilerplate, resource: resource, - defaulting: defaulting, - validation: validation, - conversion: conversion, } } // Scaffold implements Scaffolder func (s *webhookScaffolder) Scaffold() error { fmt.Println("Writing scaffold for you to edit...") - - switch { - case s.config.IsV2(), s.config.IsV3(): - return s.scaffold() - default: - return fmt.Errorf("unknown project version %v", s.config.Version) - } + return s.scaffold() } func (s *webhookScaffolder) newUniverse() *model.Universe { return model.NewUniverse( model.WithConfig(s.config), model.WithBoilerplate(s.boilerplate), - model.WithResource(s.resource), + model.WithResource(&s.resource), ) } func (s *webhookScaffolder) scaffold() error { - if s.conversion { - 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.`) - } - if err := machinery.NewScaffold().Execute( s.newUniverse(), - &api.Webhook{Defaulting: s.defaulting, Validating: s.validation}, + &api.Webhook{}, &templates.MainUpdater{WireWebhook: true}, ); err != nil { return err } + if s.resource.HasConversionWebhook() { + 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.`) + } + return nil } diff --git a/pkg/plugins/golang/v2/suite_test.go b/pkg/plugins/golang/v2/suite_test.go new file mode 100644 index 00000000000..de06fb4cf53 --- /dev/null +++ b/pkg/plugins/golang/v2/suite_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2021 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 v2 + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestGoPluginV2(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Go Plugin v2 Suite") +} diff --git a/pkg/plugins/golang/v2/webhook.go b/pkg/plugins/golang/v2/webhook.go index 5fbe63fd24c..f6df11591db 100644 --- a/pkg/plugins/golang/v2/webhook.go +++ b/pkg/plugins/golang/v2/webhook.go @@ -23,22 +23,18 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) type createWebhookSubcommand struct { - config *config.Config + config newconfig.Config // For help text. commandName string - resource *resource.Options - defaulting bool - validation bool - conversion bool + options *Options } var ( @@ -63,21 +59,23 @@ validating and (or) conversion webhooks. } func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { - p.resource = &resource.Options{} - fs.StringVar(&p.resource.Group, "group", "", "resource Group") - fs.StringVar(&p.resource.Version, "version", "", "resource Version") - fs.StringVar(&p.resource.Kind, "kind", "", "resource Kind") - fs.StringVar(&p.resource.Plural, "resource", "", "resource Resource") - - fs.BoolVar(&p.defaulting, "defaulting", false, + p.options = &Options{} + fs.StringVar(&p.options.Group, "group", "", "resource Group") + p.options.Domain = p.config.GetDomain() + fs.StringVar(&p.options.Version, "version", "", "resource Version") + fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") + fs.StringVar(&p.options.Plural, "resource", "", "resource irregular plural form") + + p.options.WebhookVersion = "v1beta1" + fs.BoolVar(&p.options.DoDefaulting, "defaulting", false, "if set, scaffold the defaulting webhook") - fs.BoolVar(&p.validation, "programmatic-validation", false, + fs.BoolVar(&p.options.DoValidation, "programmatic-validation", false, "if set, scaffold the validating webhook") - fs.BoolVar(&p.conversion, "conversion", false, + fs.BoolVar(&p.options.DoConversion, "conversion", false, "if set, scaffold the conversion webhook") } -func (p *createWebhookSubcommand) InjectConfig(c *config.Config) { +func (p *createWebhookSubcommand) InjectConfig(c newconfig.Config) { p.config = c } @@ -86,17 +84,17 @@ func (p *createWebhookSubcommand) Run() error { } func (p *createWebhookSubcommand) Validate() error { - if err := p.resource.ValidateV2(); err != nil { + if err := p.options.Validate(); err != nil { return err } - if !p.defaulting && !p.validation && !p.conversion { + if !p.options.DoDefaulting && !p.options.DoValidation && !p.options.DoConversion { 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 p.config.GetResource(p.resource.Data()) == nil { + if !p.config.HasResource(p.options.GVK()) { return fmt.Errorf("%s create webhook requires an api with the group,"+ " kind and version provided", p.commandName) } @@ -111,9 +109,9 @@ func (p *createWebhookSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { return nil, fmt.Errorf("unable to load boilerplate: %v", err) } - // Create the actual resource from the resource options - res := p.resource.NewResource(p.config, false) - return scaffolds.NewWebhookScaffolder(p.config, string(bp), res, p.defaulting, p.validation, p.conversion), nil + // Create the resource from the options + res := p.options.NewResource(p.config) + return scaffolds.NewWebhookScaffolder(p.config, string(bp), res), nil } func (p *createWebhookSubcommand) PostScaffold() error { diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index a1795d86b66..ab8a629f712 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -27,10 +27,10 @@ import ( "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" @@ -51,18 +51,16 @@ const ( const DefaultMainPath = "main.go" type createAPISubcommand struct { - config *config.Config + config config.Config // pattern indicates that we should use a plugin to build according to a pattern pattern string - resource *resource.Options + options *goPlugin.Options // Check if we have to scaffold resource and/or controller resourceFlag *pflag.Flag controllerFlag *pflag.Flag - doResource bool - doController bool // force indicates that the resource should be created even if it already exists force bool @@ -109,13 +107,6 @@ After the scaffold is written, api will run make on the project. func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.runMake, "make", true, "if true, run make after generating files") - fs.BoolVar(&p.doResource, "resource", true, - "if set, generate the resource without prompting the user") - p.resourceFlag = fs.Lookup("resource") - fs.BoolVar(&p.doController, "controller", true, - "if set, generate the controller without prompting the user") - p.controllerFlag = fs.Lookup("controller") - // TODO: remove this when a better solution for using addons is implemented. if os.Getenv("KUBEBUILDER_ENABLE_PLUGINS") != "" { fs.StringVar(&p.pattern, "pattern", "", @@ -124,16 +115,27 @@ func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.force, "force", false, "attempt to create resource even if it already exists") - p.resource = &resource.Options{} - fs.StringVar(&p.resource.Kind, "kind", "", "resource Kind") - fs.StringVar(&p.resource.Group, "group", "", "resource Group") - fs.StringVar(&p.resource.Version, "version", "", "resource Version") - fs.BoolVar(&p.resource.Namespaced, "namespaced", true, "resource is namespaced") - fs.StringVar(&p.resource.API.CRDVersion, "crd-version", defaultCRDVersion, + + p.options = &goPlugin.Options{} + fs.StringVar(&p.options.Group, "group", "", "resource Group") + p.options.Domain = p.config.GetDomain() + fs.StringVar(&p.options.Version, "version", "", "resource Version") + fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") + // p.options.Plural can be set to specify an 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.StringVar(&p.options.CRDVersion, "crd-version", defaultCRDVersion, "version of CustomResourceDefinition to scaffold. Options: [v1, v1beta1]") + 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) { +func (p *createAPISubcommand) InjectConfig(c config.Config) { p.config = c } @@ -142,11 +144,11 @@ func (p *createAPISubcommand) Run() error { } func (p *createAPISubcommand) Validate() error { - if err := p.resource.Validate(); err != nil { + if err := p.options.Validate(); err != nil { return err } - if p.resource.Group == "" && p.config.Domain == "" { + if p.options.Group == "" && p.options.Domain == "" { return fmt.Errorf("can not have group and domain both empty") } @@ -160,31 +162,30 @@ func (p *createAPISubcommand) Validate() error { reader := bufio.NewReader(os.Stdin) if !p.resourceFlag.Changed { fmt.Println("Create Resource [y/n]") - p.doResource = util.YesNo(reader) + p.options.DoAPI = util.YesNo(reader) } if !p.controllerFlag.Changed { fmt.Println("Create Controller [y/n]") - p.doController = util.YesNo(reader) + p.options.DoController = util.YesNo(reader) } // In case we want to scaffold a resource API we need to do some checks - if p.doResource { + if p.options.DoAPI { // Check that resource doesn't exist or flag force was set - res := p.config.GetResource(p.resource.Data()) - if !p.force && (res != nil && res.API != nil) { + if res, err := p.config.GetResource(p.options.GVK()); err == nil && res.HasAPI() && !p.force { return errors.New("API resource already exists") } // Check that the provided group can be added to the project - if !p.config.MultiGroup && len(p.config.Resources) != 0 && !p.config.HasGroup(p.resource.Group) { + if !p.config.IsMultiGroup() && p.config.ResourcesLength() != 0 && !p.config.HasGroup(p.options.Group) { return fmt.Errorf("multiple groups are not allowed by default, " + "to enable multi-group visit kubebuilder.io/migration/multi-group.html") } // Check CRDVersion against all other CRDVersions in p.config for compatibility. - if !p.config.IsCRDVersionCompatible(p.resource.API.CRDVersion) { + if !p.config.IsCRDVersionCompatible(p.options.CRDVersion) { return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q", - p.resource.API.CRDVersion) + p.options.CRDVersion) } } @@ -209,9 +210,9 @@ func (p *createAPISubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { return nil, fmt.Errorf("unknown pattern %q", p.pattern) } - // Create the actual resource from the resource options - res := p.resource.NewResource(p.config, p.doResource) - return scaffolds.NewAPIScaffolder(p.config, string(bp), res, p.doResource, p.doController, p.force, plugins), nil + // Create the resource from the options + res := p.options.NewResource(p.config) + return scaffolds.NewAPIScaffolder(p.config, string(bp), res, p.force, plugins), nil } func (p *createAPISubcommand) PostScaffold() error { diff --git a/pkg/plugins/golang/v3/edit.go b/pkg/plugins/golang/v3/edit.go index b42d300f6c7..503ed2fafe3 100644 --- a/pkg/plugins/golang/v3/edit.go +++ b/pkg/plugins/golang/v3/edit.go @@ -21,14 +21,14 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) type editSubcommand struct { - config *config.Config + config config.Config multigroup bool } @@ -53,7 +53,7 @@ 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) { +func (p *editSubcommand) InjectConfig(c config.Config) { p.config = c } diff --git a/pkg/plugins/golang/v3/init.go b/pkg/plugins/golang/v3/init.go index 7daabc47b3d..ebce61a60d4 100644 --- a/pkg/plugins/golang/v3/init.go +++ b/pkg/plugins/golang/v3/init.go @@ -24,8 +24,8 @@ import ( "github.com/spf13/pflag" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" @@ -33,7 +33,7 @@ import ( ) type initSubcommand struct { - config *config.Config + config config.Config // For help text. commandName string @@ -41,6 +41,12 @@ type initSubcommand struct { license string owner string + // config options + domain string + repo string + name string + componentConfig bool + // flags fetchDeps bool skipGoVersionCheck bool @@ -83,18 +89,19 @@ func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { 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") - fs.BoolVar(&p.config.ComponentConfig, "component-config", false, - "create a versioned ComponentConfig file, may be 'true' or 'false'") // project args - fs.StringVar(&p.config.Repo, "repo", "", "name to use for go module (e.g., github.com/user/repo), "+ + fs.StringVar(&p.domain, "domain", "my.domain", "domain for groups") + 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.") - fs.StringVar(&p.config.Domain, "domain", "my.domain", "domain for groups") - fs.StringVar(&p.config.ProjectName, "project-name", "", "name of this project") + fs.StringVar(&p.name, "project-name", "", "name of this project") + fs.BoolVar(&p.componentConfig, "component-config", false, + "create a versioned ComponentConfig file, may be 'true' or 'false'") } -func (p *initSubcommand) InjectConfig(c *config.Config) { - c.Layout = plugin.KeyFor(Plugin{}) +func (p *initSubcommand) InjectConfig(c config.Config) { + _ = c.SetLayout(plugin.KeyFor(Plugin{})) + p.config = c } @@ -115,31 +122,47 @@ func (p *initSubcommand) Validate() error { return err } - // Check if the project name is a valid k8s namespace (DNS 1123 label). - if p.config.ProjectName == "" { + // Assign a default project name + if p.name == "" { dir, err := os.Getwd() if err != nil { return fmt.Errorf("error getting current directory: %v", err) } - p.config.ProjectName = strings.ToLower(filepath.Base(dir)) + p.name = strings.ToLower(filepath.Base(dir)) } - if err := validation.IsDNS1123Label(p.config.ProjectName); err != nil { - return fmt.Errorf("project name (%s) is invalid: %v", p.config.ProjectName, err) + // Check if the project name is a valid k8s namespace (DNS 1123 label). + if err := validation.IsDNS1123Label(p.name); err != nil { + return fmt.Errorf("project name (%s) is invalid: %v", p.name, err) } // Try to guess repository if flag is not set. - if p.config.Repo == "" { + if p.repo == "" { repoPath, err := util.FindCurrentRepo() if err != nil { return fmt.Errorf("error finding current repository: %v", err) } - p.config.Repo = repoPath + p.repo = repoPath } return nil } func (p *initSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { + if err := p.config.SetDomain(p.domain); err != nil { + return nil, err + } + if err := p.config.SetRepository(p.repo); err != nil { + return nil, err + } + if err := p.config.SetProjectName(p.name); err != nil { + return nil, err + } + if p.componentConfig { + if err := p.config.SetComponentConfig(); err != nil { + return nil, err + } + } + return scaffolds.NewInitScaffolder(p.config, p.license, p.owner), nil } diff --git a/pkg/plugins/golang/v3/plugin.go b/pkg/plugins/golang/v3/plugin.go index edbae552e3a..f9b2d1af73f 100644 --- a/pkg/plugins/golang/v3/plugin.go +++ b/pkg/plugins/golang/v3/plugin.go @@ -17,7 +17,8 @@ limitations under the License. package v3 import ( - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins" ) @@ -25,7 +26,7 @@ import ( const pluginName = "go" + plugins.DefaultNameQualifier var ( - supportedProjectVersions = []string{config.Version3Alpha} + supportedProjectVersions = []config.Version{cfgv3alpha.Version} pluginVersion = plugin.Version{Number: 3} ) @@ -46,7 +47,7 @@ func (Plugin) Name() string { return pluginName } func (Plugin) Version() plugin.Version { return pluginVersion } // SupportedProjectVersions returns an array with all project versions supported by the plugin -func (Plugin) SupportedProjectVersions() []string { return supportedProjectVersions } +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 } diff --git a/pkg/plugins/golang/v3/scaffolds/api.go b/pkg/plugins/golang/v3/scaffolds/api.go index 9d7f798b8ad..0265d705b60 100644 --- a/pkg/plugins/golang/v3/scaffolds/api.go +++ b/pkg/plugins/golang/v3/scaffolds/api.go @@ -19,8 +19,8 @@ package scaffolds import ( "fmt" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/api" @@ -38,35 +38,31 @@ var _ cmdutil.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 + config config.Config boilerplate string - resource *resource.Resource + resource resource.Resource + // plugins is the list of plugins we should allow to transform our generated scaffolding plugins []model.Plugin - // doResource indicates whether to scaffold API Resource or not - doResource bool - // doController indicates whether to scaffold controller files or not - doController bool + // 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, + config config.Config, boilerplate string, - res *resource.Resource, - doResource, doController, force bool, + res resource.Resource, + force bool, plugins []model.Plugin, ) cmdutil.Scaffolder { return &apiScaffolder{ - config: config, - boilerplate: boilerplate, - resource: res, - plugins: plugins, - doResource: doResource, - doController: doController, - force: force, + config: config, + boilerplate: boilerplate, + resource: res, + plugins: plugins, + force: force, } } @@ -80,15 +76,21 @@ func (s *apiScaffolder) newUniverse() *model.Universe { return model.NewUniverse( model.WithConfig(s.config), model.WithBoilerplate(s.boilerplate), - model.WithResource(s.resource), + model.WithResource(&s.resource), ) } // TODO: re-use universe created by s.newUniverse() if possible. func (s *apiScaffolder) scaffold() error { - if s.doResource { + // Keep track of these values before the update + doAPI := s.resource.HasAPI() + doController := s.resource.HasController() - s.config.UpdateResources(s.resource.Data()) + if doAPI { + + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), @@ -97,8 +99,8 @@ func (s *apiScaffolder) scaffold() error { &samples.CRDSample{Force: s.force}, &rbac.CRDEditorRole{}, &rbac.CRDViewerRole{}, - &patches.EnableWebhookPatch{CRDVersion: s.resource.API.CRDVersion}, - &patches.EnableCAInjectionPatch{CRDVersion: s.resource.API.CRDVersion}, + &patches.EnableWebhookPatch{}, + &patches.EnableCAInjectionPatch{}, ); err != nil { return fmt.Errorf("error scaffolding APIs: %v", err) } @@ -106,19 +108,18 @@ func (s *apiScaffolder) scaffold() error { if err := machinery.NewScaffold().Execute( s.newUniverse(), &crd.Kustomization{}, - &crd.KustomizeConfig{CRDVersion: s.resource.API.CRDVersion}, + &crd.KustomizeConfig{}, ); err != nil { return fmt.Errorf("error scaffolding kustomization: %v", err) } } - if s.doController { + if doController { if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), - &controllers.SuiteTest{WireResource: s.doResource, Force: s.force}, - &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, WireResource: s.doResource, - Force: s.force}, + &controllers.SuiteTest{Force: s.force}, + &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force}, ); err != nil { return fmt.Errorf("error scaffolding controller: %v", err) } @@ -126,7 +127,7 @@ func (s *apiScaffolder) scaffold() error { if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), - &templates.MainUpdater{WireResource: s.doResource, WireController: s.doController}, + &templates.MainUpdater{WireResource: doAPI, WireController: doController}, ); err != nil { return fmt.Errorf("error updating main.go: %v", err) } diff --git a/pkg/plugins/golang/v3/scaffolds/edit.go b/pkg/plugins/golang/v3/scaffolds/edit.go index 1f210110c82..f4f92785359 100644 --- a/pkg/plugins/golang/v3/scaffolds/edit.go +++ b/pkg/plugins/golang/v3/scaffolds/edit.go @@ -21,19 +21,19 @@ import ( "io/ioutil" "strings" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" ) var _ cmdutil.Scaffolder = &editScaffolder{} type editScaffolder struct { - config *config.Config + config config.Config multigroup bool } // NewEditScaffolder returns a new Scaffolder for configuration edit operations -func NewEditScaffolder(config *config.Config, multigroup bool) cmdutil.Scaffolder { +func NewEditScaffolder(config config.Config, multigroup bool) cmdutil.Scaffolder { return &editScaffolder{ config: config, multigroup: multigroup, @@ -64,11 +64,15 @@ func (s *editScaffolder) Scaffold() error { } // Ignore the error encountered, if the file is already in desired format. - if err != nil && s.multigroup != s.config.MultiGroup { + if err != nil && s.multigroup != s.config.IsMultiGroup() { return err } - s.config.MultiGroup = s.multigroup + 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. diff --git a/pkg/plugins/golang/v3/scaffolds/init.go b/pkg/plugins/golang/v3/scaffolds/init.go index cc5fb251aab..e46478d42ed 100644 --- a/pkg/plugins/golang/v3/scaffolds/init.go +++ b/pkg/plugins/golang/v3/scaffolds/init.go @@ -21,8 +21,8 @@ import ( "io/ioutil" "path/filepath" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/certmanager" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault" @@ -48,14 +48,14 @@ const ( var _ cmdutil.Scaffolder = &initScaffolder{} type initScaffolder struct { - config *config.Config + config config.Config boilerplatePath string license string owner string } // NewInitScaffolder returns a new Scaffolder for project initialization operations -func NewInitScaffolder(config *config.Config, license, owner string) cmdutil.Scaffolder { +func NewInitScaffolder(config config.Config, license, owner string) cmdutil.Scaffolder { return &initScaffolder{ config: config, boilerplatePath: filepath.Join("hack", "boilerplate.go.txt"), diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go index e3ead93db6e..f8c1faa7e2b 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/group.go @@ -57,7 +57,7 @@ const groupTemplate = `{{ .Boilerplate }} // Package {{ .Resource.Version }} contains API Schema definitions for the {{ .Resource.Group }} {{ .Resource.Version }} API group //+kubebuilder:object:generate=true -//+groupName={{ .Resource.Domain }} +//+groupName={{ .Resource.QualifiedGroup }} package {{ .Resource.Version }} import ( @@ -67,7 +67,7 @@ import ( var ( // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "{{ .Resource.Domain }}", Version: "{{ .Resource.Version }}"} + 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} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go index 8147a5ad5f7..0875b04df88 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go @@ -91,7 +91,7 @@ type {{ .Resource.Kind }}Status struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -{{ if not .Resource.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} +{{ if not .Resource.API.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} // {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API type {{ .Resource.Kind }} struct { diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go index 5d276c012f2..f3f9c6ccd76 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook.go @@ -34,14 +34,7 @@ type Webhook struct { // nolint:maligned file.ResourceMixin // Is the Group domain for the Resource replacing '.' with '-' - GroupDomainWithDash string - - // Version of webhook marker to scaffold - WebhookVersion string - // If scaffold the defaulting webhook - Defaulting bool - // If scaffold the validating webhook - Validating bool + QualifiedGroupWithDash string Force bool } @@ -63,10 +56,10 @@ func (f *Webhook) SetTemplateDefaults() error { fmt.Println(f.Path) webhookTemplate := webhookTemplate - if f.Defaulting { + if f.Resource.HasDefaultingWebhook() { webhookTemplate = webhookTemplate + defaultingWebhookTemplate } - if f.Validating { + if f.Resource.HasValidationWebhook() { webhookTemplate = webhookTemplate + validatingWebhookTemplate } f.TemplateBody = webhookTemplate @@ -77,11 +70,7 @@ func (f *Webhook) SetTemplateDefaults() error { f.IfExistsAction = file.Error } - f.GroupDomainWithDash = strings.Replace(f.Resource.Domain, ".", "-", -1) - - if f.WebhookVersion == "" { - f.WebhookVersion = "v1" - } + f.QualifiedGroupWithDash = strings.Replace(f.Resource.QualifiedGroup(), ".", "-", -1) return nil } @@ -94,10 +83,10 @@ package {{ .Resource.Version }} import ( ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" - {{- if .Validating }} + {{- if .Resource.HasValidationWebhook }} "k8s.io/apimachinery/pkg/runtime" {{- end }} - {{- if or .Validating .Defaulting }} + {{- if or .Resource.HasValidationWebhook .Resource.HasDefaultingWebhook }} "sigs.k8s.io/controller-runtime/pkg/webhook" {{- end }} ) @@ -117,7 +106,7 @@ func (r *{{ .Resource.Kind }}) SetupWebhookWithManager(mgr ctrl.Manager) error { // TODO(estroz): update admissionReviewVersions to include v1 when controller-runtime supports that version. //nolint:lll defaultingWebhookTemplate = ` -//+kubebuilder:webhook:{{ if ne .WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .WebhookVersion }}{{"}"}},{{ end }}path=/mutate-{{ .GroupDomainWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=true,failurePolicy=fail,sideEffects=None,groups={{ .Resource.Domain }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=m{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={v1,v1beta1} +//+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={v1,v1beta1} var _ webhook.Defaulter = &{{ .Resource.Kind }}{} @@ -133,7 +122,7 @@ func (r *{{ .Resource.Kind }}) Default() { //nolint:lll validatingWebhookTemplate = ` // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -//+kubebuilder:webhook:{{ if ne .WebhookVersion "v1" }}webhookVersions={{"{"}}{{ .WebhookVersion }}{{"}"}},{{ end }}path=/validate-{{ .GroupDomainWithDash }}-{{ .Resource.Version }}-{{ lower .Resource.Kind }},mutating=false,failurePolicy=fail,sideEffects=None,groups={{ .Resource.Domain }},resources={{ .Resource.Plural }},verbs=create;update,versions={{ .Resource.Version }},name=v{{ lower .Resource.Kind }}.kb.io,admissionReviewVersions={v1,v1beta1} +//+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={v1,v1beta1} var _ webhook.Validator = &{{ .Resource.Kind }}{} diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go index 5d0483cac9d..9a87af34668 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomization.go @@ -78,7 +78,7 @@ func (f *Kustomization) GetCodeFragments() file.CodeFragmentsMap { // Generate resource code fragments res := make([]string, 0) - res = append(res, fmt.Sprintf(resourceCodeFragment, f.Resource.Domain, f.Resource.Plural)) + res = append(res, fmt.Sprintf(resourceCodeFragment, f.Resource.QualifiedGroup(), f.Resource.Plural)) // Generate resource code fragments webhookPatch := make([]string, 0) diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go index 700ef4df5f9..7b56a21c9df 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/kustomizeconfig.go @@ -22,16 +22,12 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) -const v1 = "v1" - var _ file.Template = &KustomizeConfig{} // KustomizeConfig scaffolds a file that configures the kustomization for the crd folder type KustomizeConfig struct { file.TemplateMixin - - // Version of CRD patch generated. - CRDVersion string + file.ResourceMixin } // SetTemplateDefaults implements file.Template @@ -42,10 +38,6 @@ func (f *KustomizeConfig) SetTemplateDefaults() error { f.TemplateBody = kustomizeConfigTemplate - if f.CRDVersion == "" { - f.CRDVersion = v1 - } - return nil } @@ -56,9 +48,9 @@ nameReference: version: v1 fieldSpecs: - kind: CustomResourceDefinition - version: {{ .CRDVersion }} + version: {{ .Resource.API.CRDVersion }} group: apiextensions.k8s.io - {{- if ne .CRDVersion "v1" }} + {{- if ne .Resource.API.CRDVersion "v1" }} path: spec/conversion/webhookClientConfig/service/name {{- else }} path: spec/conversion/webhook/clientConfig/service/name @@ -66,9 +58,9 @@ nameReference: namespace: - kind: CustomResourceDefinition - version: {{ .CRDVersion }} + version: {{ .Resource.API.CRDVersion }} group: apiextensions.k8s.io - {{- if ne .CRDVersion "v1" }} + {{- if ne .Resource.API.CRDVersion "v1" }} path: spec/conversion/webhookClientConfig/service/namespace {{- else }} path: spec/conversion/webhook/clientConfig/service/namespace diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go index df19081c768..c954670d1db 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablecainjection_patch.go @@ -22,17 +22,12 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/model/file" ) -const v1 = "v1" - var _ file.Template = &EnableCAInjectionPatch{} // EnableCAInjectionPatch scaffolds a file that defines the patch that injects CA into the CRD type EnableCAInjectionPatch struct { file.TemplateMixin file.ResourceMixin - - // Version of CRD patch to generate. - CRDVersion string } // SetTemplateDefaults implements file.Template @@ -44,22 +39,18 @@ func (f *EnableCAInjectionPatch) SetTemplateDefaults() error { f.TemplateBody = enableCAInjectionPatchTemplate - if f.CRDVersion == "" { - f.CRDVersion = v1 - } - return nil } //nolint:lll const enableCAInjectionPatchTemplate = `# The following patch adds a directive for certmanager to inject CA into the CRD -{{- if ne .CRDVersion "v1" }} +{{- if ne .Resource.API.CRDVersion "v1" }} # CRD conversion requires k8s 1.13 or later. {{- end }} -apiVersion: apiextensions.k8s.io/{{ .CRDVersion }} +apiVersion: apiextensions.k8s.io/{{ .Resource.API.CRDVersion }} kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: {{ .Resource.Plural }}.{{ .Resource.Domain }} + name: {{ .Resource.Plural }}.{{ .Resource.QualifiedGroup }} ` diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go index 9386f0a9c5d..7cc1da1d65e 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/crd/patches/enablewebhook_patch.go @@ -28,9 +28,6 @@ var _ file.Template = &EnableWebhookPatch{} type EnableWebhookPatch struct { file.TemplateMixin file.ResourceMixin - - // Version of CRD patch to generate. - CRDVersion string } // SetTemplateDefaults implements file.Template @@ -42,25 +39,21 @@ func (f *EnableWebhookPatch) SetTemplateDefaults() error { f.TemplateBody = enableWebhookPatchTemplate - if f.CRDVersion == "" { - f.CRDVersion = v1 - } - return nil } const enableWebhookPatchTemplate = `# The following patch enables a conversion webhook for the CRD -{{- if ne .CRDVersion "v1" }} +{{- if ne .Resource.API.CRDVersion "v1" }} # CRD conversion requires k8s 1.13 or later. {{- end }} -apiVersion: apiextensions.k8s.io/{{ .CRDVersion }} +apiVersion: apiextensions.k8s.io/{{ .Resource.API.CRDVersion }} kind: CustomResourceDefinition metadata: - name: {{ .Resource.Plural }}.{{ .Resource.Domain }} + name: {{ .Resource.Plural }}.{{ .Resource.QualifiedGroup }} spec: conversion: strategy: Webhook - {{- if ne .CRDVersion "v1" }} + {{- if ne .Resource.API.CRDVersion "v1" }} webhookClientConfig: service: namespace: system diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go index 85494a1bd61..a3ba80da2b9 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/kdefault/enablecainection_patch.go @@ -27,9 +27,7 @@ var _ file.Template = &WebhookCAInjectionPatch{} // WebhookCAInjectionPatch scaffolds a file that defines the patch that adds annotation to webhooks type WebhookCAInjectionPatch struct { file.TemplateMixin - - // Version of webhook patch to generate. - WebhookVersion string + file.ResourceMixin } // SetTemplateDefaults implements file.Template @@ -43,23 +41,19 @@ func (f *WebhookCAInjectionPatch) SetTemplateDefaults() error { // If file exists (ex. because a webhook was already created), skip creation. f.IfExistsAction = file.Skip - if f.WebhookVersion == "" { - f.WebhookVersion = "v1" - } - return nil } const injectCAPatchTemplate = `# This patch add annotation to admission webhook config and # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. -apiVersion: admissionregistration.k8s.io/{{ .WebhookVersion }} +apiVersion: admissionregistration.k8s.io/{{ .Resource.Webhooks.WebhookVersion }} kind: MutatingWebhookConfiguration metadata: name: mutating-webhook-configuration annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) --- -apiVersion: admissionregistration.k8s.io/{{ .WebhookVersion }} +apiVersion: admissionregistration.k8s.io/{{ .Resource.Webhooks.WebhookVersion }} kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go index b13fba6b7d1..a099b595e9b 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_editor_role.go @@ -49,7 +49,7 @@ metadata: name: {{ lower .Resource.Kind }}-editor-role rules: - apiGroups: - - {{ .Resource.Domain }} + - {{ .Resource.QualifiedGroup }} resources: - {{ .Resource.Plural }} verbs: @@ -61,7 +61,7 @@ rules: - update - watch - apiGroups: - - {{ .Resource.Domain }} + - {{ .Resource.QualifiedGroup }} resources: - {{ .Resource.Plural }}/status verbs: diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go index 6ef7b28a7dd..0b3311650b7 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/rbac/crd_viewer_role.go @@ -49,7 +49,7 @@ metadata: name: {{ lower .Resource.Kind }}-viewer-role rules: - apiGroups: - - {{ .Resource.Domain }} + - {{ .Resource.QualifiedGroup }} resources: - {{ .Resource.Plural }} verbs: @@ -57,7 +57,7 @@ rules: - list - watch - apiGroups: - - {{ .Resource.Domain }} + - {{ .Resource.QualifiedGroup }} resources: - {{ .Resource.Plural }}/status verbs: diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go index 444501525e7..ab68ba16e4c 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/samples/crd_sample.go @@ -50,7 +50,7 @@ func (f *CRDSample) SetTemplateDefaults() error { return nil } -const crdSampleTemplate = `apiVersion: {{ .Resource.Domain }}/{{ .Resource.Version }} +const crdSampleTemplate = `apiVersion: {{ .Resource.QualifiedGroup }}/{{ .Resource.Version }} kind: {{ .Resource.Kind }} metadata: name: {{ lower .Resource.Kind }}-sample diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go index 08fede11e1d..74546ab10ae 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/webhook/kustomization.go @@ -27,9 +27,7 @@ var _ file.Template = &Kustomization{} // Kustomization scaffolds a file that defines the kustomization scheme for the webhook folder type Kustomization struct { file.TemplateMixin - - // Version of webhook the project was configured with. - WebhookVersion string + file.ResourceMixin Force bool } @@ -49,15 +47,11 @@ func (f *Kustomization) SetTemplateDefaults() error { f.IfExistsAction = file.Skip } - if f.WebhookVersion == "" { - f.WebhookVersion = "v1" - } - return nil } const kustomizeWebhookTemplate = `resources: -- manifests{{ if ne .WebhookVersion "v1" }}.{{ .WebhookVersion }}{{ end }}.yaml +- manifests{{ if ne .Resource.Webhooks.WebhookVersion "v1" }}.{{ .Resource.Webhooks.WebhookVersion }}{{ end }}.yaml - service.yaml configurations: diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go index 226f0e99c83..fe2aae589b8 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go @@ -35,9 +35,6 @@ type Controller struct { ControllerRuntimeVersion string - // WireResource defines the api resources are generated or not. - WireResource bool - Force bool } @@ -68,7 +65,7 @@ func (f *Controller) SetTemplateDefaults() error { const controllerTemplate = `{{ .Boilerplate }} {{if and .MultiGroup .Resource.Group }} -package {{ .Resource.GroupPackageName }} +package {{ .Resource.PackageName }} {{else}} package controllers {{end}} @@ -79,8 +76,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - {{ if .WireResource -}} - {{ .Resource.ImportAlias }} "{{ .Resource.Package }}" + {{ if .Resource.HasAPI -}} + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" {{- end }} ) @@ -91,9 +88,9 @@ type {{ .Resource.Kind }}Reconciler struct { Scheme *runtime.Scheme } -//+kubebuilder:rbac:groups={{ .Resource.Domain }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups={{ .Resource.Domain }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch -//+kubebuilder:rbac:groups={{ .Resource.Domain }},resources={{ .Resource.Plural }}/finalizers,verbs=update +//+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. @@ -115,7 +112,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl // SetupWithManager sets up the controller with the Manager. func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - {{ if .WireResource -}} + {{ if .Resource.HasAPI -}} For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}). {{- else -}} // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go index ce9182f8ece..d8159a93c3e 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -37,9 +37,6 @@ type SuiteTest struct { // CRDDirectoryRelativePath define the Path for the CRD CRDDirectoryRelativePath string - // WireResource defines the api resources are generated or not. - WireResource bool - Force bool } @@ -101,14 +98,14 @@ func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { // Generate import code fragments imports := make([]string, 0) - if f.WireResource { - imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias, f.Resource.Package)) + if f.Resource.HasAPI() { + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } // Generate add scheme code fragments addScheme := make([]string, 0) - if f.WireResource { - addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias)) + if f.Resource.HasAPI() { + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) } // Only store code fragments in the map if the slices are non-empty @@ -125,7 +122,7 @@ func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { const controllerSuiteTestTemplate = `{{ .Boilerplate }} {{if and .MultiGroup .Resource.Group }} -package {{ .Resource.GroupPackageName }} +package {{ .Resource.PackageName }} {{else}} package controllers {{end}} @@ -166,7 +163,7 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ CRDDirectoryPaths: []string{filepath.Join({{ .CRDDirectoryRelativePath }}, "config", "crd", "bases")}, - ErrorIfCRDPathMissing: {{ .WireResource }}, + ErrorIfCRDPathMissing: {{ .Resource.HasAPI }}, } cfg, err := testEnv.Start() diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go index 4efafcb219b..65863974cb7 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/main.go @@ -134,7 +134,7 @@ func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { // Generate import code fragments imports := make([]string, 0) if f.WireResource { - imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias, f.Resource.Package)) + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } if f.WireController { @@ -142,14 +142,14 @@ func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { imports = append(imports, fmt.Sprintf(controllerImportCodeFragment, f.Repo)) } else { imports = append(imports, fmt.Sprintf(multiGroupControllerImportCodeFragment, - f.Resource.GroupPackageName, f.Repo, f.Resource.Group)) + 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)) + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) } // Generate setup code fragments @@ -160,12 +160,12 @@ func (f *MainUpdater) GetCodeFragments() file.CodeFragmentsMap { f.Resource.Kind, f.Resource.Kind, f.Resource.Kind)) } else { setup = append(setup, fmt.Sprintf(multiGroupReconcilerSetupCodeFragment, - f.Resource.GroupPackageName, f.Resource.Kind, f.Resource.Group, f.Resource.Kind, f.Resource.Kind)) + f.Resource.PackageName(), f.Resource.Kind, f.Resource.Group, f.Resource.Kind, f.Resource.Kind)) } } if f.WireWebhook { setup = append(setup, fmt.Sprintf(webhookSetupCodeFragment, - f.Resource.ImportAlias, f.Resource.Kind, f.Resource.Kind)) + f.Resource.ImportAlias(), f.Resource.Kind, f.Resource.Kind)) } // Only store code fragments in the map if the slices are non-empty diff --git a/pkg/plugins/golang/v3/scaffolds/webhook.go b/pkg/plugins/golang/v3/scaffolds/webhook.go index 1dea4143336..b195ee7eef7 100644 --- a/pkg/plugins/golang/v3/scaffolds/webhook.go +++ b/pkg/plugins/golang/v3/scaffolds/webhook.go @@ -19,8 +19,8 @@ package scaffolds import ( "fmt" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds/internal/templates/api" @@ -33,31 +33,25 @@ import ( var _ cmdutil.Scaffolder = &webhookScaffolder{} type webhookScaffolder struct { - config *config.Config + config config.Config boilerplate string - resource *resource.Resource + resource resource.Resource - // Webhook type options. - defaulting, validation, conversion, force bool + // 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, + config config.Config, boilerplate string, - resource *resource.Resource, - defaulting bool, - validation bool, - conversion bool, + resource resource.Resource, force bool, ) cmdutil.Scaffolder { return &webhookScaffolder{ config: config, boilerplate: boilerplate, resource: resource, - defaulting: defaulting, - validation: validation, - conversion: conversion, force: force, } } @@ -72,38 +66,40 @@ func (s *webhookScaffolder) newUniverse() *model.Universe { return model.NewUniverse( model.WithConfig(s.config), model.WithBoilerplate(s.boilerplate), - model.WithResource(s.resource), + model.WithResource(&s.resource), ) } func (s *webhookScaffolder) scaffold() error { - if s.conversion { - 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.`) - } + // Keep track of these values before the update + doDefaulting := s.resource.HasDefaultingWebhook() + doValidation := s.resource.HasValidationWebhook() + doConversion := s.resource.HasConversionWebhook() - s.config.UpdateResources(s.resource.Data()) + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } if err := machinery.NewScaffold().Execute( s.newUniverse(), - &api.Webhook{ - WebhookVersion: s.resource.Webhooks.WebhookVersion, - Defaulting: s.defaulting, - Validating: s.validation, - Force: s.force, - }, + &api.Webhook{Force: s.force}, &templates.MainUpdater{WireWebhook: true}, - &kdefault.WebhookCAInjectionPatch{WebhookVersion: s.resource.Webhooks.WebhookVersion}, + &kdefault.WebhookCAInjectionPatch{}, &kdefault.ManagerWebhookPatch{}, - &webhook.Kustomization{WebhookVersion: s.resource.Webhooks.WebhookVersion, Force: s.force}, + &webhook.Kustomization{Force: s.force}, &webhook.KustomizeConfig{}, &webhook.Service{}, ); 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 s.defaulting || s.validation { + if doDefaulting || doValidation { if err := machinery.NewScaffold().Execute( s.newUniverse(), &api.WebhookSuite{}, diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go index 96456324644..229fb8579b7 100644 --- a/pkg/plugins/golang/v3/webhook.go +++ b/pkg/plugins/golang/v3/webhook.go @@ -24,9 +24,9 @@ import ( "github.com/spf13/pflag" - "sigs.k8s.io/kubebuilder/v3/pkg/model/config" - "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" + goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" @@ -36,14 +36,11 @@ import ( const defaultWebhookVersion = "v1" type createWebhookSubcommand struct { - config *config.Config + config config.Config // For help text. commandName string - resource *resource.Options - defaulting bool - validation bool - conversion bool + options *goPlugin.Options // force indicates that the resource should be created even if it already exists force bool @@ -74,27 +71,28 @@ validating and (or) conversion webhooks. } func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { - p.resource = &resource.Options{} - fs.StringVar(&p.resource.Group, "group", "", "resource Group") - fs.StringVar(&p.resource.Version, "version", "", "resource Version") - fs.StringVar(&p.resource.Kind, "kind", "", "resource Kind") - fs.StringVar(&p.resource.Plural, "resource", "", "resource Resource") - fs.StringVar(&p.resource.Webhooks.WebhookVersion, "webhook-version", defaultWebhookVersion, + p.options = &goPlugin.Options{} + fs.StringVar(&p.options.Group, "group", "", "resource Group") + p.options.Domain = p.config.GetDomain() + fs.StringVar(&p.options.Version, "version", "", "resource Version") + fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") + fs.StringVar(&p.options.Plural, "resource", "", "resource irregular plural form") + + fs.StringVar(&p.options.WebhookVersion, "webhook-version", defaultWebhookVersion, "version of {Mutating,Validating}WebhookConfigurations to scaffold. Options: [v1, v1beta1]") + 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.runMake, "make", true, "if true, run make after generating files") fs.BoolVar(&p.force, "force", false, "attempt to create resource even if it already exists") - - fs.BoolVar(&p.defaulting, "defaulting", false, - "if set, scaffold the defaulting webhook") - fs.BoolVar(&p.validation, "programmatic-validation", false, - "if set, scaffold the validating webhook") - fs.BoolVar(&p.conversion, "conversion", false, - "if set, scaffold the conversion webhook") } -func (p *createWebhookSubcommand) InjectConfig(c *config.Config) { +func (p *createWebhookSubcommand) InjectConfig(c config.Config) { p.config = c } @@ -103,28 +101,26 @@ func (p *createWebhookSubcommand) Run() error { } func (p *createWebhookSubcommand) Validate() error { - if err := p.resource.Validate(); err != nil { + if err := p.options.Validate(); err != nil { return err } - if !p.defaulting && !p.validation && !p.conversion { + if !p.options.DoDefaulting && !p.options.DoValidation && !p.options.DoConversion { 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 p.config.GetResource(p.resource.Data()) == nil { + if r, err := p.config.GetResource(p.options.GVK()); err != nil { return fmt.Errorf("%s create webhook requires an api with the group,"+ " kind and version provided", p.commandName) - } - - if p.config.HasWebhook(p.resource.Data()) && !p.force { + } else if r.Webhooks != nil && !r.Webhooks.IsEmpty() && !p.force { return errors.New("webhook resource already exists") } - if !p.config.IsWebhookVersionCompatible(p.resource.Webhooks.WebhookVersion) { + if !p.config.IsWebhookVersionCompatible(p.options.WebhookVersion) { return fmt.Errorf("only one webhook version can be used for all resources, cannot add %q", - p.resource.Webhooks.WebhookVersion) + p.options.WebhookVersion) } return nil @@ -137,10 +133,9 @@ func (p *createWebhookSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { return nil, fmt.Errorf("unable to load boilerplate: %v", err) } - // Create the actual resource from the resource options - res := p.resource.NewResource(p.config, false) - return scaffolds.NewWebhookScaffolder(p.config, string(bp), res, p.defaulting, p.validation, p.conversion, - p.force), nil + // Create the resource from the options + res := p.options.NewResource(p.config) + return scaffolds.NewWebhookScaffolder(p.config, string(bp), res, p.force), nil } func (p *createWebhookSubcommand) PostScaffold() error { diff --git a/pkg/plugins/internal/machinery/scaffold.go b/pkg/plugins/internal/machinery/scaffold.go index 32e5832b810..5391c45bb2e 100644 --- a/pkg/plugins/internal/machinery/scaffold.go +++ b/pkg/plugins/internal/machinery/scaffold.go @@ -69,7 +69,7 @@ func (s *scaffold) Execute(universe *model.Universe, files ...file.Builder) erro // Set the repo as the local prefix so that it knows how to group imports if universe.Config != nil { - imports.LocalPrefix = universe.Config.Repo + imports.LocalPrefix = universe.Config.GetRepository() } for _, f := range files { diff --git a/plugins/addon/controller.go b/plugins/addon/controller.go index 0c70e80242f..032c77aef9c 100644 --- a/plugins/addon/controller.go +++ b/plugins/addon/controller.go @@ -48,7 +48,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - api "{{ .Resource.Package }}" + api "{{ .Resource.Path }}" ) var _ reconcile.Reconciler = &{{ .Resource.Kind }}Reconciler{} @@ -62,8 +62,8 @@ type {{ .Resource.Kind }}Reconciler struct { declarative.Reconciler } -//+kubebuilder:rbac:groups={{ .Resource.Domain }},resources={{ .Resource.Plural }},verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups={{ .Resource.Domain }},resources={{ .Resource.Plural }}/status,verbs=get;update;patch +//+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 func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { addon.Init() diff --git a/plugins/addon/type.go b/plugins/addon/type.go index 4046a190d38..82f24680e21 100644 --- a/plugins/addon/type.go +++ b/plugins/addon/type.go @@ -20,7 +20,7 @@ func ReplaceTypes(u *model.Universe) error { } var path string - if u.Config.MultiGroup { + if u.Config.IsMultiGroup() { path = filepath.Join("apis", u.Resource.Version, strings.ToLower(u.Resource.Kind)+"_types.go") } else { path = filepath.Join("api", u.Resource.Version, strings.ToLower(u.Resource.Kind)+"_types.go") @@ -66,8 +66,8 @@ type {{.Resource.Kind}}Spec struct { // Important: Run "make" to regenerate code after modifying this file } -// {{.Resource.Kind}}Status defines the observed state of {{.Resource.Kind}} -type {{.Resource.Kind}}Status struct { +// {{ .Resource.Kind }}Status defines the observed state of {{ .Resource.Kind }} +type {{ .Resource.Kind }}Status struct { addonv1alpha1.CommonStatus {{ JSONTag ",inline" }} // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster @@ -76,50 +76,50 @@ type {{.Resource.Kind}}Status struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -{{ if not .Resource.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} +{{ if not .Resource.API.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} // {{.Resource.Kind}} is the Schema for the {{ .Resource.Plural }} API -type {{.Resource.Kind}} struct { +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"` + "`" + ` + Spec {{ .Resource.Kind }}Spec ` + "`" + `json:"spec,omitempty"` + "`" + ` + Status {{ .Resource.Kind }}Status ` + "`" + `json:"status,omitempty"` + "`" + ` } -var _ addonv1alpha1.CommonObject = &{{.Resource.Kind}}{} +var _ addonv1alpha1.CommonObject = &{{ .Resource.Kind }}{} -func (o *{{.Resource.Kind}}) ComponentName() string { +func (o *{{ .Resource.Kind }}) ComponentName() string { return "{{ .Resource.Kind | lower }}" } -func (o *{{.Resource.Kind}}) CommonSpec() addonv1alpha1.CommonSpec { +func (o *{{ .Resource.Kind }}) CommonSpec() addonv1alpha1.CommonSpec { return o.Spec.CommonSpec } -func (o *{{.Resource.Kind}}) PatchSpec() addonv1alpha1.PatchSpec { +func (o *{{ .Resource.Kind }}) PatchSpec() addonv1alpha1.PatchSpec { return o.Spec.PatchSpec } -func (o *{{.Resource.Kind}}) GetCommonStatus() addonv1alpha1.CommonStatus { +func (o *{{ .Resource.Kind }}) GetCommonStatus() addonv1alpha1.CommonStatus { return o.Status.CommonStatus } -func (o *{{.Resource.Kind}}) SetCommonStatus(s addonv1alpha1.CommonStatus) { +func (o *{{ .Resource.Kind }}) SetCommonStatus(s addonv1alpha1.CommonStatus) { o.Status.CommonStatus = s } //+kubebuilder:object:root=true -{{ if not .Resource.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} +{{ if not .Resource.API.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} -// {{.Resource.Kind}}List contains a list of {{.Resource.Kind}} -type {{.Resource.Kind}}List struct { +// {{ .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{}) + SchemeBuilder.Register(&{{ .Resource.Kind }}{}, &{{ .Resource.Kind }}List{}) } ` From 1a6bb7a0f48d06e2465d203f207857a636a00a9d Mon Sep 17 00:00:00 2001 From: Adrian Orive Oneca Date: Fri, 22 Jan 2021 11:00:08 +0100 Subject: [PATCH 13/38] Add --plural flag (go/v3) Adds the flags and the required marker to the types file. Signed-off-by: Adrian Orive Oneca --- generate_testdata.sh | 24 +++++++-- pkg/model/resource/resource.go | 15 ++++-- pkg/model/resource/resource_test.go | 50 +++++++++++++++++++ pkg/plugins/golang/v3/api.go | 2 +- .../scaffolds/internal/templates/api/types.go | 8 ++- pkg/plugins/golang/v3/webhook.go | 2 +- testdata/project-v3/api/v1/admiral_types.go | 4 +- testdata/project-v3/api/v1/admiral_webhook.go | 2 +- .../project-v3/api/v1/webhook_suite_test.go | 4 +- ...ml => crew.testproject.org_admirales.yaml} | 6 +-- .../project-v3/config/crd/kustomization.yaml | 6 +-- ...als.yaml => cainjection_in_admirales.yaml} | 2 +- ...dmirals.yaml => webhook_in_admirales.yaml} | 2 +- .../config/rbac/admiral_editor_role.yaml | 6 +-- .../config/rbac/admiral_viewer_role.yaml | 6 +-- testdata/project-v3/config/rbac/role.yaml | 6 +-- .../project-v3/config/webhook/manifests.yaml | 2 +- .../controllers/admiral_controller.go | 6 +-- testdata/project-v3/main.go | 8 +-- 19 files changed, 121 insertions(+), 40 deletions(-) rename testdata/project-v3/config/crd/bases/{crew.testproject.org_admirals.yaml => crew.testproject.org_admirales.yaml} (93%) rename testdata/project-v3/config/crd/patches/{cainjection_in_admirals.yaml => cainjection_in_admirales.yaml} (86%) rename testdata/project-v3/config/crd/patches/{webhook_in_admirals.yaml => webhook_in_admirales.yaml} (89%) diff --git a/generate_testdata.sh b/generate_testdata.sh index 2b43d216dce..f66b16df5d9 100755 --- a/generate_testdata.sh +++ b/generate_testdata.sh @@ -59,14 +59,22 @@ scaffold_test_project() { $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false --force $kb create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation + if [ $project == "project-v3" ]; then + $kb create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation --force + fi + $kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false $kb create webhook --group crew --version v1 --kind FirstMate --conversion - $kb create api --group crew --version v1 --kind Admiral --controller=true --resource=true --namespaced=false --make=false - $kb create webhook --group crew --version v1 --kind Admiral --defaulting - $kb create api --group crew --version v1 --kind Laker --controller=true --resource=false --make=false + if [ $project == "project-v3" ]; then - $kb create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation --force + $kb create api --group crew --version v1 --kind Admiral --plural=admirales --controller=true --resource=true --namespaced=false --make=false + $kb create webhook --group crew --version v1 --kind Admiral --plural=admirales --defaulting + else + $kb create api --group crew --version v1 --kind Admiral --controller=true --resource=true --namespaced=false --make=false + $kb create webhook --group crew --version v1 --kind Admiral --defaulting fi + + $kb create api --group crew --version v1 --kind Laker --controller=true --resource=false --make=false elif [[ $project =~ multigroup ]]; then header_text 'Switching to multigroup layout ...' $kb edit --multigroup=true @@ -74,16 +82,24 @@ scaffold_test_project() { header_text 'Creating APIs ...' $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false $kb create webhook --group crew --version v1 --kind Captain --defaulting --programmatic-validation + $kb create api --group ship --version v1beta1 --kind Frigate --controller=true --resource=true --make=false $kb create webhook --group ship --version v1beta1 --kind Frigate --conversion + $kb create api --group ship --version v1 --kind Destroyer --controller=true --resource=true --namespaced=false --make=false $kb create webhook --group ship --version v1 --kind Destroyer --defaulting + $kb create api --group ship --version v2alpha1 --kind Cruiser --controller=true --resource=true --namespaced=false --make=false $kb create webhook --group ship --version v2alpha1 --kind Cruiser --programmatic-validation + $kb create api --group sea-creatures --version v1beta1 --kind Kraken --controller=true --resource=true --make=false + $kb create api --group sea-creatures --version v1beta2 --kind Leviathan --controller=true --resource=true --make=false + $kb create api --group foo.policy --version v1 --kind HealthCheckPolicy --controller=true --resource=true --make=false + $kb create api --group apps --version v1 --kind Pod --controller=true --resource=false --make=false + if [ $project == "project-v3-multigroup" ]; then $kb create api --version v1 --kind Lakers --controller=true --resource=true --make=false $kb create webhook --version v1 --kind Lakers --defaulting --programmatic-validation diff --git a/pkg/model/resource/resource.go b/pkg/model/resource/resource.go index a9237fa7a4a..4499d7074f9 100644 --- a/pkg/model/resource/resource.go +++ b/pkg/model/resource/resource.go @@ -85,6 +85,11 @@ func (r Resource) HasConversionWebhook() bool { return r.Webhooks != nil && r.Webhooks.Conversion } +// IsRegularPlural returns true if the plural is the regular plural form for the kind. +func (r Resource) IsRegularPlural() bool { + return r.Plural == RegularPlural(r.Kind) +} + // Copy returns a deep copy of the Resource that can be safely modified without affecting the original. func (r Resource) Copy() Resource { // As this function doesn't use a pointer receiver, r is already a shallow copy. @@ -112,9 +117,13 @@ func (r *Resource) Update(other Resource) error { return fmt.Errorf("unable to update a Resource with another with non-matching GVK") } - // TODO: currently Plural & Path will always match. In the future, this may not be true (e.g. providing a - // --plural flag). In that case, we should yield an error in case of updating two resources with different - // values for these fields. + if r.Plural != other.Plural { + return fmt.Errorf("unable to update Resource with another with non-matching Plural") + } + + if r.Path != other.Path { + return fmt.Errorf("unable to update Resource with another with non-matching Path") + } // Update API. if r.API == nil && other.API != nil { diff --git a/pkg/model/resource/resource_test.go b/pkg/model/resource/resource_test.go index b0bd7fd7f8e..929e4951569 100644 --- a/pkg/model/resource/resource_test.go +++ b/pkg/model/resource/resource_test.go @@ -138,6 +138,16 @@ var _ = Describe("Resource", func() { Entry("no conversion", Resource{Webhooks: &Webhooks{Conversion: false}}), ) }) + + Context("IsRegularPlural", func() { + It("should return true if the regular plural form is used", func() { + Expect(Resource{GVK: GVK{Kind: "FirstMate"}, Plural: "firstmates"}.IsRegularPlural()).To(BeTrue()) + }) + + It("should return false if an irregular plural form is used", func() { + Expect(Resource{GVK: GVK{Kind: "FirstMate"}, Plural: "mates"}.IsRegularPlural()).To(BeFalse()) + }) + }) }) Context("Copy", func() { @@ -251,6 +261,46 @@ var _ = Describe("Resource", func() { Expect(r.Update(other)).NotTo(Succeed()) }) + It("should fail for different Plurals", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Plural: "kinds", + } + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Plural: "types", + } + Expect(r.Update(other)).NotTo(Succeed()) + }) + + It("should fail for different Paths", func() { + r = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Path: "api/v1", + } + other = Resource{ + GVK: GVK{ + Group: group, + Version: version, + Kind: kind, + }, + Path: "apis/group/v1", + } + Expect(r.Update(other)).NotTo(Succeed()) + }) + Context("API", func() { It("should work with nil APIs", func() { r = Resource{ diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index ab8a629f712..04d90c0209f 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -121,7 +121,7 @@ func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { p.options.Domain = p.config.GetDomain() fs.StringVar(&p.options.Version, "version", "", "resource Version") fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") - // p.options.Plural can be set to specify an irregular plural form + 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") diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go index 0875b04df88..4c253344db0 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/types.go @@ -91,7 +91,13 @@ type {{ .Resource.Kind }}Status struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -{{ if not .Resource.API.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} +{{- 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 { diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go index 229fb8579b7..ac64f39c02e 100644 --- a/pkg/plugins/golang/v3/webhook.go +++ b/pkg/plugins/golang/v3/webhook.go @@ -76,7 +76,7 @@ func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { p.options.Domain = p.config.GetDomain() fs.StringVar(&p.options.Version, "version", "", "resource Version") fs.StringVar(&p.options.Kind, "kind", "", "resource Kind") - fs.StringVar(&p.options.Plural, "resource", "", "resource irregular plural form") + fs.StringVar(&p.options.Plural, "plural", "", "resource irregular plural form") fs.StringVar(&p.options.WebhookVersion, "webhook-version", defaultWebhookVersion, "version of {Mutating,Validating}WebhookConfigurations to scaffold. Options: [v1, v1beta1]") diff --git a/testdata/project-v3/api/v1/admiral_types.go b/testdata/project-v3/api/v1/admiral_types.go index fe0d8308a70..20f58e77112 100644 --- a/testdata/project-v3/api/v1/admiral_types.go +++ b/testdata/project-v3/api/v1/admiral_types.go @@ -40,9 +40,9 @@ type AdmiralStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -//+kubebuilder:resource:scope=Cluster +//+kubebuilder:resource:path=admirales,scope=Cluster -// Admiral is the Schema for the admirals API +// Admiral is the Schema for the admirales API type Admiral struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/testdata/project-v3/api/v1/admiral_webhook.go b/testdata/project-v3/api/v1/admiral_webhook.go index dca1250dcdc..47177bf43e4 100644 --- a/testdata/project-v3/api/v1/admiral_webhook.go +++ b/testdata/project-v3/api/v1/admiral_webhook.go @@ -33,7 +33,7 @@ func (r *Admiral) SetupWebhookWithManager(mgr ctrl.Manager) error { // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -//+kubebuilder:webhook:path=/mutate-crew-testproject-org-v1-admiral,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=admirals,verbs=create;update,versions=v1,name=madmiral.kb.io,admissionReviewVersions={v1,v1beta1} +//+kubebuilder:webhook:path=/mutate-crew-testproject-org-v1-admiral,mutating=true,failurePolicy=fail,sideEffects=None,groups=crew.testproject.org,resources=admirales,verbs=create;update,versions=v1,name=madmiral.kb.io,admissionReviewVersions={v1,v1beta1} var _ webhook.Defaulter = &Admiral{} diff --git a/testdata/project-v3/api/v1/webhook_suite_test.go b/testdata/project-v3/api/v1/webhook_suite_test.go index 21a406c4c13..1dabe80a540 100644 --- a/testdata/project-v3/api/v1/webhook_suite_test.go +++ b/testdata/project-v3/api/v1/webhook_suite_test.go @@ -109,10 +109,10 @@ var _ = BeforeSuite(func() { err = (&Captain{}).SetupWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) - err = (&Admiral{}).SetupWebhookWithManager(mgr) + err = (&Captain{}).SetupWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) - err = (&Captain{}).SetupWebhookWithManager(mgr) + err = (&Admiral{}).SetupWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:webhook diff --git a/testdata/project-v3/config/crd/bases/crew.testproject.org_admirals.yaml b/testdata/project-v3/config/crd/bases/crew.testproject.org_admirales.yaml similarity index 93% rename from testdata/project-v3/config/crd/bases/crew.testproject.org_admirals.yaml rename to testdata/project-v3/config/crd/bases/crew.testproject.org_admirales.yaml index e8612bd5328..8998d558726 100644 --- a/testdata/project-v3/config/crd/bases/crew.testproject.org_admirals.yaml +++ b/testdata/project-v3/config/crd/bases/crew.testproject.org_admirales.yaml @@ -6,20 +6,20 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null - name: admirals.crew.testproject.org + name: admirales.crew.testproject.org spec: group: crew.testproject.org names: kind: Admiral listKind: AdmiralList - plural: admirals + plural: admirales singular: admiral scope: Cluster versions: - name: v1 schema: openAPIV3Schema: - description: Admiral is the Schema for the admirals API + description: Admiral is the Schema for the admirales API properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/testdata/project-v3/config/crd/kustomization.yaml b/testdata/project-v3/config/crd/kustomization.yaml index 481fe329551..61dcc48c5e5 100644 --- a/testdata/project-v3/config/crd/kustomization.yaml +++ b/testdata/project-v3/config/crd/kustomization.yaml @@ -4,7 +4,7 @@ resources: - bases/crew.testproject.org_captains.yaml - bases/crew.testproject.org_firstmates.yaml -- bases/crew.testproject.org_admirals.yaml +- bases/crew.testproject.org_admirales.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -12,14 +12,14 @@ patchesStrategicMerge: # patches here are for enabling the conversion webhook for each CRD #- patches/webhook_in_captains.yaml #- patches/webhook_in_firstmates.yaml -#- patches/webhook_in_admirals.yaml +#- patches/webhook_in_admirales.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. # patches here are for enabling the CA injection for each CRD #- patches/cainjection_in_captains.yaml #- patches/cainjection_in_firstmates.yaml -#- patches/cainjection_in_admirals.yaml +#- patches/cainjection_in_admirales.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/testdata/project-v3/config/crd/patches/cainjection_in_admirals.yaml b/testdata/project-v3/config/crd/patches/cainjection_in_admirales.yaml similarity index 86% rename from testdata/project-v3/config/crd/patches/cainjection_in_admirals.yaml rename to testdata/project-v3/config/crd/patches/cainjection_in_admirales.yaml index ba7fea6e88d..04882738e44 100644 --- a/testdata/project-v3/config/crd/patches/cainjection_in_admirals.yaml +++ b/testdata/project-v3/config/crd/patches/cainjection_in_admirales.yaml @@ -4,4 +4,4 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: admirals.crew.testproject.org + name: admirales.crew.testproject.org diff --git a/testdata/project-v3/config/crd/patches/webhook_in_admirals.yaml b/testdata/project-v3/config/crd/patches/webhook_in_admirales.yaml similarity index 89% rename from testdata/project-v3/config/crd/patches/webhook_in_admirals.yaml rename to testdata/project-v3/config/crd/patches/webhook_in_admirales.yaml index e7cef4898e3..2bde272e76a 100644 --- a/testdata/project-v3/config/crd/patches/webhook_in_admirals.yaml +++ b/testdata/project-v3/config/crd/patches/webhook_in_admirales.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: admirals.crew.testproject.org + name: admirales.crew.testproject.org spec: conversion: strategy: Webhook diff --git a/testdata/project-v3/config/rbac/admiral_editor_role.yaml b/testdata/project-v3/config/rbac/admiral_editor_role.yaml index 7f2cc5cd99f..08184d661d2 100644 --- a/testdata/project-v3/config/rbac/admiral_editor_role.yaml +++ b/testdata/project-v3/config/rbac/admiral_editor_role.yaml @@ -1,4 +1,4 @@ -# permissions for end users to edit admirals. +# permissions for end users to edit admirales. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -7,7 +7,7 @@ rules: - apiGroups: - crew.testproject.org resources: - - admirals + - admirales verbs: - create - delete @@ -19,6 +19,6 @@ rules: - apiGroups: - crew.testproject.org resources: - - admirals/status + - admirales/status verbs: - get diff --git a/testdata/project-v3/config/rbac/admiral_viewer_role.yaml b/testdata/project-v3/config/rbac/admiral_viewer_role.yaml index ddbb0c2a85b..65df31b4b48 100644 --- a/testdata/project-v3/config/rbac/admiral_viewer_role.yaml +++ b/testdata/project-v3/config/rbac/admiral_viewer_role.yaml @@ -1,4 +1,4 @@ -# permissions for end users to view admirals. +# permissions for end users to view admirales. apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -7,7 +7,7 @@ rules: - apiGroups: - crew.testproject.org resources: - - admirals + - admirales verbs: - get - list @@ -15,6 +15,6 @@ rules: - apiGroups: - crew.testproject.org resources: - - admirals/status + - admirales/status verbs: - get diff --git a/testdata/project-v3/config/rbac/role.yaml b/testdata/project-v3/config/rbac/role.yaml index 1d105256811..a02d192e82e 100644 --- a/testdata/project-v3/config/rbac/role.yaml +++ b/testdata/project-v3/config/rbac/role.yaml @@ -9,7 +9,7 @@ rules: - apiGroups: - crew.testproject.org resources: - - admirals + - admirales verbs: - create - delete @@ -21,13 +21,13 @@ rules: - apiGroups: - crew.testproject.org resources: - - admirals/finalizers + - admirales/finalizers verbs: - update - apiGroups: - crew.testproject.org resources: - - admirals/status + - admirales/status verbs: - get - patch diff --git a/testdata/project-v3/config/webhook/manifests.yaml b/testdata/project-v3/config/webhook/manifests.yaml index 7c748d3a804..d3fc4ff3ffc 100644 --- a/testdata/project-v3/config/webhook/manifests.yaml +++ b/testdata/project-v3/config/webhook/manifests.yaml @@ -25,7 +25,7 @@ webhooks: - CREATE - UPDATE resources: - - admirals + - admirales sideEffects: None - admissionReviewVersions: - v1 diff --git a/testdata/project-v3/controllers/admiral_controller.go b/testdata/project-v3/controllers/admiral_controller.go index 5f7be6ac8c5..61b4456a335 100644 --- a/testdata/project-v3/controllers/admiral_controller.go +++ b/testdata/project-v3/controllers/admiral_controller.go @@ -34,9 +34,9 @@ type AdmiralReconciler struct { 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 +//+kubebuilder:rbac:groups=crew.testproject.org,resources=admirales,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=crew.testproject.org,resources=admirales/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=crew.testproject.org,resources=admirales/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. diff --git a/testdata/project-v3/main.go b/testdata/project-v3/main.go index 0ae83837744..fc10f63f98e 100644 --- a/testdata/project-v3/main.go +++ b/testdata/project-v3/main.go @@ -98,6 +98,10 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "Captain") os.Exit(1) } + if err = (&crewv1.Captain{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Captain") + os.Exit(1) + } if err = (&controllers.FirstMateReconciler{ Client: mgr.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("FirstMate"), @@ -130,10 +134,6 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Laker") os.Exit(1) } - if err = (&crewv1.Captain{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "Captain") - os.Exit(1) - } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { From 45f69ce26fa5a6d600397d9acde58138e620ad80 Mon Sep 17 00:00:00 2001 From: Adrian Orive Oneca Date: Sat, 23 Jan 2021 12:54:24 +0100 Subject: [PATCH 14/38] Validate the plugin flag Signed-off-by: Adrian Orive Oneca --- pkg/plugins/golang/options.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index 5dd87ad4820..dc61563e136 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -143,7 +143,13 @@ func (opts Options) Validate() error { return fmt.Errorf("invalid Kind: %#v", validationErrors) } - // TODO: validate plural strings if provided + if opts.Plural != "" { + validationErrors = append(validationErrors, validation.IsDNS1035Label(opts.Plural)...) + + if len(validationErrors) != 0 { + return fmt.Errorf("invalid Plural: %#v", validationErrors) + } + } // Ensure apiVersions for k8s types are empty or valid. for typ, apiVersion := range map[string]string{ From db966c61930d76f394f9db9221f752a8267b9208 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Tue, 26 Jan 2021 10:57:35 +0100 Subject: [PATCH 15/38] Split resource validation between Options and Resource objects so that Resources built in different ways can still be validated Signed-off-by: Adrian Orive --- pkg/model/resource/api.go | 10 ++ pkg/model/resource/api_test.go | 13 ++ pkg/model/resource/gvk.go | 40 +++++ pkg/model/resource/gvk_test.go | 41 ++++- pkg/model/resource/resource.go | 34 ++++ pkg/model/resource/resource_test.go | 244 +++++++++----------------- pkg/model/resource/utils.go | 11 ++ pkg/model/resource/webhooks.go | 10 ++ pkg/model/resource/webhooks_test.go | 13 ++ pkg/plugins/golang/options.go | 65 +------ pkg/plugins/golang/options_test.go | 93 ++++------ pkg/plugins/golang/v2/api.go | 37 ++-- pkg/plugins/golang/v2/options.go | 54 +----- pkg/plugins/golang/v2/options_test.go | 89 +++------- pkg/plugins/golang/v2/webhook.go | 21 ++- pkg/plugins/golang/v3/api.go | 48 ++--- pkg/plugins/golang/v3/webhook.go | 25 ++- 17 files changed, 390 insertions(+), 458 deletions(-) diff --git a/pkg/model/resource/api.go b/pkg/model/resource/api.go index 770171e04ae..134672edbb0 100644 --- a/pkg/model/resource/api.go +++ b/pkg/model/resource/api.go @@ -29,6 +29,16 @@ type API struct { Namespaced bool `json:"namespaced,omitempty"` } +// Validate checks that the API is valid. +func (api API) Validate() error { + // Validate the CRD version + if err := validateAPIVersion(api.CRDVersion); err != nil { + return fmt.Errorf("invalid CRD version: %w", err) + } + + return nil +} + // Copy returns a deep copy of the API that can be safely modified without affecting the original. func (api API) Copy() API { // As this function doesn't use a pointer receiver, api is already a shallow copy. diff --git a/pkg/model/resource/api_test.go b/pkg/model/resource/api_test.go index 52faad9a748..fb003afdc5c 100644 --- a/pkg/model/resource/api_test.go +++ b/pkg/model/resource/api_test.go @@ -24,6 +24,19 @@ import ( //nolint:dupl var _ = Describe("API", func() { + Context("Validate", func() { + It("should succeed for a valid API", func() { + Expect(API{CRDVersion: v1}.Validate()).To(Succeed()) + }) + + DescribeTable("should fail for invalid APIs", + func(api API) { Expect(api.Validate()).NotTo(Succeed()) }, + // Ensure that the rest of the fields are valid to check each part + Entry("empty CRD version", API{}), + Entry("invalid CRD version", API{CRDVersion: "1"}), + ) + }) + Context("Update", func() { var api, other API diff --git a/pkg/model/resource/gvk.go b/pkg/model/resource/gvk.go index feb8368887d..46f1cb79ec8 100644 --- a/pkg/model/resource/gvk.go +++ b/pkg/model/resource/gvk.go @@ -18,6 +18,18 @@ package resource import ( "fmt" + "regexp" + "strings" + + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" +) + +const ( + versionPattern = "^v\\d+(?:alpha\\d+|beta\\d+)?$" +) + +var ( + versionRegex = regexp.MustCompile(versionPattern) ) // GVK stores the Group - Version - Kind triplet that uniquely identifies a resource. @@ -29,6 +41,34 @@ type GVK struct { Kind string `json:"kind"` } +// Validate checks that the GVK is valid. +func (gvk GVK) Validate() error { + // Check if the qualified group has a valid DNS1123 subdomain value + if err := validation.IsDNS1123Subdomain(gvk.QualifiedGroup()); err != nil { + // NOTE: IsDNS1123Subdomain returns a slice of strings instead of an error, so no wrapping + return fmt.Errorf("either Group or Domain is invalid: %s", err) + } + + // Check if the version follows the valid pattern + if !versionRegex.MatchString(gvk.Version) { + return fmt.Errorf("Version must match %s (was %s)", versionPattern, gvk.Version) + } + + // Check if kind has a valid DNS1035 label value + if errors := validation.IsDNS1035Label(strings.ToLower(gvk.Kind)); len(errors) != 0 { + // NOTE: IsDNS1035Label returns a slice of strings instead of an error, so no wrapping + return fmt.Errorf("invalid Kind: %#v", errors) + } + + // Require kind to start with an uppercase character + // NOTE: previous validation already fails for empty strings, gvk.Kind[0] will not panic + if string(gvk.Kind[0]) == strings.ToLower(string(gvk.Kind[0])) { + return fmt.Errorf("invalid Kind: must start with an uppercase character") + } + + return nil +} + // QualifiedGroup returns the fully qualified group name with the available information. func (gvk GVK) QualifiedGroup() string { switch "" { diff --git a/pkg/model/resource/gvk_test.go b/pkg/model/resource/gvk_test.go index c3adf999561..e71040f6549 100644 --- a/pkg/model/resource/gvk_test.go +++ b/pkg/model/resource/gvk_test.go @@ -17,6 +17,8 @@ limitations under the License. package resource import ( + "strings" + . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" @@ -30,19 +32,50 @@ var _ = Describe("GVK", func() { kind = "Kind" ) + var gvk = GVK{Group: group, Domain: domain, Version: version, Kind: kind} + + Context("Validate", func() { + It("should succeed for a valid GVK", func() { + Expect(gvk.Validate()).To(Succeed()) + }) + + DescribeTable("should fail for invalid GVKs", + func(gvk GVK) { Expect(gvk.Validate()).NotTo(Succeed()) }, + // Ensure that the rest of the fields are valid to check each part + Entry("Group (uppercase)", GVK{Group: "Group", Domain: domain, Version: version, Kind: kind}), + Entry("Group (non-alpha characters)", GVK{Group: "_*?", Domain: domain, Version: version, Kind: kind}), + Entry("Domain (uppercase)", GVK{Group: group, Domain: "Domain", Version: version, Kind: kind}), + Entry("Domain (non-alpha characters)", GVK{Group: group, Domain: "_*?", Version: version, Kind: kind}), + Entry("Group and Domain (empty)", GVK{Group: "", Domain: "", Version: version, Kind: kind}), + Entry("Version (empty)", GVK{Group: group, Domain: domain, Version: "", Kind: kind}), + Entry("Version (no v prefix)", GVK{Group: group, Domain: domain, Version: "1", Kind: kind}), + Entry("Version (wrong prefix)", GVK{Group: group, Domain: domain, Version: "a1", Kind: kind}), + Entry("Version (unstable no v prefix)", GVK{Group: group, Domain: domain, Version: "1beta1", Kind: kind}), + Entry("Version (unstable no alpha/beta number)", + GVK{Group: group, Domain: domain, Version: "v1beta", Kind: kind}), + Entry("Version (multiple unstable)", + GVK{Group: group, Domain: domain, Version: "v1beta1alpha1", Kind: kind}), + Entry("Kind (empty)", GVK{Group: group, Domain: domain, Version: version, Kind: ""}), + Entry("Kind (whitespaces)", GVK{Group: group, Domain: domain, Version: version, Kind: "Ki nd"}), + Entry("Kind (lowercase)", GVK{Group: group, Domain: domain, Version: version, Kind: "kind"}), + Entry("Kind (starts with number)", GVK{Group: group, Domain: domain, Version: version, Kind: "1Kind"}), + Entry("Kind (ends with `-`)", GVK{Group: group, Domain: domain, Version: version, Kind: "Kind-"}), + Entry("Kind (non-alpha characters)", GVK{Group: group, Domain: domain, Version: version, Kind: "_*?"}), + Entry("Kind (too long)", + GVK{Group: group, Domain: domain, Version: version, Kind: strings.Repeat("a", 64)}), + ) + }) + Context("QualifiedGroup", func() { DescribeTable("should return the correct string", func(gvk GVK, qualifiedGroup string) { Expect(gvk.QualifiedGroup()).To(Equal(qualifiedGroup)) }, - Entry("fully qualified resource", GVK{Group: group, Domain: domain, Version: version, Kind: kind}, - group+"."+domain), + Entry("fully qualified resource", gvk, group+"."+domain), Entry("empty group name", GVK{Domain: domain, Version: version, Kind: kind}, domain), Entry("empty domain", GVK{Group: group, Version: version, Kind: kind}, group), ) }) Context("IsEqualTo", func() { - var gvk = GVK{Group: group, Domain: domain, Version: version, Kind: kind} - It("should return true for the same resource", func() { Expect(gvk.IsEqualTo(GVK{Group: group, Domain: domain, Version: version, Kind: kind})).To(BeTrue()) }) diff --git a/pkg/model/resource/resource.go b/pkg/model/resource/resource.go index 4499d7074f9..869b4a7e4d0 100644 --- a/pkg/model/resource/resource.go +++ b/pkg/model/resource/resource.go @@ -19,6 +19,8 @@ package resource import ( "fmt" "strings" + + "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" ) // Resource contains the information required to scaffold files for a resource. @@ -42,6 +44,38 @@ type Resource struct { Webhooks *Webhooks `json:"webhooks,omitempty"` } +// Validate checks that the Resource is valid. +func (r Resource) Validate() error { + // Validate the GVK + if err := r.GVK.Validate(); err != nil { + return err + } + + // Validate the Plural + // NOTE: IsDNS1035Label returns a slice of strings instead of an error, so no wrapping + if errors := validation.IsDNS1035Label(r.Plural); len(errors) != 0 { + return fmt.Errorf("invalid Plural: %#v", errors) + } + + // TODO: validate the path + + // Validate the API + if r.API != nil && !r.API.IsEmpty() { + if err := r.API.Validate(); err != nil { + return fmt.Errorf("invalid API: %w", err) + } + } + + // Validate the Webhooks + if r.Webhooks != nil && !r.Webhooks.IsEmpty() { + if err := r.Webhooks.Validate(); err != nil { + return fmt.Errorf("invalid Webhooks: %w", err) + } + } + + return nil +} + // PackageName returns a name valid to be used por go packages. func (r Resource) PackageName() string { if r.Group == "" { diff --git a/pkg/model/resource/resource_test.go b/pkg/model/resource/resource_test.go index 929e4951569..0d1151e8efc 100644 --- a/pkg/model/resource/resource_test.go +++ b/pkg/model/resource/resource_test.go @@ -29,35 +29,38 @@ var _ = Describe("Resource", func() { domain = "test.io" version = "v1" kind = "Kind" + plural = "kinds" + v1beta1 = "v1beta1" ) var ( - res1 = Resource{ - GVK: GVK{ - Group: group, - Domain: domain, - Version: version, - Kind: kind, - }, - } - res2 = Resource{ - GVK: GVK{ - // Empty group - Domain: domain, - Version: version, - Kind: kind, - }, + gvk = GVK{ + Group: group, + Domain: domain, + Version: version, + Kind: kind, } - res3 = Resource{ - GVK: GVK{ - Group: group, - // Empty domain - Version: version, - Kind: kind, - }, + res = Resource{ + GVK: gvk, + Plural: plural, } ) + Context("Validate", func() { + It("should succeed for a valid Resource", func() { + Expect(res.Validate()).To(Succeed()) + }) + + DescribeTable("should fail for invalid Resources", + func(res Resource) { Expect(res.Validate()).NotTo(Succeed()) }, + // Ensure that the rest of the fields are valid to check each part + Entry("invalid GVK", Resource{GVK: GVK{}, Plural: "plural"}), + Entry("invalid Plural", Resource{GVK: gvk, Plural: "Plural"}), + Entry("invalid API", Resource{GVK: gvk, Plural: "plural", API: &API{CRDVersion: "1"}}), + Entry("invalid Webhooks", Resource{GVK: gvk, Plural: "plural", Webhooks: &Webhooks{WebhookVersion: "1"}}), + ) + }) + Context("compound field", func() { const ( safeDomain = "testio" @@ -65,18 +68,37 @@ var _ = Describe("Resource", func() { domainVersion = safeDomain + version ) + var ( + resNoGroup = Resource{ + GVK: GVK{ + // Empty group + Domain: domain, + Version: version, + Kind: kind, + }, + } + resNoDomain = Resource{ + GVK: GVK{ + Group: group, + // Empty domain + Version: version, + Kind: kind, + }, + } + ) + DescribeTable("PackageName should return the correct string", func(res Resource, packageName string) { Expect(res.PackageName()).To(Equal(packageName)) }, - Entry("fully qualified resource", res1, group), - Entry("empty group name", res2, safeDomain), - Entry("empty domain", res3, group), + Entry("fully qualified resource", res, group), + Entry("empty group name", resNoGroup, safeDomain), + Entry("empty domain", resNoDomain, group), ) DescribeTable("ImportAlias", func(res Resource, importAlias string) { Expect(res.ImportAlias()).To(Equal(importAlias)) }, - Entry("fully qualified resource", res1, groupVersion), - Entry("empty group name", res2, domainVersion), - Entry("empty domain", res3, groupVersion), + Entry("fully qualified resource", res, groupVersion), + Entry("empty group name", resNoGroup, domainVersion), + Entry("empty domain", resNoDomain, groupVersion), ) }) @@ -141,30 +163,24 @@ var _ = Describe("Resource", func() { Context("IsRegularPlural", func() { It("should return true if the regular plural form is used", func() { - Expect(Resource{GVK: GVK{Kind: "FirstMate"}, Plural: "firstmates"}.IsRegularPlural()).To(BeTrue()) + Expect(res.IsRegularPlural()).To(BeTrue()) }) It("should return false if an irregular plural form is used", func() { - Expect(Resource{GVK: GVK{Kind: "FirstMate"}, Plural: "mates"}.IsRegularPlural()).To(BeFalse()) + Expect(Resource{GVK: gvk, Plural: "types"}.IsRegularPlural()).To(BeFalse()) }) }) }) Context("Copy", func() { const ( - plural = "kinds" path = "api/v1" crdVersion = "v1" webhookVersion = "v1" ) res := Resource{ - GVK: GVK{ - Group: group, - Domain: domain, - Version: version, - Kind: kind, - }, + GVK: gvk, Plural: plural, Path: path, API: &API{ @@ -207,11 +223,11 @@ var _ = Describe("Resource", func() { other.Kind = "kind2" other.Plural = "kind2s" other.Path = "api/v2" - other.API.CRDVersion = "v1beta1" + other.API.CRDVersion = v1beta1 other.API.Namespaced = false other.API = nil // Change fields before changing pointer other.Controller = false - other.Webhooks.WebhookVersion = "v1beta1" + other.Webhooks.WebhookVersion = v1beta1 other.Webhooks.Defaulting = false other.Webhooks.Validation = false other.Webhooks.Conversion = false @@ -244,16 +260,11 @@ var _ = Describe("Resource", func() { }) It("should fail for different GVKs", func() { - r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - } + r = Resource{GVK: gvk} other = Resource{ GVK: GVK{ Group: group, + Domain: domain, Version: version, Kind: "OtherKind", }, @@ -263,19 +274,11 @@ var _ = Describe("Resource", func() { It("should fail for different Plurals", func() { r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - Plural: "kinds", + GVK: gvk, + Plural: plural, } other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, Plural: "types", } Expect(r.Update(other)).NotTo(Succeed()) @@ -283,19 +286,11 @@ var _ = Describe("Resource", func() { It("should fail for different Paths", func() { r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, Path: "api/v1", } other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, Path: "apis/group/v1", } Expect(r.Update(other)).NotTo(Succeed()) @@ -303,19 +298,9 @@ var _ = Describe("Resource", func() { Context("API", func() { It("should work with nil APIs", func() { - r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - } + r = Resource{GVK: gvk} other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, API: &API{CRDVersion: v1}, } Expect(r.Update(other)).To(Succeed()) @@ -325,20 +310,12 @@ var _ = Describe("Resource", func() { It("should fail if API.Update fails", func() { r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, API: &API{CRDVersion: v1}, } other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - API: &API{CRDVersion: "v1beta1"}, + GVK: gvk, + API: &API{CRDVersion: v1beta1}, } Expect(r.Update(other)).NotTo(Succeed()) }) @@ -348,19 +325,9 @@ var _ = Describe("Resource", func() { Context("Controller", func() { It("should set the controller flag if provided and not previously set", func() { - r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - } + r = Resource{GVK: gvk} other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, Controller: true, } Expect(r.Update(other)).To(Succeed()) @@ -369,32 +336,18 @@ var _ = Describe("Resource", func() { It("should keep the controller flag if previously set", func() { r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, Controller: true, } By("not providing it") - other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - } + other = Resource{GVK: gvk} Expect(r.Update(other)).To(Succeed()) Expect(r.Controller).To(BeTrue()) By("providing it") other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, Controller: true, } Expect(r.Update(other)).To(Succeed()) @@ -402,20 +355,8 @@ var _ = Describe("Resource", func() { }) It("should not set the controller flag if not provided and not previously set", func() { - r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - } - other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - } + r = Resource{GVK: gvk} + other = Resource{GVK: gvk} Expect(r.Update(other)).To(Succeed()) Expect(r.Controller).To(BeFalse()) }) @@ -423,19 +364,9 @@ var _ = Describe("Resource", func() { Context("Webhooks", func() { It("should work with nil Webhooks", func() { - r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - } + r = Resource{GVK: gvk} other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, Webhooks: &Webhooks{WebhookVersion: v1}, } Expect(r.Update(other)).To(Succeed()) @@ -445,20 +376,12 @@ var _ = Describe("Resource", func() { It("should fail if Webhooks.Update fails", func() { r = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, + GVK: gvk, Webhooks: &Webhooks{WebhookVersion: v1}, } other = Resource{ - GVK: GVK{ - Group: group, - Version: version, - Kind: kind, - }, - Webhooks: &Webhooks{WebhookVersion: "v1beta1"}, + GVK: gvk, + Webhooks: &Webhooks{WebhookVersion: v1beta1}, } Expect(r.Update(other)).NotTo(Succeed()) }) @@ -468,15 +391,6 @@ var _ = Describe("Resource", func() { }) Context("Replacer", func() { - res := Resource{ - GVK: GVK{ - Group: group, - Domain: domain, - Version: version, - Kind: kind, - }, - Plural: "kinds", - } replacer := res.Replacer() DescribeTable("should replace the following strings", diff --git a/pkg/model/resource/utils.go b/pkg/model/resource/utils.go index 5d4d4c23516..f41ba46b553 100644 --- a/pkg/model/resource/utils.go +++ b/pkg/model/resource/utils.go @@ -17,12 +17,23 @@ limitations under the License. package resource import ( + "fmt" "path" "strings" "github.com/gobuffalo/flect" ) +// validateAPIVersion validates CRD or Webhook versions +func validateAPIVersion(version string) error { + switch version { + case "v1beta1", "v1": + return nil + default: + return fmt.Errorf("API version must be one of: v1beta1, v1") + } +} + // safeImport returns a cleaned version of the provided string that can be used for imports func safeImport(unsafe string) string { safe := unsafe diff --git a/pkg/model/resource/webhooks.go b/pkg/model/resource/webhooks.go index 1fa745bdbf3..b60afed5f1c 100644 --- a/pkg/model/resource/webhooks.go +++ b/pkg/model/resource/webhooks.go @@ -35,6 +35,16 @@ type Webhooks struct { Conversion bool `json:"conversion,omitempty"` } +// Validate checks that the Webhooks is valid. +func (webhooks Webhooks) Validate() error { + // Validate the Webhook version + if err := validateAPIVersion(webhooks.WebhookVersion); err != nil { + return fmt.Errorf("invalid Webhook version: %w", err) + } + + return nil +} + // Copy returns a deep copy of the API that can be safely modified without affecting the original. func (webhooks Webhooks) Copy() Webhooks { // As this function doesn't use a pointer receiver, webhooks is already a shallow copy. diff --git a/pkg/model/resource/webhooks_test.go b/pkg/model/resource/webhooks_test.go index f8ea6858f25..06175f39585 100644 --- a/pkg/model/resource/webhooks_test.go +++ b/pkg/model/resource/webhooks_test.go @@ -24,6 +24,19 @@ import ( //nolint:dupl var _ = Describe("Webhooks", func() { + Context("Validate", func() { + It("should succeed for a valid Webhooks", func() { + Expect(Webhooks{WebhookVersion: v1}.Validate()).To(Succeed()) + }) + + DescribeTable("should fail for invalid Webhooks", + func(webhooks Webhooks) { Expect(webhooks.Validate()).NotTo(Succeed()) }, + // Ensure that the rest of the fields are valid to check each part + Entry("empty webhook version", Webhooks{}), + Entry("invalid webhook version", Webhooks{WebhookVersion: "1"}), + ) + }) + Context("Update", func() { var webhook, other Webhooks diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index dc61563e136..ab2a2e0c96b 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -19,26 +19,22 @@ package golang import ( "fmt" "path" - "regexp" "strings" newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) const ( - versionPattern = "^v\\d+(?:alpha\\d+|beta\\d+)?$" groupPresent = "group flag present but empty" versionPresent = "version flag present but empty" kindPresent = "kind flag present but empty" + groupRequired = "group cannot be empty if the domain is empty" versionRequired = "version cannot be empty" kindRequired = "kind cannot be empty" ) var ( - versionRegex = regexp.MustCompile(versionPattern) - coreGroups = map[string]string{ "admission": "k8s.io", "admissionregistration": "k8s.io", @@ -112,7 +108,11 @@ func (opts Options) Validate() error { if strings.HasPrefix(opts.Kind, "-") { return fmt.Errorf(kindPresent) } + // Now we can check that all the required flags are not empty + if len(opts.Group) == 0 && len(opts.Domain) == 0 { + return fmt.Errorf(groupRequired) + } if len(opts.Version) == 0 { return fmt.Errorf(versionRequired) } @@ -120,64 +120,9 @@ func (opts Options) Validate() error { return fmt.Errorf(kindRequired) } - // Check if the qualified group has a valid DNS1123 subdomain value - if err := validation.IsDNS1123Subdomain(opts.QualifiedGroup()); err != nil { - return fmt.Errorf("either group or domain is invalid: (%v)", err) - } - - // Check if the version follows the valid pattern - if !versionRegex.MatchString(opts.Version) { - return fmt.Errorf("version must match %s (was %s)", versionPattern, opts.Version) - } - - validationErrors := []string{} - - // Require kind to start with an uppercase character - if string(opts.Kind[0]) == strings.ToLower(string(opts.Kind[0])) { - validationErrors = append(validationErrors, "kind must start with an uppercase character") - } - - validationErrors = append(validationErrors, validation.IsDNS1035Label(strings.ToLower(opts.Kind))...) - - if len(validationErrors) != 0 { - return fmt.Errorf("invalid Kind: %#v", validationErrors) - } - - if opts.Plural != "" { - validationErrors = append(validationErrors, validation.IsDNS1035Label(opts.Plural)...) - - if len(validationErrors) != 0 { - return fmt.Errorf("invalid Plural: %#v", validationErrors) - } - } - - // Ensure apiVersions for k8s types are empty or valid. - for typ, apiVersion := range map[string]string{ - "CRD": opts.CRDVersion, - "Webhook": opts.WebhookVersion, - } { - switch apiVersion { - case "", "v1", "v1beta1": - default: - return fmt.Errorf("%s version must be one of: v1, v1beta1", typ) - } - } - return nil } -// QualifiedGroup returns the fully qualified group name with the available information. -func (opts Options) QualifiedGroup() string { - switch "" { - case opts.Domain: - return opts.Group - case opts.Group: - return opts.Domain - default: - return fmt.Sprintf("%s.%s", opts.Group, opts.Domain) - } -} - // GVK returns the GVK identifier of a resource. func (opts Options) GVK() resource.GVK { return resource.GVK{ diff --git a/pkg/plugins/golang/options_test.go b/pkg/plugins/golang/options_test.go index 27e4db5cdac..629724e84b3 100644 --- a/pkg/plugins/golang/options_test.go +++ b/pkg/plugins/golang/options_test.go @@ -18,7 +18,6 @@ package golang import ( "path" - "strings" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" @@ -31,59 +30,20 @@ import ( var _ = Describe("Options", func() { Context("Validate", func() { DescribeTable("should succeed for valid options", - func(options *Options) { Expect(options.Validate()).To(Succeed()) }, - Entry("full GVK", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("missing domain", - &Options{Group: "crew", Version: "v1", Kind: "FirstMate"}), - Entry("missing group", - &Options{Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("kind with multiple initial uppercase characters", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FIRSTMate"}), + func(options Options) { Expect(options.Validate()).To(Succeed()) }, + Entry("full GVK", Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("missing domain", Options{Group: "crew", Version: "v1", Kind: "FirstMate"}), + Entry("missing group", Options{Domain: "test.io", Version: "v1", Kind: "FirstMate"}), ) DescribeTable("should fail for invalid options", - func(options *Options) { Expect(options.Validate()).NotTo(Succeed()) }, - Entry("group flag captured another flag", - &Options{Group: "--version"}), - Entry("version flag captured another flag", - &Options{Version: "--kind"}), - Entry("kind flag captured another flag", - &Options{Kind: "--group"}), - Entry("missing group and domain", - &Options{Version: "v1", Kind: "FirstMate"}), - Entry("group with uppercase characters", - &Options{Group: "Crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("group with non-alpha characters", - &Options{Group: "crew1*?", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("missing version", - &Options{Group: "crew", Domain: "test.io", Kind: "FirstMate"}), - Entry("version without v prefix", - &Options{Group: "crew", Domain: "test.io", Version: "1", Kind: "FirstMate"}), - Entry("unstable version without v prefix", - &Options{Group: "crew", Domain: "test.io", Version: "1beta1", Kind: "FirstMate"}), - Entry("unstable version with wrong prefix", - &Options{Group: "crew", Domain: "test.io", Version: "a1beta1", Kind: "FirstMate"}), - Entry("unstable version without alpha/beta number", - &Options{Group: "crew", Domain: "test.io", Version: "v1beta", Kind: "FirstMate"}), - Entry("multiple unstable version", - &Options{Group: "crew", Domain: "test.io", Version: "v1beta1alpha1", Kind: "FirstMate"}), - Entry("missing kind", - &Options{Group: "crew", Domain: "test.io", Version: "v1"}), - Entry("kind is too long", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: strings.Repeat("a", 64)}), - Entry("kind with whitespaces", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "First Mate"}), - Entry("kind ends with `-`", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate-"}), - Entry("kind starts with a decimal character", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "1FirstMate"}), - Entry("kind starts with a lowercase character", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "firstMate"}), - Entry("Invalid CRD version", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", CRDVersion: "a"}), - Entry("Invalid webhook version", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", WebhookVersion: "a"}), + func(options Options) { Expect(options.Validate()).NotTo(Succeed()) }, + Entry("group flag captured another flag", Options{Group: "--version"}), + Entry("version flag captured another flag", Options{Version: "--kind"}), + Entry("kind flag captured another flag", Options{Kind: "--group"}), + Entry("missing group and domain", Options{Version: "v1", Kind: "FirstMate"}), + Entry("missing version", Options{Group: "crew", Domain: "test.io", Kind: "FirstMate"}), + Entry("missing kind", Options{Group: "crew", Domain: "test.io", Version: "v1"}), ) }) @@ -96,7 +56,7 @@ var _ = Describe("Options", func() { }) DescribeTable("should succeed if the Resource is valid", - func(options *Options) { + func(options Options) { Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { @@ -105,6 +65,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Group).To(Equal(options.Group)) Expect(resource.Domain).To(Equal(options.Domain)) Expect(resource.Version).To(Equal(options.Version)) @@ -127,13 +88,13 @@ var _ = Describe("Options", func() { Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) } }, - Entry("basic", &Options{ + Entry("basic", Options{ Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", }), - Entry("API", &Options{ + Entry("API", Options{ Group: "crew", Domain: "test.io", Version: "v1", @@ -142,28 +103,28 @@ var _ = Describe("Options", func() { CRDVersion: "v1", Namespaced: true, }), - Entry("Controller", &Options{ + Entry("Controller", Options{ Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", DoController: true, }), - Entry("Webhooks", &Options{ + Entry("Webhooks", Options{ Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", + WebhookVersion: "v1", DoDefaulting: true, DoValidation: true, DoConversion: true, - WebhookVersion: "v1", }), ) DescribeTable("should default the Plural by pluralizing the Kind", func(kind, plural string) { - options := &Options{Group: "crew", Version: "v1", Kind: kind} + options := Options{Group: "crew", Version: "v1", Kind: kind} Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { @@ -172,6 +133,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Plural).To(Equal(plural)) } }, @@ -182,7 +144,7 @@ var _ = Describe("Options", func() { DescribeTable("should keep the Plural if specified", func(kind, plural string) { - options := &Options{Group: "crew", Version: "v1", Kind: kind, Plural: plural} + options := Options{Group: "crew", Version: "v1", Kind: kind, Plural: plural} Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { @@ -191,6 +153,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Plural).To(Equal(plural)) } }, @@ -200,7 +163,7 @@ var _ = Describe("Options", func() { DescribeTable("should allow hyphens and dots in group names", func(group, safeGroup string) { - options := &Options{ + options := Options{ Group: group, Domain: "test.io", Version: "v1", @@ -214,6 +177,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Group).To(Equal(options.Group)) if multiGroup { Expect(resource.Path).To(Equal( @@ -231,7 +195,7 @@ var _ = Describe("Options", func() { ) It("should not append '.' if provided an empty domain", func() { - options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"} + options := Options{Group: "crew", Version: "v1", Kind: "FirstMate"} Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { @@ -240,13 +204,14 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.QualifiedGroup()).To(Equal(options.Group)) } }) DescribeTable("should use core apis", func(group, qualified string) { - options := &Options{ + options := Options{ Group: group, Domain: "test.io", Version: "v1", @@ -260,6 +225,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) Expect(resource.API.CRDVersion).To(Equal("")) Expect(resource.QualifiedGroup()).To(Equal(qualified)) @@ -272,7 +238,7 @@ var _ = Describe("Options", func() { It("should use domain if the group is empty", func() { safeDomain := "testio" - options := &Options{ + options := Options{ Domain: "test.io", Version: "v1", Kind: "FirstMate", @@ -285,6 +251,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Group).To(Equal("")) if multiGroup { Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "apis", options.Version))) diff --git a/pkg/plugins/golang/v2/api.go b/pkg/plugins/golang/v2/api.go index 6516e75af7a..f73b41e4819 100644 --- a/pkg/plugins/golang/v2/api.go +++ b/pkg/plugins/golang/v2/api.go @@ -29,6 +29,7 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" @@ -44,6 +45,8 @@ type createAPISubcommand struct { options *Options + resource resource.Resource + // Check if we have to scaffold resource and/or controller resourceFlag *pflag.Flag controllerFlag *pflag.Flag @@ -124,14 +127,7 @@ func (p *createAPISubcommand) InjectConfig(c config.Config) { } func (p *createAPISubcommand) Run() error { - return cmdutil.Run(p) -} - -func (p *createAPISubcommand) Validate() error { - if err := p.options.Validate(); err != nil { - return err - } - + // Ask for API and Controller if not specified reader := bufio.NewReader(os.Stdin) if !p.resourceFlag.Changed { fmt.Println("Create Resource [y/n]") @@ -142,15 +138,30 @@ func (p *createAPISubcommand) Validate() error { p.options.DoController = util.YesNo(reader) } + // Create the resource from the options + p.resource = p.options.NewResource(p.config) + + return cmdutil.Run(p) +} + +func (p *createAPISubcommand) Validate() error { + if err := p.options.Validate(); err != nil { + return err + } + + 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 { + if p.resource.HasAPI() { // Check that resource doesn't exist or flag force was set - if !p.force && p.config.HasResource(p.options.GVK()) { + if !p.force && p.config.HasResource(p.resource.GVK) { 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.options.Group) { + 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 %s", "kubebuilder.io/migration/multi-group.html") } @@ -177,9 +188,7 @@ func (p *createAPISubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { return nil, fmt.Errorf("unknown pattern %q", p.pattern) } - // Create the resource from the options - res := p.options.NewResource(p.config) - return scaffolds.NewAPIScaffolder(p.config, string(bp), res, p.force, plugins), nil + return scaffolds.NewAPIScaffolder(p.config, string(bp), p.resource, p.force, plugins), nil } func (p *createAPISubcommand) PostScaffold() error { diff --git a/pkg/plugins/golang/v2/options.go b/pkg/plugins/golang/v2/options.go index 692a57123c7..4b3613fe295 100644 --- a/pkg/plugins/golang/v2/options.go +++ b/pkg/plugins/golang/v2/options.go @@ -19,19 +19,13 @@ package v2 import ( "fmt" "path" - "regexp" "strings" newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" - "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) const ( - v1beta1 = "v1beta1" - v1 = "v1" - - versionPattern = "^v\\d+(?:alpha\\d+|beta\\d+)?$" groupPresent = "group flag present but empty" versionPresent = "version flag present but empty" kindPresent = "kind flag present but empty" @@ -41,8 +35,6 @@ const ( ) var ( - versionRegex = regexp.MustCompile(versionPattern) - coreGroups = map[string]string{ "admission": "k8s.io", "admissionregistration": "k8s.io", @@ -116,6 +108,7 @@ func (opts Options) Validate() error { if strings.HasPrefix(opts.Kind, "-") { return fmt.Errorf(kindPresent) } + // Now we can check that all the required flags are not empty if len(opts.Group) == 0 { return fmt.Errorf(groupRequired) @@ -127,54 +120,9 @@ func (opts Options) Validate() error { return fmt.Errorf(kindRequired) } - // Check if the qualified group has a valid DNS1123 subdomain value - if err := validation.IsDNS1123Subdomain(opts.QualifiedGroup()); err != nil { - return fmt.Errorf("either group or domain is invalid: (%v)", err) - } - - // Check if the version follows the valid pattern - if !versionRegex.MatchString(opts.Version) { - return fmt.Errorf("version must match %s (was %s)", versionPattern, opts.Version) - } - - validationErrors := []string{} - - // Require kind to start with an uppercase character - if string(opts.Kind[0]) == strings.ToLower(string(opts.Kind[0])) { - validationErrors = append(validationErrors, "kind must start with an uppercase character") - } - - validationErrors = append(validationErrors, validation.IsDNS1035Label(strings.ToLower(opts.Kind))...) - - if len(validationErrors) != 0 { - return fmt.Errorf("invalid Kind: %#v", validationErrors) - } - - // TODO: validate plural strings if provided - - // Ensure apiVersions for k8s types are empty or valid. - for typ, apiVersion := range map[string]string{ - "CRD": opts.CRDVersion, - "Webhook": opts.WebhookVersion, - } { - switch apiVersion { - case "", v1beta1, v1: - default: - return fmt.Errorf("%s version must be one of: v1, v1beta1", typ) - } - } - return nil } -// QualifiedGroup returns the fully qualified group name with the available information. -func (opts Options) QualifiedGroup() string { - if opts.Domain == "" { - return opts.Group - } - return fmt.Sprintf("%s.%s", opts.Group, opts.Domain) -} - // GVK returns the GVK identifier of a resource. func (opts Options) GVK() resource.GVK { return resource.GVK{ diff --git a/pkg/plugins/golang/v2/options_test.go b/pkg/plugins/golang/v2/options_test.go index 038121e21ba..e4997e684a7 100644 --- a/pkg/plugins/golang/v2/options_test.go +++ b/pkg/plugins/golang/v2/options_test.go @@ -18,7 +18,6 @@ package v2 import ( "path" - "strings" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" @@ -31,57 +30,19 @@ import ( var _ = Describe("Options", func() { Context("Validate", func() { DescribeTable("should succeed for valid options", - func(options *Options) { Expect(options.Validate()).To(Succeed()) }, - Entry("full GVK", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("missing domain", - &Options{Group: "crew", Version: "v1", Kind: "FirstMate"}), - Entry("kind with multiple initial uppercase characters", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FIRSTMate"}), + func(options Options) { Expect(options.Validate()).To(Succeed()) }, + Entry("full GVK", Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("missing domain", Options{Group: "crew", Version: "v1", Kind: "FirstMate"}), ) DescribeTable("should fail for invalid options", - func(options *Options) { Expect(options.Validate()).NotTo(Succeed()) }, - Entry("group flag captured another flag", - &Options{Group: "--version"}), - Entry("version flag captured another flag", - &Options{Version: "--kind"}), - Entry("kind flag captured another flag", - &Options{Kind: "--group"}), - Entry("missing group", - &Options{Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("group with uppercase characters", - &Options{Group: "Crew", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("group with non-alpha characters", - &Options{Group: "crew1*?", Domain: "test.io", Version: "v1", Kind: "FirstMate"}), - Entry("missing version", - &Options{Group: "crew", Domain: "test.io", Kind: "FirstMate"}), - Entry("version without v prefix", - &Options{Group: "crew", Domain: "test.io", Version: "1", Kind: "FirstMate"}), - Entry("unstable version without v prefix", - &Options{Group: "crew", Domain: "test.io", Version: "1beta1", Kind: "FirstMate"}), - Entry("unstable version with wrong prefix", - &Options{Group: "crew", Domain: "test.io", Version: "a1beta1", Kind: "FirstMate"}), - Entry("unstable version without alpha/beta number", - &Options{Group: "crew", Domain: "test.io", Version: "v1beta", Kind: "FirstMate"}), - Entry("multiple unstable version", - &Options{Group: "crew", Domain: "test.io", Version: "v1beta1alpha1", Kind: "FirstMate"}), - Entry("missing kind", - &Options{Group: "crew", Domain: "test.io", Version: "v1"}), - Entry("kind is too long", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: strings.Repeat("a", 64)}), - Entry("kind with whitespaces", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "First Mate"}), - Entry("kind ends with `-`", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate-"}), - Entry("kind starts with a decimal character", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "1FirstMate"}), - Entry("kind starts with a lowercase character", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "firstMate"}), - Entry("Invalid CRD version", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", CRDVersion: "a"}), - Entry("Invalid webhook version", - &Options{Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", WebhookVersion: "a"}), + func(options Options) { Expect(options.Validate()).NotTo(Succeed()) }, + Entry("group flag captured another flag", Options{Group: "--version"}), + Entry("version flag captured another flag", Options{Version: "--kind"}), + Entry("kind flag captured another flag", Options{Kind: "--group"}), + Entry("missing group", Options{Domain: "test.io", Version: "v1", Kind: "FirstMate"}), + Entry("missing version", Options{Group: "crew", Domain: "test.io", Kind: "FirstMate"}), + Entry("missing kind", Options{Group: "crew", Domain: "test.io", Version: "v1"}), ) }) @@ -94,7 +55,7 @@ var _ = Describe("Options", func() { }) DescribeTable("should succeed if the Resource is valid", - func(options *Options) { + func(options Options) { Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { @@ -103,6 +64,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Group).To(Equal(options.Group)) Expect(resource.Domain).To(Equal(options.Domain)) Expect(resource.Version).To(Equal(options.Version)) @@ -125,29 +87,29 @@ var _ = Describe("Options", func() { Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) } }, - Entry("basic", &Options{ + Entry("basic", Options{ Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", }), - Entry("API", &Options{ + Entry("API", Options{ Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", DoAPI: true, - CRDVersion: v1beta1, + CRDVersion: "v1beta1", Namespaced: true, }), - Entry("Controller", &Options{ + Entry("Controller", Options{ Group: "crew", Domain: "test.io", Version: "v1", Kind: "FirstMate", DoController: true, }), - Entry("Webhooks", &Options{ + Entry("Webhooks", Options{ Group: "crew", Domain: "test.io", Version: "v1", @@ -155,13 +117,13 @@ var _ = Describe("Options", func() { DoDefaulting: true, DoValidation: true, DoConversion: true, - WebhookVersion: v1beta1, + WebhookVersion: "v1beta1", }), ) DescribeTable("should default the Plural by pluralizing the Kind", func(kind, plural string) { - options := &Options{Group: "crew", Version: "v1", Kind: kind} + options := Options{Group: "crew", Version: "v1", Kind: kind} Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { @@ -170,6 +132,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Plural).To(Equal(plural)) } }, @@ -180,7 +143,7 @@ var _ = Describe("Options", func() { DescribeTable("should keep the Plural if specified", func(kind, plural string) { - options := &Options{Group: "crew", Version: "v1", Kind: kind, Plural: plural} + options := Options{Group: "crew", Version: "v1", Kind: kind, Plural: plural} Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { @@ -189,6 +152,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Plural).To(Equal(plural)) } }, @@ -198,7 +162,7 @@ var _ = Describe("Options", func() { DescribeTable("should allow hyphens and dots in group names", func(group, safeGroup string) { - options := &Options{ + options := Options{ Group: group, Domain: "test.io", Version: "v1", @@ -212,6 +176,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Group).To(Equal(options.Group)) if multiGroup { Expect(resource.Path).To(Equal( @@ -229,7 +194,7 @@ var _ = Describe("Options", func() { ) It("should not append '.' if provided an empty domain", func() { - options := &Options{Group: "crew", Version: "v1", Kind: "FirstMate"} + options := Options{Group: "crew", Version: "v1", Kind: "FirstMate"} Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { @@ -238,13 +203,14 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.QualifiedGroup()).To(Equal(options.Group)) } }) DescribeTable("should use core apis", func(group, qualified string) { - options := &Options{ + options := Options{ Group: group, Domain: "test.io", Version: "v1", @@ -258,6 +224,7 @@ var _ = Describe("Options", func() { } resource := options.NewResource(cfg) + Expect(resource.Validate()).To(Succeed()) Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) Expect(resource.API.CRDVersion).To(Equal("")) Expect(resource.QualifiedGroup()).To(Equal(qualified)) diff --git a/pkg/plugins/golang/v2/webhook.go b/pkg/plugins/golang/v2/webhook.go index f6df11591db..2c4b2f8bb7e 100644 --- a/pkg/plugins/golang/v2/webhook.go +++ b/pkg/plugins/golang/v2/webhook.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/pflag" newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" @@ -35,6 +36,8 @@ type createWebhookSubcommand struct { commandName string options *Options + + resource resource.Resource } var ( @@ -80,6 +83,9 @@ func (p *createWebhookSubcommand) InjectConfig(c newconfig.Config) { } func (p *createWebhookSubcommand) Run() error { + // Create the resource from the options + p.resource = p.options.NewResource(p.config) + return cmdutil.Run(p) } @@ -88,15 +94,18 @@ func (p *createWebhookSubcommand) Validate() error { return err } - if !p.options.DoDefaulting && !p.options.DoValidation && !p.options.DoConversion { + 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 !p.config.HasResource(p.options.GVK()) { - return fmt.Errorf("%s create webhook requires an api with the group,"+ - " kind and version provided", p.commandName) + if !p.config.HasResource(p.resource.GVK) { + return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) } return nil @@ -109,9 +118,7 @@ func (p *createWebhookSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { return nil, fmt.Errorf("unable to load boilerplate: %v", err) } - // Create the resource from the options - res := p.options.NewResource(p.config) - return scaffolds.NewWebhookScaffolder(p.config, string(bp), res), nil + return scaffolds.NewWebhookScaffolder(p.config, string(bp), p.resource), nil } func (p *createWebhookSubcommand) PostScaffold() error { diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index 04d90c0209f..3a08897508c 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -29,6 +29,7 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" @@ -58,6 +59,8 @@ type createAPISubcommand struct { options *goPlugin.Options + resource resource.Resource + // Check if we have to scaffold resource and/or controller resourceFlag *pflag.Flag controllerFlag *pflag.Flag @@ -140,6 +143,21 @@ func (p *createAPISubcommand) InjectConfig(c config.Config) { } func (p *createAPISubcommand) Run() error { + // TODO: re-evaluate whether y/n input still makes sense. We should probably always + // scaffold the resource and controller. + 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) + } + + // Create the resource from the options + p.resource = p.options.NewResource(p.config) + return cmdutil.Run(p) } @@ -148,8 +166,8 @@ func (p *createAPISubcommand) Validate() error { return err } - if p.options.Group == "" && p.options.Domain == "" { - return fmt.Errorf("can not have group and domain both empty") + if err := p.resource.Validate(); err != nil { + return err } // check if main.go is present in the root directory @@ -157,35 +175,23 @@ func (p *createAPISubcommand) Validate() error { return fmt.Errorf("%s file should present in the root directory", DefaultMainPath) } - // TODO: re-evaluate whether y/n input still makes sense. We should probably always - // scaffold the resource and controller. - 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) - } - // In case we want to scaffold a resource API we need to do some checks - if p.options.DoAPI { + if p.resource.HasAPI() { // Check that resource doesn't exist or flag force was set - if res, err := p.config.GetResource(p.options.GVK()); err == nil && res.HasAPI() && !p.force { + if res, err := p.config.GetResource(p.resource.GVK); err == nil && res.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.options.Group) { + 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 kubebuilder.io/migration/multi-group.html") } // Check CRDVersion against all other CRDVersions in p.config for compatibility. - if !p.config.IsCRDVersionCompatible(p.options.CRDVersion) { + if !p.config.IsCRDVersionCompatible(p.resource.API.CRDVersion) { return fmt.Errorf("only one CRD version can be used for all resources, cannot add %q", - p.options.CRDVersion) + p.resource.API.CRDVersion) } } @@ -210,9 +216,7 @@ func (p *createAPISubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { return nil, fmt.Errorf("unknown pattern %q", p.pattern) } - // Create the resource from the options - res := p.options.NewResource(p.config) - return scaffolds.NewAPIScaffolder(p.config, string(bp), res, p.force, plugins), nil + return scaffolds.NewAPIScaffolder(p.config, string(bp), p.resource, p.force, plugins), nil } func (p *createAPISubcommand) PostScaffold() error { diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go index ac64f39c02e..d7bfda82c4c 100644 --- a/pkg/plugins/golang/v3/webhook.go +++ b/pkg/plugins/golang/v3/webhook.go @@ -25,6 +25,7 @@ import ( "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" + "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" @@ -42,6 +43,8 @@ type createWebhookSubcommand struct { options *goPlugin.Options + resource resource.Resource + // force indicates that the resource should be created even if it already exists force bool @@ -97,6 +100,9 @@ func (p *createWebhookSubcommand) InjectConfig(c config.Config) { } func (p *createWebhookSubcommand) Run() error { + // Create the resource from the options + p.resource = p.options.NewResource(p.config) + return cmdutil.Run(p) } @@ -105,22 +111,25 @@ func (p *createWebhookSubcommand) Validate() error { return err } - if !p.options.DoDefaulting && !p.options.DoValidation && !p.options.DoConversion { + 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.options.GVK()); err != nil { - return fmt.Errorf("%s create webhook requires an api with the group,"+ - " kind and version provided", p.commandName) + if r, err := p.config.GetResource(p.resource.GVK); err != nil { + return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) } else if r.Webhooks != nil && !r.Webhooks.IsEmpty() && !p.force { return errors.New("webhook resource already exists") } - if !p.config.IsWebhookVersionCompatible(p.options.WebhookVersion) { + if !p.config.IsWebhookVersionCompatible(p.resource.Webhooks.WebhookVersion) { return fmt.Errorf("only one webhook version can be used for all resources, cannot add %q", - p.options.WebhookVersion) + p.resource.Webhooks.WebhookVersion) } return nil @@ -133,9 +142,7 @@ func (p *createWebhookSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { return nil, fmt.Errorf("unable to load boilerplate: %v", err) } - // Create the resource from the options - res := p.options.NewResource(p.config) - return scaffolds.NewWebhookScaffolder(p.config, string(bp), res, p.force), nil + return scaffolds.NewWebhookScaffolder(p.config, string(bp), p.resource, p.force), nil } func (p *createWebhookSubcommand) PostScaffold() error { From 2aef3ad65003cfd8bd09ef8766eca5af10a64c70 Mon Sep 17 00:00:00 2001 From: Adrian Orive Oneca Date: Fri, 22 Jan 2021 15:05:09 +0100 Subject: [PATCH 16/38] Add the rest of the missing fields and stabilize config v3 Signed-off-by: Adrian Orive Oneca --- VERSIONING.md | 21 +-- cmd/main.go | 6 +- .../testdata/project/PROJECT | 17 +- docs/book/src/migration/project/v2_v3.md | 38 +--- pkg/cli/cli.go | 4 +- pkg/cli/cli_test.go | 4 +- pkg/config/interface.go | 20 +- pkg/config/v2/config_test.go | 2 +- pkg/config/{v3alpha => v3}/config.go | 54 +++--- pkg/config/{v3alpha => v3}/config_test.go | 175 ++++++++++-------- pkg/plugins/golang/options_test.go | 4 +- pkg/plugins/golang/v2/init.go | 10 +- pkg/plugins/golang/v2/plugin.go | 4 +- pkg/plugins/golang/v3/plugin.go | 4 +- test.sh | 8 +- test/e2e/v3/generate_test.go | 4 +- testdata/project-v3-addon/PROJECT | 13 +- testdata/project-v3-config/PROJECT | 17 +- testdata/project-v3-multigroup/PROJECT | 39 +++- testdata/project-v3/PROJECT | 18 +- 20 files changed, 274 insertions(+), 188 deletions(-) rename pkg/config/{v3alpha => v3}/config.go (84%) rename pkg/config/{v3alpha => v3}/config_test.go (81%) diff --git a/VERSIONING.md b/VERSIONING.md index 82404cc9187..9cbdfb4bd71 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -64,8 +64,8 @@ take care of building and publishing the artifacts. | Name | Example | Description | |--- |--- |--- | | KubeBuilder version | `v2.2.0`, `v2.3.0`, `v2.3.1` | Tagged versions of the KubeBuilder project, representing changes to the source code in this repository. See the [releases][kb-releases] page for binary releases. | -| Project version | `"1"`, `"2"`, `"3-alpha"` | Project version defines the scheme of a `PROJECT` configuration file. This version is defined in a `PROJECT` file's `version`. | -| Plugin version | `v2`, `v3-alpha` | Represents the version of an individual plugin, as well as the corresponding scaffolding that it generates. This version is defined in a plugin key, ex. `go.kubebuilder.io/v2`. See the [design doc][cli-plugins-versioning] for more details. | +| Project version | `"1"`, `"2"`, `"3"` | Project version defines the scheme of a `PROJECT` configuration file. This version is defined in a `PROJECT` file's `version`. | +| Plugin version | `v2`, `v3` | Represents the version of an individual plugin, as well as the corresponding scaffolding that it generates. This version is defined in a plugin key, ex. `go.kubebuilder.io/v2`. See the [design doc][cli-plugins-versioning] for more details. | ### Incrementing versions @@ -76,32 +76,31 @@ Project versions should only be increased if a breaking change is introduced in Similarly, the introduction of a new plugin version might only lead to a new minor version release of KubeBuilder, since no breaking change is being made to the CLI itself. It'd only be a breaking change to KubeBuilder if we remove support for an older plugin version. See the plugins design doc [versioning section][cli-plugins-versioning] for more details on plugin versioning. -**NOTE:** the scheme for project version `"2"` was defined before the concept of plugins was introduced, so plugin `go.kubebuilder.io/v2` is implicitly used for those project types. Schema for project versions `"3-alpha"` and beyond define a `layout` key that informs the plugin system of which plugin to use. +**NOTE:** the scheme for project version `"2"` was defined before the concept of plugins was introduced, so plugin `go.kubebuilder.io/v2` is implicitly used for those project types. Schema for project versions `"3"` and beyond define a `layout` key that informs the plugin system of which plugin to use. ## Introducing changes to plugins Changes made to plugins only require a plugin version increase if and only if a change is made to a plugin that breaks projects scaffolded with the previous plugin version. Once a plugin version `vX` is stabilized (it doesn't have an "alpha" or "beta" suffix), a new plugin package should be created containing a new plugin with version -`v(X+1)-alpha`. Typically this is done by (semantically) `cp -r pkg/plugin/vX pkg/plugin/v(X+1)` then updating +`v(X+1)-alpha`. Typically this is done by (semantically) `cp -r pkg/plugins/golang/vX pkg/plugins/golang/v(X+1)` then updating version numbers and paths. All further breaking changes to the plugin should be made in this package; the `vX` plugin would then be frozen to breaking changes. +You must also add a migration guide to the [migrations](https://book.kubebuilder.io/migrations.html) +section of the KubeBuilder book in your PR. It should detail the steps required +for users to upgrade their projects from `vX` to `v(X+1)-alpha`. + ### Example -KubeBuilder scaffolds projects with plugin `go.kubebuilder.io/v2` by default. A `v3-alpha` version -was created after `v2` stabilized. +KubeBuilder scaffolds projects with plugin `go.kubebuilder.io/v3` by default. You create a feature that adds a new marker to the file `main.go` scaffolded by `init` that `create api` will use to update that file. The changes introduced in your feature would cause errors if used with projects built with plugins `go.kubebuilder.io/v2` without users manually updating their projects. Thus, your changes introduce a breaking change to plugin `go.kubebuilder.io`, and can only be merged into plugin version `v3-alpha`. -This plugin's package should exist already, so a PR must be made against the - -You must also add a migration guide to the [migrations](https://book.kubebuilder.io/migrations.html) -section of the KubeBuilder book in your PR. It should detail the steps required -for users to upgrade their projects from `v2` to `v3-alpha`. +This plugin's package should exist already. [kb-releases]:https://github.com/kubernetes-sigs/kubebuilder/releases [cli-plugins-versioning]:docs/book/src/reference/cli-plugins.md diff --git a/cmd/main.go b/cmd/main.go index 6f13054b4a4..a91a49a7821 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -21,7 +21,7 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/cli" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" - cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" pluginv2 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2" pluginv3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3" ) @@ -30,13 +30,13 @@ func main() { c, err := cli.New( cli.WithCommandName("kubebuilder"), cli.WithVersion(versionString()), - cli.WithDefaultProjectVersion(cfgv3alpha.Version), + cli.WithDefaultProjectVersion(cfgv3.Version), cli.WithPlugins( &pluginv2.Plugin{}, &pluginv3.Plugin{}, ), cli.WithDefaultPlugins(cfgv2.Version, &pluginv2.Plugin{}), - cli.WithDefaultPlugins(cfgv3alpha.Version, &pluginv3.Plugin{}), + cli.WithDefaultPlugins(cfgv3.Version, &pluginv3.Plugin{}), cli.WithCompletion, ) if err != nil { diff --git a/docs/book/src/component-config-tutorial/testdata/project/PROJECT b/docs/book/src/component-config-tutorial/testdata/project/PROJECT index bcf8439f704..ffc6d718274 100644 --- a/docs/book/src/component-config-tutorial/testdata/project/PROJECT +++ b/docs/book/src/component-config-tutorial/testdata/project/PROJECT @@ -5,18 +5,23 @@ multigroup: true projectName: project repo: tutorial.kubebuilder.io/project resources: -- crdVersion: v1 +- api: + crdVersion: v1 group: batch kind: CronJob version: v1 - webhookVersion: v1 -- crdVersion: v1 + webhooks: + webhookVersion: v1 +- api: + crdVersion: v1 group: batch kind: CronJob version: v2 - webhookVersion: v1 -- crdVersion: v1 + webhooks: + webhookVersion: v1 +- api: + crdVersion: v1 group: config kind: ProjectConfig version: v2 -version: 3-alpha +version: "3" diff --git a/docs/book/src/migration/project/v2_v3.md b/docs/book/src/migration/project/v2_v3.md index 2fe2340334d..c7efe930da9 100644 --- a/docs/book/src/migration/project/v2_v3.md +++ b/docs/book/src/migration/project/v2_v3.md @@ -39,11 +39,11 @@ layout: go.kubebuilder.io/v2 - Update the `version` -The `version` field represents the version of Project layouts. So, you ought to update this to `3-alpha`: +The `version` field represents the version of Project layouts. So, you ought to update this to `"3"`: ``` ... -version: 3-alpha` +version: "3" ... ``` @@ -58,7 +58,7 @@ resources: - group: webapp kind: Guestbook version: v1 -version: 3-alpha +version: "3" ``` ### Verification @@ -68,47 +68,23 @@ fine. ## Migrating your projects to use v3+ plugins - - - +In order to migrate your projects to v3 some changes need to be manually done to the PROJECT file, and some other scaffolded files. ### Update your PROJECT file -Update the `layout` setting to the new plugin version ` go.kubebuilder.io/v3-alpha` as follows: +Update the `layout` setting to the new plugin version `go.kubebuilder.io/v3` as follows: ```sh $ cat PROJECT domain: my.domain -layout: go.kubebuilder.io/v3-alpha +layout: go.kubebuilder.io/v3 projectName: example repo: example resources: - group: webapp kind: Guestbook version: v1 -version: 3-alpha +version: "3" ``` diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index cc67383a4b9..8c12384676b 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -26,7 +26,7 @@ import ( internalconfig "sigs.k8s.io/kubebuilder/v3/pkg/cli/internal/config" "sigs.k8s.io/kubebuilder/v3/pkg/config" - cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) @@ -131,7 +131,7 @@ func newCLI(opts ...Option) (*cli, error) { // Default cli options. c := &cli{ commandName: "kubebuilder", - defaultProjectVersion: cfgv3alpha.Version, + defaultProjectVersion: cfgv3.Version, defaultPlugins: make(map[config.Version][]string), plugins: make(map[string]plugin.Plugin), } diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index 61706d893cb..d9b7000e439 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -27,7 +27,7 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/config" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" - cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" ) @@ -203,7 +203,7 @@ var _ = Describe("CLI", func() { When("having layout field", func() { It("should succeed", func() { - projectConfig = cfgv3alpha.New() + projectConfig = cfgv3.New() Expect(projectConfig.SetLayout("go.kubebuilder.io/v2")).To(Succeed()) projectVersion, plugins, err = getInfoFromConfig(projectConfig) Expect(err).NotTo(HaveOccurred()) diff --git a/pkg/config/interface.go b/pkg/config/interface.go index aae0ece0e5f..8621d5f8afd 100644 --- a/pkg/config/interface.go +++ b/pkg/config/interface.go @@ -40,17 +40,17 @@ type Config interface { SetRepository(repository string) error // GetProjectName returns the project name - // This method was introduced in project version 3-alpha. + // This method was introduced in project version 3. GetProjectName() string // SetProjectName sets the project name - // This method was introduced in project version 3-alpha. + // This method was introduced in project version 3. SetProjectName(name string) error // GetLayout returns the config layout - // This method was introduced in project version 3-alpha. + // This method was introduced in project version 3. GetLayout() string // SetLayout sets the Config layout - // This method was introduced in project version 3-alpha. + // This method was introduced in project version 3. SetLayout(layout string) error /* Boolean fields */ @@ -63,13 +63,13 @@ type Config interface { ClearMultiGroup() error // IsComponentConfig checks if component config is enabled - // This method was introduced in project version 3-alpha. + // This method was introduced in project version 3. IsComponentConfig() bool // SetComponentConfig enables component config - // This method was introduced in project version 3-alpha. + // This method was introduced in project version 3. SetComponentConfig() error // ClearComponentConfig disables component config - // This method was introduced in project version 3-alpha. + // This method was introduced in project version 3. ClearComponentConfig() error /* Resources */ @@ -97,12 +97,10 @@ type Config interface { /* Plugins */ // DecodePluginConfig decodes a plugin config stored in Config into configObj, which must be a pointer. - // This method is intended to be used for custom configuration objects, which were introduced in project version - // 3-alpha. + // This method is intended to be used for custom configuration objects, which were introduced in project version 3. DecodePluginConfig(key string, configObj interface{}) error // EncodePluginConfig encodes a config object into Config by overwriting the existing object stored under key. - // This method is intended to be used for custom configuration objects, which were introduced in project version - // 3-alpha. + // This method is intended to be used for custom configuration objects, which were introduced in project version 3. EncodePluginConfig(key string, configObj interface{}) error /* Persistence */ diff --git a/pkg/config/v2/config_test.go b/pkg/config/v2/config_test.go index 0db44a80334..8c5359550dc 100644 --- a/pkg/config/v2/config_test.go +++ b/pkg/config/v2/config_test.go @@ -78,7 +78,7 @@ var _ = Describe("cfg", func() { }) }) - Context("Name", func() { + Context("ProjectName", func() { It("GetProjectName should return an empty name", func() { Expect(c.GetProjectName()).To(Equal("")) }) diff --git a/pkg/config/v3alpha/config.go b/pkg/config/v3/config.go similarity index 84% rename from pkg/config/v3alpha/config.go rename to pkg/config/v3/config.go index 72464d9ce4e..78e48e354ec 100644 --- a/pkg/config/v3alpha/config.go +++ b/pkg/config/v3/config.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v3alpha +package v3 import ( "fmt" @@ -24,11 +24,10 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" - "sigs.k8s.io/kubebuilder/v3/pkg/model/stage" ) -// Version is the config.Version for project configuration 3-alpha -var Version = config.Version{Number: 3, Stage: stage.Alpha} +// Version is the config.Version for project configuration 3 +var Version = config.Version{Number: 3} type cfg struct { // Version @@ -157,8 +156,6 @@ func (c cfg) ResourcesLength() int { // HasResource implements config.Config func (c cfg) HasResource(gvk resource.GVK) bool { - gvk.Domain = "" // Version 3 alpha does not include domain per resource - for _, res := range c.Resources { if gvk.IsEqualTo(res.GVK) { return true @@ -170,11 +167,16 @@ func (c cfg) HasResource(gvk resource.GVK) bool { // GetResource implements config.Config func (c cfg) GetResource(gvk resource.GVK) (resource.Resource, error) { - gvk.Domain = "" // Version 3 alpha does not include domain per resource - for _, res := range c.Resources { if gvk.IsEqualTo(res.GVK) { - return res.Copy(), nil + r := res.Copy() + + // Plural is only stored if irregular, so if it is empty recover the regular form + if r.Plural == "" { + r.Plural = resource.RegularPlural(r.Kind) + } + + return r, nil } } @@ -185,25 +187,17 @@ func (c cfg) GetResource(gvk resource.GVK) (resource.Resource, error) { func (c cfg) GetResources() ([]resource.Resource, error) { resources := make([]resource.Resource, 0, len(c.Resources)) for _, res := range c.Resources { - resources = append(resources, res.Copy()) - } + r := res.Copy() - return resources, nil -} + // Plural is only stored if irregular, so if it is empty recover the regular form + if r.Plural == "" { + r.Plural = resource.RegularPlural(r.Kind) + } -func discardNonIncludedFields(res *resource.Resource) { - res.Domain = "" // Version 3 alpha does not include domain per resource - res.Plural = "" // Version 3 alpha does not include plural forms - res.Path = "" // Version 3 alpha does not include paths - if res.API != nil { - res.API.Namespaced = false // Version 3 alpha does not include if the api was namespaced - } - res.Controller = false // Version 3 alpha does not include if the controller was scaffolded - if res.Webhooks != nil { - res.Webhooks.Defaulting = false // Version 3 alpha does not include if the defaulting webhook was scaffolded - res.Webhooks.Validation = false // Version 3 alpha does not include if the validation webhook was scaffolded - res.Webhooks.Conversion = false // Version 3 alpha does not include if the conversion webhook was scaffolded + resources = append(resources, r) } + + return resources, nil } // AddResource implements config.Config @@ -211,7 +205,10 @@ func (c *cfg) AddResource(res resource.Resource) error { // As res is passed by value it is already a shallow copy, but we need to make a deep copy res = res.Copy() - discardNonIncludedFields(&res) // Version 3 alpha does not include several fields from the Resource model + // Plural is only stored if irregular + if res.Plural == resource.RegularPlural(res.Kind) { + res.Plural = "" + } if !c.HasResource(res.GVK) { c.Resources = append(c.Resources, res) @@ -224,7 +221,10 @@ func (c *cfg) UpdateResource(res resource.Resource) error { // As res is passed by value it is already a shallow copy, but we need to make a deep copy res = res.Copy() - discardNonIncludedFields(&res) // Version 3 alpha does not include several fields from the Resource model + // Plural is only stored if irregular + if res.Plural == resource.RegularPlural(res.Kind) { + res.Plural = "" + } for i, r := range c.Resources { if res.GVK.IsEqualTo(r.GVK) { diff --git a/pkg/config/v3alpha/config_test.go b/pkg/config/v3/config_test.go similarity index 81% rename from pkg/config/v3alpha/config_test.go rename to pkg/config/v3/config_test.go index 111fd98524b..853a41b56ad 100644 --- a/pkg/config/v3alpha/config_test.go +++ b/pkg/config/v3/config_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v3alpha +package v3 import ( "testing" @@ -26,9 +26,9 @@ import ( "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) -func TestConfigV3Alpha(t *testing.T) { +func TestConfigV3(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "Config V3-Alpha Suite") + RunSpecs(t, "Config V3 Suite") } var _ = Describe("cfg", func() { @@ -41,7 +41,7 @@ var _ = Describe("cfg", func() { otherDomain = "other.domain" otherRepo = "otherrepo" otherName = "OtherProjectName" - otherLayout = "go.kubebuilder.io/v3-alpha" + otherLayout = "go.kubebuilder.io/v3" ) var c cfg @@ -57,7 +57,7 @@ var _ = Describe("cfg", func() { }) Context("Version", func() { - It("GetVersion should return version 3-alpha", func() { + It("GetVersion should return version 3", func() { Expect(c.GetVersion().Compare(Version)).To(Equal(0)) }) }) @@ -84,7 +84,7 @@ var _ = Describe("cfg", func() { }) }) - Context("Name", func() { + Context("ProjectName", func() { It("GetProjectName should return the name", func() { Expect(c.GetProjectName()).To(Equal(name)) }) @@ -151,29 +151,62 @@ var _ = Describe("cfg", func() { }) Context("Resources", func() { - var res = resource.Resource{ - GVK: resource.GVK{ - Group: "group", - Version: "v1", - Kind: "Kind", - }, - API: &resource.API{ - CRDVersion: "v1", - Namespaced: true, - }, - Controller: true, - Webhooks: &resource.Webhooks{ - WebhookVersion: "v1", - Defaulting: true, - Validation: true, - Conversion: true, - }, + var ( + res = resource.Resource{ + GVK: resource.GVK{ + Group: "group", + Version: "v1", + Kind: "Kind", + }, + Plural: "kinds", + Path: "api/v1", + API: &resource.API{ + CRDVersion: "v1", + Namespaced: true, + }, + Controller: true, + Webhooks: &resource.Webhooks{ + WebhookVersion: "v1", + Defaulting: true, + Validation: true, + Conversion: true, + }, + } + resWithoutPlural = res.Copy() + ) + + // As some of the tests insert directly into the slice without using the interface methods, + // regular plural forms should not be present in here. rsWithoutPlural is used for this purpose. + resWithoutPlural.Plural = "" + + // Auxiliary function for GetResource, AddResource and UpdateResource tests + checkResource := func(result, expected resource.Resource) { + Expect(result.GVK.IsEqualTo(expected.GVK)).To(BeTrue()) + Expect(result.Plural).To(Equal(expected.Plural)) + Expect(result.Path).To(Equal(expected.Path)) + if expected.API == nil { + Expect(result.API).To(BeNil()) + } else { + Expect(result.API).NotTo(BeNil()) + Expect(result.API.CRDVersion).To(Equal(expected.API.CRDVersion)) + Expect(result.API.Namespaced).To(Equal(expected.API.Namespaced)) + } + Expect(result.Controller).To(Equal(expected.Controller)) + if expected.Webhooks == nil { + Expect(result.Webhooks).To(BeNil()) + } else { + Expect(result.Webhooks).NotTo(BeNil()) + Expect(result.Webhooks.WebhookVersion).To(Equal(expected.Webhooks.WebhookVersion)) + Expect(result.Webhooks.Defaulting).To(Equal(expected.Webhooks.Defaulting)) + Expect(result.Webhooks.Validation).To(Equal(expected.Webhooks.Validation)) + Expect(result.Webhooks.Conversion).To(Equal(expected.Webhooks.Conversion)) + } } DescribeTable("ResourcesLength should return the number of resources", func(n int) { for i := 0; i < n; i++ { - c.Resources = append(c.Resources, res) + c.Resources = append(c.Resources, resWithoutPlural) } Expect(c.ResourcesLength()).To(Equal(n)) }, @@ -187,7 +220,7 @@ var _ = Describe("cfg", func() { }) It("HasResource should return true for an existent resource", func() { - c.Resources = append(c.Resources, res) + c.Resources = append(c.Resources, resWithoutPlural) Expect(c.HasResource(res.GVK)).To(BeTrue()) }) @@ -197,43 +230,26 @@ var _ = Describe("cfg", func() { }) It("GetResource should return an existent resource", func() { - c.Resources = append(c.Resources, res) + c.Resources = append(c.Resources, resWithoutPlural) r, err := c.GetResource(res.GVK) Expect(err).NotTo(HaveOccurred()) - Expect(r.GVK.IsEqualTo(res.GVK)).To(BeTrue()) - Expect(r.API).NotTo(BeNil()) - Expect(r.API.CRDVersion).To(Equal(res.API.CRDVersion)) - Expect(r.Webhooks).NotTo(BeNil()) - Expect(r.Webhooks.WebhookVersion).To(Equal(res.Webhooks.WebhookVersion)) + + checkResource(r, res) }) It("GetResources should return a slice of the tracked resources", func() { - c.Resources = append(c.Resources, res, res, res) + c.Resources = append(c.Resources, resWithoutPlural, resWithoutPlural, resWithoutPlural) resources, err := c.GetResources() Expect(err).NotTo(HaveOccurred()) Expect(resources).To(Equal([]resource.Resource{res, res, res})) }) - // Auxiliary function for AddResource and UpdateResource tests - checkResource := func(result, expected resource.Resource) { - Expect(result.GVK.IsEqualTo(expected.GVK)).To(BeTrue()) - Expect(result.API).NotTo(BeNil()) - Expect(result.API.CRDVersion).To(Equal(expected.API.CRDVersion)) - Expect(result.API.Namespaced).To(BeFalse()) - Expect(result.Controller).To(BeFalse()) - Expect(result.Webhooks).NotTo(BeNil()) - Expect(result.Webhooks.WebhookVersion).To(Equal(expected.Webhooks.WebhookVersion)) - Expect(result.Webhooks.Defaulting).To(BeFalse()) - Expect(result.Webhooks.Validation).To(BeFalse()) - Expect(result.Webhooks.Conversion).To(BeFalse()) - } - It("AddResource should add the provided resource if non-existent", func() { l := len(c.Resources) Expect(c.AddResource(res)).To(Succeed()) Expect(len(c.Resources)).To(Equal(l + 1)) - checkResource(c.Resources[0], res) + checkResource(c.Resources[0], resWithoutPlural) }) It("AddResource should do nothing if the resource already exists", func() { @@ -248,37 +264,26 @@ var _ = Describe("cfg", func() { Expect(c.UpdateResource(res)).To(Succeed()) Expect(len(c.Resources)).To(Equal(l + 1)) - checkResource(c.Resources[0], res) + checkResource(c.Resources[0], resWithoutPlural) }) It("UpdateResource should update it if the resource already exists", func() { - c.Resources = append(c.Resources, resource.Resource{ + r := resource.Resource{ GVK: resource.GVK{ Group: "group", Version: "v1", Kind: "Kind", }, - }) + Path: "api/v1", + } + c.Resources = append(c.Resources, r) l := len(c.Resources) - Expect(c.Resources[0].GVK.IsEqualTo(res.GVK)).To(BeTrue()) - Expect(c.Resources[0].API).To(BeNil()) - Expect(c.Resources[0].Controller).To(BeFalse()) - Expect(c.Resources[0].Webhooks).To(BeNil()) + checkResource(c.Resources[0], r) Expect(c.UpdateResource(res)).To(Succeed()) Expect(len(c.Resources)).To(Equal(l)) - r := c.Resources[0] - Expect(r.GVK.IsEqualTo(res.GVK)).To(BeTrue()) - Expect(r.API).NotTo(BeNil()) - Expect(r.API.CRDVersion).To(Equal(res.API.CRDVersion)) - Expect(r.API.Namespaced).To(BeFalse()) - Expect(r.Controller).To(BeFalse()) - Expect(r.Webhooks).NotTo(BeNil()) - Expect(r.Webhooks.WebhookVersion).To(Equal(res.Webhooks.WebhookVersion)) - Expect(r.Webhooks.Defaulting).To(BeFalse()) - Expect(r.Webhooks.Validation).To(BeFalse()) - Expect(r.Webhooks.Conversion).To(BeFalse()) + checkResource(c.Resources[0], resWithoutPlural) }) It("HasGroup should return false with no tracked resources", func() { @@ -442,8 +447,9 @@ var _ = Describe("cfg", func() { Version: "v1", Kind: "Kind2", }, - API: &resource.API{CRDVersion: "v1"}, - Webhooks: &resource.Webhooks{WebhookVersion: "v1"}, + API: &resource.API{CRDVersion: "v1"}, + Controller: true, + Webhooks: &resource.Webhooks{WebhookVersion: "v1"}, }, { GVK: resource.GVK{ @@ -451,6 +457,7 @@ var _ = Describe("cfg", func() { Version: "v1-beta", Kind: "Kind", }, + Plural: "kindes", API: &resource.API{}, Webhooks: &resource.Webhooks{}, }, @@ -460,6 +467,17 @@ var _ = Describe("cfg", func() { Version: "v1", Kind: "Kind", }, + API: &resource.API{ + CRDVersion: "v1", + Namespaced: true, + }, + Controller: true, + Webhooks: &resource.Webhooks{ + WebhookVersion: "v1", + Defaulting: true, + Validation: true, + Conversion: true, + }, }, }, Plugins: PluginConfigs{ @@ -479,11 +497,11 @@ var _ = Describe("cfg", func() { layout: go.kubebuilder.io/v2 projectName: ProjectName repo: myrepo -version: 3-alpha +version: "3" ` s2 = `componentConfig: true domain: other.domain -layout: go.kubebuilder.io/v3-alpha +layout: go.kubebuilder.io/v3 multigroup: true plugins: plugin-x: @@ -502,6 +520,7 @@ resources: version: v1 - api: crdVersion: v1 + controller: true group: group kind: Kind2 version: v1 @@ -509,11 +528,21 @@ resources: webhookVersion: v1 - group: group kind: Kind + plural: kindes version: v1-beta -- group: group2 +- api: + crdVersion: v1 + namespaced: true + controller: true + group: group2 kind: Kind version: v1 -version: 3-alpha + webhooks: + conversion: true + defaulting: true + validation: true + webhookVersion: v1 +version: "3" ` ) @@ -560,13 +589,13 @@ version: 3-alpha Expect(c.Unmarshal([]byte(content))).NotTo(Succeed()) }, Entry("for unknown fields", `field: 1 -version: 3-alpha`), +version: "3"`), ) }) }) var _ = Describe("New", func() { - It("should return a new config for project configuration 3-alpha", func() { + It("should return a new config for project configuration 3", func() { Expect(New().GetVersion().Compare(Version)).To(Equal(0)) }) }) diff --git a/pkg/plugins/golang/options_test.go b/pkg/plugins/golang/options_test.go index 27e4db5cdac..4a3ee128e3b 100644 --- a/pkg/plugins/golang/options_test.go +++ b/pkg/plugins/golang/options_test.go @@ -25,7 +25,7 @@ import ( . "github.com/onsi/gomega" "sigs.k8s.io/kubebuilder/v3/pkg/config" - cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" ) var _ = Describe("Options", func() { @@ -91,7 +91,7 @@ var _ = Describe("Options", func() { var cfg config.Config BeforeEach(func() { - cfg = cfgv3alpha.New() + cfg = cfgv3.New() _ = cfg.SetRepository("test") }) diff --git a/pkg/plugins/golang/v2/init.go b/pkg/plugins/golang/v2/init.go index 34d9d9eb2e7..6ccb9dbc029 100644 --- a/pkg/plugins/golang/v2/init.go +++ b/pkg/plugins/golang/v2/init.go @@ -25,7 +25,7 @@ import ( "github.com/spf13/pflag" "sigs.k8s.io/kubebuilder/v3/pkg/config" - cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/internal/validation" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" @@ -95,14 +95,14 @@ func (p *initSubcommand) BindFlags(fs *pflag.FlagSet) { fs.StringVar(&p.domain, "domain", "my.domain", "domain for groups") 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.") - if p.config.GetVersion().Compare(cfgv3alpha.Version) >= 0 { + if p.config.GetVersion().Compare(cfgv2.Version) > 0 { fs.StringVar(&p.name, "project-name", "", "name of this project") } } func (p *initSubcommand) InjectConfig(c config.Config) { // v2+ project configs get a 'layout' value. - if c.GetVersion().Compare(cfgv3alpha.Version) >= 0 { + if c.GetVersion().Compare(cfgv2.Version) > 0 { _ = c.SetLayout(plugin.KeyFor(Plugin{})) } @@ -121,7 +121,7 @@ func (p *initSubcommand) Validate() error { } } - if p.config.GetVersion().Compare(cfgv3alpha.Version) >= 0 { + if p.config.GetVersion().Compare(cfgv2.Version) > 0 { // Assign a default project name if p.name == "" { dir, err := os.Getwd() @@ -155,7 +155,7 @@ func (p *initSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { if err := p.config.SetRepository(p.repo); err != nil { return nil, err } - if p.config.GetVersion().Compare(cfgv3alpha.Version) >= 0 { + if p.config.GetVersion().Compare(cfgv2.Version) > 0 { if err := p.config.SetProjectName(p.name); err != nil { return nil, err } diff --git a/pkg/plugins/golang/v2/plugin.go b/pkg/plugins/golang/v2/plugin.go index 314ba74d4b4..e04d35701af 100644 --- a/pkg/plugins/golang/v2/plugin.go +++ b/pkg/plugins/golang/v2/plugin.go @@ -19,7 +19,7 @@ package v2 import ( "sigs.k8s.io/kubebuilder/v3/pkg/config" cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" - cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins" ) @@ -27,7 +27,7 @@ import ( const pluginName = "go" + plugins.DefaultNameQualifier var ( - supportedProjectVersions = []config.Version{cfgv2.Version, cfgv3alpha.Version} + supportedProjectVersions = []config.Version{cfgv2.Version, cfgv3.Version} pluginVersion = plugin.Version{Number: 2} ) diff --git a/pkg/plugins/golang/v3/plugin.go b/pkg/plugins/golang/v3/plugin.go index f9b2d1af73f..f109b5d1223 100644 --- a/pkg/plugins/golang/v3/plugin.go +++ b/pkg/plugins/golang/v3/plugin.go @@ -18,7 +18,7 @@ package v3 import ( "sigs.k8s.io/kubebuilder/v3/pkg/config" - cfgv3alpha "sigs.k8s.io/kubebuilder/v3/pkg/config/v3alpha" + cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins" ) @@ -26,7 +26,7 @@ import ( const pluginName = "go" + plugins.DefaultNameQualifier var ( - supportedProjectVersions = []config.Version{cfgv3alpha.Version} + supportedProjectVersions = []config.Version{cfgv3.Version} pluginVersion = plugin.Version{Number: 3} ) diff --git a/test.sh b/test.sh index 32324a680de..f776963509e 100755 --- a/test.sh +++ b/test.sh @@ -152,9 +152,9 @@ test_project project-v2-multigroup 2 test_project project-v2-addon 2 # test project v3 -test_project project-v3 3-alpha -test_project project-v3-multigroup 3-alpha -test_project project-v3-addon 3-alpha -test_project project-v3-config 3-alpha +test_project project-v3 3 +test_project project-v3-multigroup 3 +test_project project-v3-addon 3 +test_project project-v3-config 3 exit $rc diff --git a/test/e2e/v3/generate_test.go b/test/e2e/v3/generate_test.go index 1e24526de8b..e754973743f 100644 --- a/test/e2e/v3/generate_test.go +++ b/test/e2e/v3/generate_test.go @@ -34,7 +34,7 @@ func GenerateV2(kbc *utils.TestContext) { By("initializing a project") err = kbc.Init( - "--project-version", "3-alpha", + "--project-version", "3", "--plugins", "go/v2", "--domain", kbc.Domain, "--fetch-deps=false", @@ -129,7 +129,7 @@ func GenerateV3(kbc *utils.TestContext, crdAndWebhookVersion string) { By("initializing a project") err = kbc.Init( - "--project-version", "3-alpha", + "--project-version", "3", "--plugins", "go/v3", "--domain", kbc.Domain, "--fetch-deps=false", diff --git a/testdata/project-v3-addon/PROJECT b/testdata/project-v3-addon/PROJECT index f34da953074..f5b6f774bc0 100644 --- a/testdata/project-v3-addon/PROJECT +++ b/testdata/project-v3-addon/PROJECT @@ -5,17 +5,28 @@ repo: sigs.k8s.io/kubebuilder/testdata/project-v3-addon resources: - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: crew kind: Captain + path: sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1 version: v1 - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: crew kind: FirstMate + path: sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1 version: v1 - api: crdVersion: v1 + controller: true + domain: testproject.org group: crew kind: Admiral + path: sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1 version: v1 -version: 3-alpha +version: "3" diff --git a/testdata/project-v3-config/PROJECT b/testdata/project-v3-config/PROJECT index c38c45f92d5..144702d87c3 100644 --- a/testdata/project-v3-config/PROJECT +++ b/testdata/project-v3-config/PROJECT @@ -6,23 +6,38 @@ repo: sigs.k8s.io/kubebuilder/testdata/project-v3-config resources: - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: crew kind: Captain + path: sigs.k8s.io/kubebuilder/testdata/project-v3-config/api/v1 version: v1 webhooks: + defaulting: true + validation: true webhookVersion: v1 - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: crew kind: FirstMate + path: sigs.k8s.io/kubebuilder/testdata/project-v3-config/api/v1 version: v1 webhooks: + conversion: true webhookVersion: v1 - api: crdVersion: v1 + controller: true + domain: testproject.org group: crew kind: Admiral + path: sigs.k8s.io/kubebuilder/testdata/project-v3-config/api/v1 version: v1 webhooks: + defaulting: true webhookVersion: v1 -version: 3-alpha +version: "3" diff --git a/testdata/project-v3-multigroup/PROJECT b/testdata/project-v3-multigroup/PROJECT index 46b4cc83602..30a6a0ddfa7 100644 --- a/testdata/project-v3-multigroup/PROJECT +++ b/testdata/project-v3-multigroup/PROJECT @@ -6,51 +6,88 @@ repo: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup resources: - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: crew kind: Captain + path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/crew/v1 version: v1 webhooks: + defaulting: true + validation: true webhookVersion: v1 - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: ship kind: Frigate + path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/ship/v1beta1 version: v1beta1 webhooks: + conversion: true webhookVersion: v1 - api: crdVersion: v1 + controller: true + domain: testproject.org group: ship kind: Destroyer + path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/ship/v1 version: v1 webhooks: + defaulting: true webhookVersion: v1 - api: crdVersion: v1 + controller: true + domain: testproject.org group: ship kind: Cruiser + path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/ship/v2alpha1 version: v2alpha1 webhooks: + validation: true webhookVersion: v1 - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: sea-creatures kind: Kraken + path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/sea-creatures/v1beta1 version: v1beta1 - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: sea-creatures kind: Leviathan + path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/sea-creatures/v1beta2 version: v1beta2 - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: foo.policy kind: HealthCheckPolicy + path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/foo.policy/v1 version: v1 - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org kind: Lakers + path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/v1 version: v1 webhooks: + defaulting: true + validation: true webhookVersion: v1 -version: 3-alpha +version: "3" diff --git a/testdata/project-v3/PROJECT b/testdata/project-v3/PROJECT index 7e5b7579b29..b30361d9899 100644 --- a/testdata/project-v3/PROJECT +++ b/testdata/project-v3/PROJECT @@ -5,23 +5,39 @@ repo: sigs.k8s.io/kubebuilder/testdata/project-v3 resources: - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: crew kind: Captain + path: sigs.k8s.io/kubebuilder/testdata/project-v3/api/v1 version: v1 webhooks: + defaulting: true + validation: true webhookVersion: v1 - api: crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org group: crew kind: FirstMate + path: sigs.k8s.io/kubebuilder/testdata/project-v3/api/v1 version: v1 webhooks: + conversion: true webhookVersion: v1 - api: crdVersion: v1 + controller: true + domain: testproject.org group: crew kind: Admiral + path: sigs.k8s.io/kubebuilder/testdata/project-v3/api/v1 + plural: admirales version: v1 webhooks: + defaulting: true webhookVersion: v1 -version: 3-alpha +version: "3" From 03431ed6cf01b9759fb94f81c787b3c74309f4bd Mon Sep 17 00:00:00 2001 From: Eric Stroczynski Date: Wed, 27 Jan 2021 09:29:49 -0800 Subject: [PATCH 17/38] (go/v3) change 'runAsUser: 65532', which is redundant, to 'runAsNonRoot: true' Signed-off-by: Eric Stroczynski --- .../v3/scaffolds/internal/templates/config/manager/config.go | 2 +- testdata/project-v3-addon/config/manager/manager.yaml | 2 +- testdata/project-v3-config/config/manager/manager.yaml | 2 +- testdata/project-v3-multigroup/config/manager/manager.yaml | 2 +- testdata/project-v3/config/manager/manager.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go index 477ee76aa7c..96663a7a6c9 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/config/manager/config.go @@ -69,7 +69,7 @@ spec: control-plane: controller-manager spec: securityContext: - runAsUser: 65532 + runAsNonRoot: true containers: - command: - /manager diff --git a/testdata/project-v3-addon/config/manager/manager.yaml b/testdata/project-v3-addon/config/manager/manager.yaml index 70e3f6acf1a..46a82393bfd 100644 --- a/testdata/project-v3-addon/config/manager/manager.yaml +++ b/testdata/project-v3-addon/config/manager/manager.yaml @@ -23,7 +23,7 @@ spec: control-plane: controller-manager spec: securityContext: - runAsUser: 65532 + runAsNonRoot: true containers: - command: - /manager diff --git a/testdata/project-v3-config/config/manager/manager.yaml b/testdata/project-v3-config/config/manager/manager.yaml index def5d64411d..082357842b1 100644 --- a/testdata/project-v3-config/config/manager/manager.yaml +++ b/testdata/project-v3-config/config/manager/manager.yaml @@ -23,7 +23,7 @@ spec: control-plane: controller-manager spec: securityContext: - runAsUser: 65532 + runAsNonRoot: true containers: - command: - /manager diff --git a/testdata/project-v3-multigroup/config/manager/manager.yaml b/testdata/project-v3-multigroup/config/manager/manager.yaml index 70e3f6acf1a..46a82393bfd 100644 --- a/testdata/project-v3-multigroup/config/manager/manager.yaml +++ b/testdata/project-v3-multigroup/config/manager/manager.yaml @@ -23,7 +23,7 @@ spec: control-plane: controller-manager spec: securityContext: - runAsUser: 65532 + runAsNonRoot: true containers: - command: - /manager diff --git a/testdata/project-v3/config/manager/manager.yaml b/testdata/project-v3/config/manager/manager.yaml index 70e3f6acf1a..46a82393bfd 100644 --- a/testdata/project-v3/config/manager/manager.yaml +++ b/testdata/project-v3/config/manager/manager.yaml @@ -23,7 +23,7 @@ spec: control-plane: controller-manager spec: securityContext: - runAsUser: 65532 + runAsNonRoot: true containers: - command: - /manager From 12de4fb8e6826900bdaebaf19df1808d7bb72c33 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Fri, 29 Jan 2021 12:23:53 +0100 Subject: [PATCH 18/38] Remove make calls from post-scaffold hooks Signed-off-by: Adrian Orive --- pkg/plugins/golang/v2/api.go | 6 +++--- pkg/plugins/golang/v2/init.go | 5 ----- pkg/plugins/golang/v3/api.go | 6 +++--- pkg/plugins/golang/v3/init.go | 6 ------ pkg/plugins/golang/v3/webhook.go | 8 -------- 5 files changed, 6 insertions(+), 25 deletions(-) diff --git a/pkg/plugins/golang/v2/api.go b/pkg/plugins/golang/v2/api.go index 6516e75af7a..600f4e92f21 100644 --- a/pkg/plugins/golang/v2/api.go +++ b/pkg/plugins/golang/v2/api.go @@ -91,7 +91,7 @@ After the scaffold is written, api will run make on the project. } func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { - fs.BoolVar(&p.runMake, "make", true, "if true, run make after generating files") + fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files") if os.Getenv("KUBEBUILDER_ENABLE_PLUGINS") != "" { fs.StringVar(&p.pattern, "pattern", "", @@ -198,8 +198,8 @@ func (p *createAPISubcommand) PostScaffold() error { return fmt.Errorf("unknown pattern %q", p.pattern) } - if p.runMake { - return util.RunCmd("Running make", "make") + if p.runMake { // TODO: check if API was scaffolded + return util.RunCmd("Running make", "make", "generate") } return nil } diff --git a/pkg/plugins/golang/v2/init.go b/pkg/plugins/golang/v2/init.go index 34d9d9eb2e7..602e0cf01d6 100644 --- a/pkg/plugins/golang/v2/init.go +++ b/pkg/plugins/golang/v2/init.go @@ -183,11 +183,6 @@ func (p *initSubcommand) PostScaffold() error { return err } - err = util.RunCmd("Running make", "make") - if err != nil { - return err - } - fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName) return nil } diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index 04d90c0209f..2ce2f50f1d9 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -105,7 +105,7 @@ After the scaffold is written, api will run make on the project. } func (p *createAPISubcommand) BindFlags(fs *pflag.FlagSet) { - fs.BoolVar(&p.runMake, "make", true, "if true, run make after generating files") + fs.BoolVar(&p.runMake, "make", true, "if true, run `make generate` after generating files") // TODO: remove this when a better solution for using addons is implemented. if os.Getenv("KUBEBUILDER_ENABLE_PLUGINS") != "" { @@ -232,8 +232,8 @@ func (p *createAPISubcommand) PostScaffold() error { return fmt.Errorf("unknown pattern %q", p.pattern) } - if p.runMake { - return util.RunCmd("Running make", "make") + if p.runMake { // TODO: check if API was scaffolded + return util.RunCmd("Running make", "make", "generate") } return nil } diff --git a/pkg/plugins/golang/v3/init.go b/pkg/plugins/golang/v3/init.go index ebce61a60d4..9a1cea710b5 100644 --- a/pkg/plugins/golang/v3/init.go +++ b/pkg/plugins/golang/v3/init.go @@ -185,12 +185,6 @@ func (p *initSubcommand) PostScaffold() error { return err } - // TODO: make this conditional with a '--make' flag, like in 'create api'. - err = util.RunCmd("Running make", "make") - if err != nil { - return err - } - fmt.Printf("Next: define a resource with:\n$ %s create api\n", p.commandName) return nil } diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go index ac64f39c02e..26d2f7b91ec 100644 --- a/pkg/plugins/golang/v3/webhook.go +++ b/pkg/plugins/golang/v3/webhook.go @@ -29,7 +29,6 @@ import ( goPlugin "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3/scaffolds" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/cmdutil" - "sigs.k8s.io/kubebuilder/v3/pkg/plugins/internal/util" ) // defaultWebhookVersion is the default mutating/validating webhook config API version to scaffold. @@ -44,9 +43,6 @@ type createWebhookSubcommand struct { // 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 webhooks - runMake bool } var ( @@ -87,7 +83,6 @@ func (p *createWebhookSubcommand) BindFlags(fs *pflag.FlagSet) { fs.BoolVar(&p.options.DoConversion, "conversion", false, "if set, scaffold the conversion webhook") - fs.BoolVar(&p.runMake, "make", true, "if true, run make after generating files") fs.BoolVar(&p.force, "force", false, "attempt to create resource even if it already exists") } @@ -139,8 +134,5 @@ func (p *createWebhookSubcommand) GetScaffolder() (cmdutil.Scaffolder, error) { } func (p *createWebhookSubcommand) PostScaffold() error { - if p.runMake { - return util.RunCmd("Running make", "make") - } return nil } From 4fd2cd775356e45ed88e594d80c5e6ef6bf6c11c Mon Sep 17 00:00:00 2001 From: Justin SB Date: Wed, 13 Jan 2021 07:54:06 -0500 Subject: [PATCH 19/38] Tolerate "dot" directories when checking if dir is empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Many users will be running version control; we should ignore .git directories also therefore. We ignore all directories starting with "." (just as we ignore all files starting with "."). Also add go.sum to ignore list, as go.mod is already ignored. Co-authored-by: Adrián --- pkg/plugins/golang/v3/init.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pkg/plugins/golang/v3/init.go b/pkg/plugins/golang/v3/init.go index ebce61a60d4..042b849ca89 100644 --- a/pkg/plugins/golang/v3/init.go +++ b/pkg/plugins/golang/v3/init.go @@ -207,9 +207,16 @@ func checkDir() error { if err != nil { return err } - if info.Name() != "go.mod" && !strings.HasPrefix(info.Name(), ".") { + // Allow the whole .git directory tree + if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." { + return filepath.SkipDir + } + // Also allow go.mod and dot-files + if info.Name() != "go.mod" && info.Name() != "go.sum" && !strings.HasPrefix(info.Name(), ".") { return fmt.Errorf( - "target directory is not empty (only go.mod and files with the prefix \".\" are allowed); found existing file %q", + "target directory is not empty "+ + "(only go.mod, go.sum, and files and directories with the prefix \".\" are allowed); "+ + "found existing file %q", path) } return nil From 386207fc907dfdd37e8db49fca0621cb34ca72f6 Mon Sep 17 00:00:00 2001 From: Camila Macedo Date: Fri, 29 Jan 2021 21:46:34 +0000 Subject: [PATCH 20/38] :seedling: sort plugins key for the help --- pkg/cli/root.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/cli/root.go b/pkg/cli/root.go index 1203700a2aa..0f98ef316a4 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -18,6 +18,7 @@ package cli import ( "fmt" + "sort" "strings" "github.com/spf13/cobra" @@ -113,6 +114,8 @@ func (c cli) getPluginTable() string { maxPluginKeyLength, pluginKeysHeader, maxProjectVersionLength, projectVersionsHeader)) lines = append(lines, strings.Repeat("-", maxPluginKeyLength+2)+"+"+ strings.Repeat("-", maxProjectVersionLength+2)) + + sort.Strings(pluginKeys) for i, pluginKey := range pluginKeys { supportedProjectVersions := projectVersions[i] lines = append(lines, fmt.Sprintf(" %[1]*[2]s | %[3]*[4]s", From fa4084636326754c3f7671fadd60d336b3f8b732 Mon Sep 17 00:00:00 2001 From: Adrian Orive Oneca Date: Mon, 1 Feb 2021 22:02:31 +0100 Subject: [PATCH 21/38] Return a typed error in case DecodePluginConfig was unable to find the provided key Signed-off-by: Adrian Orive Oneca --- pkg/config/errors.go | 22 ++++++++++++++++------ pkg/config/errors_test.go | 20 ++++++++++++++++---- pkg/config/v2/config.go | 14 +++++++------- pkg/config/v3/config.go | 7 ++++--- pkg/config/v3/config_test.go | 10 +++++++++- 5 files changed, 52 insertions(+), 21 deletions(-) diff --git a/pkg/config/errors.go b/pkg/config/errors.go index fed0b421882..d2884147478 100644 --- a/pkg/config/errors.go +++ b/pkg/config/errors.go @@ -32,28 +32,38 @@ func (e UnsupportedVersionError) Error() string { return fmt.Sprintf("version %s is not supported", e.Version) } -// UnsupportedField is returned when a project configuration version does not support +// UnsupportedFieldError is returned when a project configuration version does not support // one of the fields as interface must be common for all the versions -type UnsupportedField struct { +type UnsupportedFieldError struct { Version Version Field string } // Error implements error interface -func (e UnsupportedField) Error() string { +func (e UnsupportedFieldError) Error() string { return fmt.Sprintf("version %s does not support the %s field", e.Version, e.Field) } -// UnknownResource is returned by Config.GetResource when the provided GVK cannot be found -type UnknownResource struct { +// ResourceNotFoundError is returned by Config.GetResource when the provided GVK cannot be found +type ResourceNotFoundError struct { GVK resource.GVK } // Error implements error interface -func (e UnknownResource) Error() string { +func (e ResourceNotFoundError) Error() string { return fmt.Sprintf("resource %v could not be found", e.GVK) } +// PluginKeyNotFoundError is returned by Config.DecodePluginConfig when the provided key cannot be found +type PluginKeyNotFoundError struct { + Key string +} + +// Error implements error interface +func (e PluginKeyNotFoundError) Error() string { + return fmt.Sprintf("plugin key %q could not be found", e.Key) +} + // MarshalError is returned by Config.Marshal when something went wrong while marshalling to YAML type MarshalError struct { Err error diff --git a/pkg/config/errors_test.go b/pkg/config/errors_test.go index b998ea949e4..2c4fcbd727c 100644 --- a/pkg/config/errors_test.go +++ b/pkg/config/errors_test.go @@ -37,8 +37,8 @@ var _ = Describe("UnsupportedVersionError", func() { }) }) -var _ = Describe("UnsupportedField", func() { - var err = UnsupportedField{ +var _ = Describe("UnsupportedFieldError", func() { + var err = UnsupportedFieldError{ Version: Version{Number: 1}, Field: "name", } @@ -50,8 +50,8 @@ var _ = Describe("UnsupportedField", func() { }) }) -var _ = Describe("UnknownResource", func() { - var err = UnknownResource{ +var _ = Describe("ResourceNotFoundError", func() { + var err = ResourceNotFoundError{ GVK: resource.GVK{ Group: "group", Domain: "my.domain", @@ -67,6 +67,18 @@ var _ = Describe("UnknownResource", func() { }) }) +var _ = Describe("PluginKeyNotFoundError", func() { + var err = PluginKeyNotFoundError{ + Key: "go.kubebuilder.io/v1", + } + + Context("Error", func() { + It("should return the correct error message", func() { + Expect(err.Error()).To(Equal("plugin key \"go.kubebuilder.io/v1\" could not be found")) + }) + }) +}) + var _ = Describe("MarshalError", func() { var ( wrapped = fmt.Errorf("error message") diff --git a/pkg/config/v2/config.go b/pkg/config/v2/config.go index 9b3812fe8cb..b9ee4b57f5d 100644 --- a/pkg/config/v2/config.go +++ b/pkg/config/v2/config.go @@ -88,7 +88,7 @@ func (c cfg) GetProjectName() string { // SetProjectName implements config.Config func (c *cfg) SetProjectName(string) error { - return config.UnsupportedField{ + return config.UnsupportedFieldError{ Version: Version, Field: "project name", } @@ -101,7 +101,7 @@ func (c cfg) GetLayout() string { // SetLayout implements config.Config func (c *cfg) SetLayout(string) error { - return config.UnsupportedField{ + return config.UnsupportedFieldError{ Version: Version, Field: "layout", } @@ -131,7 +131,7 @@ func (c cfg) IsComponentConfig() bool { // SetComponentConfig implements config.Config func (c *cfg) SetComponentConfig() error { - return config.UnsupportedField{ + return config.UnsupportedFieldError{ Version: Version, Field: "component config", } @@ -139,7 +139,7 @@ func (c *cfg) SetComponentConfig() error { // ClearComponentConfig implements config.Config func (c *cfg) ClearComponentConfig() error { - return config.UnsupportedField{ + return config.UnsupportedFieldError{ Version: Version, Field: "component config", } @@ -175,7 +175,7 @@ func (c cfg) GetResource(gvk resource.GVK) (resource.Resource, error) { } } - return resource.Resource{}, config.UnknownResource{GVK: gvk} + return resource.Resource{}, config.ResourceNotFoundError{GVK: gvk} } // GetResources implements config.Config @@ -234,7 +234,7 @@ func (c cfg) IsWebhookVersionCompatible(webhookVersion string) bool { // DecodePluginConfig implements config.Config func (c cfg) DecodePluginConfig(string, interface{}) error { - return config.UnsupportedField{ + return config.UnsupportedFieldError{ Version: Version, Field: "plugins", } @@ -242,7 +242,7 @@ func (c cfg) DecodePluginConfig(string, interface{}) error { // EncodePluginConfig implements config.Config func (c cfg) EncodePluginConfig(string, interface{}) error { - return config.UnsupportedField{ + return config.UnsupportedFieldError{ Version: Version, Field: "plugins", } diff --git a/pkg/config/v3/config.go b/pkg/config/v3/config.go index 78e48e354ec..144e9580d96 100644 --- a/pkg/config/v3/config.go +++ b/pkg/config/v3/config.go @@ -180,7 +180,7 @@ func (c cfg) GetResource(gvk resource.GVK) (resource.Resource, error) { } } - return resource.Resource{}, config.UnknownResource{GVK: gvk} + return resource.Resource{}, config.ResourceNotFoundError{GVK: gvk} } // GetResources implements config.Config @@ -283,7 +283,7 @@ func (c cfg) resourceAPIVersionCompatible(verType, version string) bool { // DecodePluginConfig implements config.Config func (c cfg) DecodePluginConfig(key string, configObj interface{}) error { if len(c.Plugins) == 0 { - return nil + return config.PluginKeyNotFoundError{Key: key} } // Get the object blob by key and unmarshal into the object. @@ -295,9 +295,10 @@ func (c cfg) DecodePluginConfig(key string, configObj interface{}) error { if err := yaml.Unmarshal(b, configObj); err != nil { return fmt.Errorf("failed to unmarshal extra fields object: %w", err) } + return nil } - return nil + return config.PluginKeyNotFoundError{Key: key} } // EncodePluginConfig will return an error if used on any project version < v3. diff --git a/pkg/config/v3/config_test.go b/pkg/config/v3/config_test.go index 853a41b56ad..37ed9d864af 100644 --- a/pkg/config/v3/config_test.go +++ b/pkg/config/v3/config_test.go @@ -17,12 +17,14 @@ limitations under the License. package v3 import ( + "errors" "testing" . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/extensions/table" . "github.com/onsi/gomega" + "sigs.k8s.io/kubebuilder/v3/pkg/config" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) @@ -390,13 +392,19 @@ var _ = Describe("cfg", func() { } ) + It("DecodePluginConfig should fail for no plugin config object", func() { + var pluginConfig PluginConfig + err := c0.DecodePluginConfig(key, &pluginConfig) + Expect(err).To(HaveOccurred()) + Expect(errors.As(err, &config.PluginKeyNotFoundError{})).To(BeTrue()) + }) + DescribeTable("DecodePluginConfig should retrieve the plugin data correctly", func(inputConfig cfg, expectedPluginConfig PluginConfig) { var pluginConfig PluginConfig Expect(inputConfig.DecodePluginConfig(key, &pluginConfig)).To(Succeed()) Expect(pluginConfig).To(Equal(expectedPluginConfig)) }, - Entry("for no plugin config object", c0, nil), Entry("for an empty plugin config object", c1, PluginConfig{}), Entry("for a full plugin config object", c2, pluginConfig), // TODO (coverage): add cases where yaml.Marshal returns an error From 95466c552e3e4f12540b022537bdc2186c18a7c1 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Wed, 3 Feb 2021 11:31:19 +0100 Subject: [PATCH 22/38] Add documentation to v2 controllers Signed-off-by: Adrian Orive --- pkg/plugins/golang/v2/scaffolds/api.go | 2 +- .../internal/templates/controllers/controller.go | 12 ++++++++++++ plugins/addon/controller.go | 1 + .../controllers/admiral_controller.go | 1 + .../controllers/captain_controller.go | 1 + .../controllers/firstmate_controller.go | 1 + .../controllers/apps/pod_controller.go | 10 ++++++++++ .../controllers/crew/captain_controller.go | 10 ++++++++++ .../foo.policy/healthcheckpolicy_controller.go | 10 ++++++++++ .../controllers/sea-creatures/kraken_controller.go | 10 ++++++++++ .../sea-creatures/leviathan_controller.go | 10 ++++++++++ .../controllers/ship/cruiser_controller.go | 10 ++++++++++ .../controllers/ship/destroyer_controller.go | 10 ++++++++++ .../controllers/ship/frigate_controller.go | 10 ++++++++++ .../project-v2/controllers/admiral_controller.go | 10 ++++++++++ .../project-v2/controllers/captain_controller.go | 10 ++++++++++ .../project-v2/controllers/firstmate_controller.go | 10 ++++++++++ testdata/project-v2/controllers/laker_controller.go | 10 ++++++++++ .../controllers/admiral_controller.go | 1 + .../controllers/captain_controller.go | 1 + .../controllers/firstmate_controller.go | 1 + 21 files changed, 140 insertions(+), 1 deletion(-) diff --git a/pkg/plugins/golang/v2/scaffolds/api.go b/pkg/plugins/golang/v2/scaffolds/api.go index 37a3c0772ee..e4abc4d9ae6 100644 --- a/pkg/plugins/golang/v2/scaffolds/api.go +++ b/pkg/plugins/golang/v2/scaffolds/api.go @@ -122,7 +122,7 @@ func (s *apiScaffolder) scaffold() error { if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), &controllers.SuiteTest{Force: s.force}, - &controllers.Controller{Force: s.force}, + &controllers.Controller{ControllerRuntimeVersion: ControllerRuntimeVersion, Force: s.force}, ); err != nil { return fmt.Errorf("error scaffolding controller: %v", err) } diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go index 213b9b08561..f145d157d6b 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go @@ -33,6 +33,8 @@ type Controller struct { file.BoilerplateMixin file.ResourceMixin + ControllerRuntimeVersion string + Force bool } @@ -85,6 +87,15 @@ type {{ .Resource.Kind }}Reconciler struct { //+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 +// 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(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("{{ .Resource.Kind | lower }}", req.NamespacedName) @@ -94,6 +105,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(req ctrl.Request) (ctrl.Resul 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 .Resource.HasAPI -}} diff --git a/plugins/addon/controller.go b/plugins/addon/controller.go index 032c77aef9c..6a384d1e8f2 100644 --- a/plugins/addon/controller.go +++ b/plugins/addon/controller.go @@ -65,6 +65,7 @@ type {{ .Resource.Kind }}Reconciler struct { //+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 +// SetupWithManager sets up the controller with the Manager. func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { addon.Init() diff --git a/testdata/project-v2-addon/controllers/admiral_controller.go b/testdata/project-v2-addon/controllers/admiral_controller.go index 597a95cc68d..153f5df0ab2 100644 --- a/testdata/project-v2-addon/controllers/admiral_controller.go +++ b/testdata/project-v2-addon/controllers/admiral_controller.go @@ -47,6 +47,7 @@ type AdmiralReconciler struct { //+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 +// SetupWithManager sets up the controller with the Manager. func (r *AdmiralReconciler) SetupWithManager(mgr ctrl.Manager) error { addon.Init() diff --git a/testdata/project-v2-addon/controllers/captain_controller.go b/testdata/project-v2-addon/controllers/captain_controller.go index c25547bb27a..d896e491e3b 100644 --- a/testdata/project-v2-addon/controllers/captain_controller.go +++ b/testdata/project-v2-addon/controllers/captain_controller.go @@ -47,6 +47,7 @@ type CaptainReconciler struct { //+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 +// SetupWithManager sets up the controller with the Manager. func (r *CaptainReconciler) SetupWithManager(mgr ctrl.Manager) error { addon.Init() diff --git a/testdata/project-v2-addon/controllers/firstmate_controller.go b/testdata/project-v2-addon/controllers/firstmate_controller.go index 1a987a6bed6..79fdd2b8dfb 100644 --- a/testdata/project-v2-addon/controllers/firstmate_controller.go +++ b/testdata/project-v2-addon/controllers/firstmate_controller.go @@ -47,6 +47,7 @@ type FirstMateReconciler struct { //+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 +// SetupWithManager sets up the controller with the Manager. func (r *FirstMateReconciler) SetupWithManager(mgr ctrl.Manager) error { addon.Init() diff --git a/testdata/project-v2-multigroup/controllers/apps/pod_controller.go b/testdata/project-v2-multigroup/controllers/apps/pod_controller.go index 25577f0aedc..7cd866d048e 100644 --- a/testdata/project-v2-multigroup/controllers/apps/pod_controller.go +++ b/testdata/project-v2-multigroup/controllers/apps/pod_controller.go @@ -35,6 +35,15 @@ type PodReconciler struct { //+kubebuilder:rbac:groups=apps,resources=pods,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=apps,resources=pods/status,verbs=get;update;patch +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Pod object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile func (r *PodReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("pod", req.NamespacedName) @@ -44,6 +53,7 @@ func (r *PodReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, nil } +// SetupWithManager sets up the controller with the Manager. func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument diff --git a/testdata/project-v2-multigroup/controllers/crew/captain_controller.go b/testdata/project-v2-multigroup/controllers/crew/captain_controller.go index 5553643daa7..50e3a33271e 100644 --- a/testdata/project-v2-multigroup/controllers/crew/captain_controller.go +++ b/testdata/project-v2-multigroup/controllers/crew/captain_controller.go @@ -37,6 +37,15 @@ type CaptainReconciler struct { //+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 +// 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.6.4/pkg/reconcile func (r *CaptainReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("captain", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *CaptainReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 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{}). diff --git a/testdata/project-v2-multigroup/controllers/foo.policy/healthcheckpolicy_controller.go b/testdata/project-v2-multigroup/controllers/foo.policy/healthcheckpolicy_controller.go index d36e48ebcb7..43fe15ce91b 100644 --- a/testdata/project-v2-multigroup/controllers/foo.policy/healthcheckpolicy_controller.go +++ b/testdata/project-v2-multigroup/controllers/foo.policy/healthcheckpolicy_controller.go @@ -37,6 +37,15 @@ type HealthCheckPolicyReconciler struct { //+kubebuilder:rbac:groups=foo.policy.testproject.org,resources=healthcheckpolicies,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=foo.policy.testproject.org,resources=healthcheckpolicies/status,verbs=get;update;patch +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the HealthCheckPolicy object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile func (r *HealthCheckPolicyReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("healthcheckpolicy", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *HealthCheckPolicyReconciler) Reconcile(req ctrl.Request) (ctrl.Result, return ctrl.Result{}, nil } +// SetupWithManager sets up the controller with the Manager. func (r *HealthCheckPolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&foopolicyv1.HealthCheckPolicy{}). diff --git a/testdata/project-v2-multigroup/controllers/sea-creatures/kraken_controller.go b/testdata/project-v2-multigroup/controllers/sea-creatures/kraken_controller.go index 5e61308ffeb..9c0deddaf53 100644 --- a/testdata/project-v2-multigroup/controllers/sea-creatures/kraken_controller.go +++ b/testdata/project-v2-multigroup/controllers/sea-creatures/kraken_controller.go @@ -37,6 +37,15 @@ type KrakenReconciler struct { //+kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=krakens,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=krakens/status,verbs=get;update;patch +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Kraken object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile func (r *KrakenReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("kraken", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *KrakenReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, nil } +// SetupWithManager sets up the controller with the Manager. func (r *KrakenReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&seacreaturesv1beta1.Kraken{}). diff --git a/testdata/project-v2-multigroup/controllers/sea-creatures/leviathan_controller.go b/testdata/project-v2-multigroup/controllers/sea-creatures/leviathan_controller.go index 06f0d6d16a8..711dbaf85d1 100644 --- a/testdata/project-v2-multigroup/controllers/sea-creatures/leviathan_controller.go +++ b/testdata/project-v2-multigroup/controllers/sea-creatures/leviathan_controller.go @@ -37,6 +37,15 @@ type LeviathanReconciler struct { //+kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=leviathans,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=sea-creatures.testproject.org,resources=leviathans/status,verbs=get;update;patch +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Leviathan object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile func (r *LeviathanReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("leviathan", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *LeviathanReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, nil } +// SetupWithManager sets up the controller with the Manager. func (r *LeviathanReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&seacreaturesv1beta2.Leviathan{}). diff --git a/testdata/project-v2-multigroup/controllers/ship/cruiser_controller.go b/testdata/project-v2-multigroup/controllers/ship/cruiser_controller.go index e3fe92c9f41..14a6d86ac03 100644 --- a/testdata/project-v2-multigroup/controllers/ship/cruiser_controller.go +++ b/testdata/project-v2-multigroup/controllers/ship/cruiser_controller.go @@ -37,6 +37,15 @@ type CruiserReconciler struct { //+kubebuilder:rbac:groups=ship.testproject.org,resources=cruisers,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=ship.testproject.org,resources=cruisers/status,verbs=get;update;patch +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Cruiser object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile func (r *CruiserReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("cruiser", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *CruiserReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, nil } +// SetupWithManager sets up the controller with the Manager. func (r *CruiserReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&shipv2alpha1.Cruiser{}). diff --git a/testdata/project-v2-multigroup/controllers/ship/destroyer_controller.go b/testdata/project-v2-multigroup/controllers/ship/destroyer_controller.go index be1273246d3..aa62ec4ca5f 100644 --- a/testdata/project-v2-multigroup/controllers/ship/destroyer_controller.go +++ b/testdata/project-v2-multigroup/controllers/ship/destroyer_controller.go @@ -37,6 +37,15 @@ type DestroyerReconciler struct { //+kubebuilder:rbac:groups=ship.testproject.org,resources=destroyers,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=ship.testproject.org,resources=destroyers/status,verbs=get;update;patch +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Destroyer object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile func (r *DestroyerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("destroyer", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *DestroyerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, nil } +// SetupWithManager sets up the controller with the Manager. func (r *DestroyerReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&shipv1.Destroyer{}). diff --git a/testdata/project-v2-multigroup/controllers/ship/frigate_controller.go b/testdata/project-v2-multigroup/controllers/ship/frigate_controller.go index 5682769e7e1..34ccad4d650 100644 --- a/testdata/project-v2-multigroup/controllers/ship/frigate_controller.go +++ b/testdata/project-v2-multigroup/controllers/ship/frigate_controller.go @@ -37,6 +37,15 @@ type FrigateReconciler struct { //+kubebuilder:rbac:groups=ship.testproject.org,resources=frigates,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=ship.testproject.org,resources=frigates/status,verbs=get;update;patch +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Frigate object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile func (r *FrigateReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("frigate", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *FrigateReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, nil } +// SetupWithManager sets up the controller with the Manager. func (r *FrigateReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&shipv1beta1.Frigate{}). diff --git a/testdata/project-v2/controllers/admiral_controller.go b/testdata/project-v2/controllers/admiral_controller.go index 6ad74ef92c9..23daeac8e45 100644 --- a/testdata/project-v2/controllers/admiral_controller.go +++ b/testdata/project-v2/controllers/admiral_controller.go @@ -37,6 +37,15 @@ type AdmiralReconciler struct { //+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 +// 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.6.4/pkg/reconcile func (r *AdmiralReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("admiral", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *AdmiralReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 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{}). diff --git a/testdata/project-v2/controllers/captain_controller.go b/testdata/project-v2/controllers/captain_controller.go index 36dfbdc35a0..6ecab438f25 100644 --- a/testdata/project-v2/controllers/captain_controller.go +++ b/testdata/project-v2/controllers/captain_controller.go @@ -37,6 +37,15 @@ type CaptainReconciler struct { //+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 +// 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.6.4/pkg/reconcile func (r *CaptainReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("captain", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *CaptainReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 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{}). diff --git a/testdata/project-v2/controllers/firstmate_controller.go b/testdata/project-v2/controllers/firstmate_controller.go index 49716f87499..00a899419b8 100644 --- a/testdata/project-v2/controllers/firstmate_controller.go +++ b/testdata/project-v2/controllers/firstmate_controller.go @@ -37,6 +37,15 @@ type FirstMateReconciler struct { //+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 +// 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.6.4/pkg/reconcile func (r *FirstMateReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("firstmate", req.NamespacedName) @@ -46,6 +55,7 @@ func (r *FirstMateReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 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{}). diff --git a/testdata/project-v2/controllers/laker_controller.go b/testdata/project-v2/controllers/laker_controller.go index f8a74685c1d..6366e382392 100644 --- a/testdata/project-v2/controllers/laker_controller.go +++ b/testdata/project-v2/controllers/laker_controller.go @@ -35,6 +35,15 @@ type LakerReconciler struct { //+kubebuilder:rbac:groups=crew.testproject.org,resources=lakers,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=crew.testproject.org,resources=lakers/status,verbs=get;update;patch +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Laker object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile func (r *LakerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() _ = r.Log.WithValues("laker", req.NamespacedName) @@ -44,6 +53,7 @@ func (r *LakerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, nil } +// SetupWithManager sets up the controller with the Manager. func (r *LakerReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument diff --git a/testdata/project-v3-addon/controllers/admiral_controller.go b/testdata/project-v3-addon/controllers/admiral_controller.go index bf405496d14..f0a95de9bc0 100644 --- a/testdata/project-v3-addon/controllers/admiral_controller.go +++ b/testdata/project-v3-addon/controllers/admiral_controller.go @@ -47,6 +47,7 @@ type AdmiralReconciler struct { //+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 +// SetupWithManager sets up the controller with the Manager. func (r *AdmiralReconciler) SetupWithManager(mgr ctrl.Manager) error { addon.Init() diff --git a/testdata/project-v3-addon/controllers/captain_controller.go b/testdata/project-v3-addon/controllers/captain_controller.go index c576371d3a9..4a45c97e025 100644 --- a/testdata/project-v3-addon/controllers/captain_controller.go +++ b/testdata/project-v3-addon/controllers/captain_controller.go @@ -47,6 +47,7 @@ type CaptainReconciler struct { //+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 +// SetupWithManager sets up the controller with the Manager. func (r *CaptainReconciler) SetupWithManager(mgr ctrl.Manager) error { addon.Init() diff --git a/testdata/project-v3-addon/controllers/firstmate_controller.go b/testdata/project-v3-addon/controllers/firstmate_controller.go index 27cf85321be..fd81c4a4a66 100644 --- a/testdata/project-v3-addon/controllers/firstmate_controller.go +++ b/testdata/project-v3-addon/controllers/firstmate_controller.go @@ -47,6 +47,7 @@ type FirstMateReconciler struct { //+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 +// SetupWithManager sets up the controller with the Manager. func (r *FirstMateReconciler) SetupWithManager(mgr ctrl.Manager) error { addon.Init() From fed61748174e97e8511c689d0f9b8e366881d675 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Wed, 3 Feb 2021 11:44:59 +0100 Subject: [PATCH 23/38] Go fmt templates Signed-off-by: Adrian Orive --- .../v2/scaffolds/internal/templates/api/types.go | 4 +++- .../internal/templates/controllers/controller.go | 2 +- .../internal/templates/controllers/controller.go | 8 ++------ plugins/addon/controller.go | 11 +++++------ plugins/addon/type.go | 4 +++- .../controllers/admiral_controller.go | 9 ++++----- .../controllers/captain_controller.go | 9 ++++----- .../controllers/firstmate_controller.go | 9 ++++----- .../controllers/admiral_controller.go | 9 ++++----- .../controllers/captain_controller.go | 9 ++++----- .../controllers/firstmate_controller.go | 9 ++++----- 11 files changed, 38 insertions(+), 45 deletions(-) diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go index 462830c5c6f..cd0907121cf 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/api/types.go @@ -87,7 +87,9 @@ type {{ .Resource.Kind }}Status struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -{{ if not .Resource.API.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} +{{- if not .Resource.API.Namespaced }} +//+kubebuilder:resource:scope=Cluster +{{- end }} // {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API type {{ .Resource.Kind }} struct { diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go index 213b9b08561..edd62978343 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go @@ -78,7 +78,7 @@ import ( // {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object type {{ .Resource.Kind }}Reconciler struct { client.Client - Log logr.Logger + Log logr.Logger Scheme *runtime.Scheme } diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go index fe2aae589b8..a70eed4714d 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go @@ -64,11 +64,7 @@ func (f *Controller) SetTemplateDefaults() error { //nolint:lll const controllerTemplate = `{{ .Boilerplate }} -{{if and .MultiGroup .Resource.Group }} -package {{ .Resource.PackageName }} -{{else}} -package controllers -{{end}} +package {{ if and .MultiGroup .Resource.Group }}{{ .Resource.PackageName }}{{ else }}controllers{{ end }} import ( "context" @@ -84,7 +80,7 @@ import ( // {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object type {{ .Resource.Kind }}Reconciler struct { client.Client - Log logr.Logger + Log logr.Logger Scheme *runtime.Scheme } diff --git a/plugins/addon/controller.go b/plugins/addon/controller.go index 032c77aef9c..6faf15ec671 100644 --- a/plugins/addon/controller.go +++ b/plugins/addon/controller.go @@ -35,6 +35,10 @@ const controllerTemplate = `{{ .Boilerplate }} package controllers import ( + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -43,11 +47,6 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - api "{{ .Resource.Path }}" ) @@ -56,7 +55,7 @@ var _ reconcile.Reconciler = &{{ .Resource.Kind }}Reconciler{} // {{ .Resource.Kind }}Reconciler reconciles a {{ .Resource.Kind }} object type {{ .Resource.Kind }}Reconciler struct { client.Client - Log logr.Logger + Log logr.Logger Scheme *runtime.Scheme declarative.Reconciler diff --git a/plugins/addon/type.go b/plugins/addon/type.go index 82f24680e21..b7219be36dc 100644 --- a/plugins/addon/type.go +++ b/plugins/addon/type.go @@ -76,7 +76,9 @@ type {{ .Resource.Kind }}Status struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status -{{ if not .Resource.API.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} +{{- if not .Resource.API.Namespaced }} +//+kubebuilder:resource:scope=Cluster +{{- end }} // {{.Resource.Kind}} is the Schema for the {{ .Resource.Plural }} API type {{ .Resource.Kind }} struct { diff --git a/testdata/project-v2-addon/controllers/admiral_controller.go b/testdata/project-v2-addon/controllers/admiral_controller.go index 597a95cc68d..69115bd269d 100644 --- a/testdata/project-v2-addon/controllers/admiral_controller.go +++ b/testdata/project-v2-addon/controllers/admiral_controller.go @@ -17,6 +17,10 @@ limitations under the License. package controllers import ( + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -25,11 +29,6 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - api "sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1" ) diff --git a/testdata/project-v2-addon/controllers/captain_controller.go b/testdata/project-v2-addon/controllers/captain_controller.go index c25547bb27a..46ee02564f6 100644 --- a/testdata/project-v2-addon/controllers/captain_controller.go +++ b/testdata/project-v2-addon/controllers/captain_controller.go @@ -17,6 +17,10 @@ limitations under the License. package controllers import ( + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -25,11 +29,6 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - api "sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1" ) diff --git a/testdata/project-v2-addon/controllers/firstmate_controller.go b/testdata/project-v2-addon/controllers/firstmate_controller.go index 1a987a6bed6..ba5996a2223 100644 --- a/testdata/project-v2-addon/controllers/firstmate_controller.go +++ b/testdata/project-v2-addon/controllers/firstmate_controller.go @@ -17,6 +17,10 @@ limitations under the License. package controllers import ( + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -25,11 +29,6 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - api "sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1" ) diff --git a/testdata/project-v3-addon/controllers/admiral_controller.go b/testdata/project-v3-addon/controllers/admiral_controller.go index bf405496d14..17f413a6013 100644 --- a/testdata/project-v3-addon/controllers/admiral_controller.go +++ b/testdata/project-v3-addon/controllers/admiral_controller.go @@ -17,6 +17,10 @@ limitations under the License. package controllers import ( + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -25,11 +29,6 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - api "sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1" ) diff --git a/testdata/project-v3-addon/controllers/captain_controller.go b/testdata/project-v3-addon/controllers/captain_controller.go index c576371d3a9..7d07e4c3b73 100644 --- a/testdata/project-v3-addon/controllers/captain_controller.go +++ b/testdata/project-v3-addon/controllers/captain_controller.go @@ -17,6 +17,10 @@ limitations under the License. package controllers import ( + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -25,11 +29,6 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - api "sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1" ) diff --git a/testdata/project-v3-addon/controllers/firstmate_controller.go b/testdata/project-v3-addon/controllers/firstmate_controller.go index 27cf85321be..133408572ff 100644 --- a/testdata/project-v3-addon/controllers/firstmate_controller.go +++ b/testdata/project-v3-addon/controllers/firstmate_controller.go @@ -17,6 +17,10 @@ limitations under the License. package controllers import ( + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -25,11 +29,6 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - api "sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1" ) From 6e1be3da37e34313fb6994d263d4d6c4e23985b6 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Wed, 3 Feb 2021 11:53:30 +0100 Subject: [PATCH 24/38] Bump addon *_types.go and controller.go templates with changes that were done to the equivalent templates of the go plugin Signed-off-by: Adrian Orive --- plugins/addon/controller.go | 10 +++++----- plugins/addon/type.go | 9 ++++----- testdata/project-v2-addon/api/v1/admiral_types.go | 1 - .../project-v2-addon/controllers/admiral_controller.go | 6 +++--- .../project-v2-addon/controllers/captain_controller.go | 6 +++--- .../controllers/firstmate_controller.go | 6 +++--- testdata/project-v3-addon/api/v1/admiral_types.go | 1 - .../project-v3-addon/controllers/admiral_controller.go | 6 +++--- .../project-v3-addon/controllers/captain_controller.go | 6 +++--- .../controllers/firstmate_controller.go | 6 +++--- 10 files changed, 27 insertions(+), 30 deletions(-) diff --git a/plugins/addon/controller.go b/plugins/addon/controller.go index 8b9ab9fb4f8..d84615feafb 100644 --- a/plugins/addon/controller.go +++ b/plugins/addon/controller.go @@ -47,7 +47,7 @@ import ( "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon/pkg/status" "sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative" - api "{{ .Resource.Path }}" + {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" ) var _ reconcile.Reconciler = &{{ .Resource.Kind }}Reconciler{} @@ -69,12 +69,12 @@ func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) erro addon.Init() labels := map[string]string{ - "k8s-app": "{{ .Resource.Kind | lower }}", + "k8s-app": "{{ lower .Resource.Kind }}", } watchLabels := declarative.SourceLabel(mgr.GetScheme()) - if err := r.Reconciler.Init(mgr, &api.{{ .Resource.Kind }}{}, + if err := r.Reconciler.Init(mgr, &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}, declarative.WithObjectTransform(declarative.AddLabels(labels)), declarative.WithOwner(declarative.SourceAsOwner), declarative.WithLabels(watchLabels), @@ -86,13 +86,13 @@ func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) erro return err } - c, err := controller.New("{{ .Resource.Kind | lower }}-controller", mgr, controller.Options{Reconciler: r}) + c, err := controller.New("{{ lower .Resource.Kind }}-controller", mgr, controller.Options{Reconciler: r}) if err != nil { return err } // Watch for changes to {{ .Resource.Kind }} - err = c.Watch(&source.Kind{Type: &api.{{ .Resource.Kind }}{}}, &handler.EnqueueRequestForObject{}) + err = c.Watch(&source.Kind{Type: &{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}}, &handler.EnqueueRequestForObject{}) if err != nil { return err } diff --git a/plugins/addon/type.go b/plugins/addon/type.go index b7219be36dc..ebcbeab56b7 100644 --- a/plugins/addon/type.go +++ b/plugins/addon/type.go @@ -57,8 +57,8 @@ import ( // 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 { +// {{ .Resource.Kind }}Spec defines the desired state of {{ .Resource.Kind }} +type {{ .Resource.Kind }}Spec struct { addonv1alpha1.CommonSpec {{ JSONTag ",inline" }} addonv1alpha1.PatchSpec {{ JSONTag ",inline" }} @@ -80,7 +80,7 @@ type {{ .Resource.Kind }}Status struct { //+kubebuilder:resource:scope=Cluster {{- end }} -// {{.Resource.Kind}} is the Schema for the {{ .Resource.Plural }} API +// {{ .Resource.Kind }} is the Schema for the {{ .Resource.Plural }} API type {{ .Resource.Kind }} struct { metav1.TypeMeta ` + "`" + `json:",inline"` + "`" + ` metav1.ObjectMeta ` + "`" + `json:"metadata,omitempty"` + "`" + ` @@ -92,7 +92,7 @@ type {{ .Resource.Kind }} struct { var _ addonv1alpha1.CommonObject = &{{ .Resource.Kind }}{} func (o *{{ .Resource.Kind }}) ComponentName() string { - return "{{ .Resource.Kind | lower }}" + return "{{ lower .Resource.Kind }}" } func (o *{{ .Resource.Kind }}) CommonSpec() addonv1alpha1.CommonSpec { @@ -112,7 +112,6 @@ func (o *{{ .Resource.Kind }}) SetCommonStatus(s addonv1alpha1.CommonStatus) { } //+kubebuilder:object:root=true -{{ if not .Resource.API.Namespaced }} //+kubebuilder:resource:scope=Cluster {{ end }} // {{ .Resource.Kind }}List contains a list of {{ .Resource.Kind }} type {{ .Resource.Kind }}List struct { diff --git a/testdata/project-v2-addon/api/v1/admiral_types.go b/testdata/project-v2-addon/api/v1/admiral_types.go index 843fbe36383..00f0c908dde 100644 --- a/testdata/project-v2-addon/api/v1/admiral_types.go +++ b/testdata/project-v2-addon/api/v1/admiral_types.go @@ -77,7 +77,6 @@ func (o *Admiral) SetCommonStatus(s addonv1alpha1.CommonStatus) { } //+kubebuilder:object:root=true -//+kubebuilder:resource:scope=Cluster // AdmiralList contains a list of Admiral type AdmiralList struct { diff --git a/testdata/project-v2-addon/controllers/admiral_controller.go b/testdata/project-v2-addon/controllers/admiral_controller.go index 0e3572ad868..47b7abf2cec 100644 --- a/testdata/project-v2-addon/controllers/admiral_controller.go +++ b/testdata/project-v2-addon/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" - api "sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1" ) var _ reconcile.Reconciler = &AdmiralReconciler{} @@ -56,7 +56,7 @@ func (r *AdmiralReconciler) SetupWithManager(mgr ctrl.Manager) error { watchLabels := declarative.SourceLabel(mgr.GetScheme()) - if err := r.Reconciler.Init(mgr, &api.Admiral{}, + if err := r.Reconciler.Init(mgr, &crewv1.Admiral{}, declarative.WithObjectTransform(declarative.AddLabels(labels)), declarative.WithOwner(declarative.SourceAsOwner), declarative.WithLabels(watchLabels), @@ -74,7 +74,7 @@ func (r *AdmiralReconciler) SetupWithManager(mgr ctrl.Manager) error { } // Watch for changes to Admiral - err = c.Watch(&source.Kind{Type: &api.Admiral{}}, &handler.EnqueueRequestForObject{}) + err = c.Watch(&source.Kind{Type: &crewv1.Admiral{}}, &handler.EnqueueRequestForObject{}) if err != nil { return err } diff --git a/testdata/project-v2-addon/controllers/captain_controller.go b/testdata/project-v2-addon/controllers/captain_controller.go index d1392e97324..30d7f55c72d 100644 --- a/testdata/project-v2-addon/controllers/captain_controller.go +++ b/testdata/project-v2-addon/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" - api "sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1" ) var _ reconcile.Reconciler = &CaptainReconciler{} @@ -56,7 +56,7 @@ func (r *CaptainReconciler) SetupWithManager(mgr ctrl.Manager) error { watchLabels := declarative.SourceLabel(mgr.GetScheme()) - if err := r.Reconciler.Init(mgr, &api.Captain{}, + if err := r.Reconciler.Init(mgr, &crewv1.Captain{}, declarative.WithObjectTransform(declarative.AddLabels(labels)), declarative.WithOwner(declarative.SourceAsOwner), declarative.WithLabels(watchLabels), @@ -74,7 +74,7 @@ func (r *CaptainReconciler) SetupWithManager(mgr ctrl.Manager) error { } // Watch for changes to Captain - err = c.Watch(&source.Kind{Type: &api.Captain{}}, &handler.EnqueueRequestForObject{}) + err = c.Watch(&source.Kind{Type: &crewv1.Captain{}}, &handler.EnqueueRequestForObject{}) if err != nil { return err } diff --git a/testdata/project-v2-addon/controllers/firstmate_controller.go b/testdata/project-v2-addon/controllers/firstmate_controller.go index e155604c592..283168ebfd8 100644 --- a/testdata/project-v2-addon/controllers/firstmate_controller.go +++ b/testdata/project-v2-addon/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" - api "sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v2-addon/api/v1" ) var _ reconcile.Reconciler = &FirstMateReconciler{} @@ -56,7 +56,7 @@ func (r *FirstMateReconciler) SetupWithManager(mgr ctrl.Manager) error { watchLabels := declarative.SourceLabel(mgr.GetScheme()) - if err := r.Reconciler.Init(mgr, &api.FirstMate{}, + if err := r.Reconciler.Init(mgr, &crewv1.FirstMate{}, declarative.WithObjectTransform(declarative.AddLabels(labels)), declarative.WithOwner(declarative.SourceAsOwner), declarative.WithLabels(watchLabels), @@ -74,7 +74,7 @@ func (r *FirstMateReconciler) SetupWithManager(mgr ctrl.Manager) error { } // Watch for changes to FirstMate - err = c.Watch(&source.Kind{Type: &api.FirstMate{}}, &handler.EnqueueRequestForObject{}) + err = c.Watch(&source.Kind{Type: &crewv1.FirstMate{}}, &handler.EnqueueRequestForObject{}) if err != nil { return err } diff --git a/testdata/project-v3-addon/api/v1/admiral_types.go b/testdata/project-v3-addon/api/v1/admiral_types.go index 843fbe36383..00f0c908dde 100644 --- a/testdata/project-v3-addon/api/v1/admiral_types.go +++ b/testdata/project-v3-addon/api/v1/admiral_types.go @@ -77,7 +77,6 @@ func (o *Admiral) SetCommonStatus(s addonv1alpha1.CommonStatus) { } //+kubebuilder:object:root=true -//+kubebuilder:resource:scope=Cluster // AdmiralList contains a list of Admiral type AdmiralList struct { diff --git a/testdata/project-v3-addon/controllers/admiral_controller.go b/testdata/project-v3-addon/controllers/admiral_controller.go index 7d39e3e7b91..0eb73c9ed5a 100644 --- a/testdata/project-v3-addon/controllers/admiral_controller.go +++ b/testdata/project-v3-addon/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" - api "sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1" ) var _ reconcile.Reconciler = &AdmiralReconciler{} @@ -56,7 +56,7 @@ func (r *AdmiralReconciler) SetupWithManager(mgr ctrl.Manager) error { watchLabels := declarative.SourceLabel(mgr.GetScheme()) - if err := r.Reconciler.Init(mgr, &api.Admiral{}, + if err := r.Reconciler.Init(mgr, &crewv1.Admiral{}, declarative.WithObjectTransform(declarative.AddLabels(labels)), declarative.WithOwner(declarative.SourceAsOwner), declarative.WithLabels(watchLabels), @@ -74,7 +74,7 @@ func (r *AdmiralReconciler) SetupWithManager(mgr ctrl.Manager) error { } // Watch for changes to Admiral - err = c.Watch(&source.Kind{Type: &api.Admiral{}}, &handler.EnqueueRequestForObject{}) + err = c.Watch(&source.Kind{Type: &crewv1.Admiral{}}, &handler.EnqueueRequestForObject{}) if err != nil { return err } diff --git a/testdata/project-v3-addon/controllers/captain_controller.go b/testdata/project-v3-addon/controllers/captain_controller.go index 1b29623ba18..f4f2470e9ff 100644 --- a/testdata/project-v3-addon/controllers/captain_controller.go +++ b/testdata/project-v3-addon/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" - api "sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1" ) var _ reconcile.Reconciler = &CaptainReconciler{} @@ -56,7 +56,7 @@ func (r *CaptainReconciler) SetupWithManager(mgr ctrl.Manager) error { watchLabels := declarative.SourceLabel(mgr.GetScheme()) - if err := r.Reconciler.Init(mgr, &api.Captain{}, + if err := r.Reconciler.Init(mgr, &crewv1.Captain{}, declarative.WithObjectTransform(declarative.AddLabels(labels)), declarative.WithOwner(declarative.SourceAsOwner), declarative.WithLabels(watchLabels), @@ -74,7 +74,7 @@ func (r *CaptainReconciler) SetupWithManager(mgr ctrl.Manager) error { } // Watch for changes to Captain - err = c.Watch(&source.Kind{Type: &api.Captain{}}, &handler.EnqueueRequestForObject{}) + err = c.Watch(&source.Kind{Type: &crewv1.Captain{}}, &handler.EnqueueRequestForObject{}) if err != nil { return err } diff --git a/testdata/project-v3-addon/controllers/firstmate_controller.go b/testdata/project-v3-addon/controllers/firstmate_controller.go index fced404d0c4..d5386353ab6 100644 --- a/testdata/project-v3-addon/controllers/firstmate_controller.go +++ b/testdata/project-v3-addon/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" - api "sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1" + crewv1 "sigs.k8s.io/kubebuilder/testdata/project-v3-addon/api/v1" ) var _ reconcile.Reconciler = &FirstMateReconciler{} @@ -56,7 +56,7 @@ func (r *FirstMateReconciler) SetupWithManager(mgr ctrl.Manager) error { watchLabels := declarative.SourceLabel(mgr.GetScheme()) - if err := r.Reconciler.Init(mgr, &api.FirstMate{}, + if err := r.Reconciler.Init(mgr, &crewv1.FirstMate{}, declarative.WithObjectTransform(declarative.AddLabels(labels)), declarative.WithOwner(declarative.SourceAsOwner), declarative.WithLabels(watchLabels), @@ -74,7 +74,7 @@ func (r *FirstMateReconciler) SetupWithManager(mgr ctrl.Manager) error { } // Watch for changes to FirstMate - err = c.Watch(&source.Kind{Type: &api.FirstMate{}}, &handler.EnqueueRequestForObject{}) + err = c.Watch(&source.Kind{Type: &crewv1.FirstMate{}}, &handler.EnqueueRequestForObject{}) if err != nil { return err } From 4ddc71cd12c080b694db81f5f1d1ad9b69531407 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Fri, 29 Jan 2021 10:46:50 +0100 Subject: [PATCH 25/38] Improve Makefile help Signed-off-by: Adrian Orive --- Makefile | 4 +- .../scaffolds/internal/templates/makefile.go | 93 +++++++++++-------- .../scaffolds/internal/templates/makefile.go | 64 ++++++------- testdata/project-v2-addon/Makefile | 93 +++++++++++-------- testdata/project-v2-multigroup/Makefile | 93 +++++++++++-------- testdata/project-v2/Makefile | 93 +++++++++++-------- testdata/project-v3-addon/Makefile | 64 ++++++------- testdata/project-v3-config/Makefile | 64 ++++++------- testdata/project-v3-multigroup/Makefile | 64 ++++++------- testdata/project-v3/Makefile | 64 ++++++------- 10 files changed, 378 insertions(+), 318 deletions(-) diff --git a/Makefile b/Makefile index 85ee5199d07..575981e4213 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ endif # http://linuxcommand.org/lc3_adv_awk.php .PHONY: help -help: ## Display this 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) ##@ Build @@ -96,7 +96,7 @@ test: ## Run the unit tests (used in the CI) ./test.sh .PHONY: test-coverage -test-coverage: ## Run coveralls +test-coverage: ## Run coveralls # remove all coverage files if exists - rm -rf *.out # run the go tests and gen the file coverage-all used to do the integration with coverrals.io diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go index 6aa8c56c6c8..cea72da1141 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/makefile.go @@ -67,60 +67,75 @@ else GOBIN=$(shell go env GOBIN) endif -all: manager +all: build -# Run tests -test: generate fmt vet manifests +##@ 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 + +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 + +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..." + +fmt: ## Run go fmt against code. + go fmt ./... + +vet: ## Run go vet against code. + go vet ./... + +test: manifests generate fmt vet ## Run tests. go test ./... -coverprofile cover.out -# Build manager binary -manager: generate fmt vet +##@ Build + +build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests +# Backwards compatibility +manager: build ## Build manager binary (alias for build target). + +run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -# Install CRDs into a cluster -install: manifests kustomize +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize +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 - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -# Run go fmt against code -fmt: - go fmt ./... +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/default | kubectl delete -f - -# Run go vet against code -vet: - go vet ./... - -# Generate code -generate: controller-gen - $(CONTROLLER_GEN) object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..." - -# Build the docker image -docker-build: test - docker build . -t ${IMG} - -# Push the docker image -docker-push: - docker push ${IMG} -# find or download controller-gen -# download controller-gen if necessary -controller-gen: +controller-gen: ## Download controller-gen locally if necessary. ifeq (, $(shell which controller-gen)) @{ \ set -e ;\ @@ -135,7 +150,7 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif -kustomize: +kustomize: ## Download kustomize locally if necessary. ifeq (, $(shell which kustomize)) @{ \ set -e ;\ diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go index 50d9468c6c4..09b983942fa 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/makefile.go @@ -70,9 +70,9 @@ else GOBIN=$(shell go env GOBIN) endif -all: manager +all: build -##@ Targets +##@ General # The help target prints out all targets with their descriptions organized # beneath their categories. The categories are represented by '##@' and the @@ -85,26 +85,49 @@ all: manager # More info on the awk command: # http://linuxcommand.org/lc3_adv_awk.php -.PHONY: help -help: ## Display this 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 + +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..." + +fmt: ## Run go fmt against code. + go fmt ./... + +vet: ## Run go vet against code. + go vet ./... + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests ## Run tests. +test: manifests generate fmt vet ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/{{ .ControllerRuntimeVersion }}/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -manager: generate fmt vet ## Build manager binary. +##@ Build + +build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -run: generate fmt vet manifests ## Run a controller from your host. +run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. @@ -115,33 +138,10 @@ undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/confi $(KUSTOMIZE) build config/default | kubectl delete -f - -manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -fmt: ## Run go fmt against code. - go fmt ./... - - -vet: ## Run go vet against code. - go vet ./... - -generate: controller-gen ## Generate code. - $(CONTROLLER_GEN) object:headerFile={{printf "%q" .BoilerplatePath}} paths="./..." - - -docker-build: test ## Build the docker image for the controller. - docker build -t ${IMG} . - - -docker-push: ## Push the docker image for the controller. - docker push ${IMG} - - CONTROLLER_GEN = $(shell pwd)/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@{{ .ControllerToolsVersion }}) - KUSTOMIZE = $(shell pwd)/bin/kustomize kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@{{ .KustomizeVersion }}) diff --git a/testdata/project-v2-addon/Makefile b/testdata/project-v2-addon/Makefile index f6362e191e3..0cf408a4de3 100644 --- a/testdata/project-v2-addon/Makefile +++ b/testdata/project-v2-addon/Makefile @@ -11,60 +11,75 @@ else GOBIN=$(shell go env GOBIN) endif -all: manager +all: build -# Run tests -test: generate fmt vet manifests +##@ 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 + +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 + +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +fmt: ## Run go fmt against code. + go fmt ./... + +vet: ## Run go vet against code. + go vet ./... + +test: manifests generate fmt vet ## Run tests. go test ./... -coverprofile cover.out -# Build manager binary -manager: generate fmt vet +##@ Build + +build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests +# Backwards compatibility +manager: build ## Build manager binary (alias for build target). + +run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -# Install CRDs into a cluster -install: manifests kustomize +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize +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 - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -# Run go fmt against code -fmt: - go fmt ./... +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/default | kubectl delete -f - -# Run go vet against code -vet: - go vet ./... - -# Generate code -generate: controller-gen - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - -# Build the docker image -docker-build: test - docker build . -t ${IMG} - -# Push the docker image -docker-push: - docker push ${IMG} -# find or download controller-gen -# download controller-gen if necessary -controller-gen: +controller-gen: ## Download controller-gen locally if necessary. ifeq (, $(shell which controller-gen)) @{ \ set -e ;\ @@ -79,7 +94,7 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif -kustomize: +kustomize: ## Download kustomize locally if necessary. ifeq (, $(shell which kustomize)) @{ \ set -e ;\ diff --git a/testdata/project-v2-multigroup/Makefile b/testdata/project-v2-multigroup/Makefile index f6362e191e3..0cf408a4de3 100644 --- a/testdata/project-v2-multigroup/Makefile +++ b/testdata/project-v2-multigroup/Makefile @@ -11,60 +11,75 @@ else GOBIN=$(shell go env GOBIN) endif -all: manager +all: build -# Run tests -test: generate fmt vet manifests +##@ 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 + +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 + +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +fmt: ## Run go fmt against code. + go fmt ./... + +vet: ## Run go vet against code. + go vet ./... + +test: manifests generate fmt vet ## Run tests. go test ./... -coverprofile cover.out -# Build manager binary -manager: generate fmt vet +##@ Build + +build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests +# Backwards compatibility +manager: build ## Build manager binary (alias for build target). + +run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -# Install CRDs into a cluster -install: manifests kustomize +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize +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 - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -# Run go fmt against code -fmt: - go fmt ./... +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/default | kubectl delete -f - -# Run go vet against code -vet: - go vet ./... - -# Generate code -generate: controller-gen - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - -# Build the docker image -docker-build: test - docker build . -t ${IMG} - -# Push the docker image -docker-push: - docker push ${IMG} -# find or download controller-gen -# download controller-gen if necessary -controller-gen: +controller-gen: ## Download controller-gen locally if necessary. ifeq (, $(shell which controller-gen)) @{ \ set -e ;\ @@ -79,7 +94,7 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif -kustomize: +kustomize: ## Download kustomize locally if necessary. ifeq (, $(shell which kustomize)) @{ \ set -e ;\ diff --git a/testdata/project-v2/Makefile b/testdata/project-v2/Makefile index f6362e191e3..0cf408a4de3 100644 --- a/testdata/project-v2/Makefile +++ b/testdata/project-v2/Makefile @@ -11,60 +11,75 @@ else GOBIN=$(shell go env GOBIN) endif -all: manager +all: build -# Run tests -test: generate fmt vet manifests +##@ 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 + +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 + +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +fmt: ## Run go fmt against code. + go fmt ./... + +vet: ## Run go vet against code. + go vet ./... + +test: manifests generate fmt vet ## Run tests. go test ./... -coverprofile cover.out -# Build manager binary -manager: generate fmt vet +##@ Build + +build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests +# Backwards compatibility +manager: build ## Build manager binary (alias for build target). + +run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -# Install CRDs into a cluster -install: manifests kustomize +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize +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 - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -# Run go fmt against code -fmt: - go fmt ./... +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/default | kubectl delete -f - -# Run go vet against code -vet: - go vet ./... - -# Generate code -generate: controller-gen - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - -# Build the docker image -docker-build: test - docker build . -t ${IMG} - -# Push the docker image -docker-push: - docker push ${IMG} -# find or download controller-gen -# download controller-gen if necessary -controller-gen: +controller-gen: ## Download controller-gen locally if necessary. ifeq (, $(shell which controller-gen)) @{ \ set -e ;\ @@ -79,7 +94,7 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif -kustomize: +kustomize: ## Download kustomize locally if necessary. ifeq (, $(shell which kustomize)) @{ \ set -e ;\ diff --git a/testdata/project-v3-addon/Makefile b/testdata/project-v3-addon/Makefile index e81937b7b38..16d0b8de6c8 100644 --- a/testdata/project-v3-addon/Makefile +++ b/testdata/project-v3-addon/Makefile @@ -11,9 +11,9 @@ else GOBIN=$(shell go env GOBIN) endif -all: manager +all: build -##@ Targets +##@ General # The help target prints out all targets with their descriptions organized # beneath their categories. The categories are represented by '##@' and the @@ -26,26 +26,49 @@ all: manager # More info on the awk command: # http://linuxcommand.org/lc3_adv_awk.php -.PHONY: help -help: ## Display this 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 + +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +fmt: ## Run go fmt against code. + go fmt ./... + +vet: ## Run go vet against code. + go vet ./... + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests ## Run tests. +test: manifests generate fmt vet ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -manager: generate fmt vet ## Build manager binary. +##@ Build + +build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -run: generate fmt vet manifests ## Run a controller from your host. +run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. @@ -56,33 +79,10 @@ undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/confi $(KUSTOMIZE) build config/default | kubectl delete -f - -manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -fmt: ## Run go fmt against code. - go fmt ./... - - -vet: ## Run go vet against code. - go vet ./... - -generate: controller-gen ## Generate code. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - - -docker-build: test ## Build the docker image for the controller. - docker build -t ${IMG} . - - -docker-push: ## Push the docker image for the controller. - docker push ${IMG} - - CONTROLLER_GEN = $(shell pwd)/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) - KUSTOMIZE = $(shell pwd)/bin/kustomize kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) diff --git a/testdata/project-v3-config/Makefile b/testdata/project-v3-config/Makefile index e81937b7b38..16d0b8de6c8 100644 --- a/testdata/project-v3-config/Makefile +++ b/testdata/project-v3-config/Makefile @@ -11,9 +11,9 @@ else GOBIN=$(shell go env GOBIN) endif -all: manager +all: build -##@ Targets +##@ General # The help target prints out all targets with their descriptions organized # beneath their categories. The categories are represented by '##@' and the @@ -26,26 +26,49 @@ all: manager # More info on the awk command: # http://linuxcommand.org/lc3_adv_awk.php -.PHONY: help -help: ## Display this 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 + +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +fmt: ## Run go fmt against code. + go fmt ./... + +vet: ## Run go vet against code. + go vet ./... + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests ## Run tests. +test: manifests generate fmt vet ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -manager: generate fmt vet ## Build manager binary. +##@ Build + +build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -run: generate fmt vet manifests ## Run a controller from your host. +run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. @@ -56,33 +79,10 @@ undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/confi $(KUSTOMIZE) build config/default | kubectl delete -f - -manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -fmt: ## Run go fmt against code. - go fmt ./... - - -vet: ## Run go vet against code. - go vet ./... - -generate: controller-gen ## Generate code. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - - -docker-build: test ## Build the docker image for the controller. - docker build -t ${IMG} . - - -docker-push: ## Push the docker image for the controller. - docker push ${IMG} - - CONTROLLER_GEN = $(shell pwd)/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) - KUSTOMIZE = $(shell pwd)/bin/kustomize kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) diff --git a/testdata/project-v3-multigroup/Makefile b/testdata/project-v3-multigroup/Makefile index e81937b7b38..16d0b8de6c8 100644 --- a/testdata/project-v3-multigroup/Makefile +++ b/testdata/project-v3-multigroup/Makefile @@ -11,9 +11,9 @@ else GOBIN=$(shell go env GOBIN) endif -all: manager +all: build -##@ Targets +##@ General # The help target prints out all targets with their descriptions organized # beneath their categories. The categories are represented by '##@' and the @@ -26,26 +26,49 @@ all: manager # More info on the awk command: # http://linuxcommand.org/lc3_adv_awk.php -.PHONY: help -help: ## Display this 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 + +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +fmt: ## Run go fmt against code. + go fmt ./... + +vet: ## Run go vet against code. + go vet ./... + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests ## Run tests. +test: manifests generate fmt vet ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -manager: generate fmt vet ## Build manager binary. +##@ Build + +build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -run: generate fmt vet manifests ## Run a controller from your host. +run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. @@ -56,33 +79,10 @@ undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/confi $(KUSTOMIZE) build config/default | kubectl delete -f - -manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -fmt: ## Run go fmt against code. - go fmt ./... - - -vet: ## Run go vet against code. - go vet ./... - -generate: controller-gen ## Generate code. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - - -docker-build: test ## Build the docker image for the controller. - docker build -t ${IMG} . - - -docker-push: ## Push the docker image for the controller. - docker push ${IMG} - - CONTROLLER_GEN = $(shell pwd)/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) - KUSTOMIZE = $(shell pwd)/bin/kustomize kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) diff --git a/testdata/project-v3/Makefile b/testdata/project-v3/Makefile index e81937b7b38..16d0b8de6c8 100644 --- a/testdata/project-v3/Makefile +++ b/testdata/project-v3/Makefile @@ -11,9 +11,9 @@ else GOBIN=$(shell go env GOBIN) endif -all: manager +all: build -##@ Targets +##@ General # The help target prints out all targets with their descriptions organized # beneath their categories. The categories are represented by '##@' and the @@ -26,26 +26,49 @@ all: manager # More info on the awk command: # http://linuxcommand.org/lc3_adv_awk.php -.PHONY: help -help: ## Display this 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 + +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +fmt: ## Run go fmt against code. + go fmt ./... + +vet: ## Run go vet against code. + go vet ./... + ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests ## Run tests. +test: manifests generate fmt vet ## Run tests. mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out -manager: generate fmt vet ## Build manager binary. +##@ Build + +build: generate fmt vet ## Build manager binary. go build -o bin/manager main.go -run: generate fmt vet manifests ## Run a controller from your host. +run: manifests generate fmt vet ## Run a controller from your host. go run ./main.go -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl apply -f - -uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. $(KUSTOMIZE) build config/crd | kubectl delete -f - deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. @@ -56,33 +79,10 @@ undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/confi $(KUSTOMIZE) build config/default | kubectl delete -f - -manifests: controller-gen ## Generate manifests e.g. CRD, RBAC, etc. - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -fmt: ## Run go fmt against code. - go fmt ./... - - -vet: ## Run go vet against code. - go vet ./... - -generate: controller-gen ## Generate code. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - - -docker-build: test ## Build the docker image for the controller. - docker build -t ${IMG} . - - -docker-push: ## Push the docker image for the controller. - docker push ${IMG} - - CONTROLLER_GEN = $(shell pwd)/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) - KUSTOMIZE = $(shell pwd)/bin/kustomize kustomize: ## Download kustomize locally if necessary. $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) From f1685b8648cc7727a6e63df69b3b01846a071699 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Mon, 8 Feb 2021 13:49:52 +0100 Subject: [PATCH 26/38] Fix sorting issue nwith plugin versions and their supported project versions Signed-off-by: Adrian Orive --- pkg/cli/root.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/cli/root.go b/pkg/cli/root.go index 0f98ef316a4..d0eed81e44b 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -89,7 +89,7 @@ func (c cli) getPluginTable() string { maxPluginKeyLength = len(pluginKeysHeader) pluginKeys = make([]string, 0, len(c.plugins)) maxProjectVersionLength = len(projectVersionsHeader) - projectVersions = make([]string, 0, len(c.plugins)) + projectVersions = make(map[string]string, len(c.plugins)) ) for pluginKey, plugin := range c.plugins { @@ -106,7 +106,7 @@ func (c cli) getPluginTable() string { if len(supportedProjectVersionsStr) > maxProjectVersionLength { maxProjectVersionLength = len(supportedProjectVersionsStr) } - projectVersions = append(projectVersions, supportedProjectVersionsStr) + projectVersions[pluginKey] = supportedProjectVersionsStr } lines := make([]string, 0, len(c.plugins)+2) @@ -116,8 +116,8 @@ func (c cli) getPluginTable() string { strings.Repeat("-", maxProjectVersionLength+2)) sort.Strings(pluginKeys) - for i, pluginKey := range pluginKeys { - supportedProjectVersions := projectVersions[i] + for _, pluginKey := range pluginKeys { + supportedProjectVersions := projectVersions[pluginKey] lines = append(lines, fmt.Sprintf(" %[1]*[2]s | %[3]*[4]s", maxPluginKeyLength, pluginKey, maxProjectVersionLength, supportedProjectVersions)) } From a9dd9dcad8e389468505a07ae26316fb291bbde1 Mon Sep 17 00:00:00 2001 From: Adrian Orive Oneca Date: Tue, 9 Feb 2021 17:19:32 +0100 Subject: [PATCH 27/38] Fix go/v2 with config/v3 resources in config file to store webhook information Signed-off-by: Adrian Orive Oneca --- pkg/plugins/golang/v2/scaffolds/webhook.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/plugins/golang/v2/scaffolds/webhook.go b/pkg/plugins/golang/v2/scaffolds/webhook.go index c404f571eef..d60175f8c13 100644 --- a/pkg/plugins/golang/v2/scaffolds/webhook.go +++ b/pkg/plugins/golang/v2/scaffolds/webhook.go @@ -64,6 +64,10 @@ func (s *webhookScaffolder) newUniverse() *model.Universe { } func (s *webhookScaffolder) scaffold() error { + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } + if err := machinery.NewScaffold().Execute( s.newUniverse(), &api.Webhook{}, From 42091e91d9d50265c8528bf9203a9b6ee9100fac Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Wed, 10 Feb 2021 15:15:15 +0100 Subject: [PATCH 28/38] Track resources without an API in the config file (config/v3) - Remove path from resources that do not know the path - Add scaffold to controllers if we do know the path instead of if we scaffold an API - Track the resources in the config file even if we don't scaffold an API Signed-off-by: Adrian Orive --- generate_testdata.sh | 2 +- pkg/model/file/funcmap.go | 12 +++- pkg/model/resource/resource.go | 8 ++- pkg/plugins/golang/options.go | 7 ++- pkg/plugins/golang/options_test.go | 56 +++++++++++++++---- pkg/plugins/golang/v2/api.go | 6 +- pkg/plugins/golang/v2/options.go | 13 ++++- pkg/plugins/golang/v2/options_test.go | 53 ++++++++++++++---- pkg/plugins/golang/v2/scaffolds/api.go | 13 ++++- .../templates/controllers/controller.go | 4 +- .../controllers/controller_suitetest.go | 4 +- pkg/plugins/golang/v2/webhook.go | 13 ++++- pkg/plugins/golang/v3/api.go | 4 +- pkg/plugins/golang/v3/scaffolds/api.go | 8 +-- .../templates/controllers/controller.go | 4 +- .../controllers/controller_suitetest.go | 4 +- pkg/plugins/golang/v3/webhook.go | 3 +- .../config/rbac/role.yaml | 4 +- ...controller.go => deployment_controller.go} | 20 +++---- .../controllers/apps/suite_test.go | 4 ++ testdata/project-v2-multigroup/go.mod | 1 + testdata/project-v2-multigroup/main.go | 6 +- testdata/project-v3-config/PROJECT | 5 ++ testdata/project-v3-multigroup/PROJECT | 5 ++ .../config/rbac/role.yaml | 6 +- ...controller.go => deployment_controller.go} | 22 ++++---- .../controllers/apps/suite_test.go | 4 ++ testdata/project-v3-multigroup/main.go | 6 +- testdata/project-v3/PROJECT | 5 ++ 29 files changed, 215 insertions(+), 87 deletions(-) rename testdata/project-v2-multigroup/controllers/apps/{pod_controller.go => deployment_controller.go} (70%) rename testdata/project-v3-multigroup/controllers/apps/{pod_controller.go => deployment_controller.go} (66%) diff --git a/generate_testdata.sh b/generate_testdata.sh index f66b16df5d9..692d7b05528 100755 --- a/generate_testdata.sh +++ b/generate_testdata.sh @@ -98,7 +98,7 @@ scaffold_test_project() { $kb create api --group foo.policy --version v1 --kind HealthCheckPolicy --controller=true --resource=true --make=false - $kb create api --group apps --version v1 --kind Pod --controller=true --resource=false --make=false + $kb create api --group apps --version v1 --kind Deployment --controller=true --resource=false --make=false if [ $project == "project-v3-multigroup" ]; then $kb create api --version v1 --kind Lakers --controller=true --resource=true --make=false diff --git a/pkg/model/file/funcmap.go b/pkg/model/file/funcmap.go index 95d921c189a..a0a9432427b 100644 --- a/pkg/model/file/funcmap.go +++ b/pkg/model/file/funcmap.go @@ -26,12 +26,18 @@ import ( // DefaultFuncMap returns the default template.FuncMap for rendering the template. func DefaultFuncMap() template.FuncMap { return template.FuncMap{ - "title": strings.Title, - "lower": strings.ToLower, - "hashFNV": hashFNV, + "title": strings.Title, + "lower": strings.ToLower, + "isEmptyStr": isEmptyString, + "hashFNV": hashFNV, } } +// isEmptyString returns whether the string is empty +func isEmptyString(s string) bool { + return s == "" +} + // hashFNV will generate a random string useful for generating a unique string func hashFNV(s string) (string, error) { hasher := fnv.New32a() diff --git a/pkg/model/resource/resource.go b/pkg/model/resource/resource.go index 869b4a7e4d0..f5a36ba567c 100644 --- a/pkg/model/resource/resource.go +++ b/pkg/model/resource/resource.go @@ -155,8 +155,12 @@ func (r *Resource) Update(other Resource) error { return fmt.Errorf("unable to update Resource with another with non-matching Plural") } - if r.Path != other.Path { - return fmt.Errorf("unable to update Resource with another with non-matching Path") + if other.Path != "" && r.Path != other.Path { + if r.Path == "" { + r.Path = other.Path + } else { + return fmt.Errorf("unable to update Resource with another with non-matching Path") + } } // Update API. diff --git a/pkg/plugins/golang/options.go b/pkg/plugins/golang/options.go index ab2a2e0c96b..892217da7ca 100644 --- a/pkg/plugins/golang/options.go +++ b/pkg/plugins/golang/options.go @@ -137,7 +137,6 @@ func (opts Options) GVK() resource.GVK { func (opts Options) NewResource(c newconfig.Config) resource.Resource { res := resource.Resource{ GVK: opts.GVK(), - Path: resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()), Controller: opts.DoController, } @@ -149,6 +148,7 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { } if opts.DoAPI { + res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) res.API = &resource.API{ CRDVersion: opts.CRDVersion, Namespaced: opts.Namespaced, @@ -159,6 +159,7 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { } if opts.DoDefaulting || opts.DoValidation || opts.DoConversion { + res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) res.Webhooks = &resource.Webhooks{ WebhookVersion: opts.WebhookVersion, Defaulting: opts.DoDefaulting, @@ -177,7 +178,9 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { // - In any other case, default to => project resource // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath if !opts.DoAPI { - if !c.HasResource(opts.GVK()) { + loadedRes, err := c.GetResource(opts.GVK()) + alreadyHasAPI := err == nil && loadedRes.HasAPI() + if !alreadyHasAPI { if domain, found := coreGroups[opts.Group]; found { res.Domain = domain res.Path = path.Join("k8s.io", "api", opts.Group, opts.Version) diff --git a/pkg/plugins/golang/options_test.go b/pkg/plugins/golang/options_test.go index 983313fb111..8faa101d975 100644 --- a/pkg/plugins/golang/options_test.go +++ b/pkg/plugins/golang/options_test.go @@ -62,6 +62,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -70,19 +72,36 @@ var _ = Describe("Options", func() { Expect(resource.Domain).To(Equal(options.Domain)) Expect(resource.Version).To(Equal(options.Version)) Expect(resource.Kind).To(Equal(options.Kind)) - if multiGroup { - Expect(resource.Path).To(Equal( - path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + Expect(resource.API).NotTo(BeNil()) + if options.DoAPI || options.DoDefaulting || options.DoValidation || options.DoConversion { + if multiGroup { + Expect(resource.Path).To(Equal( + path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + } else { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + } } else { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + // Core-resources have a path despite not having an API/Webhook but they are not tested here + Expect(resource.Path).To(Equal("")) + } + if options.DoAPI { + Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) + Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) + Expect(resource.API.IsEmpty()).To(BeFalse()) + } else { + Expect(resource.API.IsEmpty()).To(BeTrue()) } - Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) - Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) Expect(resource.Controller).To(Equal(options.DoController)) - Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) - Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) - Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) - Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.Webhooks).NotTo(BeNil()) + if options.DoDefaulting || options.DoValidation || options.DoConversion { + Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) + Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) + Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) + Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.Webhooks.IsEmpty()).To(BeFalse()) + } else { + Expect(resource.Webhooks.IsEmpty()).To(BeTrue()) + } Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) Expect(resource.PackageName()).To(Equal(options.Group)) Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) @@ -130,6 +149,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -150,6 +171,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -168,12 +191,15 @@ var _ = Describe("Options", func() { Domain: "test.io", Version: "v1", Kind: "FirstMate", + DoAPI: true, // Scaffold the API so that the path is saved } Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -201,6 +227,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -222,12 +250,15 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) Expect(resource.Validate()).To(Succeed()) Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.API.CRDVersion).To(Equal("")) + Expect(resource.API).NotTo(BeNil()) + Expect(resource.API.IsEmpty()).To(BeTrue()) Expect(resource.QualifiedGroup()).To(Equal(qualified)) } }, @@ -242,12 +273,15 @@ var _ = Describe("Options", func() { Domain: "test.io", Version: "v1", Kind: "FirstMate", + DoAPI: true, // Scaffold the API so that the path is saved } Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) diff --git a/pkg/plugins/golang/v2/api.go b/pkg/plugins/golang/v2/api.go index 0c224a07d4a..bdca3497512 100644 --- a/pkg/plugins/golang/v2/api.go +++ b/pkg/plugins/golang/v2/api.go @@ -154,9 +154,9 @@ func (p *createAPISubcommand) Validate() error { } // In case we want to scaffold a resource API we need to do some checks - if p.resource.HasAPI() { - // Check that resource doesn't exist or flag force was set - if !p.force && p.config.HasResource(p.resource.GVK) { + if p.options.DoAPI { + // Check that resource doesn't have the API scaffolded or flag force was set + if res, err := p.config.GetResource(p.resource.GVK); err == nil && res.HasAPI() && !p.force { return errors.New("API resource already exists") } diff --git a/pkg/plugins/golang/v2/options.go b/pkg/plugins/golang/v2/options.go index 4b3613fe295..52f4410b838 100644 --- a/pkg/plugins/golang/v2/options.go +++ b/pkg/plugins/golang/v2/options.go @@ -22,6 +22,7 @@ import ( "strings" newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" ) @@ -137,7 +138,6 @@ func (opts Options) GVK() resource.GVK { func (opts Options) NewResource(c newconfig.Config) resource.Resource { res := resource.Resource{ GVK: opts.GVK(), - Path: resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()), Controller: opts.DoController, } @@ -149,6 +149,7 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { } if opts.DoAPI { + res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) res.API = &resource.API{ CRDVersion: opts.CRDVersion, Namespaced: opts.Namespaced, @@ -159,6 +160,7 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { } if opts.DoDefaulting || opts.DoValidation || opts.DoConversion { + res.Path = resource.APIPackagePath(c.GetRepository(), opts.Group, opts.Version, c.IsMultiGroup()) res.Webhooks = &resource.Webhooks{ WebhookVersion: opts.WebhookVersion, Defaulting: opts.DoDefaulting, @@ -177,7 +179,14 @@ func (opts Options) NewResource(c newconfig.Config) resource.Resource { // - In any other case, default to => project resource // TODO: need to support '--resource-pkg-path' flag for specifying resourcePath if !opts.DoAPI { - if !c.HasResource(opts.GVK()) { + var alreadyHasAPI bool + if c.GetVersion().Compare(cfgv2.Version) == 0 { + alreadyHasAPI = c.HasResource(opts.GVK()) + } else { + loadedRes, err := c.GetResource(opts.GVK()) + alreadyHasAPI = err == nil && loadedRes.HasAPI() + } + if !alreadyHasAPI { if domain, found := coreGroups[opts.Group]; found { res.Domain = domain res.Path = path.Join("k8s.io", "api", opts.Group, opts.Version) diff --git a/pkg/plugins/golang/v2/options_test.go b/pkg/plugins/golang/v2/options_test.go index e4997e684a7..692a262e5ca 100644 --- a/pkg/plugins/golang/v2/options_test.go +++ b/pkg/plugins/golang/v2/options_test.go @@ -61,6 +61,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -69,19 +71,36 @@ var _ = Describe("Options", func() { Expect(resource.Domain).To(Equal(options.Domain)) Expect(resource.Version).To(Equal(options.Version)) Expect(resource.Kind).To(Equal(options.Kind)) - if multiGroup { - Expect(resource.Path).To(Equal( - path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + Expect(resource.API).NotTo(BeNil()) + if options.DoAPI || options.DoDefaulting || options.DoValidation || options.DoConversion { + if multiGroup { + Expect(resource.Path).To(Equal( + path.Join(cfg.GetRepository(), "apis", options.Group, options.Version))) + } else { + Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + } } else { - Expect(resource.Path).To(Equal(path.Join(cfg.GetRepository(), "api", options.Version))) + // Core-resources have a path despite not having an API/Webhook but they are not tested here + Expect(resource.Path).To(Equal("")) + } + if options.DoAPI { + Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) + Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) + Expect(resource.API.IsEmpty()).To(BeFalse()) + } else { + Expect(resource.API.IsEmpty()).To(BeTrue()) } - Expect(resource.API.CRDVersion).To(Equal(options.CRDVersion)) - Expect(resource.API.Namespaced).To(Equal(options.Namespaced)) Expect(resource.Controller).To(Equal(options.DoController)) - Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) - Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) - Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) - Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.Webhooks).NotTo(BeNil()) + if options.DoDefaulting || options.DoValidation || options.DoConversion { + Expect(resource.Webhooks.WebhookVersion).To(Equal(options.WebhookVersion)) + Expect(resource.Webhooks.Defaulting).To(Equal(options.DoDefaulting)) + Expect(resource.Webhooks.Validation).To(Equal(options.DoValidation)) + Expect(resource.Webhooks.Conversion).To(Equal(options.DoConversion)) + Expect(resource.Webhooks.IsEmpty()).To(BeFalse()) + } else { + Expect(resource.Webhooks.IsEmpty()).To(BeTrue()) + } Expect(resource.QualifiedGroup()).To(Equal(options.Group + "." + options.Domain)) Expect(resource.PackageName()).To(Equal(options.Group)) Expect(resource.ImportAlias()).To(Equal(options.Group + options.Version)) @@ -129,6 +148,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -149,6 +170,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -167,12 +190,15 @@ var _ = Describe("Options", func() { Domain: "test.io", Version: "v1", Kind: "FirstMate", + DoAPI: true, // Scaffold the API so that the path is saved } Expect(options.Validate()).To(Succeed()) for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -200,6 +226,8 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) @@ -221,12 +249,15 @@ var _ = Describe("Options", func() { for _, multiGroup := range []bool{false, true} { if multiGroup { Expect(cfg.SetMultiGroup()).To(Succeed()) + } else { + Expect(cfg.ClearMultiGroup()).To(Succeed()) } resource := options.NewResource(cfg) Expect(resource.Validate()).To(Succeed()) Expect(resource.Path).To(Equal(path.Join("k8s.io", "api", options.Group, options.Version))) - Expect(resource.API.CRDVersion).To(Equal("")) + Expect(resource.API).NotTo(BeNil()) + Expect(resource.API.IsEmpty()).To(BeTrue()) Expect(resource.QualifiedGroup()).To(Equal(qualified)) } }, diff --git a/pkg/plugins/golang/v2/scaffolds/api.go b/pkg/plugins/golang/v2/scaffolds/api.go index e4abc4d9ae6..353aac0e5f0 100644 --- a/pkg/plugins/golang/v2/scaffolds/api.go +++ b/pkg/plugins/golang/v2/scaffolds/api.go @@ -20,6 +20,7 @@ import ( "fmt" "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/model" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds/internal/templates" @@ -89,11 +90,19 @@ func (s *apiScaffolder) scaffold() error { doAPI := s.resource.HasAPI() doController := s.resource.HasController() - if doAPI { - + // Project version v2 only tracked GVK triplets of each resource. + // As they were only tracked when the API was scaffolded, the presence of a + // resource in the config file was used in webhook creation to verify that + // the API had been scaffolded previously. From project version v3 onwards + // this information is stored in the API field of the resource, so we can + // update the resources except for project version 2 when no API was scaffolded. + if doAPI || s.config.GetVersion().Compare(cfgv2.Version) == 1 { if err := s.config.UpdateResource(s.resource); err != nil { return fmt.Errorf("error updating resource: %w", err) } + } + + if doAPI { if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go index b338e3b151f..ee059001455 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller.go @@ -72,7 +72,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - {{ if .Resource.HasAPI -}} + {{ if not (isEmptyStr .Resource.Path) -}} {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" {{- end }} ) @@ -108,7 +108,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(req ctrl.Request) (ctrl.Resul // SetupWithManager sets up the controller with the Manager. func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - {{ if .Resource.HasAPI -}} + {{ if not (isEmptyStr .Resource.Path) -}} For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}). {{- else -}} // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument diff --git a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go index 34e1b418aa9..61bd1d584d7 100644 --- a/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v2/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -98,13 +98,13 @@ func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { // Generate import code fragments imports := make([]string, 0) - if f.Resource.HasAPI() { + if f.Resource.Path != "" { imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } // Generate add scheme code fragments addScheme := make([]string, 0) - if f.Resource.HasAPI() { + if f.Resource.Path != "" { addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) } diff --git a/pkg/plugins/golang/v2/webhook.go b/pkg/plugins/golang/v2/webhook.go index 2c4b2f8bb7e..735c48c68df 100644 --- a/pkg/plugins/golang/v2/webhook.go +++ b/pkg/plugins/golang/v2/webhook.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/pflag" newconfig "sigs.k8s.io/kubebuilder/v3/pkg/config" + cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2" "sigs.k8s.io/kubebuilder/v3/pkg/model/resource" "sigs.k8s.io/kubebuilder/v3/pkg/plugin" "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v2/scaffolds" @@ -104,8 +105,16 @@ func (p *createWebhookSubcommand) Validate() error { } // check if resource exist to create webhook - if !p.config.HasResource(p.resource.GVK) { - return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) + if p.config.GetVersion().Compare(cfgv2.Version) == 0 { + if !p.config.HasResource(p.resource.GVK) { + return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) + } + } else { + if r, err := p.config.GetResource(p.resource.GVK); err != nil { + return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) + } else if r.Webhooks != nil && !r.Webhooks.IsEmpty() { + return fmt.Errorf("webhook resource already exists") + } } return nil diff --git a/pkg/plugins/golang/v3/api.go b/pkg/plugins/golang/v3/api.go index 98a6462e40b..f71bc7e5e4f 100644 --- a/pkg/plugins/golang/v3/api.go +++ b/pkg/plugins/golang/v3/api.go @@ -176,8 +176,8 @@ func (p *createAPISubcommand) Validate() error { } // In case we want to scaffold a resource API we need to do some checks - if p.resource.HasAPI() { - // Check that resource doesn't exist or flag force was set + if p.options.DoAPI { + // Check that resource doesn't have the API scaffolded or flag force was set if res, err := p.config.GetResource(p.resource.GVK); err == nil && res.HasAPI() && !p.force { return errors.New("API resource already exists") } diff --git a/pkg/plugins/golang/v3/scaffolds/api.go b/pkg/plugins/golang/v3/scaffolds/api.go index 0265d705b60..1d9698cafc3 100644 --- a/pkg/plugins/golang/v3/scaffolds/api.go +++ b/pkg/plugins/golang/v3/scaffolds/api.go @@ -86,11 +86,11 @@ func (s *apiScaffolder) scaffold() error { doAPI := s.resource.HasAPI() doController := s.resource.HasController() - if doAPI { + if err := s.config.UpdateResource(s.resource); err != nil { + return fmt.Errorf("error updating resource: %w", err) + } - if err := s.config.UpdateResource(s.resource); err != nil { - return fmt.Errorf("error updating resource: %w", err) - } + if doAPI { if err := machinery.NewScaffold(s.plugins...).Execute( s.newUniverse(), diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go index a70eed4714d..779edb04b45 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller.go @@ -72,7 +72,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - {{ if .Resource.HasAPI -}} + {{ if not (isEmptyStr .Resource.Path) -}} {{ .Resource.ImportAlias }} "{{ .Resource.Path }}" {{- end }} ) @@ -108,7 +108,7 @@ func (r *{{ .Resource.Kind }}Reconciler) Reconcile(ctx context.Context, req ctrl // SetupWithManager sets up the controller with the Manager. func (r *{{ .Resource.Kind }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - {{ if .Resource.HasAPI -}} + {{ if not (isEmptyStr .Resource.Path) -}} For(&{{ .Resource.ImportAlias }}.{{ .Resource.Kind }}{}). {{- else -}} // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go index d8159a93c3e..d6e6ac95af5 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/controllers/controller_suitetest.go @@ -98,13 +98,13 @@ func (f *SuiteTest) GetCodeFragments() file.CodeFragmentsMap { // Generate import code fragments imports := make([]string, 0) - if f.Resource.HasAPI() { + if f.Resource.Path != "" { imports = append(imports, fmt.Sprintf(apiImportCodeFragment, f.Resource.ImportAlias(), f.Resource.Path)) } // Generate add scheme code fragments addScheme := make([]string, 0) - if f.Resource.HasAPI() { + if f.Resource.Path != "" { addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, f.Resource.ImportAlias())) } diff --git a/pkg/plugins/golang/v3/webhook.go b/pkg/plugins/golang/v3/webhook.go index 0139225b878..7c81b017fae 100644 --- a/pkg/plugins/golang/v3/webhook.go +++ b/pkg/plugins/golang/v3/webhook.go @@ -17,7 +17,6 @@ limitations under the License. package v3 import ( - "errors" "fmt" "io/ioutil" "path/filepath" @@ -119,7 +118,7 @@ func (p *createWebhookSubcommand) Validate() error { if r, err := p.config.GetResource(p.resource.GVK); err != nil { return fmt.Errorf("%s create webhook requires a previously created API ", p.commandName) } else if r.Webhooks != nil && !r.Webhooks.IsEmpty() && !p.force { - return errors.New("webhook resource already exists") + return fmt.Errorf("webhook resource already exists") } if !p.config.IsWebhookVersionCompatible(p.resource.Webhooks.WebhookVersion) { diff --git a/testdata/project-v2-multigroup/config/rbac/role.yaml b/testdata/project-v2-multigroup/config/rbac/role.yaml index aef6d389cee..7ae49be3caa 100644 --- a/testdata/project-v2-multigroup/config/rbac/role.yaml +++ b/testdata/project-v2-multigroup/config/rbac/role.yaml @@ -9,7 +9,7 @@ rules: - apiGroups: - apps resources: - - pods + - deployments verbs: - create - delete @@ -21,7 +21,7 @@ rules: - apiGroups: - apps resources: - - pods/status + - deployments/status verbs: - get - patch diff --git a/testdata/project-v2-multigroup/controllers/apps/pod_controller.go b/testdata/project-v2-multigroup/controllers/apps/deployment_controller.go similarity index 70% rename from testdata/project-v2-multigroup/controllers/apps/pod_controller.go rename to testdata/project-v2-multigroup/controllers/apps/deployment_controller.go index 7cd866d048e..cf176817191 100644 --- a/testdata/project-v2-multigroup/controllers/apps/pod_controller.go +++ b/testdata/project-v2-multigroup/controllers/apps/deployment_controller.go @@ -20,33 +20,34 @@ import ( "context" "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) -// PodReconciler reconciles a Pod object -type PodReconciler struct { +// DeploymentReconciler reconciles a Deployment object +type DeploymentReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme } -//+kubebuilder:rbac:groups=apps,resources=pods,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=apps,resources=pods/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by -// the Pod object against the actual cluster state, and then +// the Deployment object against the actual cluster state, and then // perform operations to make the cluster state reflect the state specified by // the user. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.6.4/pkg/reconcile -func (r *PodReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { +func (r *DeploymentReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { _ = context.Background() - _ = r.Log.WithValues("pod", req.NamespacedName) + _ = r.Log.WithValues("deployment", req.NamespacedName) // your logic here @@ -54,9 +55,8 @@ func (r *PodReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { } // SetupWithManager sets up the controller with the Manager. -func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument - // For(). + For(&appsv1.Deployment{}). Complete(r) } diff --git a/testdata/project-v2-multigroup/controllers/apps/suite_test.go b/testdata/project-v2-multigroup/controllers/apps/suite_test.go index d4431b5013f..9c3d92e900b 100644 --- a/testdata/project-v2-multigroup/controllers/apps/suite_test.go +++ b/testdata/project-v2-multigroup/controllers/apps/suite_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -60,6 +61,9 @@ var _ = BeforeSuite(func(done Done) { Expect(err).ToNot(HaveOccurred()) Expect(cfg).ToNot(BeNil()) + err = appsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/testdata/project-v2-multigroup/go.mod b/testdata/project-v2-multigroup/go.mod index 2aeef58e0be..d0ffad76e06 100644 --- a/testdata/project-v2-multigroup/go.mod +++ b/testdata/project-v2-multigroup/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-logr/logr v0.1.0 github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.1 + k8s.io/api v0.18.6 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 sigs.k8s.io/controller-runtime v0.6.4 diff --git a/testdata/project-v2-multigroup/main.go b/testdata/project-v2-multigroup/main.go index 60aead1c253..9d8addc71ec 100644 --- a/testdata/project-v2-multigroup/main.go +++ b/testdata/project-v2-multigroup/main.go @@ -155,12 +155,12 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "HealthCheckPolicy") os.Exit(1) } - if err = (&appscontroller.PodReconciler{ + if err = (&appscontroller.DeploymentReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("Pod"), + Log: ctrl.Log.WithName("controllers").WithName("Deployment"), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Pod") + setupLog.Error(err, "unable to create controller", "controller", "Deployment") os.Exit(1) } //+kubebuilder:scaffold:builder diff --git a/testdata/project-v3-config/PROJECT b/testdata/project-v3-config/PROJECT index 144702d87c3..804ab898289 100644 --- a/testdata/project-v3-config/PROJECT +++ b/testdata/project-v3-config/PROJECT @@ -40,4 +40,9 @@ resources: webhooks: defaulting: true webhookVersion: v1 +- controller: true + domain: testproject.org + group: crew + kind: Laker + version: v1 version: "3" diff --git a/testdata/project-v3-multigroup/PROJECT b/testdata/project-v3-multigroup/PROJECT index 30a6a0ddfa7..4d23fb47636 100644 --- a/testdata/project-v3-multigroup/PROJECT +++ b/testdata/project-v3-multigroup/PROJECT @@ -78,6 +78,11 @@ resources: kind: HealthCheckPolicy path: sigs.k8s.io/kubebuilder/testdata/project-v3-multigroup/apis/foo.policy/v1 version: v1 +- controller: true + group: apps + kind: Deployment + path: k8s.io/api/apps/v1 + version: v1 - api: crdVersion: v1 namespaced: true diff --git a/testdata/project-v3-multigroup/config/rbac/role.yaml b/testdata/project-v3-multigroup/config/rbac/role.yaml index cd19ad8560a..5009fea150c 100644 --- a/testdata/project-v3-multigroup/config/rbac/role.yaml +++ b/testdata/project-v3-multigroup/config/rbac/role.yaml @@ -9,7 +9,7 @@ rules: - apiGroups: - apps resources: - - pods + - deployments verbs: - create - delete @@ -21,13 +21,13 @@ rules: - apiGroups: - apps resources: - - pods/finalizers + - deployments/finalizers verbs: - update - apiGroups: - apps resources: - - pods/status + - deployments/status verbs: - get - patch diff --git a/testdata/project-v3-multigroup/controllers/apps/pod_controller.go b/testdata/project-v3-multigroup/controllers/apps/deployment_controller.go similarity index 66% rename from testdata/project-v3-multigroup/controllers/apps/pod_controller.go rename to testdata/project-v3-multigroup/controllers/apps/deployment_controller.go index 8bfb4ed0dea..a18548c0177 100644 --- a/testdata/project-v3-multigroup/controllers/apps/pod_controller.go +++ b/testdata/project-v3-multigroup/controllers/apps/deployment_controller.go @@ -20,33 +20,34 @@ import ( "context" "github.com/go-logr/logr" + appsv1 "k8s.io/api/apps/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) -// PodReconciler reconciles a Pod object -type PodReconciler struct { +// DeploymentReconciler reconciles a Deployment object +type DeploymentReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme } -//+kubebuilder:rbac:groups=apps,resources=pods,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=apps,resources=pods/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=apps,resources=pods/finalizers,verbs=update +//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=apps,resources=deployments/finalizers,verbs=update // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by -// the Pod object against the actual cluster state, and then +// the Deployment object against the actual cluster state, and then // perform operations to make the cluster state reflect the state specified by // the user. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile -func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = r.Log.WithValues("pod", req.NamespacedName) +func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = r.Log.WithValues("deployment", req.NamespacedName) // your logic here @@ -54,9 +55,8 @@ func (r *PodReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.R } // SetupWithManager sets up the controller with the Manager. -func (r *PodReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument - // For(). + For(&appsv1.Deployment{}). Complete(r) } diff --git a/testdata/project-v3-multigroup/controllers/apps/suite_test.go b/testdata/project-v3-multigroup/controllers/apps/suite_test.go index 8dedd70d4ac..d0c244d0aed 100644 --- a/testdata/project-v3-multigroup/controllers/apps/suite_test.go +++ b/testdata/project-v3-multigroup/controllers/apps/suite_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + appsv1 "k8s.io/api/apps/v1" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" @@ -60,6 +61,9 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) + err = appsv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) diff --git a/testdata/project-v3-multigroup/main.go b/testdata/project-v3-multigroup/main.go index d79bb749908..5707341bd7f 100644 --- a/testdata/project-v3-multigroup/main.go +++ b/testdata/project-v3-multigroup/main.go @@ -169,12 +169,12 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "HealthCheckPolicy") os.Exit(1) } - if err = (&appscontrollers.PodReconciler{ + if err = (&appscontrollers.DeploymentReconciler{ Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("apps").WithName("Pod"), + Log: ctrl.Log.WithName("controllers").WithName("apps").WithName("Deployment"), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Pod") + setupLog.Error(err, "unable to create controller", "controller", "Deployment") os.Exit(1) } if err = (&controllers.LakersReconciler{ diff --git a/testdata/project-v3/PROJECT b/testdata/project-v3/PROJECT index b30361d9899..8dde5b76ffe 100644 --- a/testdata/project-v3/PROJECT +++ b/testdata/project-v3/PROJECT @@ -40,4 +40,9 @@ resources: webhooks: defaulting: true webhookVersion: v1 +- controller: true + domain: testproject.org + group: crew + kind: Laker + version: v1 version: "3" From bd00512d719d899d2e1d6fb8b959b601fd4ea5c7 Mon Sep 17 00:00:00 2001 From: Camila Macedo Date: Thu, 11 Feb 2021 19:09:27 +0000 Subject: [PATCH 29/38] :seeding: ignore maligned lint issue for WebhookSuite struct --- .../v3/scaffolds/internal/templates/api/webhook_suitetest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go index 1223f9b212a..f708a8afbb8 100644 --- a/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go +++ b/pkg/plugins/golang/v3/scaffolds/internal/templates/api/webhook_suitetest.go @@ -11,7 +11,7 @@ var _ file.Template = &WebhookSuite{} var _ file.Inserter = &WebhookSuite{} // WebhookSuite scaffolds the file that sets up the webhook tests -type WebhookSuite struct { +type WebhookSuite struct { //nolint:maligned file.TemplateMixin file.MultiGroupMixin file.BoilerplateMixin From 3948289fc8e4b897a3d2a30f9e346cbea1fb35eb Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Wed, 3 Feb 2021 15:44:21 +0100 Subject: [PATCH 30/38] Plugin phase 1.5 EP Signed-off-by: Adrian Orive --- ...e-cli-and-scaffolding-plugins-phase-1-5.md | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md diff --git a/designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md b/designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md new file mode 100644 index 00000000000..73f84ec9db5 --- /dev/null +++ b/designs/extensible-cli-and-scaffolding-plugins-phase-1-5.md @@ -0,0 +1,258 @@ +# Extensible CLI and Scaffolding Plugins - Phase 1.5 + +Continuation of [Extensible CLI and Scaffolding Plugins](./extensible-cli-and-scaffolding-plugins-phase-1.md). + +## Goal + +The goal of this phase is to achieve one of the goals proposed for Phase 2: chaining plugins. +Phase 2 includes several other challenging goals, but being able to chain plugins will be beneficial +for third-party developers that are using kubebuilder as a library. + +## Table of contents +- [Goal](#goal) +- [Motivation](#motivation) +- [Proposal](#proposal) +- [Implementation](#implementation) + +## Motivation + +There are several cases of plugins that want to maintain most of the go plugin functionality and add +certain features on top of it, both inside and outside kubebuilder repository: +- [Addon pattern](../plugins/addon) +- [Operator SDK](https://github.com/operator-framework/operator-sdk/tree/master/internal/plugins/golang) + +This behavior fits perfectly under Phase 1.5, where plugins could be chained. However, as this feature is +not available, the adopted temporal solution is to wrap the base go plugin and perform additional actions +after its `Run` method has been executed. This solution faces several issues: + +- Wrapper plugins are unable to access the data of the wrapped plugins, as they weren't designed for this + purpose, and therefore, most of its internal data is non-exported. An example of this inaccessible data + would be the `Resource` objects created inside the `create api` and `create webhook` commands. +- Wrapper plugins are dependent on their wrapped plugins, and therefore can't be used for other plugins. +- Under the hood, subcommands implement a second hidden interface: `RunOptions`, which further accentuates + these issues. + +Plugin chaining solves the aforementioned problems but the current plugin API, and more specifically the +`Subcommand` interface, does not support plugin chaining. + +- The `RunOptions` interface implemented under the hood is not part of the plugin API, and therefore + the cli is not able to run post-scaffold logic (implemented in `RunOptions.PostScaffold` method) after + all the plugins have scaffolded their part. +- `Resource`-related commands can't bind flags like `--group`, `--version` or `--kind` in each plugin, + it must be created outside the plugins and then injected into them similar to the approach followed + currently for `Config` objects. + +## Proposal + +Design a Plugin API that combines the current [`Subcommand`](../pkg/plugin/interfaces.go) and +[`RunOptions`](../pkg/plugins/internal/cmdutil/cmdutil.go) interfaces and enables plugin-chaining. +The new `Subcommand` methods can be split in two different categories: +- Initialization methods +- Execution methods + +Additionally, some of these methods may be optional, in which case a non-implemented method will be skipped +when it should be called and consider it succeeded. This also allows to create some methods specific for +a certain subcommand call (e.g.: `Resource`-related methods for the `edit` subcommand are not needed). + +Different ordering guarantees can be considered: +- Method order guarantee: a method for a plugin will be called after its previous methods succeeded. +- Steps order guarantee: methods will be called when all plugins have finished the previous method. +- Plugin order guarantee: same method for each plugin will be called in the order specified + by the plugin position at the plugin chain. + +All of the methods will offer plugin order guarantee, as they all modify/update some item so the order +of plugins is important. Execution methods need to guarantee step order, as the items that are being modified +in each step (config, resource, and filesystem) are also needed in the following steps. This is not true for +initialization methods that modify items (metadata and flagset) that are only used in their own methods, +so they only need to guarantee method order. + +Execution methods will be able to return an error. A specific error can be returned to specify that +no further methods of this plugin should be called, but that the scaffold process should be continued. +This enables plugins to exit early, e.g., a plugin that scaffolds some files only for cluster-scoped +resources can detect if the resource is cluster-scoped at one of the first execution steps, and +therefore, use this error to tell the CLI that no further execution step should be called for itself. + +### Initialization methods + +#### Update metadata +This method will be used for two purposes. It provides CLI-related metadata to the Subcommand (e.g., +command name) and update the subcommands metadata such as the description or examples. + +- Required/optional + - [ ] Required + - [x] Optional +- Subcommands + - [x] Init + - [x] Edit + - [x] Create API + - [x] Create webhook + +#### Bind flags +This method will allow subcommands to define specific flags. + +- Required/optional + - [ ] Required + - [x] Optional +- Subcommands + - [x] Init + - [x] Edit + - [x] Create API + - [x] Create webhook + +### Execution methods + +#### Inject configuration +This method will be used to inject the `Config` object that the plugin can modify at will. +The CLI will create/load/save this configuration object. + +- Required/optional + - [ ] Required + - [x] Optional +- Subcommands + - [x] Init + - [x] Edit + - [x] Create API + - [x] Create webhook + +#### Inject resource +This method will be used to inject the `Resource` object. + +- Required/optional + - [x] Required + - [ ] Optional +- Subcommands + - [ ] Init + - [ ] Edit + - [x] Create API + - [x] Create webhook + +#### Pre-scaffold +This method will be used to take actions before the main scaffolding is performed, e.g. validations. + +NOTE: a filesystem abstraction will be passed to this method that must be used for scaffolding. + +- Required/optional + - [ ] Required + - [x] Optional +- Subcommands + - [x] Init + - [x] Edit + - [x] Create API + - [x] Create webhook + +#### Scaffold +This method will be used to perform the main scaffolding. + +NOTE: a filesystem abstraction will be passed to this method that must be used for scaffolding. + +- Required/optional + - [x] Required + - [ ] Optional +- Subcommands + - [x] Init + - [x] Edit + - [x] Create API + - [x] Create webhook + +#### Post-scaffold +This method will be used to take actions after the main scaffolding is performed, e.g. cleanup. + +NOTE: a filesystem abstraction will **NOT** be passed to this method, as post-scaffold task do not require it. +In case some post-scaffold task requires a filesystem abstraction, it could be added. + +- Required/optional + - [ ] Required + - [x] Optional +- Subcommands + - [x] Init + - [x] Edit + - [x] Create API + - [x] Create webhook + +## Implementation + +The following types are used as input/output values of the described methods: +```go +// CLIMetadata is the runtime meta-data of the CLI +type CLIMetadata struct { + // CommandName is the root command name. + CommandName string +} + +// SubcommandMetadata is the runtime meta-data for a subcommand +type SubcommandMetadata struct { + // Description is a description of what this subcommand does. It is used to display help. + Description string + // Examples are one or more examples of the command-line usage of this subcommand. It is used to display help. + Examples string +} + +type ExitError struct { + Plugin string + Reason string +} + +func (e ExitError) Error() string { + return fmt.Sprintf("plugin %s exit early: %s", e.Plugin, e.Reason) +} +``` + +The described methods are implemented through the use of the following interfaces. +```go +type RequiresCLIMetadata interface { + InjectCLIMetadata(CLIMetadata) +} + +type UpdatesSubcommandMetadata interface { + UpdateSubcommandMetadata(*SubcommandMetadata) +} + +type HasFlags interface { + BindFlags(*pflag.FlagSet) +} + +type RequiresConfig interface { + InjectConfig(config.Config) error +} + +type RequiresResource interface { + InjectResource(*resource.Resource) error +} + +type HasPreScaffold interface { + PreScaffold(afero.Fs) error +} + +type Scaffolder interface { + Scaffold(afero.Fs) error +} + +type HasPostScaffold interface { + PostScaffold() error +} +``` + +Additional interfaces define the required method for each type of plugin: +```go +// InitSubcommand is the specific interface for subcommands returned by init plugins. +type InitSubcommand interface { + Scaffolder +} + +// EditSubcommand is the specific interface for subcommands returned by edit plugins. +type EditSubcommand interface { + Scaffolder +} + +// CreateAPISubcommand is the specific interface for subcommands returned by create API plugins. +type CreateAPISubcommand interface { + RequiresResource + Scaffolder +} + +// CreateWebhookSubcommand is the specific interface for subcommands returned by create webhook plugins. +type CreateWebhookSubcommand interface { + RequiresResource + Scaffolder +} +``` From 8c0816155d88c475cd6b703267b0e7840776a0a2 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Fri, 8 Jan 2021 14:08:17 +0100 Subject: [PATCH 31/38] Tests cleanup Signed-off-by: Adrian Orive --- Makefile | 41 ++-- common.sh | 204 ------------------ test.sh | 148 +------------ test/common.sh | 125 +++++++++++ scripts/setup.sh => test/e2e/ci.sh | 20 +- test/{ => e2e}/kind-config.yaml | 0 test_e2e_local.sh => test/e2e/local.sh | 40 +--- test/e2e/setup.sh | 49 +++++ test/integration.sh | 126 +++++++++++ check_testdata.sh => test/testdata/check.sh | 25 +-- .../testdata/generate.sh | 35 +-- test/testdata/test.sh | 44 ++++ test_e2e.sh | 27 +-- testdata/project-v2-addon/go.mod | 2 - testdata/project-v2-multigroup/go.mod | 2 - testdata/project-v2/go.mod | 2 - testdata/project-v3-addon/go.mod | 2 - testdata/project-v3-config/go.mod | 3 - testdata/project-v3-multigroup/go.mod | 2 - testdata/project-v3/go.mod | 3 - 20 files changed, 411 insertions(+), 489 deletions(-) delete mode 100644 common.sh create mode 100644 test/common.sh rename scripts/setup.sh => test/e2e/ci.sh (52%) rename test/{ => e2e}/kind-config.yaml (100%) rename test_e2e_local.sh => test/e2e/local.sh (53%) create mode 100755 test/e2e/setup.sh create mode 100755 test/integration.sh rename check_testdata.sh => test/testdata/check.sh (75%) rename generate_testdata.sh => test/testdata/generate.sh (91%) create mode 100755 test/testdata/test.sh diff --git a/Makefile b/Makefile index 575981e4213..9052d90a31a 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ generate: generate-testdata ## Update/generate all mock data. You should run thi .PHONY: generate-testdata generate-testdata: ## Update/generate the testdata in $GOPATH/src/sigs.k8s.io/kubebuilder - ./generate_testdata.sh + ./test/testdata/generate.sh .PHONY: lint lint: golangci-lint ## Run golangci-lint linter @@ -87,26 +87,35 @@ golangci-lint: ##@ Tests -.PHONY: go-test -go-test: ## Run the unit test - go test -race -v ./cmd/... ./pkg/... ./plugins/... - .PHONY: test -test: ## Run the unit tests (used in the CI) - ./test.sh +test: test-unit test-integration test-testdata ## Run the unit and integration tests (used in the CI) + +.PHONY: test-unit +test-unit: ## Run the unit tests + go test -race -v ./pkg/... .PHONY: test-coverage -test-coverage: ## Run coveralls - # remove all coverage files if exists - - rm -rf *.out - # run the go tests and gen the file coverage-all used to do the integration with coverrals.io +test-coverage: ## Run unit tests creating the output to report coverage + - rm -rf *.out # Remove all coverage files if exists go test -race -failfast -tags=integration -coverprofile=coverage-all.out ./cmd/... ./pkg/... ./plugins/... -.PHONY: test-e2e-local -test-e2e-local: ## It will run the script to install kind and run e2e tests - ## To keep the same kind cluster between test runs, use `SKIP_KIND_CLEANUP=1 make test-e2e-local` - ./test_e2e_local.sh +.PHONY: test-integration +test-integration: ## Run the integration tests + ./test/integration.sh .PHONY: check-testdata check-testdata: ## Run the script to ensure that the testdata is updated - ./check_testdata.sh + ./test/testdata/check.sh + +.PHONY: test-testdata +test-testdata: ## Run the tests of the testdata directory + ./test/testdata/test.sh + +.PHONY: test-e2e-local +test-e2e-local: ## Run the end-to-end tests locally + ## To keep the same kind cluster between test runs, use `SKIP_KIND_CLEANUP=1 make test-e2e-local` + ./test/e2e/local.sh + +.PHONY: test-e2e-ci +test-e2e-ci: ## Run the end-to-end tests (used in the CI)` + ./test/e2e/ci.sh diff --git a/common.sh b/common.sh deleted file mode 100644 index 9340312eaa0..00000000000 --- a/common.sh +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2018 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. - -set -o errexit -set -o nounset -set -o pipefail - -# check if modules are enabled -MODULES_ENABLED="" -if go mod edit -json &>/dev/null ; then - MODULES_ENABLED="1" -fi - -MOD_OPT="" -MODULES_OPT=${MODULES_OPT:-""} -if [[ -n "${MODULES_OPT}" && -n "${MODULES_ENABLED}" ]]; then - MOD_OPT="-mod=${MODULES_OPT}" -fi - - -# Enable tracing in this script off by setting the TRACE variable in your -# environment to any value: -# -# $ TRACE=1 test.sh -TRACE=${TRACE:-""} -if [ -n "$TRACE" ]; then - set -x -fi - -# By setting INJECT_KB_VERSION variable in your environment, KB will be compiled -# with this version. This is to assist testing functionality which depends on -# version .e.g gopkg.toml generation. -# -# $ INJECT_KB_VERSION=0.1.7 test.sh -INJECT_KB_VERSION=${INJECT_KB_VERSION:-unknown} - -# Make sure, we run in the root of the repo and -# therefore run the tests on all packages -base_dir="$( cd "$(dirname "$0")/" && pwd )" -cd "$base_dir" || { - echo "Cannot cd to '$base_dir'. Aborting." >&2 - exit 1 -} - -go_workspace='' -export GOPATH=${GOPATH:-$(go env GOPATH)} -for p in ${GOPATH//:/ }; do - if [[ $PWD/ = $p/* ]]; then - go_workspace=$p - fi -done - -k8s_version=1.16.4 -goarch=amd64 -goos="unknown" - -if [[ "$OSTYPE" == "linux-gnu" ]]; then - goos="linux" -elif [[ "$OSTYPE" == "darwin"* ]]; then - goos="darwin" -fi - -if [[ "$goos" == "unknown" ]]; then - echo "OS '$OSTYPE' not supported. Aborting." >&2 - exit 1 -fi - -# Turn colors in this script off by setting the NO_COLOR variable in your -# environment to any value: -# -# $ NO_COLOR=1 test.sh -NO_COLOR=${NO_COLOR:-""} -if [ -z "$NO_COLOR" ]; then - header=$'\e[1;33m' - reset=$'\e[0m' -else - header='' - reset='' -fi - -function header_text { - echo "$header$*$reset" -} - -rc=0 -tmp_root=/tmp - -kb_root_dir=$tmp_root/kubebuilder -kb_orig=$(pwd) - -# Skip fetching and untaring the tools by setting the SKIP_FETCH_TOOLS variable -# in your environment to any value: -# -# $ SKIP_FETCH_TOOLS=1 ./test.sh -# -# If you skip fetching tools, this script will use the tools already on your -# machine, but rebuild the kubebuilder and kubebuilder-bin binaries. -SKIP_FETCH_TOOLS=${SKIP_FETCH_TOOLS:-""} - -function prepare_staging_dir { - header_text "preparing staging dir" - - if [ -z "$SKIP_FETCH_TOOLS" ]; then - rm -rf "$kb_root_dir" - else - rm -f "$kb_root_dir/bin/kubebuilder" - fi -} - -# fetch k8s API gen tools and make it available under kb_root_dir/bin. -function fetch_tools { - if [ -z "$SKIP_FETCH_TOOLS" ]; then - fetch_kb_tools - fi -} - -function fetch_kb_tools { - header_text "fetching kb tools" - kb_tools_archive_name="kubebuilder-tools-$k8s_version-$goos-$goarch.tar.gz" - kb_tools_download_url="https://storage.googleapis.com/kubebuilder-tools/$kb_tools_archive_name" - - kb_tools_archive_path="$tmp_root/$kb_tools_archive_name" - if [ ! -f $kb_tools_archive_path ]; then - curl -sL ${kb_tools_download_url} -o "$kb_tools_archive_path" - fi - tar -zvxf "$kb_tools_archive_path" -C "$tmp_root/" -} - -function build_kb { - header_text "building kubebuilder" - - if [ "$INJECT_KB_VERSION" = "unknown" ]; then - opts="" - else - # Injects the version into the cmd/version.go file - opts=-ldflags "-X sigs.k8s.io/kubebuilder/v3/cmd.kubeBuilderVersion=$INJECT_KB_VERSION" - fi - - GO111MODULE=on go build $opts -o $kb_root_dir/bin/kubebuilder ./cmd -} - -function install_kind { - header_text "Checking for kind" - if ! is_installed kind ; then - header_text "Installing kind" - KIND_DIR=$(mktemp -d) - pushd $KIND_DIR - GO111MODULE=on go get sigs.k8s.io/kind@v0.7.0 - popd - fi -} - -function is_installed { - if command -v $1 &>/dev/null; then - return 0 - fi - return 1 -} - -function prepare_testdir_under_gopath { - kb_test_dir=$kb_root_dir/test - header_text "preparing test directory $kb_test_dir" - rm -rf "$kb_test_dir" && mkdir -p "$kb_test_dir" && cd "$kb_test_dir" - header_text "running kubebuilder commands in test directory $kb_test_dir" -} - -function setup_envs { - header_text "setting up env vars" - - # Setup env vars - export PATH=$kb_root_dir/bin:$PATH - export TEST_ASSET_KUBECTL=$kb_root_dir/bin/kubectl - export TEST_ASSET_KUBE_APISERVER=$kb_root_dir/bin/kube-apiserver - export TEST_ASSET_ETCD=$kb_root_dir/bin/etcd - export TEST_DEP=$kb_root_dir/init_project -} - -function cache_project { - header_text "caching initialized projects" - if [ -d "$TEST_DEP" ]; then - rm -rf "$TEST_DEP" - fi - mkdir -p "$TEST_DEP" - cp -r $PWD/* $TEST_DEP -} - -function dump_project { - header_text "restoring cached project" - if [ -d "$TEST_DEP" ]; then - cp -r $TEST_DEP/* . - fi -} diff --git a/test.sh b/test.sh index f776963509e..3f17ef45342 100755 --- a/test.sh +++ b/test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,148 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -o errexit -set -o nounset -set -o pipefail - -source common.sh - -export TRACE=1 -export GO111MODULE=on - -function test_init_project { - header_text "performing init project" - go mod init kubebuilder.io/test - kubebuilder init --domain example.com <<< "n" -} - -function test_make_project { - header_text "running make in project" - make -} - -function test_create_api_controller { - header_text "performing creating api and controller" - kubebuilder create api --group insect --version v1beta1 --kind Bee --namespaced false <&2 + exit 1 +fi + +# Turn colors in this script off by setting the NO_COLOR variable in your +# environment to any value: +# +# $ NO_COLOR=1 test.sh +NO_COLOR=${NO_COLOR:-""} +if [ -z "$NO_COLOR" ]; then + header=$'\e[1;33m' + reset=$'\e[0m' +else + header='' + reset='' +fi + +function header_text { + echo "$header$*$reset" +} + +tmp_root=/tmp +kb_root_dir=$tmp_root/kubebuilder + +# Skip fetching and untaring the tools by setting the SKIP_FETCH_TOOLS variable +# in your environment to any value: +# +# $ SKIP_FETCH_TOOLS=1 ./test.sh +# +# If you skip fetching tools, this script will use the tools already on your +# machine, but rebuild the kubebuilder and kubebuilder-bin binaries. +SKIP_FETCH_TOOLS=${SKIP_FETCH_TOOLS:-""} + +# Remove previously built binary and fetched tools if they need to be fetched again +function prepare_staging_dir { + header_text "Preparing staging dir" + + if [ -z "$SKIP_FETCH_TOOLS" ]; then + rm -rf "$kb_root_dir" + else + rm -f "$kb_root_dir/bin/kubebuilder" + fi +} + +# Build kubebuilder +function build_kb { + header_text "Building kubebuilder" + go build -o $kb_root_dir/bin/kubebuilder ./cmd + kb=$kb_root_dir/bin/kubebuilder +} + +# Fetch k8s API gen tools and make it available under kb_root_dir/bin. +function fetch_tools { + if [ -z "$SKIP_FETCH_TOOLS" ]; then + header_text "Fetching kb tools" + kb_tools_archive_name="kubebuilder-tools-$k8s_version-$goos-$goarch.tar.gz" + kb_tools_download_url="https://storage.googleapis.com/kubebuilder-tools/$kb_tools_archive_name" + + kb_tools_archive_path="$tmp_root/$kb_tools_archive_name" + if [ ! -f $kb_tools_archive_path ]; then + curl -sL ${kb_tools_download_url} -o "$kb_tools_archive_path" + fi + tar -zvxf "$kb_tools_archive_path" -C "$tmp_root/" + fi + + export KUBEBUILDER_ASSETS=$kb_root_dir/bin/ +} + +# Installing kind in a temporal dir if no previously installed +function install_kind { + header_text "Checking if kind is installed" + if ! is_installed kind ; then + header_text "Kind not found, installing kind" + pushd $(mktemp -d) + GO111MODULE=on go get sigs.k8s.io/kind@v0.7.0 + popd + fi +} + +# Check if a program is previously installed +function is_installed { + if command -v $1 &>/dev/null; then + return 0 + fi + return 1 +} diff --git a/scripts/setup.sh b/test/e2e/ci.sh similarity index 52% rename from scripts/setup.sh rename to test/e2e/ci.sh index 9878fc51614..e09ae41b8e0 100755 --- a/scripts/setup.sh +++ b/test/e2e/ci.sh @@ -1,6 +1,6 @@ -#!/bin/sh +#!/usr/bin/env bash -# Copyright 2019 The Kubernetes Authors. +# Copyright 2018 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. @@ -14,15 +14,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -K8S_VERSION=$1 -KIND_NAME="kind" -if [ $# -gt 1 ]; then - KIND_NAME=$2 -fi +source "$(dirname "$0")/../common.sh" +source "$(dirname "$0")/setup.sh" -export GO111MODULE=on +kind_cluster="kind" +create_cluster ${KIND_K8S_VERSION} $kind_cluster +trap delete_cluster EXIT -# setup go module to create the cluster - -# You can use --image flag to specify the cluster version you want, e.g --image=kindest/node:v1.13.6, the supported version are listed at https://hub.docker.com/r/kindest/node/tags -kind create cluster -v 4 --name $KIND_NAME --retain --wait=1m --config test/kind-config.yaml --image=kindest/node:$K8S_VERSION +test_cluster diff --git a/test/kind-config.yaml b/test/e2e/kind-config.yaml similarity index 100% rename from test/kind-config.yaml rename to test/e2e/kind-config.yaml diff --git a/test_e2e_local.sh b/test/e2e/local.sh similarity index 53% rename from test_e2e_local.sh rename to test/e2e/local.sh index 90527a2ebf7..0d0d82e0249 100755 --- a/test_e2e_local.sh +++ b/test/e2e/local.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,39 +14,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -o errexit -set -o nounset -set -o pipefail - -source common.sh - -export TRACE=1 -export GO111MODULE=on -export KIND_K8S_VERSION=${KIND_K8S_VERSION:-v1.18.0} - -fetch_tools -install_kind -build_kb - -setup_envs +source "$(dirname "$0")/../common.sh" +source "$(dirname "$0")/setup.sh" -export KIND_CLUSTER=local-kubebuilder-e2e -if ! kind get clusters | grep -q $KIND_CLUSTER ; then - source "$(pwd)/scripts/setup.sh" ${KIND_K8S_VERSION} $KIND_CLUSTER +kind_cluster=local-kubebuilder-e2e +create_cluster ${KIND_K8S_VERSION:-v1.18.0} $kind_cluster +if [ -z "${SKIP_KIND_CLEANUP:-}" ]; then + trap delete_cluster EXIT fi -kind export kubeconfig --kubeconfig $tmp_root/kubeconfig --name $KIND_CLUSTER +kind export kubeconfig --kubeconfig $tmp_root/kubeconfig --name $kind_cluster export KUBECONFIG=$tmp_root/kubeconfig -# remove running containers on exit -function cleanup() { - kind delete cluster --name $KIND_CLUSTER -} - -if [ -z "${SKIP_KIND_CLEANUP:-}" ]; then - trap cleanup EXIT -fi - -# when changing these commands, make sure to keep in sync with ./test_e2e.sh -go test ./test/e2e/v2 -v -ginkgo.v -go test ./test/e2e/v3 -v -ginkgo.v -timeout 20m +test_cluster -v -ginkgo.v diff --git a/test/e2e/setup.sh b/test/e2e/setup.sh new file mode 100755 index 00000000000..e0146ebe73f --- /dev/null +++ b/test/e2e/setup.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +# Copyright 2019 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. + +build_kb +export PATH=$kb_root_dir/bin:$PATH +fetch_tools +install_kind + +# Creates a kind cluster given a k8s version and a cluster name. +# +# Usage: +# +# create_cluster +function create_cluster { + if ! kind get clusters | grep -q $2 ; then + kind create cluster -v 4 --name $2 --retain --wait=1m --config $(dirname "$0")/kind-config.yaml --image=kindest/node:$1 + fi +} + +# Deletes a kind cluster by cluster name. The kind cluster needs to be defined as a variable instead of an argument +# so that this function can be used with `trap` +# +# Usage: +# +# kind_cluster= +# delete_cluster +function delete_cluster { + kind delete cluster --name $kind_cluster +} + +function test_cluster { + local flags="$@" + + go test $(dirname "$0")/v2 $flags + go test $(dirname "$0")/v3 $flags -timeout 20m +} diff --git a/test/integration.sh b/test/integration.sh new file mode 100755 index 00000000000..8f987de4c98 --- /dev/null +++ b/test/integration.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash + +# Copyright 2018 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. + +source $(dirname "$0")/common.sh + +header_text "Running kubebuilder integration tests" + +build_kb +pushd . + +kb_test_dir=$kb_root_dir/test +kb_test_cache_dir=$kb_root_dir/cache + +function prepare_test_dir { + header_text "Preparing test directory $kb_test_dir" + rm -rf "$kb_test_dir" && mkdir -p "$kb_test_dir" && cd "$kb_test_dir" + header_text "Running kubebuilder commands in test directory $kb_test_dir" +} + +function cache_project { + header_text "Caching project '$1'" + if [ -d "$kb_test_cache_dir/$1" ]; then + rm -rf "$kb_test_cache_dir/$1" + fi + mkdir -p "$kb_test_cache_dir/$1" + cp -r $PWD/* $kb_test_cache_dir/$1 +} + +function dump_project { + header_text "Restoring cached project '$1'" + if [ -d "$kb_test_cache_dir/$1" ]; then + cp -r $kb_test_cache_dir/$1/* . + fi +} + + +function test_init_project { + header_text "Init project" + go mod init kubebuilder.io/test + $kb init --domain example.com <<< "n" +} + +function test_make_project { + header_text "Running make" + make +} + +function test_create_api_controller { + header_text "Creating api and controller" + $kb create api --group insect --version v1beta1 --kind Bee --namespaced false < -# -scaffold_test_project() { +function scaffold_test_project { local project=$1 shift local init_flags="$@" - local testdata_dir=$(pwd)/testdata - mkdir -p ./testdata/$project - rm -rf ./testdata/$project/* - pushd . - cd testdata/$project - local kb=$testdata_dir/../bin/kubebuilder + local testdata_dir="$(dirname "$0")/../../testdata" + mkdir -p $testdata_dir/$project + rm -rf $testdata_dir/$project/* + pushd $testdata_dir/$project # Remove tool binaries for projects of version 2, which don't have locally-configured binaries, - # so the correct versions are used. + # so the correct versions are used. Also, webhooks in version 2 don't have --make flag if [[ $init_flags =~ --project-version=2 ]]; then rm -f "$(command -v controller-gen)" rm -f "$(command -v kustomize)" @@ -108,24 +99,20 @@ scaffold_test_project() { header_text 'enabling --pattern flag ...' export KUBEBUILDER_ENABLE_PLUGINS=1 header_text 'Creating APIs ...' - $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --pattern=addon + $kb create api --group crew --version v1 --kind Captain --controller=true --resource=true --make=false --pattern=addon $kb create api --group crew --version v1 --kind FirstMate --controller=true --resource=true --make=false --pattern=addon $kb create api --group crew --version v1 --kind Admiral --controller=true --resource=true --namespaced=false --make=false --pattern=addon unset KUBEBUILDER_ENABLE_PLUGINS fi - make all test + make generate manifests rm -f go.sum - rm -rf ./bin ./testbin + popd } -set -e - -export GO111MODULE=on -export PATH="$PATH:$(go env GOPATH)/bin" - build_kb + # Project version 2 uses plugin go/v2 (default). scaffold_test_project project-v2 --project-version=2 scaffold_test_project project-v2-multigroup --project-version=2 diff --git a/test/testdata/test.sh b/test/testdata/test.sh new file mode 100755 index 00000000000..aeee4057ce7 --- /dev/null +++ b/test/testdata/test.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# Copyright 2018 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. + +source "$(dirname "$0")/../common.sh" + +export KUBEBUILDER_ASSETS=$kb_root_dir/bin/ + +# Executes the test of the testdata directories +function test_project { + rm -f "$(command -v controller-gen)" + rm -f "$(command -v kustomize)" + + header_text "Performing tests in dir $1" + pushd "$(dirname "$0")/../../testdata/$1" + make test + popd +} + +prepare_staging_dir +fetch_tools + +# Test project v2 +test_project project-v2 +test_project project-v2-multigroup +test_project project-v2-addon + +# Test project v3 +test_project project-v3 +test_project project-v3-multigroup +test_project project-v3-addon +test_project project-v3-config diff --git a/test_e2e.sh b/test_e2e.sh index 51993c1037a..62564499911 100755 --- a/test_e2e.sh +++ b/test_e2e.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + # Copyright 2018 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -13,28 +14,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -o errexit -set -o nounset -set -o pipefail - -source common.sh - -export TRACE=1 -export GO111MODULE=on - -fetch_tools -install_kind -build_kb - -setup_envs - -source "$(pwd)/scripts/setup.sh" ${KIND_K8S_VERSION} - -# remove running containers on exit -function cleanup() { - kind delete cluster -} - -trap cleanup EXIT -go test ./test/e2e/v2 -go test ./test/e2e/v3 -timeout 20m +./test/e2e/ci.sh diff --git a/testdata/project-v2-addon/go.mod b/testdata/project-v2-addon/go.mod index 158ead9deb9..c853a92bb0f 100644 --- a/testdata/project-v2-addon/go.mod +++ b/testdata/project-v2-addon/go.mod @@ -4,8 +4,6 @@ go 1.13 require ( github.com/go-logr/logr v0.1.0 - github.com/onsi/ginkgo v1.12.1 - github.com/onsi/gomega v1.10.1 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 sigs.k8s.io/controller-runtime v0.6.4 diff --git a/testdata/project-v2-multigroup/go.mod b/testdata/project-v2-multigroup/go.mod index d0ffad76e06..48e0bcb882c 100644 --- a/testdata/project-v2-multigroup/go.mod +++ b/testdata/project-v2-multigroup/go.mod @@ -4,8 +4,6 @@ go 1.13 require ( github.com/go-logr/logr v0.1.0 - github.com/onsi/ginkgo v1.12.1 - github.com/onsi/gomega v1.10.1 k8s.io/api v0.18.6 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 diff --git a/testdata/project-v2/go.mod b/testdata/project-v2/go.mod index d12efdcca14..57b94c83092 100644 --- a/testdata/project-v2/go.mod +++ b/testdata/project-v2/go.mod @@ -4,8 +4,6 @@ go 1.13 require ( github.com/go-logr/logr v0.1.0 - github.com/onsi/ginkgo v1.12.1 - github.com/onsi/gomega v1.10.1 k8s.io/apimachinery v0.18.6 k8s.io/client-go v0.18.6 sigs.k8s.io/controller-runtime v0.6.4 diff --git a/testdata/project-v3-addon/go.mod b/testdata/project-v3-addon/go.mod index e64120517df..9419f0ce644 100644 --- a/testdata/project-v3-addon/go.mod +++ b/testdata/project-v3-addon/go.mod @@ -4,8 +4,6 @@ go 1.15 require ( github.com/go-logr/logr v0.3.0 - github.com/onsi/ginkgo v1.14.1 - github.com/onsi/gomega v1.10.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 sigs.k8s.io/controller-runtime v0.7.0 diff --git a/testdata/project-v3-config/go.mod b/testdata/project-v3-config/go.mod index f3957e4e5a8..17104c35fcb 100644 --- a/testdata/project-v3-config/go.mod +++ b/testdata/project-v3-config/go.mod @@ -4,9 +4,6 @@ go 1.15 require ( github.com/go-logr/logr v0.3.0 - github.com/onsi/ginkgo v1.14.1 - github.com/onsi/gomega v1.10.2 - k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 sigs.k8s.io/controller-runtime v0.7.0 diff --git a/testdata/project-v3-multigroup/go.mod b/testdata/project-v3-multigroup/go.mod index 20900109e2c..e5604171902 100644 --- a/testdata/project-v3-multigroup/go.mod +++ b/testdata/project-v3-multigroup/go.mod @@ -4,8 +4,6 @@ go 1.15 require ( github.com/go-logr/logr v0.3.0 - github.com/onsi/ginkgo v1.14.1 - github.com/onsi/gomega v1.10.2 k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 diff --git a/testdata/project-v3/go.mod b/testdata/project-v3/go.mod index 3a6c1779c1d..85b8c51e260 100644 --- a/testdata/project-v3/go.mod +++ b/testdata/project-v3/go.mod @@ -4,9 +4,6 @@ go 1.15 require ( github.com/go-logr/logr v0.3.0 - github.com/onsi/ginkgo v1.14.1 - github.com/onsi/gomega v1.10.2 - k8s.io/api v0.19.2 k8s.io/apimachinery v0.19.2 k8s.io/client-go v0.19.2 sigs.k8s.io/controller-runtime v0.7.0 From ab3bb7f3f1e8f905c9242632c403741061e159c5 Mon Sep 17 00:00:00 2001 From: Camila Macedo Date: Thu, 17 Dec 2020 16:53:51 +0000 Subject: [PATCH 32/38] :book: uprade migration guide for v3 --- docs/book/src/SUMMARY.md | 10 +- .../manually_migration_guide_v2_v3.md | 646 ++++++++++++++++++ .../src/migration/migration_guide_v2tov3.md | 184 +++++ docs/book/src/migration/plugin/plugins.md | 9 - docs/book/src/migration/plugin/v2_v3.md | 3 - docs/book/src/migration/project/projects.md | 4 - docs/book/src/migration/project/v2_v3.md | 212 ------ docs/book/src/migration/v2vsv3.md | 85 ++- 8 files changed, 908 insertions(+), 245 deletions(-) create mode 100644 docs/book/src/migration/manually_migration_guide_v2_v3.md create mode 100644 docs/book/src/migration/migration_guide_v2tov3.md delete mode 100644 docs/book/src/migration/plugin/plugins.md delete mode 100644 docs/book/src/migration/plugin/v2_v3.md delete mode 100644 docs/book/src/migration/project/projects.md delete mode 100644 docs/book/src/migration/project/v2_v3.md diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index bc77fb92ef2..7bbc78fa1b7 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -62,14 +62,8 @@ - [Migration Guide](./migration/legacy/migration_guide_v1tov2.md) - [Kubebuilder v2 vs v3](./migration/v2vsv3.md) - - - [Project Version Migration Guide](./migration/project/projects.md) - - - [v2 to v3](./migration/project/v2_v3.md) - - - [Plugin Version Migration Guide](./migration/plugin/plugins.md) - - - [v2 to v3](./migration/plugin/v2_v3.md) + - [Migration Guide](./migration/migration_guide_v2tov3.md) + - [Migration by updating the files](./migration/manually_migration_guide_v2_v3.md) - [Single Group to Multi-Group](./migration/multi-group.md) diff --git a/docs/book/src/migration/manually_migration_guide_v2_v3.md b/docs/book/src/migration/manually_migration_guide_v2_v3.md new file mode 100644 index 00000000000..85c2892a088 --- /dev/null +++ b/docs/book/src/migration/manually_migration_guide_v2_v3.md @@ -0,0 +1,646 @@ +# Migration from v2 to v3 by updating the files manually + +Make sure you understand the [differences between Kubebuilder v2 and v3][migration-v2vsv3] +before continuing + +Please ensure you have followed the [installation guide](/quick-start.md#installation) +to install the required components. + +The following guide describes the manual steps required to upgrade your config version and start using the plugin-enabled version. + +This way is more complex, susceptible to errors, and success cannot be assured. Also, by following these steps you will not get the improvements and bug fixes in the default generated project files. + +Usually you will only try to do it manually if you customized your project and deviated too much from the proposed scaffold. Before continuing, ensure that you understand the note about [project customizations][project-customizations]. Note that you might need to spend more effort to do this process manually than organize your project customizations to follow up the proposed layout and keep your project maintainable and upgradable with less effort in the future. + +The recommended upgrade approach is to follow the [Migration Guide v2 to V3][migration-guide-v2-to-v3] instead. + +## Migration from project config version "2" to "3" + +Migrating between project configuration versions involves additions, removals, and/or changes +to fields in your project's `PROJECT` file, which is created by running the `init` command. + +The `PROJECT` file now has a new layout. It stores more information about what resources are in use, to better enable plugins to make useful decisions when scaffolding. + +Furthermore, the `PROJECT` file itself is now versioned. The `version` field corresponds to the version of the `PROJECT` file itself, while the `layout` field indicates the scaffolding and the primary plugin version in use. + +### Steps to migrate + +The following steps describe the manual changes required to bring the project configuration file (`PROJECT`). These change will add the information that Kubebuilder would add when generating the file. This file can be found in the root directory. + +#### Add the `projectName` + +The project name is the name of the project directory in lowercase: + +```yaml +... +projectName: example +... +``` + +#### Add the `layout` + +The default plugin layout which is equivalent to the previous version is `go.kubebuilder.io/v2`: + +```yaml +... +layout: go.kubebuilder.io/v2 +... +``` + +#### Update the `version` + +The `version` field represents the version of project's layout. Update this to `"3"`: + +```yaml +... +version: "3" +... +``` + +#### Add the resource data + +The attribute `resources` represents the list of resources scaffolded in your project. + +You will need to add the following data for each resource added to the project. + +##### Add the Kubernetes API version by adding `resources[entry].api.crdVersion: v1beta1`: + +```yaml +... +resources: +- api: + ... + crdVersion: v1beta1 + domain: my.domain + group: webapp + kind: Guestbook + ... +``` + +##### Add the scope used do scaffold the CRDs by adding `resources[entry].api.namespaced: true` unless they were cluster-scoped: + +```yaml +... +resources: +- api: + ... + namespaced: true + group: webapp + kind: Guestbook + ... +``` + +##### If you have a controller scaffolded for the API then, add `resources[entry].controller: true`: + +```yaml +... +resources: +- api: + ... + controller: true + group: webapp + kind: Guestbook +``` + +##### Add the resource domain such as `resources[entry].domain: testproject.org` which usually will be the project domain unless the API scaffold is a core type and/or an external type: + +```yaml +... +resources: +- api: + ... + domain: testproject.org + group: webapp + kind: Guestbook +``` + + + +Note that you will only need to add the `domain` if your project has a scaffold for a core type API which the `Domain` value is not empty in Kubernetes API group qualified scheme definition. (For example, see [here](https://github.com/kubernetes/api/blob/v0.19.7/apps/v1/register.go#L26) that for Kinds from the API `apps` it has not a domain when see [here](https://github.com/kubernetes/api/blob/v0.19.7/authentication/v1/register.go#L26) that for Kinds from the API `authentication` its domain is `k8s.io` ) + + Check the following the list to know the core types supported and its domain: + +| Core Type | Domain | +|----------|:-------------:| +| admission | "k8s.io" | +| admissionregistration | "k8s.io" | +| apps | empty | +| auditregistration | "k8s.io" | +| apiextensions | "k8s.io" | +| authentication | "k8s.io" | +| authorization | "k8s.io" | +| autoscaling | empty | +| batch | empty | +| certificates | "k8s.io" | +| coordination | "k8s.io" | +| core | empty | +| events | "k8s.io" | +| extensions | empty | +| imagepolicy | "k8s.io" | +| networking | "k8s.io" | +| node | "k8s.io" | +| metrics | "k8s.io" | +| policy | empty | +| rbac.authorization | "k8s.io" | +| scheduling | "k8s.io" | +| setting | "k8s.io" | +| storage | "k8s.io" | + +Following an example where a controller was scaffold for the core type Kind Deployment via the command `create api --group apps --version v1 --kind Deployment --controller=true --resource=false --make=false`: + +```yaml +- controller: true + group: apps + kind: Deployment + path: k8s.io/api/apps/v1 + version: v1 +``` + +##### Add the `resources[entry].path` with the import path for the api: + + + +```yaml +... +resources: +- api: + ... + ... + group: webapp + kind: Guestbook + path: example/api/v1 +``` + +##### If your project is using webhooks then, add `resources[entry].webhooks.[type]: true` for each type generated and then, add `resources[entry].webhooks.webhookVersion: v1beta1`: + + + +```yaml +resources: +- api: + ... + ... + group: webapp + kind: Guestbook + webhooks: + defaulting: true + validation: true + webhookVersion: v1beta1 +``` + +#### Check your PROJECT file + +Now ensure that your `PROJECT` file has the same information when the manifests are generated via Kubebuilder V3 CLI. + +For the QuickStart example, the `PROJECT` file manually updated to use `go.kubebuilder.io/v2` would look like: + +```yaml +domain: my.domain +layout: go.kubebuilder.io/v2 +projectName: example +repo: example +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: my.domain + group: webapp + kind: Guestbook + path: example/api/v1 + version: v1 +version: "3" +``` + +You can check the differences between the previous layout(`version 2`) and the current format(`version 3`) with the `go.kubebuilder.io/v2` by comparing an example scenario which involves more than one API and webhook, see: + +**Example (Project version 2)** + +```yaml +domain: testproject.org +repo: sigs.k8s.io/kubebuilder/example +resources: +- group: crew + kind: Captain + version: v1 +- group: crew + kind: FirstMate + version: v1 +- group: crew + kind: Admiral + version: v1 +version: "2" +``` + +**Example (Project version 3)** + +```yaml +domain: testproject.org +layout: go.kubebuilder.io/v2 +projectName: example +repo: sigs.k8s.io/kubebuilder/example +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org + group: crew + kind: Captain + path: example/api/v1 + version: v1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: testproject.org + group: crew + kind: FirstMate + path: example/api/v1 + version: v1 + webhooks: + conversion: true + webhookVersion: v1 +- api: + crdVersion: v1 + controller: true + domain: testproject.org + group: crew + kind: Admiral + path: example/api/v1 + plural: admirales + version: v1 + webhooks: + defaulting: true + webhookVersion: v1 +version: "3" +``` + +### Verification + +In the steps above, you updated only the `PROJECT` file which represents the project configuration. This configuration is useful only for the CLI tool. It should not affect how your project behaves. + +There is no option to verify that you properly updated the configuration file. The best way to ensure the configuration file has the correct `V3+` fields is to initialize a project with the same API(s), controller(s), and webhook(s) in order to compare generated configuration with the manually changed configuration. + +If you made mistakes in the above process, you will likely face issues using the CLI. + + +## Update your project to use go/v3 plugin + +Migrating between project [plugins][plugins-doc] involves additions, removals, and/or changes +to files created by any plugin-supported command, e.g. `init` and `create`. A plugin supports +one or more project config versions; make sure you upgrade your project's +config version to the latest supported by your target plugin version before upgrading plugin versions. + +The following steps describe the manual changes required to modify the project's layout enabling your project to use the `go/v3` plugin. These steps will not help you address all the bug fixes of the already generated scaffolds. + + + +### Steps to migrate + +#### Update your plugin version into the PROJECT file + +Before updating the `layout`, please ensure you have followed the above steps to upgrade your Project version to `3`. Once you have upgraded the project version, update the `layout` to the new plugin version ` go.kubebuilder.io/v3` as follows: + +```yaml +domain: my.domain +layout: go.kubebuilder.io/v3 +... +``` + +#### Upgrade the Go version and its dependencies: + +Ensure that your `go.mod` is using Go version `1.15` and the following dependency versions: + +```go +module example + +go 1.15 + +require ( + github.com/go-logr/logr v0.3.0 + github.com/onsi/ginkgo v1.14.1 + github.com/onsi/gomega v1.10.2 + k8s.io/apimachinery v0.19.2 + k8s.io/client-go v0.19.2 + sigs.k8s.io/controller-runtime v0.7.0 +) +``` + +#### Update the golang image + +In the Dockerfile, replace: + +``` +# Build the manager binary +FROM golang:1.13 as builder +``` + +With: +``` +# Build the manager binary +FROM golang:1.15 as builder +``` + +#### Update your Makefile + +##### To allow controller-gen to scaffold the nw Kubernetes APIs + +To allow `controller-gen` and the scaffolding tool to use the new API versions, replace: + +``` +CRD_OPTIONS ?= "crd:trivialVersions=true" +``` + +With: + +``` +CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false" +``` + +##### To allow automatic downloads + +To allow downloading the newer versions of the Kubernetes binaries required by Envtest into the `testbin/` directory of your project instead of the global setup, replace: + +``` +# Run tests +test: generate fmt vet manifests + go test ./... -coverprofile cover.out +``` + +With: + +``` +ENVTEST_ASSETS_DIR=$(shell pwd)/testbin +test: manifests generate fmt vet ## Run tests. + mkdir -p ${ENVTEST_ASSETS_DIR} + test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh + source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out +``` + + + +##### To upgrade `controller-gen` and `kustomize` dependencies versions used + +To upgrade the `controller-gen` and `kustomize` version used to generate the manifests replace: + +``` +# find or download controller-gen +# download controller-gen if necessary +controller-gen: +ifeq (, $(shell which controller-gen)) + @{ \ + set -e ;\ + CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ + cd $$CONTROLLER_GEN_TMP_DIR ;\ + go mod init tmp ;\ + go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 ;\ + rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ + } +CONTROLLER_GEN=$(GOBIN)/controller-gen +else +CONTROLLER_GEN=$(shell which controller-gen) +endif +``` + +With: + +``` +CONTROLLER_GEN = $(shell pwd)/bin/controller-gen +controller-gen: ## Download controller-gen locally if necessary. + $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) + +KUSTOMIZE = $(shell pwd)/bin/kustomize +kustomize: ## Download kustomize locally if necessary. + $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) + +# go-get-tool will 'go get' any package $2 and install it to $1. +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) +define go-get-tool +@[ -f $(1) ] || { \ +set -e ;\ +TMP_DIR=$$(mktemp -d) ;\ +cd $$TMP_DIR ;\ +go mod init tmp ;\ +echo "Downloading $(2)" ;\ +GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ +rm -rf $$TMP_DIR ;\ +} +endef +``` + +And then, to make your project use the `kustomize` version defined in the Makefile, replace all usage of `kustomize` with `$(KUSTOMIZE)` + + + +#### Update your controllers + + + +Replace: + +```go +func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + log := r.Log.WithValues("cronjob", req.NamespacedName) +``` + +With: + +```go +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("cronjob", req.NamespacedName) +``` + +#### Change Logger to use flag options + +In the `main.go` file replace: + +```go +flag.Parse() + +ctrl.SetLogger(zap.New(zap.UseDevMode(true))) +``` + +With: + +```go +opts := zap.Options{ + Development: true, +} +opts.BindFlags(flag.CommandLine) +flag.Parse() + +ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) +``` + +#### Rename the manager flags + +The manager flags `--metrics-addr` and `enable-leader-election` were renamed to `--metrics-bind-address` and `--leader-elect` to be more aligned with core Kubernetes Components. More info: [#1839][issue-1893]. + +In your `main.go` file replace: + + +```go +func main() { + var metricsAddr string + var enableLeaderElection bool + flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") +``` + +With: + +```go +func main() { + var metricsAddr string + var enableLeaderElection bool + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric 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.") +``` + +And then, rename the flags in the `config/default/manager_auth_proxy_patch.yaml` and `config/default/manager.yaml`: + +```yaml +- name: manager +args: +- "--health-probe-bind-address=:8081" +- "--metrics-bind-address=127.0.0.1:8080" +- "--leader-elect" +``` + +#### Verification + +Finally, we can run `make` and `make docker-build` to ensure things are working +fine. + +## Change your project to remove the Kubernetes deprecated API versions usage + + + + +The following steps describe a workflow to upgrade your project to remove the deprecated Kubernetes APIs: `apiextensions.k8s.io/v1beta1`, `admissionregistration.k8s.io/v1beta1`, `cert-manager.io/v1alpha2`. + +The Kubebuilder CLI tool does not support scaffolded resources for both Kubernetes API versions such as; an API/CRD with `apiextensions.k8s.io/v1beta1` and another one with `apiextensions.k8s.io/v1`. + + + +The first step is to update your `PROJECT` file by replacing the `api.crdVersion:v1beta` and `webhooks.WebhookVersion:v1beta` with `api.crdVersion:v1` and `webhooks.WebhookVersion:v1` which would look like: + +```yaml +domain: my.domain +layout: go.kubebuilder.io/v3 +projectName: example +repo: example +resources: +- api: + crdVersion: v1 + namespaced: true + group: webapp + kind: Guestbook + version: v1 + webhooks: + defaulting: true + webhookVersion: v1 +version: "3" +``` + +You can try to re-create the APIS(CRDs) and Webhooks manifests by using the `--force` flag. + + + +Now, re-create the APIS(CRDs) and Webhooks manifests by running the `kubebuilder create api` and `kubebuilder create webhook` for the same group, kind and versions with the flag `--force`, respectively. + + +[migration-guide-v2-to-v3]: migration_guide_v2tov3.md +[envtest]: https://book.kubebuilder.io/reference/testing/envtest.html +[controller-releases]: https://github.com/kubernetes-sigs/controller-runtime/releases +[issue-1893]: https://github.com/kubernetes-sigs/kubebuilder/issues/1839 +[plugins-doc]: /reference/cli-plugins.md +[migration-v2vsv3]: /migration/v2vsv3.md +[custom-resource-definition-versioning]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/ +[issue-1999]: https://github.com/kubernetes-sigs/kubebuilder/issues/1999 +[project-customizations]: v2vsv3.md#project-customizations \ No newline at end of file diff --git a/docs/book/src/migration/migration_guide_v2tov3.md b/docs/book/src/migration/migration_guide_v2tov3.md new file mode 100644 index 00000000000..be1a1d2dcbd --- /dev/null +++ b/docs/book/src/migration/migration_guide_v2tov3.md @@ -0,0 +1,184 @@ +# Migration from v2 to v3 + +Make sure you understand the [differences between Kubebuilder v2 and v3][v2vsv3] +before continuing. + +Please ensure you have followed the [installation guide][quick-start] +to install the required components. + +The recommended way to migrate a v2 project is to create a new v3 project and +copy over the API and the reconciliation code. The conversion will end up with a +project that looks like a native v3 project. However, in some cases, it's +possible to do an in-place upgrade (i.e. reuse the v2 project layout, upgrading +[controller-runtime][controller-runtime] and [controller-tools][controller-tools]). + +## Initialize a v3 Project + + + +Create a new directory with the name of your project. Note that +this name is used in the scaffolds to create the name of your manager Pod and of the Namespace where the Manager is deployed by default. + +```bash +$ mkdir migration-project-name +$ cd migration-project-name +``` + +Now, we need to initialize a v3 project. Before we do that, though, we'll need +to initialize a new go module if we're not on the `GOPATH`. While technically this is +not needed inside `GOPATH`, it is still recommended. + +```bash +go mod init tutorial.kubebuilder.io/migration-project +``` + + + + +Then, we can finish initializing the project with kubebuilder. + +```bash +kubebuilder init --domain tutorial.kubebuilder.io +``` + + + +## Migrate APIs and Controllers + +Next, we'll re-scaffold out the API types and controllers. + + + +```bash +kubebuilder create api --group batch --version v1 --kind CronJob +``` + + + +### Migrate the APIs + + + +Now, let's copy the API definition from `api/v1/_types.go` in our old project to the new one. + +These files have not been modified by the new plugin, so you should be able to replace your freshly scaffolded files by your old one. There may be some cosmetic changes. So you can choose to only copy the types themselves. + +### Migrate the Controllers + +Now, let's migrate the controller code from `controllers/cronjob_controller.go` in our old project to the new one. There is a breaking change and there may be some cosmetic changes. + +The new `Reconcile` method receives the context as an argument now, instead of having to create it with `context.Background()`. You can copy the rest of the code in your old controller to the scaffolded methods replacing: + +```go +func (r *CronJobReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + log := r.Log.WithValues("cronjob", req.NamespacedName) +``` + +With: + +```go +func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := r.Log.WithValues("cronjob", req.NamespacedName) +``` + + + +## Migrate the Webhooks + + + +Now let's scaffold the webhooks for our CRD (CronJob). We'll need to run the +following command with the `--defaulting` and `--programmatic-validation` flags +(since our test project uses defaulting and validating webhooks): + +```bash +kubebuilder create webhook --group batch --version v1 --kind CronJob --defaulting --programmatic-validation +``` + + + +Now, let's copy the webhook definition from `api/v1/_webhook.go` from our old project to the new one. + +## Others + +If there are any manual updates in `main.go` in v2, we need to port the changes to the new `main.go`. We’ll also need to ensure all of the needed schemes have been registered. + +If there are additional manifests added under config directory, port them as well. + +Change the image name in the Makefile if needed. + +## Verification + +Finally, we can run `make` and `make docker-build` to ensure things are working +fine. + +[v2vsv3]: ./v2vsv3.md +[quick-start]: /quick-start.md#installation +[controller-tools]: https://github.com/kubernetes-sigs/controller-tools/releases +[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime/releases +[multi-group]: /migration/multi-group.md \ No newline at end of file diff --git a/docs/book/src/migration/plugin/plugins.md b/docs/book/src/migration/plugin/plugins.md deleted file mode 100644 index a2c57220c10..00000000000 --- a/docs/book/src/migration/plugin/plugins.md +++ /dev/null @@ -1,9 +0,0 @@ -# Plugin Migrations - -Migrating between project [plugins][plugins-doc] involves additions, removals, and/or changes -to files created by any plugin-supported command, ex. `init` and `create`. A plugin supports -one or more project config versions; make sure you [upgrade][project-migration] your project's -config version to the latest supported by your target plugin version before upgrading plugin versions. - -[plugins-doc]:/reference/cli-plugins.md -[project-migration]:/migration/projects.md diff --git a/docs/book/src/migration/plugin/v2_v3.md b/docs/book/src/migration/plugin/v2_v3.md deleted file mode 100644 index dbbc7b515c4..00000000000 --- a/docs/book/src/migration/plugin/v2_v3.md +++ /dev/null @@ -1,3 +0,0 @@ -# Migration from `go.kubebuilder.io` v2 to v3 - -TODO diff --git a/docs/book/src/migration/project/projects.md b/docs/book/src/migration/project/projects.md deleted file mode 100644 index 0ca87551096..00000000000 --- a/docs/book/src/migration/project/projects.md +++ /dev/null @@ -1,4 +0,0 @@ -# Project config Migrations - -Migrating between project configuration versions involves additions, removals, and/or changes -to fields in your project's `PROJECT` file, which is created by running the `init` command. diff --git a/docs/book/src/migration/project/v2_v3.md b/docs/book/src/migration/project/v2_v3.md deleted file mode 100644 index c7efe930da9..00000000000 --- a/docs/book/src/migration/project/v2_v3.md +++ /dev/null @@ -1,212 +0,0 @@ -# Migration from project config v2 to v3 - -Make sure you understand the [differences between Kubebuilder v2 and v3](/migration/v2vsv3.md) -before continuing - -Please ensure you have followed the [installation guide](/quick-start.md#installation) -to install the required components. - -## Steps to migrate - - - -The following changes need to be applied to the project configuration file (`PROJECT`) that can be found at the root directory: - -- Add the `projectName` - -The project name is the name of the project directory in lowercase: - -``` -... -projectName: example -... -``` - -- Add the `layout` - -The default plugin layout which is equivalent to the previous versions is `go.kubebuilder.io/v2`: - -``` -... -layout: go.kubebuilder.io/v2 -... -``` - -- Update the `version` - -The `version` field represents the version of Project layouts. So, you ought to update this to `"3"`: - -``` -... -version: "3" -... -``` - -The final PROJECT file would look like: - -```sh -domain: my.domain -layout: go.kubebuilder.io/v2 -projectName: example -repo: example -resources: -- group: webapp - kind: Guestbook - version: v1 -version: "3" -``` - -### Verification - -Finally, we can run `make` and `make docker-build` to ensure things are working -fine. - -## Migrating your projects to use v3+ plugins - -In order to migrate your projects to v3 some changes need to be manually done to the PROJECT file, and some other scaffolded files. - -### Update your PROJECT file - -Update the `layout` setting to the new plugin version `go.kubebuilder.io/v3` as follows: - -```sh - $ cat PROJECT -domain: my.domain -layout: go.kubebuilder.io/v3 -projectName: example -repo: example -resources: -- group: webapp - kind: Guestbook - version: v1 -version: "3" - -``` - -### V3+ plugins significant changes in the scalffold - - - -#### Manager Rootless - -The projects built with v3+ plugin now define a specific `user ID` and `securityContext` policy to prevent root privilege escalation from the manager container. - -- Update your Dockerfile to define an ID for the user used: - -Replace: - -``` -USER nonroot:nonroot -``` - -With: - -``` -USER 65532:65532 -``` - -- Update your `manager.yaml` manifests in the `config/manager/` directory by adding: - -```yaml -... -spec: - securityContext: - runAsUser: 65532 -... -``` - -```yaml -... -name: manager -securityContext: - allowPrivilegeEscalation: false - -``` - -The final result would look like: - -```yaml -... -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system - labels: - control-plane: controller-manager -spec: - selector: - matchLabels: - control-plane: controller-manager - replicas: 1 - template: - metadata: - labels: - control-plane: controller-manager - spec: - securityContext: - runAsUser: 65532 - containers: - - command: - - /manager - args: - - --enable-leader-election - image: controller:latest - name: manager - securityContext: - allowPrivilegeEscalation: false - resources: - limits: - cpu: 100m - memory: 30Mi - requests: - cpu: 100m - memory: 20Mi - terminationGracePeriodSeconds: 10 -``` - -#### Roles (RBAC) bug fixes - -From now on, the scaffolded roles include finalizer permissions ([more info](https://github.com/kubernetes-sigs/kubebuilder/issues/1654)). Feel free to add them to your projects. As an example: - -``` -... -- apiGroups: - - webapp.my.domain - resources: - - guestbooks/finalizers - verbs: - - update -... -``` - -Also, the following `configmaps/status` permission is no longer scaffolded since they are invalid. Feel free to remove it from your `role.yaml` file in `config/rbac/` directory: - -```yaml -... - resources: - - configmaps/status - verbs: - - get - - update - - patch -``` - -### Verification - -Finally, we can run `make` and `make docker-build` to ensure things are working -fine. - -[QuickStart]: /docs/book/src/quick-start.md -[envtest]: https://book.kubebuilder.io/reference/testing/envtest.html diff --git a/docs/book/src/migration/v2vsv3.md b/docs/book/src/migration/v2vsv3.md index a025ba6de2b..a1d262394d3 100644 --- a/docs/book/src/migration/v2vsv3.md +++ b/docs/book/src/migration/v2vsv3.md @@ -2,21 +2,88 @@ This document covers all breaking changes when migrating from v2 to v3. -The details of all changes (breaking or otherwise) can be found in [kubebuilder](https://github.com/kubernetes-sigs/kubebuilder/releases) release notes. +The details of all changes (breaking or otherwise) can be found in +[controller-runtime][controller-runtime], +[controller-tools][controller-tools] +and [kb-releases][kb-releases] release notes. + +## Common changes + +v3 projects use Go modules and request Go 1.15+. Dep is no longer supported for dependency management. ## Kubebuilder -- A plugin design was introduced to the project. For more info see the [Extensible CLI and Scaffolding Plugins][plugins-phase1-design-doc] -- The GO supported version was upgraded from 1.13+ to 1.15+ +- Preliminary support for plugins was added. For more info see the [Extensible CLI and Scaffolding Plugins][plugins-phase1-design-doc]. + +- The `PROJECT` file now has a new layout. It stores more information about what resources are in use, to better enable plugins to make useful decisions when scaffolding. + + Furthermore, the PROJECT file itself is now versioned: the `version` field corresponds to the version of the PROJECT file itself, while the `layout` field indicates the scaffolding & primary plugin version in use. + +- The version of the image `gcr.io/kubebuilder/kube-rbac-proxy`, which is an optional component enabled by default to secure the request made against the manager, was updated from `0.5.0` to `0.8.0` to address security concerns. The details of all changes can be found in [kube-rbac-proxy][kube-rbac-proxy]. + +## TL;DR of the New `go/v3` Plugin + +***More details on this can be found at [here][kb-releases], but for the highlights, check below*** + + + +- Scaffolded/Generated API version changes: + * Use `apiextensions/v1` for generated CRDs (`apiextensions/v1beta1` was deprecated in Kubernetes `1.16`) + * Use `admissionregistration.k8s.io/v1` for generated webhooks (`admissionregistration.k8s.io/v1beta1` was deprecated in Kubernetes `1.16`) + * Use `cert-manager.io/v1` for the certificate manager when webhooks are used (`cert-manager.io/v1alpha2` was deprecated in `Cert-Manager 0.14`. More info: [CertManager v1.0 docs][cert-manager-docs]) + +- Code changes: + * The manager flags `--metrics-addr` and `enable-leader-election` now are named `--metrics-bind-address` and `--leader-elect` to be more aligned with core Kubernetes Components. More info: [#1839][issue-1893] + * Liveness and Readiness probes are now added by default using [`healthz.Ping`][healthz-ping]. + * A new option to create the projects using ComponentConfig is introduced. For more info see its [enhancement proposal][enhancement proposal] and the [Component config tutorial][component-config-tutorial] + * Manager manifests now use `SecurityContext` to address security concerns. More info: [#1637][issue-1637] +- Misc: + * Support for [controller-tools][controller-tools] `v0.4.1` (for `go/v2` it is `v0.3.0` and previously it was `v0.2.5`) + * Support for [controller-runtime][controller-runtime] `v0.7.0` (for `go/v2` it is `v0.6.4` and previously it was `v0.5.0`) + * Support for [kustomize][kustomize] `v3.8.7` (for `go/v2` it is `v3.5.4` and previously it was `v3.1.0`) + * Required Envtest binaries are automatically downloaded + * The minimum Go version is now `1.15` (previously it was `1.13). + + + +## Migrating to Kubebuilder v3 + +So you want to upgrade your scaffolding to use the latest and greatest features then, follow up the following guide which will cover the steps in the most straightforward way to allow you to upgrade your project to get all latest changes and improvements. + +- [Migration Guide v2 to V3][migration-guide-v2-to-v3] **(Recommended)** + +### By updating the files manually -## Project config versions +So you want to use the latest version of Kubebuilder CLI without changing your scaffolding then, check the following guide which will describe the manually steps required for you to upgrade only your PROJECT version and starts to use the plugins versions. -- [v3][project-v3] +This way is more complex, susceptible to errors, and success cannot be assured. Also, by following these steps you will not get the improvements and bug fixes in the default generated project files. -## `go.kubebuilder.io` plugin versions +You will check that you can still using the previous layout by using the `go/v2` plugin which will not upgrade the [controller-runtime][controller-runtime] and [controller-tools][controller-tools] to the latest version used with `go/v3` becuase of its breaking changes. By checking this guide you know also how to manually change the files to use the `go/v3` plugin and its dependencies versions. -- [v3][plugin-v3] +- [Migrating to Kubebuilder v3 by updating the files manually][manually-upgrade] [plugins-phase1-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1.md -[project-v3]:/migration/project/v2_v3.md -[plugin-v3]:/migration/plugin/v2_v3.md +[manually-upgrade]: manually_migration_guide_v2_v3.md +[component-config-tutorial]: ../component-config-tutorial/tutorial.md +[issue-1893]: https://github.com/kubernetes-sigs/kubebuilder/issues/1839 +[migration-guide-v2-to-v3]: migration_guide_v2tov3.md +[healthz-ping]: https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/healthz#CheckHandler +[controller-runtime]: https://github.com/kubernetes-sigs/controller-runtime/releases +[controller-tools]: https://github.com/kubernetes-sigs/controller-tools/releases +[kustomize]: https://github.com/kubernetes-sigs/kustomize/releases +[issue-1637]: https://github.com/kubernetes-sigs/kubebuilder/issues/1637 +[enhancement proposal]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/wgs +[cert-manager-docs]: https://cert-manager.io/docs/installation/upgrading/ +[kb-releases]: https://github.com/kubernetes-sigs/kubebuilder/releases +[kube-rbac-proxy]: https://github.com/brancz/kube-rbac-proxy/releases +[basic-project-doc]: ../cronjob-tutorial/basic-project.md \ No newline at end of file From 9fafbf94a2dcb1a3ce046fedbc74db06635509a5 Mon Sep 17 00:00:00 2001 From: Adrian Orive Oneca Date: Sat, 13 Feb 2021 13:57:50 +0100 Subject: [PATCH 33/38] Accept auto-generated files from GitHub into the allowed set of files Made an list that makes it easy to add new files to the allowed list with their justification Signed-off-by: Adrian Orive Oneca --- pkg/plugins/golang/v3/init.go | 40 +++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/pkg/plugins/golang/v3/init.go b/pkg/plugins/golang/v3/init.go index 29642ad9067..b2572c7bf4a 100644 --- a/pkg/plugins/golang/v3/init.go +++ b/pkg/plugins/golang/v3/init.go @@ -189,34 +189,46 @@ func (p *initSubcommand) PostScaffold() error { return nil } -// checkDir will return error if the current directory has files which are -// not the go.mod and/or starts with the prefix (.) such as .gitignore. +// 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. The go.mod is allowed because user might run -// go mod init before use the plugin it for not be required inform -// the go module via the repo --flag. +// 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 the whole .git directory tree + // Allow directory trees starting with '.' if info.IsDir() && strings.HasPrefix(info.Name(), ".") && info.Name() != "." { return filepath.SkipDir } - // Also allow go.mod and dot-files - if info.Name() != "go.mod" && info.Name() != "go.sum" && !strings.HasPrefix(info.Name(), ".") { - return fmt.Errorf( - "target directory is not empty "+ - "(only go.mod, go.sum, and files and directories with the prefix \".\" are allowed); "+ - "found existing file %q", - path) + // Allow files starting with '.' + if strings.HasPrefix(info.Name(), ".") { + 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 + "LICENSE", // can be generated when initializing a GitHub project + "README.md", // can be generated when initializing a GitHub project + } + for _, allowedFile := range allowedFiles { + if info.Name() == allowedFile { + return nil + } } - return nil + // Do not allow any other file + return fmt.Errorf( + "target directory is not empty (only %s, and files and directories with the prefix \".\" are "+ + "allowed); found existing file %q", strings.Join(allowedFiles, ", "), path) }) if err != nil { return err } return nil } + +// The go.mod is allowed because user might run +// go mod init before use the plugin it for not be required inform +// the go module via the repo --flag. From a9c3e50e31b43061c5f32e8c66aac022c7c6145a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A1n?= Date: Sat, 13 Feb 2021 01:21:40 +0100 Subject: [PATCH 34/38] Add Adirio to aprovers --- OWNERS_ALIASES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES index 2957174f2f4..40e40cf9f50 100644 --- a/OWNERS_ALIASES +++ b/OWNERS_ALIASES @@ -13,12 +13,12 @@ aliases: kubebuilder-approvers: - camilamacedo86 - estroz + - adirio # folks who can review and LGTM any PRs in the repo (doesn't include # approvers & admins -- those count too via the OWNERS file) kubebuilder-reviewers: - joelanford - - adirio # folks who may have context on ancient history, # but are no longer directly involved From 53b2cf6b19637186371e1018491c73a12555162a Mon Sep 17 00:00:00 2001 From: Camila Macedo Date: Mon, 9 Nov 2020 11:40:11 -0300 Subject: [PATCH 35/38] :book: proposal for new plugin to generate code --- designs/code-generate-image-plugin.md | 309 ++++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 designs/code-generate-image-plugin.md diff --git a/designs/code-generate-image-plugin.md b/designs/code-generate-image-plugin.md new file mode 100644 index 00000000000..18e804ba017 --- /dev/null +++ b/designs/code-generate-image-plugin.md @@ -0,0 +1,309 @@ +--- +title: Neat-Enhancement-Idea +authors: + - "@camilamacedo86" +reviewers: + - TBD +approvers: + - TBD +creation-date: 2020-11-09 +last-updated: 2021-02-14 +status: implementable +--- + +# New Plugin (`deploy-image.go.kubebuilder.io/v1beta1`) to generate code + +## Summary + +This proposal defines a new plugin which allow users get the scaffold with the + required code to have a project that will deploy and manage an image on the cluster following the the guidelines and what have been considered as good practices. + +## Motivation + +The biggest part of the Kubebuilder users looking for to create a project that will at the end only deploy an image. In this way, one of the mainly motivations of this proposal is to abstract the complexities to achieve this goal and still giving the possibility of users improve and customize their projects according to their requirements. + +**Note:** This plugin will address requests that has been raised for a while and for many users in the community. Check [here](https://github.com/operator-framework/operator-sdk/pull/2158), for example, a request done in the past for the SDK project which is integrated with Kubebuidler to address the same need. + +### Goals + +- Add a new plugin to generate the code required to deploy and manage an image on the cluster +- Promote the best practices as give example of common implementations +- Make the process to develop operators projects easier and more agil. +- Give flexibility to the users and allow them to change the code according to their needs +- Provide examples of code implementations and of the most common features usage and reduce the learning curve + +### Non-Goals + +The idea of this proposal is provide a facility for the users. This plugin can be improved +in the future, however, this proposal just covers the basic requirements. In this way, is a non-goal +allow extra configurations such as; scaffold the project using webhooks and the controller covered by tests. + +## Proposal + +Add the new plugin code generate which will scaffold code implementation to deploy the image informed which would like such as; `kubebuilder create api --group=crew --version=v1 --image=myexample:0.0.1 --kind=App --plugins=deploy-image.go.kubebuilder.io/v1beta1` which will: + +- Add a code implementation which will do the Custom Resource reconciliation and create a Deployment resource for the `--image`; + +- Add an EnvVar on the manager manifest (`config/manager/manager.yaml`) which will store the image informed and shows its possibility to users: + +```yaml + .. + spec: + containers: + - name: manager + env: + - name: {{ resource}}-IMAGE + value: {{image:tag}} + image: controller:latest + ... +``` + +- Add a check into reconcile to ensure that the replicas of the deployment on cluster are equals the size defined in the CR: + +```go + // Ensure the deployment size is the same as the spec + size := {{ resource }}.Spec.Size + if *found.Spec.Replicas != size { + found.Spec.Replicas = &size + err = r.Update(ctx, found) + if err != nil { + log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) + return ctrl.Result{}, err + } + // Spec updated - return and requeue + return ctrl.Result{Requeue: true}, nil + } +``` + +- Add the watch feature for the Deployment managed by the controller: + +```go +func (r *{{ resource }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&cachev1alpha1.{{ resource }}{}). + Owns(&appsv1.Deployment{}). + Complete(r) +} +``` + +- Add the RBAC permissions required for the scenario such as: + +```go +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete +``` + +- A status [conditions][conditions] to allow users check that if the deployment occurred successfully or its errors + +- Add a [marker][markers] in the spec definition to demonstrate how to use OpenAPI schemas validation such as `+kubebuilder:validation:Minimum=1` + +- Add the specs on the `_types.go` to generate the CRD/CR sample with default values for `ImagePullPolicy` (`Always`), `ContainerPort` (`80`) and the `Replicas Size` (`3`) + +- Add a finalizer implementation with TODO for the CR managed by the controller such as described in the SDK doc [Handle Cleanup on Deletion](https://sdk.operatorframework.io/docs/building-operators/golang/advanced-topics/#handle-cleanup-on-deletion) + +### User Stories + +- I am as user, would like to use a command to scaffold my common need which is deploy an image of my application, so that I do not need to know exactly how to implement it + +- I am as user, would like to have a good example code base which uses the common features, so that I can easily learn its concepts and have a good start point to address my needs. + +- I am as maintainer, would like to have a good example to address the common questions, so that I can easily describe how to implement the projects and/or use the common features. + +### Implementation Details/Notes/Constraints + +**Example of the controller template** + +```go +// +kubebuilder:rbac:groups=cache.example.com,resources={{ resource.plural }},verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=cache.example.com,resources={{ resource.plural }}/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=cache.example.com,resources={{ resource.plural }}/finalizers,verbs=update +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete + +func (r *{{ resource }}.Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + log := r.Log.WithValues("{{ resource }}", req.NamespacedName) + + // Fetch the {{ resource }} instance + {{ resource }} := &{{ apiimportalias }}.{{ resource }}{} + err := r.Get(ctx, req.NamespacedName, {{ resource }}) + if err != nil { + if errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. + // Return and don't requeue + log.Info("{{ resource }} 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 {{ resource }}") + return ctrl.Result{}, err + } + + // Check if the deployment already exists, if not create a new one + found := &appsv1.Deployment{} + err = r.Get(ctx, types.NamespacedName{Name: {{ resource }}.Name, Namespace: {{ resource }}.Namespace}, found) + if err != nil && errors.IsNotFound(err) { + // Define a new deployment + dep := r.deploymentFor{{ resource }}({{ resource }}) + log.Info("Creating a new Deployment", "Deployment.Namespace", dep.Namespace, "Deployment.Name", dep.Name) + err = r.Create(ctx, dep) + if 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 - return and requeue + return ctrl.Result{Requeue: true}, nil + } else if err != nil { + log.Error(err, "Failed to get Deployment") + return ctrl.Result{}, err + } + + // Ensure the deployment size is the same as the spec + size := {{ resource }}.Spec.Size + if *found.Spec.Replicas != size { + found.Spec.Replicas = &size + err = r.Update(ctx, found) + if err != nil { + log.Error(err, "Failed to update Deployment", "Deployment.Namespace", found.Namespace, "Deployment.Name", found.Name) + return ctrl.Result{}, err + } + // Spec updated - return and requeue + return ctrl.Result{Requeue: true}, nil + } + + // TODO: add here code implementation to update/manage the status + + return ctrl.Result{}, nil +} + +// deploymentFor{{ resource }} returns a {{ resource }} Deployment object +func (r *{{ resource }}Reconciler) deploymentFor{{ resource }}(m *{{ apiimportalias }}.{{ resource }}) *appsv1.Deployment { + ls := labelsFor{{ resource }}(m.Name) + replicas := m.Spec.Size + + dep := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: m.Name, + Namespace: m.Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Replicas: &replicas, + Selector: &metav1.LabelSelector{ + MatchLabels: ls, + }, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: ls, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Image: imageFor{{ resource }}(m.Name), + Name: {{ resource }}, + ImagePullPolicy: {{ resource }}.Spec.ContainerImagePullPolicy, + Command: []string{"{{ resource }}"}, + Ports: []corev1.ContainerPort{{ + ContainerPort: {{ resource }}.Spec.ContainerPort, + Name: "{{ resource }}", + }}, + }}, + }, + }, + }, + } + // Set {{ resource }} instance as the owner and controller + ctrl.SetControllerReference(m, dep, r.Scheme) + return dep +} + +// labelsFor{{ resource }} returns the labels for selecting the resources +// belonging to the given {{ resource }} CR name. +func labelsFor{{ resource }}(name string) map[string]string { + return map[string]string{"type": "{{ resource }}", "{{ resource }}_cr": name} +} + +// imageFor{{ resource }} returns the image for the resources +// belonging to the given {{ resource }} CR name. +func imageFor{{ resource }}(name string) string { + // TODO: this method will return the value of the envvar create to store the image:tag informed +} + +func (r *{{ resource }}Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&cachev1alpha1.{{ resource }}{}). + Owns(&appsv1.Deployment{}). + Complete(r) +} + +``` + +**Example of the spec for the _types.go template** + +```go +// {{ resource }}Spec defines the desired state of {{ resource }} +type {{ resource }}Spec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // +kubebuilder:validation:Minimum=1 + // Size defines the number of {{ resource }} instances + Size int32 `json:"size,omitempty"` + + // ImagePullPolicy defines the policy to pull the container images + ImagePullPolicy string `json:"image-pull-policy,omitempty"` + + // ContainerPort specifies the port which will be used by the image container + ContainerPort int `json:"container-port,omitempty"` + +} +``` + +## Design Details + +### Test Plan + +To ensure this implementation a new project example should be generated in the [testdata](../testdata/) directory of the project. See the [test/testadata/generate.sh](../test/testadata/generate.sh). Also, we should use this scaffold in the [integration tests](../test/e2e/) to ensure that the data scaffolded with works on the cluster as expected. + +### Graduation Criteria + +- The new plugin will only be support `project-version=3` +- The attribute image with the value informed should be added to the resources model in the PROJECT file to let the tool know that the Resource get done with the common basic code implementation: + +```yaml +plugins: + deploy-image.go.kubebuilder.io/v1beta1: + resources: + - domain: example.io + group: crew + kind: Captain + version: v1 + image: "/: +``` + +For further information check the definition agreement register in the comment https://github.com/kubernetes-sigs/kubebuilder/issues/1941#issuecomment-778649947. + +## Open Questions + +1. Should we allow to scaffold the code for an API that is already created for the project? +No, at least in the first moment to keep the simplicity. + +2. Should we support StatefulSet and Deployments? +The idea is we start it by using a Deployment. However, we can improve the feature in follow-ups to support more default types of scaffolds which could be like `kubebuilder create api --group=crew --version=v1 --image=myexample:0.0.1 --kind=App --plugins=deploy-image.go.kubebuilder.io/v1beta1 --type=[deployment|statefulset|webhook]` + +3. Could this feature be useful to other languages or is it just valid to Go based operators? + +This plugin would is reponsable to scaffold content and files for Go-based operators. In a future, if other language-based operators starts to be supported (either officially or by the community) this plugin could be used as reference to create an equivalent one for their languages. Therefore, it should probably not to be a `subdomain` of `go.kubebuilder.io.` + +For its integration with SDK, it might be valid for the Ansible-based operators where a new `playbook/role` could be generated as well. However, for example, for the Helm plugin it might to be useless. E.g `deploy-image.ansible.sdk.operatorframework.io/v1beta1` + +4. Should we consider create a separate repo for plugins? + +In the long term yes. However, see that currently, Kubebuilder has not too many plugins yet. And then, and the preliminary support for plugins did not indeed release. For more info see the [Extensible CLI and Scaffolding Plugins][plugins-phase1-design-doc]. + +In this way, at this moment, it shows to be a little Premature Optimization. Note that the issue [#2016](https://github.com/kubernetes-sigs/kubebuilder/issues/1378) will check the possibility of the plugins be as separate binaries that can be discovered by the Kubebuilder CLI binary via user-specified plugin file paths. Then, the discussion over the best approach to dealing with many plugins and if they should or not leave in the Kubebuilder repository would be better addressed after that. + +5. Is Kubebuilder prepared to receive this implementation already? + +The [Extensible CLI and Scaffolding Plugins - Phase 1.5](extensible-cli-and-scaffolding-plugins-phase-1-5.md) and the issue #1941 requires to be implemented before this proposal. Also, to have a better idea over the proposed solutions made so for the Plugin Ecosystem see the meta issue [#2016](https://github.com/kubernetes-sigs/kubebuilder/issues/2016) + +[markers]: ../docs/book/src/reference/markers.md +[conditions]: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#typical-status-properties +[plugins-phase1-design-doc]: https://github.com/kubernetes-sigs/kubebuilder/blob/master/designs/extensible-cli-and-scaffolding-plugins-phase-1.md \ No newline at end of file From 5afa97ea1c5b1cf29d9b4f817a20865c98e2b1e3 Mon Sep 17 00:00:00 2001 From: Houston Putman Date: Tue, 16 Feb 2021 17:19:03 -0500 Subject: [PATCH 36/38] Update book examples to Kubebuilder v3. --- .../src/cronjob-tutorial/testdata/emptyapi.go | 7 ++- .../testdata/emptycontroller.go | 3 +- .../cronjob-tutorial/testdata/emptymain.go | 62 ++++++++++++++++--- .../testdata/finalizer_example.go | 15 +++-- 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/docs/book/src/cronjob-tutorial/testdata/emptyapi.go b/docs/book/src/cronjob-tutorial/testdata/emptyapi.go index 3cf42200c9e..9b26ffd8114 100644 --- a/docs/book/src/cronjob-tutorial/testdata/emptyapi.go +++ b/docs/book/src/cronjob-tutorial/testdata/emptyapi.go @@ -68,8 +68,9 @@ a Kind. Then, the `object` generator generates an implementation of the interface that all types representing Kinds must implement. */ -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + // CronJob is the Schema for the cronjobs API type CronJob struct { metav1.TypeMeta `json:",inline"` @@ -79,7 +80,7 @@ type CronJob struct { Status CronJobStatus `json:"status,omitempty"` } -// +kubebuilder:object:root=true +//+kubebuilder:object:root=true // CronJobList contains a list of CronJob type CronJobList struct { diff --git a/docs/book/src/cronjob-tutorial/testdata/emptycontroller.go b/docs/book/src/cronjob-tutorial/testdata/emptycontroller.go index ce3b9785445..f1b723e4432 100644 --- a/docs/book/src/cronjob-tutorial/testdata/emptycontroller.go +++ b/docs/book/src/cronjob-tutorial/testdata/emptycontroller.go @@ -77,8 +77,7 @@ logging works by attaching key-value pairs to a static message. We can pre-assi some pairs at the top of our reconcile method to have those attached to all log lines in this reconciler. */ -func (r *CronJobReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { - _ = context.Background() +func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("cronjob", req.NamespacedName) // your logic here diff --git a/docs/book/src/cronjob-tutorial/testdata/emptymain.go b/docs/book/src/cronjob-tutorial/testdata/emptymain.go index 12a43cfdc95..c06646254e1 100644 --- a/docs/book/src/cronjob-tutorial/testdata/emptymain.go +++ b/docs/book/src/cronjob-tutorial/testdata/emptymain.go @@ -30,10 +30,17 @@ import ( "fmt" "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" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" // +kubebuilder:scaffold:imports ) @@ -51,8 +58,9 @@ var ( ) func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - // +kubebuilder:scaffold:scheme + //+kubebuilder:scaffold:scheme } /* @@ -79,12 +87,29 @@ soon. func main() { var metricsAddr string - flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") + 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.") + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) flag.Parse() - ctrl.SetLogger(zap.New(zap.UseDevMode(true))) + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{Scheme: scheme, MetricsBindAddress: metricsAddr}) + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: metricsAddr, + Port: 9443, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "80807133.tutorial.kubebuilder.io", + }) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) @@ -95,9 +120,13 @@ func main() { */ mgr, err = ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - Namespace: namespace, - MetricsBindAddress: metricsAddr, + Scheme: scheme, + Namespace: namespace, + MetricsBindAddress: metricsAddr, + Port: 9443, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "80807133.tutorial.kubebuilder.io", }) /* @@ -112,9 +141,13 @@ func main() { var namespaces []string // List of Namespaces mgr, err = ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - NewCache: cache.MultiNamespacedCacheBuilder(namespaces), - MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort), + Scheme: scheme, + NewCache: cache.MultiNamespacedCacheBuilder(namespaces), + MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort), + Port: 9443, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "80807133.tutorial.kubebuilder.io", }) /* @@ -123,6 +156,15 @@ func main() { // +kubebuilder:scaffold:builder + 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") diff --git a/docs/book/src/cronjob-tutorial/testdata/finalizer_example.go b/docs/book/src/cronjob-tutorial/testdata/finalizer_example.go index 60c53797359..add890835d7 100644 --- a/docs/book/src/cronjob-tutorial/testdata/finalizer_example.go +++ b/docs/book/src/cronjob-tutorial/testdata/finalizer_example.go @@ -33,12 +33,19 @@ import ( // +kubebuilder:docs-gen:collapse=Imports +/* +By default, kubebuilder will include the RBAC rules necessary to update finalizers for CronJobs. +*/ + +//+kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=batch.tutorial.kubebuilder.io,resources=cronjobs/finalizers,verbs=update + /* The code snippet below shows skeleton code for implementing a finalizer. */ -func (r *CronJobReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { - ctx := context.Background() +func (r *CronJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := r.Log.WithValues("cronjob", req.NamespacedName) var cronJob *batchv1.CronJob @@ -60,7 +67,7 @@ func (r *CronJobReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { // registering our finalizer. if !containsString(cronJob.GetFinalizers(), myFinalizerName) { cronJob.SetFinalizers(append(cronJob.GetFinalizers(), myFinalizerName)) - if err := r.Update(context.Background(), cronJob); err != nil { + if err := r.Update(ctx, cronJob); err != nil { return ctrl.Result{}, err } } @@ -76,7 +83,7 @@ func (r *CronJobReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { // remove our finalizer from the list and update it. cronJob.SetFinalizers(removeString(cronJob.GetFinalizers(), myFinalizerName)) - if err := r.Update(context.Background(), cronJob); err != nil { + if err := r.Update(ctx, cronJob); err != nil { return ctrl.Result{}, err } } From e23cbe43eaf6e2d13aa8c976bb93e95b856da842 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Wed, 17 Feb 2021 09:32:00 +0100 Subject: [PATCH 37/38] Fix the bug where an error was being hidden by a potentially valid flag not being recognized by the root command Signed-off-by: Adrian Orive --- pkg/cli/root.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/cli/root.go b/pkg/cli/root.go index d0eed81e44b..e0baa398180 100644 --- a/pkg/cli/root.go +++ b/pkg/cli/root.go @@ -49,6 +49,10 @@ func (c cli) newRootCmd() *cobra.Command { fs.String(projectVersionFlag, "", "project version") fs.StringSlice(pluginsFlag, nil, "plugin keys of the plugin to initialize the project with") + // As the root command will be used to shot the help message under some error conditions, + // like during plugin resolving, we need to allow unknown flags to prevent parsing errors. + cmd.FParseErrWhitelist = cobra.FParseErrWhitelist{UnknownFlags: true} + return cmd } From d71b1a9c4f1d607a134bbf1517938de96b543601 Mon Sep 17 00:00:00 2001 From: Adrian Orive Date: Wed, 17 Feb 2021 14:43:59 +0100 Subject: [PATCH 38/38] Allow cross-package coverage inside the "pkg" directory Signed-off-by: Adrian Orive --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9052d90a31a..0f0e1c8cb34 100644 --- a/Makefile +++ b/Makefile @@ -97,7 +97,7 @@ test-unit: ## Run the unit tests .PHONY: test-coverage test-coverage: ## Run unit tests creating the output to report coverage - rm -rf *.out # Remove all coverage files if exists - go test -race -failfast -tags=integration -coverprofile=coverage-all.out ./cmd/... ./pkg/... ./plugins/... + go test -race -failfast -tags=integration -coverprofile=coverage-all.out -coverpkg="./pkg/cli/...,./pkg/config/...,./pkg/internal/...,./pkg/model/...,./pkg/plugin/...,./pkg/plugins/golang,./pkg/plugins/internal/..." ./pkg/... .PHONY: test-integration test-integration: ## Run the integration tests