new feature : import jobs

This commit is contained in:
Vincent Behar 2011-07-28 14:21:44 +02:00
parent b34a6e39db
commit 2cac570478
16 changed files with 610 additions and 9 deletions

View file

@ -396,6 +396,11 @@
<artifactId>httpclient</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>4.1.1</version>
</dependency>
<!-- Commons -->
<dependency>
<groupId>commons-lang</groupId>

View file

@ -22,10 +22,12 @@
<author>Vincent Behar</author>
</properties>
<body>
<release version="1.1" date="Not Yet Released" description="Add missing features">
<action dev="vbehar" type="add">Import jobs</action>
<action dev="vbehar" type="add">Support for using an HTTP proxy</action>
</release>
<release version="1.0" date="2011-07-08" description="Use RunDeck REST API version 1 (RunDeck 1.2+)">
<action dev="vbehar" type="add">
Initial release
</action>
<action dev="vbehar" type="add">Initial release</action>
</release>
</body>
</document>

View file

@ -18,6 +18,7 @@ package org.rundeck.api;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ProxySelector;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
@ -26,6 +27,7 @@ import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
@ -39,7 +41,11 @@ import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.ProxySelectorRoutePlanner;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
@ -138,6 +144,30 @@ class ApiCall {
return response;
}
/**
* Execute an HTTP POST 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.
*
* @param apiPath on which we will make the HTTP request - see {@link ApiPathBuilder}
* @param parser used to parse the response
* @return the result of the call, as formatted by the parser
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails
*/
public <T> T post(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException {
HttpPost httpPost = new HttpPost(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath);
// POST a multi-part request, with all attachments
MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
for (Entry<String, InputStream> attachment : apiPath.getAttachments().entrySet()) {
entity.addPart(attachment.getKey(), new InputStreamBody(attachment.getValue(), attachment.getKey()));
}
httpPost.setEntity(entity);
return execute(httpPost, parser);
}
/**
* 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.
@ -316,6 +346,9 @@ class ApiCall {
* @return an {@link HttpClient} instance - won't be null
*/
private HttpClient instantiateHttpClient() {
DefaultHttpClient httpClient = new DefaultHttpClient();
// configure SSL
SSLSocketFactory socketFactory = null;
try {
socketFactory = new SSLSocketFactory(new TrustStrategy() {
@ -334,9 +367,13 @@ class ApiCall {
} catch (KeyStoreException e) {
throw new RuntimeException(e);
}
HttpClient httpClient = new DefaultHttpClient();
httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));
// configure proxy (use system env : http.proxyHost / http.proxyPort)
System.setProperty("java.net.useSystemProxies", "true");
httpClient.setRoutePlanner(new ProxySelectorRoutePlanner(httpClient.getConnectionManager().getSchemeRegistry(),
ProxySelector.getDefault()));
return httpClient;
}
}

View file

@ -15,6 +15,9 @@
*/
package org.rundeck.api;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.rundeck.api.util.ParametersUtil;
@ -29,7 +32,10 @@ class ApiPathBuilder {
/** Internally, we store everything in a {@link StringBuilder} */
private final StringBuilder apiPath;
/** Maker for using the right separator between parameters ("?" or "&") */
/** When POSTing, we can add attachments */
private final Map<String, InputStream> attachments;
/** Marker for using the right separator between parameters ("?" or "&") */
private boolean firstParamDone = false;
/**
@ -40,6 +46,7 @@ class ApiPathBuilder {
*/
public ApiPathBuilder(String... paths) {
apiPath = new StringBuilder();
attachments = new HashMap<String, InputStream>();
if (paths != null) {
for (String path : paths) {
if (StringUtils.isNotBlank(path)) {
@ -68,6 +75,22 @@ class ApiPathBuilder {
return this;
}
/**
* Append the given parameter (key and value). This will only append the parameter if it is not null, and make sure
* to add the right separator ("?" or "&") before. The key and value will be separated by the "=" character. Also,
* the value will be converted to lower-case.
*
* @param key of the parameter. Must not be null or empty
* @param value of the parameter. May be null
* @return this, for method chaining
*/
public ApiPathBuilder param(String key, Enum<?> value) {
if (value != null) {
param(key, StringUtils.lowerCase(value.toString()));
}
return this;
}
/**
* Append the given parameter (key and value). This will only append the parameter if it is not null, and make sure
* to add the right separator ("?" or "&") before. The key and value will be separated by the "=" character.
@ -129,6 +152,28 @@ class ApiPathBuilder {
return this;
}
/**
* When POSTing a request, add the given {@link InputStream} as an attachment to the content of the request. This
* will only add the stream if it is not null.
*
* @param name of the attachment. Must not be null or empty
* @param stream. May be null
* @return this, for method chaining
*/
public ApiPathBuilder attach(String name, InputStream stream) {
if (stream != null) {
attachments.put(name, stream);
}
return this;
}
/**
* @return all attachments to be POSTed, with their names
*/
public Map<String, InputStream> getAttachments() {
return attachments;
}
@Override
public String toString() {
return apiPath.toString();

View file

@ -0,0 +1,25 @@
/*
* Copyright 2011 Vincent Behar
*
* Licensed 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.rundeck.api;
/**
* All supported types of files.
*
* @author Vincent Behar
*/
public enum FileType {
XML, YAML;
}

View file

@ -16,6 +16,7 @@
package org.rundeck.api;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
@ -30,6 +31,8 @@ import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
import org.rundeck.api.domain.RundeckAbort;
import org.rundeck.api.domain.RundeckExecution;
import org.rundeck.api.domain.RundeckJob;
import org.rundeck.api.domain.RundeckJobsImportMethod;
import org.rundeck.api.domain.RundeckJobsImportResult;
import org.rundeck.api.domain.RundeckNode;
import org.rundeck.api.domain.RundeckProject;
import org.rundeck.api.domain.RundeckSystemInfo;
@ -37,6 +40,7 @@ import org.rundeck.api.domain.RundeckExecution.ExecutionStatus;
import org.rundeck.api.parser.AbortParser;
import org.rundeck.api.parser.ExecutionParser;
import org.rundeck.api.parser.JobParser;
import org.rundeck.api.parser.JobsImportResultParser;
import org.rundeck.api.parser.ListParser;
import org.rundeck.api.parser.NodeParser;
import org.rundeck.api.parser.ProjectParser;
@ -316,6 +320,173 @@ public class RundeckClient implements Serializable {
return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId));
}
/**
* Import the definitions of jobs, from the given file
*
* @param filename of the file containing the jobs definitions - mandatory
* @param fileType type of the file. See {@link FileType} - mandatory
* @return a {@link RundeckJobsImportResult} instance - won't be null
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login failed
* @throws IllegalArgumentException if the filename or fileType is blank (null, empty or whitespace), or the
* fileType is invalid
* @throws IOException if we failed to read the file
* @see #importJobs(InputStream, String)
* @see #importJobs(String, FileType, RundeckJobsImportMethod)
*/
public RundeckJobsImportResult importJobs(String filename, String fileType) throws RundeckApiException,
RundeckApiLoginException, IllegalArgumentException, IOException {
AssertUtil.notBlank(fileType, "fileType is mandatory to import jobs !");
return importJobs(filename, FileType.valueOf(StringUtils.upperCase(fileType)));
}
/**
* Import the definitions of jobs, from the given file
*
* @param filename of the file containing the jobs definitions - mandatory
* @param fileType type of the file. See {@link FileType} - mandatory
* @return a {@link RundeckJobsImportResult} instance - won't be null
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login failed
* @throws IllegalArgumentException if the filename is blank (null, empty or whitespace), or the fileType is null
* @throws IOException if we failed to read the file
* @see #importJobs(InputStream, FileType)
* @see #importJobs(String, FileType, RundeckJobsImportMethod)
*/
public RundeckJobsImportResult importJobs(String filename, FileType fileType) throws RundeckApiException,
RundeckApiLoginException, IllegalArgumentException, IOException {
return importJobs(filename, fileType, (RundeckJobsImportMethod) null);
}
/**
* Import the definitions of jobs, from the given file, using the given behavior
*
* @param filename of the file containing the jobs definitions - mandatory
* @param fileType type of the file. See {@link FileType} - mandatory
* @param importBehavior see {@link RundeckJobsImportMethod}
* @return a {@link RundeckJobsImportResult} instance - won't be null
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login failed
* @throws IllegalArgumentException if the filename or fileType is blank (null, empty or whitespace), or the
* fileType or behavior is not valid
* @throws IOException if we failed to read the file
* @see #importJobs(InputStream, String, String)
* @see #importJobs(String, FileType, RundeckJobsImportMethod)
*/
public RundeckJobsImportResult importJobs(String filename, String fileType, String importBehavior)
throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException, IOException {
AssertUtil.notBlank(fileType, "fileType is mandatory to import jobs !");
return importJobs(filename,
FileType.valueOf(StringUtils.upperCase(fileType)),
RundeckJobsImportMethod.valueOf(StringUtils.upperCase(importBehavior)));
}
/**
* Import the definitions of jobs, from the given file, using the given behavior
*
* @param filename of the file containing the jobs definitions - mandatory
* @param fileType type of the file. See {@link FileType} - mandatory
* @param importBehavior see {@link RundeckJobsImportMethod}
* @return a {@link RundeckJobsImportResult} instance - won't be null
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login failed
* @throws IllegalArgumentException if the filename is blank (null, empty or whitespace), or the fileType is null
* @throws IOException if we failed to read the file
* @see #importJobs(InputStream, FileType, RundeckJobsImportMethod)
*/
public RundeckJobsImportResult importJobs(String filename, FileType fileType, RundeckJobsImportMethod importBehavior)
throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException, IOException {
AssertUtil.notBlank(filename, "filename (of jobs file) is mandatory to import jobs !");
FileInputStream stream = null;
try {
stream = FileUtils.openInputStream(new File(filename));
return importJobs(stream, fileType, importBehavior);
} finally {
IOUtils.closeQuietly(stream);
}
}
/**
* Import the definitions of jobs, from the given input stream
*
* @param stream inputStream for reading the definitions - mandatory
* @param fileType type of the file. See {@link FileType} - mandatory
* @return a {@link RundeckJobsImportResult} instance - won't be null
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login failed
* @throws IllegalArgumentException if the stream is null, or the fileType is blank (null, empty or whitespace) or
* invalid
* @see #importJobs(String, String)
* @see #importJobs(InputStream, FileType, RundeckJobsImportMethod)
*/
public RundeckJobsImportResult importJobs(InputStream stream, String fileType) throws RundeckApiException,
RundeckApiLoginException, IllegalArgumentException {
AssertUtil.notBlank(fileType, "fileType is mandatory to import jobs !");
return importJobs(stream, FileType.valueOf(StringUtils.upperCase(fileType)));
}
/**
* Import the definitions of jobs, from the given input stream
*
* @param stream inputStream for reading the definitions - mandatory
* @param fileType type of the file. See {@link FileType} - mandatory
* @return a {@link RundeckJobsImportResult} instance - won't be null
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login failed
* @throws IllegalArgumentException if the stream or fileType is null
* @see #importJobs(String, FileType)
* @see #importJobs(InputStream, FileType, RundeckJobsImportMethod)
*/
public RundeckJobsImportResult importJobs(InputStream stream, FileType fileType) throws RundeckApiException,
RundeckApiLoginException, IllegalArgumentException {
return importJobs(stream, fileType, (RundeckJobsImportMethod) null);
}
/**
* Import the definitions of jobs, from the given input stream, using the given behavior
*
* @param stream inputStream for reading the definitions - mandatory
* @param fileType type of the file. See {@link FileType} - mandatory
* @param importBehavior see {@link RundeckJobsImportMethod}
* @return a {@link RundeckJobsImportResult} instance - won't be null
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login failed
* @throws IllegalArgumentException if the stream is null, or the fileType is blank (null, empty or whitespace), or
* the fileType or behavior is not valid
* @see #importJobs(String, String, String)
* @see #importJobs(InputStream, FileType, RundeckJobsImportMethod)
*/
public RundeckJobsImportResult importJobs(InputStream stream, String fileType, String importBehavior)
throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException {
AssertUtil.notBlank(fileType, "fileType is mandatory to import jobs !");
return importJobs(stream,
FileType.valueOf(StringUtils.upperCase(fileType)),
RundeckJobsImportMethod.valueOf(StringUtils.upperCase(importBehavior)));
}
/**
* Import the definitions of jobs, from the given input stream, using the given behavior
*
* @param stream inputStream for reading the definitions - mandatory
* @param fileType type of the file. See {@link FileType} - mandatory
* @param importBehavior see {@link RundeckJobsImportMethod}
* @return a {@link RundeckJobsImportResult} instance - won't be null
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login failed
* @throws IllegalArgumentException if the stream or fileType is null
* @see #importJobs(String, FileType, RundeckJobsImportMethod)
*/
public RundeckJobsImportResult importJobs(InputStream stream, FileType fileType,
RundeckJobsImportMethod importBehavior) throws RundeckApiException, RundeckApiLoginException,
IllegalArgumentException {
AssertUtil.notNull(stream, "inputStream of jobs is mandatory to import jobs !");
AssertUtil.notNull(fileType, "fileType is mandatory to import jobs !");
return new ApiCall(this).post(new ApiPathBuilder("/jobs/import").param("format", fileType)
.param("dupeOption", importBehavior)
.attach("xmlBatch", stream),
new JobsImportResultParser("result"));
}
/**
* 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).
@ -851,8 +1022,7 @@ public class RundeckClient implements Serializable {
public List<RundeckExecution> getJobExecutions(String jobId, ExecutionStatus status, Long max, Long offset)
throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException {
AssertUtil.notBlank(jobId, "jobId is mandatory to get the executions of a job !");
return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId, "/executions").param("status",
status != null ? StringUtils.lowerCase(status.toString()) : null)
return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId, "/executions").param("status", status)
.param("max", max)
.param("offset", offset),
new ListParser<RundeckExecution>(new ExecutionParser(),

View file

@ -0,0 +1,25 @@
/*
* Copyright 2011 Vincent Behar
*
* Licensed 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.rundeck.api.domain;
/**
* The behavior when importing jobs (which may already exist).
*
* @author Vincent Behar
*/
public enum RundeckJobsImportMethod {
CREATE, UPDATE, SKIP;
}

View file

@ -0,0 +1,106 @@
/*
* Copyright 2011 Vincent Behar
*
* Licensed 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.rundeck.api.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Result of importing some jobs into RunDeck
*
* @author Vincent Behar
*/
public class RundeckJobsImportResult implements Serializable {
private static final long serialVersionUID = 1L;
private final List<RundeckJob> succeededJobs = new ArrayList<RundeckJob>();
private final List<RundeckJob> skippedJobs = new ArrayList<RundeckJob>();
private final Map<RundeckJob, String> failedJobs = new HashMap<RundeckJob, String>();
public void addSucceededJob(RundeckJob job) {
succeededJobs.add(job);
}
public void addSkippedJob(RundeckJob job) {
skippedJobs.add(job);
}
public void addFailedJob(RundeckJob job, String errorMessage) {
failedJobs.put(job, errorMessage);
}
public List<RundeckJob> getSucceededJobs() {
return succeededJobs;
}
public List<RundeckJob> getSkippedJobs() {
return skippedJobs;
}
public Map<RundeckJob, String> getFailedJobs() {
return failedJobs;
}
@Override
public String toString() {
return "RundeckJobsImportResult [succeededJobs=" + succeededJobs + ", skippedJobs=" + skippedJobs
+ ", failedJobs=" + failedJobs + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((failedJobs == null) ? 0 : failedJobs.hashCode());
result = prime * result + ((skippedJobs == null) ? 0 : skippedJobs.hashCode());
result = prime * result + ((succeededJobs == null) ? 0 : succeededJobs.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RundeckJobsImportResult other = (RundeckJobsImportResult) obj;
if (failedJobs == null) {
if (other.failedJobs != null)
return false;
} else if (!failedJobs.equals(other.failedJobs))
return false;
if (skippedJobs == null) {
if (other.skippedJobs != null)
return false;
} else if (!skippedJobs.equals(other.skippedJobs))
return false;
if (succeededJobs == null) {
if (other.succeededJobs != null)
return false;
} else if (!succeededJobs.equals(other.succeededJobs))
return false;
return true;
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright 2011 Vincent Behar
*
* Licensed 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.rundeck.api.parser;
import java.util.List;
import org.dom4j.Node;
import org.rundeck.api.domain.RundeckJob;
import org.rundeck.api.domain.RundeckJobsImportResult;
/**
* Parser for a single {@link RundeckJobsImportResult}
*
* @author Vincent Behar
*/
public class JobsImportResultParser implements XmlNodeParser<RundeckJobsImportResult> {
private String xpath;
public JobsImportResultParser() {
super();
}
/**
* @param xpath of the result element if it is not the root node
*/
public JobsImportResultParser(String xpath) {
super();
this.xpath = xpath;
}
@Override
public RundeckJobsImportResult parseXmlNode(Node node) {
Node resultNode = xpath != null ? node.selectSingleNode(xpath) : node;
RundeckJobsImportResult result = new RundeckJobsImportResult();
@SuppressWarnings("unchecked")
List<Node> succeededJobsNodes = resultNode.selectNodes("succeeded/job");
if (succeededJobsNodes != null) {
for (Node succeededJobNode : succeededJobsNodes) {
RundeckJob job = new JobParser().parseXmlNode(succeededJobNode);
result.addSucceededJob(job);
}
}
@SuppressWarnings("unchecked")
List<Node> skippedJobsNodes = resultNode.selectNodes("skipped/job");
if (skippedJobsNodes != null) {
for (Node skippedJobNode : skippedJobsNodes) {
RundeckJob job = new JobParser().parseXmlNode(skippedJobNode);
result.addSkippedJob(job);
}
}
@SuppressWarnings("unchecked")
List<Node> failedJobsNodes = resultNode.selectNodes("failed/job");
if (failedJobsNodes != null) {
for (Node failedJobNode : failedJobsNodes) {
RundeckJob job = new JobParser().parseXmlNode(failedJobNode);
result.addFailedJob(job, failedJobNode.valueOf("error"));
}
}
return result;
}
}

View file

@ -85,6 +85,16 @@ rundeck.exportJobsToFile("/tmp/jobs.xml", "my-project")
rundeck.exportJobToFile("/tmp/job.xml", "job-id")
{code}
h2. Importing jobs
{code}
import org.rundeck.api.RundeckClient
rundeck = new RundeckClient("http://localhost:4440", "admin", "admin")
result = rundeck.importJobs("/tmp/jobs.xml", "xml")
println "${result.succeededJobs.size} jobs successfully imported, ${result.skippedJobs.size} jobs skipped, and ${result.failedJobs.size} jobs failed"
{code}
h2. And more...
See the API documentation of the [RundeckClient|./apidocs/org/rundeck/api/RundeckClient.html] class for more interactions with your RunDeck instance...

View file

@ -90,6 +90,16 @@ rundeck.exportJobsToFile("/tmp/jobs.xml", "my-project")
rundeck.exportJobToFile("/tmp/job.xml", "job-id")
{code}
h2. Importing jobs
{code}
import org.rundeck.api.RundeckClient
rundeck = RundeckClient.new("http://localhost:4440", "admin", "admin")
result = rundeck.importJobs("/tmp/jobs.xml", "xml")
puts "#{result.succeededJobs.size} jobs successfully imported, #{result.skippedJobs.size} jobs skipped, and #{result.failedJobs.size} jobs failed"
{code}
h2. And more...
See the API documentation of the [RundeckClient|./apidocs/org/rundeck/api/RundeckClient.html] class for more interactions with your RunDeck instance...

View file

@ -74,6 +74,16 @@ rundeck.exportJobsToFile("/tmp/jobs.xml", "my-project")
rundeck.exportJobToFile("/tmp/job.xml", "job-id")
{code}
h2. Importing jobs
{code}
from org.rundeck.api import RundeckClient
rundeck = RundeckClient("http://localhost:4440", "admin", "admin")
result = rundeck.importJobs("/tmp/jobs.xml", "xml")
print("%s jobs successfully imported, %s jobs skipped, and %s jobs failed" % (result.succeededJobs.size, result.skippedJobs.size, result.failedJobs.size))
{code}
h2. And more...
See the API documentation of the [RundeckClient|./apidocs/org/rundeck/api/RundeckClient.html] class for more interactions with your RunDeck instance...

View file

@ -10,7 +10,7 @@ h2. RunDeck API version 1
* Listing Jobs - OK
* Running a Job - OK
* Exporting Jobs - OK (XML only, YAML not supported yet)
* Importing Jobs - *TODO*
* Importing Jobs - OK
* Getting a Job Definition - OK (XML only, YAML not supported yet)
* Deleting a Job Definition - OK
* Getting Executions for a Job - OK

View file

@ -36,6 +36,30 @@
</ul>
</answer>
</faq>
</part>
<part id="http">
<title>HTTP connections to RunDeck</title>
<faq id="ssl">
<question>
What about SSL (HTTPS) ?
</question>
<answer>
<p>The lib will trust any certificate when connecting to a RunDeck instance running on HTTPS (even self-signed certificates).</p>
</answer>
</faq>
<faq id="proxy">
<question>
How do I use a proxy ?
</question>
<answer>
<p>See <a href="http://download.oracle.com/javase/6/docs/technotes/guides/net/proxies.html">the Java doc on proxies</a>. You will need a JAVA_OPTS env variable with a value like "-Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888".</p>
</answer>
</faq>
</part>
</faqs>

View file

@ -0,0 +1,51 @@
/*
* Copyright 2011 Vincent Behar
*
* Licensed 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.rundeck.api.parser;
import java.io.InputStream;
import org.dom4j.Document;
import org.junit.Assert;
import org.junit.Test;
import org.rundeck.api.domain.RundeckJob;
import org.rundeck.api.domain.RundeckJobsImportResult;
/**
* Test the {@link JobsImportResultParser}
*
* @author Vincent Behar
*/
public class JobsImportResultParserTest {
@Test
public void parseResult() throws Exception {
InputStream input = getClass().getResourceAsStream("jobs-import.xml");
Document document = ParserHelper.loadDocument(input);
RundeckJobsImportResult result = new JobsImportResultParser("result").parseXmlNode(document);
Assert.assertEquals(2, result.getSucceededJobs().size());
Assert.assertEquals(0, result.getSkippedJobs().size());
Assert.assertEquals(1, result.getFailedJobs().size());
Assert.assertEquals("job-one", result.getSucceededJobs().get(0).getName());
Assert.assertEquals("job-two", result.getSucceededJobs().get(1).getName());
RundeckJob failedJob = result.getFailedJobs().keySet().iterator().next();
Assert.assertEquals("job-three", failedJob.getName());
Assert.assertEquals("Error message", result.getFailedJobs().get(failedJob));
}
}

View file

@ -0,0 +1 @@
<result success='true' apiversion='1'><succeeded count='2'><job index='1'><id>1</id><name>job-one</name><group>import</group><project>test</project><url>/job/show/1</url></job><job index='2'><id>2</id><name>job-two</name><group>import</group><project>test</project><url>/job/show/2</url></job></succeeded><failed count='1'><job index='3'><name>job-three</name><group>import</group><project>test</project><error>Error message</error></job></failed><skipped count='0'></skipped></result>