diff --git a/charts/fleet-crd/templates/crds.yaml b/charts/fleet-crd/templates/crds.yaml index ac30aacd1f..2977c20105 100644 --- a/charts/fleet-crd/templates/crds.yaml +++ b/charts/fleet-crd/templates/crds.yaml @@ -245,6 +245,10 @@ spec: description: DisableDNS can be used to customize Helm's EnableDNS option, which Fleet sets to `true` by default. type: boolean + disableDependencyUpdate: + description: DisableDependencyUpdate allows skipping chart + dependencies update + type: boolean disablePreProcess: description: DisablePreProcess disables template processing in values @@ -547,6 +551,10 @@ spec: description: DisableDNS can be used to customize Helm's EnableDNS option, which Fleet sets to `true` by default. type: boolean + disableDependencyUpdate: + description: DisableDependencyUpdate allows skipping chart + dependencies update + type: boolean disablePreProcess: description: DisablePreProcess disables template processing in values @@ -1239,6 +1247,10 @@ spec: description: DisableDNS can be used to customize Helm's EnableDNS option, which Fleet sets to `true` by default. type: boolean + disableDependencyUpdate: + description: DisableDependencyUpdate allows skipping chart dependencies + update + type: boolean disablePreProcess: description: DisablePreProcess disables template processing in values @@ -1934,6 +1946,10 @@ spec: description: DisableDNS can be used to customize Helm's EnableDNS option, which Fleet sets to `true` by default. type: boolean + disableDependencyUpdate: + description: DisableDependencyUpdate allows skipping chart + dependencies update + type: boolean disablePreProcess: description: DisablePreProcess disables template processing in values diff --git a/e2e/assets/deps-charts/gitrepo.yaml b/e2e/assets/deps-charts/gitrepo.yaml new file mode 100644 index 0000000000..a43265ecb7 --- /dev/null +++ b/e2e/assets/deps-charts/gitrepo.yaml @@ -0,0 +1,11 @@ +kind: GitRepo +apiVersion: fleet.cattle.io/v1alpha1 +metadata: + name: {{.Name}} +spec: + repo: {{.Repo}} + branch: {{.Branch}} + helmSecretName: "helm-secret" + targetNamespace: {{.TargetNamespace}} + paths: + - examples diff --git a/e2e/assets/deps-charts/no-fleet-yaml/Chart.yaml b/e2e/assets/deps-charts/no-fleet-yaml/Chart.yaml new file mode 100644 index 0000000000..893980b620 --- /dev/null +++ b/e2e/assets/deps-charts/no-fleet-yaml/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +appVersion: 1.16.0 +description: A chart for testing dependencies +name: deps-chart +type: application +version: 1.0.0 +dependencies: +- name: sleeper-chart + version: "0.1.0" + repository: {{.HelmRepoUrl}} diff --git a/e2e/assets/deps-charts/no-fleet-yaml/templates/configmap.yaml b/e2e/assets/deps-charts/no-fleet-yaml/templates/configmap.yaml new file mode 100644 index 0000000000..c6b509ce7b --- /dev/null +++ b/e2e/assets/deps-charts/no-fleet-yaml/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-simple-deps-chart +data: + test: "valuedeps" + name: {{ .Values.name }} diff --git a/e2e/assets/deps-charts/no-fleet-yaml/values.yaml b/e2e/assets/deps-charts/no-fleet-yaml/values.yaml new file mode 100644 index 0000000000..1d9970923f --- /dev/null +++ b/e2e/assets/deps-charts/no-fleet-yaml/values.yaml @@ -0,0 +1 @@ +name: deps-default-name diff --git a/e2e/assets/deps-charts/with-fleet-yaml/Chart.yaml b/e2e/assets/deps-charts/with-fleet-yaml/Chart.yaml new file mode 100644 index 0000000000..893980b620 --- /dev/null +++ b/e2e/assets/deps-charts/with-fleet-yaml/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +appVersion: 1.16.0 +description: A chart for testing dependencies +name: deps-chart +type: application +version: 1.0.0 +dependencies: +- name: sleeper-chart + version: "0.1.0" + repository: {{.HelmRepoUrl}} diff --git a/e2e/assets/deps-charts/with-fleet-yaml/fleet.yaml b/e2e/assets/deps-charts/with-fleet-yaml/fleet.yaml new file mode 100644 index 0000000000..72225fa9b1 --- /dev/null +++ b/e2e/assets/deps-charts/with-fleet-yaml/fleet.yaml @@ -0,0 +1,8 @@ +namespace: {{.TestNamespace}} +helm: + releaseName: simple-with-fleet-yaml + chart: "" + repo: "" + version: "" + disableDependencyUpdate: {{.DisableDependencyUpdate}} + diff --git a/e2e/assets/deps-charts/with-fleet-yaml/templates/configmap.yaml b/e2e/assets/deps-charts/with-fleet-yaml/templates/configmap.yaml new file mode 100644 index 0000000000..c6b509ce7b --- /dev/null +++ b/e2e/assets/deps-charts/with-fleet-yaml/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-simple-deps-chart +data: + test: "valuedeps" + name: {{ .Values.name }} diff --git a/e2e/assets/deps-charts/with-fleet-yaml/values.yaml b/e2e/assets/deps-charts/with-fleet-yaml/values.yaml new file mode 100644 index 0000000000..1d9970923f --- /dev/null +++ b/e2e/assets/deps-charts/with-fleet-yaml/values.yaml @@ -0,0 +1 @@ +name: deps-default-name diff --git a/e2e/single-cluster/helm_dependencies_test.go b/e2e/single-cluster/helm_dependencies_test.go new file mode 100644 index 0000000000..7aaf680157 --- /dev/null +++ b/e2e/single-cluster/helm_dependencies_test.go @@ -0,0 +1,161 @@ +package singlecluster_test + +import ( + "fmt" + "math/rand" + "os" + "path" + "path/filepath" + "strings" + + "github.com/rancher/fleet/e2e/testenv" + "github.com/rancher/fleet/e2e/testenv/githelper" + "github.com/rancher/fleet/e2e/testenv/kubectl" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + cp "github.com/otiai10/copy" +) + +func getChartMuseumExternalAddr(env *testenv.Env) string { + username := os.Getenv("GIT_HTTP_USER") + passwd := os.Getenv("GIT_HTTP_PASSWORD") + Expect(username).ToNot(Equal("")) + Expect(passwd).ToNot(Equal("")) + return fmt.Sprintf("https://%s:%s@chartmuseum-service.%s.svc.cluster.local:8081", username, passwd, env.Namespace) +} + +func setupChartDepsInTmpDir(chartDir string, tmpDir string, namespace string, disableDependencyUpdate bool, env *testenv.Env) { + err := cp.Copy(chartDir, tmpDir) + Expect(err).ToNot(HaveOccurred()) + // replace the helm repo url + helmRepoUrl := getChartMuseumExternalAddr(env) + out := filepath.Join(tmpDir, "Chart.yaml") + in := filepath.Join(chartDir, "Chart.yaml") + err = testenv.Template(out, in, struct { + HelmRepoUrl string + }{ + helmRepoUrl, + }) + Expect(err).ToNot(HaveOccurred()) + + if _, err = os.Stat(filepath.Join(chartDir, "fleet.yaml")); !os.IsNotExist(err) { + out = filepath.Join(tmpDir, "fleet.yaml") + in = filepath.Join(chartDir, "fleet.yaml") + err = testenv.Template(out, in, struct { + TestNamespace string + DisableDependencyUpdate bool + }{ + namespace, + disableDependencyUpdate, + }) + Expect(err).ToNot(HaveOccurred()) + } +} + +var _ = Describe("Helm dependency update tests", Label("infra-setup", "helm-registry"), func() { + var ( + asset string + k kubectl.Command + gh *githelper.Git + clonedir string + inClusterRepoURL string + tmpDir string + gitrepoName string + r = rand.New(rand.NewSource(GinkgoRandomSeed())) + namespace string + disableDependencyUpdate bool + ) + + JustBeforeEach(func() { + k = env.Kubectl.Namespace(env.Namespace) + // 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("gitrepo-test", r) + + // setup the tmp chart dir. + // we use a tmp dir because dependencies are downloaded to the directory + tmpChart := GinkgoT().TempDir() + setupChartDepsInTmpDir(testenv.AssetPath(asset), tmpChart, namespace, disableDependencyUpdate, env) + + _, err = gh.Create(clonedir, tmpChart, "examples") + Expect(err).ToNot(HaveOccurred()) + + err = testenv.ApplyTemplate(k, testenv.AssetPath("deps-charts/gitrepo.yaml"), struct { + Name string + Repo string + Branch string + TargetNamespace string + }{ + gitrepoName, + inClusterRepoURL, + gh.Branch, + namespace, // to avoid conflicts with other tests + }) + Expect(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + out, err := k.Delete("gitrepo", gitrepoName) + Expect(err).ToNot(HaveOccurred(), out) + out, err = k.Delete("ns", namespace) + Expect(err).ToNot(HaveOccurred(), out) + os.RemoveAll(tmpDir) + }) + + When("applying a gitrepo resource", func() { + Context("containing a helm chart with dependencies and no fleet.yaml", func() { + BeforeEach(func() { + namespace = "no-fleet-yaml" + asset = "deps-charts/" + namespace + disableDependencyUpdate = false + }) + It("deploys the chart plus its dependencies", func() { + Eventually(func() bool { + outConfigMaps, _ := k.Namespace(namespace).Get("configmaps") + outPods, _ := k.Namespace(namespace).Get("pods") + return strings.Contains(outConfigMaps, "test-simple-deps-chart") && strings.Contains(outPods, "sleeper-") + }).Should(BeTrue()) + }) + }) + Context("containing a helm chart with dependencies and fleet.yaml with disableDependencyUpdate=false", func() { + BeforeEach(func() { + namespace = "with-fleet-yaml" + asset = "deps-charts/" + namespace + disableDependencyUpdate = false + }) + It("deploys the chart plus its dependencies", func() { + Eventually(func() bool { + outConfigMaps, _ := k.Namespace(namespace).Get("configmaps") + outPods, _ := k.Namespace(namespace).Get("pods") + return strings.Contains(outConfigMaps, "test-simple-deps-chart") && strings.Contains(outPods, "sleeper-") + }).Should(BeTrue()) + }) + }) + Context("containing a helm chart with dependencies and fleet.yaml with disableDependencyUpdate=true", func() { + BeforeEach(func() { + namespace = "with-fleet-yaml" + asset = "deps-charts/" + namespace + disableDependencyUpdate = true + }) + It("deploys the chart, but not its dependencies", func() { + Eventually(func() bool { + outConfigMaps, _ := k.Namespace(namespace).Get("configmaps") + outPods, _ := k.Namespace(namespace).Get("pods") + return strings.Contains(outConfigMaps, "test-simple-deps-chart") && !strings.Contains(outPods, "sleeper-") + }).Should(BeTrue()) + }) + }) + }) +}) diff --git a/integrationtests/cli/apply/apply_test.go b/integrationtests/cli/apply/apply_test.go index db77994e79..08391e349e 100644 --- a/integrationtests/cli/apply/apply_test.go +++ b/integrationtests/cli/apply/apply_test.go @@ -1,11 +1,21 @@ package apply import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gcustom" + "github.com/onsi/gomega/types" + cp "github.com/otiai10/copy" "github.com/rancher/fleet/integrationtests/cli" "github.com/rancher/fleet/internal/cmd/cli/apply" + "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" ) var _ = Describe("Fleet apply", Ordered, func() { @@ -28,17 +38,12 @@ var _ = Describe("Fleet apply", Ordered, func() { }) It("then a Bundle is created with all the resources and keepResources is false", func() { - Eventually(func() bool { - bundle, err := cli.GetBundleFromOutput(buf) - Expect(err).NotTo(HaveOccurred()) - Expect(len(bundle.Spec.Resources)).To(Equal(2)) - isSvcPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"simple/svc.yaml", bundle.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isDeploymentPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"simple/deployment.yaml", bundle.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - - return isSvcPresent && isDeploymentPresent && !bundle.Spec.KeepResources - }).Should(BeTrue()) + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(len(bundle.Spec.Resources)).To(Equal(2)) + Expect(cli.AssetsPath + "simple/svc.yaml").To(bePresentInBundleResources(bundle.Spec.Resources)) + Expect(cli.AssetsPath + "simple/deployment.yaml").To(bePresentInBundleResources(bundle.Spec.Resources)) + Expect(bundle.Spec.KeepResources).Should(BeFalse()) }) }) @@ -49,19 +54,12 @@ var _ = Describe("Fleet apply", Ordered, func() { }) It("then a Bundle is created with all the resources", func() { - Eventually(func() bool { - bundle, err := cli.GetBundleFromOutput(buf) - Expect(err).NotTo(HaveOccurred()) - Expect(len(bundle.Spec.Resources)).To(Equal(3)) - isSvcPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_simple/simple/svc.yaml", bundle.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isDeploymentPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_simple/simple/deployment.yaml", bundle.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isREADMEPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_simple/README.md", bundle.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - - return isSvcPresent && isDeploymentPresent && isREADMEPresent - }).Should(BeTrue()) + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(len(bundle.Spec.Resources)).To(Equal(3)) + Expect(cli.AssetsPath + "nested_simple/simple/svc.yaml").To(bePresentInBundleResources(bundle.Spec.Resources)) + Expect(cli.AssetsPath + "nested_simple/simple/deployment.yaml").To(bePresentInBundleResources(bundle.Spec.Resources)) + Expect(cli.AssetsPath + "nested_simple/README.md").To(bePresentInBundleResources(bundle.Spec.Resources)) }) }) @@ -72,17 +70,11 @@ var _ = Describe("Fleet apply", Ordered, func() { }) It("then a Bundle is created with all the resources", func() { - Eventually(func() bool { - bundle, err := cli.GetBundleFromOutput(buf) - Expect(err).NotTo(HaveOccurred()) - Expect(len(bundle.Spec.Resources)).To(Equal(2)) - isSvcPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_two_levels/nested/svc/svc.yaml", bundle.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isDeploymentPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_two_levels/nested/deployment/deployment.yaml", bundle.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - - return isSvcPresent && isDeploymentPresent - }).Should(BeTrue()) + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(len(bundle.Spec.Resources)).To(Equal(2)) + Expect(cli.AssetsPath + "nested_two_levels/nested/svc/svc.yaml").To(bePresentInBundleResources(bundle.Spec.Resources)) + Expect(cli.AssetsPath + "nested_two_levels/nested/deployment/deployment.yaml").To(bePresentInBundleResources(bundle.Spec.Resources)) }) }) @@ -93,33 +85,23 @@ var _ = Describe("Fleet apply", Ordered, func() { }) It("then 3 Bundles are created with the relevant resources", func() { - Eventually(func() bool { - bundle, err := cli.GetBundleListFromOutput(buf) - Expect(err).NotTo(HaveOccurred()) - Expect(len(bundle)).To(Equal(3)) - deploymentA := bundle[0] - deploymentB := bundle[1] - deploymentC := bundle[2] - - Expect(len(deploymentA.Spec.Resources)).To(Equal(2)) - Expect(len(deploymentB.Spec.Resources)).To(Equal(2)) - Expect(len(deploymentC.Spec.Resources)).To(Equal(2)) - - isFleetAPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_multiple/deploymentA/fleet.yaml", deploymentA.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isSvcAPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_multiple/deploymentA/svc/svc.yaml", deploymentA.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isFleetBPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_multiple/deploymentB/fleet.yaml", deploymentB.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isSvcBPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_multiple/deploymentB/svc/nested/svc.yaml", deploymentB.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isFleetCPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_multiple/deploymentC/fleet.yaml", deploymentC.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isDeploymentCPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_multiple/deploymentC/deployment.yaml", deploymentC.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - - return isFleetAPresent && isSvcAPresent && isFleetBPresent && isSvcBPresent && isFleetCPresent && isDeploymentCPresent - }).Should(BeTrue()) + bundle, err := cli.GetBundleListFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(len(bundle)).To(Equal(3)) + deploymentA := bundle[0] + deploymentB := bundle[1] + deploymentC := bundle[2] + + Expect(len(deploymentA.Spec.Resources)).To(Equal(2)) + Expect(len(deploymentB.Spec.Resources)).To(Equal(2)) + Expect(len(deploymentC.Spec.Resources)).To(Equal(2)) + + Expect(cli.AssetsPath + "nested_multiple/deploymentA/fleet.yaml").To(bePresentInBundleResources(deploymentA.Spec.Resources)) + Expect(cli.AssetsPath + "nested_multiple/deploymentA/svc/svc.yaml").To(bePresentInBundleResources(deploymentA.Spec.Resources)) + Expect(cli.AssetsPath + "nested_multiple/deploymentB/fleet.yaml").To(bePresentInBundleResources(deploymentB.Spec.Resources)) + Expect(cli.AssetsPath + "nested_multiple/deploymentB/svc/nested/svc.yaml").To(bePresentInBundleResources(deploymentB.Spec.Resources)) + Expect(cli.AssetsPath + "nested_multiple/deploymentC/fleet.yaml").To(bePresentInBundleResources(deploymentC.Spec.Resources)) + Expect(cli.AssetsPath + "nested_multiple/deploymentC/deployment.yaml").To(bePresentInBundleResources(deploymentC.Spec.Resources)) }) }) @@ -130,37 +112,25 @@ var _ = Describe("Fleet apply", Ordered, func() { }) It("then Bundles are created with all the resources", func() { - Eventually(func() bool { - bundle, err := cli.GetBundleListFromOutput(buf) - Expect(err).NotTo(HaveOccurred()) - Expect(len(bundle)).To(Equal(3)) - root := bundle[0] - deploymentA := bundle[1] - deploymentC := bundle[2] - - Expect(len(deploymentA.Spec.Resources)).To(Equal(2)) - Expect(len(deploymentC.Spec.Resources)).To(Equal(1)) - Expect(len(root.Spec.Resources)).To(Equal(5)) - - isFleetAPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_mixed_two_levels/nested/deploymentA/fleet.yaml", deploymentA.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isDeploymentAPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_mixed_two_levels/nested/deploymentA/fleet.yaml", deploymentA.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isFleetCPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_mixed_two_levels/nested/deploymentC/fleet.yaml", deploymentC.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isRootDeploymentAPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_mixed_two_levels/nested/deploymentA/fleet.yaml", root.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isRootFleetAPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_mixed_two_levels/nested/deploymentA/fleet.yaml", root.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isRootSvcBPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_mixed_two_levels/nested/deploymentB/svc.yaml", root.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isRootFleetCPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_mixed_two_levels/nested/deploymentC/fleet.yaml", root.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - isRootDeploymentDPresent, err := cli.IsResourcePresentInBundle(cli.AssetsPath+"nested_mixed_two_levels/nested/deploymentD/deployment.yaml", root.Spec.Resources) - Expect(err).NotTo(HaveOccurred()) - - return isFleetAPresent && isDeploymentAPresent && isFleetCPresent && isRootDeploymentAPresent && isRootFleetAPresent && isRootSvcBPresent && isRootFleetCPresent && isRootDeploymentDPresent - }).Should(BeTrue()) + bundle, err := cli.GetBundleListFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(len(bundle)).To(Equal(3)) + root := bundle[0] + deploymentA := bundle[1] + deploymentC := bundle[2] + + Expect(len(deploymentA.Spec.Resources)).To(Equal(2)) + Expect(len(deploymentC.Spec.Resources)).To(Equal(1)) + Expect(len(root.Spec.Resources)).To(Equal(5)) + + Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentA/fleet.yaml").To(bePresentInBundleResources(deploymentA.Spec.Resources)) + Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentA/deployment.yaml").To(bePresentInBundleResources(deploymentA.Spec.Resources)) + Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentC/fleet.yaml").To(bePresentInBundleResources(deploymentC.Spec.Resources)) + Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentA/fleet.yaml").To(bePresentInBundleResources(root.Spec.Resources)) + Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentA/deployment.yaml").To(bePresentInBundleResources(root.Spec.Resources)) + Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentB/svc.yaml").To(bePresentInBundleResources(root.Spec.Resources)) + Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentC/fleet.yaml").To(bePresentInBundleResources(root.Spec.Resources)) + Expect(cli.AssetsPath + "nested_mixed_two_levels/nested/deploymentD/deployment.yaml").To(bePresentInBundleResources(root.Spec.Resources)) }) }) @@ -171,11 +141,9 @@ var _ = Describe("Fleet apply", Ordered, func() { }) It("then a Bundle is created with keepResources", func() { - Eventually(func() bool { - bundle, err := cli.GetBundleFromOutput(buf) - Expect(err).NotTo(HaveOccurred()) - return bundle.Spec.KeepResources - }).Should(BeTrue()) + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(bundle.Spec.KeepResources).To(BeTrue()) }) }) @@ -187,16 +155,14 @@ var _ = Describe("Fleet apply", Ordered, func() { }) It("publishes the flag in the bundle options", func() { - Eventually(func() bool { - bundle, err := cli.GetBundleFromOutput(buf) - Expect(err).NotTo(HaveOccurred()) - return bundle.Spec.Helm.TakeOwnership && - bundle.Spec.Helm.Atomic && - bundle.Spec.Helm.Force && - bundle.Spec.Helm.WaitForJobs && - bundle.Spec.Helm.DisablePreProcess && - bundle.Spec.Helm.ReleaseName == "enabled" - }).Should(BeTrue()) + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(bundle.Spec.Helm.TakeOwnership).To(BeTrue()) + Expect(bundle.Spec.Helm.Atomic).To(BeTrue()) + Expect(bundle.Spec.Helm.Force).To(BeTrue()) + Expect(bundle.Spec.Helm.WaitForJobs).To(BeTrue()) + Expect(bundle.Spec.Helm.DisablePreProcess).To(BeTrue()) + Expect(bundle.Spec.Helm.ReleaseName).To(Equal("enabled")) }) }) @@ -207,16 +173,14 @@ var _ = Describe("Fleet apply", Ordered, func() { }) It("publishes the flag in the bundle options", func() { - Eventually(func() bool { - bundle, err := cli.GetBundleFromOutput(buf) - Expect(err).NotTo(HaveOccurred()) - return bundle.Spec.Helm.TakeOwnership == false && - bundle.Spec.Helm.Atomic == false && - bundle.Spec.Helm.Force == false && - bundle.Spec.Helm.WaitForJobs == false && - bundle.Spec.Helm.DisablePreProcess == false && - bundle.Spec.Helm.ReleaseName == "disabled" - }).Should(BeTrue()) + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(bundle.Spec.Helm.TakeOwnership).To(BeFalse()) + Expect(bundle.Spec.Helm.Atomic).To(BeFalse()) + Expect(bundle.Spec.Helm.Force).To(BeFalse()) + Expect(bundle.Spec.Helm.WaitForJobs).To(BeFalse()) + Expect(bundle.Spec.Helm.DisablePreProcess).To(BeFalse()) + Expect(bundle.Spec.Helm.ReleaseName).To(Equal("disabled")) }) }) @@ -227,13 +191,276 @@ var _ = Describe("Fleet apply", Ordered, func() { }) It("publishes the flag in the bundle options", func() { - Eventually(func() bool { - bundle, err := cli.GetBundleFromOutput(buf) - Expect(err).NotTo(HaveOccurred()) - return bundle.Spec.Helm.TakeOwnership && - bundle.Spec.Helm.ReleaseName == "kustomize" - }).Should(BeTrue()) + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(bundle.Spec.Helm.TakeOwnership).To(BeTrue()) + Expect(bundle.Spec.Helm.ReleaseName).To(Equal("kustomize")) }) }) }) }) + +var _ = Describe("Fleet apply with helm charts with dependencies", Ordered, func() { + + var ( + dirs []string + name string + options apply.Options + tmpDirRel string + tmpDir string + repo = repository{ + port: port, + } + ) + + JustBeforeEach(func() { + // start a fake helm repository + repo.startRepository(false) + tmpDir = GinkgoT().TempDir() + err := cp.Copy(path.Join(cli.AssetsPath, "deps-charts", name), tmpDir) + Expect(err).NotTo(HaveOccurred()) + // get the relative path because fleet apply needs a relative path + pwd, err := os.Getwd() + Expect(err).NotTo(HaveOccurred()) + tmpDirRel, err = filepath.Rel(pwd, tmpDir) + Expect(err).NotTo(HaveOccurred()) + dirs = []string{tmpDirRel} + err = fleetApply(name, dirs, options) + Expect(err).NotTo(HaveOccurred()) + }) + + When("folder contains helm chart with no fleet.yaml", func() { + BeforeEach(func() { + name = "no-fleet-yaml" + }) + + It("creates a Bundle with all the resources, including the dependencies", func() { + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + // files expected are: + // Chart.yaml + values.yaml + templates/configmap.yaml + + // Chart.lock + charts/config-chart-0.1.0.tgz + Expect(len(bundle.Spec.Resources)).To(Equal(5)) + files, err := getAllFilesInDir(tmpDir) + Expect(err).NotTo(HaveOccurred()) + Expect(len(files)).To(Equal(len(bundle.Spec.Resources))) + for _, file := range files { + Expect(file).To(bePresentInBundleResources(bundle.Spec.Resources)) + } + // explicitly check for dependency files + Expect(path.Join(tmpDir, "Chart.lock")).To(bePresentInBundleResources(bundle.Spec.Resources)) + Expect(path.Join(tmpDir, "charts/config-chart-0.1.0.tgz")).To(bePresentInBundleResources(bundle.Spec.Resources)) + }) + }) + + When("folder contains helm chart with fleet.yaml, disableDependencyUpdate is not set", func() { + BeforeEach(func() { + name = "simple-with-fleet-yaml" + }) + + It("creates a Bundle with all the resources, including the dependencies", func() { + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + // files expected are: + // Chart.yaml + values.yaml + templates/configmap.yaml + fleet.yaml + + // Chart.lock + charts/config-chart-0.1.0.tgz + Expect(len(bundle.Spec.Resources)).To(Equal(6)) + files, err := getAllFilesInDir(tmpDirRel) + Expect(err).NotTo(HaveOccurred()) + Expect(len(files)).To(Equal(len(bundle.Spec.Resources))) + for _, file := range files { + Expect(file).To(bePresentInBundleResources(bundle.Spec.Resources)) + } + // explicitly check for dependency files + Expect(path.Join(tmpDirRel, "Chart.lock")).To(bePresentInBundleResources(bundle.Spec.Resources)) + Expect(path.Join(tmpDirRel, "charts/config-chart-0.1.0.tgz")).To(bePresentInBundleResources(bundle.Spec.Resources)) + }) + }) + + When("folder contains helm chart with fleet.yaml, disableDependencyUpdate is set to true", func() { + BeforeEach(func() { + name = "simple-with-fleet-yaml-no-deps" + }) + + It("creates a Bundle with all the resources, dependencies should not be in the bundle", func() { + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + // files expected are: + // Chart.yaml + values.yaml + templates/configmap.yaml + fleet.yaml + Expect(len(bundle.Spec.Resources)).To(Equal(4)) + files, err := getAllFilesInDir(tmpDirRel) + Expect(err).NotTo(HaveOccurred()) + Expect(len(files)).To(Equal(len(bundle.Spec.Resources))) + for _, file := range files { + Expect(file).To(bePresentInBundleResources(bundle.Spec.Resources)) + } + // explicitly check for dependency files (they should not exist in the file system nor in bundle resources) + Expect(path.Join(tmpDirRel, "Chart.lock")).NotTo(BeAnExistingFile()) + Expect(path.Join(tmpDirRel, "Chart.lock")).NotTo(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + Expect(path.Join(tmpDirRel, "charts/config-chart-0.1.0.tgz")).NotTo(BeAnExistingFile()) + Expect(path.Join(tmpDirRel, "charts/config-chart-0.1.0.tgz")).NotTo(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + }) + }) + + When("folder contains fleet.yaml defining a remote chart which has dependencies", func() { + BeforeEach(func() { + name = "remote-chart-with-deps" + }) + + It("creates a Bundle with all the resources, dependencies should be in the bundle", func() { + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + // expected files are: + // fleet.yaml + Chart.yaml + values.yaml + templates/configmap.yaml + + // Chart.lock + charts/config-chart-0.1.0.tgz + Expect(len(bundle.Spec.Resources)).To(Equal(6)) + Expect(path.Join(tmpDirRel, "fleet.yaml")).To(bePresentInBundleResources(bundle.Spec.Resources)) + // as files were unpacked from the downloaded chart we can't just + // list the files in the original folder and compare. + // Files are only located in the bundle resources + Expect("Chart.yaml").To(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + Expect("values.yaml").To(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + Expect("templates/configmap.yaml").To(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + Expect("Chart.lock").To(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + Expect("charts/config-chart-0.1.0.tgz").To(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + }) + }) + + When("folder contains fleet.yaml defining a remote chart which has dependencies, and disableDependencyUpdate is set", func() { + BeforeEach(func() { + name = "remote-chart-with-deps-disabled" + }) + + It("creates a Bundle with all the resources, dependencies should not be in the bundle", func() { + bundle, err := cli.GetBundleFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + // expected files are: + // fleet.yaml + + // Chart.yaml + values.yaml + templates/configmap.yaml + Expect(len(bundle.Spec.Resources)).To(Equal(4)) + Expect(path.Join(tmpDirRel, "fleet.yaml")).To(bePresentInBundleResources(bundle.Spec.Resources)) + // as files were unpacked from the downloaded chart we can't just + // list the files in the original folder and compare. + // Files are only located in the bundle resources + Expect("Chart.yaml").To(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + Expect("values.yaml").To(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + Expect("templates/configmap.yaml").To(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + + // explicitly check for dependency files (they should not exist in the file system nor in bundle resources) + Expect(path.Join(tmpDirRel, "Chart.lock")).NotTo(BeAnExistingFile()) + Expect(path.Join(tmpDirRel, "Chart.lock")).NotTo(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + Expect(path.Join(tmpDirRel, "charts/config-chart-0.1.0.tgz")).NotTo(BeAnExistingFile()) + Expect(path.Join(tmpDirRel, "charts/config-chart-0.1.0.tgz")).NotTo(bePresentOnlyInBundleResources(bundle.Spec.Resources)) + }) + }) + + When("folder contains multiple charts with different options", func() { + BeforeEach(func() { + name = "multi-chart" + }) + + It("creates Bundles with the corresponding resources, depending if they should update dependencies", func() { + bundle, err := cli.GetBundleListFromOutput(buf) + Expect(err).NotTo(HaveOccurred()) + Expect(len(bundle)).To(Equal(3)) + remoteDepl := bundle[0] + simpleDepl := bundle[1] + noDepsDepl := bundle[2] + + // remoteDepl corresponds to multi-chart/remote-chart-with-deps + // expected files are: + // fleet.yaml + + // Chart.yaml + values.yaml + templates/configmap.yaml + Chart.lock + charts/config-chart-0.1.0.tgz + Expect(len(remoteDepl.Spec.Resources)).To(Equal(6)) + Expect(path.Join(tmpDirRel, "remote-chart-with-deps", "fleet.yaml")).To(bePresentInBundleResources(remoteDepl.Spec.Resources)) + // as files were unpacked from the downloaded chart we can't just + // list the files in the original folder and compare. + // Files are only located in the bundle resources + Expect("Chart.yaml").To(bePresentOnlyInBundleResources(remoteDepl.Spec.Resources)) + Expect("values.yaml").To(bePresentOnlyInBundleResources(remoteDepl.Spec.Resources)) + Expect("templates/configmap.yaml").To(bePresentOnlyInBundleResources(remoteDepl.Spec.Resources)) + Expect("Chart.lock").To(bePresentOnlyInBundleResources(remoteDepl.Spec.Resources)) + Expect("charts/config-chart-0.1.0.tgz").To(bePresentOnlyInBundleResources(remoteDepl.Spec.Resources)) + + // simpleDepl corresponds to multi-chart/simple-with-fleet-yaml + // expected files are: + // fleet.yaml + Chart.yaml + values.yaml + templates/configmap.yaml + + // Chart.lock + charts/config-chart-0.1.0.tgz + Expect(len(simpleDepl.Spec.Resources)).To(Equal(6)) + files, err := getAllFilesInDir(path.Join(tmpDirRel, "simple-with-fleet-yaml")) + Expect(err).NotTo(HaveOccurred()) + Expect(len(files)).To(Equal(len(simpleDepl.Spec.Resources))) + for _, file := range files { + Expect(file).To(bePresentInBundleResources(simpleDepl.Spec.Resources)) + } + // explicitly check for dependency files + Expect(path.Join(tmpDirRel, "simple-with-fleet-yaml", "Chart.lock")).To(bePresentInBundleResources(simpleDepl.Spec.Resources)) + Expect(path.Join(tmpDirRel, "simple-with-fleet-yaml", "charts/config-chart-0.1.0.tgz")).To(bePresentInBundleResources(simpleDepl.Spec.Resources)) + + // noDepsDepl corresponds to multi-char/simple-with-fleet-yaml-no-deps + // expected files are: + // Chart.yaml + fleet.yaml + values.yaml + templates/configmap.yaml + Expect(len(noDepsDepl.Spec.Resources)).To(Equal(4)) + files, err = getAllFilesInDir(path.Join(tmpDirRel, "simple-with-fleet-yaml-no-deps")) + Expect(err).NotTo(HaveOccurred()) + Expect(len(files)).To(Equal(len(noDepsDepl.Spec.Resources))) + for _, file := range files { + Expect(file).To(bePresentInBundleResources(noDepsDepl.Spec.Resources)) + } + // explicitly check for dependency files (they should not exist in the file system nor in bundle resources) + Expect(path.Join(tmpDirRel, "simple-with-fleet-yaml-no-deps", "Chart.lock")).NotTo(BeAnExistingFile()) + Expect(path.Join(tmpDirRel, "simple-with-fleet-yaml-no-deps", "Chart.lock")).NotTo(bePresentOnlyInBundleResources(noDepsDepl.Spec.Resources)) + Expect(path.Join(tmpDirRel, "simple-with-fleet-yaml-no-deps", "charts/config-chart-0.1.0.tgz")).NotTo(BeAnExistingFile()) + Expect(path.Join(tmpDirRel, "simple-with-fleet-yaml-no-deps", "charts/config-chart-0.1.0.tgz")).NotTo(bePresentOnlyInBundleResources(noDepsDepl.Spec.Resources)) + }) + }) + + AfterEach(func() { + err := repo.stopRepository() + Expect(err).NotTo(HaveOccurred()) + }) +}) + +func bePresentInBundleResources(expected interface{}) types.GomegaMatcher { + return gcustom.MakeMatcher(func(path string) (bool, error) { + resources, ok := expected.([]v1alpha1.BundleResource) + if !ok { + return false, fmt.Errorf("BePresentInBundleResources matcher expects []v1alpha1.BundleResource") + } + isPresent, err := cli.IsResourcePresentInBundle(path, resources) + if err != nil { + return false, fmt.Errorf("Failed to check for path in resources: %s", err.Error()) + } + return isPresent, nil + }).WithTemplate("Expected:\n{{.FormattedActual}}\n{{.To}} be present in \n{{format .Data 1}}").WithTemplateData(expected) +} + +func bePresentOnlyInBundleResources(expected interface{}) types.GomegaMatcher { + return gcustom.MakeMatcher(func(path string) (bool, error) { + resources, ok := expected.([]v1alpha1.BundleResource) + if !ok { + return false, fmt.Errorf("bePresentOnlyInBundleResources matcher expects []v1alpha1.BundleResource") + } + found := false + for _, resource := range resources { + if strings.HasSuffix(resource.Name, path) { + found = true + } + } + return found, nil + }).WithTemplate("Expected:\n{{.FormattedActual}}\n{{.To}} be present in \n{{format .Data 1}}").WithTemplateData(expected) +} + +func getAllFilesInDir(chartPath string) ([]string, error) { + var files []string + err := filepath.Walk(chartPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + files = append(files, path) + } + return nil + }) + return files, err +} diff --git a/integrationtests/cli/assets/deps-charts/multi-chart/remote-chart-with-deps/fleet.yaml b/integrationtests/cli/assets/deps-charts/multi-chart/remote-chart-with-deps/fleet.yaml new file mode 100644 index 0000000000..116fbbf3cc --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/multi-chart/remote-chart-with-deps/fleet.yaml @@ -0,0 +1,8 @@ +namespace: test-deps-helm-yamls-with-fleet +helm: + releaseName: simple-with-fleet-yaml + chart: deps-chart + repo: http://localhost:3000 + version: 1.0.0 + force: false + diff --git a/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/Chart.yaml b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/Chart.yaml new file mode 100644 index 0000000000..52dc058c1c --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +appVersion: 1.16.0 +description: A chart for testing dependencies +name: deps-chart +type: application +version: 1.0.0 +dependencies: +- name: config-chart + version: "0.1.0" + repository: "http://localhost:3000" diff --git a/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/fleet.yaml b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/fleet.yaml new file mode 100644 index 0000000000..a3d77664e0 --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/fleet.yaml @@ -0,0 +1,8 @@ +namespace: test-deps-helm-yamls-with-fleet +helm: + releaseName: simple-with-fleet-yaml-no-deps + chart: "" + repo: "" + version: "" + disableDependencyUpdate: true + diff --git a/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/templates/configmap.yaml b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/templates/configmap.yaml new file mode 100644 index 0000000000..c6b509ce7b --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-simple-deps-chart +data: + test: "valuedeps" + name: {{ .Values.name }} diff --git a/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/values.yaml b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/values.yaml new file mode 100644 index 0000000000..1d9970923f --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml-no-deps/values.yaml @@ -0,0 +1 @@ +name: deps-default-name diff --git a/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/Chart.yaml b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/Chart.yaml new file mode 100644 index 0000000000..52dc058c1c --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +appVersion: 1.16.0 +description: A chart for testing dependencies +name: deps-chart +type: application +version: 1.0.0 +dependencies: +- name: config-chart + version: "0.1.0" + repository: "http://localhost:3000" diff --git a/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/fleet.yaml b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/fleet.yaml new file mode 100644 index 0000000000..74c08e0abc --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/fleet.yaml @@ -0,0 +1,8 @@ +namespace: test-deps-helm-yamls-with-fleet +helm: + releaseName: simple-with-fleet-yaml + chart: "" + repo: "" + version: "" + force: false + diff --git a/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/templates/configmap.yaml b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/templates/configmap.yaml new file mode 100644 index 0000000000..c6b509ce7b --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-simple-deps-chart +data: + test: "valuedeps" + name: {{ .Values.name }} diff --git a/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/values.yaml b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/values.yaml new file mode 100644 index 0000000000..1d9970923f --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/multi-chart/simple-with-fleet-yaml/values.yaml @@ -0,0 +1 @@ +name: deps-default-name diff --git a/integrationtests/cli/assets/deps-charts/no-fleet-yaml/Chart.yaml b/integrationtests/cli/assets/deps-charts/no-fleet-yaml/Chart.yaml new file mode 100644 index 0000000000..52dc058c1c --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/no-fleet-yaml/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +appVersion: 1.16.0 +description: A chart for testing dependencies +name: deps-chart +type: application +version: 1.0.0 +dependencies: +- name: config-chart + version: "0.1.0" + repository: "http://localhost:3000" diff --git a/integrationtests/cli/assets/deps-charts/no-fleet-yaml/templates/configmap.yaml b/integrationtests/cli/assets/deps-charts/no-fleet-yaml/templates/configmap.yaml new file mode 100644 index 0000000000..c6b509ce7b --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/no-fleet-yaml/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-simple-deps-chart +data: + test: "valuedeps" + name: {{ .Values.name }} diff --git a/integrationtests/cli/assets/deps-charts/no-fleet-yaml/values.yaml b/integrationtests/cli/assets/deps-charts/no-fleet-yaml/values.yaml new file mode 100644 index 0000000000..1d9970923f --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/no-fleet-yaml/values.yaml @@ -0,0 +1 @@ +name: deps-default-name diff --git a/integrationtests/cli/assets/deps-charts/remote-chart-with-deps-disabled/fleet.yaml b/integrationtests/cli/assets/deps-charts/remote-chart-with-deps-disabled/fleet.yaml new file mode 100644 index 0000000000..ccd2f60cbf --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/remote-chart-with-deps-disabled/fleet.yaml @@ -0,0 +1,9 @@ +namespace: test-deps-helm-yamls-with-fleet +helm: + releaseName: simple-with-fleet-yaml + chart: deps-chart + repo: http://localhost:3000 + version: 1.0.0 + force: false + disableDependencyUpdate: true + diff --git a/integrationtests/cli/assets/deps-charts/remote-chart-with-deps/fleet.yaml b/integrationtests/cli/assets/deps-charts/remote-chart-with-deps/fleet.yaml new file mode 100644 index 0000000000..116fbbf3cc --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/remote-chart-with-deps/fleet.yaml @@ -0,0 +1,8 @@ +namespace: test-deps-helm-yamls-with-fleet +helm: + releaseName: simple-with-fleet-yaml + chart: deps-chart + repo: http://localhost:3000 + version: 1.0.0 + force: false + diff --git a/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/Chart.yaml b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/Chart.yaml new file mode 100644 index 0000000000..52dc058c1c --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +appVersion: 1.16.0 +description: A chart for testing dependencies +name: deps-chart +type: application +version: 1.0.0 +dependencies: +- name: config-chart + version: "0.1.0" + repository: "http://localhost:3000" diff --git a/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/fleet.yaml b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/fleet.yaml new file mode 100644 index 0000000000..a3d77664e0 --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/fleet.yaml @@ -0,0 +1,8 @@ +namespace: test-deps-helm-yamls-with-fleet +helm: + releaseName: simple-with-fleet-yaml-no-deps + chart: "" + repo: "" + version: "" + disableDependencyUpdate: true + diff --git a/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/templates/configmap.yaml b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/templates/configmap.yaml new file mode 100644 index 0000000000..c6b509ce7b --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-simple-deps-chart +data: + test: "valuedeps" + name: {{ .Values.name }} diff --git a/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/values.yaml b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/values.yaml new file mode 100644 index 0000000000..1d9970923f --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml-no-deps/values.yaml @@ -0,0 +1 @@ +name: deps-default-name diff --git a/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/Chart.yaml b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/Chart.yaml new file mode 100644 index 0000000000..52dc058c1c --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +appVersion: 1.16.0 +description: A chart for testing dependencies +name: deps-chart +type: application +version: 1.0.0 +dependencies: +- name: config-chart + version: "0.1.0" + repository: "http://localhost:3000" diff --git a/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/fleet.yaml b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/fleet.yaml new file mode 100644 index 0000000000..74c08e0abc --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/fleet.yaml @@ -0,0 +1,8 @@ +namespace: test-deps-helm-yamls-with-fleet +helm: + releaseName: simple-with-fleet-yaml + chart: "" + repo: "" + version: "" + force: false + diff --git a/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/templates/configmap.yaml b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/templates/configmap.yaml new file mode 100644 index 0000000000..c6b509ce7b --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-simple-deps-chart +data: + test: "valuedeps" + name: {{ .Values.name }} diff --git a/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/values.yaml b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/values.yaml new file mode 100644 index 0000000000..1d9970923f --- /dev/null +++ b/integrationtests/cli/assets/deps-charts/simple-with-fleet-yaml/values.yaml @@ -0,0 +1 @@ +name: deps-default-name diff --git a/integrationtests/cli/assets/helmrepository/deps-chart-1.0.0.tgz b/integrationtests/cli/assets/helmrepository/deps-chart-1.0.0.tgz new file mode 100644 index 0000000000..7862bdddc0 Binary files /dev/null and b/integrationtests/cli/assets/helmrepository/deps-chart-1.0.0.tgz differ diff --git a/integrationtests/cli/assets/helmrepository/deps-chart/Chart.yaml b/integrationtests/cli/assets/helmrepository/deps-chart/Chart.yaml new file mode 100644 index 0000000000..52dc058c1c --- /dev/null +++ b/integrationtests/cli/assets/helmrepository/deps-chart/Chart.yaml @@ -0,0 +1,10 @@ +apiVersion: v2 +appVersion: 1.16.0 +description: A chart for testing dependencies +name: deps-chart +type: application +version: 1.0.0 +dependencies: +- name: config-chart + version: "0.1.0" + repository: "http://localhost:3000" diff --git a/integrationtests/cli/assets/helmrepository/deps-chart/templates/configmap.yaml b/integrationtests/cli/assets/helmrepository/deps-chart/templates/configmap.yaml new file mode 100644 index 0000000000..c6b509ce7b --- /dev/null +++ b/integrationtests/cli/assets/helmrepository/deps-chart/templates/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-simple-deps-chart +data: + test: "valuedeps" + name: {{ .Values.name }} diff --git a/integrationtests/cli/assets/helmrepository/deps-chart/values.yaml b/integrationtests/cli/assets/helmrepository/deps-chart/values.yaml new file mode 100644 index 0000000000..1d9970923f --- /dev/null +++ b/integrationtests/cli/assets/helmrepository/deps-chart/values.yaml @@ -0,0 +1 @@ +name: deps-default-name diff --git a/integrationtests/cli/assets/helmrepository/index.yaml b/integrationtests/cli/assets/helmrepository/index.yaml index 7fdb2b943e..0d28bcdb30 100644 --- a/integrationtests/cli/assets/helmrepository/index.yaml +++ b/integrationtests/cli/assets/helmrepository/index.yaml @@ -11,4 +11,15 @@ entries: urls: - http://localhost:3000/config-chart-0.1.0.tgz version: 0.1.0 + deps-chart: + - apiVersion: v2 + appVersion: 1.16.0 + created: "2023-01-03T11:05:22.378628588+01:00" + description: A Helm chart for testing dependency update + digest: 8bae7a819851c6d96bacd213288d7892985951ab3ae79f27c3300f6426aec759 + name: testrepo + type: application + urls: + - http://localhost:3000/deps-chart-1.0.0.tgz + version: 1.0.0 generated: "2023-01-03T11:05:22.378100239+01:00" diff --git a/integrationtests/cli/helpers.go b/integrationtests/cli/helpers.go index d1e1805f88..10924d7e7e 100644 --- a/integrationtests/cli/helpers.go +++ b/integrationtests/cli/helpers.go @@ -7,6 +7,7 @@ import ( "os" "strings" + "github.com/rancher/fleet/internal/content" "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" "k8s.io/apimachinery/pkg/util/yaml" @@ -62,8 +63,18 @@ func IsResourcePresentInBundle(resourcePath string, resources []v1alpha1.BundleR } for _, resource := range resources { - if strings.ReplaceAll(resource.Content, "\n", "") == strings.ReplaceAll(string(resourceFile), "\n", "") { - return true, nil + if resource.Encoding == "base64+gz" { + resourceFileEncoded, err := content.Base64GZ(resourceFile) + if err != nil { + return false, err + } + if resource.Content == resourceFileEncoded { + return true, nil + } + } else { + if strings.ReplaceAll(resource.Content, "\n", "") == strings.ReplaceAll(string(resourceFile), "\n", "") { + return true, nil + } } } diff --git a/internal/bundlereader/loaddirectory.go b/internal/bundlereader/loaddirectory.go index 6ca1d700e4..38eeacdaf7 100644 --- a/internal/bundlereader/loaddirectory.go +++ b/internal/bundlereader/loaddirectory.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/go-getter" "github.com/pkg/errors" "github.com/rancher/fleet/internal/content" + "github.com/rancher/fleet/internal/helmupdater" fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1" "helm.sh/helm/v3/pkg/cli" "helm.sh/helm/v3/pkg/downloader" @@ -151,10 +152,10 @@ func readFleetIgnore(path string) ([]string, error) { return ignored, nil } -func loadDirectory(ctx context.Context, compress bool, prefix, base, source, version string, auth Auth) ([]fleet.BundleResource, error) { +func loadDirectory(ctx context.Context, compress bool, disableDepsUpdate bool, prefix, base, source, version string, auth Auth) ([]fleet.BundleResource, error) { var resources []fleet.BundleResource - files, err := GetContent(ctx, base, source, version, auth) + files, err := GetContent(ctx, base, source, version, auth, disableDepsUpdate) if err != nil { return nil, err } @@ -181,7 +182,7 @@ func loadDirectory(ctx context.Context, compress bool, prefix, base, source, ver } // GetContent uses go-getter (and Helm for OCI) to read the files from directories and servers. -func GetContent(ctx context.Context, base, source, version string, auth Auth) (map[string][]byte, error) { +func GetContent(ctx context.Context, base, source, version string, auth Auth, disableDepsUpdate bool) (map[string][]byte, error) { temp, err := os.MkdirTemp("", "fleet") if err != nil { return nil, err @@ -266,6 +267,13 @@ func GetContent(ctx context.Context, base, source, version string, auth Auth) (m } if info.IsDir() { + // If the folder is a helm chart and dependency updates are not disabled, + // try to update possible dependencies. + if !disableDepsUpdate && helmupdater.ChartYAMLExists(path) { + if err = helmupdater.UpdateHelmDependencies(path); err != nil { + return err + } + } // Skip .fleetignore'd and hidden directories if ignore || strings.HasPrefix(filepath.Base(path), ".") { return filepath.SkipDir diff --git a/internal/bundlereader/loaddirectory_test.go b/internal/bundlereader/loaddirectory_test.go index 1d3d7a0772..4f88968eb5 100644 --- a/internal/bundlereader/loaddirectory_test.go +++ b/internal/bundlereader/loaddirectory_test.go @@ -333,7 +333,7 @@ func TestGetContent(t *testing.T) { root := createDirStruct(t, base, c.directoryStructure) - files, err := bundlereader.GetContent(context.Background(), root, root, "", bundlereader.Auth{}) + files, err := bundlereader.GetContent(context.Background(), root, root, "", bundlereader.Auth{}, false) assert.NoError(t, err) assert.Equal(t, len(c.expectedFiles), len(files)) diff --git a/internal/bundlereader/resources.go b/internal/bundlereader/resources.go index 90a86eebb8..58a7f3a024 100644 --- a/internal/bundlereader/resources.go +++ b/internal/bundlereader/resources.go @@ -62,7 +62,12 @@ func readResources(ctx context.Context, spec *fleet.BundleSpec, compress bool, b return nil, err } - resources, err := loadDirectories(ctx, compress, directories...) + // helm chart dependency update is enabled by default + disableDepsUpdate := false + if spec.Helm != nil { + disableDepsUpdate = spec.Helm.DisableDependencyUpdate + } + resources, err := loadDirectories(ctx, compress, disableDepsUpdate, directories...) if err != nil { return nil, err } @@ -198,7 +203,7 @@ func checksum(helm *fleet.HelmOptions) string { return fmt.Sprintf(".chart/%x", sha256.Sum256([]byte(helm.Chart + ":" + helm.Repo + ":" + helm.Version)[:])) } -func loadDirectories(ctx context.Context, compress bool, directories ...directory) (map[string][]fleet.BundleResource, error) { +func loadDirectories(ctx context.Context, compress bool, disableDepsUpdate bool, directories ...directory) (map[string][]fleet.BundleResource, error) { var ( sem = semaphore.NewWeighted(4) result = map[string][]fleet.BundleResource{} @@ -216,7 +221,7 @@ func loadDirectories(ctx context.Context, compress bool, directories ...director dir := dir eg.Go(func() error { defer sem.Release(1) - resources, err := loadDirectory(ctx, compress, dir.prefix, dir.base, dir.source, dir.version, dir.auth) + resources, err := loadDirectory(ctx, compress, disableDepsUpdate, dir.prefix, dir.base, dir.source, dir.version, dir.auth) if err != nil { return err } diff --git a/internal/helmupdater/helmupdater.go b/internal/helmupdater/helmupdater.go new file mode 100644 index 0000000000..5dcf72d7bb --- /dev/null +++ b/internal/helmupdater/helmupdater.go @@ -0,0 +1,66 @@ +package helmupdater + +import ( + "os" + "path/filepath" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/downloader" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/registry" +) + +const ( + ChartYaml = "Chart.yaml" +) + +// ChartYAMLExists checks if the provided path is a directory containing a `Chart.yaml` file. +// Returns true if it does, false otherwise or if an error happens when checking `/Chart.yaml`. +func ChartYAMLExists(path string) bool { + chartPath := filepath.Join(path, ChartYaml) + if _, err := os.Stat(chartPath); err != nil { + return false + } + return true +} + +// UpdateHelmDependencies checks if the helm chart located at the given directory has unmet dependencies and, if so, updates them +func UpdateHelmDependencies(path string) error { + // load the chart to check if there are unmet dependencies first + chartRequested, err := loader.Load(path) + if err != nil { + return err + } + + if req := chartRequested.Metadata.Dependencies; req != nil { + if err := action.CheckDependencies(chartRequested, req); err != nil { + settings := cli.New() + registryClient, err := registry.NewClient( + registry.ClientOptDebug(settings.Debug), + registry.ClientOptEnableCache(true), + registry.ClientOptWriter(os.Stderr), + registry.ClientOptCredentialsFile(settings.RegistryConfig), + ) + if err != nil { + return err + } + man := &downloader.Manager{ + Out: os.Stdout, + ChartPath: path, + Keyring: "", + SkipUpdate: false, + Getters: getter.All(settings), + RepositoryConfig: settings.RegistryConfig, + RepositoryCache: settings.RepositoryCache, + Debug: settings.Debug, + RegistryClient: registryClient, + } + if err := man.Update(); err != nil { + return err + } + } + } + return nil +} diff --git a/internal/helmupdater/helmupdater_test.go b/internal/helmupdater/helmupdater_test.go new file mode 100644 index 0000000000..42f30c1882 --- /dev/null +++ b/internal/helmupdater/helmupdater_test.go @@ -0,0 +1,101 @@ +package helmupdater_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/rancher/fleet/internal/helmupdater" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type fsNodeSimple struct { + name string + children []fsNodeSimple + isDir bool +} + +// createDirStruct generates and populates a directory structure which root is node, placing it at basePath. +func createDirStruct(t *testing.T, basePath string, node fsNodeSimple) { + path := filepath.Join(basePath, node.name) + + if !node.isDir { + _, err := os.Create(path) + require.NoError(t, err) + return + } + + err := os.Mkdir(path, 0777) + require.NoError(t, err) + + for _, c := range node.children { + createDirStruct(t, path, c) + } +} + +func TestChartYAMLExists(t *testing.T) { + cases := []struct { + name string + testDir string + directoryStructure fsNodeSimple + expectedResult bool + }{ + { + name: "simple case having Chart yaml file in the root", + testDir: "simpleok", + directoryStructure: fsNodeSimple{ + name: "Chart.yaml", + }, + expectedResult: true, + }, + { + name: "Chart.yml file instead of Chart.yaml", + testDir: "extensionnotyaml", + directoryStructure: fsNodeSimple{ + name: "Chart.yml", + }, + expectedResult: false, + }, + { + name: "simple case not having Chart yaml file in the root", + testDir: "simplenotfound", + directoryStructure: fsNodeSimple{ + name: "whatever.foo", + }, + expectedResult: false, + }, + { + name: "Chart.yaml is located in a subdirectory", + testDir: "subdir", + directoryStructure: fsNodeSimple{ + isDir: true, + children: []fsNodeSimple{ + { + name: "Chart.yaml", + }, + }, + name: "whatever", + }, + expectedResult: false, + }, + } + + base, err := os.MkdirTemp("", "test-fleet") + require.NoError(t, err) + + defer os.RemoveAll(base) + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + testDirPath := filepath.Join(base, c.testDir) + err := os.Mkdir(testDirPath, 0777) + require.NoError(t, err) + defer os.RemoveAll(testDirPath) + + createDirStruct(t, testDirPath, c.directoryStructure) + found := helmupdater.ChartYAMLExists(testDirPath) + + assert.Equal(t, c.expectedResult, found) + }) + } +} diff --git a/pkg/apis/fleet.cattle.io/v1alpha1/bundledeployment_types.go b/pkg/apis/fleet.cattle.io/v1alpha1/bundledeployment_types.go index 91d4b00e2f..0cb9daddb9 100644 --- a/pkg/apis/fleet.cattle.io/v1alpha1/bundledeployment_types.go +++ b/pkg/apis/fleet.cattle.io/v1alpha1/bundledeployment_types.go @@ -238,6 +238,9 @@ type HelmOptions struct { // SkipSchemaValidation allows skipping schema validation against the chart values SkipSchemaValidation bool `json:"skipSchemaValidation,omitempty"` + + // DisableDependencyUpdate allows skipping chart dependencies update + DisableDependencyUpdate bool `json:"disableDependencyUpdate,omitempty"` } // IgnoreOptions defines conditions to be ignored when monitoring the Bundle.