From 790ec77c6a10555bde3f3b3d8ddc884ac2688ac7 Mon Sep 17 00:00:00 2001 From: Katrina Ronquillo Date: Sun, 20 Oct 2024 15:24:34 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20typos=20in=20tes?= =?UTF-8?q?t=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/controller/template_controller_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/controller/template_controller_test.go b/internal/controller/template_controller_test.go index 141e750..f8386fc 100644 --- a/internal/controller/template_controller_test.go +++ b/internal/controller/template_controller_test.go @@ -36,7 +36,7 @@ var _ = Describe("Template Controller Integration Test", func() { BeforeEach(func() { ctx = context.Background() - By("ensuring the namespace exists") + By("ensuring the templates namespace exists") namespace := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: namespaceName, @@ -59,11 +59,10 @@ var _ = Describe("Template Controller Integration Test", func() { fmt.Printf("Namespace: %s, Phase: %s\n", namespace.Name, namespace.Status.Phase) return fmt.Errorf("namespace not active yet") }, 10*time.Second, 2*time.Second).Should(Succeed()) - }) AfterEach(func() { - By("deleting all applied resources") + By("deleting all test resources") exampleFolderPath := path.Join("../", "../", "examples") exampleFolders, err := utils.GetSubDirs(exampleFolderPath) Expect(err).NotTo(HaveOccurred()) @@ -91,7 +90,8 @@ var _ = Describe("Template Controller Integration Test", func() { } }) - It("repeate apply and verify examples to check some race conditions", func() { + + It("repeat apply and verify examples to check some race conditions", func() { exampleFolderPath := path.Join("../", "../", "examples") exampleFolders, err := utils.GetSubDirs(exampleFolderPath) @@ -107,7 +107,6 @@ var _ = Describe("Template Controller Integration Test", func() { exampleFolderPath := path.Join("..", "..", "examples", exampleFolder) verifyExampleOutput(exampleFolderPath, "out.yaml") } - }) }) From c684fade1a1904f891f79d14582a5bca480cba9b Mon Sep 17 00:00:00 2001 From: Katrina Ronquillo Date: Mon, 21 Oct 2024 20:31:50 -0400 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=85=20=20Add=20test=20to=20ensure=20T?= =?UTF-8?q?emplate=20triggers=20deletion=20of=20managed=20resources?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{input => }/templates/aws-auth.yaml | 0 .../{input => }/templates/generate_role.yaml | 0 .../controller/template_controller_test.go | 221 +++++++++++------- 3 files changed, 134 insertions(+), 87 deletions(-) rename examples/aws-auth/{input => }/templates/aws-auth.yaml (100%) rename examples/extending-rbac/{input => }/templates/generate_role.yaml (100%) diff --git a/examples/aws-auth/input/templates/aws-auth.yaml b/examples/aws-auth/templates/aws-auth.yaml similarity index 100% rename from examples/aws-auth/input/templates/aws-auth.yaml rename to examples/aws-auth/templates/aws-auth.yaml diff --git a/examples/extending-rbac/input/templates/generate_role.yaml b/examples/extending-rbac/templates/generate_role.yaml similarity index 100% rename from examples/extending-rbac/input/templates/generate_role.yaml rename to examples/extending-rbac/templates/generate_role.yaml diff --git a/internal/controller/template_controller_test.go b/internal/controller/template_controller_test.go index f8386fc..b6fd566 100644 --- a/internal/controller/template_controller_test.go +++ b/internal/controller/template_controller_test.go @@ -7,7 +7,6 @@ import ( "os" "path" "path/filepath" - "strings" "time" sigyaml "sigs.k8s.io/yaml" @@ -59,87 +58,131 @@ var _ = Describe("Template Controller Integration Test", func() { fmt.Printf("Namespace: %s, Phase: %s\n", namespace.Name, namespace.Status.Phase) return fmt.Errorf("namespace not active yet") }, 10*time.Second, 2*time.Second).Should(Succeed()) - }) - AfterEach(func() { - By("deleting all test resources") + By("creating input resources") exampleFolderPath := path.Join("../", "../", "examples") exampleFolders, err := utils.GetSubDirs(exampleFolderPath) Expect(err).NotTo(HaveOccurred()) for _, exampleFolder := range exampleFolders { - deleteExampleResources(ctx, path.Join(exampleFolderPath, exampleFolder, "input")) + inputFolder := path.Join(exampleFolderPath, exampleFolder, "input") + applyYAMLFilesFromDirectory(ctx, inputFolder) } - }) - It("should apply and verify examples", func() { - + AfterEach(func() { + By("deleting input resources and templates") exampleFolderPath := path.Join("../", "../", "examples") exampleFolders, err := utils.GetSubDirs(exampleFolderPath) Expect(err).NotTo(HaveOccurred()) for _, exampleFolder := range exampleFolders { - By(fmt.Sprintf("Running example %s", exampleFolder)) - applyExampleResources(ctx, path.Join(exampleFolderPath, exampleFolder)) + inputFolder := path.Join(exampleFolderPath, exampleFolder, "input") + deleteYAMLFilesFromDirectory(ctx, inputFolder) + templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") + deleteYAMLFilesFromDirectory(ctx, templateFolder) } + }) - // another eventuallly block to check if the output.yaml file has been created - By("verifying that the output.yaml file has been created") - for _, exampleFolder := range exampleFolders { - exampleFolderPath := path.Join("..", "..", "examples", exampleFolder) - verifyExampleOutput(exampleFolderPath, "out.yaml") - } + Context("When Template is applied", func() { + It("creates expected managed resources", func() { + exampleFolderPath := path.Join("../", "../", "examples") + exampleFolders, err := utils.GetSubDirs(exampleFolderPath) + Expect(err).NotTo(HaveOccurred()) + for _, exampleFolder := range exampleFolders { + By(fmt.Sprintf("Running example %s", exampleFolder)) + + By("applying example templates") + templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") + applyYAMLFilesFromDirectory(ctx, templateFolder) + + By("verifying that the output.yaml file has been created") + verifyExampleOutput(path.Join(exampleFolderPath, exampleFolder), "out.yaml") + } + }) + + It("creates expected managed resources again to test for race conditions", func() { + exampleFolderPath := path.Join("../", "../", "examples") + exampleFolders, err := utils.GetSubDirs(exampleFolderPath) + Expect(err).NotTo(HaveOccurred()) + + for _, exampleFolder := range exampleFolders { + By(fmt.Sprintf("Running example %s", exampleFolder)) + + By("applying example templates") + templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") + applyYAMLFilesFromDirectory(ctx, templateFolder) + + By("verifying that the output.yaml file has been created") + verifyExampleOutput(path.Join(exampleFolderPath, exampleFolder), "out.yaml") + } + }) }) - It("repeat apply and verify examples to check some race conditions", func() { + Context("When Template is deleted", func() { + JustBeforeEach(func() { + By("applying example templates") + exampleFolderPath := path.Join("../", "../", "examples") + exampleFolders, err := utils.GetSubDirs(exampleFolderPath) + Expect(err).NotTo(HaveOccurred()) + for _, exampleFolder := range exampleFolders { + templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") + applyYAMLFilesFromDirectory(ctx, templateFolder) + } + }) - exampleFolderPath := path.Join("../", "../", "examples") - exampleFolders, err := utils.GetSubDirs(exampleFolderPath) - Expect(err).NotTo(HaveOccurred()) - for _, exampleFolder := range exampleFolders { - By(fmt.Sprintf("Running example %s", exampleFolder)) - applyExampleResources(ctx, path.Join(exampleFolderPath, exampleFolder)) - } + It("deletes managed resources", func() { + exampleFolderPath := path.Join("../", "../", "examples") + exampleFolders, err := utils.GetSubDirs(exampleFolderPath) + Expect(err).NotTo(HaveOccurred()) - // another eventuallly block to check if the output.yaml file has been created - By("verifying that the output.yaml file has been created") - for _, exampleFolder := range exampleFolders { - exampleFolderPath := path.Join("..", "..", "examples", exampleFolder) - verifyExampleOutput(exampleFolderPath, "out.yaml") - } + for _, exampleFolder := range exampleFolders { + By("deleting example template") + templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") + deleteYAMLFilesFromDirectory(ctx, templateFolder) + + By("verifying expected managed resources got deleted") + expectedOutputPath := path.Join(exampleFolderPath, exampleFolder, "expected", "out.yaml") + expectedOutputResource, err := getK8sClientObject(expectedOutputPath) + Expect(err).NotTo(HaveOccurred()) + waitForDeletion(ctx, expectedOutputResource, 10*time.Second, 5*time.Millisecond) + } + }) }) }) -// Function to apply resources from an example -func applyExampleResources(ctx context.Context, examplePath string) { - inputPath := filepath.Join(examplePath, "input") - // get all folders in the input directory - folders, err := utils.GetSubDirs(inputPath) +// Get Kubernetes object defined by the YAML in the given path, filePath +func getK8sClientObject(path string) (*unstructured.Unstructured, error) { + fileInfo, err := os.Stat(path) Expect(err).NotTo(HaveOccurred()) - for _, folder := range folders { - applyYAMLFilesFromDirectory(ctx, path.Join(inputPath, folder)) + + if fileInfo.IsDir() || (filepath.Ext(fileInfo.Name()) != ".yaml" && filepath.Ext(fileInfo.Name()) != ".yml") { + return nil, fmt.Errorf("Invalid path: %s should be a YAML file", path) } -} -// Function to apply all YAML files from a directory -func applyYAMLFilesFromDirectory(ctx context.Context, dir string) { - files, err := os.ReadDir(dir) + content, err := os.ReadFile(path) Expect(err).NotTo(HaveOccurred()) - for _, file := range files { - if !file.IsDir() && (strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml")) { - filePath := filepath.Join(dir, file.Name()) + decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) + obj := &unstructured.Unstructured{} + _, _, err = decoder.Decode(content, nil, obj) + Expect(err).NotTo(HaveOccurred()) - content, err := os.ReadFile(filePath) - Expect(err).NotTo(HaveOccurred()) + // Remove resourceVersion if set + obj.SetResourceVersion("") - decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) - obj := &unstructured.Unstructured{} - _, _, err = decoder.Decode(content, nil, obj) - Expect(err).NotTo(HaveOccurred()) + return obj, nil +} - // Remove resourceVersion if set - obj.SetResourceVersion("") +// Apply all YAML files defined in the given directory, dir, as well as all sub-directories +func applyYAMLFilesFromDirectory(ctx context.Context, dir string) { + err := filepath.WalkDir(dir, func(path string, entry os.DirEntry, err error) error { + Expect(err).NotTo(HaveOccurred()) + + // Entry is a YAML file - Apply manifest + if !entry.IsDir() && (filepath.Ext(entry.Name()) == ".yaml" || filepath.Ext(entry.Name()) == ".yml") { + // Get K8s resource from YAML file + obj, err := getK8sClientObject(path) + Expect(err).NotTo(HaveOccurred()) // Apply the resource to the cluster err = k8sClient.Create(ctx, obj) @@ -148,44 +191,57 @@ func applyYAMLFilesFromDirectory(ctx context.Context, dir string) { } Expect(err).NotTo(HaveOccurred()) } - } + + // Entry is a directory - Continue traversal + return nil + }) + Expect(err).NotTo(HaveOccurred()) +} + +// Waits until the given K8s Client resource is deleted, or times out +func waitForDeletion(ctx context.Context, obj *unstructured.Unstructured, timeout time.Duration, interval time.Duration) { + fmt.Printf("Waiting for deletion of resource %s/%s\n", obj.GetKind(), obj.GetName()) + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(obj), obj) + if errors.IsNotFound(err) { + fmt.Printf("Resource %s/%s deleted done\n", obj.GetKind(), obj.GetName()) + return nil + } + if err != nil { + return err + } + + fmt.Printf("Resource %s/%s not deleted yet\n", obj.GetKind(), obj.GetName()) + return fmt.Errorf("Resource %s/%s still exists", obj.GetKind(), obj.GetName()) + }, timeout, interval).Should(Succeed()) } +// Delete all YAML files defined in the given directory, dir, as well as all sub-directories func deleteYAMLFilesFromDirectory(ctx context.Context, dir string) { - files, err := os.ReadDir(dir) - Expect(err).NotTo(HaveOccurred()) + err := filepath.WalkDir(dir, func(path string, entry os.DirEntry, err error) error { + Expect(err).NotTo(HaveOccurred()) - for _, file := range files { - if !file.IsDir() && (strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml")) { - filePath := filepath.Join(dir, file.Name()) - fileData, err := os.ReadFile(filePath) - Expect(err).NotTo(HaveOccurred()) - decoder := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) - obj := &unstructured.Unstructured{} - _, _, err = decoder.Decode(fileData, nil, obj) + // Entry is a YAML file - Delete manifest + if !entry.IsDir() && (filepath.Ext(entry.Name()) == ".yaml" || filepath.Ext(entry.Name()) == ".yml") { + // Get K8s resource from YAML file + obj, err := getK8sClientObject(path) Expect(err).NotTo(HaveOccurred()) + fmt.Printf("Deleting %s/%s\n", obj.GetKind(), obj.GetName()) err = k8sClient.Delete(ctx, obj) if errors.IsNotFound(err) { - continue + return nil } Expect(err).NotTo(HaveOccurred()) - fmt.Printf("Deleting %s/%s\n", obj.GetKind(), obj.GetName()) + // Wait for the resource to be deleted - Eventually(func() error { - err = k8sClient.Get(ctx, client.ObjectKeyFromObject(obj), obj) - if errors.IsNotFound(err) { - fmt.Printf("Resource %s/%s deleted done\n", obj.GetKind(), obj.GetName()) - return nil - } - if err != nil { - return err - } - fmt.Printf("Resource %s/%s not deleted yet\n", obj.GetKind(), obj.GetName()) - return fmt.Errorf("resource still exists") - }, 10*time.Second, 5*time.Millisecond).Should(Succeed()) + waitForDeletion(ctx, obj, 10*time.Second, 5*time.Millisecond) } - } + + // Entry is a directory - Continue traversal + return nil + }) + Expect(err).NotTo(HaveOccurred()) } func verifyExampleOutput(exampleFolder string, exampleFile string) { @@ -264,13 +320,4 @@ func verifyExampleOutput(exampleFolder string, exampleFile string) { return nil }, 10*time.Second, 2*time.Second).Should(Succeed()) - -} - -func deleteExampleResources(ctx context.Context, inputPath string) { - folders, err := utils.GetSubDirs(inputPath) - Expect(err).NotTo(HaveOccurred()) - for _, folder := range folders { - deleteYAMLFilesFromDirectory(ctx, path.Join(inputPath, folder)) - } } From 70f1510f94e0a45e6646d98bd4177bc0d1a2fe1e Mon Sep 17 00:00:00 2001 From: Katrina Ronquillo Date: Sun, 27 Oct 2024 19:44:11 +0000 Subject: [PATCH 3/4] :rotating_light: Extract repeating logic to check for YAML file to function and fix linter error --- internal/controller/template_controller_test.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/internal/controller/template_controller_test.go b/internal/controller/template_controller_test.go index b6fd566..6e16f1c 100644 --- a/internal/controller/template_controller_test.go +++ b/internal/controller/template_controller_test.go @@ -150,12 +150,17 @@ var _ = Describe("Template Controller Integration Test", func() { }) }) -// Get Kubernetes object defined by the YAML in the given path, filePath -func getK8sClientObject(path string) (*unstructured.Unstructured, error) { +// Returns True if the given path refers to a YAML file +func isYAMLFile(path string) bool { fileInfo, err := os.Stat(path) Expect(err).NotTo(HaveOccurred()) - if fileInfo.IsDir() || (filepath.Ext(fileInfo.Name()) != ".yaml" && filepath.Ext(fileInfo.Name()) != ".yml") { + return !fileInfo.IsDir() && (filepath.Ext(fileInfo.Name()) == ".yaml" || filepath.Ext(fileInfo.Name()) == ".yml") +} + +// Get Kubernetes object defined by the YAML in the given path +func getK8sClientObject(path string) (*unstructured.Unstructured, error) { + if isValidPath := isYAMLFile(path); !isValidPath { return nil, fmt.Errorf("Invalid path: %s should be a YAML file", path) } @@ -179,7 +184,7 @@ func applyYAMLFilesFromDirectory(ctx context.Context, dir string) { Expect(err).NotTo(HaveOccurred()) // Entry is a YAML file - Apply manifest - if !entry.IsDir() && (filepath.Ext(entry.Name()) == ".yaml" || filepath.Ext(entry.Name()) == ".yml") { + if isYAMLFile(path) { // Get K8s resource from YAML file obj, err := getK8sClientObject(path) Expect(err).NotTo(HaveOccurred()) @@ -199,7 +204,7 @@ func applyYAMLFilesFromDirectory(ctx context.Context, dir string) { } // Waits until the given K8s Client resource is deleted, or times out -func waitForDeletion(ctx context.Context, obj *unstructured.Unstructured, timeout time.Duration, interval time.Duration) { +func waitForDeletion(ctx context.Context, obj *unstructured.Unstructured, timeout, interval time.Duration) { fmt.Printf("Waiting for deletion of resource %s/%s\n", obj.GetKind(), obj.GetName()) Eventually(func() error { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(obj), obj) @@ -222,7 +227,7 @@ func deleteYAMLFilesFromDirectory(ctx context.Context, dir string) { Expect(err).NotTo(HaveOccurred()) // Entry is a YAML file - Delete manifest - if !entry.IsDir() && (filepath.Ext(entry.Name()) == ".yaml" || filepath.Ext(entry.Name()) == ".yml") { + if isYAMLFile(path) { // Get K8s resource from YAML file obj, err := getK8sClientObject(path) Expect(err).NotTo(HaveOccurred()) From f4a7de2dffefddb414a21032d86377862a90c69b Mon Sep 17 00:00:00 2001 From: Katrina Ronquillo Date: Fri, 1 Nov 2024 16:51:55 +0000 Subject: [PATCH 4/4] :rewind: Restore file structure for examples --- .../{ => input}/templates/aws-auth.yaml | 0 .../{ => input}/templates/generate_role.yaml | 0 .../controller/template_controller_test.go | 32 ++++++++++++------- 3 files changed, 21 insertions(+), 11 deletions(-) rename examples/aws-auth/{ => input}/templates/aws-auth.yaml (100%) rename examples/extending-rbac/{ => input}/templates/generate_role.yaml (100%) diff --git a/examples/aws-auth/templates/aws-auth.yaml b/examples/aws-auth/input/templates/aws-auth.yaml similarity index 100% rename from examples/aws-auth/templates/aws-auth.yaml rename to examples/aws-auth/input/templates/aws-auth.yaml diff --git a/examples/extending-rbac/templates/generate_role.yaml b/examples/extending-rbac/input/templates/generate_role.yaml similarity index 100% rename from examples/extending-rbac/templates/generate_role.yaml rename to examples/extending-rbac/input/templates/generate_role.yaml diff --git a/internal/controller/template_controller_test.go b/internal/controller/template_controller_test.go index 6e16f1c..a387996 100644 --- a/internal/controller/template_controller_test.go +++ b/internal/controller/template_controller_test.go @@ -7,6 +7,7 @@ import ( "os" "path" "path/filepath" + "slices" "time" sigyaml "sigs.k8s.io/yaml" @@ -65,7 +66,8 @@ var _ = Describe("Template Controller Integration Test", func() { Expect(err).NotTo(HaveOccurred()) for _, exampleFolder := range exampleFolders { inputFolder := path.Join(exampleFolderPath, exampleFolder, "input") - applyYAMLFilesFromDirectory(ctx, inputFolder) + templateFolder := path.Join(inputFolder, "templates") + applyYAMLFilesFromDirectory(ctx, inputFolder, []string{templateFolder}) } }) @@ -77,8 +79,6 @@ var _ = Describe("Template Controller Integration Test", func() { for _, exampleFolder := range exampleFolders { inputFolder := path.Join(exampleFolderPath, exampleFolder, "input") deleteYAMLFilesFromDirectory(ctx, inputFolder) - templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") - deleteYAMLFilesFromDirectory(ctx, templateFolder) } }) @@ -92,8 +92,8 @@ var _ = Describe("Template Controller Integration Test", func() { By(fmt.Sprintf("Running example %s", exampleFolder)) By("applying example templates") - templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") - applyYAMLFilesFromDirectory(ctx, templateFolder) + templateFolder := path.Join(exampleFolderPath, exampleFolder, "input", "templates") + applyYAMLFilesFromDirectory(ctx, templateFolder, nil) By("verifying that the output.yaml file has been created") verifyExampleOutput(path.Join(exampleFolderPath, exampleFolder), "out.yaml") @@ -109,8 +109,8 @@ var _ = Describe("Template Controller Integration Test", func() { By(fmt.Sprintf("Running example %s", exampleFolder)) By("applying example templates") - templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") - applyYAMLFilesFromDirectory(ctx, templateFolder) + templateFolder := path.Join(exampleFolderPath, exampleFolder, "input", "templates") + applyYAMLFilesFromDirectory(ctx, templateFolder, nil) By("verifying that the output.yaml file has been created") verifyExampleOutput(path.Join(exampleFolderPath, exampleFolder), "out.yaml") @@ -125,8 +125,8 @@ var _ = Describe("Template Controller Integration Test", func() { exampleFolders, err := utils.GetSubDirs(exampleFolderPath) Expect(err).NotTo(HaveOccurred()) for _, exampleFolder := range exampleFolders { - templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") - applyYAMLFilesFromDirectory(ctx, templateFolder) + templateFolder := path.Join(exampleFolderPath, exampleFolder, "input", "templates") + applyYAMLFilesFromDirectory(ctx, templateFolder, nil) } }) @@ -137,7 +137,7 @@ var _ = Describe("Template Controller Integration Test", func() { for _, exampleFolder := range exampleFolders { By("deleting example template") - templateFolder := path.Join(exampleFolderPath, exampleFolder, "templates") + templateFolder := path.Join(exampleFolderPath, exampleFolder, "input", "templates") deleteYAMLFilesFromDirectory(ctx, templateFolder) By("verifying expected managed resources got deleted") @@ -179,10 +179,20 @@ func getK8sClientObject(path string) (*unstructured.Unstructured, error) { } // Apply all YAML files defined in the given directory, dir, as well as all sub-directories -func applyYAMLFilesFromDirectory(ctx context.Context, dir string) { +// Ignores paths specified in excludePaths +func applyYAMLFilesFromDirectory(ctx context.Context, dir string, excludePaths []string) { + if excludePaths == nil { + excludePaths = []string{} + } + err := filepath.WalkDir(dir, func(path string, entry os.DirEntry, err error) error { Expect(err).NotTo(HaveOccurred()) + // Ignore paths specified in excludePaths + if slices.Contains(excludePaths, path) { + return filepath.SkipDir + } + // Entry is a YAML file - Apply manifest if isYAMLFile(path) { // Get K8s resource from YAML file