start working on a new-style api (fluent api / method chaining)

This commit is contained in:
Vincent Behar 2011-08-01 17:29:58 +02:00
parent d28b7d7064
commit 564a3ce90f
13 changed files with 1208 additions and 92 deletions

View file

@ -66,6 +66,7 @@ import org.rundeck.api.util.AssertUtil;
* *
* @author Vincent Behar * @author Vincent Behar
*/ */
@Deprecated
class ApiCall { class ApiCall {
/** RunDeck HTTP header for the auth-token (in case of token-based authentication) */ /** RunDeck HTTP header for the auth-token (in case of token-based authentication) */
@ -158,14 +159,14 @@ class ApiCall {
* Execute an HTTP GET request to the RunDeck instance, on the given path. We will login first, and then execute the * 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. * 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 apiPath on which we will make the HTTP request - see {@link OldApiPathBuilder}
* @param parser used to parse the response * @param parser used to parse the response
* @return the result of the call, as formatted by the parser * @return the result of the call, as formatted by the parser
* @throws RundeckApiException in case of error when calling the API * @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication) * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication) * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/ */
public <T> T get(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException, public <T> T get(OldApiPathBuilder 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() + RundeckClient.API_ENDPOINT + apiPath), parser);
} }
@ -174,13 +175,13 @@ class ApiCall {
* Execute an HTTP GET request to the RunDeck instance, on the given path. We will login first, and then execute the * Execute an HTTP GET request to the RunDeck instance, on the given path. We will login first, and then execute the
* API call. * API call.
* *
* @param apiPath on which we will make the HTTP request - see {@link ApiPathBuilder} * @param apiPath on which we will make the HTTP request - see {@link OldApiPathBuilder}
* @return a new {@link InputStream} instance, not linked with network resources * @return a new {@link InputStream} instance, not linked with network resources
* @throws RundeckApiException in case of error when calling the API * @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication) * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication) * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/ */
public InputStream get(ApiPathBuilder apiPath) throws RundeckApiException, RundeckApiLoginException, public InputStream get(OldApiPathBuilder apiPath) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException { RundeckApiTokenException {
ByteArrayInputStream response = execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath)); ByteArrayInputStream response = execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath));
@ -195,14 +196,14 @@ class ApiCall {
* Execute an HTTP POST request to the RunDeck instance, on the given path. We will login first, and then execute * 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. * 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 apiPath on which we will make the HTTP request - see {@link OldApiPathBuilder}
* @param parser used to parse the response * @param parser used to parse the response
* @return the result of the call, as formatted by the parser * @return the result of the call, as formatted by the parser
* @throws RundeckApiException in case of error when calling the API * @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication) * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication) * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/ */
public <T> T post(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException, public <T> T post(OldApiPathBuilder 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() + RundeckClient.API_ENDPOINT + apiPath);
@ -220,14 +221,14 @@ class ApiCall {
* Execute an HTTP DELETE request to the RunDeck instance, on the given path. We will login first, and then execute * 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. * 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 apiPath on which we will make the HTTP request - see {@link OldApiPathBuilder}
* @param parser used to parse the response * @param parser used to parse the response
* @return the result of the call, as formatted by the parser * @return the result of the call, as formatted by the parser
* @throws RundeckApiException in case of error when calling the API * @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication) * @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication) * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/ */
public <T> T delete(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException, public <T> T delete(OldApiPathBuilder 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() + RundeckClient.API_ENDPOINT + apiPath), parser);
} }

View file

