From 480d1c260f7285c4aefab74032ea5c4807682d59 Mon Sep 17 00:00:00 2001 From: Linda Siebert <39100394+LindaSieb@users.noreply.github.com> Date: Tue, 25 Jan 2022 10:42:42 +0100 Subject: [PATCH] Add testing for helm during acceptance stage (#3402) * Add kubernetesDeploy to Acceptance * Add more kubernetesDeploy * Add helm tests * Change documentation * Fix docu * Change generated * Add tests * Add groovy tests * Fix tests * Change tests Co-authored-by: Thorsten Duda --- cmd/kubernetesDeploy.go | 20 ++ cmd/kubernetesDeploy_generated.go | 22 ++ cmd/kubernetesDeploy_test.go | 231 ++++++++++++++++++ resources/metadata/kubernetesDeploy.yaml | 18 ++ .../PiperPipelineStageAcceptanceTest.groovy | 20 +- vars/piperPipelineStageAcceptance.groovy | 9 + 6 files changed, 319 insertions(+), 1 deletion(-) diff --git a/cmd/kubernetesDeploy.go b/cmd/kubernetesDeploy.go index ca4188567..5c76588da 100644 --- a/cmd/kubernetesDeploy.go +++ b/cmd/kubernetesDeploy.go @@ -225,6 +225,26 @@ func runHelmDeploy(config kubernetesDeployOptions, utils kubernetesDeployUtils, if err := utils.RunExecutable("helm", upgradeParams...); err != nil { log.Entry().WithError(err).Fatal("Helm upgrade call failed") } + + testParams := []string{ + "test", + config.DeploymentName, + "--namespace", config.Namespace, + } + + if config.ShowTestLogs { + testParams = append( + testParams, + "--logs", + ) + } + + if config.RunHelmTests { + if err := utils.RunExecutable("helm", testParams...); err != nil { + log.Entry().WithError(err).Fatal("Helm test call failed") + } + } + return nil } diff --git a/cmd/kubernetesDeploy_generated.go b/cmd/kubernetesDeploy_generated.go index f28a0f851..62519d0a7 100644 --- a/cmd/kubernetesDeploy_generated.go +++ b/cmd/kubernetesDeploy_generated.go @@ -35,6 +35,8 @@ type kubernetesDeployOptions struct { Image string `json:"image,omitempty"` IngressHosts []string `json:"ingressHosts,omitempty"` KeepFailedDeployments bool `json:"keepFailedDeployments,omitempty"` + RunHelmTests bool `json:"runHelmTests,omitempty"` + ShowTestLogs bool `json:"showTestLogs,omitempty"` KubeConfig string `json:"kubeConfig,omitempty"` KubeContext string `json:"kubeContext,omitempty"` KubeToken string `json:"kubeToken,omitempty"` @@ -174,6 +176,8 @@ func addKubernetesDeployFlags(cmd *cobra.Command, stepConfig *kubernetesDeployOp cmd.Flags().StringVar(&stepConfig.Image, "image", os.Getenv("PIPER_image"), "Full name of the image to be deployed.") cmd.Flags().StringSliceVar(&stepConfig.IngressHosts, "ingressHosts", []string{}, "(Deprecated) List of ingress hosts to be exposed via helm deployment.") cmd.Flags().BoolVar(&stepConfig.KeepFailedDeployments, "keepFailedDeployments", false, "Defines whether a failed deployment will be purged") + cmd.Flags().BoolVar(&stepConfig.RunHelmTests, "runHelmTests", false, "Defines whether or not to run helm tests against the recently deployed release") + cmd.Flags().BoolVar(&stepConfig.ShowTestLogs, "showTestLogs", false, "Defines whether to print the pod logs after running helm tests") cmd.Flags().StringVar(&stepConfig.KubeConfig, "kubeConfig", os.Getenv("PIPER_kubeConfig"), "Defines the path to the \"kubeconfig\" file.") cmd.Flags().StringVar(&stepConfig.KubeContext, "kubeContext", os.Getenv("PIPER_kubeContext"), "Defines the context to use from the \"kubeconfig\" file.") cmd.Flags().StringVar(&stepConfig.KubeToken, "kubeToken", os.Getenv("PIPER_kubeToken"), "Contains the id_token used by kubectl for authentication. Consider using kubeConfig parameter instead.") @@ -415,6 +419,24 @@ func kubernetesDeployMetadata() config.StepData { Aliases: []config.Alias{}, Default: false, }, + { + Name: "runHelmTests", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + Default: false, + }, + { + Name: "showTestLogs", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, + Type: "bool", + Mandatory: false, + Aliases: []config.Alias{}, + Default: false, + }, { Name: "kubeConfig", ResourceRef: []config.ResourceReference{ diff --git a/cmd/kubernetesDeploy_test.go b/cmd/kubernetesDeploy_test.go index d9a3edd71..545462544 100644 --- a/cmd/kubernetesDeploy_test.go +++ b/cmd/kubernetesDeploy_test.go @@ -370,6 +370,237 @@ func TestRunKubernetesDeploy(t *testing.T) { }, mockUtils.Calls[1].Params, "Wrong upgrade parameters") }) + t.Run("test helm v3 - runs helm tests", func(t *testing.T) { + opts := kubernetesDeployOptions{ + ContainerRegistryURL: "https://my.registry:55555", + ContainerRegistryUser: "registryUser", + ContainerRegistryPassword: "dummy", + ContainerRegistrySecret: "testSecret", + ChartPath: "path/to/chart", + DeploymentName: "deploymentName", + DeployTool: "helm3", + ForceUpdates: true, + HelmDeployWaitSeconds: 400, + HelmValues: []string{"values1.yaml", "values2.yaml"}, + Image: "path/to/Image:latest", + AdditionalParameters: []string{"--testParam", "testValue"}, + KubeContext: "testCluster", + Namespace: "deploymentNamespace", + DockerConfigJSON: ".pipeline/docker/config.json", + RunHelmTests: true, + } + + dockerConfigJSON := `{"kind": "Secret","data":{".dockerconfigjson": "ThisIsOurBase64EncodedSecret=="}}` + + mockUtils := newKubernetesDeployMockUtils() + mockUtils.StdoutReturn = map[string]string{ + `kubectl create secret generic testSecret --from-file=.dockerconfigjson=.pipeline/docker/config.json --type=kubernetes.io/dockerconfigjson --insecure-skip-tls-verify=true --dry-run=client --output=json`: dockerConfigJSON, + } + + var stdout bytes.Buffer + + runKubernetesDeploy(opts, mockUtils, &stdout) + + assert.Equal(t, "kubectl", mockUtils.Calls[0].Exec, "Wrong secret creation command") + assert.Equal(t, []string{ + "create", + "secret", + "generic", + "testSecret", + "--from-file=.dockerconfigjson=.pipeline/docker/config.json", + "--type=kubernetes.io/dockerconfigjson", + "--insecure-skip-tls-verify=true", + "--dry-run=client", + "--output=json"}, + mockUtils.Calls[0].Params, "Wrong secret creation parameters") + + assert.Equal(t, "helm", mockUtils.Calls[1].Exec, "Wrong upgrade command") + assert.Equal(t, []string{ + "upgrade", + "deploymentName", + "path/to/chart", + "--values", + "values1.yaml", + "--values", + "values2.yaml", + "--install", + "--namespace", + "deploymentNamespace", + "--set", + "image.repository=my.registry:55555/path/to/Image,image.tag=latest,secret.name=testSecret,secret.dockerconfigjson=ThisIsOurBase64EncodedSecret==,imagePullSecrets[0].name=testSecret", + "--force", + "--wait", + "--timeout", + "400s", + "--atomic", + "--kube-context", + "testCluster", + "--testParam", + "testValue", + }, mockUtils.Calls[1].Params, "Wrong upgrade parameters") + + assert.Equal(t, "helm", mockUtils.Calls[2].Exec, "Wrong test command") + assert.Equal(t, []string{ + "test", + "deploymentName", + "--namespace", + "deploymentNamespace", + }, mockUtils.Calls[2].Params, "Wrong test parameters") + }) + + t.Run("test helm v3 - runs helm tests with logs", func(t *testing.T) { + opts := kubernetesDeployOptions{ + ContainerRegistryURL: "https://my.registry:55555", + ContainerRegistryUser: "registryUser", + ContainerRegistryPassword: "dummy", + ContainerRegistrySecret: "testSecret", + ChartPath: "path/to/chart", + DeploymentName: "deploymentName", + DeployTool: "helm3", + ForceUpdates: true, + HelmDeployWaitSeconds: 400, + HelmValues: []string{"values1.yaml", "values2.yaml"}, + Image: "path/to/Image:latest", + AdditionalParameters: []string{"--testParam", "testValue"}, + KubeContext: "testCluster", + Namespace: "deploymentNamespace", + DockerConfigJSON: ".pipeline/docker/config.json", + RunHelmTests: true, + ShowTestLogs: true, + } + + dockerConfigJSON := `{"kind": "Secret","data":{".dockerconfigjson": "ThisIsOurBase64EncodedSecret=="}}` + + mockUtils := newKubernetesDeployMockUtils() + mockUtils.StdoutReturn = map[string]string{ + `kubectl create secret generic testSecret --from-file=.dockerconfigjson=.pipeline/docker/config.json --type=kubernetes.io/dockerconfigjson --insecure-skip-tls-verify=true --dry-run=client --output=json`: dockerConfigJSON, + } + + var stdout bytes.Buffer + + runKubernetesDeploy(opts, mockUtils, &stdout) + + assert.Equal(t, "kubectl", mockUtils.Calls[0].Exec, "Wrong secret creation command") + assert.Equal(t, []string{ + "create", + "secret", + "generic", + "testSecret", + "--from-file=.dockerconfigjson=.pipeline/docker/config.json", + "--type=kubernetes.io/dockerconfigjson", + "--insecure-skip-tls-verify=true", + "--dry-run=client", + "--output=json"}, + mockUtils.Calls[0].Params, "Wrong secret creation parameters") + + assert.Equal(t, "helm", mockUtils.Calls[1].Exec, "Wrong upgrade command") + assert.Equal(t, []string{ + "upgrade", + "deploymentName", + "path/to/chart", + "--values", + "values1.yaml", + "--values", + "values2.yaml", + "--install", + "--namespace", + "deploymentNamespace", + "--set", + "image.repository=my.registry:55555/path/to/Image,image.tag=latest,secret.name=testSecret,secret.dockerconfigjson=ThisIsOurBase64EncodedSecret==,imagePullSecrets[0].name=testSecret", + "--force", + "--wait", + "--timeout", + "400s", + "--atomic", + "--kube-context", + "testCluster", + "--testParam", + "testValue", + }, mockUtils.Calls[1].Params, "Wrong upgrade parameters") + + assert.Equal(t, "helm", mockUtils.Calls[2].Exec, "Wrong test command") + assert.Equal(t, []string{ + "test", + "deploymentName", + "--namespace", + "deploymentNamespace", + "--logs", + }, mockUtils.Calls[2].Params, "Wrong test parameters") + }) + + t.Run("test helm v3 - should not run helm tests", func(t *testing.T) { + opts := kubernetesDeployOptions{ + ContainerRegistryURL: "https://my.registry:55555", + ContainerRegistryUser: "registryUser", + ContainerRegistryPassword: "dummy", + ContainerRegistrySecret: "testSecret", + ChartPath: "path/to/chart", + DeploymentName: "deploymentName", + DeployTool: "helm3", + ForceUpdates: true, + HelmDeployWaitSeconds: 400, + HelmValues: []string{"values1.yaml", "values2.yaml"}, + Image: "path/to/Image:latest", + AdditionalParameters: []string{"--testParam", "testValue"}, + KubeContext: "testCluster", + Namespace: "deploymentNamespace", + DockerConfigJSON: ".pipeline/docker/config.json", + RunHelmTests: false, + ShowTestLogs: true, + } + + dockerConfigJSON := `{"kind": "Secret","data":{".dockerconfigjson": "ThisIsOurBase64EncodedSecret=="}}` + + mockUtils := newKubernetesDeployMockUtils() + mockUtils.StdoutReturn = map[string]string{ + `kubectl create secret generic testSecret --from-file=.dockerconfigjson=.pipeline/docker/config.json --type=kubernetes.io/dockerconfigjson --insecure-skip-tls-verify=true --dry-run=client --output=json`: dockerConfigJSON, + } + + var stdout bytes.Buffer + + runKubernetesDeploy(opts, mockUtils, &stdout) + + assert.Equal(t, "kubectl", mockUtils.Calls[0].Exec, "Wrong secret creation command") + assert.Equal(t, []string{ + "create", + "secret", + "generic", + "testSecret", + "--from-file=.dockerconfigjson=.pipeline/docker/config.json", + "--type=kubernetes.io/dockerconfigjson", + "--insecure-skip-tls-verify=true", + "--dry-run=client", + "--output=json"}, + mockUtils.Calls[0].Params, "Wrong secret creation parameters") + + assert.Equal(t, "helm", mockUtils.Calls[1].Exec, "Wrong upgrade command") + assert.Equal(t, []string{ + "upgrade", + "deploymentName", + "path/to/chart", + "--values", + "values1.yaml", + "--values", + "values2.yaml", + "--install", + "--namespace", + "deploymentNamespace", + "--set", + "image.repository=my.registry:55555/path/to/Image,image.tag=latest,secret.name=testSecret,secret.dockerconfigjson=ThisIsOurBase64EncodedSecret==,imagePullSecrets[0].name=testSecret", + "--force", + "--wait", + "--timeout", + "400s", + "--atomic", + "--kube-context", + "testCluster", + "--testParam", + "testValue", + }, mockUtils.Calls[1].Params, "Wrong upgrade parameters") + + assert.Equal(t, 2, len(mockUtils.Calls), "Too many helm calls") + }) + t.Run("test helm v3 - with containerImageName and containerImageTag instead of image", func(t *testing.T) { opts := kubernetesDeployOptions{ ContainerRegistryURL: "https://my.registry:55555", diff --git a/resources/metadata/kubernetesDeploy.yaml b/resources/metadata/kubernetesDeploy.yaml index 29d62d99d..c17302b23 100644 --- a/resources/metadata/kubernetesDeploy.yaml +++ b/resources/metadata/kubernetesDeploy.yaml @@ -260,6 +260,24 @@ spec: - PARAMETERS - STAGES - STEPS + - name: runHelmTests + type: bool + description: Defines whether or not to run helm tests against the recently deployed release + default: false + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS + - name: showTestLogs + type: bool + description: Defines whether to print the pod logs after running helm tests + default: false + scope: + - GENERAL + - PARAMETERS + - STAGES + - STEPS - name: kubeConfig type: string description: Defines the path to the "kubeconfig" file. diff --git a/test/groovy/templates/PiperPipelineStageAcceptanceTest.groovy b/test/groovy/templates/PiperPipelineStageAcceptanceTest.groovy index e9b384d04..c73e21306 100644 --- a/test/groovy/templates/PiperPipelineStageAcceptanceTest.groovy +++ b/test/groovy/templates/PiperPipelineStageAcceptanceTest.groovy @@ -56,6 +56,11 @@ class PiperPipelineStageAcceptanceTest extends BasePiperTest { stepParameters.neoDeploy = m }) + helper.registerAllowedMethod('kubernetesDeploy', [Map.class], {m -> + stepsCalled.add('kubernetesDeploy') + stepParameters.kubernetesDeploy = m + }) + helper.registerAllowedMethod('gaugeExecuteTests', [Map.class], {m -> stepsCalled.add('gaugeExecuteTests') stepParameters.gaugeExecuteTests = m @@ -89,7 +94,7 @@ class PiperPipelineStageAcceptanceTest extends BasePiperTest { script: nullScript, juStabUtils: utils ) - assertThat(stepsCalled, not(anyOf(hasItem('cloudFoundryDeploy'), hasItem('neoDeploy'), hasItem('healthExecuteCheck'), hasItem('newmanExecute'), hasItem('uiVeri5ExecuteTests'), hasItem('gaugeExecuteTests')))) + assertThat(stepsCalled, not(anyOf(hasItem('cloudFoundryDeploy'), hasItem('neoDeploy'), hasItem('kubernetesDeploy'), hasItem('healthExecuteCheck'), hasItem('newmanExecute'), hasItem('uiVeri5ExecuteTests'), hasItem('gaugeExecuteTests')))) } @@ -132,6 +137,19 @@ class PiperPipelineStageAcceptanceTest extends BasePiperTest { assertThat(stepsCalled, not(hasItem('testsPublishResults'))) } + @Test + void testReleaseStageKubernetes() { + + jsr.step.piperPipelineStageRelease( + script: nullScript, + juStabUtils: utils, + kubernetesDeploy: true + ) + + assertThat(stepsCalled, hasItem('kubernetesDeploy')) + assertThat(stepsCalled, not(hasItem('testsPublishResults'))) + } + @Test void testAcceptanceStageGauge() { diff --git a/vars/piperPipelineStageAcceptance.groovy b/vars/piperPipelineStageAcceptance.groovy index 5a00aea72..e8d5ac3be 100644 --- a/vars/piperPipelineStageAcceptance.groovy +++ b/vars/piperPipelineStageAcceptance.groovy @@ -17,6 +17,8 @@ import static com.sap.piper.Prerequisites.checkScript 'cloudFoundryDeploy', /** Performs behavior-driven tests using Gauge test framework against the deployed application/service. */ 'gaugeExecuteTests', + /** For Kubernetes use-cases: Performs deployment to Kubernetes landscape. */ + 'kubernetesDeploy', /** * Performs health check in order to prove one aspect of operational readiness. * In order to be able to respond to health checks from infrastructure components (like load balancers) it is important to provide one unprotected application endpoint which allows a judgement about the health of your application. @@ -58,6 +60,7 @@ void call(Map parameters = [:]) { .mixin(parameters, PARAMETER_KEYS) .addIfEmpty('multicloudDeploy', script.commonPipelineEnvironment.configuration.runStep?.get(stageName)?.multicloudDeploy) .addIfEmpty('cloudFoundryDeploy', script.commonPipelineEnvironment.configuration.runStep?.get(stageName)?.cloudFoundryDeploy) + .addIfEmpty('kubernetesDeploy', script.commonPipelineEnvironment.configuration.runStep?.get(stageName)?.kubernetesDeploy) .addIfEmpty('gaugeExecuteTests', script.commonPipelineEnvironment.configuration.runStep?.get(stageName)?.gaugeExecuteTests) .addIfEmpty('healthExecuteCheck', script.commonPipelineEnvironment.configuration.runStep?.get(stageName)?.healthExecuteCheck) .addIfEmpty('neoDeploy', script.commonPipelineEnvironment.configuration.runStep?.get(stageName)?.neoDeploy) @@ -88,6 +91,12 @@ void call(Map parameters = [:]) { neoDeploy script: script } } + + if (config.kubernetesDeploy){ + durationMeasure(script: script, measurementName: 'deploy_release_kubernetes_duration') { + kubernetesDeploy script: script + } + } } if (config.healthExecuteCheck) {