diff --git a/pkg/plugin/v3/api.go b/pkg/plugin/v3/api.go index e80e73921ee..5400845cfd0 100644 --- a/pkg/plugin/v3/api.go +++ b/pkg/plugin/v3/api.go @@ -43,6 +43,10 @@ import ( // TODO: remove this when a better solution for using addons is implemented. const KbDeclarativePatternVersion = "v0.0.0-20200522144838-848d48e5b073" +// AddonPlugin is the name of the plugin for addons. +// TODO: the addon should be a plugin. More info: #1543 +const AddonPlugin = "addon" + type createAPIPlugin struct { config *config.Config @@ -182,7 +186,7 @@ func (p *createAPIPlugin) GetScaffolder() (scaffold.Scaffolder, error) { switch strings.ToLower(p.pattern) { case "": // Default pattern - case "addon": + case AddonPlugin: plugins = append(plugins, &addon.Plugin{}) default: return nil, fmt.Errorf("unknown pattern %q", p.pattern) @@ -198,7 +202,7 @@ func (p *createAPIPlugin) PostScaffold() error { switch strings.ToLower(p.pattern) { case "": // Default pattern - case "addon": + case AddonPlugin: // Ensure that we are pinning sigs.k8s.io/kubebuilder-declarative-pattern version // TODO: either find a better way to inject this version (ex. tools.go). err := util.RunCmd("Get kubebuilder-declarative-pattern dependency", "go", "get", diff --git a/pkg/plugin/v3/scaffolds/internal/templates/config/api/webhook_suitetest.go b/pkg/plugin/v3/scaffolds/internal/templates/config/api/webhook_suitetest.go new file mode 100644 index 00000000000..9f30b302c24 --- /dev/null +++ b/pkg/plugin/v3/scaffolds/internal/templates/config/api/webhook_suitetest.go @@ -0,0 +1,223 @@ +package api + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/pkg/model/file" +) + +var _ file.Template = &WebhookSuite{} +var _ file.Inserter = &WebhookSuite{} + +// WebhookSuite scaffolds the webhook_suite.go file to setup the webhook test +type WebhookSuite struct { + file.TemplateMixin + file.MultiGroupMixin + file.BoilerplateMixin + file.ResourceMixin + + // BaseDirectoryRelativePath define the Path for the base directory when it is multigroup + BaseDirectoryRelativePath string +} + +// SetTemplateDefaults implements file.Template +func (f *WebhookSuite) SetTemplateDefaults() error { + if f.Path == "" { + if f.MultiGroup { + if f.Resource.Group != "" { + f.Path = filepath.Join("apis", "%[group]", "%[version]", "webhook_suite_test.go") + } else { + f.Path = filepath.Join("apis", "%[version]", "webhook_suite_test.go") + } + } else { + f.Path = filepath.Join("api", "%[version]", "webhook_suite_test.go") + } + } + f.Path = f.Resource.Replacer().Replace(f.Path) + + f.TemplateBody = fmt.Sprintf(webhookTestSuiteTemplate, + file.NewMarkerFor(f.Path, importMarker), + file.NewMarkerFor(f.Path, addSchemeMarker), + file.NewMarkerFor(f.Path, addWebhookManagerMarker), + ) + + // If is multigroup the path needs to be ../../.. since it has the group dir. + f.BaseDirectoryRelativePath = `"..", ".."` + if f.MultiGroup && f.Resource.Group != "" { + f.BaseDirectoryRelativePath = `"..", "..",".."` + } + + return nil +} + +const ( + // TODO: admission webhook versions should be based on the input of the user. For More Info #1664 + admissionImportAlias = "admissionv1beta1" + admissionPath = "k8s.io/api/admission/v1beta1" + importMarker = "imports" + addWebhookManagerMarker = "webhook" + addSchemeMarker = "scheme" +) + +// GetMarkers implements file.Inserter +func (f *WebhookSuite) GetMarkers() []file.Marker { + return []file.Marker{ + file.NewMarkerFor(f.Path, importMarker), + file.NewMarkerFor(f.Path, addSchemeMarker), + file.NewMarkerFor(f.Path, addWebhookManagerMarker), + } +} + +const ( + apiImportCodeFragment = `%s "%s" +` + addschemeCodeFragment = `err = %s.AddToScheme(scheme ) +Expect(err).NotTo(HaveOccurred()) + +` + addWebhookManagerCodeFragment = `err = (&%s{}).SetupWebhookWithManager(mgr) +Expect(err).NotTo(HaveOccurred()) + +` +) + +// GetCodeFragments implements file.Inserter +func (f *WebhookSuite) GetCodeFragments() file.CodeFragmentsMap { + fragments := make(file.CodeFragmentsMap, 3) + + // Generate import code fragments + imports := make([]string, 0) + imports = append(imports, fmt.Sprintf(apiImportCodeFragment, admissionImportAlias, admissionPath)) + + // Generate add scheme code fragments + addScheme := make([]string, 0) + addScheme = append(addScheme, fmt.Sprintf(addschemeCodeFragment, admissionImportAlias)) + + // Generate add webhookManager code fragments + addWebhookManager := make([]string, 0) + addWebhookManager = append(addWebhookManager, fmt.Sprintf(addWebhookManagerCodeFragment, f.Resource.Kind)) + + // Only store code fragments in the map if the slices are non-empty + if len(addWebhookManager) != 0 { + fragments[file.NewMarkerFor(f.Path, addWebhookManagerMarker)] = addWebhookManager + } + if len(imports) != 0 { + fragments[file.NewMarkerFor(f.Path, importMarker)] = imports + } + if len(addScheme) != 0 { + fragments[file.NewMarkerFor(f.Path, addSchemeMarker)] = addScheme + } + + return fragments +} + +const ( + webhookTestSuiteTemplate = ` +package {{ .Resource.Version }} + +import ( + "path/filepath" + "testing" + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + %s + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var stopCh = make(chan struct{}) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Webhook Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "crd", "bases")}, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + DirectoryPaths: []string{filepath.Join({{ .BaseDirectoryRelativePath }}, "config", "webhook")}, + }, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).ToNot(HaveOccurred()) + Expect(cfg).ToNot(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + %s + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).ToNot(HaveOccurred()) + Expect(k8sClient).ToNot(BeNil()) + + // start webhook server using Manager + wio := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: wio.LocalServingHost, + Port: wio.LocalServingPort, + CertDir: wio.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).ToNot(HaveOccurred()) + + %s + + go func() { + err = mgr.Start(stopCh) + if err != nil { + panic(err) + } + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := wio.LocalServingHost + ":" + fmt.Sprint(wio.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + + close(done) +}, 60) + +var _ = AfterSuite(func() { + close(stopCh) + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).ToNot(HaveOccurred()) +}) +` +) diff --git a/pkg/plugin/v3/scaffolds/webhook.go b/pkg/plugin/v3/scaffolds/webhook.go index 3ee2b0e5dbf..0b26eb9e95c 100644 --- a/pkg/plugin/v3/scaffolds/webhook.go +++ b/pkg/plugin/v3/scaffolds/webhook.go @@ -34,7 +34,6 @@ type webhookScaffolder struct { config *config.Config boilerplate string resource *resource.Resource - // Webhook type options. defaulting, validation, conversion bool } @@ -86,5 +85,15 @@ You need to implement the conversion.Hub and conversion.Convertible interfaces f return err } + // TODO: Add test suite for conversion webhook after #1664 has been merged & conversion tests supported in envtest. + if s.defaulting || s.validation { + if err := machinery.NewScaffold().Execute( + s.newUniverse(), + &api.WebhookSuite{}, + ); err != nil { + return err + } + } + return nil } diff --git a/pkg/plugin/v3/webhook.go b/pkg/plugin/v3/webhook.go index 2c643f204a6..b2f87be1d93 100644 --- a/pkg/plugin/v3/webhook.go +++ b/pkg/plugin/v3/webhook.go @@ -108,7 +108,8 @@ func (p *createWebhookPlugin) GetScaffolder() (scaffold.Scaffolder, error) { // 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 + return scaffolds.NewWebhookScaffolder(p.config, string(bp), res, p.defaulting, p.validation, p.conversion), + nil } func (p *createWebhookPlugin) PostScaffold() error { 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 new file mode 100644 index 00000000000..3e505119a17 --- /dev/null +++ b/testdata/project-v3-multigroup/apis/crew/v1/webhook_suite_test.go @@ -0,0 +1,115 @@ +package v1 + +import ( + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + // +kubebuilder:scaffold:imports + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var stopCh = make(chan struct{}) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Webhook Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crd", "bases")}, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + DirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "webhook")}, + }, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).ToNot(HaveOccurred()) + Expect(cfg).ToNot(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1beta1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).ToNot(HaveOccurred()) + Expect(k8sClient).ToNot(BeNil()) + + // start webhook server using Manager + wio := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: wio.LocalServingHost, + Port: wio.LocalServingPort, + CertDir: wio.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).ToNot(HaveOccurred()) + + err = (&Captain{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:webhook + + go func() { + err = mgr.Start(stopCh) + if err != nil { + panic(err) + } + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := wio.LocalServingHost + ":" + fmt.Sprint(wio.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + + close(done) +}, 60) + +var _ = AfterSuite(func() { + close(stopCh) + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).ToNot(HaveOccurred()) +}) diff --git a/testdata/project-v3-multigroup/apis/v1/webhook_suite_test.go b/testdata/project-v3-multigroup/apis/v1/webhook_suite_test.go new file mode 100644 index 00000000000..fca8266cdc7 --- /dev/null +++ b/testdata/project-v3-multigroup/apis/v1/webhook_suite_test.go @@ -0,0 +1,115 @@ +package v1 + +import ( + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + // +kubebuilder:scaffold:imports + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var stopCh = make(chan struct{}) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Webhook Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + DirectoryPaths: []string{filepath.Join("..", "..", "config", "webhook")}, + }, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).ToNot(HaveOccurred()) + Expect(cfg).ToNot(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1beta1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).ToNot(HaveOccurred()) + Expect(k8sClient).ToNot(BeNil()) + + // start webhook server using Manager + wio := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: wio.LocalServingHost, + Port: wio.LocalServingPort, + CertDir: wio.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).ToNot(HaveOccurred()) + + err = (&Lakers{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:webhook + + go func() { + err = mgr.Start(stopCh) + if err != nil { + panic(err) + } + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := wio.LocalServingHost + ":" + fmt.Sprint(wio.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + + close(done) +}, 60) + +var _ = AfterSuite(func() { + close(stopCh) + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).ToNot(HaveOccurred()) +}) diff --git a/testdata/project-v3-multigroup/go.mod b/testdata/project-v3-multigroup/go.mod index 6f434a9bfd7..1e1d7f7c9e2 100644 --- a/testdata/project-v3-multigroup/go.mod +++ b/testdata/project-v3-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.2 diff --git a/testdata/project-v3/api/v1/webhook_suite_test.go b/testdata/project-v3/api/v1/webhook_suite_test.go new file mode 100644 index 00000000000..4d88cbb8477 --- /dev/null +++ b/testdata/project-v3/api/v1/webhook_suite_test.go @@ -0,0 +1,115 @@ +package v1 + +import ( + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + // +kubebuilder:scaffold:imports + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment +var stopCh = make(chan struct{}) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Webhook Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + DirectoryPaths: []string{filepath.Join("..", "..", "config", "webhook")}, + }, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).ToNot(HaveOccurred()) + Expect(cfg).ToNot(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1beta1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).ToNot(HaveOccurred()) + Expect(k8sClient).ToNot(BeNil()) + + // start webhook server using Manager + wio := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: wio.LocalServingHost, + Port: wio.LocalServingPort, + CertDir: wio.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).ToNot(HaveOccurred()) + + err = (&Captain{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:webhook + + go func() { + err = mgr.Start(stopCh) + if err != nil { + panic(err) + } + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := wio.LocalServingHost + ":" + fmt.Sprint(wio.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + + close(done) +}, 60) + +var _ = AfterSuite(func() { + close(stopCh) + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).ToNot(HaveOccurred()) +}) diff --git a/testdata/project-v3/go.mod b/testdata/project-v3/go.mod index 50a80e46900..ef312bc5126 100644 --- a/testdata/project-v3/go.mod +++ b/testdata/project-v3/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.2