Skip to content

Commit 59b71aa

Browse files
khmarbaisedellgreen
authored andcommitted
Fixed #290
o Added support for retrieving build status with jobname and build number directly avoiding intermediate server requests.
2 parents 6673444 + 061efa5 commit 59b71aa

File tree

7 files changed

+238
-0
lines changed

7 files changed

+238
-0
lines changed

ReleaseNotes.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22

33
## Release 0.3.8 (NOT RELEASED YET)
44

5+
6+
* [Fixed Issue 290][issue-290]
7+
8+
Added support for retrieving build status with jobname and build number
9+
directly avoiding intermediate server requests. Useful for other systems that
10+
regularly monitor Jenkins builds.
11+
12+
13+
* [JENKINS-46472](https://issues.jenkins-ci.org/browse/JENKINS-46472)
14+
15+
Added ability to modify offline cause for offline computers.
16+
17+
```java
18+
ComputerWithDetails computer = ...
19+
if (!computer.getOffline()){
20+
computer.toggleOffline();
21+
computer.changeOfflineCause("Scheduled for termination");
22+
}
23+
```
24+
525
* [JENKINS-46445](https://issues.jenkins-ci.org/browse/JENKINS-46445)
626

727
Add support for both client TLS and basic authentication.

jenkins-client/src/main/java/com/offbytwo/jenkins/JenkinsServer.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.offbytwo.jenkins.client.util.UrlUtils;
3030
import com.offbytwo.jenkins.helper.JenkinsVersion;
3131
import com.offbytwo.jenkins.model.Build;
32+
import com.offbytwo.jenkins.model.BuildWithDetails;
3233
import com.offbytwo.jenkins.model.Computer;
3334
import com.offbytwo.jenkins.model.ComputerSet;
3435
import com.offbytwo.jenkins.model.FolderJob;
@@ -882,6 +883,52 @@ public void renameJob(FolderJob folder, String oldJobName, String newJobName, Bo
882883
+ "/doRename?newName=" + EncodingUtils.encodeParam(newJobName),
883884
crumbFlag);
884885
}
886+
887+
888+
889+
890+
/**
891+
* Get build details for a specific job and build number directly.
892+
* This method makes a single call to the server
893+
* @param jobName the name of the job
894+
* @param buildNum the build number
895+
* @param treeProps any tree property query values
896+
* @return details of build, null if not present
897+
* @throws IOException In case of a failure.
898+
* @see #getBuildDetails(com.offbytwo.jenkins.model.FolderJob, java.lang.String, int)
899+
*/
900+
public BuildWithDetails getBuildDetails(final String jobName, final int buildNum,
901+
final String... treeProps)
902+
throws IOException {
903+
return getBuildDetails(null, UrlUtils.toFullJobPath(jobName), buildNum, treeProps);
904+
}
905+
906+
907+
908+
/**
909+
* Get build details for a specific job and build number directly.
910+
* This method makes a single call to the server
911+
* @param folder the folder where the given job is located if any
912+
* @param jobName the name of the job
913+
* @param buildNum the build number
914+
* @param treeProps any tree property query values
915+
* @return details of build, null if not present
916+
* @throws IOException In case of a failure.
917+
*/
918+
public BuildWithDetails getBuildDetails(final FolderJob folder, final String jobName,
919+
final int buildNum, final String... treeProps) throws IOException {
920+
try {
921+
final String path = UrlUtils.toBuildBaseUrl(folder, jobName, buildNum, treeProps);
922+
final BuildWithDetails details = client.get(path, BuildWithDetails.class);
923+
details.setClient(client);
924+
return details;
925+
} catch (final HttpResponseException e) {
926+
LOGGER.debug("getBuildDetails(jobName={}, buildNum={}, treeProps={}) status={}",
927+
jobName, buildNum, treeProps, e.getStatusCode());
928+
if (e.getStatusCode() == HttpStatus.SC_NOT_FOUND) return null;
929+
throw e;
930+
}
931+
}
885932

886933

887934
}

jenkins-client/src/main/java/com/offbytwo/jenkins/client/util/UrlUtils.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,29 @@ public static URI toNoApiUri(final URI uri, final String context, final String p
153153

154154

155155

156+
/**
157+
* Helper to create the base url for a build, with or without a given folder
158+
* @param folder the folder or {@code null}
159+
* @param jobName the name of the job.
160+
* @param buildNum the build number of interest
161+
* @param treeProps any tree property query values
162+
* @return converted base url.
163+
*/
164+
public static String toBuildBaseUrl(final FolderJob folder, final String jobName,
165+
final int buildNum, final String... treeProps) {
166+
final String path = UrlUtils.toJobBaseUrl(folder, jobName);
167+
final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE);
168+
sb.append(path);
169+
if (!path.endsWith("/")) sb.append('/');
170+
sb.append(Integer.toString(buildNum));
171+
if (treeProps != null && treeProps.length > 0) {
172+
sb.append("?tree=");
173+
for(int i=0; i<treeProps.length; i++) {
174+
if (i>0) sb.append("%2C"); //comma
175+
sb.append(EncodingUtils.encodeParam(treeProps[i]));
176+
}
177+
}
178+
return sb.toString();
179+
}
180+
156181
}

