diff --git a/pom.xml b/pom.xml
index 69b74fd..31f082c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -397,6 +397,11 @@
commons-lang
2.6
+
+ commons-io
+ commons-io
+ 2.0.1
+
dom4j
diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java
index 7acb0b7..2281804 100644
--- a/src/main/java/org/rundeck/api/ApiCall.java
+++ b/src/main/java/org/rundeck/api/ApiCall.java
@@ -15,7 +15,9 @@
*/
package org.rundeck.api;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@@ -117,6 +119,25 @@ class ApiCall {
return execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), parser);
}
+ /**
+ * Execute an HTTP GET request to the RunDeck instance, on the given path. We will login first, and then execute the
+ * API call.
+ *
+ * @param apiPath on which we will make the HTTP request - see {@link ApiPathBuilder}
+ * @return a new {@link InputStream} instance, not linked with network resources
+ * @throws RundeckApiException in case of error when calling the API
+ * @throws RundeckApiLoginException if the login fails
+ */
+ public InputStream get(ApiPathBuilder apiPath) throws RundeckApiException, RundeckApiLoginException {
+ ByteArrayInputStream response = execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath));
+
+ // try to load the document, to throw an exception in case of error
+ ParserHelper.loadDocument(response);
+ response.reset();
+
+ return response;
+ }
+
/**
* Execute an HTTP DELETE request to the RunDeck instance, on the given path. We will login first, and then execute
* the API call. At the end, the given parser will be used to convert the response to a more useful result object.
@@ -144,6 +165,23 @@ class ApiCall {
*/
private T execute(HttpRequestBase request, XmlNodeParser parser) throws RundeckApiException,
RundeckApiLoginException {
+ // execute the request
+ InputStream response = execute(request);
+
+ // read and parse the response
+ Document xmlDocument = ParserHelper.loadDocument(response);
+ return parser.parseXmlNode(xmlDocument);
+ }
+
+ /**
+ * Execute an HTTP request to the RunDeck instance. We will login first, and then execute the API call.
+ *
+ * @param request to execute. see {@link HttpGet}, {@link HttpDelete}, and so on...
+ * @return a new {@link InputStream} instance, not linked with network resources
+ * @throws RundeckApiException in case of error when calling the API
+ * @throws RundeckApiLoginException if the login fails
+ */
+ private ByteArrayInputStream execute(HttpRequestBase request) throws RundeckApiException, RundeckApiLoginException {
HttpClient httpClient = instantiateHttpClient();
try {
login(httpClient);
@@ -176,6 +214,7 @@ class ApiCall {
}
}
+ // check the response code (should be 2xx, even in case of error : error message is in the XML result)
if (response.getStatusLine().getStatusCode() / 100 != 2) {
throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' for "
+ request.getURI());
@@ -185,17 +224,12 @@ class ApiCall {
+ response.getStatusLine());
}
- // read and parse the response
- Document xmlDocument = ParserHelper.loadDocument(response);
- T result = parser.parseXmlNode(xmlDocument);
-
- // release the connection
+ // return a new inputStream, so that we can close all network resources
try {
- EntityUtils.consume(response.getEntity());
+ return new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity()));
} catch (IOException e) {
- throw new RundeckApiException("Failed to consume entity (release connection)", e);
+ throw new RundeckApiException("Failed to consume entity and convert the inputStream", e);
}
- return result;
} finally {
httpClient.getConnectionManager().shutdown();
}
diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java
index 02b8072..e1eb52a 100644
--- a/src/main/java/org/rundeck/api/RundeckClient.java
+++ b/src/main/java/org/rundeck/api/RundeckClient.java
@@ -15,11 +15,16 @@
*/
package org.rundeck.api;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
import org.rundeck.api.domain.RundeckAbort;
@@ -195,6 +200,85 @@ public class RundeckClient implements Serializable {
new ListParser(new JobParser(), "result/jobs/job"));
}
+ /**
+ * Export the definitions of all jobs that belongs to the given project, as an XML file
+ *
+ * @param filename path of the file where the content should be saved
+ * @param project name of the project - mandatory
+ * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
+ * @throws RundeckApiLoginException if the login failed
+ * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
+ * @throws IOException if we failed to write to the file
+ * @see #exportJobsToFile(String, String, String, String, String...)
+ * @see #exportJobs(String)
+ */
+ public void exportJobsToFile(String filename, String project) throws RundeckApiException, RundeckApiLoginException,
+ IllegalArgumentException, IOException {
+ exportJobsToFile(filename, project, null, null, new String[0]);
+ }
+
+ /**
+ * Export the definitions of the jobs that belongs to the given project, and matches the given criteria (jobFilter,
+ * groupPath and jobIds), as an XML file
+ *
+ * @param filename path of the file where the content should be saved
+ * @param project name of the project - mandatory
+ * @param jobFilter a filter for the job Name - optional
+ * @param groupPath a group or partial group path to include all jobs within that group path - optional
+ * @param jobIds a list of Job IDs to include - optional
+ * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
+ * @throws RundeckApiLoginException if the login failed
+ * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
+ * @throws IOException if we failed to write to the file
+ * @see #exportJobsToFile(String, String)
+ * @see #exportJobs(String, String, String, String...)
+ */
+ public void exportJobsToFile(String filename, String project, String jobFilter, String groupPath, String... jobIds)
+ throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException, IOException {
+ InputStream inputStream = exportJobs(project, jobFilter, groupPath, jobIds);
+ FileUtils.writeByteArrayToFile(new File(filename), IOUtils.toByteArray(inputStream));
+ }
+
+ /**
+ * Export the definitions of all jobs that belongs to the given project
+ *
+ * @param project name of the project - mandatory
+ * @return an {@link InputStream} instance, not linked to any network resources - won't be null
+ * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
+ * @throws RundeckApiLoginException if the login failed
+ * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
+ * @see #exportJobs(String, String, String, String...)
+ * @see #exportJobsToFile(String, String)
+ */
+ public InputStream exportJobs(String project) throws RundeckApiException, RundeckApiLoginException,
+ IllegalArgumentException {
+ return exportJobs(project, null, null, new String[0]);
+ }
+
+ /**
+ * Export the definitions of the jobs that belongs to the given project, and matches the given criteria (jobFilter,
+ * groupPath and jobIds)
+ *
+ * @param project name of the project - mandatory
+ * @param jobFilter a filter for the job Name - optional
+ * @param groupPath a group or partial group path to include all jobs within that group path - optional
+ * @param jobIds a list of Job IDs to include - optional
+ * @return an {@link InputStream} instance, not linked to any network resources - won't be null
+ * @throws RundeckApiException in case of error when calling the API (non-existent project with this name)
+ * @throws RundeckApiLoginException if the login failed
+ * @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
+ * @see #exportJobs(String)
+ * @see #exportJobsToFile(String, String, String, String, String...)
+ */
+ public InputStream exportJobs(String project, String jobFilter, String groupPath, String... jobIds)
+ throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException {
+ AssertUtil.notBlank(project, "project is mandatory to export all jobs !");
+ return new ApiCall(this).get(new ApiPathBuilder("/jobs/export").param("project", project)
+ .param("jobFilter", jobFilter)
+ .param("groupPath", groupPath)
+ .param("idlist", StringUtils.join(jobIds, ",")));
+ }
+
/**
* Find a job, identified by its project, group and name. Note that the groupPath is optional, as a job does not
* need to belong to a group (either pass null, or an empty string).
diff --git a/src/main/java/org/rundeck/api/parser/ParserHelper.java b/src/main/java/org/rundeck/api/parser/ParserHelper.java
index 6cfbbc7..139d945 100644
--- a/src/main/java/org/rundeck/api/parser/ParserHelper.java
+++ b/src/main/java/org/rundeck/api/parser/ParserHelper.java
@@ -15,9 +15,7 @@
*/
package org.rundeck.api.parser;
-import java.io.IOException;
import java.io.InputStream;
-import org.apache.http.HttpResponse;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Node;
@@ -31,35 +29,12 @@ import org.rundeck.api.RundeckApiException;
*/
public class ParserHelper {
- /**
- * Load an XML {@link Document} from the given RunDeck {@link HttpResponse}.
- *
- * @param httpResponse from an API call to RunDeck
- * @return an XML {@link Document}
- * @throws RundeckApiException if we failed to read the response, or if the response is an error
- * @see #loadDocument(InputStream)
- */
- public static Document loadDocument(HttpResponse httpResponse) throws RundeckApiException {
- InputStream inputStream = null;
-
- try {
- inputStream = httpResponse.getEntity().getContent();
- } catch (IllegalStateException e) {
- throw new RundeckApiException("Failed to read RunDeck reponse", e);
- } catch (IOException e) {
- throw new RundeckApiException("Failed to read RunDeck reponse", e);
- }
-
- return loadDocument(inputStream);
- }
-
/**
* Load an XML {@link Document} from the given {@link InputStream}
*
* @param inputStream from an API call to RunDeck
* @return an XML {@link Document}
* @throws RundeckApiException if we failed to read the response, or if the response is an error
- * @see #loadDocument(HttpResponse)
*/
public static Document loadDocument(InputStream inputStream) throws RundeckApiException {
SAXReader reader = new SAXReader();