Merge pull request #15 from rundeck/apiv12

Add API v12 support
This commit is contained in:
Greg Schueler 2014-11-07 09:02:27 -08:00
commit 3d60907013
13 changed files with 571 additions and 2 deletions

View file

@ -23,6 +23,7 @@ import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
import org.rundeck.api.RundeckApiException.RundeckApiTokenException;
import org.rundeck.api.domain.*;
import org.rundeck.api.domain.RundeckExecution.ExecutionStatus;
import org.rundeck.api.generator.DeleteExecutionsGenerator;
import org.rundeck.api.generator.ProjectConfigGenerator;
import org.rundeck.api.generator.ProjectConfigPropertyGenerator;
import org.rundeck.api.generator.ProjectGenerator;
@ -95,6 +96,7 @@ public class RundeckClient implements Serializable {
V9(9),
V10(10),
V11(11),
V12(12),
;
private int versionNumber;
@ -108,7 +110,7 @@ public class RundeckClient implements Serializable {
}
}
/** Version of the API supported */
public static final transient int API_VERSION = Version.V11.getVersionNumber();
public static final transient int API_VERSION = Version.V12.getVersionNumber();
private static final String API = "/api/";
@ -1641,7 +1643,76 @@ public class RundeckClient implements Serializable {
if(null!=asUser) {
apiPath.param("asUser", asUser);
}
return new ApiCall(this).get(apiPath, new AbortParser(rootXpath()+"/abort"));
return new ApiCall(this).get(apiPath, new AbortParser(rootXpath() + "/abort"));
}
/**
* Delete all executions for a job specified by a job ID
*
* @param jobId Identifier for the job
*
* @return a {@link DeleteExecutionsResponse} instance - won't be null
*
* @throws RundeckApiException in case of error when calling the API (non-existent
* execution with this ID)
* @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 IllegalArgumentException if the executionIds is null
*/
public DeleteExecutionsResponse deleteAllJobExecutions(final String jobId)
throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException
{
AssertUtil.notNull(jobId, "jobId is mandatory to delete executions!");
return new ApiCall(this).delete(
new ApiPathBuilder("/job/",jobId,"/executions"),
new DeleteExecutionsResponseParser(rootXpath() + "/deleteExecutions")
);
}
/**
* Delete a set of executions, identified by the given IDs
*
* @param executionIds set of identifiers for the executions - mandatory
* @return a {@link DeleteExecutionsResponse} instance - won't be null
* @throws RundeckApiException in case of error when calling the API (non-existent execution with this ID)
* @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 IllegalArgumentException if the executionIds is null
*/
public DeleteExecutionsResponse deleteExecutions(final Set<Long> executionIds)
throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException
{
AssertUtil.notNull(executionIds, "executionIds is mandatory to delete executions!");
if (executionIds.size() < 1) {
throw new IllegalArgumentException("executionIds cannot be empty");
}
final ApiPathBuilder apiPath = new ApiPathBuilder("/executions/delete").xml(
new DeleteExecutionsGenerator(executionIds)
);
return new ApiCall(this).post(
apiPath,
new DeleteExecutionsResponseParser( rootXpath() + "/deleteExecutions")
);
}
/**
* Delete a single execution, identified by the given ID
*
* @param executionId identifier for the execution - mandatory
* @throws RundeckApiException in case of error when calling the API (non-existent execution with this ID)
* @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 IllegalArgumentException if the executionId is null
*/
public void deleteExecution(final Long executionId)
throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException
{
AssertUtil.notNull(executionId, "executionId is mandatory to delete an execution!");
new ApiCall(this).delete(new ApiPathBuilder("/execution/", executionId.toString()));
}
/*

View file

@ -0,0 +1,84 @@
package org.rundeck.api.domain;
import java.io.Serializable;
import java.util.List;
/**
* DeleteExecutionsResponse is ...
*
* @author Greg Schueler <greg@simplifyops.com>
* @since 2014-11-06
*/
public class DeleteExecutionsResponse implements Serializable {
private static final long serialVersionUID = 1L;
private int failedCount;
private int successCount;
private boolean allsuccessful;
private int requestCount;
private List<DeleteFailure> failures;
public int getFailedCount() {
return failedCount;
}
public void setFailedCount(final int failedCount) {
this.failedCount = failedCount;
}
public int getSuccessCount() {
return successCount;
}
public void setSuccessCount(final int successCount) {
this.successCount = successCount;
}
public boolean isAllsuccessful() {
return allsuccessful;
}
public void setAllsuccessful(final boolean allsuccessful) {
this.allsuccessful = allsuccessful;
}
public int getRequestCount() {
return requestCount;
}
public void setRequestCount(final int requestCount) {
this.requestCount = requestCount;
}
public List<DeleteFailure> getFailures() {
return failures;
}
public void setFailures(final List<DeleteFailure> failures) {
this.failures = failures;
}
public static class DeleteFailure implements Serializable{
private static final long serialVersionUID = 1L;
private Long executionId;
private String message;
public Long getExecutionId() {
return executionId;
}
public void setExecutionId(final Long executionId) {
this.executionId = executionId;
}
public String getMessage() {
return message;
}
public void setMessage(final String message) {
this.message = message;
}
}
}

