Skip to content

Commit 6852174

Browse files
author
Denis
committed
Basic Jenkins tasks and tests implemented
1 parent 348d053 commit 6852174

File tree

16 files changed

+596
-105
lines changed

16 files changed

+596
-105
lines changed

README.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,54 @@
1-
# pk-jenkins
1+
[![Release](https://jitpack.io/v/ProjectKaiser/pk-jenkins.svg)](https://jitpack.io/#ProjectKaiser/pk-jenkins)
2+
3+
# Overview
4+
Pk-jenkins is a tiny framework used to manage basic Jenkins tasks:
5+
- Jobs copy, create, read, update, delete
6+
- Run and read builds
7+
8+
# Terms
9+
- Job
10+
- Alias of Project in Jenkins terms
11+
- Job Detailed
12+
- An user-level Job description: name, builds list etc
13+
- Job Config Xml
14+
- A Jenkins-level Job description: schtdule, used plugins config etc
15+
- Queue Item
16+
- A build queue element. Represents a separate build for a task: color, state(running, finished, stuck etc) etc
17+
18+
# Using pk-jenkins Api
19+
- Create IJenkinsApi implementation class providing Jenkins server url, username and password
20+
```java
21+
IJenkinsApi jenkins = new JenkinsApi("http://localhost:8080", "user", "password");
22+
```
23+
- `void createJob(String jobName, String jobConfigXML)`
24+
- Creates new job named `jobName` with provided job config xml. Use `getJobConfigXml()` to obtain one from an existing Job
25+
- `JobDetailed getJobDetailed(String jobName) throws EPKJNotFound`
26+
- Returns Job Detailed object
27+
- `QueueItem getBuild(Long buildId) throws EPKJNotFound`
28+
- Returns Queue item object
29+
- `void updateJobConfigXml(String jobName, String configXml) throws EPKJNotFound`
30+
- Updates Job Config Xml. Xml manipulating is made by caller side, i.e.: `getJobConfigXml()`, then add\remove\change Xml elements, then `updateJobCOnfigXml()`
31+
- `void copyJob(String srcName, String dstName) throws EPKJNotFound, EPKJExists`
32+
- Clones an existing Job
33+
- `List<String> getJobsList()`
34+
- Returns list of all jobs on the server
35+
- `String getJobConfigXml(String jobName) throws EPKJNotFound`
36+
- Returns Job Config Xml as a string
37+
- `void deleteJob(String jobName) throws EPKJNotFound`
38+
- Deletes job
39+
40+
#Functional testing
41+
A working Jenkins server is required to run functional tests.
42+
Also following environment vars or JVM vars must be defined:
43+
- PK_TEST_JENKINS_URL
44+
- URL to the Jenkins server used for tests
45+
- TEST_JENKINS_USER
46+
- Jenkins username used for run tests
47+
- PK_TEST_JENKINS_PASS
48+
- Password of Jenkins user used for run tests
49+
To run functional tests just execute JenkinsApiTest class as JUnit test. All jobs created during testing are deleted after automatically
50+
51+
52+
53+
54+

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ repositories {
1616
dependencies {
1717
testCompile 'junit:junit:4.12'
1818
testCompile 'org.mockito:mockito-core:2.0.62-beta'
19+
testCompile 'xmlunit:xmlunit:1.6'
1920

2021
compile 'commons-io:commons-io:2.4'
2122
compile 'com.google.code.gson:gson:2.8.0'
2223
compile 'org.apache.httpcomponents:httpclient:4.5.2'
24+
compile 'commons-httpclient:commons-httpclient:3.1'
25+
2326
}
2427

2528
task sourcesJar(type: Jar, dependsOn: classes) {

src/main/java/com/projectkaiser/scm/jenkins/api/IJenkinsApi.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,29 @@
22

33
import java.util.List;
44

5+
import com.projectkaiser.scm.jenkins.api.exceptions.EPKJExists;
6+
import com.projectkaiser.scm.jenkins.api.exceptions.EPKJNotFound;
57
import com.projectkaiser.scm.jenkins.data.JobDetailed;
8+
import com.projectkaiser.scm.jenkins.data.QueueItem;
69

710
public interface IJenkinsApi {
811

9-
void createJob(String jobName, String jobConfigXML);
12+
void createJob(String jobName, String jobConfigXML) throws EPKJExists;
1013

11-
JobDetailed getJobDetailed(String jobName);
14+
JobDetailed getJobDetailed(String jobName) throws EPKJNotFound;
1215

13-
void runJob(String jobName);
16+
Long enqueueBuild(String jobName) throws EPKJNotFound;
17+
18+
QueueItem getBuild(Long buildId) throws EPKJNotFound;
1419

15-
void updateJobConfigXml(String jobName, String jobConfigXML);
20+
void updateJobConfigXml(String jobName, String configXml) throws EPKJNotFound;
1621

17-
void copyJob(String srcName, String dstName);
22+
void copyJob(String srcName, String dstName) throws EPKJNotFound, EPKJExists;
1823

1924
List<String> getJobsList();
2025

21-
String getJobConfigXml(String jobName);
26+
String getJobConfigXml(String jobName) throws EPKJNotFound;
27+
28+
void deleteJob(String jobName) throws EPKJNotFound;
2229

2330
}

src/main/java/com/projectkaiser/scm/jenkins/api/JenkinsApi.java

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,20 @@
88
import java.util.List;
99
import java.util.Map;
1010

11+
import org.apache.http.Header;
12+
import org.apache.http.HttpResponse;
13+
1114
import com.google.gson.Gson;
1215
import com.google.gson.GsonBuilder;
1316
import com.google.gson.reflect.TypeToken;
17+
import com.projectkaiser.scm.jenkins.api.exceptions.EPKJExists;
18+
import com.projectkaiser.scm.jenkins.api.exceptions.EPKJNotFound;
19+
import com.projectkaiser.scm.jenkins.api.exceptions.EPKJenkinsException;
1420
import com.projectkaiser.scm.jenkins.api.facade.IJenkinsApiFacade;
1521
import com.projectkaiser.scm.jenkins.api.facade.JenkinsApiHttpFacade;
1622
import com.projectkaiser.scm.jenkins.data.JobDetailed;
17-
18-
/*
19-
http://localhost:8080/hudson/job/toolkit/api/json
20-
http://gmpxp:8080/hudson/api/json?tree=jobs[name]
21-
http://localhost:8080/hudson/job/toolkit-2/config.xml
22-
*/
23+
import com.projectkaiser.scm.jenkins.data.JobListElement;
24+
import com.projectkaiser.scm.jenkins.data.QueueItem;
2325

2426
public class JenkinsApi implements IJenkinsApi {
2527

@@ -30,13 +32,28 @@ public JenkinsApi(String baseAddress, String user, String password) {
3032
}
3133

3234
@Override
33-
public void runJob(String jobName) {
35+
public Long enqueueBuild(String jobName) throws EPKJNotFound {
3436
String url = "job/" + encodeUrl(jobName) + "/build";
35-
facade.getResponsePOST(url, null);
37+
HttpResponse resp = facade.getResponsePOST(url, null);
38+
Header[] headers = resp.getHeaders("Location");
39+
if (headers == null || headers.length == 0) {
40+
throw new EPKJenkinsException("Failed to obtain Location header from response");
41+
}
42+
String[] strs = headers[0].getValue().split("/");
43+
return Long.parseLong(strs[strs.length - 1]);
44+
}
45+
46+
@Override
47+
public QueueItem getBuild(Long buildId) throws EPKJNotFound {
48+
String url = String.format("queue/item/%d/api/json?pretty=true", buildId);
49+
String queueItemJson = facade.getResponseContentGET(url);
50+
Gson gson = new GsonBuilder().setPrettyPrinting().create();
51+
QueueItem queueItem = gson.fromJson(queueItemJson, QueueItem.class);
52+
return queueItem;
3653
}
3754

3855
@Override
39-
public void copyJob(String srcName, String dstName) {
56+
public void copyJob(String srcName, String dstName) throws EPKJNotFound, EPKJExists {
4057
String url = "createItem";
4158
Map<String, String> q = new HashMap<String, String>();
4259
q.put("name", dstName);
@@ -47,17 +64,17 @@ public void copyJob(String srcName, String dstName) {
4764
}
4865

4966
@Override
50-
public void createJob(String jobName, String jobConfigXML) {
67+
public void createJob(String jobName, String jobConfigXML) throws EPKJExists {
5168
String url = "createItem";
5269
Map<String, String> q = new HashMap<String, String>();
5370
q.put("name", jobName);
5471
url = appendQuery(url, q);
55-
facade.getResponsePOST(url, null);
72+
facade.getResponsePOST(url, jobConfigXML);
5673
}
5774

5875
private static String encodeUrl(String str) {
5976
try {
60-
return URLEncoder.encode(str, "UTF-8");
77+
return URLEncoder.encode(str, "UTF-8").replace("+", "%20");
6178
} catch (UnsupportedEncodingException e) {
6279
throw new RuntimeException(e);
6380
}
@@ -81,33 +98,46 @@ public List<String> getJobsList() {
8198
Map<String, String> q = new HashMap<String, String>();
8299
q.put("tree", "jobs[name]");
83100
url = appendQuery(url, q);
84-
String json = facade.getResponseGET(url);
101+
String json = facade.getResponseContentGET(url);
85102
Gson gson = new GsonBuilder().create();
86-
Type type = new TypeToken<Map<String, Map<String, String>>>() {
103+
Type type = new TypeToken<Map<String, List<JobListElement>>>() {
87104
}.getType();
88105

89-
Map<String, Map<String, String>> res = gson.fromJson(json, type);
90-
return new ArrayList(res.get("jobs").values());
106+
Map<String, List<JobListElement>> jobsMap = gson.fromJson(json, type);
107+
List<JobListElement> jobs = jobsMap.get("jobs");
108+
List<String> res = new ArrayList<>();
109+
for (JobListElement job : jobs) {
110+
res.add(job.getName());
111+
}
112+
return res;
91113
}
92114

93115
@Override
94-
public String getJobConfigXml(String jobName) {
116+
public String getJobConfigXml(String jobName) throws EPKJNotFound {
95117
String url = "job/" + encodeUrl(jobName) + "/config.xml";
96-
return facade.getResponseGET(url);
118+
return facade.getResponseContentGET(url);
97119
}
98120

99121
@Override
100-
public void updateJobConfigXml(String jobName, String jobConfigXML) {
122+
public void updateJobConfigXml(String jobName, String configXml) throws EPKJNotFound {
101123
String url = "job/" + encodeUrl(jobName) + "/config.xml";
102-
facade.getResponsePOST(url, jobConfigXML);
124+
facade.getResponsePOST(url, configXml);
103125
}
104126

105127
@Override
106-
public JobDetailed getJobDetailed(String jobName) {
128+
public JobDetailed getJobDetailed(String jobName) throws EPKJNotFound {
107129
String url = "job/" + encodeUrl(jobName) + "/api/json?pretty=true";
108-
String json = facade.getResponseGET(url);
130+
String json = facade.getResponseContentGET(url);
109131
Gson gson = new GsonBuilder().setPrettyPrinting().create();
110132
JobDetailed job = gson.fromJson(json, JobDetailed.class);
111133
return job;
112134
}
135+
136+
@Override
137+
public void deleteJob(String jobName) throws EPKJNotFound {
138+
String url = "job/" + encodeUrl(jobName) + "/doDelete";
139+
facade.getResponsePOST(url, null);
140+
}
141+
142+
113143
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.projectkaiser.scm.jenkins.api.exceptions;
2+
3+
public class EPKJExists extends EPKJenkinsServerException {
4+
5+
public EPKJExists(Integer code, String errorMessage) {
6+
super(code, errorMessage);
7+
}
8+
9+
private static final long serialVersionUID = 7637144484632267911L;
10+
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.projectkaiser.scm.jenkins.api.exceptions;
2+
3+
public class EPKJNotFound extends EPKJenkinsServerException {
4+
5+
public EPKJNotFound(Integer code, String errorMes) {
6+
super(code, errorMes);
7+
}
8+
9+
private static final long serialVersionUID = 2478559349028227084L;
10+
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.projectkaiser.scm.jenkins.api.exceptions;
2+
3+
public class EPKJenkinsException extends RuntimeException {
4+
5+
public EPKJenkinsException(String string) {
6+
super(string);
7+
}
8+
9+
private static final long serialVersionUID = 5415122631779829397L;
10+
11+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.projectkaiser.scm.jenkins.api.exceptions;
2+
3+
import org.apache.commons.httpclient.HttpStatus;
4+
5+
public class EPKJenkinsServerException extends EPKJenkinsException {
6+
7+
private static final long serialVersionUID = 8612747334003389372L;
8+
private String errorMessage;
9+
private Integer code;
10+
11+
public String getErrorMessage() {
12+
return errorMessage;
13+
}
14+
15+
public Integer getCode() {
16+
return code;
17+
}
18+
19+
public EPKJenkinsServerException(Integer code, String errorMessage) {
20+
super(HttpStatus.getStatusText(code));
21+
this.code = code;
22+
this.errorMessage = errorMessage;
23+
}
24+
25+
@Override
26+
public String toString() {
27+
return String.format("%s (%d: %s)", errorMessage, code, super.getMessage());
28+
}
29+
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.projectkaiser.scm.jenkins.api.facade;
22

3+
import org.apache.http.HttpResponse;
4+
35
public interface IJenkinsApiFacade {
4-
String getResponseGET(String url);
6+
HttpResponse getResponseGET(String url);
7+
8+
String getResponseContentGET(String url);
59

6-
String getResponsePOST(String url, String entity);
10+
HttpResponse getResponsePOST(String url, String entity);
711
}

0 commit comments

Comments
 (0)