diff --git a/src/main/java/com/offbytwo/jenkins/JenkinsServer.java b/src/main/java/com/offbytwo/jenkins/JenkinsServer.java index fe0b5747..5a52a265 100644 --- a/src/main/java/com/offbytwo/jenkins/JenkinsServer.java +++ b/src/main/java/com/offbytwo/jenkins/JenkinsServer.java @@ -6,13 +6,28 @@ package com.offbytwo.jenkins; +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +import javax.xml.bind.JAXBException; + +import org.apache.http.client.HttpResponseException; +import org.apache.http.entity.ContentType; +import org.assertj.core.util.Lists; +import org.dom4j.DocumentException; + import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.offbytwo.jenkins.client.JenkinsHttpClient; import com.offbytwo.jenkins.client.util.EncodingUtils; import com.offbytwo.jenkins.model.Build; import com.offbytwo.jenkins.model.Computer; import com.offbytwo.jenkins.model.ComputerSet; +import com.offbytwo.jenkins.model.FolderJob; import com.offbytwo.jenkins.model.Job; import com.offbytwo.jenkins.model.JobConfiguration; import com.offbytwo.jenkins.model.JobWithDetails; @@ -23,17 +38,6 @@ import com.offbytwo.jenkins.model.QueueReference; import com.offbytwo.jenkins.model.View; -import org.apache.http.client.HttpResponseException; -import org.apache.http.entity.ContentType; -import org.dom4j.DocumentException; - -import javax.xml.bind.JAXBException; - -import java.io.IOException; -import java.net.URI; -import java.util.List; -import java.util.Map; - /** * The main starting point for interacting with a Jenkins server. */ @@ -189,6 +193,23 @@ public MavenJobWithDetails getMavenJob(String jobName) throws IOException { } } + public Optional getFolderJob(Job job) throws IOException { + try { + FolderJob folder = client.get(job.getUrl(), FolderJob.class); + if (!folder.isFolder()) { + return Optional.absent(); + } + folder.setClient(client); + + return Optional.of(folder); + } catch (HttpResponseException e) { + if (e.getStatusCode() == 404) { + return null; + } + throw e; + } + } + /** * Create a job on the server using the provided xml * @@ -203,6 +224,30 @@ public void createJob(String jobName, String jobXml, Boolean crumbFlag) throws I client.post_xml("/createItem?name=" + EncodingUtils.encodeParam(jobName), jobXml, crumbFlag); } + /** + * Create a folder on the server (in the root) + * + * @throws IOException + */ + public void createFolder(String jobName) throws IOException { + createFolder(jobName, false); + } + + /** + * Create a folder on the server (in the root) + * + * @throws IOException + */ + public void createFolder(String jobName, Boolean crumbFlag) throws IOException { + // https://gist.github.com/stuart-warren/7786892 was slightly helpful here + ImmutableMap params = ImmutableMap.of( + "mode", "com.cloudbees.hudson.plugins.folder.Folder", + "name", EncodingUtils.encodeParam(jobName), + "from", "", + "Submit", "OK"); + client.post_form("/createItem?", params, crumbFlag); + } + /** * Get the xml description of an existing job * diff --git a/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java b/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java index 31e531ad..138d1d4d 100644 --- a/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java +++ b/src/main/java/com/offbytwo/jenkins/client/JenkinsHttpClient.java @@ -6,19 +6,21 @@ package com.offbytwo.jenkins.client; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.offbytwo.jenkins.client.util.RequestReleasingInputStream; -//import com.offbytwo.jenkins.client.util.HttpResponseContentExtractor; -import com.offbytwo.jenkins.client.validator.HttpResponseValidator; -import com.offbytwo.jenkins.model.BaseModel; -import com.offbytwo.jenkins.model.Crumb; -import com.offbytwo.jenkins.model.ExtractHeader; +import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; +import static org.apache.commons.lang.StringUtils.isNotBlank; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; @@ -28,20 +30,25 @@ import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicHeader; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.HttpParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.util.EntityUtils; +import org.jsoup.helper.StringUtil; +import org.mortbay.util.ajax.JSON; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; - -import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; -import static org.apache.commons.lang.StringUtils.isNotBlank; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +import com.google.common.io.ByteStreams; +import com.offbytwo.jenkins.client.util.EncodingUtils; +import com.offbytwo.jenkins.client.util.RequestReleasingInputStream; +//import com.offbytwo.jenkins.client.util.HttpResponseContentExtractor; +import com.offbytwo.jenkins.client.validator.HttpResponseValidator; +import com.offbytwo.jenkins.model.BaseModel; +import com.offbytwo.jenkins.model.Crumb; +import com.offbytwo.jenkins.model.ExtractHeader; public class JenkinsHttpClient { @@ -231,6 +238,47 @@ public R post(String path, D data, Class cls, boolea } } + /** + * Perform a POST request using form url encoding + * + * @param path path to request, can be relative or absolute + * @param data data to post + * @throws IOException, HttpResponseException + */ + public void post_form(String path, Map data, boolean crumbFlag) throws IOException { + HttpPost request; + if (data != null) { + // https://gist.github.com/stuart-warren/7786892 was slightly helpful here + List queryParams = Lists.newArrayList(); + for (String param : data.keySet()) { + queryParams.add(param + "=" + EncodingUtils.encodeParam(data.get(param))); + } + queryParams.add("json=" + EncodingUtils.encodeParam(JSON.toString(data))); + String value = mapper.writeValueAsString(data); + StringEntity stringEntity = new StringEntity(value, ContentType.APPLICATION_FORM_URLENCODED); + request = new HttpPost(noapi(path) + StringUtil.join(queryParams, "&")); + request.setEntity(stringEntity); + } else { + request = new HttpPost(noapi(path)); + } + + if (crumbFlag == true) { + Crumb crumb = get("/crumbIssuer", Crumb.class); + if (crumb != null) { + request.addHeader(new BasicHeader(crumb.getCrumbRequestField(), crumb.getCrumb())); + } + } + + HttpResponse response = client.execute(request, localContext); + + try { + httpResponseValidator.validateResponse(response); + } finally { + EntityUtils.consume(response.getEntity()); + releaseConnection(request); + } + } + /** * Perform a POST request of XML (instead of using json mapper) and return a string rendering of the response * entity. @@ -336,6 +384,9 @@ private String urlJoin(String path1, String path2) { } private URI api(String path) { + if (path.contains("createItem")) { + return uri.resolve("/").resolve(path); + } if (!path.toLowerCase().matches("https?://.*")) { path = urlJoin(this.context, path); } @@ -348,9 +399,20 @@ private URI api(String path) { return uri.resolve("/").resolve(path); } + private URI noapi(String path) { + if (!path.toLowerCase().matches("https?://.*")) { + path = urlJoin(this.context, path); + } + return uri.resolve("/").resolve(path); + } + private T objectFromResponse(Class cls, HttpResponse response) throws IOException { InputStream content = response.getEntity().getContent(); - T result = mapper.readValue(content, cls); + byte[] bytes = ByteStreams.toByteArray(content); + String bytestring = new String(bytes); + T result = mapper.readValue(bytes, cls); + // TODO: original: + //T result = mapper.readValue(content, cls); result.setClient(this); return result; } diff --git a/src/main/java/com/offbytwo/jenkins/model/FolderJob.java b/src/main/java/com/offbytwo/jenkins/model/FolderJob.java new file mode 100644 index 00000000..987f234a --- /dev/null +++ b/src/main/java/com/offbytwo/jenkins/model/FolderJob.java @@ -0,0 +1,62 @@ +package com.offbytwo.jenkins.model; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import com.google.common.base.Function; +import com.google.common.collect.Maps; + +public class FolderJob extends Job { + + String displayName; + List jobs; + + public FolderJob() {} + + public String getDisplayName() { + return displayName; + } + + /** + * Determine if this FolderJob object is a valid folder or not. + * + * (internally: if jobs list exists) + * + * @return true if this job is a folder. + */ + public boolean isFolder() { + if (jobs != null) { + return true; + } + return false; + } + + /** + * Get a list of all the defined jobs in this folder + * + * @return list of defined jobs (summary level, for details @see Job#details + * @throws IOException + */ + public Map getJobs() { + return Maps.uniqueIndex(jobs, new Function() { + @Override + public String apply(Job job) { + job.setClient(client); + return job.getName().toLowerCase(); + } + }); + } + + /* TODO + public List getJobsRecursive() { + return Lists.transform(jobs, new Function() { + @Override + public Job apply(Job job) { + // TODO: try to see if each job is a folder + return job; + } + }); + } + */ +} diff --git a/src/test/java/Testz.java b/src/test/java/Testz.java new file mode 100644 index 00000000..ef6893a2 --- /dev/null +++ b/src/test/java/Testz.java @@ -0,0 +1,39 @@ +import java.net.URI; +import java.util.Map; + +import org.junit.Test; + +import com.google.common.base.Optional; +import com.offbytwo.jenkins.JenkinsServer; +import com.offbytwo.jenkins.model.FolderJob; +import com.offbytwo.jenkins.model.Job; + + + +public class Testz { + + @Test + public void testStuff() { + try { + JenkinsServer test = new JenkinsServer(new URI("http://localhost:8080/")); + Map jobs = test.getJobs(); + for (String s : jobs.keySet()) { + System.out.println("Job " + s + " is at " + jobs.get(s).getUrl()); + } + Job job = jobs.get("test folder"); + + Optional ofj = test.getFolderJob(job); + FolderJob fj = ofj.get(); + + Map moarJobs = fj.getJobs(); + for (String s : moarJobs.keySet()) { + System.out.println("Job " + s + " is at " + moarJobs.get(s).getUrl()); + } + + System.out.println("test: " + fj.toString()); + test.createFolder("testzzz"); + } catch (Exception e) { + e.printStackTrace(); + } + } +}