View file

@ -0,0 +1,38 @@
package org.rundeck.api.generator;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.rundeck.api.domain.ProjectConfig;
import java.util.List;
import java.util.Set;
/**
* DeleteExecutionsGenerator is ...
*
* @author Greg Schueler <greg@simplifyops.com>
* @since 2014-11-06
*/
public class DeleteExecutionsGenerator extends BaseDocGenerator {
private Set<Long> executionIds;
public DeleteExecutionsGenerator(final Set<Long> executionIds) {
this.executionIds = executionIds;
}
@Override public Element generateXmlElement() {
Element rootElem = DocumentFactory.getInstance().createElement("executions");
for (Long executionId : executionIds) {
rootElem.addElement("execution").addAttribute("id", Long.toString(executionId));
}
return rootElem;
}
public Set<Long> getExecutionIds() {
return executionIds;
}
public void setExecutionIds(final Set<Long> executionIds) {
this.executionIds = executionIds;
}
}

View file

@ -0,0 +1,52 @@
package org.rundeck.api.parser;
import org.dom4j.Node;
import org.rundeck.api.domain.DeleteExecutionsResponse;
import java.util.ArrayList;
import java.util.List;
/**
* DeleteExecutionsResponseParser is ...
*
* @author Greg Schueler <greg@simplifyops.com>
* @since 2014-11-06
*/
public class DeleteExecutionsResponseParser implements XmlNodeParser<DeleteExecutionsResponse> {
private String xpath;
public DeleteExecutionsResponseParser(final String xpath) {
this.xpath = xpath;
}
@Override public DeleteExecutionsResponse parseXmlNode(final Node node) {
final Node baseNode = xpath != null ? node.selectSingleNode(xpath) : node;
final DeleteExecutionsResponse response = new DeleteExecutionsResponse();
response.setAllsuccessful(Boolean.parseBoolean(baseNode.valueOf("@allsuccessful")));
response.setRequestCount(Integer.parseInt(baseNode.valueOf("@requestCount")));
response.setSuccessCount(Integer.parseInt(baseNode.valueOf("successful/@count")));
final Node failedNode = baseNode.selectSingleNode("failed");
//parse failures
final List<DeleteExecutionsResponse.DeleteFailure> failures = new ArrayList
<DeleteExecutionsResponse.DeleteFailure>();
int failedCount = 0;
if (null != failedNode) {
failedCount = Integer.parseInt(baseNode.valueOf("failed/@count"));
final List list = baseNode.selectNodes("failed/execution");
for (final Object o : list) {
final Node execNode = (Node) o;
final DeleteExecutionsResponse.DeleteFailure deleteFailure =
new DeleteExecutionsResponse.DeleteFailure();
deleteFailure.setExecutionId(Long.parseLong(execNode.valueOf("@id")));
deleteFailure.setMessage(execNode.valueOf("@message"));
failures.add(deleteFailure);
}
}
response.setFailedCount(failedCount);
response.setFailures(failures);
return response;
}
}

