mirror of
https://github.com/Fishwaldo/rundeck-api-java-client.git
synced 2025-07-06 13:08:38 +00:00
initial commit
This commit is contained in:
commit
d294673b45
29 changed files with 1652 additions and 0 deletions
8
README
Normal file
8
README
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
Java client for the RunDeck REST API
|
||||
|
||||
RunDeck Home : http://rundeck.org/
|
||||
|
||||
LICENSE : The Apache Software License, Version 2.0
|
||||
See http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
|
81
pom.xml
Normal file
81
pom.xml
Normal file
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.sonatype.oss</groupId>
|
||||
<artifactId>oss-parent</artifactId>
|
||||
<version>7</version>
|
||||
</parent>
|
||||
|
||||
<groupId>org.rundeck</groupId>
|
||||
<artifactId>rundeck-api-java-client</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>RunDeck API - Java Client</name>
|
||||
<description>Java client for the RunDeck REST API</description>
|
||||
<url>https://github.com/vbehar/rundeck-api-java-client</url>
|
||||
<scm>
|
||||
<url>https://github.com/vbehar/rundeck-api-java-client</url>
|
||||
<connection>scm:git:git://github.com/vbehar/rundeck-api-java-client.git</connection>
|
||||
<developerConnection>scm:git:git@github.com:vbehar/rundeck-api-java-client.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>The Apache Software License, Version 2.0</name>
|
||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>vbehar</id>
|
||||
<name>Vincent Behar</name>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- HTTP -->
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Commons -->
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
|
||||
<!-- XML Parsing -->
|
||||
<dependency>
|
||||
<groupId>dom4j</groupId>
|
||||
<artifactId>dom4j</artifactId>
|
||||
<version>1.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>jaxen</groupId>
|
||||
<artifactId>jaxen</artifactId>
|
||||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
220
src/main/java/org/rundeck/api/ApiCall.java
Normal file
220
src/main/java/org/rundeck/api/ApiCall.java
Normal file
|
@ -0,0 +1,220 @@
|
|||
package org.rundeck.api;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.ParseException;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
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.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.protocol.HTTP;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.dom4j.Document;
|
||||
import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
|
||||
import org.rundeck.api.parser.NodeParser;
|
||||
import org.rundeck.api.parser.ParserHelper;
|
||||
|
||||
/**
|
||||
* Class responsible for making the HTTP API calls
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
class ApiCall {
|
||||
|
||||
private final RundeckClient client;
|
||||
|
||||
/**
|
||||
* Build a new instance, linked to the given RunDeck client
|
||||
*
|
||||
* @param client holding the RunDeck url and the credentials
|
||||
*/
|
||||
public ApiCall(RundeckClient client) {
|
||||
super();
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to "ping" the RunDeck instance to see if it is alive
|
||||
*
|
||||
* @throws RundeckApiException if the ping fails
|
||||
*/
|
||||
public void ping() throws RundeckApiException {
|
||||
HttpClient httpClient = instantiateHttpClient();
|
||||
try {
|
||||
HttpResponse response = httpClient.execute(new HttpGet(client.getUrl()));
|
||||
if (response.getStatusLine().getStatusCode() / 100 != 2) {
|
||||
throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' when pinging "
|
||||
+ client.getUrl());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RundeckApiException("Failed to ping RunDeck instance at " + client.getUrl(), e);
|
||||
} finally {
|
||||
httpClient.getConnectionManager().shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the credentials (login/password) on the RunDeck instance
|
||||
*
|
||||
* @throws RundeckApiLoginException if the login fails
|
||||
*/
|
||||
public void testCredentials() throws RundeckApiLoginException {
|
||||
HttpClient httpClient = instantiateHttpClient();
|
||||
try {
|
||||
login(httpClient);
|
||||
} finally {
|
||||
httpClient.getConnectionManager().shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an HTTP GET 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
|
||||
* @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 get(String apiPath, NodeParser<T> parser) throws RundeckApiException, RundeckApiLoginException {
|
||||
String apiUrl = client.getUrl() + RundeckClient.API_ENDPOINT + apiPath;
|
||||
|
||||
HttpClient httpClient = instantiateHttpClient();
|
||||
try {
|
||||
login(httpClient);
|
||||
|
||||
// execute the HTTP request
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
response = httpClient.execute(new HttpGet(apiUrl));
|
||||
} catch (IOException e) {
|
||||
throw new RundeckApiException("Failed to execute an HTTP GET on url : " + apiUrl, e);
|
||||
}
|
||||
if (response.getStatusLine().getStatusCode() / 100 != 2) {
|
||||
throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' for " + apiUrl);
|
||||
}
|
||||
if (response.getEntity() == null) {
|
||||
throw new RundeckApiException("Empty RunDeck response ! HTTP status line is : "
|
||||
+ response.getStatusLine());
|
||||
}
|
||||
|
||||
// read and parse the response
|
||||
Document xmlDocument = ParserHelper.loadDocument(response);
|
||||
T result = parser.parseNode(xmlDocument);
|
||||
|
||||
// release the connection
|
||||
try {
|
||||
EntityUtils.consume(response.getEntity());
|
||||
} catch (IOException e) {
|
||||
throw new RundeckApiException("Failed to consume entity (release connection)", e);
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
httpClient.getConnectionManager().shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do the actual work of login, using the given {@link HttpClient} instance. You'll need to re-use this instance
|
||||
* when making API calls (such as running a job).
|
||||
*
|
||||
* @param httpClient pre-instantiated
|
||||
* @throws RundeckApiLoginException if the login failed
|
||||
*/
|
||||
private void login(HttpClient httpClient) throws RundeckApiLoginException {
|
||||
String location = client.getUrl() + "/j_security_check";
|
||||
|
||||
while (true) {
|
||||
HttpPost postLogin = new HttpPost(location);
|
||||
List<NameValuePair> params = new ArrayList<NameValuePair>();
|
||||
params.add(new BasicNameValuePair("j_username", client.getLogin()));
|
||||
params.add(new BasicNameValuePair("j_password", client.getPassword()));
|
||||
params.add(new BasicNameValuePair("action", "login"));
|
||||
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
postLogin.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
|
||||
response = httpClient.execute(postLogin);
|
||||
} catch (IOException e) {
|
||||
throw new RundeckApiLoginException("Failed to post login form on " + location, e);
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() / 100 == 3) {
|
||||
// HTTP client refuses to handle redirects (code 3xx) for POST, so we have to do it manually...
|
||||
location = response.getFirstHeader("Location").getValue();
|
||||
try {
|
||||
EntityUtils.consume(response.getEntity());
|
||||
} catch (IOException e) {
|
||||
throw new RundeckApiLoginException("Failed to consume entity (release connection)", e);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (response.getStatusLine().getStatusCode() / 100 != 2) {
|
||||
throw new RundeckApiLoginException("Invalid HTTP response '" + response.getStatusLine() + "' for "
|
||||
+ location);
|
||||
}
|
||||
try {
|
||||
String content = EntityUtils.toString(response.getEntity(), HTTP.UTF_8);
|
||||
if (StringUtils.contains(content, "j_security_check")) {
|
||||
throw new RundeckApiLoginException("Login failed for user " + client.getLogin());
|
||||
}
|
||||
try {
|
||||
EntityUtils.consume(response.getEntity());
|
||||
} catch (IOException e) {
|
||||
throw new RundeckApiLoginException("Failed to consume entity (release connection)", e);
|
||||
}
|
||||
} catch (IOException io) {
|
||||
throw new RundeckApiLoginException("Failed to read RunDeck result", io);
|
||||
} catch (ParseException p) {
|
||||
throw new RundeckApiLoginException("Failed to parse RunDeck response", p);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new {@link HttpClient} instance, configured to accept all SSL certificates
|
||||
*
|
||||
* @return an {@link HttpClient} instance - won't be null
|
||||
*/
|
||||
private HttpClient instantiateHttpClient() {
|
||||
SSLSocketFactory socketFactory = null;
|
||||
try {
|
||||
socketFactory = new SSLSocketFactory(new TrustStrategy() {
|
||||
|
||||
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
|
||||
return true;
|
||||
}
|
||||
}, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||
} catch (KeyManagementException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
HttpClient httpClient = new DefaultHttpClient();
|
||||
httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory));
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
}
|
36
src/main/java/org/rundeck/api/RundeckApiException.java
Normal file
36
src/main/java/org/rundeck/api/RundeckApiException.java
Normal file
|
@ -0,0 +1,36 @@
|
|||
package org.rundeck.api;
|
||||
|
||||
/**
|
||||
* A generic (unchecked) exception when using the RunDeck API
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class RundeckApiException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RundeckApiException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RundeckApiException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific login-related error
|
||||
*/
|
||||
public static class RundeckApiLoginException extends RundeckApiException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public RundeckApiLoginException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public RundeckApiLoginException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
231
src/main/java/org/rundeck/api/RundeckClient.java
Normal file
231
src/main/java/org/rundeck/api/RundeckClient.java
Normal file
|
@ -0,0 +1,231 @@
|
|||
package org.rundeck.api;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
|
||||
import org.rundeck.api.domain.RundeckExecution;
|
||||
import org.rundeck.api.domain.RundeckJob;
|
||||
import org.rundeck.api.parser.ExecutionParser;
|
||||
import org.rundeck.api.parser.ExecutionsParser;
|
||||
import org.rundeck.api.parser.JobParser;
|
||||
import org.rundeck.api.parser.JobsParser;
|
||||
import org.rundeck.api.util.ArgsUtil;
|
||||
import org.rundeck.api.util.AssertUtil;
|
||||
|
||||
/**
|
||||
* Main entry point to talk to a RunDeck instance
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class RundeckClient implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final transient int API_VERSION = 1;
|
||||
|
||||
public static final transient String API_ENDPOINT = "/api/" + API_VERSION;
|
||||
|
||||
private final String url;
|
||||
|
||||
private final String login;
|
||||
|
||||
private final String password;
|
||||
|
||||
/**
|
||||
* Instantiate a new {@link RundeckClient} for the RunDeck instance at the given url
|
||||
*
|
||||
* @param url of the RunDeck instance ("http://localhost:4440", "http://rundeck.your-compagny.com/", etc)
|
||||
* @param login
|
||||
* @param password
|
||||
*/
|
||||
public RundeckClient(String url, String login, String password) {
|
||||
super();
|
||||
this.url = url;
|
||||
this.login = login;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to "ping" the RunDeck instance to see if it is alive
|
||||
*
|
||||
* @throws RundeckApiException if the ping fails
|
||||
*/
|
||||
public void ping() throws RundeckApiException {
|
||||
new ApiCall(this).ping();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test your credentials (login/password) on the RunDeck instance
|
||||
*
|
||||
* @throws RundeckApiLoginException if the login fails
|
||||
*/
|
||||
public void testCredentials() throws RundeckApiLoginException {
|
||||
new ApiCall(this).testCredentials();
|
||||
}
|
||||
|
||||
/**
|
||||
* List all jobs that belongs to the given project
|
||||
*
|
||||
* @param project name of the project - mandatory
|
||||
* @return a {@link List} of {@link RundeckJob} : might be empty, but won't be null
|
||||
* @throws RundeckApiException in case of error when calling the API
|
||||
* @throws RundeckApiLoginException if the login failed
|
||||
* @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
|
||||
* @see #getJobs(String, String, String, String...)
|
||||
*/
|
||||
public List<RundeckJob> getJobs(String project) throws RundeckApiException, RundeckApiLoginException,
|
||||
IllegalArgumentException {
|
||||
return getJobs(project, null, null, new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* List 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 a {@link List} of {@link RundeckJob} : might be empty, but won't be null
|
||||
* @throws RundeckApiException in case of error when calling the API
|
||||
* @throws RundeckApiLoginException if the login failed
|
||||
* @throws IllegalArgumentException if the project is blank (null, empty or whitespace)
|
||||
* @see #getJobs(String)
|
||||
*/
|
||||
public List<RundeckJob> getJobs(String project, String jobFilter, String groupPath, String... jobIds)
|
||||
throws RundeckApiException, RundeckApiLoginException, IllegalArgumentException {
|
||||
AssertUtil.notBlank(project, "project is mandatory to get all jobs !");
|
||||
StringBuilder apiPath = new StringBuilder("/jobs");
|
||||
apiPath.append("?project=").append(project);
|
||||
if (StringUtils.isNotBlank(jobFilter)) {
|
||||
apiPath.append("&jobFilter=").append(jobFilter);
|
||||
}
|
||||
if (StringUtils.isNotBlank(groupPath)) {
|
||||
apiPath.append("&groupPath=").append(groupPath);
|
||||
}
|
||||
if (jobIds != null && jobIds.length > 0) {
|
||||
apiPath.append("&idlist=").append(StringUtils.join(jobIds, ","));
|
||||
}
|
||||
return new ApiCall(this).get(apiPath.toString(), new JobsParser("result/jobs/job"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the definition of a single job, identified by the given ID
|
||||
*
|
||||
* @param jobId identifier of the job - mandatory
|
||||
* @return a {@link RundeckJob} instance
|
||||
* @throws RundeckApiException in case of error when calling the API
|
||||
* @throws RundeckApiLoginException if the login failed
|
||||
* @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
|
||||
*/
|
||||
public RundeckJob getJob(String jobId) throws RundeckApiException, RundeckApiLoginException,
|
||||
IllegalArgumentException {
|
||||
AssertUtil.notBlank(jobId, "jobId is mandatory to get the details of a job !");
|
||||
return new ApiCall(this).get("/job/" + jobId, new JobParser("joblist/job"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the executions of the given job
|
||||
*
|
||||
* @param jobId identifier of the job - mandatory
|
||||
* @return a {@link List} of {@link RundeckExecution} : might be empty, but won't be null
|
||||
* @throws RundeckApiException in case of error when calling the API
|
||||
* @throws RundeckApiLoginException if the login failed
|
||||
* @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
|
||||
*/
|
||||
public List<RundeckExecution> getJobExecutions(String jobId) throws RundeckApiException, RundeckApiLoginException,
|
||||
IllegalArgumentException {
|
||||
AssertUtil.notBlank(jobId, "jobId is mandatory to get the executions of a job !");
|
||||
return new ApiCall(this).get("/job/" + jobId + "/executions",
|
||||
new ExecutionsParser("result/executions/execution"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single execution, identified by the given ID
|
||||
*
|
||||
* @param executionId identifier of the execution - mandatory
|
||||
* @return a {@link RundeckExecution} instance
|
||||
* @throws RundeckApiException in case of error when calling the API
|
||||
* @throws RundeckApiLoginException if the login failed
|
||||
* @throws IllegalArgumentException if the executionId is null
|
||||
*/
|
||||
public RundeckExecution getExecution(Long executionId) throws RundeckApiException, RundeckApiLoginException,
|
||||
IllegalArgumentException {
|
||||
AssertUtil.notNull(executionId, "executionId is mandatory to get the details of an execution !");
|
||||
return new ApiCall(this).get("/execution/" + executionId, new ExecutionParser("result/executions/execution"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the execution of a RunDeck job (identified by the given ID), and return immediately (without waiting the
|
||||
* end of the job execution)
|
||||
*
|
||||
* @param jobId identifier of the job - mandatory
|
||||
* @param options of the job - optional
|
||||
* @return a {@link RundeckExecution} instance representing the newly created (and running) execution
|
||||
* @throws RundeckApiException in case of error when calling the API
|
||||
* @throws RundeckApiLoginException if the login failed
|
||||
* @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace)
|
||||
*/
|
||||
public RundeckExecution triggerJob(String jobId, Properties options) throws RundeckApiException,
|
||||
RundeckApiLoginException, IllegalArgumentException {
|
||||
AssertUtil.notBlank(jobId, "jobId is mandatory to trigger a job !");
|
||||
String apiPath = "/job/" + jobId + "/run?argString=" + ArgsUtil.generateUrlEncodedArgString(options);
|
||||
return new ApiCall(this).get(apiPath, new ExecutionParser("result/executions/execution"));
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getLogin() {
|
||||
return login;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RundeckClient [url=" + url + ", login=" + login + ", password=" + password + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((login == null) ? 0 : login.hashCode());
|
||||
result = prime * result + ((password == null) ? 0 : password.hashCode());
|
||||
result = prime * result + ((url == null) ? 0 : url.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;
|
||||
RundeckClient other = (RundeckClient) obj;
|
||||
if (login == null) {
|
||||
if (other.login != null)
|
||||
return false;
|
||||
} else if (!login.equals(other.login))
|
||||
return false;
|
||||
if (password == null) {
|
||||
if (other.password != null)
|
||||
return false;
|
||||
} else if (!password.equals(other.password))
|
||||
return false;
|
||||
if (url == null) {
|
||||
if (other.url != null)
|
||||
return false;
|
||||
} else if (!url.equals(other.url))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
196
src/main/java/org/rundeck/api/domain/RundeckExecution.java
Normal file
196
src/main/java/org/rundeck/api/domain/RundeckExecution.java
Normal file
|
@ -0,0 +1,196 @@
|
|||
package org.rundeck.api.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Represents a RunDeck execution, usually triggered by an API call. An execution could be a {@link RundeckJob}
|
||||
* execution or an "ad-hoc" execution.
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class RundeckExecution implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private Long id;
|
||||
|
||||
private String url;
|
||||
|
||||
private ExecutionStatus status;
|
||||
|
||||
/** Optional - only if it is a job execution */
|
||||
private RundeckJob job;
|
||||
|
||||
private String startedBy;
|
||||
|
||||
private Date startedAt;
|
||||
|
||||
/** only if the execution has ended */
|
||||
private Date endedAt;
|
||||
|
||||
/** only if the execution was aborted */
|
||||
private String abortedBy;
|
||||
|
||||
private String description;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public ExecutionStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(ExecutionStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public RundeckJob getJob() {
|
||||
return job;
|
||||
}
|
||||
|
||||
public void setJob(RundeckJob job) {
|
||||
this.job = job;
|
||||
}
|
||||
|
||||
public String getStartedBy() {
|
||||
return startedBy;
|
||||
}
|
||||
|
||||
public void setStartedBy(String startedBy) {
|
||||
this.startedBy = startedBy;
|
||||
}
|
||||
|
||||
public Date getStartedAt() {
|
||||
return startedAt;
|
||||
}
|
||||
|
||||
public void setStartedAt(Date startedAt) {
|
||||
this.startedAt = startedAt;
|
||||
}
|
||||
|
||||
public Date getEndedAt() {
|
||||
return endedAt;
|
||||
}
|
||||
|
||||
public void setEndedAt(Date endedAt) {
|
||||
this.endedAt = endedAt;
|
||||
}
|
||||
|
||||
public String getAbortedBy() {
|
||||
return abortedBy;
|
||||
}
|
||||
|
||||
public void setAbortedBy(String abortedBy) {
|
||||
this.abortedBy = abortedBy;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RundeckExecution [abortedBy=" + abortedBy + ", description=" + description + ", endedAt=" + endedAt
|
||||
+ ", id=" + id + ", job=" + job + ", startedAt=" + startedAt + ", startedBy=" + startedBy + ", status="
|
||||
+ status + ", url=" + url + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((abortedBy == null) ? 0 : abortedBy.hashCode());
|
||||
result = prime * result + ((description == null) ? 0 : description.hashCode());
|
||||
result = prime * result + ((endedAt == null) ? 0 : endedAt.hashCode());
|
||||
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||
result = prime * result + ((job == null) ? 0 : job.hashCode());
|
||||
result = prime * result + ((startedAt == null) ? 0 : startedAt.hashCode());
|
||||
result = prime * result + ((startedBy == null) ? 0 : startedBy.hashCode());
|
||||
result = prime * result + ((status == null) ? 0 : status.hashCode());
|
||||
result = prime * result + ((url == null) ? 0 : url.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;
|
||||
RundeckExecution other = (RundeckExecution) obj;
|
||||
if (abortedBy == null) {
|
||||
if (other.abortedBy != null)
|
||||
return false;
|
||||
} else if (!abortedBy.equals(other.abortedBy))
|
||||
return false;
|
||||
if (description == null) {
|
||||
if (other.description != null)
|
||||
return false;
|
||||
} else if (!description.equals(other.description))
|
||||
return false;
|
||||
if (endedAt == null) {
|
||||
if (other.endedAt != null)
|
||||
return false;
|
||||
} else if (!endedAt.equals(other.endedAt))
|
||||
return false;
|
||||
if (id == null) {
|
||||
if (other.id != null)
|
||||
return false;
|
||||
} else if (!id.equals(other.id))
|
||||
return false;
|
||||
if (job == null) {
|
||||
if (other.job != null)
|
||||
return false;
|
||||
} else if (!job.equals(other.job))
|
||||
return false;
|
||||
if (startedAt == null) {
|
||||
if (other.startedAt != null)
|
||||
return false;
|
||||
} else if (!startedAt.equals(other.startedAt))
|
||||
return false;
|
||||
if (startedBy == null) {
|
||||
if (other.startedBy != null)
|
||||
return false;
|
||||
} else if (!startedBy.equals(other.startedBy))
|
||||
return false;
|
||||
if (status == null) {
|
||||
if (other.status != null)
|
||||
return false;
|
||||
} else if (!status.equals(other.status))
|
||||
return false;
|
||||
if (url == null) {
|
||||
if (other.url != null)
|
||||
return false;
|
||||
} else if (!url.equals(other.url))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The status of an execution
|
||||
*/
|
||||
public static enum ExecutionStatus {
|
||||
RUNNING, SUCCEEDED, FAILED, ABORTED;
|
||||
}
|
||||
|
||||
}
|
132
src/main/java/org/rundeck/api/domain/RundeckJob.java
Normal file
132
src/main/java/org/rundeck/api/domain/RundeckJob.java
Normal file
|
@ -0,0 +1,132 @@
|
|||
package org.rundeck.api.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* Represents a RunDeck job
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class RundeckJob implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private String group;
|
||||
|
||||
private String project;
|
||||
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* @return the fullname : group + name (exact format is : "group/name")
|
||||
*/
|
||||
public String getFullName() {
|
||||
StringBuilder fullName = new StringBuilder();
|
||||
if (StringUtils.isNotBlank(group)) {
|
||||
fullName.append(group).append("/");
|
||||
}
|
||||
fullName.append(name);
|
||||
return fullName.toString();
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getGroup() {
|
||||
return group;
|
||||
}
|
||||
|
||||
public void setGroup(String group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
public String getProject() {
|
||||
return project;
|
||||
}
|
||||
|
||||
public void setProject(String project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RundeckJob [id=" + id + ", name=" + name + ", group=" + group + ", project=" + project
|
||||
+ ", description=" + description + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((description == null) ? 0 : description.hashCode());
|
||||
result = prime * result + ((group == null) ? 0 : group.hashCode());
|
||||
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + ((project == null) ? 0 : project.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;
|
||||
RundeckJob other = (RundeckJob) obj;
|
||||
if (description == null) {
|
||||
if (other.description != null)
|
||||
return false;
|
||||
} else if (!description.equals(other.description))
|
||||
return false;
|
||||
if (group == null) {
|
||||
if (other.group != null)
|
||||
return false;
|
||||
} else if (!group.equals(other.group))
|
||||
return false;
|
||||
if (id == null) {
|
||||
if (other.id != null)
|
||||
return false;
|
||||
} else if (!id.equals(other.id))
|
||||
return false;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
} else if (!name.equals(other.name))
|
||||
return false;
|
||||
if (project == null) {
|
||||
if (other.project != null)
|
||||
return false;
|
||||
} else if (!project.equals(other.project))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
58
src/main/java/org/rundeck/api/parser/ExecutionParser.java
Normal file
58
src/main/java/org/rundeck/api/parser/ExecutionParser.java
Normal file
|
@ -0,0 +1,58 @@
|
|||
package org.rundeck.api.parser;
|
||||
|
||||
import java.util.Date;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.dom4j.Node;
|
||||
import org.rundeck.api.domain.RundeckExecution;
|
||||
import org.rundeck.api.domain.RundeckJob;
|
||||
import org.rundeck.api.domain.RundeckExecution.ExecutionStatus;
|
||||
|
||||
/**
|
||||
* Parser for a single {@link RundeckExecution}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class ExecutionParser implements NodeParser<RundeckExecution> {
|
||||
|
||||
private String xpath;
|
||||
|
||||
public ExecutionParser() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param xpath of the execution element if it is not the root node
|
||||
*/
|
||||
public ExecutionParser(String xpath) {
|
||||
super();
|
||||
this.xpath = xpath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RundeckExecution parseNode(Node node) {
|
||||
Node execNode = xpath != null ? node.selectSingleNode(xpath) : node;
|
||||
|
||||
RundeckExecution execution = new RundeckExecution();
|
||||
|
||||
execution.setId(Long.valueOf(execNode.valueOf("@id")));
|
||||
execution.setUrl(StringUtils.trimToNull(execNode.valueOf("@href")));
|
||||
execution.setStatus(ExecutionStatus.valueOf(StringUtils.upperCase(execNode.valueOf("@status"))));
|
||||
execution.setDescription(StringUtils.trimToNull(execNode.valueOf("description")));
|
||||
execution.setStartedBy(StringUtils.trimToNull(execNode.valueOf("user")));
|
||||
execution.setStartedAt(new Date(Long.valueOf(execNode.valueOf("date-started/@unixtime"))));
|
||||
execution.setAbortedBy(StringUtils.trimToNull(execNode.valueOf("abortedby")));
|
||||
String endedAt = StringUtils.trimToNull(execNode.valueOf("date-ended/@unixtime"));
|
||||
if (endedAt != null) {
|
||||
execution.setEndedAt(new Date(Long.valueOf(endedAt)));
|
||||
}
|
||||
|
||||
Node jobNode = execNode.selectSingleNode("job");
|
||||
if (jobNode != null) {
|
||||
RundeckJob job = new JobParser().parseNode(jobNode);
|
||||
execution.setJob(job);
|
||||
}
|
||||
|
||||
return execution;
|
||||
}
|
||||
|
||||
}
|
40
src/main/java/org/rundeck/api/parser/ExecutionsParser.java
Normal file
40
src/main/java/org/rundeck/api/parser/ExecutionsParser.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
package org.rundeck.api.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.dom4j.Node;
|
||||
import org.rundeck.api.domain.RundeckExecution;
|
||||
|
||||
/**
|
||||
* Parser for a {@link List} of {@link RundeckExecution}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class ExecutionsParser implements NodeParser<List<RundeckExecution>> {
|
||||
|
||||
private final String xpath;
|
||||
|
||||
/**
|
||||
* @param xpath of the executions elements
|
||||
*/
|
||||
public ExecutionsParser(String xpath) {
|
||||
super();
|
||||
this.xpath = xpath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RundeckExecution> parseNode(Node node) {
|
||||
List<RundeckExecution> executions = new ArrayList<RundeckExecution>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Node> execNodes = node.selectNodes(xpath);
|
||||
|
||||
for (Node execNode : execNodes) {
|
||||
RundeckExecution execution = new ExecutionParser().parseNode(execNode);
|
||||
executions.add(execution);
|
||||
}
|
||||
|
||||
return executions;
|
||||
}
|
||||
|
||||
}
|
57
src/main/java/org/rundeck/api/parser/JobParser.java
Normal file
57
src/main/java/org/rundeck/api/parser/JobParser.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
package org.rundeck.api.parser;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.dom4j.Node;
|
||||
import org.rundeck.api.domain.RundeckJob;
|
||||
|
||||
/**
|
||||
* Parser for a single {@link RundeckJob}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class JobParser implements NodeParser<RundeckJob> {
|
||||
|
||||
private String xpath;
|
||||
|
||||
public JobParser() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param xpath of the job element if it is not the root node
|
||||
*/
|
||||
public JobParser(String xpath) {
|
||||
super();
|
||||
this.xpath = xpath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RundeckJob parseNode(Node node) {
|
||||
Node jobNode = xpath != null ? node.selectSingleNode(xpath) : node;
|
||||
|
||||
RundeckJob job = new RundeckJob();
|
||||
|
||||
job.setName(StringUtils.trimToNull(jobNode.valueOf("name")));
|
||||
job.setDescription(StringUtils.trimToNull(jobNode.valueOf("description")));
|
||||
job.setGroup(StringUtils.trimToNull(jobNode.valueOf("group")));
|
||||
|
||||
// ID is either an attribute or an child element...
|
||||
String jobId = null;
|
||||
jobId = jobNode.valueOf("id");
|
||||
if (StringUtils.isBlank(jobId)) {
|
||||
jobId = jobNode.valueOf("@id");
|
||||
}
|
||||
job.setId(jobId);
|
||||
|
||||
// project is either a nested element of context, or just a child element
|
||||
Node contextNode = jobNode.selectSingleNode("context");
|
||||
if (contextNode != null) {
|
||||
job.setProject(StringUtils.trimToNull(contextNode.valueOf("project")));
|
||||
} else {
|
||||
job.setProject(StringUtils.trimToNull(jobNode.valueOf("project")));
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
}
|
40
src/main/java/org/rundeck/api/parser/JobsParser.java
Normal file
40
src/main/java/org/rundeck/api/parser/JobsParser.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
package org.rundeck.api.parser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.dom4j.Node;
|
||||
import org.rundeck.api.domain.RundeckJob;
|
||||
|
||||
/**
|
||||
* Parser for a {@link List} of {@link RundeckJob}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class JobsParser implements NodeParser<List<RundeckJob>> {
|
||||
|
||||
private final String xpath;
|
||||
|
||||
/**
|
||||
* @param xpath of the jobs elements
|
||||
*/
|
||||
public JobsParser(String xpath) {
|
||||
super();
|
||||
this.xpath = xpath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RundeckJob> parseNode(Node node) {
|
||||
List<RundeckJob> jobs = new ArrayList<RundeckJob>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Node> jobNodes = node.selectNodes(xpath);
|
||||
|
||||
for (Node jobNode : jobNodes) {
|
||||
RundeckJob job = new JobParser().parseNode(jobNode);
|
||||
jobs.add(job);
|
||||
}
|
||||
|
||||
return jobs;
|
||||
}
|
||||
|
||||
}
|
20
src/main/java/org/rundeck/api/parser/NodeParser.java
Normal file
20
src/main/java/org/rundeck/api/parser/NodeParser.java
Normal file
|
@ -0,0 +1,20 @@
|
|||
package org.rundeck.api.parser;
|
||||
|
||||
import org.dom4j.Node;
|
||||
|
||||
/**
|
||||
* Interface to be implemented for parsers that handle XML {@link Node}s
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public interface NodeParser<T> {
|
||||
|
||||
/**
|
||||
* Parse the given XML {@link Node}
|
||||
*
|
||||
* @param node
|
||||
* @return any object holding the converted value
|
||||
*/
|
||||
T parseNode(Node node);
|
||||
|
||||
}
|
72
src/main/java/org/rundeck/api/parser/ParserHelper.java
Normal file
72
src/main/java/org/rundeck/api/parser/ParserHelper.java
Normal file
|
@ -0,0 +1,72 @@
|
|||
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;
|
||||
import org.dom4j.io.SAXReader;
|
||||
import org.rundeck.api.RundeckApiException;
|
||||
|
||||
/**
|
||||
* Helper for parsing RunDeck responses
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
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();
|
||||
reader.setEncoding("UTF-8");
|
||||
|
||||
Document document;
|
||||
try {
|
||||
document = reader.read(inputStream);
|
||||
} catch (DocumentException e) {
|
||||
throw new RundeckApiException("Failed to read RunDeck reponse", e);
|
||||
}
|
||||
document.setXMLEncoding("UTF-8");
|
||||
|
||||
Node result = document.selectSingleNode("result");
|
||||
if (result != null) {
|
||||
Boolean failure = Boolean.valueOf(result.valueOf("@error"));
|
||||
if (failure) {
|
||||
throw new RundeckApiException(result.valueOf("error/message"));
|
||||
}
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
}
|
72
src/main/java/org/rundeck/api/util/ArgsUtil.java
Normal file
72
src/main/java/org/rundeck/api/util/ArgsUtil.java
Normal file
|
@ -0,0 +1,72 @@
|
|||
package org.rundeck.api.util;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Properties;
|
||||
import java.util.Map.Entry;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility class for RunDeck arguments
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class ArgsUtil {
|
||||
|
||||
/**
|
||||
* Generates and url-encode a RunDeck "argString" representing the given options. Format of the argString is
|
||||
* <code>"-key1 value1 -key2 'value 2 with spaces'"</code>
|
||||
*
|
||||
* @param options to be converted
|
||||
* @return an url-encoded string. null if options is null, empty if there are no valid options.
|
||||
* @see #generateArgString(Properties)
|
||||
*/
|
||||
public static String generateUrlEncodedArgString(Properties options) {
|
||||
String argString = generateArgString(options);
|
||||
if (StringUtils.isBlank(argString)) {
|
||||
return argString;
|
||||
}
|
||||
|
||||
try {
|
||||
return URLEncoder.encode(argString, "UTF8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a RunDeck "argString" representing the given options. Format of the argString is
|
||||
* <code>"-key1 value1 -key2 'value 2 with spaces'"</code>
|
||||
*
|
||||
* @param options to be converted
|
||||
* @return a string. null if options is null, empty if there are no valid options.
|
||||
* @see #generateUrlEncodedArgString(Properties)
|
||||
*/
|
||||
public static String generateArgString(Properties options) {
|
||||
if (options == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder argString = new StringBuilder();
|
||||
for (Entry<Object, Object> option : options.entrySet()) {
|
||||
String key = String.valueOf(option.getKey());
|
||||
String value = String.valueOf(option.getValue());
|
||||
|
||||
if (StringUtils.isNotBlank(key) && StringUtils.isNotBlank(value)) {
|
||||
if (argString.length() > 0) {
|
||||
argString.append(" ");
|
||||
}
|
||||
argString.append("-").append(key);
|
||||
argString.append(" ");
|
||||
if (value.indexOf(" ") >= 0
|
||||
&& !(0 == value.indexOf("'") && (value.length() - 1) == value.lastIndexOf("'"))) {
|
||||
argString.append("'").append(value).append("'");
|
||||
} else {
|
||||
argString.append(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return argString.toString();
|
||||
}
|
||||
|
||||
}
|
38
src/main/java/org/rundeck/api/util/AssertUtil.java
Normal file
38
src/main/java/org/rundeck/api/util/AssertUtil.java
Normal file
|
@ -0,0 +1,38 @@
|
|||
package org.rundeck.api.util;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility class for assertions
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class AssertUtil {
|
||||
|
||||
/**
|
||||
* Test if the given {@link Object} is null
|
||||
*
|
||||
* @param object
|
||||
* @param errorMessage to be used if the object is null
|
||||
* @throws IllegalArgumentException if the given object is null
|
||||
*/
|
||||
public static void notNull(Object object, String errorMessage) throws IllegalArgumentException {
|
||||
if (object == null) {
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the given {@link String} is blank (null, empty or only whitespace)
|
||||
*
|
||||
* @param input string
|
||||
* @param errorMessage to be used if the string is blank
|
||||
* @throws IllegalArgumentException if the given string is blank
|
||||
*/
|
||||
public static void notBlank(String input, String errorMessage) throws IllegalArgumentException {
|
||||
if (StringUtils.isBlank(input)) {
|
||||
throw new IllegalArgumentException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package org.rundeck.api.parser;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
import org.dom4j.Document;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.rundeck.api.domain.RundeckExecution;
|
||||
import org.rundeck.api.domain.RundeckJob;
|
||||
import org.rundeck.api.domain.RundeckExecution.ExecutionStatus;
|
||||
|
||||
/**
|
||||
* Test the {@link ExecutionParser}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class ExecutionParserTest {
|
||||
|
||||
@Test
|
||||
public void parseRunningNode() throws Exception {
|
||||
InputStream input = getClass().getResourceAsStream("execution-running.xml");
|
||||
Document document = ParserHelper.loadDocument(input);
|
||||
|
||||
RundeckExecution execution = new ExecutionParser("result/executions/execution").parseNode(document);
|
||||
RundeckJob job = execution.getJob();
|
||||
|
||||
Assert.assertEquals(new Long(1), execution.getId());
|
||||
Assert.assertEquals("http://localhost:4440/execution/follow/1", execution.getUrl());
|
||||
Assert.assertEquals(ExecutionStatus.RUNNING, execution.getStatus());
|
||||
Assert.assertEquals("admin", execution.getStartedBy());
|
||||
Assert.assertEquals(new Date(1302183830082L), execution.getStartedAt());
|
||||
Assert.assertEquals(null, execution.getEndedAt());
|
||||
Assert.assertEquals(null, execution.getAbortedBy());
|
||||
Assert.assertEquals("ls ${option.dir}", execution.getDescription());
|
||||
|
||||
Assert.assertEquals("1", job.getId());
|
||||
Assert.assertEquals("ls", job.getName());
|
||||
Assert.assertEquals("system", job.getGroup());
|
||||
Assert.assertEquals("test", job.getProject());
|
||||
Assert.assertEquals("list files", job.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseSucceededNode() throws Exception {
|
||||
InputStream input = getClass().getResourceAsStream("execution-succeeded.xml");
|
||||
Document document = ParserHelper.loadDocument(input);
|
||||
|
||||
RundeckExecution execution = new ExecutionParser("result/executions/execution").parseNode(document);
|
||||
RundeckJob job = execution.getJob();
|
||||
|
||||
Assert.assertEquals(new Long(1), execution.getId());
|
||||
Assert.assertEquals("http://localhost:4440/execution/follow/1", execution.getUrl());
|
||||
Assert.assertEquals(ExecutionStatus.SUCCEEDED, execution.getStatus());
|
||||
Assert.assertEquals("admin", execution.getStartedBy());
|
||||
Assert.assertEquals(new Date(1308322895104L), execution.getStartedAt());
|
||||
Assert.assertEquals(new Date(1308322959420L), execution.getEndedAt());
|
||||
Assert.assertEquals(null, execution.getAbortedBy());
|
||||
Assert.assertEquals("ls ${option.dir}", execution.getDescription());
|
||||
|
||||
Assert.assertEquals("1", job.getId());
|
||||
Assert.assertEquals("ls", job.getName());
|
||||
Assert.assertEquals("system", job.getGroup());
|
||||
Assert.assertEquals("test", job.getProject());
|
||||
Assert.assertEquals("list files", job.getDescription());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package org.rundeck.api.parser;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.dom4j.Document;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.rundeck.api.domain.RundeckExecution;
|
||||
import org.rundeck.api.domain.RundeckJob;
|
||||
import org.rundeck.api.domain.RundeckExecution.ExecutionStatus;
|
||||
|
||||
/**
|
||||
* Test the {@link ExecutionsParser}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class ExecutionsParserTest {
|
||||
|
||||
@Test
|
||||
public void parseNode() throws Exception {
|
||||
InputStream input = getClass().getResourceAsStream("executions.xml");
|
||||
Document document = ParserHelper.loadDocument(input);
|
||||
|
||||
List<RundeckExecution> executions = new ExecutionsParser("result/executions/execution").parseNode(document);
|
||||
Assert.assertEquals(2, executions.size());
|
||||
|
||||
RundeckExecution exec1 = executions.get(0);
|
||||
Assert.assertEquals(new Long(1), exec1.getId());
|
||||
Assert.assertEquals("http://localhost:4440/execution/follow/1", exec1.getUrl());
|
||||
Assert.assertEquals(ExecutionStatus.SUCCEEDED, exec1.getStatus());
|
||||
Assert.assertEquals("admin", exec1.getStartedBy());
|
||||
Assert.assertEquals(new Date(1308322895104L), exec1.getStartedAt());
|
||||
Assert.assertEquals(new Date(1308322959420L), exec1.getEndedAt());
|
||||
Assert.assertEquals(null, exec1.getAbortedBy());
|
||||
Assert.assertEquals("ls ${option.dir}", exec1.getDescription());
|
||||
|
||||
RundeckExecution exec2 = executions.get(1);
|
||||
Assert.assertEquals(new Long(2), exec2.getId());
|
||||
Assert.assertEquals("http://localhost:4440/execution/follow/2", exec2.getUrl());
|
||||
Assert.assertEquals(ExecutionStatus.SUCCEEDED, exec2.getStatus());
|
||||
Assert.assertEquals("admin", exec2.getStartedBy());
|
||||
Assert.assertEquals(new Date(1309524165388L), exec2.getStartedAt());
|
||||
Assert.assertEquals(new Date(1309524174635L), exec2.getEndedAt());
|
||||
Assert.assertEquals(null, exec2.getAbortedBy());
|
||||
Assert.assertEquals("ls ${option.dir}", exec2.getDescription());
|
||||
|
||||
RundeckJob job1 = exec1.getJob();
|
||||
Assert.assertEquals("1", job1.getId());
|
||||
Assert.assertEquals("ls", job1.getName());
|
||||
Assert.assertEquals("system", job1.getGroup());
|
||||
Assert.assertEquals("test", job1.getProject());
|
||||
Assert.assertEquals("list files", job1.getDescription());
|
||||
|
||||
RundeckJob job2 = exec2.getJob();
|
||||
Assert.assertEquals("1", job2.getId());
|
||||
Assert.assertEquals("ls", job2.getName());
|
||||
Assert.assertEquals("system", job2.getGroup());
|
||||
Assert.assertEquals("test", job2.getProject());
|
||||
Assert.assertEquals("list files", job2.getDescription());
|
||||
|
||||
Assert.assertEquals(job1, job2);
|
||||
}
|
||||
|
||||
}
|
30
src/test/java/org/rundeck/api/parser/JobParserTest.java
Normal file
30
src/test/java/org/rundeck/api/parser/JobParserTest.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
* Test the {@link JobParser}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class JobParserTest {
|
||||
|
||||
@Test
|
||||
public void parseNode() throws Exception {
|
||||
InputStream input = getClass().getResourceAsStream("job.xml");
|
||||
Document document = ParserHelper.loadDocument(input);
|
||||
|
||||
RundeckJob job = new JobParser("joblist/job").parseNode(document);
|
||||
|
||||
Assert.assertEquals("1", job.getId());
|
||||
Assert.assertEquals("job-name", job.getName());
|
||||
Assert.assertEquals("job description", job.getDescription());
|
||||
Assert.assertEquals("group-name", job.getGroup());
|
||||
Assert.assertEquals("project-name", job.getProject());
|
||||
}
|
||||
|
||||
}
|
40
src/test/java/org/rundeck/api/parser/JobsParserTest.java
Normal file
40
src/test/java/org/rundeck/api/parser/JobsParserTest.java
Normal file
|
@ -0,0 +1,40 @@
|
|||
package org.rundeck.api.parser;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import org.dom4j.Document;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.rundeck.api.domain.RundeckJob;
|
||||
|
||||
/**
|
||||
* Test the {@link JobsParser}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class JobsParserTest {
|
||||
|
||||
@Test
|
||||
public void parseNode() throws Exception {
|
||||
InputStream input = getClass().getResourceAsStream("jobs.xml");
|
||||
Document document = ParserHelper.loadDocument(input);
|
||||
|
||||
List<RundeckJob> jobs = new JobsParser("result/jobs/job").parseNode(document);
|
||||
Assert.assertEquals(2, jobs.size());
|
||||
|
||||
RundeckJob job1 = jobs.get(0);
|
||||
Assert.assertEquals("1", job1.getId());
|
||||
Assert.assertEquals("ls", job1.getName());
|
||||
Assert.assertEquals("list files", job1.getDescription());
|
||||
Assert.assertEquals("system", job1.getGroup());
|
||||
Assert.assertEquals("test", job1.getProject());
|
||||
|
||||
RundeckJob job2 = jobs.get(1);
|
||||
Assert.assertEquals("2", job2.getId());
|
||||
Assert.assertEquals("ps", job2.getName());
|
||||
Assert.assertEquals("list processes", job2.getDescription());
|
||||
Assert.assertEquals("system", job2.getGroup());
|
||||
Assert.assertEquals("test", job2.getProject());
|
||||
}
|
||||
|
||||
}
|
50
src/test/java/org/rundeck/api/parser/ParserHelperTest.java
Normal file
50
src/test/java/org/rundeck/api/parser/ParserHelperTest.java
Normal file
|
@ -0,0 +1,50 @@
|
|||
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.RundeckApiException;
|
||||
|
||||
/**
|
||||
* Test the {@link ParserHelper}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class ParserHelperTest {
|
||||
|
||||
/**
|
||||
* XML with an explicit "error" result should throw an exception
|
||||
*/
|
||||
@Test
|
||||
public void loadErrorDocument() throws Exception {
|
||||
InputStream input = getClass().getResourceAsStream("error.xml");
|
||||
try {
|
||||
ParserHelper.loadDocument(input);
|
||||
Assert.fail("should have thrown an exception !");
|
||||
} catch (RundeckApiException e) {
|
||||
Assert.assertEquals("This is the error message", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* XML with an explicit "success" result should NOT throw an exception
|
||||
*/
|
||||
@Test
|
||||
public void loadSuccessDocument() throws Exception {
|
||||
InputStream input = getClass().getResourceAsStream("success.xml");
|
||||
Document document = ParserHelper.loadDocument(input);
|
||||
Assert.assertNotNull(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* XML with no result should NOT throw an exception
|
||||
*/
|
||||
@Test
|
||||
public void loadEmptyDocument() throws Exception {
|
||||
InputStream input = getClass().getResourceAsStream("empty.xml");
|
||||
Document document = ParserHelper.loadDocument(input);
|
||||
Assert.assertNotNull(document);
|
||||
}
|
||||
|
||||
}
|
30
src/test/java/org/rundeck/api/util/ArgsUtilTest.java
Normal file
30
src/test/java/org/rundeck/api/util/ArgsUtilTest.java
Normal file
|
@ -0,0 +1,30 @@
|
|||
package org.rundeck.api.util;
|
||||
|
||||
import java.util.Properties;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test the {@link ArgsUtil}
|
||||
*
|
||||
* @author Vincent Behar
|
||||
*/
|
||||
public class ArgsUtilTest {
|
||||
|
||||
@Test
|
||||
public void generateArgString() throws Exception {
|
||||
Assert.assertNull(ArgsUtil.generateArgString(null));
|
||||
Assert.assertEquals("", ArgsUtil.generateArgString(new Properties()));
|
||||
|
||||
Properties options = new Properties();
|
||||
options.put("key1", "value1");
|
||||
options.put("key2", "value 2 with spaces");
|
||||
String argString = ArgsUtil.generateArgString(options);
|
||||
if (argString.startsWith("-key1")) {
|
||||
Assert.assertEquals("-key1 value1 -key2 'value 2 with spaces'", argString);
|
||||
} else {
|
||||
Assert.assertEquals("-key2 'value 2 with spaces' -key1 value1", argString);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1
src/test/resources/org/rundeck/api/parser/empty.xml
Normal file
1
src/test/resources/org/rundeck/api/parser/empty.xml
Normal file
|
@ -0,0 +1 @@
|
|||
<whatever></whatever>
|
1
src/test/resources/org/rundeck/api/parser/error.xml
Normal file
1
src/test/resources/org/rundeck/api/parser/error.xml
Normal file
|
@ -0,0 +1 @@
|
|||
<result error="true" apiversion="1"><error><message>This is the error message</message></error></result>
|
|
@ -0,0 +1 @@
|
|||
<result success='true' apiversion='1'><executions count='1'><execution id='1' href='http://localhost:4440/execution/follow/1' status='running'><user>admin</user><date-started unixtime='1302183830082'>2011-04-07T13:43:50Z</date-started><job id='1'><name>ls</name><group>system</group><project>test</project><description>list files</description></job><description>ls ${option.dir}</description></execution></executions></result>
|
|
@ -0,0 +1 @@
|
|||
<result success='true' apiversion='1'><executions count='1'><execution id='1' href='http://localhost:4440/execution/follow/1' status='succeeded'><user>admin</user><date-started unixtime="1308322895104">2011-06-17T15:01:35Z</date-started><date-ended unixtime="1308322959420">2011-06-17T15:02:39Z</date-ended><job id='1'><name>ls</name><group>system</group><project>test</project><description>list files</description></job><description>ls ${option.dir}</description></execution></executions></result>
|
1
src/test/resources/org/rundeck/api/parser/executions.xml
Normal file
1
src/test/resources/org/rundeck/api/parser/executions.xml
Normal file
|
@ -0,0 +1 @@
|
|||
<result success='true' apiversion='1'><executions count='2'><execution id='1' href='http://localhost:4440/execution/follow/1' status='succeeded'><user>admin</user><date-started unixtime="1308322895104">2011-06-17T15:01:35Z</date-started><date-ended unixtime="1308322959420">2011-06-17T15:02:39Z</date-ended><job id='1'><name>ls</name><group>system</group><project>test</project><description>list files</description></job><description>ls ${option.dir}</description></execution><execution id='2' href='http://localhost:4440/execution/follow/2' status='succeeded'><user>admin</user><date-started unixtime="1309524165388">2011-07-01T12:42:45Z</date-started><date-ended unixtime="1309524174635">2011-07-01T12:42:54Z</date-ended><job id='1'><name>ls</name><group>system</group><project>test</project><description>list files</description></job><description>ls ${option.dir}</description></execution></executions></result>
|
62
src/test/resources/org/rundeck/api/parser/job.xml
Normal file
62
src/test/resources/org/rundeck/api/parser/job.xml
Normal file
|
@ -0,0 +1,62 @@
|
|||
<joblist>
|
||||
<job>
|
||||
<id>1</id>
|
||||
<schedule>
|
||||
<time hour='20' seconds='0' minute='42' />
|
||||
<weekday day='*' />
|
||||
<month month='*' />
|
||||
<year year='*' />
|
||||
</schedule>
|
||||
<loglevel>INFO</loglevel>
|
||||
<sequence keepgoing='false' strategy='node-first'>
|
||||
<command>
|
||||
<exec>echo ${option.opt1} ${option.opt2} ${option.opt3}</exec>
|
||||
</command>
|
||||
<command>
|
||||
<scriptargs>options</scriptargs>
|
||||
<script><![CDATA[script]]></script>
|
||||
</command>
|
||||
</sequence>
|
||||
<description>job description</description>
|
||||
<name>job-name</name>
|
||||
<context>
|
||||
<project>project-name</project>
|
||||
<options>
|
||||
<option name='opt1'>
|
||||
<description>description for opt1</description>
|
||||
</option>
|
||||
<option name='opt2' required='true' value='default value'>
|
||||
<description>description for opt2</description>
|
||||
</option>
|
||||
<option name='opt3' enforcedvalues='true' value='value1, value2'>
|
||||
<description>description for opt3</description>
|
||||
<multivalued>true</multivalued>
|
||||
<delimiter>|</delimiter>
|
||||
</option>
|
||||
<option name='opt4' regex='plop' valuesUrl='http://www.google.com'>
|
||||
<description>description for opt4</description>
|
||||
</option>
|
||||
</options>
|
||||
</context>
|
||||
<notification>
|
||||
<onsuccess>
|
||||
<email recipients='mail1@toto.com' />
|
||||
</onsuccess>
|
||||
<onfailure>
|
||||
<email recipients='mail2@toto.com' />
|
||||
</onfailure>
|
||||
</notification>
|
||||
<dispatch>
|
||||
<threadcount>2</threadcount>
|
||||
<keepgoing>true</keepgoing>
|
||||
<excludePrecedence>true</excludePrecedence>
|
||||
</dispatch>
|
||||
<nodefilters>
|
||||
<include>
|
||||
<tags>tag</tags>
|
||||
<name>name</name>
|
||||
</include>
|
||||
</nodefilters>
|
||||
<group>group-name</group>
|
||||
</job>
|
||||
</joblist>
|
1
src/test/resources/org/rundeck/api/parser/jobs.xml
Normal file
1
src/test/resources/org/rundeck/api/parser/jobs.xml
Normal file
|
@ -0,0 +1 @@
|
|||
<result success='true' apiversion='1'><jobs count='2'><job id='1'><name>ls</name><group>system</group><project>test</project><description>list files</description></job><job id='2'><name>ps</name><group>system</group><project>test</project><description>list processes</description></job></jobs></result>
|
1
src/test/resources/org/rundeck/api/parser/success.xml
Normal file
1
src/test/resources/org/rundeck/api/parser/success.xml
Normal file
|
@ -0,0 +1 @@
|
|||
<result success='true' apiversion='1'></result>
|
Loading…
Add table
Add a link
Reference in a new issue