@ -0,0 +1,220 @@
/*
* 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;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.rundeck.api.util.ParametersUtil;
/**
* Builder for API paths
*
* @author Vincent Behar
*/
@Deprecated
class OldApiPathBuilder {
/** Internally, we store everything in a {@link StringBuilder} */
private final StringBuilder apiPath;
/** 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;
/**
* Build a new instance, for the given "path" (the "path" is the part before the parameters. The path and the
* parameters are separated by a "?")
*
* @param paths elements of the path
*/
public OldApiPathBuilder(String... paths) {
apiPath = new StringBuilder();
attachments = new HashMap<String, InputStream>();
if (paths != null) {
for (String path : paths) {
if (StringUtils.isNotBlank(path)) {
append(path);
}
}
}
}
/**
* Append the given parameter (key and value). This will only append the parameter if it is not blank (null, empty
* or whitespace), 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 url-encoded.
*
* @param key of the parameter. Must not be null or empty
* @param value of the parameter. May be null/empty/blank. Will be url-encoded.
* @return this, for method chaining
*/
public OldApiPathBuilder param(String key, String value) {
if (StringUtils.isNotBlank(value)) {
appendSeparator();
append(key);
append("=");
append(ParametersUtil.urlEncode(value));
}
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 OldApiPathBuilder 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.
*
* @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 OldApiPathBuilder param(String key, Date value) {
if (value != null) {
param(key, value.getTime());
}
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.
*
* @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 OldApiPathBuilder param(String key, Long value) {
if (value != null) {
param(key, 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.
*
* @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 OldApiPathBuilder param(String key, Integer value) {
if (value != null) {
param(key, 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.
*
* @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 OldApiPathBuilder param(String key, Boolean value) {
if (value != null) {
param(key, value.toString());
}
return this;
}
/**
* Append the given node filters, only if it is not null/empty
*
* @param nodeFilters may be null/empty
* @return this, for method chaining
* @see ParametersUtil#generateNodeFiltersString(Properties)
*/
public OldApiPathBuilder nodeFilters(Properties nodeFilters) {
String filters = ParametersUtil.generateNodeFiltersString(nodeFilters);
if (StringUtils.isNotBlank(filters)) {
appendSeparator();
append(filters);
}
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 OldApiPathBuilder 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();
}
/**
* Append the given string
*
* @param str to append
*/
private void append(String str) {
apiPath.append(str);
}
/**
* Append the right separator "?" or "&" between 2 parameters
*/
private void appendSeparator() {
if (firstParamDone) {
append("&");
} else {
append("?");
firstParamDone = true;
}
}
}

View file

@ -47,9 +47,16 @@ import org.rundeck.api.parser.JobParser;
import org.rundeck.api.parser.JobsImportResultParser; import org.rundeck.api.parser.JobsImportResultParser;
import org.rundeck.api.parser.ListParser; import org.rundeck.api.parser.ListParser;
import org.rundeck.api.parser.NodeParser; import org.rundeck.api.parser.NodeParser;
import org.rundeck.api.parser.ProjectParser;
import org.rundeck.api.parser.StringParser; import org.rundeck.api.parser.StringParser;
import org.rundeck.api.parser.SystemInfoParser; import org.rundeck.api.parser.SystemInfoParser;
import org.rundeck.api.request.AuthRequest;
import org.rundeck.api.request.HistoryRequest;
import org.rundeck.api.request.JobRunRequest;
import org.rundeck.api.request.JobTriggerRequest;
import org.rundeck.api.request.JobsListingRequest;
import org.rundeck.api.request.PingRequest;
import org.rundeck.api.request.ProjectDetailsRequest;
import org.rundeck.api.request.ProjectsListingRequest;
import org.rundeck.api.util.AssertUtil; import org.rundeck.api.util.AssertUtil;
import org.rundeck.api.util.ParametersUtil; import org.rundeck.api.util.ParametersUtil;
@ -154,7 +161,7 @@ public class RundeckClient implements Serializable {
* @throws RundeckApiException if the ping fails * @throws RundeckApiException if the ping fails
*/ */
public void ping() throws RundeckApiException { public void ping() throws RundeckApiException {
new ApiCall(this).ping(); new PingRequest(this).execute();
} }
/** /**
@ -164,7 +171,7 @@ public class RundeckClient implements Serializable {
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication) * @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/ */
public void testAuth() throws RundeckApiLoginException, RundeckApiTokenException { public void testAuth() throws RundeckApiLoginException, RundeckApiTokenException {
new ApiCall(this).testAuth(); new AuthRequest(this).execute();
} }
/** /**
@ -176,6 +183,38 @@ public class RundeckClient implements Serializable {
testAuth(); testAuth();
} }
/*
* New-style API
*/
public ProjectsListingRequest newProjectsListingRequest() {
return new ProjectsListingRequest(this);
}
public ProjectDetailsRequest newProjectDetailsRequest(String projectName) {
return new ProjectDetailsRequest(this, projectName);
}
public JobsListingRequest newJobsListingRequest(String project) {
return new JobsListingRequest(this, project);
}
public JobTriggerRequest newJobTriggerRequest(String jobId) {
return new JobTriggerRequest(this, jobId);
}
public JobRunRequest newJobRunRequest(String jobId) {
return new JobRunRequest(this, jobId);
}
public HistoryRequest newHistoryRequest(String project) {
return new HistoryRequest(this, project);
}
/*
* Old-style API
*/
/* /*
* Projects * Projects
*/ */
@ -190,8 +229,7 @@ public class RundeckClient implements Serializable {
*/ */
public List<RundeckProject> getProjects() throws RundeckApiException, RundeckApiLoginException, public List<RundeckProject> getProjects() throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException { RundeckApiTokenException {
return new ApiCall(this).get(new ApiPathBuilder("/projects"), return newProjectsListingRequest().execute();
new ListParser<RundeckProject>(new ProjectParser(), "result/projects/project"));
} }
/** /**
@ -206,9 +244,7 @@ public class RundeckClient implements Serializable {
*/ */
public RundeckProject getProject(String projectName) throws RundeckApiException, RundeckApiLoginException, public RundeckProject getProject(String projectName) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(projectName, "projectName is mandatory to get the details of a project !"); return newProjectDetailsRequest(projectName).execute();
return new ApiCall(this).get(new ApiPathBuilder("/project/", projectName),
new ProjectParser("result/projects/project"));
} }
/* /*
@ -244,7 +280,7 @@ public class RundeckClient implements Serializable {
*/ */
public List<RundeckJob> getJobs(String project) throws RundeckApiException, RundeckApiLoginException, public List<RundeckJob> getJobs(String project) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
return getJobs(project, null, null, new String[0]); return newJobsListingRequest(project).execute();
} }
/** /**
@ -264,10 +300,10 @@ public class RundeckClient implements Serializable {
public List<RundeckJob> getJobs(String project, String jobFilter, String groupPath, String... jobIds) public List<RundeckJob> getJobs(String project, String jobFilter, String groupPath, String... jobIds)
throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException { throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(project, "project is mandatory to get all jobs !"); AssertUtil.notBlank(project, "project is mandatory to get all jobs !");
return new ApiCall(this).get(new ApiPathBuilder("/jobs").param("project", project) return new ApiCall(this).get(new OldApiPathBuilder("/jobs").param("project", project)
.param("jobFilter", jobFilter) .param("jobFilter", jobFilter)
.param("groupPath", groupPath) .param("groupPath", groupPath)
.param("idlist", StringUtils.join(jobIds, ",")), .param("idlist", StringUtils.join(jobIds, ",")),
new ListParser<RundeckJob>(new JobParser(), "result/jobs/job")); new ListParser<RundeckJob>(new JobParser(), "result/jobs/job"));
} }
@ -450,11 +486,12 @@ public class RundeckClient implements Serializable {
throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException { throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notNull(format, "format is mandatory to export jobs !"); AssertUtil.notNull(format, "format is mandatory to export jobs !");
AssertUtil.notBlank(project, "project is mandatory to export jobs !"); AssertUtil.notBlank(project, "project is mandatory to export jobs !");
return new ApiCall(this).get(new ApiPathBuilder("/jobs/export").param("format", format) return new ApiCall(this).get(new OldApiPathBuilder("/jobs/export").param("format", format)
.param("project", project) .param("project", project)
.param("jobFilter", jobFilter) .param("jobFilter", jobFilter)
.param("groupPath", groupPath) .param("groupPath", groupPath)
.param("idlist", StringUtils.join(jobIds, ","))); .param("idlist",
StringUtils.join(jobIds, ",")));
} }
/** /**
@ -538,7 +575,7 @@ public class RundeckClient implements Serializable {
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notNull(format, "format is mandatory to export a job !"); AssertUtil.notNull(format, "format is mandatory to export a job !");
AssertUtil.notBlank(jobId, "jobId is mandatory to export a job !"); AssertUtil.notBlank(jobId, "jobId is mandatory to export a job !");
return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId).param("format", format)); return new ApiCall(this).get(new OldApiPathBuilder("/job/", jobId).param("format", format));
} }
/** /**
@ -712,9 +749,9 @@ public class RundeckClient implements Serializable {
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notNull(stream, "inputStream of jobs is mandatory to import jobs !"); AssertUtil.notNull(stream, "inputStream of jobs is mandatory to import jobs !");
AssertUtil.notNull(fileType, "fileType 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) return new ApiCall(this).post(new OldApiPathBuilder("/jobs/import").param("format", fileType)
.param("dupeOption", importBehavior) .param("dupeOption", importBehavior)
.attach("xmlBatch", stream), .attach("xmlBatch", stream),
new JobsImportResultParser("result")); new JobsImportResultParser("result"));
} }
@ -755,7 +792,7 @@ public class RundeckClient implements Serializable {
public RundeckJob getJob(String jobId) throws RundeckApiException, RundeckApiLoginException, public RundeckJob getJob(String jobId) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(jobId, "jobId is mandatory to get the details of a job !"); AssertUtil.notBlank(jobId, "jobId is mandatory to get the details of a job !");
return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId), new JobParser("joblist/job")); return new ApiCall(this).get(new OldApiPathBuilder("/job/", jobId), new JobParser("joblist/job"));
} }
/** /**
@ -771,7 +808,8 @@ public class RundeckClient implements Serializable {
public String deleteJob(String jobId) throws RundeckApiException, RundeckApiLoginException, public String deleteJob(String jobId) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(jobId, "jobId is mandatory to delete a job !"); AssertUtil.notBlank(jobId, "jobId is mandatory to delete a job !");
return new ApiCall(this).delete(new ApiPathBuilder("/job/", jobId), new StringParser("result/success/message")); return new ApiCall(this).delete(new OldApiPathBuilder("/job/", jobId),
new StringParser("result/success/message"));
} }
/** /**
@ -829,11 +867,7 @@ public class RundeckClient implements Serializable {
*/ */
public RundeckExecution triggerJob(String jobId, Properties options, Properties nodeFilters) public RundeckExecution triggerJob(String jobId, Properties options, Properties nodeFilters)
throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException { throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(jobId, "jobId is mandatory to trigger a job !"); return newJobTriggerRequest(jobId).addOptions(options).filterNodes(nodeFilters).execute();
return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId, "/run").param("argString",
ParametersUtil.generateArgString(options))
.nodeFilters(nodeFilters),
new ExecutionParser("result/executions/execution"));
} }
/** /**
@ -941,24 +975,10 @@ public class RundeckClient implements Serializable {
public RundeckExecution runJob(String jobId, Properties options, Properties nodeFilters, long poolingInterval, public RundeckExecution runJob(String jobId, Properties options, Properties nodeFilters, long poolingInterval,
TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, TimeUnit poolingUnit) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException,
IllegalArgumentException { IllegalArgumentException {
if (poolingInterval <= 0) { return newJobRunRequest(jobId).addOptions(options)
poolingInterval = DEFAULT_POOLING_INTERVAL; .filterNodes(nodeFilters)
poolingUnit = DEFAULT_POOLING_UNIT; .poolingInterval(poolingInterval, poolingUnit)
} .execute();
if (poolingUnit == null) {
poolingUnit = DEFAULT_POOLING_UNIT;
}
RundeckExecution execution = triggerJob(jobId, options, nodeFilters);
while (ExecutionStatus.RUNNING.equals(execution.getStatus())) {
try {
Thread.sleep(poolingUnit.toMillis(poolingInterval));
} catch (InterruptedException e) {
break;
}
execution = getExecution(execution.getId());
}
return execution;
} }
/* /*
@ -1026,13 +1046,14 @@ public class RundeckClient implements Serializable {
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(project, "project is mandatory to trigger an ad-hoc command !"); AssertUtil.notBlank(project, "project is mandatory to trigger an ad-hoc command !");
AssertUtil.notBlank(command, "command is mandatory to trigger an ad-hoc command !"); AssertUtil.notBlank(command, "command is mandatory to trigger an ad-hoc command !");
RundeckExecution execution = new ApiCall(this).get(new ApiPathBuilder("/run/command").param("project", project) RundeckExecution execution = new ApiCall(this).get(new OldApiPathBuilder("/run/command").param("project",
.param("exec", command) project)
.param("nodeThreadcount", .param("exec", command)
nodeThreadcount) .param("nodeThreadcount",
.param("nodeKeepgoing", nodeThreadcount)
nodeKeepgoing) .param("nodeKeepgoing",
.nodeFilters(nodeFilters), nodeKeepgoing)
.nodeFilters(nodeFilters),
new ExecutionParser("result/execution")); new ExecutionParser("result/execution"));
// the first call just returns the ID of the execution, so we need another call to get a "real" execution // the first call just returns the ID of the execution, so we need another call to get a "real" execution
return getExecution(execution.getId()); return getExecution(execution.getId());
@ -1385,16 +1406,17 @@ public class RundeckClient implements Serializable {
RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException { RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(project, "project is mandatory to trigger an ad-hoc script !"); AssertUtil.notBlank(project, "project is mandatory to trigger an ad-hoc script !");
AssertUtil.notNull(script, "script is mandatory to trigger an ad-hoc script !"); AssertUtil.notNull(script, "script is mandatory to trigger an ad-hoc script !");
RundeckExecution execution = new ApiCall(this).post(new ApiPathBuilder("/run/script").param("project", project) RundeckExecution execution = new ApiCall(this).post(new OldApiPathBuilder("/run/script").param("project",
.attach("scriptFile", project)
script) .attach("scriptFile",
.param("argString", script)
ParametersUtil.generateArgString(options)) .param("argString",
.param("nodeThreadcount", ParametersUtil.generateArgString(options))
nodeThreadcount) .param("nodeThreadcount",
.param("nodeKeepgoing", nodeThreadcount)
nodeKeepgoing) .param("nodeKeepgoing",
.nodeFilters(nodeFilters), nodeKeepgoing)
.nodeFilters(nodeFilters),
new ExecutionParser("result/execution")); new ExecutionParser("result/execution"));
// the first call just returns the ID of the execution, so we need another call to get a "real" execution // the first call just returns the ID of the execution, so we need another call to get a "real" execution
return getExecution(execution.getId()); return getExecution(execution.getId());
@ -1890,7 +1912,7 @@ public class RundeckClient implements Serializable {
public List<RundeckExecution> getRunningExecutions(String project) throws RundeckApiException, public List<RundeckExecution> getRunningExecutions(String project) throws RundeckApiException,
RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException { RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(project, "project is mandatory get all running executions !"); AssertUtil.notBlank(project, "project is mandatory get all running executions !");
return new ApiCall(this).get(new ApiPathBuilder("/executions/running").param("project", project), return new ApiCall(this).get(new OldApiPathBuilder("/executions/running").param("project", project),
new ListParser<RundeckExecution>(new ExecutionParser(), new ListParser<RundeckExecution>(new ExecutionParser(),
"result/executions/execution")); "result/executions/execution"));
} }
@ -1986,9 +2008,9 @@ public class RundeckClient implements Serializable {
public List<RundeckExecution> getJobExecutions(String jobId, ExecutionStatus status, Long max, Long offset) public List<RundeckExecution> getJobExecutions(String jobId, ExecutionStatus status, Long max, Long offset)
throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException { throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(jobId, "jobId is mandatory to get the executions of a job !"); 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) return new ApiCall(this).get(new OldApiPathBuilder("/job/", jobId, "/executions").param("status", status)
.param("max", max) .param("max", max)
.param("offset", offset), .param("offset", offset),
new ListParser<RundeckExecution>(new ExecutionParser(), new ListParser<RundeckExecution>(new ExecutionParser(),
"result/executions/execution")); "result/executions/execution"));
} }
@ -2006,7 +2028,7 @@ public class RundeckClient implements Serializable {
public RundeckExecution getExecution(Long executionId) throws RundeckApiException, RundeckApiLoginException, public RundeckExecution getExecution(Long executionId) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notNull(executionId, "executionId is mandatory to get the details of an execution !"); AssertUtil.notNull(executionId, "executionId is mandatory to get the details of an execution !");
return new ApiCall(this).get(new ApiPathBuilder("/execution/", executionId.toString()), return new ApiCall(this).get(new OldApiPathBuilder("/execution/", executionId.toString()),
new ExecutionParser("result/executions/execution")); new ExecutionParser("result/executions/execution"));
} }
@ -2023,7 +2045,7 @@ public class RundeckClient implements Serializable {
public RundeckAbort abortExecution(Long executionId) throws RundeckApiException, RundeckApiLoginException, public RundeckAbort abortExecution(Long executionId) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notNull(executionId, "executionId is mandatory to abort an execution !"); AssertUtil.notNull(executionId, "executionId is mandatory to abort an execution !");
return new ApiCall(this).get(new ApiPathBuilder("/execution/", executionId.toString(), "/abort"), return new ApiCall(this).get(new OldApiPathBuilder("/execution/", executionId.toString(), "/abort"),
new AbortParser("result/abort")); new AbortParser("result/abort"));
} }
@ -2207,15 +2229,15 @@ public class RundeckClient implements Serializable {
Date begin, Date end, Long max, Long offset) throws RundeckApiException, RundeckApiLoginException, Date begin, Date end, Long max, Long offset) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(project, "project is mandatory to get the history !"); AssertUtil.notBlank(project, "project is mandatory to get the history !");
return new ApiCall(this).get(new ApiPathBuilder("/history").param("project", project) return new ApiCall(this).get(new OldApiPathBuilder("/history").param("project", project)
.param("jobIdFilter", jobId) .param("jobIdFilter", jobId)
.param("reportIdFilter", reportId) .param("reportIdFilter", reportId)
.param("userFilter", user) .param("userFilter", user)
.param("recentFilter", recent) .param("recentFilter", recent)
.param("begin", begin) .param("begin", begin)
.param("end", end) .param("end", end)
.param("max", max) .param("max", max)
.param("offset", offset), .param("offset", offset),
new HistoryParser("result/events")); new HistoryParser("result/events"));
} }
@ -2269,8 +2291,8 @@ public class RundeckClient implements Serializable {
public List<RundeckNode> getNodes(String project, Properties nodeFilters) throws RundeckApiException, public List<RundeckNode> getNodes(String project, Properties nodeFilters) throws RundeckApiException,
RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException { RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(project, "project is mandatory to get all nodes !"); AssertUtil.notBlank(project, "project is mandatory to get all nodes !");
return new ApiCall(this).get(new ApiPathBuilder("/resources").param("project", project) return new ApiCall(this).get(new OldApiPathBuilder("/resources").param("project", project)
.nodeFilters(nodeFilters), .nodeFilters(nodeFilters),
new ListParser<RundeckNode>(new NodeParser(), "project/node")); new ListParser<RundeckNode>(new NodeParser(), "project/node"));
} }
@ -2289,7 +2311,7 @@ public class RundeckClient implements Serializable {
RundeckApiTokenException, IllegalArgumentException { RundeckApiTokenException, IllegalArgumentException {
AssertUtil.notBlank(name, "the name of the node is mandatory to get a node !"); AssertUtil.notBlank(name, "the name of the node is mandatory to get a node !");
AssertUtil.notBlank(project, "project is mandatory to get a node !"); AssertUtil.notBlank(project, "project is mandatory to get a node !");
return new ApiCall(this).get(new ApiPathBuilder("/resource/", name).param("project", project), return new ApiCall(this).get(new OldApiPathBuilder("/resource/", name).param("project", project),
new NodeParser("project/node")); new NodeParser("project/node"));
} }
@ -2307,7 +2329,7 @@ public class RundeckClient implements Serializable {
*/ */
public RundeckSystemInfo getSystemInfo() throws RundeckApiException, RundeckApiLoginException, public RundeckSystemInfo getSystemInfo() throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException { RundeckApiTokenException {
return new ApiCall(this).get(new ApiPathBuilder("/system/info"), new SystemInfoParser("result/system")); return new ApiCall(this).get(new OldApiPathBuilder("/system/info"), new SystemInfoParser("result/system"));
} }
/** /**

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.rundeck.api; package org.rundeck.api.request;
import java.io.InputStream; import java.io.InputStream;
import java.util.Date; import java.util.Date;

View file

@ -0,0 +1,444 @@
/*
* 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.request;
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;
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.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
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.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
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.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.rundeck.api.RundeckApiException;
import org.rundeck.api.RundeckClient;
import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
import org.rundeck.api.RundeckApiException.RundeckApiTokenException;
import org.rundeck.api.parser.ParserHelper;
import org.rundeck.api.parser.XmlNodeParser;
import org.rundeck.api.util.AssertUtil;
/**
* TODO
*
* @author Vincent Behar
*/
abstract class ApiRequest<T> {
/** RunDeck HTTP header for the auth-token (in case of token-based authentication) */
private static final transient String AUTH_TOKEN_HEADER = "X-RunDeck-Auth-Token";
/** {@link RundeckClient} instance holding the RunDeck url and the credentials */
protected final RundeckClient client;
/**
* TODO
*
* @param client
*/
protected ApiRequest(RundeckClient client) {
super();
this.client = client;
AssertUtil.notNull(client, "The RunDeck Client must not be null !");
}
/**
* TODO
*
* @return
*/
public abstract T execute();
/**
* Try to "ping" the RunDeck instance to see if it is alive
*
* @throws RundeckApiException if the ping fails
*/
protected 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 authentication on the RunDeck instance. Will delegate to either {@link #testLoginAuth()} (in case of
* login-based auth) or {@link #testTokenAuth()} (in case of token-based auth).
*
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
* @see #testLoginAuth()
* @see #testTokenAuth()
*/
protected void testAuth() throws RundeckApiLoginException, RundeckApiTokenException {
if (client.getToken() != null) {
testTokenAuth();
} else {
testLoginAuth();
}
}
/**
* Test the login-based authentication on the RunDeck instance
*
* @throws RundeckApiLoginException if the login fails
* @see #testAuth()
*/
protected void testLoginAuth() throws RundeckApiLoginException {
HttpClient httpClient = instantiateHttpClient();
try {
login(httpClient);
} finally {
httpClient.getConnectionManager().shutdown();
}
}
/**
* Test the token-based authentication on the RunDeck instance
*
* @throws RundeckApiTokenException if the token is invalid
* @see #testAuth()
*/
protected void testTokenAuth() throws RundeckApiTokenException {
try {
execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + "/system/info"));
} catch (RundeckApiTokenException e) {
throw e;
} catch (RundeckApiException e) {
throw new RundeckApiTokenException("Failed to verify token", e);
}
}
/**
* 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 - 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 (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
protected T get(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException, RundeckApiTokenException {
return execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), parser);
}
/**
* Execute an HTTP GET request to the RunDeck instance, on the given path. We will login first, and then execute the
* API call.
*
* @param apiPath on which we will make the HTTP request - see {@link ApiPathBuilder}
* @return a new {@link InputStream} instance, not linked with network resources
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
protected InputStream get(ApiPathBuilder apiPath) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException {
ByteArrayInputStream response = execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath));
// try to load the document, to throw an exception in case of error
ParserHelper.loadDocument(response);
response.reset();
return response;
}
/**
* Execute an HTTP 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 (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
protected T post(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException, RundeckApiTokenException {
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.
*
* @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 (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
protected T delete(ApiPathBuilder apiPath, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException, RundeckApiTokenException {
return execute(new HttpDelete(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), parser);
}
/**
* Execute an HTTP request to the RunDeck instance. 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 request to execute. see {@link HttpGet}, {@link HttpDelete}, and so on...
* @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 (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
private T execute(HttpRequestBase request, XmlNodeParser<T> parser) throws RundeckApiException,
RundeckApiLoginException, RundeckApiTokenException {
// execute the request
InputStream response = execute(request);
// read and parse the response
Document xmlDocument = ParserHelper.loadDocument(response);
return parser.parseXmlNode(xmlDocument);
}
/**
* Execute an HTTP request to the RunDeck instance. We will login first, and then execute the API call.
*
* @param request to execute. see {@link HttpGet}, {@link HttpDelete}, and so on...
* @return a new {@link InputStream} instance, not linked with network resources
* @throws RundeckApiException in case of error when calling the API
* @throws RundeckApiLoginException if the login fails (in case of login-based authentication)
* @throws RundeckApiTokenException if the token is invalid (in case of token-based authentication)
*/
private ByteArrayInputStream execute(HttpRequestBase request) throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException {
HttpClient httpClient = instantiateHttpClient();
try {
// we only need to manually login in case of login-based authentication
// note that in case of token-based auth, the auth (via an HTTP header) is managed by an interceptor.
if (client.getToken() == null) {
login(httpClient);
}
// execute the HTTP request
HttpResponse response = null;
try {
response = httpClient.execute(request);
} catch (IOException e) {
throw new RundeckApiException("Failed to execute an HTTP " + request.getMethod() + " on url : "
+ request.getURI(), e);
}
// in case of error, we get a redirect to /api/error
// that we need to follow manually for POST and DELETE requests (as GET)
if (response.getStatusLine().getStatusCode() / 100 == 3) {
String newLocation = response.getFirstHeader("Location").getValue();
try {
EntityUtils.consume(response.getEntity());
} catch (IOException e) {
throw new RundeckApiException("Failed to consume entity (release connection)", e);
}
request = new HttpGet(newLocation);
try {
response = httpClient.execute(request);
} catch (IOException e) {
throw new RundeckApiException("Failed to execute an HTTP GET on url : " + request.getURI(), e);
}
}
// check the response code (should be 2xx, even in case of error : error message is in the XML result)
if (response.getStatusLine().getStatusCode() / 100 != 2) {
if (response.getStatusLine().getStatusCode() == 403 && client.getToken() != null) {
throw new RundeckApiTokenException("Invalid Token ! Got HTTP response '" + response.getStatusLine()
+ "' for " + request.getURI());
} else {
throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' for "
+ request.getURI());
}
}
if (response.getEntity() == null) {
throw new RundeckApiException("Empty RunDeck response ! HTTP status line is : "
+ response.getStatusLine());
}
// return a new inputStream, so that we can close all network resources
try {
return new ByteArrayInputStream(EntityUtils.toByteArray(response.getEntity()));
} catch (IOException e) {
throw new RundeckApiException("Failed to consume entity and convert the inputStream", e);
}
} 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). Only use this in case of login-based authentication.
*
* @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() {
DefaultHttpClient httpClient = new DefaultHttpClient();
// configure user-agent
HttpProtocolParams.setUserAgent(httpClient.getParams(), "RunDeck API Java Client " + RundeckClient.API_VERSION);
// configure SSL
SSLSocketFactory socketFactory = null;
try {
socketFactory = new SSLSocketFactory(new TrustStrategy() {
@Override
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.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()));
// in case of token-based authentication, add the correct HTTP header to all requests via an interceptor
httpClient.addRequestInterceptor(new HttpRequestInterceptor() {
@Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
if (client.getToken() != null) {
request.addHeader(AUTH_TOKEN_HEADER, client.getToken());
}
}
});
return httpClient;
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.request;
import org.rundeck.api.RundeckClient;
/**
* TODO
*
* @author Vincent Behar
*/
public class AuthRequest extends ApiRequest<Void> {
public AuthRequest(RundeckClient client) {
super(client);
}
@Override
public Void execute() {
testAuth();
return null;
}
}

View file

@ -0,0 +1,83 @@
/*
* 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.request;
import java.util.Date;
import org.rundeck.api.RundeckClient;
import org.rundeck.api.domain.RundeckHistory;
import org.rundeck.api.parser.HistoryParser;
import org.rundeck.api.util.AssertUtil;
/**
* TODO
*
* @author Vincent Behar
*/
public class HistoryRequest extends ApiRequest<RundeckHistory> {
private final String project;
private String jobId;
private String reportId;
private String user;
private String recent;
private Date beginAt;
private Date end;
private Long max;
private Long offset;
public HistoryRequest(RundeckClient client, String project) {
super(client);
this.project = project;
AssertUtil.notBlank(project, "project is mandatory to get the history !");
}
public HistoryRequest beginAt(Date beginAt) {
this.beginAt = beginAt;
return this;
}
public HistoryRequest max(Long max) {
this.max = max;
return this;
}
public HistoryRequest offset(Long offset) {
this.offset = offset;
return this;
}
@Override
public RundeckHistory execute() {
return get(new ApiPathBuilder("/history").param("project", project)
.param("jobIdFilter", jobId)
.param("reportIdFilter", reportId)
.param("userFilter", user)
.param("recentFilter", recent)
.param("begin", beginAt)
.param("end", end)
.param("max", max)
.param("offset", offset), new HistoryParser("result/events"));
}
}

View file

@ -0,0 +1,72 @@
/*
* 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.request;
import java.util.concurrent.TimeUnit;
import org.rundeck.api.RundeckClient;
import org.rundeck.api.domain.RundeckExecution;
import org.rundeck.api.domain.RundeckExecution.ExecutionStatus;
/**
* TODO
*
* @author Vincent Behar
*/
public class JobRunRequest extends JobTriggerRequest<JobRunRequest> {
/** Default value for the "pooling interval" used when running jobs/commands/scripts */
public static final transient long DEFAULT_POOLING_INTERVAL = 5;
/** Default unit of the "pooling interval" used when running jobs/commands/scripts */
public static final transient TimeUnit DEFAULT_POOLING_UNIT = TimeUnit.SECONDS;
private Long poolingInterval;
private TimeUnit poolingUnit;
public JobRunRequest(RundeckClient client, String project) {
super(client, project);
}
public JobRunRequest poolingInterval(Long poolingInterval, TimeUnit poolingUnit) {
this.poolingInterval = poolingInterval;
this.poolingUnit = poolingUnit;
return this;
}
@Override
public RundeckExecution execute() {
if (poolingInterval <= 0) {
poolingInterval = DEFAULT_POOLING_INTERVAL;
poolingUnit = DEFAULT_POOLING_UNIT;
}
if (poolingUnit == null) {
poolingUnit = DEFAULT_POOLING_UNIT;
}
RundeckExecution execution = super.execute();
while (ExecutionStatus.RUNNING.equals(execution.getStatus())) {
try {
Thread.sleep(poolingUnit.toMillis(poolingInterval));
} catch (InterruptedException e) {
break;
}
execution = client.getExecution(execution.getId());
}
return execution;
}
}

View file

@ -0,0 +1,64 @@
/*
* 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.request;
import java.util.Properties;
import org.rundeck.api.RundeckClient;
import org.rundeck.api.domain.RundeckExecution;
import org.rundeck.api.parser.ExecutionParser;
import org.rundeck.api.util.AssertUtil;
import org.rundeck.api.util.ParametersUtil;
/**
* TODO
*
* @author Vincent Behar
*/
public class JobTriggerRequest<T extends JobTriggerRequest<?>> extends ApiRequest<RundeckExecution> {
private final String jobId;
private Properties options;
private Properties nodeFilters;
public JobTriggerRequest(RundeckClient client, String jobId) {
super(client);
this.jobId = jobId;
AssertUtil.notBlank(jobId, "jobId is mandatory !");
}
@SuppressWarnings("unchecked")
public T addOptions(Properties options) {
this.options = options;
return (T) this;
}
@SuppressWarnings("unchecked")
public T filterNodes(Properties nodeFilters) {
this.nodeFilters = nodeFilters;
return (T) this;
}
@Override
public RundeckExecution execute() {
return get(new ApiPathBuilder("/job/", jobId, "/run").param("argString",
ParametersUtil.generateArgString(options))
.nodeFilters(nodeFilters),
new ExecutionParser("result/executions/execution"));
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.request;
import java.util.List;
import org.rundeck.api.RundeckClient;
import org.rundeck.api.domain.RundeckJob;
import org.rundeck.api.parser.JobParser;
import org.rundeck.api.parser.ListParser;
import org.rundeck.api.util.AssertUtil;
/**
* TODO
*
* @author Vincent Behar
*/
public class JobsListingRequest extends ApiRequest<List<RundeckJob>> {
private final String project;
private String jobFilter;
public JobsListingRequest(RundeckClient client, String project) {
super(client);
this.project = project;
AssertUtil.notBlank(project, "project is mandatory to get all jobs !");
}
public JobsListingRequest jobFilter(String jobFilter) {
this.jobFilter = jobFilter;
return this;
}
@Override
public List<RundeckJob> execute() {
return get(new ApiPathBuilder("/jobs").param("project", project).param("jobFilter", jobFilter),
new ListParser<RundeckJob>(new JobParser(), "result/jobs/job"));
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.request;
import org.rundeck.api.RundeckClient;
/**
* TODO
*
* @author Vincent Behar
*/
public class PingRequest extends ApiRequest<Void> {
public PingRequest(RundeckClient client) {
super(client);
}
@Override
public Void execute() {
ping();
return null;
}
}

View file

@ -0,0 +1,43 @@
/*
* 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.request;
import org.rundeck.api.RundeckClient;
import org.rundeck.api.domain.RundeckProject;
import org.rundeck.api.parser.ProjectParser;
import org.rundeck.api.util.AssertUtil;
/**
* TODO
*
* @author Vincent Behar
*/
public class ProjectDetailsRequest extends ApiRequest<RundeckProject> {
private final String projectName;
public ProjectDetailsRequest(RundeckClient client, String projectName) {
super(client);
this.projectName = projectName;
AssertUtil.notBlank(projectName, "projectName is mandatory to get the details of a project !");
}
@Override
public RundeckProject execute() {
return get(new ApiPathBuilder("/project/", projectName), new ProjectParser("result/projects/project"));
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.request;
import java.util.List;
import org.rundeck.api.RundeckClient;
import org.rundeck.api.domain.RundeckProject;
import org.rundeck.api.parser.ListParser;
import org.rundeck.api.parser.ProjectParser;
/**
* TODO
*
* @author Vincent Behar
*/
public class ProjectsListingRequest extends ApiRequest<List<RundeckProject>> {
public ProjectsListingRequest(RundeckClient client) {
super(client);
}
@Override
public List<RundeckProject> execute() {
return get(new ApiPathBuilder("/projects"), new ListParser<RundeckProject>(new ProjectParser(),
"result/projects/project"));
}
}