Add bulk Delete executions endpoint

This commit is contained in:
Greg Schueler 2014-11-06 09:32:30 -08:00
parent bbe599b915
commit a7c0c22699
8 changed files with 361 additions and 1 deletions

View file

@ -23,6 +23,7 @@ import org.rundeck.api.RundeckApiException.RundeckApiLoginException;
import org.rundeck.api.RundeckApiException.RundeckApiTokenException; import org.rundeck.api.RundeckApiException.RundeckApiTokenException;
import org.rundeck.api.domain.*; import org.rundeck.api.domain.*;
import org.rundeck.api.domain.RundeckExecution.ExecutionStatus; import org.rundeck.api.domain.RundeckExecution.ExecutionStatus;
import org.rundeck.api.generator.DeleteExecutionsGenerator;
import org.rundeck.api.generator.ProjectConfigGenerator; import org.rundeck.api.generator.ProjectConfigGenerator;
import org.rundeck.api.generator.ProjectConfigPropertyGenerator; import org.rundeck.api.generator.ProjectConfigPropertyGenerator;
import org.rundeck.api.generator.ProjectGenerator; import org.rundeck.api.generator.ProjectGenerator;
@ -95,6 +96,7 @@ public class RundeckClient implements Serializable {
V9(9), V9(9),
V10(10), V10(10),
V11(11), V11(11),
V12(12),
; ;
private int versionNumber; private int versionNumber;
@ -108,7 +110,7 @@ public class RundeckClient implements Serializable {
} }
} }
/** Version of the API supported */ /** 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/"; private static final String API = "/api/";
@ -1643,6 +1645,30 @@ public class RundeckClient implements Serializable {
return new ApiCall(this).get(apiPath, new AbortParser(rootXpath()+"/abort")); return new ApiCall(this).get(apiPath, new AbortParser(rootXpath()+"/abort"));
} }
/**
* Delete a set of executions, identified by the given IDs
*
* @param executionIds set of identifiers for the executions - mandatory
* @return a {@link RundeckExecution} 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 executionId is null
*/
public DeleteExecutionsResponse deleteExecutions(Set<Long> executionIds)
throws RundeckApiException, RundeckApiLoginException,
RundeckApiTokenException, IllegalArgumentException
{
AssertUtil.notNull(executionIds, "executionIds is mandatory to abort an execution !");
final ApiPathBuilder apiPath = new ApiPathBuilder("/executions/delete").xml(
new DeleteExecutionsGenerator(executionIds)
);
return new ApiCall(this).post(
apiPath,
new DeleteExecutionsResponseParser( rootXpath() + "/deleteExecutions")
);
}
/* /*
* History * History
*/ */

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

@ -56,6 +56,7 @@ public class RundeckClientTest {
public static final String TEST_TOKEN_5 = "C3O6d5O98Kr6Dpv71sdE4ERdCuU12P6d"; public static final String TEST_TOKEN_5 = "C3O6d5O98Kr6Dpv71sdE4ERdCuU12P6d";
public static final String TEST_TOKEN_6 = "Do4d3NUD5DKk21DR4sNK755RcPk618vn"; public static final String TEST_TOKEN_6 = "Do4d3NUD5DKk21DR4sNK755RcPk618vn";
public static final String TEST_TOKEN_7 = "8Dp9op111ER6opsDRkddvE86K9sE499s"; public static final String TEST_TOKEN_7 = "8Dp9op111ER6opsDRkddvE86K9sE499s";
public static final String TEST_TOKEN_8 = "GG7uj1y6UGahOs7QlmeN2sIwz1Y2j7zI";
@Rule @Rule
public Recorder recorder = new Recorder(); public Recorder recorder = new Recorder();
@ -1490,6 +1491,87 @@ 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()
);
}
@Before @Before
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

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=