Skip to content

Commit

Permalink
Merge pull request #33 from cloudogu/feature/plain-cronjobs
Browse files Browse the repository at this point in the history
Implement plain deployments for CronJobs
  • Loading branch information
pwrdeli authored May 3, 2023
2 parents bc86674 + 8dfe9de commit c8ba07d
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 94 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Make `deployments.plain` work with `CronJob

## [0.4.0](https://github.com/cloudogu/gitops-build-lib/releases/tag/0.4.0) - 2023-03-21

### Added
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,10 @@ def gitopsConfig = [

In plain pipelines, the library creates the deployment resources by updating the image tag within the deployment.

---
Note that this works with Kubernetes `Deployment`s, `StatefulSet`s and `CronJob`s. For all other kinds of resources the
library tries to find the `containers` at the following YAML path: `spec.template.spec.containers`. This might fail, of course. If you encounter a case
like this, please create an issue. Eventually, we're planning to provide a `fieldPath` option just like in helm releases.


### Helm deployment
Besides plain k8s resources you can also use helm charts to generate the resources. You can choose between these types
Expand Down
25 changes: 23 additions & 2 deletions src/com/cloudogu/gitopsbuildlib/deployment/plain/Plain.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.cloudogu.gitopsbuildlib.deployment.plain
import com.cloudogu.gitopsbuildlib.deployment.Deployment
import com.cloudogu.gitopsbuildlib.deployment.SourceType

class Plain extends Deployment{
class Plain extends Deployment {

Plain(def script, def gitopsConfig) {
super(script, gitopsConfig)
Expand All @@ -30,14 +30,35 @@ class Plain extends Deployment{
private updateImage(String stage) {
def destinationPath = getDestinationFolder(getFolderStructureStrategy(), stage)

script.echo "About Updating images in plain deployment: ${gitopsConfig.deployments.plain.updateImages}"
gitopsConfig.deployments.plain.updateImages.each {
script.echo "Replacing image '${it['imageName']}' in file: ${it['filename']}"
def deploymentFilePath = "${destinationPath}/${it['filename']}"
def data = script.readYaml file: deploymentFilePath
def containers = data.spec.template.spec.containers
String kind = data.kind
def containers = findContainers(data, kind)
script.echo "Found containers '${containers}' in YAML: ${it['filename']}"
def containerName = it['containerName']
def updateContainer = containers.find { it.name == containerName }
updateContainer.image = it['imageName']
script.writeYaml file: deploymentFilePath, data: data, overwrite: true
script.echo "Wrote file ${deploymentFilePath} with yaml:\n${data}"
}
}

def findContainers(def data, String kind) {
//noinspection GroovyFallthrough
switch (kind) {
case 'Deployment':
// Falling through because Deployment and StatefulSet's paths are the same
case 'StatefulSet':
return data.spec.template.spec.containers
case 'CronJob':
return data.spec.jobTemplate.spec.template.spec.containers
default:
script.echo "Warning: Kind '$kind' is unknown, using best effort to find 'containers' in YAML"
// Best effort: Try the same as for Deployment and StatefulSet
return data.spec.template.spec.containers
}
}
}
15 changes: 1 addition & 14 deletions test/com/cloudogu/gitopsbuildlib/ScriptMock.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,7 @@ class ScriptMock {
List<String> actualReadYamlArgs = new LinkedList<>()
List<String> actualGitArgs = new LinkedList<>()
List<String> actualDir = new LinkedList<>()
def configYaml = '''\
---
#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'
'''
def configYaml = ''
List<String> actualWriteYamlArgs = new LinkedList<>()
List<String> actualReadFileArgs = new LinkedList<>()
List<String> actualWriteFileArgs = new LinkedList<>()
Expand Down
49 changes: 13 additions & 36 deletions test/com/cloudogu/gitopsbuildlib/deployment/HelmTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ class HelmTest {
def helmHelm = new Helm(scriptMock.mock, getGitopsConfig(helmRepo))
def helmLocal = new Helm(scriptMock.mock, getGitopsConfig(localRepo, 'ARGO'))

@BeforeEach
void init () {
scriptMock.configYaml = '''
to:
be:
changed: 'oldValue'
'''
}

@Test
void 'creating helm release with git repo'() {
helmGit.preValidation('staging')
Expand All @@ -121,15 +130,7 @@ spec:
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\'
Expand Down Expand Up @@ -160,15 +161,7 @@ spec:
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\'
Expand Down Expand Up @@ -200,15 +193,7 @@ spec:
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\'
Expand Down Expand Up @@ -242,15 +227,7 @@ spec:
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\'
Expand Down
64 changes: 60 additions & 4 deletions test/com/cloudogu/gitopsbuildlib/deployment/PlainTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import com.cloudogu.gitopsbuildlib.deployment.plain.Plain
import com.cloudogu.gitopsbuildlib.validation.HelmKubeval
import com.cloudogu.gitopsbuildlib.validation.Kubeval
import com.cloudogu.gitopsbuildlib.validation.Yamllint
import org.junit.jupiter.api.*
import static org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

import static org.assertj.core.api.Assertions.assertThat

class PlainTest {

Expand Down Expand Up @@ -52,22 +53,77 @@ class PlainTest {
],
])

String deploymentYaml = '''
kind: Deployment
spec:
template:
spec:
containers:
- name: 'application'
image: 'oldImageName'
'''

String cronJobYaml = '''
kind: CronJob
spec:
jobTemplate:
spec:
template:
spec:
containers:
- name: 'application'
image: 'oldImageName'
- name: 'other'
image: 'otherImageName'
'''

@BeforeEach
void init () {
scriptMock.configYaml = deploymentYaml
}

@Test
void 'successful update'() {

plain.preValidation('staging')
assertThat(scriptMock.actualReadYamlArgs[0]).isEqualTo('[file:staging/app/deployment.yaml]')
assertThat(scriptMock.actualWriteYamlArgs[0]).isEqualTo('[file:staging/app/deployment.yaml, data:[spec:[template:[spec:[containers:[[image:imageNameReplacedTest, name:application]]]]], to:[be:[changed:oldValue]]], overwrite:true]')
assertThat(scriptMock.actualWriteYamlArgs[0]).isEqualTo('[file:staging/app/deployment.yaml, data:[kind:Deployment, spec:[template:[spec:[containers:[[image:imageNameReplacedTest, name:application]]]]]], overwrite:true]')
}

@Test
void 'successful update with statefulSet'() {
scriptMock.configYaml = scriptMock.configYaml.replace('kind: Deployment', 'kind: StatefulSet')
plain.preValidation('staging')
assertThat(scriptMock.actualReadYamlArgs[0]).isEqualTo('[file:staging/app/deployment.yaml]')
assertThat(scriptMock.actualWriteYamlArgs[0]).isEqualTo('[file:staging/app/deployment.yaml, data:[kind:StatefulSet, spec:[template:[spec:[containers:[[image:imageNameReplacedTest, name:application]]]]]], overwrite:true]')
}

@Test
void 'successful update with cronjob'() {
scriptMock.configYaml = cronJobYaml

plain.preValidation('staging')
assertThat(scriptMock.actualReadYamlArgs[0]).isEqualTo('[file:staging/app/deployment.yaml]')
assertThat(scriptMock.actualWriteYamlArgs[0]).isEqualTo('[file:staging/app/deployment.yaml, data:[kind:CronJob, spec:[jobTemplate:[spec:[template:[spec:[containers:[[image:imageNameReplacedTest, name:application], [image:otherImageName, name:other]]]]]]]], overwrite:true]')
}

@Test
void 'successful update with other resource'() {
scriptMock.configYaml = scriptMock.configYaml.replace('kind: Deployment', 'kind: SomethingElse')
plain.preValidation('staging')
assertThat(scriptMock.actualReadYamlArgs[0]).isEqualTo('[file:staging/app/deployment.yaml]')
assertThat(scriptMock.actualWriteYamlArgs[0]).isEqualTo('[file:staging/app/deployment.yaml, data:[kind:SomethingElse, spec:[template:[spec:[containers:[[image:imageNameReplacedTest, name:application]]]]]], overwrite:true]')
assertThat(scriptMock.actualEchoArgs).contains('Warning: Kind \'SomethingElse\' is unknown, using best effort to find \'containers\' in YAML')
}

@Test
void 'successful update with ENV_PER_APP and other destinationRootPath '() {
plain.gitopsConfig['folderStructureStrategy'] = 'ENV_PER_APP'
plain.gitopsConfig['deployments']['destinationRootPath'] = 'apps'

plain.preValidation('staging')
assertThat(scriptMock.actualReadYamlArgs[0]).isEqualTo('[file:apps/app/staging/deployment.yaml]')
assertThat(scriptMock.actualWriteYamlArgs[0]).isEqualTo('[file:apps/app/staging/deployment.yaml, data:[spec:[template:[spec:[containers:[[image:imageNameReplacedTest, name:application]]]]], to:[be:[changed:oldValue]]], overwrite:true]')
assertThat(scriptMock.actualWriteYamlArgs[0]).isEqualTo('[file:apps/app/staging/deployment.yaml, data:[kind:Deployment, spec:[template:[spec:[containers:[[image:imageNameReplacedTest, name:application]]]]]], overwrite:true]')
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease

import com.cloudogu.gitopsbuildlib.ScriptMock
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

import static org.assertj.core.api.Assertions.assertThat
Expand All @@ -10,6 +11,11 @@ class FluxV1ReleaseTest {
def scriptMock = new ScriptMock()
def fluxV1Release = new FluxV1Release(scriptMock.mock)

@BeforeEach
void init () {
scriptMock.configYaml = 'a: b'
}

@Test
void 'correct helm release with git repo'() {
def output = fluxV1Release.create([
Expand Down Expand Up @@ -39,18 +45,7 @@ spec:
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'
a: b
""")
}

Expand Down Expand Up @@ -84,18 +79,7 @@ spec:
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'
a: b
""")
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.cloudogu.gitopsbuildlib.deployment.helm.helmrelease

import com.cloudogu.gitopsbuildlib.ScriptMock
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

import static org.assertj.core.api.Assertions.assertThat
Expand All @@ -10,23 +11,16 @@ class HelmReleaseTest {
def scriptMock = new ScriptMock()
def repoType = new HelmReleaseUnderTest(scriptMock.mock)

@BeforeEach
void init () {
scriptMock.configYaml = 'a: b'
}

@Test
void 'inline yaml test'() {
def output = repoType.fileToInlineYaml('filepath')
assertThat(scriptMock.actualReadFileArgs[0]).isEqualTo('filepath')
assertThat(output).isEqualTo('''\
---
#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\'''')
assertThat(output).isEqualTo(' a: b')
}

class HelmReleaseUnderTest extends HelmRelease {
Expand Down

0 comments on commit c8ba07d

Please sign in to comment.