From f08bc6e8a914474a1f881de2dc0a9d1dca28f180 Mon Sep 17 00:00:00 2001 From: Patrick Seidensal Date: Mon, 2 Dec 2024 09:31:29 +0100 Subject: [PATCH] Add E2E tests Refers to #2943 --- .../chart-with-template-vars/Chart.yaml | 24 +++ .../chart-with-template-vars/fleet.yaml | 4 + .../templates/configmap.yaml | 7 + e2e/assets/status/gitrepo.yaml | 11 ++ e2e/single-cluster/gitrepo_test.go | 8 +- e2e/single-cluster/status_test.go | 168 ++++++++++++++++++ 6 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 e2e/assets/status/chart-with-template-vars/Chart.yaml create mode 100644 e2e/assets/status/chart-with-template-vars/fleet.yaml create mode 100644 e2e/assets/status/chart-with-template-vars/templates/configmap.yaml create mode 100644 e2e/assets/status/gitrepo.yaml diff --git a/e2e/assets/status/chart-with-template-vars/Chart.yaml b/e2e/assets/status/chart-with-template-vars/Chart.yaml new file mode 100644 index 0000000000..017949df22 --- /dev/null +++ b/e2e/assets/status/chart-with-template-vars/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: chart-with-template-vars +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/e2e/assets/status/chart-with-template-vars/fleet.yaml b/e2e/assets/status/chart-with-template-vars/fleet.yaml new file mode 100644 index 0000000000..01e3b9bbaf --- /dev/null +++ b/e2e/assets/status/chart-with-template-vars/fleet.yaml @@ -0,0 +1,4 @@ +helm: + values: + templatedLabel: "${ .ClusterLabels.foo }-foo" + releaseName: reproducer diff --git a/e2e/assets/status/chart-with-template-vars/templates/configmap.yaml b/e2e/assets/status/chart-with-template-vars/templates/configmap.yaml new file mode 100644 index 0000000000..2327303f39 --- /dev/null +++ b/e2e/assets/status/chart-with-template-vars/templates/configmap.yaml @@ -0,0 +1,7 @@ +kind: ConfigMap +apiVersion: v1 +metadata: + name: chart-with-template-vars-configmap + namespace: fleet-local +data: + foo: bar diff --git a/e2e/assets/status/gitrepo.yaml b/e2e/assets/status/gitrepo.yaml new file mode 100644 index 0000000000..c34afdb185 --- /dev/null +++ b/e2e/assets/status/gitrepo.yaml @@ -0,0 +1,11 @@ +kind: GitRepo +apiVersion: fleet.cattle.io/v1alpha1 +metadata: + name: {{.Name}} + namespace: fleet-local +spec: + repo: {{.Repo}} + branch: {{.Branch}} + targetNamespace: {{.TargetNamespace}} + paths: + - examples diff --git a/e2e/single-cluster/gitrepo_test.go b/e2e/single-cluster/gitrepo_test.go index 6989a2d834..0ac10257c7 100644 --- a/e2e/single-cluster/gitrepo_test.go +++ b/e2e/single-cluster/gitrepo_test.go @@ -194,7 +194,7 @@ var _ = Describe("Monitoring Git repos via HTTP for change", Label("infra-setup" }, } Eventually(func(g Gomega) { - status := getGitRepoStatus(k, gitrepoName) + status := getGitRepoStatus(g, k, gitrepoName) g.Expect(status).To(matchGitRepoStatus(expectedStatus)) }).Should(Succeed()) @@ -355,7 +355,7 @@ var _ = Describe("Monitoring Git repos via HTTP for change", Label("infra-setup" }, } Eventually(func(g Gomega) { - status := getGitRepoStatus(k, gitrepoName) + status := getGitRepoStatus(g, k, gitrepoName) g.Expect(status).To(matchGitRepoStatus(expectedStatus)) }).Should(Succeed()) @@ -381,10 +381,10 @@ func replace(path string, s string, r string) { } // getGitRepoStatus retrieves the status of the gitrepo with the provided name. -func getGitRepoStatus(k kubectl.Command, name string) fleet.GitRepoStatus { +func getGitRepoStatus(g Gomega, k kubectl.Command, name string) fleet.GitRepoStatus { gr, err := k.Get("gitrepo", name, "-o=json") - Expect(err).ToNot(HaveOccurred()) + g.Expect(err).ToNot(HaveOccurred()) var gitrepo fleet.GitRepo _ = json.Unmarshal([]byte(gr), &gitrepo) diff --git a/e2e/single-cluster/status_test.go b/e2e/single-cluster/status_test.go index 13d60e44dd..fbc8bbf06e 100644 --- a/e2e/single-cluster/status_test.go +++ b/e2e/single-cluster/status_test.go @@ -1,13 +1,23 @@ package singlecluster_test import ( + "encoding/json" "errors" + "fmt" + "math/rand" + "os" + "path" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/rancher/fleet/e2e/testenv" + "github.com/rancher/fleet/e2e/testenv/githelper" "github.com/rancher/fleet/e2e/testenv/kubectl" + fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" + "github.com/rancher/wrangler/v3/pkg/genericcondition" + corev1 "k8s.io/api/core/v1" ) var _ = Describe("Checks status updates happen for a simple deployment", Ordered, func() { @@ -108,3 +118,161 @@ var _ = Describe("Checks status updates happen for a simple deployment", Ordered }) }) }) + +var _ = Describe("Checks that template errors are shown in bundles and gitrepos", Ordered, Label("infra-setup"), func() { + var ( + tmpDir string + cloneDir string + k kubectl.Command + gh *githelper.Git + repoName string + inClusterRepoURL string + gitrepoName string + r = rand.New(rand.NewSource(GinkgoRandomSeed())) + targetNamespace string + ) + + BeforeEach(func() { + k = env.Kubectl.Namespace(env.Namespace) + repoName = "repo" + }) + + JustBeforeEach(func() { + // Build git repo URL reachable _within_ the cluster, for the GitRepo + host, err := githelper.BuildGitHostname(env.Namespace) + Expect(err).ToNot(HaveOccurred()) + + addr, err := githelper.GetExternalRepoAddr(env, port, repoName) + Expect(err).ToNot(HaveOccurred()) + gh = githelper.NewHTTP(addr) + + inClusterRepoURL = gh.GetInClusterURL(host, port, repoName) + + tmpDir, _ = os.MkdirTemp("", "fleet-") + cloneDir = path.Join(tmpDir, repoName) + + gitrepoName = testenv.RandomFilename("status-test", r) + + _, err = gh.Create(cloneDir, testenv.AssetPath("status/chart-with-template-vars"), "examples") + Expect(err).ToNot(HaveOccurred()) + + err = testenv.ApplyTemplate(k, testenv.AssetPath("status/gitrepo.yaml"), struct { + Name string + Repo string + Branch string + TargetNamespace string + }{ + gitrepoName, + inClusterRepoURL, + gh.Branch, + targetNamespace, // to avoid conflicts with other tests + }) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + _ = os.RemoveAll(tmpDir) + + _, err := k.Delete("gitrepo", gitrepoName) + Expect(err).ToNot(HaveOccurred()) + + // Check that the bundle deployment resource has been deleted + Eventually(func(g Gomega) { + out, _ := k.Get( + "bundledeployments", + "-A", + "-l", + fmt.Sprintf("fleet.cattle.io/repo-name=%s", gitrepoName), + ) + g.Expect(out).To(ContainSubstring("No resources found")) + }).Should(Succeed()) + + // Deleting the targetNamespace is not necessary when the GitRepo did not successfully + // render, as in a few test cases here. If no targetNamespace was created, trying to delete + // the namespace will result in an error, which is why we are not checking for errors when + // deleting namespaces here. + _, _ = k.Delete("ns", targetNamespace) + }) + + expectNoError := func(g Gomega, conditions []genericcondition.GenericCondition) { + for _, condition := range conditions { + if condition.Type == string(fleet.Ready) { + g.Expect(condition.Status).To(Equal(corev1.ConditionTrue)) + g.Expect(condition.Message).To(BeEmpty()) + break + } + } + } + + expectTargetingError := func(g Gomega, conditions []genericcondition.GenericCondition) { + found := false + for _, condition := range conditions { + if condition.Type == string(fleet.Ready) { + g.Expect(condition.Status).To(Equal(corev1.ConditionFalse)) + g.Expect(condition.Message).To(ContainSubstring("Targeting error")) + g.Expect(condition.Message).To( + ContainSubstring( + "<.ClusterLabels.foo>: map has no entry for key \"foo\"")) + found = true + break + } + } + g.Expect(found).To(BeTrue()) + } + + ensureClusterHasLabelFoo := func() (string, error) { + return k.Namespace("fleet-local"). + Patch("cluster", "local", "--type", "json", "--patch", + `[{"op": "add", "path": "/metadata/labels/foo", "value": "bar"}]`) + } + + ensureClusterHasNoLabelFoo := func() (string, error) { + return k.Namespace("fleet-local"). + Patch("cluster", "local", "--type", "json", "--patch", + `[{"op": "remove", "path": "/metadata/labels/foo"}]`) + } + + When("a git repository is created that contains a template error", func() { + BeforeEach(func() { + targetNamespace = testenv.NewNamespaceName("target", r) + }) + + It("should have an error in the bundle", func() { + _, _ = ensureClusterHasNoLabelFoo() + Eventually(func(g Gomega) { + status := getBundleStatus(g, k, gitrepoName+"-examples") + expectTargetingError(g, status.Conditions) + }).Should(Succeed()) + }) + + It("should have an error in the gitrepo", func() { + _, _ = ensureClusterHasNoLabelFoo() + Eventually(func(g Gomega) { + status := getGitRepoStatus(g, k, gitrepoName) + expectTargetingError(g, status.Conditions) + }).Should(Succeed()) + }) + }) + + When("a git repository is created that contains no template error", func() { + It("should have no error in the bundle", func() { + _, _ = ensureClusterHasLabelFoo() + Eventually(func(g Gomega) { + status := getBundleStatus(g, k, gitrepoName+"-examples") + expectNoError(g, status.Conditions) + }).Should(Succeed()) + }) + }) +}) + +// getBundleStatus retrieves the status of the bundle with the provided name. +func getBundleStatus(g Gomega, k kubectl.Command, name string) fleet.BundleStatus { + gr, err := k.Get("bundle", name, "-o=json") + + g.Expect(err).ToNot(HaveOccurred()) + + var bundle fleet.Bundle + _ = json.Unmarshal([]byte(gr), &bundle) + + return bundle.Status +}