diff --git a/README.md b/README.md index c05a9a8..bac16a0 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,8 @@ working example bundled with the complete infrastructure for a gitops deep dive. - [Real life examples](#real-life-examples) - [Usage](#usage) - [Default Folder Structure](#default-folder-structure) - - [FluxV1](#fluxv1) - - [Plain-k8s](#plain-k8s) - - [Helm](#helm) - - [FluxV2](#fluxv2) - - [ArgoCD](#argocd) + - [Plain-k8s](#plain-k8s) + - [Helm](#helm) - [GitOps-Config](#gitops-config) - [SCM-Provider](#scm-provider) - [Stages](#stages) @@ -89,6 +86,7 @@ def gitopsConfig = [ repositoryUrl: 'fluxv1/gitops' ], application: 'spring-petclinic', + gitopsTool: 'FLUX', stages: [ staging: [ deployDirectly: true @@ -120,6 +118,7 @@ def gitopsConfig = [ cesBuildLibVersion: /* Default: a recent cesBuildLibVersion see deployViaGitops.groovy */ , cesBuildLibCredentialsId: /* Default: '', empty due to default public github repo */, application: 'spring-petclinic', + gitopsTool: 'FLUX' mainBranch: 'master' /* Default: 'main' */, deployments: [ sourcePath: 'k8s' /* Default: 'k8s' */, @@ -157,9 +156,14 @@ deployViaGitops(gitopsConfig) ### Real life examples **FluxV1:** -* [using petclinic with helm](https://github.com/cloudogu/k8s-gitops-playground/blob/main/applications/petclinic/fluxv1/helm/Jenkinsfile) -* [using petclinic with plain-k8s](https://github.com/cloudogu/k8s-gitops-playground/blob/main/applications/petclinic/fluxv1/plain-k8s/Jenkinsfile) -* [using helm with nginx and extra resources](https://github.com/cloudogu/k8s-gitops-playground/blob/main/applications/nginx/fluxv1/Jenkinsfile) +* [using petclinic with helm and extra k8s-resources and extra files](https://github.com/cloudogu/k8s-gitops-playground/blob/main/applications/petclinic/fluxv1/helm/Jenkinsfile) +* [using petclinic with plain-k8s and extra files](https://github.com/cloudogu/k8s-gitops-playground/blob/main/applications/petclinic/fluxv1/plain-k8s/Jenkinsfile) +* [using nginx with helm and extra files](https://github.com/cloudogu/k8s-gitops-playground/blob/main/applications/nginx/fluxv1/Jenkinsfile) + +**ArgoCD:** +* [using petclinic with helm and extra k8s-resources and extra files](https://github.com/cloudogu/k8s-gitops-playground/blob/main/applications/petclinic/argocd/helm/Jenkinsfile) +* [using petclinic with plain-k8s](https://github.com/cloudogu/k8s-gitops-playground/blob/main/applications/petclinic/argocd/plain-k8s/Jenkinsfile) + --- @@ -201,9 +205,7 @@ A default project structure in your application repo could look like the example and/or helm resources bundled in a folder. This specific resources folder (here `k8s`) will later be specified by the `sourcePath` within the deployments section of your `gitopsConfig`. -### FluxV1 - -#### Plain-k8s +### Plain-k8s ``` ├── application ├── config.yamllint.yaml // not necessarily needed @@ -217,7 +219,7 @@ and/or helm resources bundled in a folder. This specific resources folder (here └── service.yaml ``` -#### Helm +### Helm ``` ├── application ├── config.yamllint.yaml // not necessarily needed @@ -238,14 +240,6 @@ and/or helm resources bundled in a folder. This specific resources folder (here └── values-staging.yaml ``` -### FluxV2 - -Upcoming - -### ArgoCD - -Upcoming - --- ## GitOps-Config @@ -254,12 +248,16 @@ You can find a complete yet simple example [here](#examples). **Properties** -First of all there are some mandatory properties e.g. the information about your gitops repository and the application repository. +First of all there are some mandatory properties e.g. the information about your gitops repository, the application repository and the gitops tool to be used. ``` application: 'spring-petclinic' // Name of the application. Used as a folder in GitOps repo ``` +``` +gitopsTool: 'ARGO' // Name of the gitops tool. Currently supporting 'FLUX' (for now only fluxV1) and 'ARGO' (for now supporting only helm charts from git repos) +``` + and some optional parameters (below are the defaults) for the configuration of the dependency to the ces-build-lib or the default name for the git branch: ``` cesBuildLibRepo: 'https://github.com/cloudogu/ces-build-lib', diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/Deployment.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/Deployment.groovy index 9a41e94..8d9190d 100644 --- a/src/com/cloudogu/gitopsbuildlib/deployment/Deployment.groovy +++ b/src/com/cloudogu/gitopsbuildlib/deployment/Deployment.groovy @@ -17,13 +17,13 @@ abstract class Deployment { def create(String stage) { createFoldersAndCopyK8sResources(stage) createFileConfigmaps(stage) - createPreValidation(stage) + preValidation(stage) validate(stage) - createPostValidation(stage) + postValidation(stage) } - abstract createPreValidation(String stage) - abstract createPostValidation(String stage) + abstract preValidation(String stage) + abstract postValidation(String stage) def validate(String stage) { gitopsConfig.validators.each { validatorConfig -> @@ -36,11 +36,11 @@ abstract class Deployment { def sourcePath = gitopsConfig.deployments.sourcePath def application = gitopsConfig.application - script.sh "mkdir -p ${stage}/${application}/${sourcePath}/" + script.sh "mkdir -p ${stage}/${application}/extraResources/" script.sh "mkdir -p ${configDir}/" // copy extra resources like sealed secrets - script.echo "Copying k8s payload from application repo to gitOps Repo: '${sourcePath}/${stage}/*' to '${stage}/${application}/${sourcePath}'" - script.sh "cp -r ${script.env.WORKSPACE}/${sourcePath}/${stage}/* ${stage}/${application}/${sourcePath}/ || true" + script.echo "Copying k8s payload from application repo to gitOps Repo: '${sourcePath}/${stage}/*' to '${stage}/${application}/extraResources/'" + script.sh "cp -r ${script.env.WORKSPACE}/${sourcePath}/${stage}/* ${stage}/${application}/extraResources/ || true" script.sh "cp ${script.env.WORKSPACE}/*.yamllint.yaml ${configDir}/ || true" } diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/Helm.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/helm/Helm.groovy similarity index 51% rename from src/com/cloudogu/gitopsbuildlib/deployment/Helm.groovy rename to src/com/cloudogu/gitopsbuildlib/deployment/helm/Helm.groovy index a561710..fb52fc7 100644 --- a/src/com/cloudogu/gitopsbuildlib/deployment/Helm.groovy +++ b/src/com/cloudogu/gitopsbuildlib/deployment/helm/Helm.groovy @@ -1,24 +1,34 @@ -package com.cloudogu.gitopsbuildlib.deployment +package com.cloudogu.gitopsbuildlib.deployment.helm -import com.cloudogu.gitopsbuildlib.deployment.repotype.GitRepo -import com.cloudogu.gitopsbuildlib.deployment.repotype.HelmRepo -import com.cloudogu.gitopsbuildlib.deployment.repotype.RepoType +import com.cloudogu.gitopsbuildlib.deployment.Deployment +import com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease.ArgoCDRelease +import com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease.FluxV1Release +import com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease.HelmRelease +import com.cloudogu.gitopsbuildlib.deployment.helm.repotype.GitRepo +import com.cloudogu.gitopsbuildlib.deployment.helm.repotype.HelmRepo +import com.cloudogu.gitopsbuildlib.deployment.helm.repotype.RepoType class Helm extends Deployment { - protected RepoType helm + protected RepoType chartRepo + protected HelmRelease helmRelease Helm(def script, def gitopsConfig) { super(script, gitopsConfig) if (gitopsConfig.deployments.helm.repoType == 'GIT') { - helm = new GitRepo(script) + chartRepo = new GitRepo(script) } else if (gitopsConfig.deployments.helm.repoType == 'HELM') { - helm = new HelmRepo(script) + chartRepo = new HelmRepo(script) + } + if(gitopsConfig.gitopsTool == 'FLUX') { + helmRelease = new FluxV1Release(script) + } else if(gitopsConfig.gitopsTool == 'ARGO') { + helmRelease = new ArgoCDRelease(script) } } @Override - def createPreValidation(String stage) { + def preValidation(String stage) { def helmConfig = gitopsConfig.deployments.helm def application = gitopsConfig.application def sourcePath = gitopsConfig.deployments.sourcePath @@ -26,16 +36,24 @@ class Helm extends Deployment { // writing the merged-values.yaml via writeYaml into a file has the advantage, that it gets formatted as valid yaml // This makes it easier to read in and indent for the inline use in the helmRelease. // It enables us to reuse the `fileToInlineYaml` function, without writing a complex formatting logic. - script.writeFile file: "${stage}/${application}/mergedValues.yaml", text: helm.mergeValues(helmConfig, ["${script.env.WORKSPACE}/${sourcePath}/values-${stage}.yaml", "${script.env.WORKSPACE}/${sourcePath}/values-shared.yaml"] as String[]) + script.writeFile file: "${stage}/${application}/mergedValues.yaml", text: chartRepo.mergeValues(helmConfig, ["${script.env.WORKSPACE}/${sourcePath}/values-${stage}.yaml", "${script.env.WORKSPACE}/${sourcePath}/values-shared.yaml"] as String[]) updateYamlValue("${stage}/${application}/mergedValues.yaml", helmConfig) - script.writeFile file: "${stage}/${application}/helmRelease.yaml", text: helm.createHelmRelease(helmConfig, application, getNamespace(stage), "${stage}/${application}/mergedValues.yaml") + + script.writeFile file: "${stage}/${application}/applicationRelease.yaml", text: helmRelease.create(helmConfig, application, getNamespace(stage), "${stage}/${application}/mergedValues.yaml") + // since the values are already inline (helmRelease.yaml) we do not need to commit them into the gitops repo script.sh "rm ${stage}/${application}/mergedValues.yaml" } @Override - def createPostValidation(String stage) { + def postValidation(String stage) { + def helmConfig = gitopsConfig.deployments.helm + + // clean the gitrepo helm chart folder since the helmRelease.yaml ist now created + if (helmConfig.repoType == 'GIT') { + script.sh "rm -rf chart || true" + } } private void updateYamlValue(String yamlFilePath, Map helmConfig) { @@ -53,28 +71,4 @@ class Helm extends Deployment { } script.writeYaml file: yamlFilePath, data: data, overwrite: true } - - - //TODO helmValuesFromFile not yet implemented -// private String createFromFileValues(String stage, Map gitopsConfig) { -// String values = "" -// -// gitopsConfig.helmValuesFromFile.each { -// if (stage in it['stage']) { -// values = fileToInlineYaml(it['key'], "${script.env.WORKSPACE}/k8s/${it['file']}") -// } -// } -// return values -// } -// -// private String fileToInlineYaml(String key, String filePath) { -// String values = "" -// String indent = " " -// -// def fileContent = readFile filePath -// values += "\n ${key}: |\n${indent}" -// values += fileContent.split("\\n").join("\n" + indent) -// -// return values -// } } diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/ArgoCDRelease.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/ArgoCDRelease.groovy new file mode 100644 index 0000000..9ee96bb --- /dev/null +++ b/src/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/ArgoCDRelease.groovy @@ -0,0 +1,42 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease + +import com.cloudogu.gitopsbuildlib.docker.DockerWrapper + +class ArgoCDRelease extends HelmRelease{ + + protected DockerWrapper dockerWrapper + + ArgoCDRelease(def script) { + super(script) + dockerWrapper = new DockerWrapper(script) + } + + @Override + String create(Map helmConfig, String application, String namespace, String mergedValuesFile) { + + String helmRelease = "" + if (helmConfig.repoType == 'GIT') { + helmRelease = createResourcesFromGitRepo(helmConfig, application, mergedValuesFile) + } else if (helmConfig.repoType == 'HELM') { + // TODO not yet implemented + } + return helmRelease + } + + private String createResourcesFromGitRepo(Map helmConfig, String application, String mergedValuesFile) { + String helmRelease = "" + + def chartPath = '' + if (helmConfig.containsKey('chartPath')) { + chartPath = helmConfig.chartPath + } + + dockerWrapper.withHelm { + String templateScript = "helm template ${application} chart/${chartPath} -f ${mergedValuesFile}" + helmRelease = script.sh returnStdout: true, script: templateScript + } + // this line removes all empty lines since helm template creates some and the helm kubeval validator will throw an error if there are emtpy lines present + helmRelease = helmRelease.replaceAll("(?m)^[ \t]*\r?\n", "") + return helmRelease + } +} diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/FluxV1Release.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/FluxV1Release.groovy new file mode 100644 index 0000000..0d3bf7c --- /dev/null +++ b/src/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/FluxV1Release.groovy @@ -0,0 +1,55 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease + +class FluxV1Release extends HelmRelease{ + + FluxV1Release(def script) { + super(script) + } + + @Override + String create(Map helmConfig, String application, String namespace, String mergedValuesFile) { + def values = fileToInlineYaml(mergedValuesFile) + def chart = getChart(helmConfig) + return """apiVersion: helm.fluxcd.io/v1 +kind: HelmRelease +metadata: + name: ${application} + namespace: ${namespace} + annotations: + fluxcd.io/automated: "false" +spec: + releaseName: ${application} + chart:${chart} + values: +${values} +""" + } + + private String gitRepoChart(Map helmConfig) { + + def chartPath = "." + if (helmConfig.containsKey('chartPath') && helmConfig.chartPath) { + chartPath = helmConfig.chartPath + } + + return """ + git: ${helmConfig.repoUrl} + ref: ${helmConfig.version} + path: ${chartPath}""" + } + + private String helmRepoChart(Map helmConfig) { + return """ + repository: ${helmConfig.repoUrl} + name: ${helmConfig.chartName} + version: ${helmConfig.version}""" + } + + private String getChart(Map helmConfig) { + if (helmConfig.repoType == 'GIT') { + return gitRepoChart(helmConfig) + } else if (helmConfig.repoType == 'HELM') { + return helmRepoChart(helmConfig) + } + } +} diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/HelmRelease.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/HelmRelease.groovy new file mode 100644 index 0000000..7342fff --- /dev/null +++ b/src/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/HelmRelease.groovy @@ -0,0 +1,27 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease + +abstract class HelmRelease { + + protected script + + HelmRelease(def script) { + this.script = script + } + + abstract String create(Map helmConfig, String application, String namespace, String mergedValuesFile) + + String fileToInlineYaml(String fileContents) { + String values = "" + String indent = " " + String fileContent = script.readFile fileContents + fileContent.split("\n").each { line -> + if(line.size() > 0) { + values += indent + line + "\n" + } else { + values += line + "\n" + } + } + // remove unnecessary last blank line + return values.substring(0, values.lastIndexOf('\n')) + } +} diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/GitRepo.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/GitRepo.groovy new file mode 100644 index 0000000..acdb073 --- /dev/null +++ b/src/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/GitRepo.groovy @@ -0,0 +1,48 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.repotype + +class GitRepo extends RepoType { + + GitRepo(def script) { + super(script) + } + + @Override + String mergeValues(Map helmConfig, String[] valuesFiles) { + String merge = "" + + getHelmChartFromGitRepo(helmConfig) + + def chartPath = '' + if (helmConfig.containsKey('chartPath')) { + chartPath = helmConfig.chartPath + } + + withHelm { + script.sh "helm dep update chart/${chartPath}" + String helmScript = "helm values chart/${chartPath} ${valuesFilesWithParameter(valuesFiles)}" + merge = script.sh returnStdout: true, script: helmScript + } + + return merge + } + + private getHelmChartFromGitRepo(Map helmConfig) { + def git + + script.dir("chart") { + + if (helmConfig.containsKey('credentialsId')) { + git = script.cesBuildLib.Git.new(script, helmConfig.credentialsId) + } else { + git = script.cesBuildLib.Git.new(script) + } + + git url: helmConfig.repoUrl, branch: 'main', changelog: false, poll: false + + if(helmConfig.containsKey('version') && helmConfig.version) { + git.fetch() + git.checkout(helmConfig.version) + } + } + } +} diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/HelmRepo.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/HelmRepo.groovy new file mode 100644 index 0000000..e8a9639 --- /dev/null +++ b/src/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/HelmRepo.groovy @@ -0,0 +1,25 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.repotype + +class HelmRepo extends RepoType{ + + HelmRepo(def script) { + super(script) + } + + @Override + String mergeValues(Map helmConfig, String[] valuesFiles) { + String merge = "" + + withHelm { + script.sh "helm repo add chartRepo ${helmConfig.repoUrl}" + script.sh "helm repo update" + script.sh "helm pull chartRepo/${helmConfig.chartName} --version=${helmConfig.version} --untar --untardir=${script.env.WORKSPACE}/chart" + String helmScript = "helm values ${script.env.WORKSPACE}/chart/${helmConfig.chartName} ${valuesFilesWithParameter(valuesFiles)}" + merge = script.sh returnStdout: true, script: helmScript + } + + script.sh "rm -rf ${script.env.WORKSPACE}/chart || true" + + return merge + } +} diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/RepoType.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/RepoType.groovy new file mode 100644 index 0000000..00f65c6 --- /dev/null +++ b/src/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/RepoType.groovy @@ -0,0 +1,30 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.repotype + +import com.cloudogu.gitopsbuildlib.docker.DockerWrapper + +abstract class RepoType { + + protected script + protected DockerWrapper dockerWrapper + + RepoType(def script) { + this.script = script + dockerWrapper = new DockerWrapper(script) + } + + abstract mergeValues(Map helmConfig, String[] files) + + void withHelm(Closure body) { + dockerWrapper.withHelm { + body() + } + } + + protected String valuesFilesWithParameter(String[] valuesFiles) { + String valuesFilesWithParameter = "" + valuesFiles.each { + valuesFilesWithParameter += "-f $it " + } + return valuesFilesWithParameter + } +} diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/Plain.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/plain/Plain.groovy similarity index 81% rename from src/com/cloudogu/gitopsbuildlib/deployment/Plain.groovy rename to src/com/cloudogu/gitopsbuildlib/deployment/plain/Plain.groovy index 2b87019..11729fd 100644 --- a/src/com/cloudogu/gitopsbuildlib/deployment/Plain.groovy +++ b/src/com/cloudogu/gitopsbuildlib/deployment/plain/Plain.groovy @@ -1,4 +1,6 @@ -package com.cloudogu.gitopsbuildlib.deployment +package com.cloudogu.gitopsbuildlib.deployment.plain + +import com.cloudogu.gitopsbuildlib.deployment.Deployment class Plain extends Deployment{ @@ -7,11 +9,11 @@ class Plain extends Deployment{ } @Override - def createPreValidation(String stage) { + def preValidation(String stage) { } @Override - def createPostValidation(String stage) { + def postValidation(String stage) { updateImage(stage) } diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/repotype/GitRepo.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/repotype/GitRepo.groovy deleted file mode 100644 index 2f390f4..0000000 --- a/src/com/cloudogu/gitopsbuildlib/deployment/repotype/GitRepo.groovy +++ /dev/null @@ -1,60 +0,0 @@ -package com.cloudogu.gitopsbuildlib.deployment.repotype - -class GitRepo extends RepoType { - - GitRepo(def script) { - super(script) - } - - @Override - String mergeValues(Map helmConfig, String[] files) { - String merge = "" - String _files = "" - files.each { - _files += "-f $it " - } - - script.dir("${script.env.WORKSPACE}/chart") { - if (helmConfig.containsKey('credentialsId')) { - script.git credentialsId: helmConfig.credentialsId, url: helmConfig.repoUrl, branch: 'main', changelog: false, poll: false - } else { - script.git url: helmConfig.repoUrl, branch: 'main', changelog: false, poll: false - } - } - - def chartPath = '' - if (helmConfig.containsKey('chartPath')) { - chartPath = helmConfig.chartPath - } - - withHelm { - String helmScript = "helm values ${script.env.WORKSPACE}/chart/${chartPath} ${_files}" - merge = script.sh returnStdout: true, script: helmScript - } - - script.sh "rm -rf ${script.env.WORKSPACE}/chart || true" - - return merge - } - - @Override - String createHelmRelease(Map helmConfig, String application, String namespace, String valuesFile) { - def values = fileToInlineYaml(valuesFile) - return """apiVersion: helm.fluxcd.io/v1 -kind: HelmRelease -metadata: - name: ${application} - namespace: ${namespace} - annotations: - fluxcd.io/automated: "false" -spec: - releaseName: ${application} - chart: - git: ${helmConfig.repoUrl} - ref: ${helmConfig.version} - path: . - values: -${values} -""" - } -} diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/repotype/HelmRepo.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/repotype/HelmRepo.groovy deleted file mode 100644 index ca5337d..0000000 --- a/src/com/cloudogu/gitopsbuildlib/deployment/repotype/HelmRepo.groovy +++ /dev/null @@ -1,51 +0,0 @@ -package com.cloudogu.gitopsbuildlib.deployment.repotype - -class HelmRepo extends RepoType{ - - HelmRepo(def script) { - super(script) - } - - @Override - String mergeValues(Map helmConfig, String[] files) { - String merge = "" - String _files = "" - files.each { - _files += "-f $it " - } - - withHelm { - script.sh "helm repo add chartRepo ${helmConfig.repoUrl}" - script.sh "helm repo update" - script.sh "helm pull chartRepo/${helmConfig.chartName} --version=${helmConfig.version} --untar --untardir=${script.env.WORKSPACE}/chart" - String helmScript = "helm values ${script.env.WORKSPACE}/chart/${helmConfig.chartName} ${_files}" - merge = script.sh returnStdout: true, script: helmScript - } - - script.sh "rm -rf ${script.env.WORKSPACE}/chart || true" - - return merge - } - - - @Override - String createHelmRelease(Map helmConfig, String application, String namespace, String valuesFile) { - def values = fileToInlineYaml(valuesFile) - return """apiVersion: helm.fluxcd.io/v1 -kind: HelmRelease -metadata: - name: ${application} - namespace: ${namespace} - annotations: - fluxcd.io/automated: "false" -spec: - releaseName: ${application} - chart: - repository: ${helmConfig.repoUrl} - name: ${helmConfig.chartName} - version: ${helmConfig.version} - values: -${values} -""" - } -} diff --git a/src/com/cloudogu/gitopsbuildlib/deployment/repotype/RepoType.groovy b/src/com/cloudogu/gitopsbuildlib/deployment/repotype/RepoType.groovy deleted file mode 100644 index 2cb19e6..0000000 --- a/src/com/cloudogu/gitopsbuildlib/deployment/repotype/RepoType.groovy +++ /dev/null @@ -1,38 +0,0 @@ -package com.cloudogu.gitopsbuildlib.deployment.repotype - -abstract class RepoType { - - protected static String getHelmImage() { 'ghcr.io/cloudogu/helm:3.4.1-1' } - - protected script - - RepoType(def script) { - this.script = script - } - - abstract createHelmRelease(Map helmConfig, String application, String namespace, String valuesFile) - abstract mergeValues(Map helmConfig, String[] files) - - String fileToInlineYaml(String fileContents) { - String values = "" - String indent = " " - String fileContent = script.readFile fileContents - fileContent.split("\n").each { line -> - if(line.size() > 0) { - values += indent + line + "\n" - } else { - values += line + "\n" - } - } - // remove unnecessary last blank line - return values.substring(0, values.lastIndexOf('\n')) - } - - void withHelm(Closure body) { - script.cesBuildLib.Docker.new(script).image(helmImage).inside( - "${script.pwd().equals(script.env.WORKSPACE) ? '' : "-v ${script.env.WORKSPACE}:${script.env.WORKSPACE}"}" - ) { - body() - } - } -} diff --git a/src/com/cloudogu/gitopsbuildlib/docker/DockerWrapper.groovy b/src/com/cloudogu/gitopsbuildlib/docker/DockerWrapper.groovy new file mode 100644 index 0000000..bea2205 --- /dev/null +++ b/src/com/cloudogu/gitopsbuildlib/docker/DockerWrapper.groovy @@ -0,0 +1,31 @@ +package com.cloudogu.gitopsbuildlib.docker + +class DockerWrapper { + + protected static String getHelmImage() { 'ghcr.io/cloudogu/helm:3.5.4-1' } + + protected def script + + DockerWrapper(def script) { + this.script = script + } + + void withDockerImage(String image, Closure body) { + script.docker.image(image).inside( + // Allow accessing WORKSPACE even when we are in a child dir (using "dir() {}") + "${script.pwd().equals(script.env.WORKSPACE) ? '' : "-v ${script.env.WORKSPACE}:${script.env.WORKSPACE} "}" + + // Avoid: "ERROR: The container started but didn't run the expected command" + '--entrypoint=""' + ) { + body() + } + } + + void withHelm(Closure body) { + script.cesBuildLib.Docker.new(script).image(helmImage).inside( + "${script.pwd().equals(script.env.WORKSPACE) ? '' : "-v ${script.env.WORKSPACE}:${script.env.WORKSPACE}"}" + ) { + body() + } + } +} diff --git a/src/com/cloudogu/gitopsbuildlib/validation/HelmKubeval.groovy b/src/com/cloudogu/gitopsbuildlib/validation/HelmKubeval.groovy index d8136fb..766ed53 100644 --- a/src/com/cloudogu/gitopsbuildlib/validation/HelmKubeval.groovy +++ b/src/com/cloudogu/gitopsbuildlib/validation/HelmKubeval.groovy @@ -1,5 +1,7 @@ package com.cloudogu.gitopsbuildlib.validation +import com.cloudogu.gitopsbuildlib.docker.DockerWrapper + class HelmKubeval extends Validator { HelmKubeval(def script) { @@ -10,13 +12,16 @@ class HelmKubeval extends Validator { void validate(String targetDirectory, Map config, Map deployments) { if (deployments.containsKey('helm')) { if (deployments.helm.repoType == 'GIT') { - script.dir("${targetDirectory}/chart") { - def git = (deployments.helm.containsKey('credentialsId')) - ? script.cesBuildLib.Git.new(script, deployments.helm.credentialsId) - : script.cesBuildLib.Git.new(script) - git url: deployments.helm.repoUrl, branch: 'main', changelog: false, poll: false - git.checkout(deployments.helm.version) - } +// script.dir("${targetDirectory}/chart") { +// def git = (deployments.helm.containsKey('credentialsId')) +// ? script.cesBuildLib.Git.new(script, deployments.helm.credentialsId) +// : script.cesBuildLib.Git.new(script) +// git url: deployments.helm.repoUrl, branch: 'main', changelog: false, poll: false +// +// if(deployments.helm.containsKey('version') && deployments.helm.version) { +// git.checkout(deployments.helm.version) +// } +// } def chartPath = '' if (deployments.helm.containsKey('chartPath')) { @@ -24,10 +29,9 @@ class HelmKubeval extends Validator { } withDockerImage(config.image) { - script.sh "helm kubeval ${targetDirectory}/chart/${chartPath} -v ${config.k8sSchemaVersion}" + script.sh "helm kubeval chart/${chartPath} -v ${config.k8sSchemaVersion}" } - script.sh "rm -rf ${targetDirectory}/chart" } else if (deployments.helm.repoType == 'HELM') { withDockerImage(config.image) { script.sh "helm repo add chartRepo ${deployments.helm.repoUrl}" diff --git a/src/com/cloudogu/gitopsbuildlib/validation/Kubeval.groovy b/src/com/cloudogu/gitopsbuildlib/validation/Kubeval.groovy index 863c1a8..f8c86aa 100644 --- a/src/com/cloudogu/gitopsbuildlib/validation/Kubeval.groovy +++ b/src/com/cloudogu/gitopsbuildlib/validation/Kubeval.groovy @@ -1,10 +1,13 @@ package com.cloudogu.gitopsbuildlib.validation +import com.cloudogu.gitopsbuildlib.docker.DockerWrapper + /** * Validates all yaml-resources within the target-directory against the specs of the given k8s version */ class Kubeval extends Validator { + Kubeval(def script) { super(script) } @@ -12,7 +15,7 @@ class Kubeval extends Validator { @Override void validate(String targetDirectory, Map config, Map deployments) { withDockerImage(config.image) { - script.sh "kubeval -d ${targetDirectory}/${deployments.sourcePath} -v ${config.k8sSchemaVersion} --strict" + script.sh "kubeval -d ${targetDirectory} -v ${config.k8sSchemaVersion} --strict" } } } diff --git a/src/com/cloudogu/gitopsbuildlib/validation/Validator.groovy b/src/com/cloudogu/gitopsbuildlib/validation/Validator.groovy index a89bef0..294a610 100644 --- a/src/com/cloudogu/gitopsbuildlib/validation/Validator.groovy +++ b/src/com/cloudogu/gitopsbuildlib/validation/Validator.groovy @@ -1,11 +1,15 @@ package com.cloudogu.gitopsbuildlib.validation +import com.cloudogu.gitopsbuildlib.docker.DockerWrapper + abstract class Validator { protected script + protected DockerWrapper dockerWrapper Validator(def script) { this.script = script + dockerWrapper = new DockerWrapper(script) } void validate(boolean enabled, String targetDirectory, Map config, Map deployments) { @@ -19,13 +23,6 @@ abstract class Validator { abstract protected void validate(String targetDirectory, Map config, Map deployments) protected void withDockerImage(String image, Closure body) { - script.docker.image(image).inside( - // Allow accessing WORKSPACE even when we are in a child dir (using "dir() {}") - "${script.pwd().equals(script.env.WORKSPACE) ? '' : "-v ${script.env.WORKSPACE}:${script.env.WORKSPACE} "}" + - // Avoid: "ERROR: The container started but didn't run the expected command" - '--entrypoint=""' - ) { - body() - } + dockerWrapper.withDockerImage(image, body) } } diff --git a/src/com/cloudogu/gitopsbuildlib/validation/Yamllint.groovy b/src/com/cloudogu/gitopsbuildlib/validation/Yamllint.groovy index 43d22f0..8427c77 100644 --- a/src/com/cloudogu/gitopsbuildlib/validation/Yamllint.groovy +++ b/src/com/cloudogu/gitopsbuildlib/validation/Yamllint.groovy @@ -1,5 +1,7 @@ package com.cloudogu.gitopsbuildlib.validation +import com.cloudogu.gitopsbuildlib.docker.DockerWrapper + /** * Checks for correct YAML syntax using yamllint * diff --git a/test/com/cloudogu/gitopsbuildlib/DeployViaGitopsTest.groovy b/test/com/cloudogu/gitopsbuildlib/DeployViaGitopsTest.groovy index 5df75b2..2378eec 100644 --- a/test/com/cloudogu/gitopsbuildlib/DeployViaGitopsTest.groovy +++ b/test/com/cloudogu/gitopsbuildlib/DeployViaGitopsTest.groovy @@ -1,6 +1,6 @@ package com.cloudogu.gitopsbuildlib -import com.cloudogu.ces.cesbuildlib.DockerMock + import com.cloudogu.ces.cesbuildlib.Git import com.cloudogu.gitopsbuildlib.validation.Kubeval import com.cloudogu.gitopsbuildlib.validation.Yamllint @@ -16,7 +16,8 @@ import static org.mockito.ArgumentMatchers.anyString import static org.mockito.ArgumentMatchers.eq import static org.mockito.Mockito.* -@TestInstance(TestInstance.Lifecycle.PER_CLASS) +// Lifecycle.PER_METHOD is slower than PER_CLASS but we had issues regarding shared state (by executing all tests there was different behaviour than executing single tests in isolation). +@TestInstance(TestInstance.Lifecycle.PER_METHOD) class DeployViaGitopsTest extends BasePipelineTest { class CesBuildLibMock { @@ -99,14 +100,12 @@ class DeployViaGitopsTest extends BasePipelineTest { qa : [deployDirectly: false] ] - @BeforeAll - void setUp() throws Exception { - scriptRoots += 'vars' - super.setUp() - } - @BeforeEach void init() { + super.setUp() + + scriptRoots += 'vars' + deployViaGitops = loadScript('vars/deployViaGitops.groovy') binding.getVariable('currentBuild').result = 'SUCCESS' setupGlobals(deployViaGitops) diff --git a/test/com/cloudogu/gitopsbuildlib/DockerMock.groovy b/test/com/cloudogu/gitopsbuildlib/DockerMock.groovy index 474ce92..72308f6 100644 --- a/test/com/cloudogu/gitopsbuildlib/DockerMock.groovy +++ b/test/com/cloudogu/gitopsbuildlib/DockerMock.groovy @@ -1,5 +1,6 @@ -package com.cloudogu.ces.cesbuildlib +package com.cloudogu.gitopsbuildlib +import com.cloudogu.ces.cesbuildlib.Docker import org.mockito.invocation.InvocationOnMock import org.mockito.stubbing.Answer diff --git a/test/com/cloudogu/gitopsbuildlib/GitMock.groovy b/test/com/cloudogu/gitopsbuildlib/GitMock.groovy index a7b040b..3362b48 100644 --- a/test/com/cloudogu/gitopsbuildlib/GitMock.groovy +++ b/test/com/cloudogu/gitopsbuildlib/GitMock.groovy @@ -1,9 +1,10 @@ -package com.cloudogu.ces.cesbuildlib +package com.cloudogu.gitopsbuildlib + +import com.cloudogu.ces.cesbuildlib.Git import static org.mockito.Mockito.mock class GitMock { - List actualArgs = new LinkedList<>() Git createMock() { Git gitMock = mock(Git.class) diff --git a/test/com/cloudogu/gitopsbuildlib/ScriptMock.groovy b/test/com/cloudogu/gitopsbuildlib/ScriptMock.groovy index e109221..b27d653 100644 --- a/test/com/cloudogu/gitopsbuildlib/ScriptMock.groovy +++ b/test/com/cloudogu/gitopsbuildlib/ScriptMock.groovy @@ -1,7 +1,5 @@ package com.cloudogu.gitopsbuildlib -import com.cloudogu.ces.cesbuildlib.DockerMock -import com.cloudogu.ces.cesbuildlib.GitMock import groovy.yaml.YamlSlurper class ScriptMock { @@ -13,7 +11,7 @@ class ScriptMock { List actualEchoArgs = new LinkedList<>() List actualReadYamlArgs = new LinkedList<>() List actualGitArgs = new LinkedList<>() - String actualDir + List actualDir = new LinkedList<>() def configYaml = '''\ --- #this part is only for PlainTest regarding updating the image name @@ -39,8 +37,9 @@ to: new: { args -> return dockerMock.createMock() } ], Git: [ - new: { args, creds -> return gitMock.createMock() }, - checkout: { 'checkout' } + new: { args -> return gitMock.createMock() }, + fetch: { gitMock.setFetch() }, + checkout: { args -> gitMock.actualCheckoutArgs(args) } ], ], docker: dockerMock.createMock(), @@ -55,6 +54,6 @@ to: env : [ WORKSPACE: 'workspace' ], - dir: { dir, closure -> actualDir = dir; return closure.call() } + dir: { dir, closure -> println(dir); actualDir += dir.toString(); return closure.call() } ] } diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/DeploymentTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/DeploymentTest.groovy index 5708ac9..f28b974 100644 --- a/test/com/cloudogu/gitopsbuildlib/deployment/DeploymentTest.groovy +++ b/test/com/cloudogu/gitopsbuildlib/deployment/DeploymentTest.groovy @@ -56,10 +56,10 @@ class DeploymentTest { @Test void 'creating folders for plain deployment'() { deploymentUnderTest.createFoldersAndCopyK8sResources('staging',) - assertThat(scriptMock.actualEchoArgs[0]).isEqualTo('Copying k8s payload from application repo to gitOps Repo: \'k8s/staging/*\' to \'staging/app/k8s\'') - assertThat(scriptMock.actualShArgs[0]).isEqualTo('mkdir -p staging/app/k8s/') + assertThat(scriptMock.actualEchoArgs[0]).isEqualTo('Copying k8s payload from application repo to gitOps Repo: \'k8s/staging/*\' to \'staging/app/extraResources/\'') + assertThat(scriptMock.actualShArgs[0]).isEqualTo('mkdir -p staging/app/extraResources/') assertThat(scriptMock.actualShArgs[1]).isEqualTo('mkdir -p .config/') - assertThat(scriptMock.actualShArgs[2]).isEqualTo('cp -r workspace/k8s/staging/* staging/app/k8s/ || true') + assertThat(scriptMock.actualShArgs[2]).isEqualTo('cp -r workspace/k8s/staging/* staging/app/extraResources/ || true') assertThat(scriptMock.actualShArgs[3]).isEqualTo('cp workspace/*.yamllint.yaml .config/ || true') } @@ -78,24 +78,24 @@ class DeploymentTest { assertThat(scriptMock.actualShArgs[0]).isEqualTo('[returnStdout:true, script:KUBECONFIG=pwd/.kube/config kubectl create configmap index --from-file=index.html=workspace/k8s/../index.html --dry-run=client -o yaml -n fluxv1-staging]') - assertThat(scriptMock.actualWriteFileArgs[0]).isEqualTo('[file:pwd/.kube/config, text:apiVersion: v1\n' + - 'clusters:\n' + - '- cluster:\n' + - ' certificate-authority-data: DATA+OMITTED\n' + - ' server: https://localhost\n' + - ' name: self-hosted-cluster\n' + - 'contexts:\n' + - '- context:\n' + - ' cluster: self-hosted-cluster\n' + - ' user: svcs-acct-dply\n' + - ' name: svcs-acct-context\n' + - 'current-context: svcs-acct-context\n' + - 'kind: Config\n' + - 'preferences: {}\n' + - 'users:\n' + - '- name: svcs-acct-dply\n' + - ' user:\n' + - ' token: DATA+OMITTED]') + assertThat(scriptMock.actualWriteFileArgs[0]).isEqualTo('''[file:pwd/.kube/config, text:apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: DATA+OMITTED + server: https://localhost + name: self-hosted-cluster +contexts: +- context: + cluster: self-hosted-cluster + user: svcs-acct-dply + name: svcs-acct-context +current-context: svcs-acct-context +kind: Config +preferences: {} +users: +- name: svcs-acct-dply + user: + token: DATA+OMITTED]''') assertThat(scriptMock.actualWriteFileArgs[1]).contains('[file:staging/app/generatedResources/index.yaml') } @@ -106,12 +106,12 @@ class DeploymentTest { } @Override - def createPreValidation(String stage) { + def preValidation(String stage) { return null } @Override - def createPostValidation(String stage) { + def postValidation(String stage) { return null } } diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/HelmTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/HelmTest.groovy index a88b5d7..fd3f7a4 100644 --- a/test/com/cloudogu/gitopsbuildlib/deployment/HelmTest.groovy +++ b/test/com/cloudogu/gitopsbuildlib/deployment/HelmTest.groovy @@ -1,6 +1,7 @@ package com.cloudogu.gitopsbuildlib.deployment import com.cloudogu.gitopsbuildlib.ScriptMock +import com.cloudogu.gitopsbuildlib.deployment.helm.Helm import org.junit.jupiter.api.* import static org.assertj.core.api.Assertions.assertThat @@ -11,6 +12,7 @@ class HelmTest { def dockerMock = scriptMock.dockerMock def helmGit = new Helm(scriptMock.mock, [ application: 'testapp', + gitopsTool: 'FLUX', stages: [ staging: [ namespace: 'fluxv1-staging' @@ -27,15 +29,14 @@ class HelmTest { fileConfigmaps: [ [ name : "index", - sourceFilePath : "../index.html", // relative to deployments.sourcePath - // additional feature in the future. current default folder is '../generated-resources' - // destinationFilePath: "../generated-resources/html/" // realtive to deployments.sourcePath + sourceFilePath : "../index.html", stage: ["staging"] ] ] ]) def helmHelm = new Helm(scriptMock.mock, [ application: 'testapp', + gitopsTool: 'FLUX', stages: [ staging: [ namespace: 'fluxv1-staging' @@ -47,15 +48,14 @@ class HelmTest { repoType: 'HELM', repoUrl: 'repoUrl', chartName: 'chartName', + credentials: 'creds', version: '1.0' ] ], fileConfigmaps: [ [ name : "index", - sourceFilePath : "../index.html", // relative to deployments.sourcePath - // additional feature in the future. current default folder is '../generated-resources' - // destinationFilePath: "../generated-resources/html/" // realtive to deployments.sourcePath + sourceFilePath : "../index.html", stage: ["staging"] ] ] @@ -63,48 +63,47 @@ class HelmTest { @Test void 'creating helm release with git repo'() { - helmGit.createPreValidation('staging') + helmGit.preValidation('staging') - assertThat(dockerMock.actualImages[0]).isEqualTo('ghcr.io/cloudogu/helm:3.4.1-1') - assertThat(scriptMock.actualShArgs[0]).isEqualTo('[returnStdout:true, script:helm values workspace/chart/chartPath -f workspace/k8s/values-staging.yaml -f workspace/k8s/values-shared.yaml ]') - assertThat(scriptMock.actualShArgs[1]).isEqualTo('rm -rf workspace/chart || true') + assertThat(dockerMock.actualImages[0]).contains('ghcr.io/cloudogu/helm:') + assertThat(scriptMock.actualShArgs[0]).isEqualTo('helm dep update chart/chartPath') + assertThat(scriptMock.actualShArgs[1]).isEqualTo('[returnStdout:true, script:helm values chart/chartPath -f workspace/k8s/values-staging.yaml -f workspace/k8s/values-shared.yaml ]') assertThat(scriptMock.actualShArgs[2]).isEqualTo('rm staging/testapp/mergedValues.yaml') - assertThat(scriptMock.actualGitArgs[0]).isEqualTo('[url:repoUrl, branch:main, changelog:false, poll:false]') - assertThat(scriptMock.actualWriteFileArgs[0]).isEqualTo('[file:staging/testapp/mergedValues.yaml, text:[[returnStdout:true, script:helm values workspace/chart/chartPath -f workspace/k8s/values-staging.yaml -f workspace/k8s/values-shared.yaml ]]]') - assertThat(scriptMock.actualWriteFileArgs[1]).isEqualTo('[file:staging/testapp/helmRelease.yaml, text:apiVersion: helm.fluxcd.io/v1\n' + - 'kind: HelmRelease\n' + - 'metadata:\n' + - ' name: testapp\n' + - ' namespace: fluxv1-staging\n' + - ' annotations:\n' + - ' fluxcd.io/automated: "false"\n' + - 'spec:\n' + - ' releaseName: testapp\n' + - ' chart:\n' + - ' git: repoUrl\n' + - ' ref: null\n' + - ' path: .\n' + - ' values:\n' + - ' ---\n' + - ' #this part is only for PlainTest regarding updating the image name\n' + - ' spec:\n' + - ' template:\n' + - ' spec:\n' + - ' containers:\n' + - ' - name: \'application\'\n' + - ' image: \'oldImageName\'\n' + - ' #this part is only for HelmTest regarding changing the yaml values\n' + - ' to:\n' + - ' be:\n' + - ' changed: \'oldValue\'\n' + - ']') + assertThat(scriptMock.actualWriteFileArgs[0]).isEqualTo('[file:staging/testapp/mergedValues.yaml, text:[helm dep update chart/chartPath, [returnStdout:true, script:helm values chart/chartPath -f workspace/k8s/values-staging.yaml -f workspace/k8s/values-shared.yaml ]]]') + assertThat(scriptMock.actualWriteFileArgs[1]).isEqualTo('''[file:staging/testapp/applicationRelease.yaml, text:apiVersion: helm.fluxcd.io/v1 +kind: HelmRelease +metadata: + name: testapp + namespace: fluxv1-staging + annotations: + fluxcd.io/automated: "false" +spec: + releaseName: testapp + chart: + git: repoUrl + ref: null + path: chartPath + values: + --- + #this part is only for PlainTest regarding updating the image name + spec: + template: + spec: + containers: + - name: \'application\' + image: \'oldImageName\' + #this part is only for HelmTest regarding changing the yaml values + to: + be: + changed: \'oldValue\' +]''') } @Test void 'creating helm release with helm repo'() { - helmHelm.createPreValidation('staging') + helmHelm.preValidation('staging') - assertThat(dockerMock.actualImages[0]).isEqualTo('ghcr.io/cloudogu/helm:3.4.1-1') + assertThat(dockerMock.actualImages[0]).contains('ghcr.io/cloudogu/helm:') assertThat(scriptMock.actualShArgs[0]).isEqualTo('helm repo add chartRepo repoUrl') assertThat(scriptMock.actualShArgs[1]).isEqualTo('helm repo update') assertThat(scriptMock.actualShArgs[2]).isEqualTo('helm pull chartRepo/chartName --version=1.0 --untar --untardir=workspace/chart') @@ -115,32 +114,32 @@ class HelmTest { 'text:[helm repo add chartRepo repoUrl, helm repo update, ' + 'helm pull chartRepo/chartName --version=1.0 --untar --untardir=workspace/chart, ' + '[returnStdout:true, script:helm values workspace/chart/chartName -f workspace/k8s/values-staging.yaml -f workspace/k8s/values-shared.yaml ]]]') - assertThat(scriptMock.actualWriteFileArgs[1]).isEqualTo('[file:staging/testapp/helmRelease.yaml, text:apiVersion: helm.fluxcd.io/v1\n' + - 'kind: HelmRelease\n' + - 'metadata:\n' + - ' name: testapp\n' + - ' namespace: fluxv1-staging\n' + - ' annotations:\n' + - ' fluxcd.io/automated: "false"\n' + - 'spec:\n' + - ' releaseName: testapp\n' + - ' chart:\n' + - ' repository: repoUrl\n' + - ' name: chartName\n' + - ' version: 1.0\n' + - ' values:\n' + - ' ---\n' + - ' #this part is only for PlainTest regarding updating the image name\n' + - ' spec:\n' + - ' template:\n' + - ' spec:\n' + - ' containers:\n' + - ' - name: \'application\'\n' + - ' image: \'oldImageName\'\n' + - ' #this part is only for HelmTest regarding changing the yaml values\n' + - ' to:\n' + - ' be:\n' + - ' changed: \'oldValue\'\n' + - ']') + assertThat(scriptMock.actualWriteFileArgs[1]).isEqualTo('''[file:staging/testapp/applicationRelease.yaml, text:apiVersion: helm.fluxcd.io/v1 +kind: HelmRelease +metadata: + name: testapp + namespace: fluxv1-staging + annotations: + fluxcd.io/automated: "false" +spec: + releaseName: testapp + chart: + repository: repoUrl + name: chartName + version: 1.0 + values: + --- + #this part is only for PlainTest regarding updating the image name + spec: + template: + spec: + containers: + - name: \'application\' + image: \'oldImageName\' + #this part is only for HelmTest regarding changing the yaml values + to: + be: + changed: \'oldValue\' +]''') } } diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/PlainTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/PlainTest.groovy index 972195e..bfbeb9b 100644 --- a/test/com/cloudogu/gitopsbuildlib/deployment/PlainTest.groovy +++ b/test/com/cloudogu/gitopsbuildlib/deployment/PlainTest.groovy @@ -1,6 +1,7 @@ package com.cloudogu.gitopsbuildlib.deployment import com.cloudogu.gitopsbuildlib.ScriptMock +import com.cloudogu.gitopsbuildlib.deployment.plain.Plain import org.junit.jupiter.api.* import static org.assertj.core.api.Assertions.assertThat @@ -25,7 +26,7 @@ class PlainTest { @Test void 'successful update'() { - plain.createPostValidation('staging') + plain.postValidation('staging') assertThat(scriptMock.actualReadYamlArgs[0]).isEqualTo('[file:staging/testApp/k8s/deployment.yaml]') assertThat(scriptMock.actualWriteYamlArgs[0]).isEqualTo('[file:staging/testApp/k8s/deployment.yaml, data:[spec:[template:[spec:[containers:[[image:imageNameReplacedTest, name:application]]]]], to:[be:[changed:oldValue]]], overwrite:true]') diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/ArgoCDReleaseTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/ArgoCDReleaseTest.groovy new file mode 100644 index 0000000..39607d7 --- /dev/null +++ b/test/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/ArgoCDReleaseTest.groovy @@ -0,0 +1,43 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease + +import com.cloudogu.gitopsbuildlib.ScriptMock +import org.junit.jupiter.api.Test + +import static org.assertj.core.api.Assertions.assertThat + +class ArgoCDReleaseTest { + + def scriptMock = new ScriptMock() + def argoCdReleaseTest = new ArgoCDRelease(scriptMock.mock) + + @Test + void 'correct helm release with git repo and chartPath'() { + argoCdReleaseTest.create([ + repoType: 'GIT', + repoUrl: 'url', + chartName: 'chartName', + chartPath: 'path', + version: '1.0' + ], + 'app', + 'namespace', + 'this/is/a/valusfile') + + assertThat(scriptMock.actualShArgs[0]).isEqualTo('[returnStdout:true, script:helm template app chart/path -f this/is/a/valusfile]') + } + + @Test + void 'correct helm release with git repo without chartPath'() { + argoCdReleaseTest.create([ + repoType: 'GIT', + repoUrl: 'url', + chartName: 'chartName', + version: '1.0' + ], + 'app', + 'namespace', + 'this/is/a/valusfile') + + assertThat(scriptMock.actualShArgs[0]).isEqualTo('[returnStdout:true, script:helm template app chart/ -f this/is/a/valusfile]') + } +} diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/FluxV1ReleaseTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/FluxV1ReleaseTest.groovy new file mode 100644 index 0000000..c0ef668 --- /dev/null +++ b/test/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/FluxV1ReleaseTest.groovy @@ -0,0 +1,94 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease + +import com.cloudogu.gitopsbuildlib.ScriptMock +import org.junit.jupiter.api.Test + +import static org.assertj.core.api.Assertions.assertThat + +class FluxV1ReleaseTest { + + def scriptMock = new ScriptMock() + def fluxV1Release = new FluxV1Release(scriptMock.mock) + + @Test + void 'correct helm release with git repo'() { + def output = fluxV1Release.create([ + repoType: 'GIT', + repoUrl: 'url', + chartName: 'chartName', + version: '1.0' + ], + 'app', + 'namespace', + 'this/is/a/valusfile') + + assertThat(output).isEqualTo("""apiVersion: helm.fluxcd.io/v1 +kind: HelmRelease +metadata: + name: app + namespace: namespace + annotations: + fluxcd.io/automated: "false" +spec: + releaseName: app + chart: + git: url + ref: 1.0 + path: . + values: + --- + #this part is only for PlainTest regarding updating the image name + spec: + template: + spec: + containers: + - name: 'application' + image: 'oldImageName' + #this part is only for HelmTest regarding changing the yaml values + to: + be: + changed: 'oldValue' +""") + } + + @Test + void 'correct helm release with helm repo'() { + def output = fluxV1Release.create([ + repoType: 'HELM', + repoUrl: 'url', + chartName: 'chartName', + version: '1.0' + ], + 'app', + 'namespace', + 'this/is/a/valusfile') + + assertThat(output).isEqualTo("""apiVersion: helm.fluxcd.io/v1 +kind: HelmRelease +metadata: + name: app + namespace: namespace + annotations: + fluxcd.io/automated: "false" +spec: + releaseName: app + chart: + repository: url + name: chartName + version: 1.0 + values: + --- + #this part is only for PlainTest regarding updating the image name + spec: + template: + spec: + containers: + - name: 'application' + image: 'oldImageName' + #this part is only for HelmTest regarding changing the yaml values + to: + be: + changed: 'oldValue' +""") + } +} diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/repotype/RepoTypeTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/HelmReleaseTest.groovy similarity index 63% rename from test/com/cloudogu/gitopsbuildlib/deployment/repotype/RepoTypeTest.groovy rename to test/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/HelmReleaseTest.groovy index 5ed5ea7..9253d4d 100644 --- a/test/com/cloudogu/gitopsbuildlib/deployment/repotype/RepoTypeTest.groovy +++ b/test/com/cloudogu/gitopsbuildlib/deployment/helm/helmrelease/HelmReleaseTest.groovy @@ -1,13 +1,14 @@ -package com.cloudogu.gitopsbuildlib.deployment.repotype +package com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease import com.cloudogu.gitopsbuildlib.ScriptMock -import org.junit.jupiter.api.* +import org.junit.jupiter.api.Test + import static org.assertj.core.api.Assertions.assertThat -class RepoTypeTest { +class HelmReleaseTest { def scriptMock = new ScriptMock() - def repoType = new RepoTypeUnderTest(scriptMock.mock) + def repoType = new HelmReleaseUnderTest(scriptMock.mock) @Test void 'inline yaml test'() { @@ -28,19 +29,14 @@ class RepoTypeTest { changed: 'oldValue\'''') } - class RepoTypeUnderTest extends RepoType { + class HelmReleaseUnderTest extends HelmRelease { - RepoTypeUnderTest(Object script) { + HelmReleaseUnderTest(Object script) { super(script) } @Override - def createHelmRelease(Map helmConfig, String application, String namespace, String valuesFile) { - return null - } - - @Override - def mergeValues(Map helmConfig, String[] files) { + String create(Map helmConfig, String application, String namespace, String valuesFile) { return null } } diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/GitRepoTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/GitRepoTest.groovy new file mode 100644 index 0000000..7e17742 --- /dev/null +++ b/test/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/GitRepoTest.groovy @@ -0,0 +1,27 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.repotype + +import com.cloudogu.gitopsbuildlib.ScriptMock +import org.junit.jupiter.api.* + +import static org.assertj.core.api.Assertions.assertThat + +class GitRepoTest { + + def scriptMock = new ScriptMock() + def gitRepo = new GitRepo(scriptMock.mock) + + @Test + void 'merges values successfully'() { + gitRepo.mergeValues([ + repoUrl: 'url', + chartPath: 'chartPath', + version: '1.0' + ], [ + 'file1', + 'file2' + ] as String[]) + + assertThat(scriptMock.actualShArgs[0]).isEqualTo('helm dep update chart/chartPath') + assertThat(scriptMock.actualShArgs[1]).isEqualTo('[returnStdout:true, script:helm values chart/chartPath -f file1 -f file2 ]') + } +} diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/repotype/HelmRepoTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/HelmRepoTest.groovy similarity index 51% rename from test/com/cloudogu/gitopsbuildlib/deployment/repotype/HelmRepoTest.groovy rename to test/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/HelmRepoTest.groovy index 593a8ff..1b4ec30 100644 --- a/test/com/cloudogu/gitopsbuildlib/deployment/repotype/HelmRepoTest.groovy +++ b/test/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/HelmRepoTest.groovy @@ -1,4 +1,4 @@ -package com.cloudogu.gitopsbuildlib.deployment.repotype +package com.cloudogu.gitopsbuildlib.deployment.helm.repotype import com.cloudogu.gitopsbuildlib.ScriptMock import org.junit.jupiter.api.* @@ -26,46 +26,4 @@ class HelmRepoTest { assertThat(scriptMock.actualShArgs[3]).isEqualTo('[returnStdout:true, script:helm values workspace/chart/chartName -f file1 -f file2 ]') assertThat(scriptMock.actualShArgs[4]).isEqualTo('rm -rf workspace/chart || true') } - - @Test - void 'create helm release yields correct helmRelease'() { - def helmRelease = helmRepo.createHelmRelease([ - repoUrl: 'url', - chartName: 'chartName', - version: '1.0' - ], - 'app', - 'namespace', - 'valuesFile') - - assertThat(helmRelease).isEqualTo('''\ -apiVersion: helm.fluxcd.io/v1 -kind: HelmRelease -metadata: - name: app - namespace: namespace - annotations: - fluxcd.io/automated: "false" -spec: - releaseName: app - chart: - repository: url - name: chartName - version: 1.0 - values: - --- - #this part is only for PlainTest regarding updating the image name - spec: - template: - spec: - containers: - - name: 'application\' - image: 'oldImageName' - #this part is only for HelmTest regarding changing the yaml values - to: - be: - changed: 'oldValue' -''') - } - } diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/RepoTypeTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/RepoTypeTest.groovy new file mode 100644 index 0000000..e65fc9d --- /dev/null +++ b/test/com/cloudogu/gitopsbuildlib/deployment/helm/repotype/RepoTypeTest.groovy @@ -0,0 +1,30 @@ +package com.cloudogu.gitopsbuildlib.deployment.helm.repotype; + +import com.cloudogu.gitopsbuildlib.ScriptMock; +import org.junit.jupiter.api.Test + +import static org.assertj.core.api.Assertions.assertThat; + +class RepoTypeTest { + + def scriptMock = new ScriptMock() + def repoType = new RepoTypeUnderTest(scriptMock.mock) + + @Test + void 'values files getting parameters attached'() { + def output = repoType.valuesFilesWithParameter(['file1.yaml', 'file2.yaml'] as String[]) + assertThat(output).isEqualTo('-f file1.yaml -f file2.yaml ') + } + + class RepoTypeUnderTest extends RepoType { + + RepoTypeUnderTest(Object script) { + super(script) + } + + @Override + Object mergeValues(Map helmConfig, String[] files) { + return null; + } + } +} diff --git a/test/com/cloudogu/gitopsbuildlib/deployment/repotype/GitRepoTest.groovy b/test/com/cloudogu/gitopsbuildlib/deployment/repotype/GitRepoTest.groovy deleted file mode 100644 index 17ed48e..0000000 --- a/test/com/cloudogu/gitopsbuildlib/deployment/repotype/GitRepoTest.groovy +++ /dev/null @@ -1,70 +0,0 @@ -package com.cloudogu.gitopsbuildlib.deployment.repotype - -import com.cloudogu.gitopsbuildlib.ScriptMock -import org.junit.jupiter.api.* - -import static org.assertj.core.api.Assertions.assertThat - -class GitRepoTest { - - def scriptMock = new ScriptMock() - def gitRepo = new GitRepo(scriptMock.mock) - - @Test - void 'merges values successfully'() { - gitRepo.mergeValues([ - repoUrl: 'url', - chartPath: 'chartPath', - version: '1.0' - ], [ - 'file1', - 'file2' - ] as String[]) - - assertThat(scriptMock.actualShArgs[0]).isEqualTo('[returnStdout:true, script:helm values workspace/chart/chartPath -f file1 -f file2 ]') - assertThat(scriptMock.actualShArgs[1]).isEqualTo('rm -rf workspace/chart || true') - assertThat(scriptMock.actualGitArgs[0]).isEqualTo('[url:url, branch:main, changelog:false, poll:false]') - } - - @Test - void 'create helm release yields correct helmRelease'() { - def helmRelease = gitRepo.createHelmRelease([ - repoUrl: 'url', - chartName: 'chartName', - version: '1.0' - ], - 'app', - 'namespace', - 'valuesFile') - - assertThat(helmRelease).isEqualTo('''\ -apiVersion: helm.fluxcd.io/v1 -kind: HelmRelease -metadata: - name: app - namespace: namespace - annotations: - fluxcd.io/automated: "false" -spec: - releaseName: app - chart: - git: url - ref: 1.0 - path: . - values: - --- - #this part is only for PlainTest regarding updating the image name - spec: - template: - spec: - containers: - - name: 'application\' - image: 'oldImageName' - #this part is only for HelmTest regarding changing the yaml values - to: - be: - changed: 'oldValue' -''') - } - -} diff --git a/test/com/cloudogu/gitopsbuildlib/validation/HelmKubevalTest.groovy b/test/com/cloudogu/gitopsbuildlib/validation/HelmKubevalTest.groovy index f10f42f..7af0860 100644 --- a/test/com/cloudogu/gitopsbuildlib/validation/HelmKubevalTest.groovy +++ b/test/com/cloudogu/gitopsbuildlib/validation/HelmKubevalTest.groovy @@ -25,14 +25,12 @@ class HelmKubevalTest { repoType: 'GIT', repoUrl: 'chartRepo/namespace/repoPath', chartPath: 'chartPath', - credentialsId: 'creds', version: 'version' ] ] ) assertThat(dockerMock.actualImages[0]).isEqualTo('img') - assertThat(scriptMock.actualShArgs[0]).isEqualTo('helm kubeval target/chart/chartPath -v 1.5') - assertThat(scriptMock.actualShArgs[1]).isEqualTo('rm -rf target/chart') + assertThat(scriptMock.actualShArgs[0]).isEqualTo('helm kubeval chart/chartPath -v 1.5') } @Test diff --git a/test/com/cloudogu/gitopsbuildlib/validation/KubevalTest.groovy b/test/com/cloudogu/gitopsbuildlib/validation/KubevalTest.groovy index accf77d..53b5358 100644 --- a/test/com/cloudogu/gitopsbuildlib/validation/KubevalTest.groovy +++ b/test/com/cloudogu/gitopsbuildlib/validation/KubevalTest.groovy @@ -24,7 +24,7 @@ class KubevalTest { ) assertThat(dockerMock.actualImages[0]).isEqualTo('img') assertThat(scriptMock.actualShArgs[0]).isEqualTo( - 'kubeval -d target/k8s -v 1.5 --strict' + 'kubeval -d target -v 1.5 --strict' ) } } diff --git a/test/com/cloudogu/gitopsbuildlib/validation/ValidatorTest.groovy b/test/com/cloudogu/gitopsbuildlib/validation/ValidatorTest.groovy index 1b64a81..9b131d8 100644 --- a/test/com/cloudogu/gitopsbuildlib/validation/ValidatorTest.groovy +++ b/test/com/cloudogu/gitopsbuildlib/validation/ValidatorTest.groovy @@ -1,6 +1,7 @@ package com.cloudogu.gitopsbuildlib.validation import com.cloudogu.gitopsbuildlib.ScriptMock +import com.cloudogu.gitopsbuildlib.docker.DockerWrapper import com.cloudogu.gitopsbuildlib.validation.Validator import org.junit.jupiter.api.Test import static org.assertj.core.api.Assertions.assertThat diff --git a/vars/deployViaGitops.groovy b/vars/deployViaGitops.groovy index 110f001..bf92529 100644 --- a/vars/deployViaGitops.groovy +++ b/vars/deployViaGitops.groovy @@ -1,15 +1,15 @@ #!groovy import com.cloudogu.gitopsbuildlib.* import com.cloudogu.gitopsbuildlib.deployment.Deployment -import com.cloudogu.gitopsbuildlib.deployment.Helm -import com.cloudogu.gitopsbuildlib.deployment.Plain +import com.cloudogu.gitopsbuildlib.deployment.helm.Helm +import com.cloudogu.gitopsbuildlib.deployment.plain.Plain import com.cloudogu.gitopsbuildlib.scm.SCMManager import com.cloudogu.gitopsbuildlib.scm.SCMProvider import com.cloudogu.gitopsbuildlib.validation.HelmKubeval import com.cloudogu.gitopsbuildlib.validation.Kubeval import com.cloudogu.gitopsbuildlib.validation.Yamllint -String getHelmImage() { 'ghcr.io/cloudogu/helm:3.4.1-1' } +String getHelmImage() { 'ghcr.io/cloudogu/helm:3.5.4-1' } List getMandatoryFields() { return [ @@ -21,7 +21,7 @@ Map getDefaultConfig() { return [ cesBuildLibRepo : 'https://github.com/cloudogu/ces-build-lib', - cesBuildLibVersion : '1.45.0', + cesBuildLibVersion : '1.46.1', cesBuildLibCredentialsId: '', mainBranch : 'main', deployments : [