diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index 3293151..7acb0b7 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -30,8 +30,10 @@ 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; @@ -41,8 +43,8 @@ import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import org.dom4j.Document; import org.rundeck.api.RundeckApiException.RundeckApiLoginException; -import org.rundeck.api.parser.XmlNodeParser; import org.rundeck.api.parser.ParserHelper; +import org.rundeck.api.parser.XmlNodeParser; import org.rundeck.api.util.AssertUtil; /** @@ -110,9 +112,38 @@ class ApiCall { * @throws RundeckApiException in case of error when calling the API * @throws RundeckApiLoginException if the login fails */ - public T get(ApiPathBuilder apiPath, XmlNodeParser parser) throws RundeckApiException, RundeckApiLoginException { - String apiUrl = client.getUrl() + RundeckClient.API_ENDPOINT + apiPath; + public T get(ApiPathBuilder apiPath, XmlNodeParser parser) throws RundeckApiException, + RundeckApiLoginException { + return execute(new HttpGet(client.getUrl() + RundeckClient.API_ENDPOINT + apiPath), 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 + */ + public T delete(ApiPathBuilder apiPath, XmlNodeParser parser) throws RundeckApiException, + RundeckApiLoginException { + 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 + */ + private T execute(HttpRequestBase request, XmlNodeParser parser) throws RundeckApiException, + RundeckApiLoginException { HttpClient httpClient = instantiateHttpClient(); try { login(httpClient); @@ -120,12 +151,34 @@ class ApiCall { // execute the HTTP request HttpResponse response = null; try { - response = httpClient.execute(new HttpGet(apiUrl)); + response = httpClient.execute(request); } catch (IOException e) { - throw new RundeckApiException("Failed to execute an HTTP GET on url : " + apiUrl, e); + throw new RundeckApiException("Failed to execute an HTTP " + request.getMethod() + " on url : " + + request.getURI(), e); } + + // HTTP client refuses to handle redirects (code 3xx) for DELETE, so we have to do it manually... + // See http://rundeck.lighthouseapp.com/projects/59277/tickets/248 + if (response.getStatusLine().getStatusCode() / 100 == 3 + && HttpDelete.METHOD_NAME.equals(request.getMethod())) { + 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 HttpDelete(newLocation); + try { + response = httpClient.execute(request); + } catch (IOException e) { + throw new RundeckApiException("Failed to execute an HTTP " + request.getMethod() + " on url : " + + request.getURI(), e); + } + } + if (response.getStatusLine().getStatusCode() / 100 != 2) { - throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' for " + apiUrl); + 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 : " @@ -235,5 +288,4 @@ class ApiCall { httpClient.getConnectionManager().getSchemeRegistry().register(new Scheme("https", 443, socketFactory)); return httpClient; } - } diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index b99f287..02b8072 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -24,16 +24,17 @@ import org.apache.commons.lang.StringUtils; import org.rundeck.api.RundeckApiException.RundeckApiLoginException; import org.rundeck.api.domain.RundeckAbort; import org.rundeck.api.domain.RundeckExecution; -import org.rundeck.api.domain.RundeckExecution.ExecutionStatus; import org.rundeck.api.domain.RundeckJob; import org.rundeck.api.domain.RundeckNode; import org.rundeck.api.domain.RundeckProject; +import org.rundeck.api.domain.RundeckExecution.ExecutionStatus; import org.rundeck.api.parser.AbortParser; import org.rundeck.api.parser.ExecutionParser; import org.rundeck.api.parser.JobParser; import org.rundeck.api.parser.ListParser; import org.rundeck.api.parser.NodeParser; import org.rundeck.api.parser.ProjectParser; +import org.rundeck.api.parser.StringParser; import org.rundeck.api.util.AssertUtil; import org.rundeck.api.util.ParametersUtil; @@ -229,6 +230,21 @@ public class RundeckClient implements Serializable { return new ApiCall(this).get(new ApiPathBuilder("/job/", jobId), new JobParser("joblist/job")); } + /** + * Delete a single job, identified by the given ID + * + * @param jobId identifier of the job - mandatory + * @return the success message (note that in case of error, you'll get an exception) + * @throws RundeckApiException in case of error when calling the API (non-existent job with this ID) + * @throws RundeckApiLoginException if the login failed + * @throws IllegalArgumentException if the jobId is blank (null, empty or whitespace) + */ + public String deleteJob(String jobId) throws RundeckApiException, RundeckApiLoginException, + IllegalArgumentException { + AssertUtil.notBlank(jobId, "jobId is mandatory to delete a job !"); + return new ApiCall(this).delete(new ApiPathBuilder("/job/", jobId), new StringParser("result/success/message")); + } + /** * Trigger the execution of a RunDeck job (identified by the given ID), and return immediately (without waiting the * end of the job execution) diff --git a/src/main/java/org/rundeck/api/parser/StringParser.java b/src/main/java/org/rundeck/api/parser/StringParser.java new file mode 100644 index 0000000..d53817f --- /dev/null +++ b/src/main/java/org/rundeck/api/parser/StringParser.java @@ -0,0 +1,49 @@ +/* + * 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.parser; + +import org.apache.commons.lang.StringUtils; +import org.dom4j.Node; + +/** + * Parser for a single {@link String} + * + * @author Vincent Behar + */ +public class StringParser implements XmlNodeParser { + + private String xpath; + + public StringParser() { + super(); + } + + /** + * @param xpath of the string element if it is not the root node + */ + public StringParser(String xpath) { + super(); + this.xpath = xpath; + } + + @Override + public String parseXmlNode(Node node) { + Node strNode = xpath != null ? node.selectSingleNode(xpath) : node; + + return StringUtils.trimToNull(strNode.getStringValue()); + } + +} diff --git a/src/test/java/org/rundeck/api/parser/StringParserTest.java b/src/test/java/org/rundeck/api/parser/StringParserTest.java new file mode 100644 index 0000000..de96a7b --- /dev/null +++ b/src/test/java/org/rundeck/api/parser/StringParserTest.java @@ -0,0 +1,40 @@ +/* + * 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.parser; + +import java.io.InputStream; +import org.dom4j.Document; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test the {@link StringParser} + * + * @author Vincent Behar + */ +public class StringParserTest { + + @Test + public void parseJob() throws Exception { + InputStream input = getClass().getResourceAsStream("message.xml"); + Document document = ParserHelper.loadDocument(input); + + String message = new StringParser("result/success/message").parseXmlNode(document); + + Assert.assertEquals("Job was successfully deleted: [1] /job-name", message); + } + +} diff --git a/src/test/resources/org/rundeck/api/parser/message.xml b/src/test/resources/org/rundeck/api/parser/message.xml new file mode 100644 index 0000000..fd2c28d --- /dev/null +++ b/src/test/resources/org/rundeck/api/parser/message.xml @@ -0,0 +1 @@ +Job was successfully deleted: [1] /job-name \ No newline at end of file