diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsAsyncClient.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsAsyncClient.java
index 57d5ecd29c..eec53e9615 100644
--- a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsAsyncClient.java
+++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsAsyncClient.java
@@ -18,9 +18,21 @@
*/
package org.jclouds.jenkins.v1;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.jenkins.v1.domain.Node;
import org.jclouds.jenkins.v1.features.ComputerAsyncClient;
import org.jclouds.jenkins.v1.features.JobAsyncClient;
+import org.jclouds.jenkins.v1.filters.BasicAuthenticationUnlessAnonymous;
import org.jclouds.rest.annotations.Delegate;
+import org.jclouds.rest.annotations.ExceptionParser;
+import org.jclouds.rest.annotations.RequestFilters;
+import org.jclouds.rest.functions.ReturnNullOnNotFoundOr404;
+
+import com.google.common.util.concurrent.ListenableFuture;
/**
* Provides asynchronous access to Jenkins via their REST API.
@@ -30,8 +42,18 @@ import org.jclouds.rest.annotations.Delegate;
* @see api doc
* @author Adrian Cole
*/
+@RequestFilters(BasicAuthenticationUnlessAnonymous.class)
public interface JenkinsAsyncClient {
-
+
+ /**
+ * @see JenkinsClient#getMaster
+ */
+ @GET
+ @Path("/api/json")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @ExceptionParser(ReturnNullOnNotFoundOr404.class)
+ ListenableFuture getMaster();
+
/**
* Provides asynchronous access to Computer features.
*/
diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsClient.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsClient.java
index 38bff221c0..93db464829 100644
--- a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsClient.java
+++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/JenkinsClient.java
@@ -21,6 +21,7 @@ package org.jclouds.jenkins.v1;
import java.util.concurrent.TimeUnit;
import org.jclouds.concurrent.Timeout;
+import org.jclouds.jenkins.v1.domain.Node;
import org.jclouds.jenkins.v1.features.ComputerClient;
import org.jclouds.jenkins.v1.features.JobClient;
import org.jclouds.rest.annotations.Delegate;
@@ -35,6 +36,10 @@ import org.jclouds.rest.annotations.Delegate;
*/
@Timeout(duration = 60, timeUnit = TimeUnit.SECONDS)
public interface JenkinsClient {
+ /**
+ * @return the master computer
+ */
+ Node getMaster();
/**
* Provides synchronous access to Computer features.
diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Job.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Job.java
new file mode 100644
index 0000000000..b003617fe0
--- /dev/null
+++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Job.java
@@ -0,0 +1,157 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.jenkins.v1.domain;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.URI;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+
+/**
+ * Minimal info about a Job
+ *
+ * @author Adrian Cole
+ */
+public class Job {
+
+ public static Builder> builder() {
+ return new ConcreteBuilder();
+ }
+
+ public Builder> toBuilder() {
+ return builder().fromJob(this);
+ }
+
+ private static class ConcreteBuilder extends Builder {
+ }
+
+ public static abstract class Builder> {
+ private String name;
+ private URI url;
+ private String color;
+
+ @SuppressWarnings("unchecked")
+ protected B self() {
+ return (B) this;
+ }
+
+ /**
+ * @see Job#getName()
+ */
+ public B name(String name) {
+ this.name = name;
+ return self();
+ }
+
+ /**
+ * @see Job#getUrl()
+ */
+ public B url(URI url) {
+ this.url = url;
+ return self();
+ }
+
+ /**
+ * @see Job#getColor()
+ */
+ public B color(String color) {
+ this.color = color;
+ return self();
+ }
+
+ public Job build() {
+ return new Job(this);
+ }
+
+ protected B fromJob(Job in) {
+ return name(in.getName()).color(in.getColor()).url(in.getUrl());
+ }
+ }
+
+ private final String name;
+ private final String color;
+ private final URI url;
+
+ protected Job(Builder> builder) {
+ this.name = checkNotNull(builder.name, "name");
+ this.color = checkNotNull(builder.color, "color");
+ this.url = checkNotNull(builder.url, "url");
+ }
+
+ /**
+ * name of the job
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ *
+ * color of the job
+ */
+ public String getColor() {
+ return color;
+ }
+
+ /**
+ *
+ * url of the job
+ */
+ public URI getUrl() {
+ return url;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Job that = Job.class.cast(o);
+ return equal(this.name, that.name) && equal(this.url, that.url)
+ && equal(this.color, that.color);
+ }
+
+ public boolean clone(Object o) {
+ if (this == o)
+ return false;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ Job that = Job.class.cast(o);
+ return equal(this.color, that.color);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(name, url, color);
+ }
+
+ @Override
+ public String toString() {
+ return string().toString();
+ }
+
+ protected ToStringHelper string() {
+ return Objects.toStringHelper("").add("name", name).add("url", url).add("color",
+ color);
+ }
+}
diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/JobDetails.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/JobDetails.java
new file mode 100644
index 0000000000..09519baae2
--- /dev/null
+++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/JobDetails.java
@@ -0,0 +1,62 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.jenkins.v1.domain;
+
+import com.google.common.base.Objects.ToStringHelper;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+public class JobDetails extends Job {
+
+ public static Builder> builder() {
+ return new ConcreteBuilder();
+ }
+
+ @Override
+ public Builder> toBuilder() {
+ return builder().fromJobDetails(this);
+ }
+
+ public static class Builder> extends Job.Builder {
+
+ @Override
+ public JobDetails build() {
+ return new JobDetails(this);
+ }
+
+ public B fromJobDetails(JobDetails in) {
+ return fromJob(in);
+ }
+ }
+
+ private static class ConcreteBuilder extends Builder {
+ }
+
+ protected JobDetails(Builder> builder) {
+ super(builder);
+ }
+
+ @Override
+ public ToStringHelper string() {
+ return super.string(); // .add("field", field);
+ }
+
+}
diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Node.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Node.java
new file mode 100644
index 0000000000..ab4f15cd6c
--- /dev/null
+++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/domain/Node.java
@@ -0,0 +1,147 @@
+package org.jclouds.jenkins.v1.domain;
+
+import static com.google.common.base.Objects.equal;
+import static com.google.common.base.Objects.toStringHelper;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.Set;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Objects.ToStringHelper;
+import com.google.common.collect.ImmutableSet;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * @author Adrian Cole
+ * @see api
+ * doc
+ */
+public class Node implements Comparable {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public Builder toBuilder() {
+ return builder().fromNodeMetadata(this);
+ }
+
+ public static class Builder {
+ protected String name;
+ protected String description;
+ protected Set jobs = ImmutableSet.of();
+
+ /**
+ * @see Node#getName()
+ */
+ public Builder name(String name) {
+ this.name = checkNotNull(name, "name");
+ return this;
+ }
+
+ /**
+ * @see Node#getDescription()
+ */
+ public Builder description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ /**
+ * @see Node#getJobs()
+ */
+ public Builder jobs(Job... jobs) {
+ return jobs(ImmutableSet.copyOf(checkNotNull(jobs, "jobs")));
+ }
+
+ /**
+ * @see Node#getJobs()
+ */
+ public Builder jobs(Set jobs) {
+ this.jobs = ImmutableSet.copyOf(checkNotNull(jobs, "jobs"));
+ return this;
+ }
+
+ public Node build() {
+ return new Node(name, description, jobs);
+ }
+
+ public Builder fromNodeMetadata(Node from) {
+ return name(from.getName()).description(from.getDescription()).jobs(from.getJobs());
+ }
+ }
+
+ @SerializedName("nodeName")
+ protected final String name;
+ @SerializedName("nodeDescription")
+ protected final String description;
+ protected final Set jobs;
+
+ public Node(String name, String description, Set jobs) {
+ this.name = checkNotNull(name, "name");
+ this.description = description;
+ this.jobs = ImmutableSet.copyOf(checkNotNull(jobs, "jobs"));
+ }
+
+ /**
+ *
+ * @return the name of the node
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ *
+ * @return the description of the node
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * @return the jobs on this node
+ */
+ public Set getJobs() {
+ return jobs;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
+ }
+ if (object instanceof Node) {
+ final Node other = Node.class.cast(object);
+ return equal(getName(), other.getName()) && equal(getDescription(), other.getDescription())
+ && equal(getJobs(), other.getJobs());
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(getName(), getDescription(), getJobs());
+ }
+
+ @Override
+ public String toString() {
+ return string().toString();
+ }
+
+ protected ToStringHelper string() {
+ return toStringHelper("").add("name", getName()).add("description", getDescription()).add("jobs", getJobs());
+ }
+
+ @Override
+ public int compareTo(Node that) {
+ if (that == null)
+ return 1;
+ if (this == that)
+ return 0;
+ return this.getName().compareTo(that.getName());
+ }
+
+}
diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerAsyncClient.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerAsyncClient.java
index 1e86e9b340..30953985a4 100644
--- a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerAsyncClient.java
+++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerAsyncClient.java
@@ -61,4 +61,5 @@ public interface ComputerAsyncClient {
@Consumes(MediaType.APPLICATION_JSON)
@ExceptionParser(ReturnNullOnNotFoundOr404.class)
ListenableFuture get(@PathParam("displayName") String displayName);
+
}
diff --git a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerClient.java b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerClient.java
index 120fba7015..25beb3fcf5 100644
--- a/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerClient.java
+++ b/labs/jenkins/src/main/java/org/jclouds/jenkins/v1/features/ComputerClient.java
@@ -45,4 +45,5 @@ public interface ComputerClient {
* @return computer or null if not found
*/
Computer get(String displayName);
+
}
diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsClientExpectTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsClientExpectTest.java
new file mode 100644
index 0000000000..77ae552d59
--- /dev/null
+++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsClientExpectTest.java
@@ -0,0 +1,58 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.jenkins.v1;
+
+import static org.testng.Assert.assertEquals;
+
+import java.net.URI;
+
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.jenkins.v1.internal.BaseJenkinsClientExpectTest;
+import org.jclouds.jenkins.v1.parse.ParseNodeTest;
+import org.testng.annotations.Test;
+
+import com.google.common.collect.ImmutableMultimap;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "JenkinsClientExpectTest")
+public class JenkinsClientExpectTest extends BaseJenkinsClientExpectTest {
+
+ public void testGetMasterWhenResponseIs2xx() {
+ HttpRequest getMaster = HttpRequest
+ .builder()
+ .method("GET")
+ .endpoint(URI.create("http://localhost:8080/api/json"))
+ .headers(
+ ImmutableMultimap. builder()
+ .put("Accept", "application/json")
+ .put("Authorization", "Basic aWRlbnRpdHk6Y3JlZGVudGlhbA==").build()).build();
+
+ HttpResponse getMasterResponse = HttpResponse.builder().statusCode(200)
+ .payload(payloadFromResource("/master.json")).build();
+
+ JenkinsClient clientWhenMasterExists = requestSendsResponse(getMaster, getMasterResponse);
+
+ assertEquals(clientWhenMasterExists.getMaster().toString(),
+ new ParseNodeTest().expected().toString());
+ }
+}
diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsClientLiveTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsClientLiveTest.java
new file mode 100644
index 0000000000..ae1b83c3b7
--- /dev/null
+++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/JenkinsClientLiveTest.java
@@ -0,0 +1,41 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.jenkins.v1;
+
+import static org.testng.Assert.assertNotNull;
+
+import org.jclouds.jenkins.v1.domain.Node;
+import org.jclouds.jenkins.v1.internal.BaseJenkinsClientLiveTest;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "live", testName = "JenkinsClientLiveTest")
+public class JenkinsClientLiveTest extends BaseJenkinsClientLiveTest {
+
+ public void testGetMaster(){
+ Node master = context.getApi().getMaster();
+ assertNotNull(master);
+ assertNotNull(master.getName());
+ assertNotNull(master.getJobs());
+ }
+
+}
\ No newline at end of file
diff --git a/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseNodeTest.java b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseNodeTest.java
new file mode 100644
index 0000000000..d2ec328d32
--- /dev/null
+++ b/labs/jenkins/src/test/java/org/jclouds/jenkins/v1/parse/ParseNodeTest.java
@@ -0,0 +1,55 @@
+/**
+ * Licensed to jclouds, Inc. (jclouds) under one or more
+ * contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. jclouds licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.jclouds.jenkins.v1.parse;
+
+import java.net.URI;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.core.MediaType;
+
+import org.jclouds.jenkins.v1.domain.Job;
+import org.jclouds.jenkins.v1.domain.Node;
+import org.jclouds.json.BaseItemParserTest;
+import org.testng.annotations.Test;
+
+/**
+ *
+ * @author Adrian Cole
+ */
+@Test(groups = "unit", testName = "ParseNodeTest")
+public class ParseNodeTest extends BaseItemParserTest {
+
+ @Override
+ public String resource() {
+ return "/master.json";
+ }
+
+ @Override
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Node expected() {
+ return Node.builder()
+ .name("")
+ .description("the master Jenkins node")
+ .jobs(Job.builder()
+ .name("ddd")
+ .url(URI.create("http://localhost:8080/job/ddd/"))
+ .color("grey").build())
+ .build();
+ }
+}
diff --git a/labs/jenkins/src/test/resources/job.json b/labs/jenkins/src/test/resources/job.json
new file mode 100644
index 0000000000..ebf43f35fb
--- /dev/null
+++ b/labs/jenkins/src/test/resources/job.json
@@ -0,0 +1,29 @@
+{
+ "actions": [],
+ "description": "",
+ "displayName": "ddd",
+ "displayNameOrNull": null,
+ "name": "ddd",
+ "url": "http://localhost:8080/job/ddd/",
+ "buildable": true,
+ "builds": [],
+ "color": "grey",
+ "firstBuild": null,
+ "healthReport": [],
+ "inQueue": false,
+ "keepDependencies": false,
+ "lastBuild": null,
+ "lastCompletedBuild": null,
+ "lastFailedBuild": null,
+ "lastStableBuild": null,
+ "lastSuccessfulBuild": null,
+ "lastUnstableBuild": null,
+ "lastUnsuccessfulBuild": null,
+ "nextBuildNumber": 1,
+ "property": [],
+ "queueItem": null,
+ "concurrentBuild": false,
+ "downstreamProjects": [],
+ "scm": {},
+ "upstreamProjects": []
+}
\ No newline at end of file
diff --git a/labs/jenkins/src/test/resources/master.json b/labs/jenkins/src/test/resources/master.json
new file mode 100644
index 0000000000..8e5c622b95
--- /dev/null
+++ b/labs/jenkins/src/test/resources/master.json
@@ -0,0 +1,26 @@
+{
+ "assignedLabels": [{}],
+ "mode": "NORMAL",
+ "nodeDescription": "the master Jenkins node",
+ "nodeName": "",
+ "numExecutors": 2,
+ "description": null,
+ "jobs": [{
+ "name": "ddd",
+ "url": "http://localhost:8080/job/ddd/",
+ "color": "grey"
+ }],
+ "overallLoad": {},
+ "primaryView": {
+ "name": "All",
+ "url": "http://localhost:8080/"
+ },
+ "quietingDown": false,
+ "slaveAgentPort": 0,
+ "useCrumbs": false,
+ "useSecurity": false,
+ "views": [{
+ "name": "All",
+ "url": "http://localhost:8080/"
+ }]
+}
\ No newline at end of file