View file

@ -113,3 +113,11 @@ h2. RunDeck API version 11
* API Token create - OK
* API Token list - OK
* API Token delete - OK
h2. Rundeck API version 12
[Documentation of the RunDeck API version 12|http://rundeck.org/2.2.0/api/index.html]
* Bulk delete executions - OK
* Delete execution - OK
* Delete all executions for a job - OK

View file

@ -56,6 +56,7 @@ public class RundeckClientTest {
public static final String TEST_TOKEN_5 = "C3O6d5O98Kr6Dpv71sdE4ERdCuU12P6d";
public static final String TEST_TOKEN_6 = "Do4d3NUD5DKk21DR4sNK755RcPk618vn";
public static final String TEST_TOKEN_7 = "8Dp9op111ER6opsDRkddvE86K9sE499s";
public static final String TEST_TOKEN_8 = "GG7uj1y6UGahOs7QlmeN2sIwz1Y2j7zI";
@Rule
public Recorder recorder = new Recorder();
@ -1516,6 +1517,159 @@ public class RundeckClientTest {
}
/**
* delete executions with failure
*/
@Test
@Betamax(tape = "delete_executions_unauthorized", mode = TapeMode.READ_ONLY)
public void deleteExecutionsUnauthorized() throws Exception {
final RundeckClient client = createClient(TEST_TOKEN_8, 12);
final DeleteExecutionsResponse response = client.deleteExecutions(
new HashSet<Long>() {{
add(640L);
add(641L);
}}
);
Assert.assertEquals(2, response.getRequestCount());
Assert.assertEquals(0, response.getSuccessCount());
Assert.assertEquals(2, response.getFailedCount());
Assert.assertFalse(response.isAllsuccessful());
Assert.assertNotNull(response.getFailures());
Assert.assertEquals(2, response.getFailures().size());
Assert.assertEquals(Long.valueOf(641L), response.getFailures().get(0).getExecutionId());
Assert.assertEquals(
"Unauthorized: Delete execution in project test",
response.getFailures().get(0).getMessage()
);
Assert.assertEquals(Long.valueOf(640L), response.getFailures().get(1).getExecutionId());
Assert.assertEquals(
"Unauthorized: Delete execution in project test",
response.getFailures().get(1).getMessage()
);
}
/**
* delete executions with success
*/
@Test
@Betamax(tape = "delete_executions_success", mode = TapeMode.READ_ONLY)
public void deleteExecutionsSuccess() throws Exception {
final RundeckClient client = createClient(TEST_TOKEN_8, 12);
final DeleteExecutionsResponse response = client.deleteExecutions(
new HashSet<Long>() {{
add(640L);
add(641L);
}}
);
Assert.assertEquals(2, response.getRequestCount());
Assert.assertEquals(2, response.getSuccessCount());
Assert.assertEquals(0, response.getFailedCount());
Assert.assertTrue(response.isAllsuccessful());
Assert.assertNotNull(response.getFailures());
Assert.assertEquals(0, response.getFailures().size());
}
/**
* delete executions mixed success
*/
@Test
@Betamax(tape = "delete_executions_mixed", mode = TapeMode.READ_ONLY)
public void deleteExecutionsMixed() throws Exception {
final RundeckClient client = createClient(TEST_TOKEN_8, 12);
final DeleteExecutionsResponse response = client.deleteExecutions(
new HashSet<Long>() {{
add(642L);
add(640L);
add(1640L);
}}
);
Assert.assertEquals(3, response.getRequestCount());
Assert.assertEquals(1, response.getSuccessCount());
Assert.assertEquals(2, response.getFailedCount());
Assert.assertFalse(response.isAllsuccessful());
Assert.assertNotNull(response.getFailures());
Assert.assertEquals(2, response.getFailures().size());
Assert.assertEquals(Long.valueOf(1640L), response.getFailures().get(0).getExecutionId());
Assert.assertEquals(
"Execution Not found: 1640",
response.getFailures().get(0).getMessage()
);
Assert.assertEquals(Long.valueOf(640L), response.getFailures().get(1).getExecutionId());
Assert.assertEquals(
"Execution Not found: 640",
response.getFailures().get(1).getMessage()
);
}
/**
* delete executions with failure
*/
@Test
@Betamax(tape = "delete_all_job_executions_unauthorized", mode = TapeMode.READ_ONLY)
public void deleteAllJobExecutionsUnauthorized() throws Exception {
final RundeckClient client = createClient(TEST_TOKEN_8, 12);
try {
final DeleteExecutionsResponse response = client.deleteAllJobExecutions(
"764c1209-68ed-4185-8d43-a739364bf156"
);
Assert.fail();
} catch (RundeckApiException.RundeckApiTokenException e) {
e.printStackTrace();
}
}
/**
*
* @throws Exception
*/
@Test
@Betamax(tape = "delete_all_job_executions_success", mode = TapeMode.READ_ONLY)
public void deleteAllJobExecutionsSuccess() throws Exception {
final RundeckClient client = createClient(TEST_TOKEN_8, 12);
final DeleteExecutionsResponse response = client.deleteAllJobExecutions(
"764c1209-68ed-4185-8d43-a739364bf156"
);
Assert.assertEquals(2, response.getRequestCount());
Assert.assertEquals(2, response.getSuccessCount());
Assert.assertEquals(0, response.getFailedCount());
Assert.assertTrue(response.isAllsuccessful());
Assert.assertNotNull(response.getFailures());
Assert.assertEquals(0, response.getFailures().size());
}
/**
* delete single execution success
*/
@Test
@Betamax(tape = "delete_execution_success", mode = TapeMode.READ_ONLY)
public void deleteExecutionSuccess() throws Exception {
final RundeckClient client = createClient(TEST_TOKEN_8, 12);
client.deleteExecution(643L);
}
/**
* delete single execution failure (does not exist)
*/
@Test
@Betamax(tape = "delete_execution_failure", mode = TapeMode.READ_ONLY)
public void deleteExecutionFailure() throws Exception {
final RundeckClient client = createClient(TEST_TOKEN_8, 12);
try {
client.deleteExecution(640L);
Assert.fail();
} catch (RundeckApiException.RundeckApiHttpStatusException e) {
Assert.assertEquals(404, e.getStatusCode());
}
}
/**
* delete single execution null input
*/
@Test
public void deleteExecutionNullInput() throws Exception {
final RundeckClient client = createClient(TEST_TOKEN_8, 12);
try {
client.deleteExecution(null);
Assert.fail();
} catch (IllegalArgumentException e) {
}
}
@Before
public void setUp() throws Exception {
// not that you can put whatever here, because we don't actually connect to the RunDeck instance

View file

@ -0,0 +1,23 @@
!tape
name: delete_all_job_executions_success
interactions:
- recorded: 2014-11-06T18:03:44.789Z
request:
method: DELETE
uri: http://rundeck.local:4440/api/12/job/764c1209-68ed-4185-8d43-a739364bf156/executions
headers:
Host: rundeck.local:4440
Proxy-Connection: Keep-Alive
User-Agent: RunDeck API Java Client 12
X-RunDeck-Auth-Token: GG7uj1y6UGahOs7QlmeN2sIwz1Y2j7zI
response:
status: 200
headers:
Content-Type: application/xml;charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Server: Jetty(7.6.0.v20120127)
Set-Cookie: JSESSIONID=cqqoff205qus10sz1v3t54rk7;Path=/
X-Rundeck-API-Version: '12'
X-Rundeck-API-XML-Response-Wrapper: 'false'
body: !!binary |-
PGRlbGV0ZUV4ZWN1dGlvbnMgcmVxdWVzdENvdW50PScyJyBhbGxzdWNjZXNzZnVsPSd0cnVlJz4KICA8c3VjY2Vzc2Z1bCBjb3VudD0nMicgLz4KPC9kZWxldGVFeGVjdXRpb25zPg==

View file

@ -0,0 +1,21 @@
!tape
name: delete_all_job_executions_unauthorized
interactions:
- recorded: 2014-11-06T17:57:02.905Z
request:
method: DELETE
uri: http://rundeck.local:4440/api/12/job/764c1209-68ed-4185-8d43-a739364bf156/executions
headers:
Host: rundeck.local:4440
Proxy-Connection: Keep-Alive
User-Agent: RunDeck API Java Client 12
X-RunDeck-Auth-Token: GG7uj1y6UGahOs7QlmeN2sIwz1Y2j7zI
response:
status: 403
headers:
Content-Type: text/xml;charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Server: Jetty(7.6.0.v20120127)
Set-Cookie: JSESSIONID=splzhkd5xunl1noidus2o7im3;Path=/
X-Rundeck-API-Version: '12'
body: "<result error='true' apiversion='12'>\n <error code='api.error.item.unauthorized'>\n <message>Not authorized for action \"Delete Execution\" for Project test</message>\n </error>\n</result>"

View file

@ -0,0 +1,21 @@
!tape
name: delete_execution_failure
interactions:
- recorded: 2014-11-06T17:45:47.952Z
request:
method: DELETE
uri: http://rundeck.local:4440/api/12/execution/640
headers:
Host: rundeck.local:4440
Proxy-Connection: Keep-Alive
User-Agent: RunDeck API Java Client 12
X-RunDeck-Auth-Token: GG7uj1y6UGahOs7QlmeN2sIwz1Y2j7zI
response:
status: 404
headers:
Content-Type: text/xml;charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Server: Jetty(7.6.0.v20120127)
Set-Cookie: JSESSIONID=1eynpbisggwsy18ax352yya8k0;Path=/
X-Rundeck-API-Version: '12'
body: "<result error='true' apiversion='12'>\n <error code='api.error.item.doesnotexist'>\n <message>Execution ID does not exist: 640</message>\n </error>\n</result>"

View file

@ -0,0 +1,19 @@
!tape
name: delete_execution_success
interactions:
- recorded: 2014-11-06T17:45:47.749Z
request:
method: DELETE
uri: http://rundeck.local:4440/api/12/execution/643
headers:
Host: rundeck.local:4440
Proxy-Connection: Keep-Alive
User-Agent: RunDeck API Java Client 12
X-RunDeck-Auth-Token: GG7uj1y6UGahOs7QlmeN2sIwz1Y2j7zI
response:
status: 204
headers:
Content-Type: text/html;charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Server: Jetty(7.6.0.v20120127)
Set-Cookie: JSESSIONID=1hlysemt7deir1j9r7l6ildg5x;Path=/

View file

@ -0,0 +1,26 @@
!tape
name: delete_executions_mixed
interactions:
- recorded: 2014-11-06T17:29:44.266Z
request:
method: POST
uri: http://rundeck.local:4440/api/12/executions/delete
headers:
Accept: text/xml
Content-Type: application/xml
Host: rundeck.local:4440
Proxy-Connection: Keep-Alive
Transfer-Encoding: chunked
User-Agent: RunDeck API Java Client 12
X-RunDeck-Auth-Token: GG7uj1y6UGahOs7QlmeN2sIwz1Y2j7zI
response:
status: 200
headers:
Content-Type: application/xml;charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Server: Jetty(7.6.0.v20120127)
Set-Cookie: JSESSIONID=smlj6wcfe4yccemt6bptnmgy;Path=/
X-Rundeck-API-Version: '12'
X-Rundeck-API-XML-Response-Wrapper: 'false'
body: !!binary |-
PGRlbGV0ZUV4ZWN1dGlvbnMgcmVxdWVzdENvdW50PSczJyBhbGxzdWNjZXNzZnVsPSdmYWxzZSc+CiAgPHN1Y2Nlc3NmdWwgY291bnQ9JzEnIC8+CiAgPGZhaWxlZCBjb3VudD0nMic+CiAgICA8ZXhlY3V0aW9uIGlkPScxNjQwJyBtZXNzYWdlPSdFeGVjdXRpb24gTm90IGZvdW5kOiAxNjQwJyAvPgogICAgPGV4ZWN1dGlvbiBpZD0nNjQwJyBtZXNzYWdlPSdFeGVjdXRpb24gTm90IGZvdW5kOiA2NDAnIC8+CiAgPC9mYWlsZWQ+CjwvZGVsZXRlRXhlY3V0aW9ucz4=

View file

@ -0,0 +1,26 @@
!tape
name: delete_executions_success
interactions:
- recorded: 2014-11-06T17:24:56.487Z
request:
method: POST
uri: http://rundeck.local:4440/api/12/executions/delete
headers:
Accept: text/xml
Content-Type: application/xml
Host: rundeck.local:4440
Proxy-Connection: Keep-Alive
Transfer-Encoding: chunked
User-Agent: RunDeck API Java Client 12
X-RunDeck-Auth-Token: GG7uj1y6UGahOs7QlmeN2sIwz1Y2j7zI
response:
status: 200
headers:
Content-Type: application/xml;charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Server: Jetty(7.6.0.v20120127)
Set-Cookie: JSESSIONID=1xueq6cjfrsnxjn43hcfadqyk;Path=/
X-Rundeck-API-Version: '12'
X-Rundeck-API-XML-Response-Wrapper: 'false'
body: !!binary |-
PGRlbGV0ZUV4ZWN1dGlvbnMgcmVxdWVzdENvdW50PScyJyBhbGxzdWNjZXNzZnVsPSd0cnVlJz4KICA8c3VjY2Vzc2Z1bCBjb3VudD0nMicgLz4KPC9kZWxldGVFeGVjdXRpb25zPg==

View file

@ -0,0 +1,26 @@
!tape
name: delete_executions_success
interactions:
- recorded: 2014-11-06T17:15:24.673Z
request:
method: POST
uri: http://rundeck.local:4440/api/12/executions/delete
headers:
Accept: text/xml
Content-Type: application/xml
Host: rundeck.local:4440
Proxy-Connection: Keep-Alive
Transfer-Encoding: chunked
User-Agent: RunDeck API Java Client 12
X-RunDeck-Auth-Token: GG7uj1y6UGahOs7QlmeN2sIwz1Y2j7zI
response:
status: 200
headers:
Content-Type: application/xml;charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Server: Jetty(7.6.0.v20120127)
Set-Cookie: JSESSIONID=1kzmot0r2scpjfxlcpfthh7tz;Path=/
X-Rundeck-API-Version: '12'
X-Rundeck-API-XML-Response-Wrapper: 'false'
body: !!binary |-
PGRlbGV0ZUV4ZWN1dGlvbnMgcmVxdWVzdENvdW50PScyJyBhbGxzdWNjZXNzZnVsPSdmYWxzZSc+CiAgPHN1Y2Nlc3NmdWwgY291bnQ9JzAnIC8+CiAgPGZhaWxlZCBjb3VudD0nMic+CiAgICA8ZXhlY3V0aW9uIGlkPSc2NDEnIG1lc3NhZ2U9J1VuYXV0aG9yaXplZDogRGVsZXRlIGV4ZWN1dGlvbiBpbiBwcm9qZWN0IHRlc3QnIC8+CiAgICA8ZXhlY3V0aW9uIGlkPSc2NDAnIG1lc3NhZ2U9J1VuYXV0aG9yaXplZDogRGVsZXRlIGV4ZWN1dGlvbiBpbiBwcm9qZWN0IHRlc3QnIC8+CiAgPC9mYWlsZWQ+CjwvZGVsZXRlRXhlY3V0aW9ucz4=