From bb6c829bf7d3ef414f10f5881354fcb95fd1fcd7 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 | 394 +++++++++++++----------- 1 file changed, 210 insertions(+), 184 deletions(-) diff --git a/test/e2e/alphagenerate/generate_test.go b/test/e2e/alphagenerate/generate_test.go index fb93663f0f9..ae2c81d8098 100644 --- a/test/e2e/alphagenerate/generate_test.go +++ b/test/e2e/alphagenerate/generate_test.go @@ -18,124 +18,160 @@ package alphagenerate import ( "fmt" - "io" - "os" "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" - - "sigs.k8s.io/kubebuilder/v4/test/e2e/utils" ) var _ = Describe("kubebuilder", func() { - Context("alpha generate ", func() { + Context("alpha generate", func() { + var ( - kbc *utils.TestContext + 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() { - ReGenerateProject(kbc) + 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) + }) }) }) -// ReGenerateProject implements a project that is regenerated by kubebuilder. -func ReGenerateProject(kbc *utils.TestContext) { - var err error +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("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", + By("creating API definition") + err = kbc.CreateAPI( + "--group", kbc.Group, + "--version", kbc.Version, + "--kind", kbc.Kind, + "--namespaced", + "--resource", + "--controller", + "--make=false", ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) + Expect(err).NotTo(HaveOccurred(), "Failed to scaffold api with resource and controller") - By("create APIs with resource and controller") + By("creating API definition with controller and resource") err = kbc.CreateAPI( "--group", "crew", "--version", "v1", - "--kind", "Captain", + "--kind", "Memcached", "--namespaced", "--resource", "--controller", + "--make=false", ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) + Expect(err).NotTo(HaveOccurred(), "Failed to scaffold API with resource and controller") - By("create Webhooks with conversion and validating webhook") + By("creating Webhook for Memcached API") err = kbc.CreateWebhook( "--group", "crew", "--version", "v1", - "--kind", "Captain", + "--kind", "Memcached", + "--defaulting", "--programmatic-validation", "--conversion", ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) + Expect(err).NotTo(HaveOccurred(), "Failed to scaffold webhook for Memcached API") - By("create APIs non namespaced with resource and controller") + By("creating API without controller (Admiral)") err = kbc.CreateAPI( "--group", "crew", "--version", "v1", "--kind", "Admiral", + "--controller=false", + "--resource=true", "--namespaced=false", - "--resource", - "--controller", + "--make=false", ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) + Expect(err).NotTo(HaveOccurred(), "Failed to scaffold API without controller") - By("create APIs with deploy-image plugin") + 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", @@ -143,128 +179,118 @@ func ReGenerateProject(kbc *utils.TestContext) { "--image-container-command=memcached,--memory-limit=64,modern,-v", "--image-container-port=11211", "--run-as-user=1001", - "--plugins=\"deploy-image/v1-alpha\"", + "--plugins=deploy-image/v1-alpha", ) - ExpectWithOffset(1, err).NotTo(HaveOccurred()) + Expect(err).NotTo(HaveOccurred(), "Failed to create API with Deploy Image Plugin") +} - 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) +// 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(bytesBefore).Should(Equal(bytesAfter)) + 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") }