diff --git a/CHANGELOG.md b/CHANGELOG.md index 0286193..1c91cdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] + +## [1.68.0](https://github.com/cloudogu/ces-build-lib/releases/tag/1.68.0) - 2023-11-30 +### 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..4c9158e 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: @@ -1082,6 +1082,9 @@ try { stage('Do something with your cluster') { k3d.kubectl("get nodes") } + stage('Apply your Helm chart') { + k3d.helm("install path/to/your/chart") + } stage('build and push development artefact') { String myCurrentArtefactVersion = "yourTag-1.2.3-dev" diff --git a/pom.xml b/pom.xml index be79365..1d02b1b 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.cloudogu.ces ces-build-lib ces-build-lib - 1.67.0 + 1.68.0 diff --git a/src/com/cloudogu/ces/cesbuildlib/K3d.groovy b/src/com/cloudogu/ces/cesbuildlib/K3d.groovy index 09177e6..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() } } @@ -189,6 +190,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 +377,19 @@ 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.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..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() { @@ -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("install path/to/chart/") + + // then + assertThat(scriptMock.allActualArgs[0].trim()).isEqualTo("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config helm install 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" @@ -74,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() { @@ -110,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() { @@ -156,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() {