Skip to content

Commit 9f9b86f

Browse files
schnatterernihussmann
authored andcommitted
Migrate Jenkins.createJob to groovy
This also fixes namePrefix issues, when starting the job and update from the legacy to the current ScmManagerBranchDiscoveryTrait. The main reason is that this avoids the following error occurring in production with Cloudogu Ecosystem: ++++ curl -s --cookie-jar /tmp/cookies --retry 3 --retry-delay 1 ... ++++ jq -rsc '(.[1] | .http_code|tostring), (.[0] | .crumb)' jq: parse error: Invalid numeric literal at line 4, column 14 +++ RESPONSE= +++ EXIT_STATUS=5 +++ mapfile -t RESPONSE +++ '[' 5 '!=' 0 ']' +++ echo 'Creating Credentials failed with exit code: curl: 5, HTTP Status: ' +++ exit 5 ++ curl -s -H 'Jenkins-Crumb:Creating Credentials failed with exit code: curl: 5, HTTP Status: ' --cookie /tmp/cookie ...-H Content-Type:text/xml --data '<?xml version="1.1" encoding="UTF-8"?> -> status code: 403
1 parent 0274612 commit 9f9b86f

File tree

7 files changed

+160
-55
lines changed

7 files changed

+160
-55
lines changed
Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
<?xml version="1.1" encoding="UTF-8"?>
2-
<jenkins.branch.OrganizationFolder plugin="branch-api@2.6.2">
1+
<?xml version='1.1' encoding='UTF-8'?>
2+
<jenkins.branch.OrganizationFolder plugin="branch-api@2.1178.v969d9eb_c728e">
33
<actions/>
44
<description></description>
55
<properties>
66
<jenkins.branch.OrganizationChildHealthMetricsProperty>
77
<templates>
8-
<com.cloudbees.hudson.plugins.folder.health.WorstChildHealthMetric plugin="cloudbees-folder@6.15">
8+
<com.cloudbees.hudson.plugins.folder.health.WorstChildHealthMetric plugin="cloudbees-folder@6.942.vb_43318a_156b_2">
99
<nonRecursive>false</nonRecursive>
1010
</com.cloudbees.hudson.plugins.folder.health.WorstChildHealthMetric>
1111
</templates>
@@ -15,18 +15,19 @@
1515
</jenkins.branch.OrganizationChildOrphanedItemsProperty>
1616
<jenkins.branch.OrganizationChildTriggersProperty>
1717
<templates>
18-
<com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger plugin="cloudbees-folder@6.15">
18+
<com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger plugin="cloudbees-folder@6.942.vb_43318a_156b_2">
1919
<spec>H H/4 * * *</spec>
2020
<interval>86400000</interval>
2121
</com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger>
2222
</templates>
2323
</jenkins.branch.OrganizationChildTriggersProperty>
2424
<org.jenkinsci.plugins.docker.workflow.declarative.FolderConfig plugin="[email protected]">
2525
<dockerLabel></dockerLabel>
26-
<registry plugin="docker-commons@1.17"/>
26+
<registry plugin="docker-commons@443.v921729d5611d"/>
2727
</org.jenkinsci.plugins.docker.workflow.declarative.FolderConfig>
2828
<jenkins.branch.NoTriggerOrganizationFolderProperty>
2929
<branches>.*</branches>
30+
<strategy>NONE</strategy>
3031
</jenkins.branch.NoTriggerOrganizationFolderProperty>
3132
</properties>
3233
<folderViews class="jenkins.branch.OrganizationFolderViewHolder">
@@ -36,13 +37,14 @@
3637
<icon class="jenkins.branch.MetadataActionFolderIcon">
3738
<owner class="jenkins.branch.OrganizationFolder" reference="../.."/>
3839
</icon>
39-
<orphanedItemStrategy class="com.cloudbees.hudson.plugins.folder.computed.DefaultOrphanedItemStrategy" plugin="cloudbees-folder@6.15">
40+
<orphanedItemStrategy class="com.cloudbees.hudson.plugins.folder.computed.DefaultOrphanedItemStrategy" plugin="cloudbees-folder@6.942.vb_43318a_156b_2">
4041
<pruneDeadBranches>true</pruneDeadBranches>
4142
<daysToKeep>-1</daysToKeep>
4243
<numToKeep>-1</numToKeep>
44+
<abortBuilds>false</abortBuilds>
4345
</orphanedItemStrategy>
4446
<triggers>
45-
<com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger plugin="cloudbees-folder@6.15">
47+
<com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger plugin="cloudbees-folder@6.942.vb_43318a_156b_2">
4648
<spec>H H/4 * * *</spec>
4749
<interval>86400000</interval>
4850
</com.cloudbees.hudson.plugins.folder.computed.PeriodicFolderTrigger>
@@ -53,20 +55,20 @@
5355
<serverUrl>${SCMM_NAMESPACE_JOB_SERVER_URL}</serverUrl>
5456
<namespace>${SCMM_NAMESPACE_JOB_NAMESPACE}</namespace>
5557
<credentialsId>${SCMM_NAMESPACE_JOB_CREDENTIALS_ID}</credentialsId>
56-
<dependencyChecker class="com.cloudogu.scmmanager.scm.ScmManagerNavigator$$Lambda$267/0x0000000100a46c40"/>
58+
<dependencyChecker class="null"/>
5759
<traits>
58-
<com.cloudogu.scmmanager.scm.BranchDiscoveryTrait/>
5960
<com.cloudogu.scmmanager.scm.PullRequestDiscoveryTrait>
6061
<excludeBranchesWithPRs>false</excludeBranchesWithPRs>
6162
</com.cloudogu.scmmanager.scm.PullRequestDiscoveryTrait>
63+
<com.cloudogu.scmmanager.scm.ScmManagerBranchDiscoveryTrait/>
6264
</traits>
6365
<apiFactory>
6466
<credentialsLookup/>
6567
</apiFactory>
6668
</com.cloudogu.scmmanager.scm.ScmManagerNavigator>
6769
</navigators>
6870
<projectFactories>
69-
<org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProjectFactory plugin="workflow-multibranch@2.22">
71+
<org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProjectFactory plugin="workflow-multibranch@795.ve0cb_1f45ca_9a_">
7072
<scriptPath>Jenkinsfile</scriptPath>
7173
</org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProjectFactory>
7274
</projectFactories>

