diff --git a/.circleci/config.yml b/.circleci/config.yml index 072c6736..35be6bc8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,6 +6,21 @@ jobs: steps: - checkout - run: ct lint --config .circleci/chart-testing.yaml --lint-conf .circleci/lintconf.yaml + test: + machine: + image: ubuntu-1604:201903-01 + steps: + - checkout + - run: | # Required to drop files from repo into test image + docker create -v /app/test --name test_sources alpine:3.4 /bin/true + docker cp charts test_sources:/app/test + docker run --name terratest --volumes-from test_sources seanson/terratest-helm-tester:latest + docker cp terratest:/app/out ./test_results + - store_artifacts: + path: ./test_results + - store_test_results: + path: ./test_results + # Technically this only needs to be run on master, but it's good to have it run on every PR # so that it is regularly tested. publish: @@ -24,7 +39,7 @@ jobs: set -x if [ "$CIRCLE_BRANCH" = "master" ]; then export GIT_PUSH=true - else + else export GIT_PUSH=false fi sh ./scripts/publish.sh @@ -33,6 +48,7 @@ workflows: workflow: jobs: - lint + - test - publish: requires: - - lint \ No newline at end of file + - lint diff --git a/charts/argo-cd/.helmignore b/charts/argo-cd/.helmignore index 2df6bf53..0a6a49de 100644 --- a/charts/argo-cd/.helmignore +++ b/charts/argo-cd/.helmignore @@ -1,2 +1,3 @@ *.tgz output +test diff --git a/charts/argo-cd/test/helm_test.go b/charts/argo-cd/test/helm_test.go new file mode 100644 index 00000000..8083b0f0 --- /dev/null +++ b/charts/argo-cd/test/helm_test.go @@ -0,0 +1,386 @@ +package helm_test + +import ( + "path/filepath" + "testing" + + . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/require" + + monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1" + certmanagerv1alpha2 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2" + appsv1 "k8s.io/api/apps/v1" + v1 "k8s.io/api/core/v1" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/k8s" +) + +func TestApplicationControllerImageOverrides(t *testing.T) { + t.Parallel() + helmChartPath, err := filepath.Abs("../") + releaseName := "argo-cd" + require.NoError(t, err) + + options := &helm.Options{ + SetValues: map[string]string{ + "controller.image.repository": "custom/argocd", + "controller.image.tag": "1.2.3", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", "default"), + } + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/argocd-application-controller/deployment.yaml"}) + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(t, output, &deployment) + + expectedContainerImage := "custom/argocd:1.2.3" + deploymentContainers := deployment.Spec.Template.Spec.Containers + require.Equal(t, len(deploymentContainers), 1) + require.Equal(t, deploymentContainers[0].Image, expectedContainerImage) +} + +// https://github.com/argoproj/argo-helm/pull/171 +func TestApplicationCertConfig(t *testing.T) { + t.Parallel() + helmChartPath, err := filepath.Abs("../") + releaseName := "argo-cd" + require.NoError(t, err) + + options := &helm.Options{ + SetValues: map[string]string{ + "server.certificate.enabled": "true", + "server.certificate.issuer.kind": "certKind", + "server.certificate.issuer.name": "certName", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", "default"), + } + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/argocd-server/certificate.yaml"}) + var certificate certmanagerv1alpha2.Certificate + helm.UnmarshalK8SYaml(t, output, &certificate) + + // Finally, we verify the deployment pod template spec is set to the expected container image value + // expectedContainerImage := "custom/argocd:1.2.3" + // deploymentContainers := deployment.Spec.Template.Spec.Containers + require.Equal(t, certificate.Spec.IssuerRef.Kind, "certKind") + require.Equal(t, certificate.Spec.IssuerRef.Name, "certName") + // require.Equal(t, deploymentContainers[0].Image, expectedContainerImage) +} + +func TestArgoCDConfigsSecret(t *testing.T) { + t.Parallel() + helmChartPath, err := filepath.Abs("../") + releaseName := "argo-cd" + require.NoError(t, err) + + options := &helm.Options{ + SetValues: map[string]string{ + "configs.secret.argocdServerTlsConfig.key": "testKey", + "configs.secret.argocdServerTlsConfig.crt": "testCrt", + "configs.secret.argocdServerAdminPassword": "testPassword", + "configs.secret.githubSecret": "githubTest", + "configs.secret.gitlabSecret": "gitlabTest", + "configs.secret.bitbucketServerSecret": "bitbucketServerTest", + "configs.secret.bitbucketUUID": "bitbucketUUIDTest", + "configs.secret.gogsSecret": "gogsTest", + }, + KubectlOptions: k8s.NewKubectlOptions("", "", "default"), + } + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, []string{"templates/argocd-configs/argocd-secret.yaml"}) + var secret v1.Secret + helm.UnmarshalK8SYaml(t, output, &secret) + require.Equal(t, "testKey", string(secret.Data["tls.key"])) + require.Equal(t, "testCrt", string(secret.Data["tls.crt"])) + require.Equal(t, "testPassword", string(secret.Data["admin.password"])) + require.Equal(t, "githubTest", string(secret.Data["webhook.github.secret"])) + require.Equal(t, "gitlabTest", string(secret.Data["webhook.gitlab.secret"])) + require.Equal(t, "bitbucketServerTest", string(secret.Data["webhook.bitbucketserver.secret"])) + require.Equal(t, "bitbucketUUIDTest", string(secret.Data["webhook.bitbucket.uuid"])) + require.Equal(t, "gogsTest", string(secret.Data["webhook.gogs.secret"])) +} + +// https://github.com/argoproj/argo-helm/pull/180 +func TestArgoCDControllerServiceMonitors(t *testing.T) { + t.Parallel() + helmChartPath, err := filepath.Abs("../") + releaseName := "argo-cd" + require.NoError(t, err) + + tests := []struct { + Name string + DeploymentYAML []string + SetValues map[string]string + }{ + { + Name: "Given application controller servicemonitor is enabled", + DeploymentYAML: []string{"templates/argocd-application-controller/servicemonitor.yaml"}, + SetValues: map[string]string{ + "controller.metrics.enabled": "true", + "controller.metrics.serviceMonitor.enabled": "true", + }, + }, + { + Name: "Given server servicemonitor is enabled", + DeploymentYAML: []string{"templates/argocd-server/servicemonitor.yaml"}, + SetValues: map[string]string{ + "server.metrics.enabled": "true", + "server.metrics.serviceMonitor.enabled": "true", + }, + }, + { + Name: "Given reposerver servicemonitor is enabled", + DeploymentYAML: []string{"templates/argocd-repo-server/servicemonitor.yaml"}, + SetValues: map[string]string{ + "repoServer.metrics.enabled": "true", + "repoServer.metrics.serviceMonitor.enabled": "true", + }, + }, + } + + var servicemonitor monitoringv1.ServiceMonitor + + for _, test := range tests { + options := &helm.Options{ + SetValues: test.SetValues, + KubectlOptions: k8s.NewKubectlOptions("", "", "default"), + } + Convey(test.Name, t, func() { + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, test.DeploymentYAML) + helm.UnmarshalK8SYaml(t, output, &servicemonitor) + endpoints := servicemonitor.Spec.Endpoints + So(len(endpoints), ShouldEqual, 1) + So(endpoints[0].Path, ShouldEqual, "/metrics") + }) + } +} + +// https://github.com/argoproj/argo-helm/pull/181 +func TestArgoCDEnvironmentConfig(t *testing.T) { + t.Parallel() + helmChartPath, err := filepath.Abs("../") + releaseName := "argo-cd" + require.NoError(t, err) + + tests := []struct { + Name string + DeploymentYAML []string + SetValues map[string]string + }{ + { + Name: "Given an application controller environment variable", + DeploymentYAML: []string{"templates/argocd-application-controller/deployment.yaml"}, + SetValues: map[string]string{ + "controller.env[0].name": "TEST_ENV", + "controller.env[0].value": "TEST_VALUE", + }, + }, + { + Name: "Given a server environment variable", + DeploymentYAML: []string{"templates/argocd-server/deployment.yaml"}, + SetValues: map[string]string{ + "server.env[0].name": "TEST_ENV", + "server.env[0].value": "TEST_VALUE", + }, + }, + { + Name: "Given a repo-server environment variable", + DeploymentYAML: []string{"templates/argocd-repo-server/deployment.yaml"}, + SetValues: map[string]string{ + "repoServer.env[0].name": "TEST_ENV", + "repoServer.env[0].value": "TEST_VALUE", + }, + }, + } + + var deployment appsv1.Deployment + + for _, test := range tests { + options := &helm.Options{ + SetValues: test.SetValues, + KubectlOptions: k8s.NewKubectlOptions("", "", "default"), + } + Convey(test.Name, t, func() { + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, test.DeploymentYAML) + helm.UnmarshalK8SYaml(t, output, &deployment) + deploymentContainers := deployment.Spec.Template.Spec.Containers + So(len(deploymentContainers), ShouldEqual, 1) + So(len(deploymentContainers[0].Env), ShouldEqual, 1) + So(deploymentContainers[0].Env[0].Name, ShouldEqual, "TEST_ENV") + So(deploymentContainers[0].Env[0].Value, ShouldEqual, "TEST_VALUE") + }) + } +} + +// https://github.com/argoproj/argo-helm/pull/177 +func TestArgoCDRepoServerCustomTools(t *testing.T) { + t.Parallel() + helmChartPath, err := filepath.Abs("../") + releaseName := "argo-cd" + require.NoError(t, err) + + tests := []struct { + Name string + DeploymentYAML []string + SetValues map[string]string + SetStrValues map[string]string + }{ + { + Name: "Given a repo-server initContainer", + DeploymentYAML: []string{"templates/argocd-repo-server/deployment.yaml"}, + SetValues: map[string]string{ + "repoServer.volumes[0].name": "custom-tools", + "repoServer.initContainers[0].name": "download-tools", + "repoServer.initContainers[0].image": "alpine:3.8", + "repoServer.initContainers[0].volumeMounts[0].mountPath": "/usr/local/bin/helm", + "repoServer.initContainers[0].volumeMounts[0].name": "custom-tools", + "repoServer.volumeMounts[0].name": "custom-tools", + "repoServer.volumeMounts[0].mountPath": "/usr/local/bin/helm", + "repoServer.volumeMounts[0].subPath": "helm", + }, + SetStrValues: map[string]string{ + "repoServer.volumes[0].emptyDir": "{}", + }, + }, + } + + var deployment appsv1.Deployment + + for _, test := range tests { + options := &helm.Options{ + SetValues: test.SetValues, + KubectlOptions: k8s.NewKubectlOptions("", "", "default"), + } + Convey(test.Name, t, func() { + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, test.DeploymentYAML) + helm.UnmarshalK8SYaml(t, output, &deployment) + initContainers := deployment.Spec.Template.Spec.InitContainers + + So(len(deployment.Spec.Template.Spec.Volumes), ShouldEqual, 2) + So(deployment.Spec.Template.Spec.Volumes[0].Name, ShouldEqual, "custom-tools") + So(deployment.Spec.Template.Spec.Volumes[0].EmptyDir, ShouldEqual, nil) + + So(len(deployment.Spec.Template.Spec.Containers[0].VolumeMounts), ShouldEqual, 2) + So(deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].Name, ShouldEqual, "custom-tools") + So(deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].MountPath, ShouldEqual, "/usr/local/bin/helm") + So(deployment.Spec.Template.Spec.Containers[0].VolumeMounts[0].SubPath, ShouldEqual, "helm") + + So(len(initContainers), ShouldEqual, 1) + So(len(initContainers[0].VolumeMounts), ShouldEqual, 1) + So(initContainers[0].Name, ShouldEqual, "download-tools") + So(initContainers[0].Image, ShouldEqual, "alpine:3.8") + So(initContainers[0].VolumeMounts[0].Name, ShouldEqual, "custom-tools") + So(initContainers[0].VolumeMounts[0].MountPath, ShouldEqual, "/usr/local/bin/helm") + }) + } +} + +// https://github.com/argoproj/argo-helm/pull/178 +func TestArgoCDControllerPrometheusRules(t *testing.T) { + t.Parallel() + helmChartPath, err := filepath.Abs("../") + releaseName := "argo-cd" + require.NoError(t, err) + + tests := []struct { + Name string + DeploymentYAML []string + SetValues map[string]string + }{ + { + Name: "Given application controller rules are enabled", + DeploymentYAML: []string{"templates/argocd-application-controller/prometheusrule.yaml"}, + SetValues: map[string]string{ + "controller.metrics.enabled": "true", + "controller.metrics.rules.enabled": "true", + "controller.metrics.rules.spec[0].alert": "TestAlert", + "controller.metrics.rules.spec[0].expr": "absent(argocd_app_info)", + "controller.metrics.rules.spec[0].for": "15m", + "controller.metrics.rules.spec[0].labels.severity": "critical", + "controller.metrics.rules.spec[0].annotations.summary": "Test summary", + "controller.metrics.rules.spec[0].annotations.description": "Test description", + }, + }, + } + + var servicemonitor monitoringv1.PrometheusRule + + for _, test := range tests { + options := &helm.Options{ + SetValues: test.SetValues, + KubectlOptions: k8s.NewKubectlOptions("", "", "default"), + } + Convey(test.Name, t, func() { + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, test.DeploymentYAML) + helm.UnmarshalK8SYaml(t, output, &servicemonitor) + + So(len(servicemonitor.Spec.Groups), ShouldEqual, 1) + rules := servicemonitor.Spec.Groups[0].Rules + So(len(rules), ShouldEqual, 1) + So(rules[0].Alert, ShouldEqual, "TestAlert") + // Requires StrVal as Expr is type IntOrStr + So(rules[0].Expr.StrVal, ShouldEqual, "absent(argocd_app_info)") + So(rules[0].For, ShouldEqual, "15m") + So(rules[0].Labels["severity"], ShouldEqual, "critical") + So(rules[0].Annotations["summary"], ShouldEqual, "Test summary") + So(rules[0].Annotations["description"], ShouldEqual, "Test description") + }) + } +} + +// https://github.com/argoproj/argo-helm/pull/185 +func TestArgoCDSecurityContext(t *testing.T) { + t.Parallel() + helmChartPath, err := filepath.Abs("../") + releaseName := "argo-cd" + require.NoError(t, err) + + tests := []struct { + Name string + DeploymentYAML []string + SetValues map[string]string + SetStrValues map[string]string + }{ + { + Name: "Given a global securityContext and an application controller deployment", + DeploymentYAML: []string{"templates/argocd-application-controller/deployment.yaml"}, + SetValues: map[string]string{ + "global.securityContext.runAsUser": "999", + }, + }, + { + Name: "Given a global securityContext and a reposerver deployment", + DeploymentYAML: []string{"templates/argocd-repo-server/deployment.yaml"}, + SetValues: map[string]string{ + "global.securityContext.runAsUser": "999", + }, + }, + { + Name: "Given a global securityContext and a server deployment", + DeploymentYAML: []string{"templates/argocd-server/deployment.yaml"}, + SetValues: map[string]string{ + "global.securityContext.runAsUser": "999", + }, + }, + { + Name: "Given a global securityContext and a redis deployment", + DeploymentYAML: []string{"templates/redis/deployment.yaml"}, + SetValues: map[string]string{ + "global.securityContext.runAsUser": "999", + }, + }, + } + + var deployment appsv1.Deployment + + for _, test := range tests { + options := &helm.Options{ + SetValues: test.SetValues, + KubectlOptions: k8s.NewKubectlOptions("", "", "default"), + } + Convey(test.Name, t, func() { + output := helm.RenderTemplate(t, options, helmChartPath, releaseName, test.DeploymentYAML) + helm.UnmarshalK8SYaml(t, output, &deployment) + + So(*deployment.Spec.Template.Spec.SecurityContext.RunAsUser, ShouldEqual, 999) + }) + } +}