jenkins-client/src/main/java/com/offbytwo/jenkins/model/ComputerWithDetails.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,23 @@ public void toggleOffline() throws IOException {
9797
toggleOffline( false );
9898
}
9999

100+
public void changeOfflineCause(String cause, boolean crumbFlag) throws IOException {
101+
String name;
102+
if ("master".equals(displayName)) {
103+
name = "(master)";
104+
} else {
105+
name = UrlEscapers.urlPathSegmentEscaper().escape(displayName);
106+
}
107+
108+
Map<String, String> data = new HashMap<String, String>();
109+
data.put( "offlineMessage", cause );
110+
client.post_form("/computer/" + name + "/changeOfflineCause?", data, crumbFlag);
111+
}
112+
113+
public void changeOfflineCause(String cause) throws IOException {
114+
changeOfflineCause(cause, false);
115+
}
116+
100117
public Boolean getManualLaunchAllowed() {
101118
return manualLaunchAllowed;
102119
}

jenkins-client/src/test/java/com/offbytwo/jenkins/JenkinsServerTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,15 @@
2727

2828
import com.google.common.base.Optional;
2929
import com.offbytwo.jenkins.client.JenkinsHttpClient;
30+
import com.offbytwo.jenkins.model.BuildWithDetails;
3031
import com.offbytwo.jenkins.model.FolderJob;
3132
import com.offbytwo.jenkins.model.Job;
3233
import com.offbytwo.jenkins.model.JobWithDetails;
3334
import com.offbytwo.jenkins.model.MainView;
3435
import com.offbytwo.jenkins.model.View;
36+
import org.apache.http.HttpStatus;
37+
import org.apache.http.client.HttpResponseException;
38+
import static org.junit.Assert.assertNull;
3539

3640
import static org.mockito.Mockito.mock;
3741
import static org.mockito.Mockito.times;
@@ -328,6 +332,57 @@ public void getVersionShouldNotFailWithNPE()
328332

329333
}
330334

