Add Builder for RundeckClient

support multiple API versions
This commit is contained in:
Greg Schueler 2013-01-03 11:45:26 -08:00
parent 98aa9238cf
commit 00268997c1
4 changed files with 237 additions and 59 deletions

View file

@ -15,22 +15,7 @@
*/ */
package org.rundeck.api; package org.rundeck.api;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.ProxySelector;
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 java.util.Map.Entry;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.cookie.Cookie;
import org.apache.http.Header; import org.apache.http.Header;
import org.apache.http.HttpException; import org.apache.http.HttpException;
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
@ -38,7 +23,6 @@ import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair; import org.apache.http.NameValuePair;
import org.apache.http.ParseException; import org.apache.http.ParseException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpDelete;
@ -65,6 +49,21 @@ import org.rundeck.api.parser.ParserHelper;
import org.rundeck.api.parser.XmlNodeParser; import org.rundeck.api.parser.XmlNodeParser;
import org.rundeck.api.util.AssertUtil; import org.rundeck.api.util.AssertUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.ProxySelector;
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 java.util.Map.Entry;
/** /**
* Class responsible for making the HTTP API calls * Class responsible for making the HTTP API calls
* *
@ -157,7 +156,7 @@ class ApiCall {
*/ */
public void testTokenAuth() throws RundeckApiTokenException { public void testTokenAuth() throws RundeckApiTokenException {
try { try {
execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + "/system/info")); execute(new HttpGet(client.getUrl() + client.getApiEndpoint() + "/system/info"));
} catch (RundeckApiTokenException e) { } catch (RundeckApiTokenException e) {
throw e; throw e;
} catch (RundeckApiException e) { } catch (RundeckApiException e) {
@ -178,7 +177,7 @@ class ApiCall {
*/ */
public <T> T get(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException, public <T> T get(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException, RundeckApiTokenException { RundeckApiLoginException, RundeckApiTokenException {
return execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), parser); return execute(new HttpGet(client.getUrl() + client.getApiEndpoint() + apiPath), parser);
} }
/** /**
@ -193,7 +192,7 @@ class ApiCall {
*/ */
public InputStream get(ApiPathBuilder apiPath) throws RundeckApiException, RundeckApiLoginException, public InputStream get(ApiPathBuilder apiPath) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException { RundeckApiTokenException {
ByteArrayInputStream response = execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath)); ByteArrayInputStream response = execute(new HttpGet(client.getUrl() + client.getApiEndpoint() + apiPath));
// try to load the document, to throw an exception in case of error // try to load the document, to throw an exception in case of error
ParserHelper.loadDocument(response); ParserHelper.loadDocument(response);
@ -257,7 +256,7 @@ class ApiCall {
*/ */
public <T> T post(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException, public <T> T post(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException, RundeckApiTokenException { RundeckApiLoginException, RundeckApiTokenException {
HttpPost httpPost = new HttpPost(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath); HttpPost httpPost = new HttpPost(client.getUrl() + client.getApiEndpoint() + apiPath);
// POST a multi-part request, with all attachments // POST a multi-part request, with all attachments
if(apiPath.getAttachments().size()>0){ if(apiPath.getAttachments().size()>0){
@ -292,7 +291,7 @@ class ApiCall {
*/ */
public <T> T delete(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException, public <T> T delete(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException, RundeckApiTokenException { RundeckApiLoginException, RundeckApiTokenException {
return execute(new HttpDelete(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), parser); return execute(new HttpDelete(client.getUrl() + client.getApiEndpoint() + apiPath), parser);
} }
/** /**
@ -470,7 +469,7 @@ class ApiCall {
DefaultHttpClient httpClient = new DefaultHttpClient(); DefaultHttpClient httpClient = new DefaultHttpClient();
// configure user-agent // configure user-agent
HttpProtocolParams.setUserAgent(httpClient.getParams(), "RunDeck API Java Client " + RundeckClient.API_VERSION); HttpProtocolParams.setUserAgent(httpClient.getParams(), "RunDeck API Java Client " + client.getApiVersion());
// configure SSL // configure SSL
SSLSocketFactory socketFactory = null; SSLSocketFactory socketFactory = null;

View file

@ -15,16 +15,6 @@
*/ */
package org.rundeck.api; package org.rundeck.api;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
@ -38,6 +28,17 @@ import org.rundeck.api.util.AssertUtil;
import org.rundeck.api.util.PagedResults; import org.rundeck.api.util.PagedResults;
import org.rundeck.api.util.ParametersUtil; import org.rundeck.api.util.ParametersUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
/** /**
* Main entry point to talk to a RunDeck instance.<br> * Main entry point to talk to a RunDeck instance.<br>
* You have 2 methods for authentication : login-based or token-based. If you want to use the first, you need to provide * You have 2 methods for authentication : login-based or token-based. If you want to use the first, you need to provide
@ -71,11 +72,31 @@ public class RundeckClient implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* Supported version numbers
*/
public static enum Version {
V5(5),
V6(6),
;
private int versionNumber;
Version(final int i) {
versionNumber = i;
}
public int getVersionNumber() {
return versionNumber;
}
}
/** Version of the API supported */ /** Version of the API supported */
public static final transient int API_VERSION = 5; public static final transient int API_VERSION = Version.V6.getVersionNumber();
private static final String API = "/api/";
/** End-point of the API */ /** End-point of the API */
public static final transient String API_ENDPOINT = "/api/" + API_VERSION; public static final transient String API_ENDPOINT = API + API_VERSION;
/** Default value for the "pooling interval" used when running jobs/commands/scripts */ /** Default value for the "pooling interval" used when running jobs/commands/scripts */
private static final transient long DEFAULT_POOLING_INTERVAL = 5; private static final transient long DEFAULT_POOLING_INTERVAL = 5;
@ -86,17 +107,48 @@ public class RundeckClient implements Serializable {
/** URL of the RunDeck instance ("http://localhost:4440", "http://rundeck.your-compagny.com/", etc) */ /** URL of the RunDeck instance ("http://localhost:4440", "http://rundeck.your-compagny.com/", etc) */
private final String url; private final String url;
/** Auth-token for authentication (if not using login-based auth) */ private int apiVersion = API_VERSION;
private final String token;
/** Login to use for authentication on the RunDeck instance (if not using token-based auth) */ private String token;
private final String login;
/** Password to use for authentication on the RunDeck instance (if not using token-based auth) */ private String login;
private final String password;
private String password;
private String sessionID; private String sessionID;
void setToken(String token) {
this.token = token;
}
void setLogin(String login) {
this.login = login;
}
void setPassword(String password) {
this.password = password;
}
void setSessionID(String sessionID) {
this.sessionID = sessionID;
}
int getApiVersion() {
return apiVersion;
}
void setApiVersion(int apiVersion) {
this.apiVersion = apiVersion;
}
void setApiVersion(Version apiVersion) {
this.apiVersion = apiVersion.getVersionNumber();
}
String getApiEndpoint() {
return API + getApiVersion();
}
/** /**
* Instantiate a new {@link RundeckClient} for the RunDeck instance at the given url, using login-based * Instantiate a new {@link RundeckClient} for the RunDeck instance at the given url, using login-based
* authentication. * authentication.
@ -107,11 +159,9 @@ public class RundeckClient implements Serializable {
* @throws IllegalArgumentException if the url, login or password is blank (null, empty or whitespace) * @throws IllegalArgumentException if the url, login or password is blank (null, empty or whitespace)
*/ */
public RundeckClient(String url, String login, String password) throws IllegalArgumentException { public RundeckClient(String url, String login, String password) throws IllegalArgumentException {
super(); this(url);
AssertUtil.notBlank(url, "The RunDeck URL is mandatory !");
AssertUtil.notBlank(login, "The RunDeck login is mandatory !"); AssertUtil.notBlank(login, "The RunDeck login is mandatory !");
AssertUtil.notBlank(password, "The RunDeck password is mandatory !"); AssertUtil.notBlank(password, "The RunDeck password is mandatory !");
this.url = url;
this.login = login; this.login = login;
this.password = password; this.password = password;
this.token = null; this.token = null;
@ -126,10 +176,11 @@ public class RundeckClient implements Serializable {
* @param sessionID to use for session authentication on the RunDeck instance * @param sessionID to use for session authentication on the RunDeck instance
* @param useToken should be true if using token, false if using sessionID * @param useToken should be true if using token, false if using sessionID
* @throws IllegalArgumentException if the url or token is blank (null, empty or whitespace) * @throws IllegalArgumentException if the url or token is blank (null, empty or whitespace)
* @deprecated Use the builder {@link RundeckClientBuilder}
*/ */
public RundeckClient(String url, String token, String sessionID, boolean useToken) throws IllegalArgumentException { public RundeckClient(String url, String token, String sessionID, boolean useToken) throws IllegalArgumentException {
super(); this(url);
AssertUtil.notBlank(url, "The RunDeck URL is mandatory !");
if(useToken){ if(useToken){
AssertUtil.notBlank(token, "Token is mandatory!"); AssertUtil.notBlank(token, "Token is mandatory!");
this.token = token; this.token = token;
@ -140,7 +191,6 @@ public class RundeckClient implements Serializable {
this.sessionID = sessionID; this.sessionID = sessionID;
this.token = null; this.token = null;
} }
this.url = url;
this.login = null; this.login = null;
this.password = null; this.password = null;
} }
@ -150,6 +200,20 @@ public class RundeckClient implements Serializable {
this(url, token, null, true); this(url, token, null, true);
} }
/**
* Used by RundeckClientBuilder
*/
RundeckClient(final String url) throws IllegalArgumentException {
AssertUtil.notBlank(url, "The RunDeck URL is mandatory !");
this.url=url;
}
/**
* Create a builder for RundeckClient
*/
public static RundeckClientBuilder builder() {
return new RundeckClientBuilder();
}
/** /**
* Try to "ping" the RunDeck instance to see if it is alive * Try to "ping" the RunDeck instance to see if it is alive

View file

@ -0,0 +1,104 @@
package org.rundeck.api;
import org.rundeck.api.util.AssertUtil;
/**
* Builder to create a {@link RundeckClient}, you must specify a url, and at least one of (login, password), token, or
* sessionId.
*/
public class RundeckClientBuilder {
private String url;
private String login;
private String password;
private String token = null;
private String id = null;
private int version = -1;
RundeckClientBuilder(){
}
/**
* Specify the URL
*/
public RundeckClientBuilder url(String url) {
this.url = url;
return this;
}
public RundeckClientBuilder login(String login) {
this.login = login;
return this;
}
public RundeckClientBuilder login(String login, String password) {
this.login = login;
this.password = password;
return this;
}
public RundeckClientBuilder password(String password) {
this.password = password;
return this;
}
/**
* Specify a Rundeck API Token string for authentication
*/
public RundeckClientBuilder token(String token) {
this.token = token;
return this;
}
/**
* Specify a web session ID string for authentication
*/
public RundeckClientBuilder sessionId(String id) {
this.id = id;
return this;
}
/**
* Specify another version number to use
*/
public RundeckClientBuilder version(final RundeckClient.Version version) {
this.version = version.getVersionNumber();
return this;
}
/**
* Specify another version number to use
*/
public RundeckClientBuilder version(final int version) {
this.version = version;
return this;
}
/**
* Create the RundeckClient instance
*/
public RundeckClient build() {
if (null == url) {
AssertUtil.notBlank(url, "The Rundeck URL is required");
}
final RundeckClient client = new RundeckClient(url);
if (null != login && null != password) {
AssertUtil.notBlank(login, "login cannot be blank");
AssertUtil.notBlank(password, "password cannot be blank");
client.setLogin(login);
client.setPassword(password);
} else if (null != token) {
AssertUtil.notBlank(token, "token cannot be blank");
client.setToken(token);
} else if (null != id) {
AssertUtil.notBlank(token, "sessionId cannot be blank");
client.setSessionID(id);
} else {
throw new IllegalStateException("login/password, token, or sessionID must be specified");
}
if (version > 0) {
client.setApiVersion(version);
}
return client;
}
}

View file

@ -15,12 +15,9 @@
*/ */
package org.rundeck.api; package org.rundeck.api;
import java.util.ArrayList; import betamax.Betamax;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import betamax.MatchRule; import betamax.MatchRule;
import betamax.Recorder;
import betamax.TapeMode; import betamax.TapeMode;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -32,11 +29,14 @@ import org.rundeck.api.domain.RundeckHistory;
import org.rundeck.api.domain.RundeckJobDelete; import org.rundeck.api.domain.RundeckJobDelete;
import org.rundeck.api.domain.RundeckJobDeleteBulk; import org.rundeck.api.domain.RundeckJobDeleteBulk;
import org.rundeck.api.domain.RundeckProject; import org.rundeck.api.domain.RundeckProject;
import betamax.Betamax;
import betamax.Recorder;
import org.rundeck.api.query.ExecutionQuery; import org.rundeck.api.query.ExecutionQuery;
import org.rundeck.api.util.PagedResults; import org.rundeck.api.util.PagedResults;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/** /**
* Test the {@link RundeckClient}. Uses betamax to unit-test HTTP requests without a live RunDeck instance. * Test the {@link RundeckClient}. Uses betamax to unit-test HTTP requests without a live RunDeck instance.
@ -45,6 +45,10 @@ import org.rundeck.api.util.PagedResults;
*/ */
public class RundeckClientTest { public class RundeckClientTest {
public static final String TEST_TOKEN_0 = "PVnN5K3OPc5vduS3uVuVnEsD57pDC5pd";
public static final String TEST_TOKEN_1 = "0UUNkeRp4d58EDeCs7S6UdODp334DvK9";
public static final String TEST_TOKEN_2 = "PP4s4SdCRO6KUoNPd1D303Dc304ORN87";
@Rule @Rule
public Recorder recorder = new Recorder(); public Recorder recorder = new Recorder();
@ -132,7 +136,7 @@ public class RundeckClientTest {
match = {MatchRule.uri, MatchRule.headers, MatchRule.method, MatchRule.path, MatchRule.query}) match = {MatchRule.uri, MatchRule.headers, MatchRule.method, MatchRule.path, MatchRule.query})
public void getExecutions() throws Exception { public void getExecutions() throws Exception {
RundeckClient client = new RundeckClient("http://rundeck.local:4440", "0UUNkeRp4d58EDeCs7S6UdODp334DvK9"); RundeckClient client = createClient(TEST_TOKEN_1);
final String projectName = "blah"; final String projectName = "blah";
@ -259,7 +263,7 @@ public class RundeckClientTest {
@Test @Test
@Betamax(tape = "get_executions_paging") @Betamax(tape = "get_executions_paging")
public void getExecutionsPaging() throws Exception{ public void getExecutionsPaging() throws Exception{
RundeckClient client = new RundeckClient("http://rundeck.local:4440", "0UUNkeRp4d58EDeCs7S6UdODp334DvK9"); RundeckClient client = createClient(TEST_TOKEN_1);
final String projectName = "blah"; final String projectName = "blah";
//2 max, 1 offset //2 max, 1 offset
final PagedResults<RundeckExecution> adhocTest = client.getExecutions(ExecutionQuery.builder() final PagedResults<RundeckExecution> adhocTest = client.getExecutions(ExecutionQuery.builder()
@ -290,7 +294,7 @@ public class RundeckClientTest {
@Test @Test
@Betamax(tape = "bulk_delete") @Betamax(tape = "bulk_delete")
public void bulkDelete() throws Exception { public void bulkDelete() throws Exception {
RundeckClient client = new RundeckClient("http://rundeck.local:4440", "PP4s4SdCRO6KUoNPd1D303Dc304ORN87"); RundeckClient client = createClient(TEST_TOKEN_2);
final RundeckJobDeleteBulk deleteTest final RundeckJobDeleteBulk deleteTest
= client.deleteJobs(Arrays.asList("0ce457b5-ba84-41ca-812e-02b31da355a4")); = client.deleteJobs(Arrays.asList("0ce457b5-ba84-41ca-812e-02b31da355a4"));
@ -308,7 +312,7 @@ public class RundeckClientTest {
@Test @Test
@Betamax(tape = "bulk_delete_dne") @Betamax(tape = "bulk_delete_dne")
public void bulkDeleteFailDNE() throws Exception { public void bulkDeleteFailDNE() throws Exception {
RundeckClient client = new RundeckClient("http://rundeck.local:4440", "PP4s4SdCRO6KUoNPd1D303Dc304ORN87"); RundeckClient client = createClient(TEST_TOKEN_2);
final RundeckJobDeleteBulk deleteTest final RundeckJobDeleteBulk deleteTest
= client.deleteJobs(Arrays.asList("does-not-exist")); = client.deleteJobs(Arrays.asList("does-not-exist"));
@ -326,7 +330,7 @@ public class RundeckClientTest {
@Test @Test
@Betamax(tape = "bulk_delete_unauthorized") @Betamax(tape = "bulk_delete_unauthorized")
public void bulkDeleteFailUnauthorized() throws Exception { public void bulkDeleteFailUnauthorized() throws Exception {
RundeckClient client = new RundeckClient("http://rundeck.local:4440", "PP4s4SdCRO6KUoNPd1D303Dc304ORN87"); RundeckClient client = createClient(TEST_TOKEN_2);
final RundeckJobDeleteBulk deleteTest final RundeckJobDeleteBulk deleteTest
= client.deleteJobs(Arrays.asList("3a6d16be-4268-4d26-86a9-cebc1781f768")); = client.deleteJobs(Arrays.asList("3a6d16be-4268-4d26-86a9-cebc1781f768"));
@ -356,7 +360,14 @@ public class RundeckClientTest {
public void setUp() throws Exception { public void setUp() throws Exception {
// not that you can put whatever here, because we don't actually connect to the RunDeck instance // not that you can put whatever here, because we don't actually connect to the RunDeck instance
// but instead use betamax as a proxy to serve the previously recorded tapes (in src/test/resources) // but instead use betamax as a proxy to serve the previously recorded tapes (in src/test/resources)
client = new RundeckClient("http://rundeck.local:4440", "PVnN5K3OPc5vduS3uVuVnEsD57pDC5pd"); client = createClient(TEST_TOKEN_0);
}
private RundeckClient createClient(final String token) {
return RundeckClient.builder().url("http://rundeck.local:4440")
.token(token)
.version(5)
.build();
} }
} }