-
Notifications
You must be signed in to change notification settings - Fork 46
/
Jenkinsfile
291 lines (244 loc) · 13.2 KB
/
Jenkinsfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
#!groovy
@Library('github.com/cloudogu/[email protected]')
import com.cloudogu.ces.cesbuildlib.*
String getDockerRegistryBaseUrl() { 'ghcr.io' }
String getDockerImageName() { 'cloudogu/gitops-playground' }
String getTrivyVersion() { '0.55.0'}
properties([
// Dont keep builds forever to preserve space
buildDiscarder(logRotator(numToKeepStr: '50')),
// For now allow concurrent builds.
// This is a slight risk of failing builds if two Jobs of the same branch install k3d (workspace-local) at the same time.
// If this happens to occur often, add the following here: disableConcurrentBuilds(),
parameters([
booleanParam(defaultValue: false, name: 'forcePushImage', description: 'Pushes the image with the current git commit as tag, even when it is on a branch')
])
])
node('high-cpu') {
git = new Git(this)
// Avoid 'No such property: clusterName' error in 'Stop k3d' stage in builds that have failed in an early stage
clusterName = ''
images = []
imageNames = []
timestamps {
catchError {
timeout(activity: false, time: 60, unit: 'MINUTES') {
stage('Checkout') {
checkout scm
git.clean('')
// Otherwise git.isTag() will not be reliable. Jenkins seems to do a sparse checkout only
sh "git fetch --tags"
}
stage('Build cli') {
// Read Java version from Dockerfile (DRY)
String jdkVersion = sh(returnStdout: true, script:
'grep -r \'ARG JDK_VERSION\' Dockerfile | sed "s/.*JDK_VERSION=\'\\(.*\\)\'.*/\\1/" ').trim()
// Groovy version is defined by micronaut version. Get it from there.
String groovyVersion = sh(returnStdout: true, script:
'MICRONAUT_VERSION=$(cat pom.xml | sed -n \'/<parent>/,/<\\/parent>/p\' | ' +
'sed -n \'s/.*<version>\\(.*\\)<\\/version>.*/\\1/p\'); ' +
'curl -s https://repo1.maven.org/maven2/io/micronaut/micronaut-core-bom/${MICRONAUT_VERSION}/micronaut-core-bom-${MICRONAUT_VERSION}.pom | ' +
'sed -n \'s/.*<groovy.version>\\(.*\\)<\\/groovy.version>.*/\\1/p\'').trim()
groovyImage = "groovy:${groovyVersion}-jdk${jdkVersion}"
// Re-use groovy image here, even though we only need JDK
mvn = new MavenWrapperInDocker(this, groovyImage)
mvn 'clean install -DskipTests'
}
stage('Test cli') {
mvn 'test -Dmaven.test.failure.ignore=true'
// Archive test results. Makes build unstable on failed tests.
junit testResults: '**/target/surefire-reports/TEST-*.xml'
}
stage('Build images') {
imageNames += createImageName(git.commitHashShort)
imageNames += createImageName(git.commitHashShort) + '-dev'
images += buildImage(imageNames[0])
images += buildImage(imageNames[1], '--build-arg ENV=dev')
}
parallel(
'Scan image': {
stage('Scan image') {
scanForCriticalVulns(imageNames[0],"prod-criticals")
scanForCriticalVulns(imageNames[1], "dev-criticals")
scanForAllVulns(imageNames[0], "prod-all")
scanForAllVulns(imageNames[1], "dev-all")
}
},
'Start gitops playground': {
stage('start gitops playground') {
clusterName = createClusterName()
startK3d(clusterName)
String registryPort = sh(
script: 'docker inspect ' +
'--format=\'{{ with (index .NetworkSettings.Ports "30000/tcp") }}{{ (index . 0).HostPort }}{{ end }}\' ' +
" k3d-${clusterName}-serverlb",
returnStdout: true
).trim()
docker.image(imageNames[0])
.inside("-e KUBECONFIG=${env.WORKSPACE}/.kube/config " +
" --network=host --entrypoint=''") {
sh "/app/apply-ng --yes --trace --internal-registry-port=${registryPort} " +
"--argocd --monitoring --vault=dev --ingress-nginx --mailhog --base-url=http://localhost"
}
}
}
)
stage('Integration test') {
String k3dAddress = sh(
script: "docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' k3d-${clusterName}-server-0",
returnStdout: true
).trim()
String k3dNetwork = sh(
script: "docker inspect -f '{{range .NetworkSettings.Networks}}{{.NetworkID}}{{end}}' k3d-${clusterName}-server-0",
returnStdout: true
).trim()
int ret=0
new Docker(this).image(groovyImage)
// Avoids errors ("unable to resolve class") probably due to missing HOME for container in JVM.
.mountJenkinsUser()
.inside("--network=${k3dNetwork}") {
// removing m2 and grapes avoids issues where grapes primarily resolves local m2 and fails on missing versions
sh "rm -rf .m2/"
sh "rm -rf .groovy/grapes"
ret = sh(returnStatus: true,
script: "groovy ./scripts/e2e.groovy --url http://${k3dAddress}:9090 --user admin --password admin --writeFailedLog --fail --retry 2")
}
if (ret > 0 ) {
if (fileExists('playground-logs-of-failed-jobs')) {
archiveArtifacts artifacts: 'playground-logs-of-failed-jobs/*.log'
}
unstable "Integration tests failed, see logs appended to jobs and cluster status in logs"
sh "docker exec k3d-${clusterName}-server-0 kubectl get all -A"
}
}
stage('Push image') {
if (isBuildSuccessful()) {
docker.withRegistry("https://${dockerRegistryBaseUrl}", 'cesmarvin-ghcr') {
// Push prod image last, because last pushed image is listed on top in GitHub
if (git.isTag() && env.BRANCH_NAME == 'main') {
// Build tags only on main to avoid human errors
images[1].push()
images[1].push(git.tag + '-dev')
images[1].push('dev')
images[1].push('latest-dev')
images[0].push()
images[0].push('latest')
images[0].push(git.tag)
currentBuild.description = createImageName(git.tag)
currentBuild.description += "\n${imageNames[0]}"
} else if (env.BRANCH_NAME == 'main') {
images[1].push()
images[0].push()
currentBuild.description = "${imageNames[0]}"
} else if (env.BRANCH_NAME == 'test') {
images[1].push()
images[1].push('test-dev')
images[0].push()
images[0].push('test')
currentBuild.description = createImageName('test')
currentBuild.description += "\n${imageNames[0]}"
} else if (params.forcePushImage) {
images[1].push()
images[0].push()
currentBuild.description = imageNames[0]
} else {
echo "Skipping deployment to github container registry because not a tag and not main branch."
}
}
}
}
}
}
stage('Stop k3d') {
if (clusterName) {
// Don't fail build if cleaning up fails
withEnv(["PATH=${WORKSPACE}/.local/bin:${PATH}"]) {
sh "if k3d cluster ls ${clusterName} > /dev/null; " +
"then k3d cluster delete ${clusterName}; " +
"fi"
}
}
}
mailIfStatusChanged(git.commitAuthorEmail)
if (env.BRANCH_NAME == 'main' && env.GOP_DEVELOPERS) {
mailIfStatusChanged(env.GOP_DEVELOPERS)
}
}
}
def buildImage(String imageName, String additionalBuildArgs = '') {
String rfcDate = sh(returnStdout: true, script: 'date --rfc-3339 ns').trim()
return docker.build(imageName,
"--build-arg BUILD_DATE='${rfcDate}' " +
"--build-arg VCS_REF='${git.commitHash}' " +
"${additionalBuildArgs} " +
// if using optional parameters you need to add the '.' argument at the end for docker to build the image
".")
}
def scanForCriticalVulns(String imageName, String fileName){
def additionalTrivyConfig = [
severity : ['CRITICAL'],
additionalFlags: '--ignore-unfixed',
]
def vulns = trivy(imageName, additionalTrivyConfig)
if (vulns.size() > 0) {
writeFile(file: ".trivy/${fileName}.json", encoding: "UTF-8", text: readFile(file: '.trivy/trivyOutput.json', encoding: "UTF-8"))
archiveArtifacts artifacts: ".trivy/${fileName}.json"
unstable "Found ${vulns.size()} vulnerabilities in image. See ${fileName}.json"
}
}
def scanForAllVulns(String imageName, String fileName){
def vulns = trivy(imageName)
if (vulns.size() > 0) {
writeFile(file: ".trivy/${fileName}.json", encoding: "UTF-8", text: readFile(file: '.trivy/trivyOutput.json', encoding: "UTF-8"))
archiveArtifacts artifacts: ".trivy/${fileName}.json"
}
}
def trivy(String imageName, Map additionalTrivyConfig = [:]) {
def trivyConfig = [
imageName : imageName,
trivyVersion : trivyVersion,
additionalFlags: ''
]
trivyConfig.putAll(additionalTrivyConfig)
trivyConfig.additionalFlags += ' --db-repository public.ecr.aws/aquasecurity/trivy-db'
trivyConfig.additionalFlags += ' --java-db-repository public.ecr.aws/aquasecurity/trivy-java-db'
findVulnerabilitiesWithTrivy(trivyConfig)
}
def startK3d(clusterName) {
// Download latest version of static curl, needed insight the container bellow.
sh "mkdir -p $WORKSPACE/.local/bin"
sh(returnStdout: true, script: 'curl -sLo .local/bin/curl ' +
// Note that the repo moparisthebest/static-curl is listed on the official page, so it should be trustworthy
'$(curl -sL -I -o /dev/null -w %{url_effective} https://github.com/moparisthebest/static-curl/releases/latest ' +
'| sed "s/tag/download/")/curl-amd64 && ' +
'chmod +x .local/bin/curl'
).trim()
// Start k3d in a bash3 container to make sure we're OSX compatible 😞
new Docker(this).image('bash:3')
.mountDockerSocket()
.installDockerClient()
.inside() {
withEnv([
// Install k3d to WORSKPACE and make k3d write kubeconfig to WORKSPACE
"HOME=${WORKSPACE}",
// Put k3d and curl on the path
"PATH=${WORKSPACE}/.local/bin:${PATH}"]) {
// Start k3d cluster, binding to an arbitrary registry port
sh "yes | ./scripts/init-cluster.sh --bind-ingress-port=0 --cluster-name=${clusterName} --bind-registry-port=0"
}
}
}
String createClusterName() {
String[] randomUUIDs = UUID.randomUUID().toString().split("-")
String uuid = randomUUIDs[randomUUIDs.length - 1]
return "citest-" + uuid
}
String createImageName(String tag) {
return "${dockerRegistryBaseUrl}/${dockerImageName}:${tag}"
}
def images
def imageNames
String clusterName
def mvn
String groovyImage
def git