Skip to content

Commit

Permalink
Merge branch 'release/v1.59.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
ppxl committed Nov 28, 2022
2 parents d126753 + ee776e9 commit ae98e9c
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 6 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.59.0](https://github.com/cloudogu/ces-build-lib/releases/tag/1.59.0) - 2022-11-28
### Added
- Function `collectAndArchiveLogs` to collect dogu and resource information to help debugging k3s Jenkins buils. #89
- Function `applyDoguResource(String doguName, String doguNamespace, String doguVersion)` to apply a custom dogu
resource into the cluster. This effectively installs a dogu if it is available. #89

## [1.58.0](https://github.com/cloudogu/ces-build-lib/releases/tag/1.58.0) - 2022-11-07
### Changed
- Push k8s yaml content via file reference #87
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,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 lokal k3s cluster in Docker.

Example:

Expand Down Expand Up @@ -1079,7 +1079,16 @@ try {
k3d.waitForDeploymentRollout("my-dogu-name", 300, 5)
}
stage('install a dependent dogu by applying a dogu resource') {
k3d.applyDoguResource("my-dependency", "nyNamespace", "10.0.0-1")
k3d.waitForDeploymentRollout("my-dependency", 300, 5)
}
} catch (Exception ignored) {
// in case of a failed build collect dogus, resources and pod logs and archive them as log file on the build.
k3d.collectAndArchiveLogs()
throw e
} finally {
stage('Remove k3d cluster') {
k3d.deleteK3d()
Expand Down
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<groupId>com.cloudogu.ces</groupId>
<artifactId>ces-build-lib</artifactId>
<name>ces-build-lib</name>
<version>1.58.0</version>
<version>1.59.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand All @@ -18,13 +18,13 @@
<dependency>
<groupId>com.cloudbees</groupId>
<artifactId>groovy-cps</artifactId>
<version>1.21</version>
<version>1.31</version>
</dependency>

<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.11</version>
<version>2.4.21</version>
</dependency>

<dependency>
Expand Down
105 changes: 105 additions & 0 deletions src/com/cloudogu/ces/cesbuildlib/K3d.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class K3d {
* The version of k3d to be installed
*/
private static String K3D_VERSION = "4.4.7"
private static String K3D_LOG_FILENAME = "k8sLogs"

private String clusterName
private script
Expand Down Expand Up @@ -225,6 +226,33 @@ class K3d {
patchDoguDeployment(dogu, image)
}

/**
* Applies the specified dogu resource into the k8s cluster. This should be used for dogus which are not build or
* locally installed in the build process. An example for the usage would be to install a dogu dependency before
* starting integration tests.
*
* @param doguName Name of the dogu, e.g., "nginx-ingress"
* @param doguNamespace Namespace of the dogu, e.g., "official"
* @param doguVersion Version of the dogu, e.g., "13.9.9-1"
*/
void applyDoguResource(String doguName, String doguNamespace, String doguVersion) {
def filename = "target/make/k8s/${doguName}.yaml"
def doguContentYaml = """
apiVersion: k8s.cloudogu.com/v1
kind: Dogu
metadata:
name: ${doguName}
labels:
dogu: ${doguName}
spec:
name: ${doguNamespace}/${doguName}
version: ${doguVersion}
"""

script.writeFile(file: filename.toString(), text: doguContentYaml.toString())
kubectl("apply -f ${filename}")
}

private void applyDevDoguDescriptor(Docker docker, String dogu, String imageUrl, String port) {
String imageDev
String doguJsonDevFile = "${this.workspace}/target/dogu.json"
Expand Down Expand Up @@ -459,5 +487,82 @@ data:
"registryConfigEncrypted": {${config.registryConfigEncrypted}}
}"""
}


/**
* Collects all necessary resources and log information used to identify problems with our kubernetes cluster.
*
* The collected information are archived as zip files at the build.
*/
void collectAndArchiveLogs() {
script.dir(K3D_LOG_FILENAME) {
script.deleteDir()
}
script.sh("rm -rf ${K3D_LOG_FILENAME}.zip".toString())

collectResourcesSummaries()
collectDoguDescriptions()
collectPodLogs()

String fileNameString = "${K3D_LOG_FILENAME}.zip".toString()
script.zip(zipFile: fileNameString, archive: "false", dir: "${K3D_LOG_FILENAME}".toString())
script.archiveArtifacts(artifacts: fileNameString, allowEmptyArchive: "true")
}

/**
* Collects all information about resources and their quantity and saves them as .yaml files.
*/
void collectResourcesSummaries() {
def relevantResources = [
"persistentvolumeclaim",
"statefulset",
"replicaset",
"deployment",
"service",
"secret",
"pod",
]

for (def resource : relevantResources) {
def resourceYaml = kubectl("get ${resource} --show-kind --ignore-not-found -l app=ces -o yaml || true", true)
script.dir("${K3D_LOG_FILENAME}") {
script.writeFile(file: "${resource}.yaml".toString(), text: resourceYaml)
}
}
}

/**
* Collects all descriptions of dogus resources and saves them as .yaml files into the k8s logs directory.
*/
void collectDoguDescriptions() {
def allDoguNames = kubectl("get dogu --ignore-not-found -o name || true", true)
def doguNames = allDoguNames.split("\n")
for (def doguName : doguNames) {
def doguFileName = doguName.split("/")[1]
def doguDescribe = kubectl("describe ${doguName} || true", true)
script.dir("${K3D_LOG_FILENAME}") {
script.dir('dogus') {
script.writeFile(file: "${doguFileName}.txt".toString(), text: doguDescribe)
}
}
}
}

/**
* Collects all pod logs and saves them into the k8s logs directory.
*/
void collectPodLogs() {
def allPodNames = kubectl("get pods -o name || true", true)
def podNames = allPodNames.split("\n")
for (def podName : podNames) {
def podFileName = podName.split("/")[1]
def podLogs = kubectl("logs ${podName} || true", true)
script.dir("${K3D_LOG_FILENAME}") {
script.dir('pods') {
script.writeFile(file: "${podFileName}.log".toString(), text: podLogs)
}
}
}
}
}

100 changes: 99 additions & 1 deletion test/com/cloudogu/ces/cesbuildlib/K3dTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ class K3dTest extends GroovyTestCase {
K3d sut = new K3d(scriptMock, workspaceDir, k3dWorkspaceDir, "path")
String prefixedRegistryName = "k3d-${sut.getRegistryName()}"
String port = "5000"
String imageUrl = "myIP:1234/test/myimage:0.1.2"

scriptMock.expectedShRetValueForScript.put('whoami'.toString(), "itsme")
scriptMock.expectedShRetValueForScript.put('cat /etc/passwd | grep itsme'.toString(), "test:x:900:1001::/home/test:/bin/sh")
Expand Down Expand Up @@ -324,4 +323,103 @@ spec:
assertThat(scriptMock.allActualArgs[20].trim()).startsWith("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl patch deployment 'test' -p '{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"test\",\"image\":\"myIP:1234/test/myimage:0.1.2\"}]}}}}'")
assertThat(scriptMock.allActualArgs.size()).isEqualTo(21)
}


void testK3d_collectAndArchiveLogs() {
// given
def workspaceDir = "leWorkspace"
def k3dWorkspaceDir = "leK3dWorkSpace"
def scriptMock = new ScriptMock()
K3d sut = new K3d(scriptMock, workspaceDir, k3dWorkspaceDir, "path")

def relevantResources = ["persistentvolumeclaim","statefulset","replicaset","deployment","service","secret","pod"]
for(def resource : relevantResources) {
scriptMock.expectedShRetValueForScript.put("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get ${resource} --show-kind --ignore-not-found -l app=ces -o yaml || true".toString(), "value for ${resource}")
}

scriptMock.expectedShRetValueForScript.put("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get dogu --ignore-not-found -o name || true".toString(), "k8s.cloudogu.com/testdogu")
scriptMock.expectedShRetValueForScript.put("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl describe k8s.cloudogu.com/testdogu || true".toString(), "this is the description of a dogu")

scriptMock.expectedShRetValueForScript.put("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get pods -o name || true".toString(), "pod/testpod-1234\npod/testpod2-1234")
scriptMock.expectedShRetValueForScript.put("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl logs pod/testpod-1234 || true".toString(), "this is the log from testpod")
scriptMock.expectedShRetValueForScript.put("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl logs pod/testpod2-1234 || true".toString(), "this is the log from testpod2")

// when
sut.collectAndArchiveLogs()

// then
int i = 0
assertThat(scriptMock.allActualArgs[i++].trim()).contains("called deleteDir()")
assertThat(scriptMock.allActualArgs[i++].trim()).contains("rm -rf k8sLogs.zip")

assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get persistentvolumeclaim --show-kind --ignore-not-found -l app=ces -o yaml || true")
assertThat(scriptMock.writeFileParams[0]).isEqualTo(["file": "persistentvolumeclaim.yaml", "text": "value for persistentvolumeclaim"])

assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get statefulset --show-kind --ignore-not-found -l app=ces -o yaml || true")
assertThat(scriptMock.writeFileParams[1]).isEqualTo(["file": "statefulset.yaml", "text": "value for statefulset"])

assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get replicaset --show-kind --ignore-not-found -l app=ces -o yaml || true")
assertThat(scriptMock.writeFileParams[2]).isEqualTo(["file": "replicaset.yaml", "text": "value for replicaset"])

assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get deployment --show-kind --ignore-not-found -l app=ces -o yaml || true")
assertThat(scriptMock.writeFileParams[3]).isEqualTo(["file": "deployment.yaml", "text": "value for deployment"])

assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get service --show-kind --ignore-not-found -l app=ces -o yaml || true")
assertThat(scriptMock.writeFileParams[4]).isEqualTo(["file": "service.yaml", "text": "value for service"])

assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get secret --show-kind --ignore-not-found -l app=ces -o yaml || true")
assertThat(scriptMock.writeFileParams[5]).isEqualTo(["file": "secret.yaml", "text": "value for secret"])

assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get pod --show-kind --ignore-not-found -l app=ces -o yaml || true")
assertThat(scriptMock.writeFileParams[6]).isEqualTo(["file": "pod.yaml", "text": "value for pod"])

assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get dogu --ignore-not-found -o name || true")
assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl describe k8s.cloudogu.com/testdogu || true")
assertThat(scriptMock.writeFileParams[7]).isEqualTo(["file": "testdogu.txt", "text": "this is the description of a dogu"])

assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl get pods -o name || true")
assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl logs pod/testpod-1234 || true")
assertThat(scriptMock.allActualArgs[i++].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl logs pod/testpod2-1234 || true")
assertThat(scriptMock.writeFileParams[8]).isEqualTo(["file": "testpod-1234.log", "text": "this is the log from testpod"])
assertThat(scriptMock.writeFileParams[9]).isEqualTo(["file": "testpod2-1234.log", "text": "this is the log from testpod2"])

assertThat(scriptMock.zipParams.size()).isEqualTo(1)
assertThat(scriptMock.zipParams[0]).isEqualTo(["archive":"false", "dir":"k8sLogs", "zipFile":"k8sLogs.zip"])
assertThat(scriptMock.archivedArtifacts.size()).isEqualTo(1)
assertThat(scriptMock.archivedArtifacts[0]).isEqualTo(["allowEmptyArchive":"true", "artifacts":"k8sLogs.zip"])

assertThat(scriptMock.allActualArgs.size()).isEqualTo(i)
assertThat(scriptMock.writeFileParams.size()).isEqualTo(10)
}

void testK3d_applyDoguResource() {
// given
def workspaceDir = "leWorkspace"
def k3dWorkspaceDir = "leK3dWorkSpace"
def scriptMock = new ScriptMock()
K3d sut = new K3d(scriptMock, workspaceDir, k3dWorkspaceDir, "path")

def filename = "target/make/k8s/testName.yaml"
def doguContentYaml = """
apiVersion: k8s.cloudogu.com/v1
kind: Dogu
metadata:
name: testName
labels:
dogu: testName
spec:
name: nyNamespace/testName
version: 14.1.1-1
"""

// when
sut.applyDoguResource("testName", "nyNamespace", "14.1.1-1")

// then
assertThat(scriptMock.writeFileParams[0]).isEqualTo(["file": filename, "text": doguContentYaml])
assertThat(scriptMock.writeFileParams.size()).isEqualTo(1)

assertThat(scriptMock.allActualArgs[0].trim()).contains("sudo KUBECONFIG=leK3dWorkSpace/.k3d/.kube/config kubectl apply -f target/make/k8s/testName.yaml")
assertThat(scriptMock.allActualArgs.size()).isEqualTo(1)
}
}
14 changes: 14 additions & 0 deletions test/com/cloudogu/ces/cesbuildlib/ScriptMock.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class ScriptMock {
List<String> actualShMapArgs = new LinkedList<>()

List<Map<String, String>> writeFileParams = new LinkedList<>()
List<Map<String, String>> zipParams = new LinkedList<>()
List<Map<String, String>> archivedArtifacts = new LinkedList<>()
Map actualFileArgs
Map actualStringArgs
Map files = new HashMap<String, String>()
Expand Down Expand Up @@ -60,6 +62,10 @@ class ScriptMock {
actualJUnitFlags = map
}

void deleteDir() {
allActualArgs.add("called deleteDir()")
}

String sh(Map<String, Object> args) {
actualShMapArgs.add(args.script.toString())
allActualArgs.add(args.script.toString())
Expand Down Expand Up @@ -150,6 +156,14 @@ class ScriptMock {
writeFileParams.add(params)
}

void zip(Map<String, String> params) {
zipParams.add(params)
}

void archiveArtifacts(Map<String, String> params) {
archivedArtifacts.add(params)
}

String readFile(String file) {
return files.get(file)
}
Expand Down

0 comments on commit ae98e9c

Please sign in to comment.