From d6e00bcca6ef37ae7a2388a091a096bba4d4056f Mon Sep 17 00:00:00 2001 From: Philipp Pixel Date: Tue, 28 Nov 2023 13:15:44 +0100 Subject: [PATCH 1/4] #115 add helm installation --- CHANGELOG.md | 3 ++ README.md | 6 +++- src/com/cloudogu/ces/cesbuildlib/K3d.groovy | 32 +++++++++++++++++++ .../cloudogu/ces/cesbuildlib/K3dTest.groovy | 30 +++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0286193..66ea66d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- Add Helm installation with `k3d.installHelm()`; #115 + ## [1.67.0](https://github.com/cloudogu/ces-build-lib/releases/tag/1.67.0) - 2023-09-04 ### Changed - Switch to hadolint Dockerfile linter; #111 diff --git a/README.md b/README.md index f8d2dc1..c6cbbec 100644 --- a/README.md +++ b/README.md @@ -1067,7 +1067,7 @@ if (response.status == '201' && response.content-type == 'application/json') { # K3d -`K3d` provides functions to set up and administer a lokal k3s cluster in Docker. +`K3d` provides functions to set up and administer a local k3s cluster in Docker. Example: @@ -1077,11 +1077,15 @@ K3d k3d = new K3d(this, env.WORKSPACE, env.PATH) try { stage('Set up k3d cluster') { k3d.startK3d() + k3d.installHelm() // Helm may be installed additionally } stage('Do something with your cluster') { k3d.kubectl("get nodes") } + stage('Apply your Helm chart') { // requires previously called installHelm() + k3d.helm("apply path/to/your/chart") + } stage('build and push development artefact') { String myCurrentArtefactVersion = "yourTag-1.2.3-dev" diff --git a/src/com/cloudogu/ces/cesbuildlib/K3d.groovy b/src/com/cloudogu/ces/cesbuildlib/K3d.groovy index 09177e6..22f807d 100644 --- a/src/com/cloudogu/ces/cesbuildlib/K3d.groovy +++ b/src/com/cloudogu/ces/cesbuildlib/K3d.groovy @@ -189,6 +189,23 @@ class K3d { return script.sh(script: "sudo KUBECONFIG=${k3dDir}/.kube/config kubectl ${command}", returnStdout: returnStdout) } + /** + * Runs any helm command. + * @param command must contain all necessary arguments and flags. + */ + void helm(command) { + helm(command, false) + } + + /** + * Runs any helm command and returns the generated output if configured. + * @param command must contain all necessary arguments and flags. + * @param returnStdout if set to true this method returns the standard output stream generated by helm. + */ + String helm(command, returnStdout) { + return script.sh(script: "sudo KUBECONFIG=${k3dDir}/.kube/config helm ${command}", returnStdout: returnStdout) + } + String kubectlHideCommand(command, returnStdout) { return script.sh(script: "set +x; sudo KUBECONFIG=${k3dDir}/.kube/config kubectl ${command}", returnStdout: returnStdout) } @@ -359,6 +376,21 @@ spec: script.echo "Installing kubectl..." script.sh script: "sudo snap install kubectl --classic" } + /** + * Installs helm + */ + void installHelm() { + def helmStatusCode = script.sh script: "snap list helm", returnStatus: true + if (helmStatusCode == 0 || helmStatusCode.equals("0")) { + script.echo "helm already installed" + return + } + + script.echo "Installing helm..." + script.withEnv(["HOME=${k3dDir}", "PATH=${k3dBinaryDir}:${path}"]) { + script.sh script: "sudo snap install helm --classic" + } + } private String getExecPodName(String dogu, Integer timeout, Integer interval) { for (int i = 0; i < timeout / interval; i++) { diff --git a/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy b/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy index 80a78ef..e36ec07 100644 --- a/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy @@ -53,6 +53,36 @@ class K3dTest extends GroovyTestCase { assertThat(scriptMock.allActualArgs[0].trim()).isEqualTo("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get nodes".trim()) assertThat(scriptMock.allActualArgs.size()).isEqualTo(1) } + void testHelm() { + // given + String workspaceDir = "leWorkspace" + def scriptMock = new ScriptMock() + K3d sut = new K3d(scriptMock, workspaceDir, "leK3dWorkSpace", "path") + + // when + sut.helm("apply path/to/chart/") + + // then + assertThat(scriptMock.allActualArgs[0].trim()).isEqualTo("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config helm apply path/to/chart/".trim()) + assertThat(scriptMock.allActualArgs.size()).isEqualTo(1) + } + + // we cannot test lazy-installation because the mock is incapable of mocking the right types, the right values + // and thus repeated calls to the same script with different results. + void testInstallHelm_initially() { + // given + String workspaceDir = "leWorkspace" + def scriptMock = new ScriptMock() + K3d sut = new K3d(scriptMock, workspaceDir, "leK3dWorkSpace", "path") + + // when + sut.installHelm() + + // then + assertThat(scriptMock.allActualArgs.size()).isEqualTo(2) + assertThat(scriptMock.allActualArgs[0].trim()).isEqualTo("snap list helm".trim()) + assertThat(scriptMock.allActualArgs[1].trim()).isEqualTo("sudo snap install helm --classic".trim()) + } void testStartK3d() { def workspaceDir = "leWorkspace" From 71c2d296adbeb383535d9ba99b0eb6974b07999a Mon Sep 17 00:00:00 2001 From: Philipp Pixel Date: Wed, 29 Nov 2023 16:26:03 +0100 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: nhinze23 <83591279+nhinze23@users.noreply.github.com> --- README.md | 2 +- test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c6cbbec..1cc4d79 100644 --- a/README.md +++ b/README.md @@ -1084,7 +1084,7 @@ try { k3d.kubectl("get nodes") } stage('Apply your Helm chart') { // requires previously called installHelm() - k3d.helm("apply path/to/your/chart") + k3d.helm("install path/to/your/chart") } stage('build and push development artefact') { diff --git a/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy b/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy index e36ec07..4b14bae 100644 --- a/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy @@ -60,10 +60,10 @@ class K3dTest extends GroovyTestCase { K3d sut = new K3d(scriptMock, workspaceDir, "leK3dWorkSpace", "path") // when - sut.helm("apply path/to/chart/") + sut.helm("install path/to/chart/") // then - assertThat(scriptMock.allActualArgs[0].trim()).isEqualTo("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config helm apply path/to/chart/".trim()) + assertThat(scriptMock.allActualArgs[0].trim()).isEqualTo("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config helm install path/to/chart/".trim()) assertThat(scriptMock.allActualArgs.size()).isEqualTo(1) } From d73975d92a05c94397e2557e09f5b24147a68e93 Mon Sep 17 00:00:00 2001 From: Philipp Pixel Date: Wed, 29 Nov 2023 16:31:47 +0100 Subject: [PATCH 3/4] #115 install helm at cluster start --- README.md | 3 +-- src/com/cloudogu/ces/cesbuildlib/K3d.groovy | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1cc4d79..4c9158e 100644 --- a/README.md +++ b/README.md @@ -1077,13 +1077,12 @@ K3d k3d = new K3d(this, env.WORKSPACE, env.PATH) try { stage('Set up k3d cluster') { k3d.startK3d() - k3d.installHelm() // Helm may be installed additionally } stage('Do something with your cluster') { k3d.kubectl("get nodes") } - stage('Apply your Helm chart') { // requires previously called installHelm() + stage('Apply your Helm chart') { k3d.helm("install path/to/your/chart") } diff --git a/src/com/cloudogu/ces/cesbuildlib/K3d.groovy b/src/com/cloudogu/ces/cesbuildlib/K3d.groovy index 22f807d..36517d4 100644 --- a/src/com/cloudogu/ces/cesbuildlib/K3d.groovy +++ b/src/com/cloudogu/ces/cesbuildlib/K3d.groovy @@ -91,6 +91,7 @@ class K3d { installLocalRegistry() initializeCluster() installKubectl() + installHelm() loginBackend() } } @@ -387,9 +388,7 @@ spec: } script.echo "Installing helm..." - script.withEnv(["HOME=${k3dDir}", "PATH=${k3dBinaryDir}:${path}"]) { - script.sh script: "sudo snap install helm --classic" - } + script.sh script: "sudo snap install helm --classic" } private String getExecPodName(String dogu, Integer timeout, Integer interval) { From 0a91f7e096c8c84f3e5e30b924c248ae272df9c9 Mon Sep 17 00:00:00 2001 From: Philipp Pixel Date: Wed, 29 Nov 2023 16:54:28 +0100 Subject: [PATCH 4/4] #115 fix tests after k3d install change --- .../cloudogu/ces/cesbuildlib/K3dTest.groovy | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy b/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy index 4b14bae..2263994 100644 --- a/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy +++ b/test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy @@ -35,9 +35,9 @@ class K3dTest extends GroovyTestCase { sut.deleteK3d() // then - assertThat(scriptMock.allActualArgs[20].trim()).contains("k3d registry delete citest-") - assertThat(scriptMock.allActualArgs[21].trim()).contains("k3d cluster delete citest-") - assertThat(scriptMock.allActualArgs.size()).isEqualTo(22) + assertThat(scriptMock.allActualArgs[22].trim()).contains("k3d registry delete citest-") + assertThat(scriptMock.allActualArgs[23].trim()).contains("k3d cluster delete citest-") + assertThat(scriptMock.allActualArgs.size()).isEqualTo(24) } void testKubectl() { @@ -104,19 +104,21 @@ class K3dTest extends GroovyTestCase { assertThat(scriptMock.allActualArgs[5].trim()).startsWith("k3d cluster create citest-") assertThat(scriptMock.allActualArgs[6].trim()).startsWith("k3d kubeconfig merge citest-") assertThat(scriptMock.allActualArgs[7].trim()).startsWith("snap list kubectl") - assertThat(scriptMock.allActualArgs[8].trim()).startsWith("sudo snap install kubectl") - assertThat(scriptMock.allActualArgs[9].trim()).startsWith("echo \"Using credentials: cesmarvin-setup\"") - assertThat(scriptMock.allActualArgs[10].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret k8s-dogu-operator-dogu-registry || true") - assertThat(scriptMock.allActualArgs[11].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret k8s-dogu-operator-docker-registry || true") - assertThat(scriptMock.allActualArgs[12].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create secret generic k8s-dogu-operator-dogu-registry --from-literal=endpoint=\"https://dogu.cloudogu.com/api/v2/dogus\" --from-literal=username=\"null\" --from-literal=password=\"null\"") - assertThat(scriptMock.allActualArgs[13].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create secret docker-registry k8s-dogu-operator-docker-registry --docker-server=\"registry.cloudogu.com\" --docker-username=\"null\" --docker-email=\"a@b.c\" --docker-password=\"null\"") - assertThat(scriptMock.allActualArgs[14].trim()).startsWith("echo \"Using credentials: harborhelmchartpush\"") - assertThat(scriptMock.allActualArgs[15].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete configmap component-operator-helm-repository || true") - assertThat(scriptMock.allActualArgs[16].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret component-operator-helm-registry || true") - assertThat(scriptMock.allActualArgs[17].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create configmap component-operator-helm-repository --from-literal=endpoint=\"registry.cloudogu.com\" --from-literal=schema=\"oci\" --from-literal=plainHttp=\"false\"") - assertThat(scriptMock.allActualArgs[18].trim()).startsWith("printf '%s:%s' 'null' 'null' | base64") - assertThat(scriptMock.allActualArgs[19].trim()).startsWith("set +x; sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl create secret generic component-operator-helm-registry --from-literal=config.json='{\"auths\": {\"registry.cloudogu.com\": {\"auth\": \"null\"}}}'") - assertThat(scriptMock.allActualArgs.size()).isEqualTo(20) + assertThat(scriptMock.allActualArgs[8].trim()).startsWith("sudo snap install kubectl --classic") + assertThat(scriptMock.allActualArgs[9].trim()).startsWith("snap list helm") + assertThat(scriptMock.allActualArgs[10].trim()).startsWith("sudo snap install helm --classic") + assertThat(scriptMock.allActualArgs[11].trim()).startsWith("echo \"Using credentials: cesmarvin-setup\"") + assertThat(scriptMock.allActualArgs[12].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret k8s-dogu-operator-dogu-registry || true") + assertThat(scriptMock.allActualArgs[13].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret k8s-dogu-operator-docker-registry || true") + assertThat(scriptMock.allActualArgs[14].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create secret generic k8s-dogu-operator-dogu-registry --from-literal=endpoint=\"https://dogu.cloudogu.com/api/v2/dogus\" --from-literal=username=\"null\" --from-literal=password=\"null\"") + assertThat(scriptMock.allActualArgs[15].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create secret docker-registry k8s-dogu-operator-docker-registry --docker-server=\"registry.cloudogu.com\" --docker-username=\"null\" --docker-email=\"a@b.c\" --docker-password=\"null\"") + assertThat(scriptMock.allActualArgs[16].trim()).startsWith("echo \"Using credentials: harborhelmchartpush\"") + assertThat(scriptMock.allActualArgs[17].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete configmap component-operator-helm-repository || true") + assertThat(scriptMock.allActualArgs[18].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret component-operator-helm-registry || true") + assertThat(scriptMock.allActualArgs[19].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create configmap component-operator-helm-repository --from-literal=endpoint=\"registry.cloudogu.com\" --from-literal=schema=\"oci\" --from-literal=plainHttp=\"false\"") + assertThat(scriptMock.allActualArgs[20].trim()).startsWith("printf '%s:%s' 'null' 'null' | base64") + assertThat(scriptMock.allActualArgs[21].trim()).startsWith("set +x; sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl create secret generic component-operator-helm-registry --from-literal=config.json='{\"auths\": {\"registry.cloudogu.com\": {\"auth\": \"null\"}}}'") + assertThat(scriptMock.allActualArgs.size()).isEqualTo(22) } void testStartK3dWithCustomCredentials() { @@ -140,18 +142,20 @@ class K3dTest extends GroovyTestCase { assertThat(scriptMock.allActualArgs[6].trim()).startsWith("k3d kubeconfig merge citest-") assertThat(scriptMock.allActualArgs[7].trim()).startsWith("snap list kubectl") assertThat(scriptMock.allActualArgs[8].trim()).startsWith("sudo snap install kubectl") - assertThat(scriptMock.allActualArgs[9].trim()).startsWith("echo \"Using credentials: myBackendCredentialsID\"") - assertThat(scriptMock.allActualArgs[10].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret k8s-dogu-operator-dogu-registry || true") - assertThat(scriptMock.allActualArgs[11].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret k8s-dogu-operator-docker-registry || true") - assertThat(scriptMock.allActualArgs[12].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create secret generic k8s-dogu-operator-dogu-registry --from-literal=endpoint=\"https://dogu.cloudogu.com/api/v2/dogus\" --from-literal=username=\"null\" --from-literal=password=\"null\"") - assertThat(scriptMock.allActualArgs[13].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create secret docker-registry k8s-dogu-operator-docker-registry --docker-server=\"registry.cloudogu.com\" --docker-username=\"null\" --docker-email=\"a@b.c\" --docker-password=\"null\"") - assertThat(scriptMock.allActualArgs[14].trim()).startsWith("echo \"Using credentials: myHarborCredentials\"") - assertThat(scriptMock.allActualArgs[15].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete configmap component-operator-helm-repository || true") - assertThat(scriptMock.allActualArgs[16].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret component-operator-helm-registry || true") - assertThat(scriptMock.allActualArgs[17].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create configmap component-operator-helm-repository --from-literal=endpoint=\"registry.cloudogu.com\" --from-literal=schema=\"oci\" --from-literal=plainHttp=\"false\"") - assertThat(scriptMock.allActualArgs[18].trim()).startsWith("printf '%s:%s' 'null' 'null' | base64") - assertThat(scriptMock.allActualArgs[19].trim()).startsWith("set +x; sudo KUBECONFIG=path/.k3d/.kube/config kubectl create secret generic component-operator-helm-registry --from-literal=config.json='{\"auths\": {\"registry.cloudogu.com\": {\"auth\": \"null\"}}}'") - assertThat(scriptMock.allActualArgs.size()).isEqualTo(20) + assertThat(scriptMock.allActualArgs[9].trim()).startsWith("snap list helm") + assertThat(scriptMock.allActualArgs[10].trim()).startsWith("sudo snap install helm") + assertThat(scriptMock.allActualArgs[11].trim()).startsWith("echo \"Using credentials: myBackendCredentialsID\"") + assertThat(scriptMock.allActualArgs[12].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret k8s-dogu-operator-dogu-registry || true") + assertThat(scriptMock.allActualArgs[13].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret k8s-dogu-operator-docker-registry || true") + assertThat(scriptMock.allActualArgs[14].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create secret generic k8s-dogu-operator-dogu-registry --from-literal=endpoint=\"https://dogu.cloudogu.com/api/v2/dogus\" --from-literal=username=\"null\" --from-literal=password=\"null\"") + assertThat(scriptMock.allActualArgs[15].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create secret docker-registry k8s-dogu-operator-docker-registry --docker-server=\"registry.cloudogu.com\" --docker-username=\"null\" --docker-email=\"a@b.c\" --docker-password=\"null\"") + assertThat(scriptMock.allActualArgs[16].trim()).startsWith("echo \"Using credentials: myHarborCredentials\"") + assertThat(scriptMock.allActualArgs[17].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete configmap component-operator-helm-repository || true") + assertThat(scriptMock.allActualArgs[18].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl delete secret component-operator-helm-registry || true") + assertThat(scriptMock.allActualArgs[19].trim()).startsWith("sudo KUBECONFIG=${k3dWorkspaceDir}/.k3d/.kube/config kubectl create configmap component-operator-helm-repository --from-literal=endpoint=\"registry.cloudogu.com\" --from-literal=schema=\"oci\" --from-literal=plainHttp=\"false\"") + assertThat(scriptMock.allActualArgs[20].trim()).startsWith("printf '%s:%s' 'null' 'null' | base64") + assertThat(scriptMock.allActualArgs[21].trim()).startsWith("set +x; sudo KUBECONFIG=path/.k3d/.kube/config kubectl create secret generic component-operator-helm-registry --from-literal=config.json='{\"auths\": {\"registry.cloudogu.com\": {\"auth\": \"null\"}}}'") + assertThat(scriptMock.allActualArgs.size()).isEqualTo(22) } void testBuildAndPush() { @@ -186,8 +190,8 @@ class K3dTest extends GroovyTestCase { sut.buildAndPushToLocalRegistry(imageName, imageTag) // then - assertThat(scriptMock.allActualArgs[20].trim()).isEqualTo("image pushed".toString()) - assertThat(scriptMock.allActualArgs.size()).isEqualTo(21) + assertThat(scriptMock.allActualArgs[22].trim()).isEqualTo("image pushed".toString()) + assertThat(scriptMock.allActualArgs.size()).isEqualTo(23) } void testSetup() {