scripts/jenkins/init-jenkins.sh

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,6 @@ function configureJenkins() {
134134
# Since safeRestart can take time until it really restarts jenkins, we will sleep here before querying jenkins status.
135135
sleep 5
136136
waitForJenkins
137-
138-
if [[ $INSTALL_ARGOCD == true ]]; then
139-
createJob "${NAME_PREFIX}example-apps" "${SCMM_URL}" "${NAME_PREFIX}argocd" "scmm-user"
140-
fi
141137
}
142138

143139
initJenkins "$@"

scripts/jenkins/jenkins-REST-client.sh

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,6 @@ function curlJenkins() {
77
"$@"
88
}
99

10-
function createJob() {
11-
JOB_NAME=${1}
12-
13-
# shellcheck disable=SC2016
14-
# we don't want to expand these variables in single quotes
15-
JOB_CONFIG=$(env -i \
16-
SCMM_NAMESPACE_JOB_SERVER_URL="${2}" \
17-
SCMM_NAMESPACE_JOB_NAMESPACE="${3}" \
18-
SCMM_NAMESPACE_JOB_CREDENTIALS_ID="${4}" \
19-
envsubst '${SCMM_NAMESPACE_JOB_SERVER_URL},
20-
${SCMM_NAMESPACE_JOB_NAMESPACE},
21-
${SCMM_NAMESPACE_JOB_CREDENTIALS_ID}' \
22-
< scripts/jenkins/namespaceJobTemplate.xml)
23-
24-
printf 'Creating job %s ... ' "${JOB_NAME}"
25-
26-
# Don't add --fail here, because if the job already exists we get a return code of 400
27-
STATUS=$(curlJenkins -L -o /dev/null --write-out '%{http_code}' \
28-
-X POST "${JENKINS_URL}/createItem?name=${JOB_NAME}" \
29-
-H "Content-Type:text/xml" \
30-
--data "${JOB_CONFIG}" ) && EXIT_STATUS=$? || EXIT_STATUS=$?
31-
if [ $EXIT_STATUS != 0 ]
32-
then
33-
echo "Creating Job failed with exit code: curl: ${EXIT_STATUS}, HTTP Status: ${STATUS}"
34-
exit $EXIT_STATUS
35-
fi
36-
37-
printStatus "${STATUS}"
38-
}
39-
4010
function crumb() {
4111

4212
RESPONSE=$(curl -s --cookie-jar /tmp/cookies \

src/main/groovy/com/cloudogu/gitops/features/Jenkins.groovy

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,24 @@ class Jenkins extends Feature {
9999
prometheusConfigurator.enableAuthentication()
100100

101101
if (config.features['argocd']['active']) {
102+
103+
String jobName = "${config.application['namePrefix']}example-apps"
104+
def credentialId = "scmm-user"
105+
106+
jobManger.createJob(jobName,
107+
config.scmm['urlForJenkins'] as String,
108+
"${config.application['namePrefix']}argocd",
109+
credentialId)
110+
102111
jobManger.createCredential(
103-
"${config.application['namePrefix']}example-apps",
104-
"scmm-user",
112+
jobName,
113+
credentialId,
105114
"${config.application['namePrefix']}gitops",
106115
"${config.scmm['password']}",
107116
'credentials for accessing scm-manager')
108117

109118
jobManger.createCredential(
110-
"${config.application['namePrefix']}example-apps",
119+
jobName,
111120
"registry-user",
112121
"${config.registry['username']}",
113122
"${config.registry['password']}",
@@ -123,7 +132,7 @@ class Jenkins extends Feature {
123132

124133
}
125134
// Once everything is set up, start the jobs.
126-
jobManger.startJob('example-apps')
135+
jobManger.startJob(jobName)
127136
}
128137
}
129138
}

src/main/groovy/com/cloudogu/gitops/jenkins/JobManager.groovy

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package com.cloudogu.gitops.jenkins
22

3+
import com.cloudogu.gitops.utils.TemplatingEngine
34
import groovy.json.JsonOutput
5+
import groovy.util.logging.Slf4j
46
import jakarta.inject.Singleton
57
import okhttp3.FormBody
8+
import okhttp3.MediaType
9+
import okhttp3.RequestBody
610
import org.intellij.lang.annotations.Language
711

812
@Singleton
13+
@Slf4j
914
class JobManager {
1015
private ApiClient apiClient
1116

@@ -19,12 +24,12 @@ class JobManager {
1924
new FormBody.Builder()
2025
.add("json", JsonOutput.toJson([
2126
credentials: [
22-
"scope" : "GLOBAL",
23-
"id" : id,
24-
"username" : username,
25-
"password" : password,
26-
"description": description,
27-
"\$class" : "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl",
27+
scope : "GLOBAL",
28+
id : id,
29+
username : username,
30+
password : password,
31+
description: description,
32+
$class : "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl",
2833
]
2934
]))
3035
.build()
@@ -35,6 +40,39 @@ class JobManager {
3540
}
3641
}
3742

43+
/**
44+
* @return true, if created; false if job already exists and nothing was changed.
45+
*/
46+
boolean createJob(String name, String serverUrl, String jobNamespace, String credentialsId) {
47+
if (jobExists(name)) {
48+
log.warn("Job '${name}' already exists, ignoring.")
49+
return false
50+
} else {
51+
// Note for development: the XML representation of an existing job can be exporting by adding /config.xml to the URL
52+
String payloadXml = new TemplatingEngine().template(new File('jenkins/namespaceJobTemplate.xml.ftl'),
53+
[
54+
SCMM_NAMESPACE_JOB_SERVER_URL : serverUrl,
55+
SCMM_NAMESPACE_JOB_NAMESPACE : jobNamespace,
56+
SCMM_NAMESPACE_JOB_CREDENTIALS_ID: credentialsId
57+
])
58+
59+
RequestBody body = RequestBody.create(payloadXml, MediaType.get("text/xml"))
60+
61+
def response = apiClient.postRequestWithCrumb("createItem?name=$name", body)
62+
63+
if (response.code() != 200) {
64+
throw new RuntimeException("Could not create job '${name}'. StatusCode: ${response.code()}")
65+
}
66+
}
67+
return true
68+
}
69+
70+
boolean jobExists(String name) {
71+
def response= apiClient.postRequestWithCrumb("job/$name")
72+
73+
return response.code() == 200
74+
}
75+
3876
void deleteJob(String name) {
3977
if (name.contains("'")) {
4078
throw new RuntimeException('Job name cannot contain quotes.')

src/test/groovy/com/cloudogu/gitops/features/JenkinsTest.groovy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ class JenkinsTest {
114114
verify(jobManger).createCredential('my-prefix-example-apps', 'scmm-user',
115115
'my-prefix-gitops', 'scmm-pw', 'credentials for accessing scm-manager')
116116

117-
verify(jobManger).startJob('example-apps')
117+
verify(jobManger).startJob('my-prefix-example-apps')
118+
verify(jobManger).createJob('my-prefix-example-apps', 'http://scmm-scm-manager/scm',
119+
"my-prefix-argocd",'scmm-user')
118120

119121
verify(jobManger).createCredential('my-prefix-example-apps', 'registry-user',
120122
'reg-usr', 'reg-pw', 'credentials for accessing the docker-registry for writing images built on jenkins')

src/test/groovy/com/cloudogu/gitops/jenkins/JobManagerTest.groovy

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,92 @@ class JobManagerTest {
116116

117117
verify(client).runScript("print(Jenkins.instance.getItem('foo')?.delete())")
118118
}
119+
120+
@Test
121+
void 'checks existing Job'() {
122+
def server = new MockWebServer()
123+
try {
124+
server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
125+
server.enqueue(new MockResponse().setResponseCode(200))
126+
def jobManager = new JobManager(new ApiClient(server.url("jenkins").toString(), 'admin', 'admin', new OkHttpClient()))
127+
128+
def exists = jobManager.jobExists('the-jobname')
129+
130+
assertThat(exists).isEqualTo(true)
131+
assertThat(server.requestCount).isEqualTo(2)
132+
server.takeRequest() // crumb
133+
def request = server.takeRequest()
134+
assertThat(request.path).isEqualTo("/jenkins/job/the-jobname")
135+
} finally {
136+
server.shutdown()
137+
}
138+
}
139+
140+
@Test
141+
void 'checks non-existing Job'() {
142+
def server = new MockWebServer()
143+
try {
144+
server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
145+
server.enqueue(new MockResponse().setResponseCode(404))
146+
def jobManager = new JobManager(new ApiClient(server.url("jenkins").toString(), 'admin', 'admin', new OkHttpClient()))
147+
148+
def exists = jobManager.jobExists('the-jobname')
149+
150+
assertThat(exists).isEqualTo(false)
151+
assertThat(server.requestCount).isEqualTo(2)
152+
server.takeRequest() // crumb
153+
def request = server.takeRequest()
154+
assertThat(request.path).isEqualTo("/jenkins/job/the-jobname")
155+
} finally {
156+
server.shutdown()
157+
}
158+
}
159+
160+
@Test
161+
void 'creates Job'() {
162+
def server = new MockWebServer()
163+
try {
164+
server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
165+
server.enqueue(new MockResponse().setResponseCode(404)) // jobExists
166+
server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
167+
server.enqueue(new MockResponse().setResponseCode(200))
168+
def jobManager = new JobManager(new ApiClient(server.url("jenkins").toString(), 'admin', 'admin', new OkHttpClient()))
169+
170+
def created = jobManager.createJob('the-jobname', 'http://scm', 'ns', 'creds')
171+
172+
assertThat(created).isEqualTo(true)
173+
assertThat(server.requestCount).isEqualTo(4)
174+
server.takeRequest() // crumb
175+
server.takeRequest() // exists
176+
server.takeRequest() // crumb
177+
def request = server.takeRequest()
178+
assertThat(request.path).isEqualTo("/jenkins/createItem?name=the-jobname")
179+
180+
def body = request.body.readUtf8()
181+
assertThat(body).contains('<serverUrl>http://scm</serverUrl>')
182+
assertThat(body).contains('<namespace>ns</namespace>')
183+
assertThat(body).contains('<credentialsId>creds</credentialsId>')
184+
} finally {
185+
server.shutdown()
186+
}
187+
}
188+
189+
@Test
190+
void 'ignores existing Job'() {
191+
def server = new MockWebServer()
192+
try {
193+
server.enqueue(new MockResponse().setBody('{"crumb":"the-crumb"}'))
194+
server.enqueue(new MockResponse().setResponseCode(200)) // jobExists
195+
def jobManager = new JobManager(new ApiClient(server.url("jenkins").toString(), 'admin', 'admin', new OkHttpClient()))
196+
197+
def created = jobManager.createJob('the-jobname', 'http://scm', 'ns', 'creds')
198+
199+
assertThat(created).isEqualTo(false)
200+
assertThat(server.requestCount).isEqualTo(2)
201+
server.takeRequest() // crumb
202+
server.takeRequest() // exists
203+
} finally {
204+
server.shutdown()
205+
}
206+
}
119207
}

0 commit comments

Comments
 (0)