335+
336+
@Test
337+
public void testGetBuildDetails_NotFound() throws IOException {
338+
final HttpResponseException ex = new HttpResponseException(HttpStatus.SC_NOT_FOUND,"");
339+
given(client.get(anyString(), eq(BuildWithDetails.class))).willThrow(ex);
340+
assertNull(server.getBuildDetails("someJob", 1));
341+
}
342+
343+
@Test(expected=HttpResponseException.class)
344+
public void testGetBuildDetails_ResponseException() throws IOException {
345+
final HttpResponseException ex = new HttpResponseException(HttpStatus.SC_BAD_REQUEST,"");
346+
given(client.get(anyString(), eq(BuildWithDetails.class))).willThrow(ex);
347+
server.getBuildDetails("someJob", 1);
348+
}
349+
350+
351+
@Test
352+
public void testGetBuildDetails_WithFolderJob() throws Exception {
353+
final String expectedPath = "http://localhost/jobs/someFolder/job/someJob/1";
354+
final FolderJob folderJob = new FolderJob("someFolder", "http://localhost/jobs/someFolder/");
355+
final BuildWithDetails expected = new BuildWithDetails();
356+
given(client.get(eq(expectedPath), eq(BuildWithDetails.class))).willReturn(expected);
357+
final BuildWithDetails details = server.getBuildDetails(folderJob, "someJob", 1);
358+
assertTrue(details == expected);
359+
}
360+
361+
362+
363+
364+
@Test
365+
public void testGetBuildDetails_WithOutFolderJob() throws Exception {
366+
final String expectedPath = "/job/someJob/1";
367+
final BuildWithDetails expected = new BuildWithDetails();
368+
given(client.get(eq(expectedPath), eq(BuildWithDetails.class))).willReturn(expected);
369+
final BuildWithDetails details = server.getBuildDetails("someJob", 1);
370+
assertTrue(details == expected);
371+
}
372+
373+
@Test
374+
public void testGetBuildDetails_WithTreeProperties() throws Exception {
375+
final String expectedPath = "/job/someJob/1?tree=building%2Cculprits%5BfullName%5D";
376+
final BuildWithDetails expected = new BuildWithDetails();
377+
given(client.get(eq(expectedPath), eq(BuildWithDetails.class))).willReturn(expected);
378+
final String[] treeProps = {"building", "culprits[fullName]"};
379+
final BuildWithDetails details = server.getBuildDetails("someJob", 1, treeProps);
380+
assertTrue(details == expected);
381+
}
382+
383+
384+
385+
331386
private void shouldGetFolderJobs(String... jobNames) throws IOException {
332387
// given
333388
String path = "http://localhost/jobs/someFolder/";

jenkins-client/src/test/java/com/offbytwo/jenkins/client/util/UrlUtilsTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,46 @@ public void testToNoQueryUri_NullContext() throws Exception {
321321
UrlUtils.toNoApiUri(new URI("http://localhost/jenkins"), null, "job/ajob");
322322
}
323323

324+
325+
326+
@Test
327+
public void testToBuildBaseUrl_WithFolder_WithTreeProperties() {
328+
final String fpath = "http://localhost/jobs/someFolder/";
329+
final FolderJob folderJob = new FolderJob("someFolder", fpath);
330+
final String expected = "http://localhost/jobs/someFolder/job/someJob/1234?tree=building%2Cculprits%5BfullName%5D";
331+
final String[] treeProps = {"building", "culprits[fullName]"};
332+
assertEquals(expected, UrlUtils.toBuildBaseUrl(folderJob, "someJob", 1234, treeProps));
333+
}
334+
335+
336+
@Test
337+
public void testToBuildBaseUrl_NoTrailingFolderSlash() {
338+
final String fpath = "http://localhost/jobs/someFolder";
339+
final FolderJob folderJob = new FolderJob("someFolder", fpath);
340+
final String expected = "http://localhost/jobs/someFolder/job/someJob/1234";
341+
assertEquals(expected, UrlUtils.toBuildBaseUrl(folderJob, "someJob", 1234));
342+
}
343+
344+
345+
@Test
346+
public void testToBuildBaseUrl_NullFolderJob() {
347+
assertEquals("/job/someJob/1234", UrlUtils.toBuildBaseUrl(null, "someJob", 1234));
348+
}
349+
350+
351+
@Test
352+
public void testToBuildBaseUrl_EmptyJobName() {
353+
final String fpath = "http://localhost/jobs/someFolder";
354+
final FolderJob folderJob = new FolderJob("someFolder", fpath);
355+
final String expected = "http://localhost/jobs/someFolder/job/1234";
356+
assertEquals(expected, UrlUtils.toBuildBaseUrl(folderJob, "", 1234));
357+
}
358+
359+
@Test(expected = NullPointerException.class)
360+
public void testToBuildBaseUrl_NullJobName() {
361+
final String fpath = "http://localhost/jobs/someFolder";
362+
final FolderJob folderJob = new FolderJob("someFolder", fpath);
363+
UrlUtils.toBuildBaseUrl(folderJob, null, 1234);
364+
}
365+
324366
}

jenkins-client/src/test/java/com/offbytwo/jenkins/integration/BuildWithDetailsIT.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
import com.offbytwo.jenkins.model.JobWithDetails;
1919

2020
import hudson.model.Cause;
21+
import hudson.model.FreeStyleBuild;
2122
import hudson.model.FreeStyleProject;
2223
import hudson.model.ParametersAction;
2324
import hudson.model.StringParameterValue;
25+
import static org.junit.Assert.assertFalse;
26+
import static org.junit.Assert.assertNull;
2427

2528
public class BuildWithDetailsIT {
2629

@@ -55,5 +58,34 @@ public void checkCauses() throws Exception {
5558

5659
assertThat(build.getBuiltOn()).isEmpty();
5760
}
61+
62+
63+
64+
@Test
65+
public void testGetBuildDetails_FullDetails() throws Exception {
66+
final FreeStyleProject proj = jenkinsRule.createFreeStyleProject();
67+
final FreeStyleBuild build = proj.scheduleBuild2(0, new Cause.UserIdCause()).get();
68+
final BuildWithDetails details = server.getBuildDetails(proj.getName(), build.number);
69+
assertEquals(BuildResult.SUCCESS, details.getResult());
70+
assertNotNull(details.getFullDisplayName());
71+
assertNotNull(details.getActions());
72+
assertNotNull(details.getUrl());
73+
}
74+
75+
76+
@Test
77+
public void testGetBuildDetails_SomeDetails() throws Exception {
78+
final FreeStyleProject proj = jenkinsRule.createFreeStyleProject();
79+
final FreeStyleBuild build = proj.scheduleBuild2(0, new Cause.UserIdCause()).get();
80+
final String[] treeProps = {"building", "result", "actions[causes]"};
81+
BuildWithDetails details = server.getBuildDetails(proj.getName(), build.number, treeProps);
82+
assertEquals(BuildResult.SUCCESS, details.getResult());
83+
assertFalse(details.isBuilding());
84+
assertNull(details.getFullDisplayName());
85+
assertNotNull(details.getActions());
86+
assertNull(details.getUrl());
87+
}
88+
89+
5890

5991
}

0 commit comments

Comments
 (0)