From fc50f2fb1355b124dc7b310fde5074334517a989 Mon Sep 17 00:00:00 2001 From: Camila Macedo Date: Mon, 23 Sep 2024 08:27:27 +0100 Subject: [PATCH] cleanup/refactor: Implement and refactor e2e tests for 'alpha generate' command - Added comprehensive end-to-end tests for the 'generate' command, ensuring proper validation of the 'PROJECT' file after project initialization and regeneration. - Verified correct handling of multigroup layouts, Grafana, and DeployImage plugins. - Refactored test structure to align with established patterns from other tests, improving maintainability and consistency. - Increased test coverage to support future growth and cover more scenarios. --- test/e2e/alphagenerate/generate_test.go | 270 ---------------- .../e2e_suite_test.go | 2 +- test/e2e/generate/generate_test.go | 296 ++++++++++++++++++ 3 files changed, 297 insertions(+), 271 deletions(-) delete mode 100644 test/e2e/alphagenerate/generate_test.go rename test/e2e/{alphagenerate => generate}/e2e_suite_test.go (97%) create mode 100644 test/e2e/generate/generate_test.go diff --git a/test/e2e/alphagenerate/generate_test.go b/test/e2e/alphagenerate/generate_test.go deleted file mode 100644 index fb93663f0f9..00000000000 --- a/test/e2e/alphagenerate/generate_test.go +++ /dev/null @@ -1,270 +0,0 @@ -/* -Copyright 2023 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 alphagenerate - -import ( - "fmt" - "io" - "os" - "path/filepath" - - pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "sigs.k8s.io/kubebuilder/v4/test/e2e/utils" -) - -var _ = Describe("kubebuilder", func() { - Context("alpha generate ", func() { - var ( - kbc *utils.TestContext - ) - - BeforeEach(func() { - var err error - kbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, "GO111MODULE=on") - Expect(err).NotTo(HaveOccurred()) - Expect(kbc.Prepare()).To(Succeed()) - }) - - AfterEach(func() { - kbc.Destroy() - }) - - It("should regenerate the project with success", func() { - ReGenerateProject(kbc) - }) - - }) -}) - -// ReGenerateProject implements a project that is regenerated by kubebuilder. -func ReGenerateProject(kbc *utils.TestContext) { - var err error - - By("initializing a project") - err = kbc.Init( - "--plugins", "go/v4", - "--project-version", "3", - "--domain", kbc.Domain, - ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("regenerating the project") - err = kbc.Regenerate( - "--input-dir", kbc.Dir, - "--output-dir", filepath.Join(kbc.Dir, "testdir"), - ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("checking if the project file was generated with the expected layout") - var layout = `layout: -- go.kubebuilder.io/v4 -` - fileContainsExpr, err := pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir", "PROJECT"), layout) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected domain") - var domain = fmt.Sprintf("domain: %s", kbc.Domain) - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir", "PROJECT"), domain) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected version") - var version = `version: "3"` - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir", "PROJECT"), version) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("editing a project with multigroup=true") - err = kbc.Edit( - "--multigroup=true", - ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("create APIs with resource and controller") - err = kbc.CreateAPI( - "--group", "crew", - "--version", "v1", - "--kind", "Captain", - "--namespaced", - "--resource", - "--controller", - ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("create Webhooks with conversion and validating webhook") - err = kbc.CreateWebhook( - "--group", "crew", - "--version", "v1", - "--kind", "Captain", - "--programmatic-validation", - "--conversion", - ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("create APIs non namespaced with resource and controller") - err = kbc.CreateAPI( - "--group", "crew", - "--version", "v1", - "--kind", "Admiral", - "--namespaced=false", - "--resource", - "--controller", - ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("create APIs with deploy-image plugin") - err = kbc.CreateAPI( - "--group", "crew", - "--version", "v1", - "--kind", "Memcached", - "--image=memcached:1.6.15-alpine", - "--image-container-command=memcached,--memory-limit=64,modern,-v", - "--image-container-port=11211", - "--run-as-user=1001", - "--plugins=\"deploy-image/v1-alpha\"", - ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("Enable grafana plugin to an existing project") - err = kbc.Edit( - "--plugins", "grafana.kubebuilder.io/v1-alpha", - ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("Edit the grafana config file") - grafanaConfig, err := os.OpenFile(filepath.Join(kbc.Dir, "grafana/custom-metrics/config.yaml"), - os.O_APPEND|os.O_WRONLY, 0644) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - newLine := "test_new_line" - _, err = io.WriteString(grafanaConfig, newLine) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - err = grafanaConfig.Close() - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("regenerating the project at another output directory") - err = kbc.Regenerate( - "--input-dir", kbc.Dir, - "--output-dir", filepath.Join(kbc.Dir, "testdir2"), - ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - - By("checking if the project file was generated with the expected multigroup flag") - var multiGroup = `multigroup: true` - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), multiGroup) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected group") - var APIGroup = "group: crew" - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), APIGroup) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected kind") - var APIKind = "kind: Captain" - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), APIKind) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected version") - var APIVersion = "version: v1" - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), APIVersion) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected namespaced") - var namespaced = "namespaced: true" - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), namespaced) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected controller") - var controller = "controller: true" - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), controller) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected webhook") - var webhook = `webhooks: - conversion: true - validation: true` - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), webhook) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated without namespace: true") - var nonNamespacedFields = fmt.Sprintf(`api: - crdVersion: v1 - controller: true - domain: %s - group: crew - kind: Admiral`, kbc.Domain) - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), nonNamespacedFields) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - Expect(fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected deploy-image plugin fields") - var deployImagePlugin = "deploy-image.go.kubebuilder.io/v1-alpha" - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), deployImagePlugin) - Expect(err).NotTo(HaveOccurred()) - Expect(fileContainsExpr).To(BeTrue()) - var deployImagePluginFields = `kind: Memcached - options: - containerCommand: memcached,--memory-limit=64,modern,-v - containerPort: "11211" - image: memcached:1.6.15-alpine - runAsUser: "1001"` - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), deployImagePluginFields) - Expect(err).NotTo(HaveOccurred()) - Expect(fileContainsExpr).To(BeTrue()) - - By("checking if the project file was generated with the expected grafana plugin fields") - var grafanaPlugin = "grafana.kubebuilder.io/v1-alpha" - fileContainsExpr, err = pluginutil.HasFileContentWith( - filepath.Join(kbc.Dir, "testdir2", "PROJECT"), grafanaPlugin) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) - ExpectWithOffset(1, fileContainsExpr).To(BeTrue()) - - By("checking if the generated grafana config file has the same content as the old one") - grafanaConfigPath := filepath.Join(kbc.Dir, "grafana/custom-metrics/config.yaml") - generatedGrafanaConfigPath := filepath.Join(kbc.Dir, "testdir2", "grafana/custom-metrics/config.yaml") - Expect(grafanaConfigPath).Should(BeARegularFile()) - Expect(generatedGrafanaConfigPath).Should(BeARegularFile()) - bytesBefore, err := os.ReadFile(grafanaConfigPath) - Expect(err).NotTo(HaveOccurred()) - bytesAfter, err := os.ReadFile(generatedGrafanaConfigPath) - Expect(err).NotTo(HaveOccurred()) - Expect(bytesBefore).Should(Equal(bytesAfter)) -} diff --git a/test/e2e/alphagenerate/e2e_suite_test.go b/test/e2e/generate/e2e_suite_test.go similarity index 97% rename from test/e2e/alphagenerate/e2e_suite_test.go rename to test/e2e/generate/e2e_suite_test.go index 7f3d82bf0ea..da778e3a875 100644 --- a/test/e2e/alphagenerate/e2e_suite_test.go +++ b/test/e2e/generate/e2e_suite_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package alphagenerate +package generate import ( "fmt" diff --git a/test/e2e/generate/generate_test.go b/test/e2e/generate/generate_test.go new file mode 100644 index 00000000000..18314c68f07 --- /dev/null +++ b/test/e2e/generate/generate_test.go @@ -0,0 +1,296 @@ +/* +Copyright 2023 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 generate + +import ( + "fmt" + "path/filepath" + + "sigs.k8s.io/kubebuilder/v4/pkg/model/resource" + + "github.com/spf13/afero" + "sigs.k8s.io/kubebuilder/v4/pkg/config" + "sigs.k8s.io/kubebuilder/v4/pkg/config/store/yaml" + "sigs.k8s.io/kubebuilder/v4/pkg/machinery" + pluginutil "sigs.k8s.io/kubebuilder/v4/pkg/plugin/util" + "sigs.k8s.io/kubebuilder/v4/pkg/plugins/golang/deploy-image/v1alpha1" + "sigs.k8s.io/kubebuilder/v4/test/e2e/utils" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("kubebuilder", func() { + Context("alpha generate", func() { + + var ( + kbc *utils.TestContext + projectOutputDir string + projectFilePath string + ) + + const outputDir = "output" + + BeforeEach(func() { + var err error + kbc, err = utils.NewTestContext(pluginutil.KubebuilderBinName, "GO111MODULE=on") + Expect(err).NotTo(HaveOccurred()) + Expect(kbc.Prepare()).To(Succeed()) + + projectOutputDir = filepath.Join(kbc.Dir, outputDir) + projectFilePath = filepath.Join(projectOutputDir, "PROJECT") + + By("initializing a project") + err = kbc.Init( + "--plugins", "go/v4", + "--project-version", "3", + "--domain", kbc.Domain, + ) + Expect(err).NotTo(HaveOccurred(), "Failed to create project") + }) + + AfterEach(func() { + By("destroying directory") + kbc.Destroy() + }) + + It("should regenerate the project with success", func() { + generateProject(kbc) + regenerateProject(kbc, projectOutputDir) + validateProjectFile(kbc, projectFilePath) + }) + + It("should regenerate project with grafana plugin with success", func() { + generateProjectWithGrafanaPlugin(kbc) + regenerateProject(kbc, projectOutputDir) + validateGrafanaPlugin(projectFilePath) + }) + + It("should regenerate project with DeployImage plugin with success", func() { + generateProjectWithDeployImagePlugin(kbc) + regenerateProject(kbc, projectOutputDir) + validateDeployImagePlugin(projectFilePath) + }) + }) +}) + +func generateProject(kbc *utils.TestContext) { + By("editing project to enable multigroup layout") + err := kbc.Edit("--multigroup", "true") + Expect(err).NotTo(HaveOccurred(), "Failed to edit project for multigroup layout") + + By("creating API definition") + err = kbc.CreateAPI( + "--group", kbc.Group, + "--version", kbc.Version, + "--kind", kbc.Kind, + "--namespaced", + "--resource", + "--controller", + "--make=false", + ) + Expect(err).NotTo(HaveOccurred(), "Failed to scaffold api with resource and controller") + + By("creating API definition with controller and resource") + err = kbc.CreateAPI( + "--group", "crew", + "--version", "v1", + "--kind", "Memcached", + "--namespaced", + "--resource", + "--controller", + "--make=false", + ) + Expect(err).NotTo(HaveOccurred(), "Failed to scaffold API with resource and controller") + + By("creating Webhook for Memcached API") + err = kbc.CreateWebhook( + "--group", "crew", + "--version", "v1", + "--kind", "Memcached", + "--defaulting", + "--programmatic-validation", + "--conversion", + ) + Expect(err).NotTo(HaveOccurred(), "Failed to scaffold webhook for Memcached API") + + By("creating API without controller (Admiral)") + err = kbc.CreateAPI( + "--group", "crew", + "--version", "v1", + "--kind", "Admiral", + "--controller=false", + "--resource=true", + "--namespaced=false", + "--make=false", + ) + Expect(err).NotTo(HaveOccurred(), "Failed to scaffold API without controller") + + By("creating API with controller and resource (Captain)") + err = kbc.CreateAPI( + "--group", "crew", + "--version", "v1", + "--kind", "Captain", + "--controller=true", + "--resource=true", + "--namespaced=true", + "--make=false", + ) + Expect(err).NotTo(HaveOccurred(), "Failed to scaffold API with namespaced true") + +} + +func regenerateProject(kbc *utils.TestContext, projectOutputDir string) { + By("regenerating the project") + err := kbc.Regenerate( + fmt.Sprintf("--input-dir=%s", kbc.Dir), + fmt.Sprintf("--output-dir=%s", projectOutputDir), + ) + Expect(err).NotTo(HaveOccurred(), "Failed to regenerate project") +} + +func generateProjectWithGrafanaPlugin(kbc *utils.TestContext) { + By("editing project to enable Grafana plugin") + err := kbc.Edit("--plugins", "grafana.kubebuilder.io/v1-alpha") + Expect(err).NotTo(HaveOccurred(), "Failed to edit project to enable Grafana Plugin") +} + +func generateProjectWithDeployImagePlugin(kbc *utils.TestContext) { + By("creating an API with DeployImage plugin") + err := kbc.CreateAPI( + "--group", "crew", + "--version", "v1", + "--kind", "Memcached", + "--image=memcached:1.6.15-alpine", + "--image-container-command=memcached,--memory-limit=64,modern,-v", + "--image-container-port=11211", + "--run-as-user=1001", + "--plugins=deploy-image/v1-alpha", + ) + Expect(err).NotTo(HaveOccurred(), "Failed to create API with Deploy Image Plugin") +} + +// Validate the PROJECT file for basic content and additional resources +func validateProjectFile(kbc *utils.TestContext, projectFile string) { + projectConfig := getConfigFromProjectFile(projectFile) + + By("checking the layout in the PROJECT file") + Expect(projectConfig.GetPluginChain()).To(ContainElement("go.kubebuilder.io/v4")) + + By("checking the multigroup flag in the PROJECT file") + Expect(projectConfig.IsMultiGroup()).To(BeTrue()) + + By("checking the domain in the PROJECT file") + Expect(projectConfig.GetDomain()).To(Equal(kbc.Domain)) + + By("checking the version in the PROJECT file") + Expect(projectConfig.GetVersion().String()).To(Equal("3")) + + By("validating the Memcached API with controller and resource") + memcachedGVK := resource.GVK{ + Group: "crew", + Domain: projectConfig.GetDomain(), // Adding the Domain field + Version: "v1", + Kind: "Memcached", + } + Expect(projectConfig.HasResource(memcachedGVK)).To(BeTrue(), "Memcached API should be present in the PROJECT file") + memcachedResource, err := projectConfig.GetResource(memcachedGVK) + Expect(err).NotTo(HaveOccurred(), "Memcached API should be retrievable") + Expect(memcachedResource.Controller).To(BeTrue(), "Memcached API should have a controller") + Expect(memcachedResource.API.Namespaced).To(BeTrue(), "Memcached API should be namespaced") + + By("validating the Webhook for Memcached API") + Expect(memcachedResource.Webhooks.Defaulting).To(BeTrue(), "Memcached API should have defaulting webhook") + Expect(memcachedResource.Webhooks.Validation).To(BeTrue(), "Memcached API should have validation webhook") + Expect(memcachedResource.Webhooks.Conversion).To(BeTrue(), "Memcached API should have a conversion webhook") + Expect(memcachedResource.Webhooks.WebhookVersion).To(Equal("v1"), "Memcached API should have webhook version v1") + + // Validate the presence of Admiral API without controller + By("validating the Admiral API without a controller") + admiralGVK := resource.GVK{ + Group: "crew", + Domain: projectConfig.GetDomain(), // Adding the Domain field + Version: "v1", + Kind: "Admiral", + } + Expect(projectConfig.HasResource(admiralGVK)).To(BeTrue(), "Admiral API should be present in the PROJECT file") + admiralResource, err := projectConfig.GetResource(admiralGVK) + Expect(err).NotTo(HaveOccurred(), "Admiral API should be retrievable") + Expect(admiralResource.Controller).To(BeFalse(), "Admiral API should not have a controller") + Expect(admiralResource.API.Namespaced).To(BeFalse(), "Admiral API should be cluster-scoped (not namespaced)") + Expect(admiralResource.Webhooks).To(BeNil(), "Admiral API should not have webhooks") + + By("validating the Captain API with controller and namespaced true") + captainGVK := resource.GVK{ + Group: "crew", + Domain: projectConfig.GetDomain(), // Adding the Domain field + Version: "v1", + Kind: "Captain", + } + Expect(projectConfig.HasResource(captainGVK)).To(BeTrue(), "Captain API should be present in the PROJECT file") + captainResource, err := projectConfig.GetResource(captainGVK) + Expect(err).NotTo(HaveOccurred(), "Captain API should be retrievable") + Expect(captainResource.Controller).To(BeTrue(), "Captain API should have a controller") + Expect(captainResource.API.Namespaced).To(BeTrue(), "Captain API should be namespaced") + Expect(captainResource.Webhooks).To(BeNil(), "Capitan API should not have webhooks") +} + +func getConfigFromProjectFile(projectFilePath string) config.Config { + By("loading the PROJECT configuration") + fs := afero.NewOsFs() + store := yaml.New(machinery.Filesystem{FS: fs}) + err := store.LoadFrom(projectFilePath) + Expect(err).NotTo(HaveOccurred(), "Failed to load PROJECT configuration") + + cfg := store.Config() + return cfg +} + +// Validate the PROJECT file for the Grafana plugin +func validateGrafanaPlugin(projectFile string) { + projectConfig := getConfigFromProjectFile(projectFile) + + By("checking the Grafana plugin in the PROJECT file") + var grafanaPluginConfig map[string]interface{} + err := projectConfig.DecodePluginConfig("grafana.kubebuilder.io/v1-alpha", &grafanaPluginConfig) + Expect(err).NotTo(HaveOccurred()) + Expect(grafanaPluginConfig).NotTo(BeNil()) +} + +// Validate the PROJECT file for the DeployImage plugin +func validateDeployImagePlugin(projectFile string) { + projectConfig := getConfigFromProjectFile(projectFile) + + By("decoding the DeployImage plugin configuration") + var deployImageConfig v1alpha1.PluginConfig + err := projectConfig.DecodePluginConfig("deploy-image.go.kubebuilder.io/v1-alpha", &deployImageConfig) + Expect(err).NotTo(HaveOccurred(), "Failed to decode DeployImage plugin configuration") + + // Validate the resource configuration + Expect(deployImageConfig.Resources).ToNot(BeEmpty(), "Expected at least one resource for the DeployImage plugin") + + resource := deployImageConfig.Resources[0] + Expect(resource.Group).To(Equal("crew"), "Expected group to be 'crew'") + Expect(resource.Kind).To(Equal("Memcached"), "Expected kind to be 'Memcached'") + + options := resource.Options + Expect(options.Image).To(Equal("memcached:1.6.15-alpine"), "Expected image to match") + Expect(options.ContainerCommand).To(Equal("memcached,--memory-limit=64,modern,-v"), + "Expected container command to match") + Expect(options.ContainerPort).To(Equal("11211"), "Expected container port to match") + Expect(options.RunAsUser).To(Equal("1001"), "Expected runAsUser to match") +}