From c3b1cbc39f510c917b1a63ff486e7adcb809db08 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Thu, 27 Feb 2014 12:29:37 -0800 Subject: [PATCH 01/24] PRoduces xml stream from a document --- .../api/util/DocumentContentProducer.java | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/main/java/org/rundeck/api/util/DocumentContentProducer.java diff --git a/src/main/java/org/rundeck/api/util/DocumentContentProducer.java b/src/main/java/org/rundeck/api/util/DocumentContentProducer.java new file mode 100644 index 0000000..ace7840 --- /dev/null +++ b/src/main/java/org/rundeck/api/util/DocumentContentProducer.java @@ -0,0 +1,38 @@ +package org.rundeck.api.util; + +import org.apache.http.entity.ContentProducer; +import org.dom4j.Document; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * DocumentContentProducer writes XML document to a stream + * + * @author greg + * @since 2014-02-27 + */ +public class DocumentContentProducer implements ContentProducer { + Document document; + private OutputFormat format; + + public DocumentContentProducer(final Document document, final OutputFormat format) { + this.document = document; + this.format = format; + } + + public DocumentContentProducer(final Document document) { + this.document = document; + format = new OutputFormat("", false, "UTF-8"); + } + + @Override + public void writeTo(final OutputStream outstream) throws IOException { + + final XMLWriter xmlWriter = new XMLWriter(outstream, format); + xmlWriter.write(document); + xmlWriter.flush(); + } +} From bd7e8f5cec2eb2f154818b6218d7d376e3c349d1 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Thu, 27 Feb 2014 12:30:07 -0800 Subject: [PATCH 02/24] Allow adding an xml document to the api request --- .../java/org/rundeck/api/ApiPathBuilder.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/rundeck/api/ApiPathBuilder.java b/src/main/java/org/rundeck/api/ApiPathBuilder.java index 22643c8..e749004 100644 --- a/src/main/java/org/rundeck/api/ApiPathBuilder.java +++ b/src/main/java/org/rundeck/api/ApiPathBuilder.java @@ -26,6 +26,7 @@ import java.util.Properties; import org.apache.commons.lang.StringUtils; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; +import org.dom4j.Document; import org.rundeck.api.util.ParametersUtil; /** @@ -43,6 +44,7 @@ class ApiPathBuilder { /** When POSTing, we can add attachments */ private final Map attachments; private final List form = new ArrayList(); + private Document xmlDocument; /** Marker for using the right separator between parameters ("?" or "&") */ private boolean firstParamDone = false; @@ -266,6 +268,18 @@ class ApiPathBuilder { } return this; } + /** + * When POSTing a request, add the given XMl Document as the content of the request. + * + * @param document XMl document to send + * @return this, for method chaining + */ + public ApiPathBuilder xml(final Document document) { + if (document != null) { + xmlDocument = document; + } + return this; + } /** * @return all attachments to be POSTed, with their names @@ -311,7 +325,7 @@ class ApiPathBuilder { * Return true if there are any Attachments or Form data for a POST request. */ public boolean hasPostContent() { - return getAttachments().size() > 0 || getForm().size() > 0; + return getAttachments().size() > 0 || getForm().size() > 0 || null != xmlDocument; } /** @@ -321,6 +335,10 @@ class ApiPathBuilder { return accept; } + public Document getXmlDocument() { + return xmlDocument; + } + /** * BuildsParameters can add URL or POST parameters to an {@link ApiPathBuilder} * From 7543d5d630eb51acd1e05b215bb187d3f5f23fa7 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Thu, 27 Feb 2014 12:30:23 -0800 Subject: [PATCH 03/24] Send xml document if included in request --- src/main/java/org/rundeck/api/ApiCall.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index daac945..1dc2059 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -32,6 +32,7 @@ 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.EntityTemplate; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.InputStreamBody; @@ -48,6 +49,7 @@ import org.rundeck.api.RundeckApiException.RundeckApiTokenException; import org.rundeck.api.parser.ParserHelper; import org.rundeck.api.parser.XmlNodeParser; import org.rundeck.api.util.AssertUtil; +import org.rundeck.api.util.DocumentContentProducer; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -286,6 +288,9 @@ class ApiCall { } catch (UnsupportedEncodingException e) { throw new RundeckApiException("Unsupported encoding: " + e.getMessage(), e); } + }else if(apiPath.getXmlDocument()!=null) { + httpPost.setHeader("Content-Type", "application/xml"); + httpPost.setEntity(new EntityTemplate(new DocumentContentProducer(apiPath.getXmlDocument()))); }else { throw new IllegalArgumentException("No Form or Multipart entity for POST content-body"); } From 303a3dec576f5c390855a95435d1b5b635856a7d Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Thu, 27 Feb 2014 12:31:09 -0800 Subject: [PATCH 04/24] Generates project xml document for creation request --- .../api/generator/ProjectGenerator.java | 38 +++++++++++++++++++ .../api/generator/ProjectGeneratorTest.java | 25 ++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/main/java/org/rundeck/api/generator/ProjectGenerator.java create mode 100644 src/test/java/org/rundeck/api/generator/ProjectGeneratorTest.java diff --git a/src/main/java/org/rundeck/api/generator/ProjectGenerator.java b/src/main/java/org/rundeck/api/generator/ProjectGenerator.java new file mode 100644 index 0000000..37a45a4 --- /dev/null +++ b/src/main/java/org/rundeck/api/generator/ProjectGenerator.java @@ -0,0 +1,38 @@ +package org.rundeck.api.generator; + +import org.dom4j.Document; +import org.dom4j.DocumentFactory; +import org.dom4j.Element; +import org.rundeck.api.domain.ProjectConfig; +import org.rundeck.api.domain.RundeckProject; + +/** + * ProjectGenerator is ... + * + * @author greg + * @since 2014-02-27 + */ +public class ProjectGenerator { + RundeckProject project; + + public ProjectGenerator(RundeckProject project) { + this.project = project; + } + + public Document generate() { + Document projectDom = DocumentFactory.getInstance().createDocument(); + Element rootElem = projectDom.addElement("project"); + rootElem.addElement("name").setText(project.getName()); + ProjectConfig configuration = project.getProjectConfig(); + if (null != configuration) { + + Element config = rootElem.addElement("config"); + for (String s : configuration.getProperties().keySet()) { + Element property = config.addElement("property"); + property.addAttribute("key", s); + property.addAttribute("value", configuration.getProperties().get(s)); + } + } + return projectDom; + } +} diff --git a/src/test/java/org/rundeck/api/generator/ProjectGeneratorTest.java b/src/test/java/org/rundeck/api/generator/ProjectGeneratorTest.java new file mode 100644 index 0000000..a9c5b1b --- /dev/null +++ b/src/test/java/org/rundeck/api/generator/ProjectGeneratorTest.java @@ -0,0 +1,25 @@ +package org.rundeck.api.generator; + +import junit.framework.Assert; +import org.dom4j.Document; +import org.junit.Test; +import org.rundeck.api.domain.RundeckProject; + +/** + * ProjectGeneratorTest is ... + * + * @author greg + * @since 2014-02-27 + */ +public class ProjectGeneratorTest { + @Test + public void generate() { + RundeckProject project = new RundeckProject(); + project.setName("monkey1"); + + Document doc = new ProjectGenerator(project).generate(); + Assert.assertEquals("project", doc.getRootElement().getName()); + Assert.assertNotNull(doc.selectSingleNode("/project/name")); + Assert.assertEquals("monkey1", doc.selectSingleNode("/project/name").getText()); + } +} From b571c2a5beb1ca07bf7e12db7ad549777bf98d8b Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Thu, 27 Feb 2014 12:31:42 -0800 Subject: [PATCH 05/24] Support API v11, and project create endpoint --- .../java/org/rundeck/api/RundeckClient.java | 118 +++++++++++++----- .../org/rundeck/api/domain/ProjectConfig.java | 64 ++++++++++ .../rundeck/api/domain/RundeckProject.java | 23 +++- .../api/parser/ProjectConfigParser.java | 43 +++++++ .../org/rundeck/api/parser/ProjectParser.java | 5 +- .../rundeck/api/parser/ProjectParserV11.java | 36 ++++++ .../org/rundeck/api/RundeckClientTest.java | 10 ++ .../api/parser/ProjectConfigParserTest.java | 51 ++++++++ .../api/parser/ProjectParserV11Test.java | 28 +++++ .../betamax/tapes/create_projectv11.yaml | 26 ++++ .../org/rundeck/api/parser/projectv11.xml | 17 +++ 11 files changed, 388 insertions(+), 33 deletions(-) create mode 100644 src/main/java/org/rundeck/api/domain/ProjectConfig.java create mode 100644 src/main/java/org/rundeck/api/parser/ProjectConfigParser.java create mode 100644 src/main/java/org/rundeck/api/parser/ProjectParserV11.java create mode 100644 src/test/java/org/rundeck/api/parser/ProjectConfigParserTest.java create mode 100644 src/test/java/org/rundeck/api/parser/ProjectParserV11Test.java create mode 100644 src/test/resources/betamax/tapes/create_projectv11.yaml create mode 100644 src/test/resources/org/rundeck/api/parser/projectv11.xml diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index 61911bc..57ae3af 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -18,10 +18,14 @@ package org.rundeck.api; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; +import org.dom4j.Document; +import org.dom4j.DocumentFactory; +import org.dom4j.Element; 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.ProjectGenerator; import org.rundeck.api.parser.*; import org.rundeck.api.query.ExecutionQuery; import org.rundeck.api.util.AssertUtil; @@ -33,10 +37,7 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Properties; +import java.util.*; import java.util.concurrent.TimeUnit; /** @@ -95,6 +96,7 @@ public class RundeckClient implements Serializable { V8(8), V9(9), V10(10), + V11(11), ; 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.V10.getVersionNumber(); + public static final transient int API_VERSION = Version.V11.getVersionNumber(); private static final String API = "/api/"; @@ -274,10 +276,26 @@ public class RundeckClient implements Serializable { testAuth(); } + /** + * Return root xpath for xml api results. for v11 and later it is empty, for earlier it is "result" + * + * @return + */ + private String rootXpath() { + return getApiVersion() < Version.V11.getVersionNumber() ? "result" : ""; + } /* * Projects */ + private ProjectParser createProjectParser() { + return createProjectParser(null); + } + + private ProjectParser createProjectParser(final String xpath) { + return new ProjectParserV11(xpath); + } + /** * List all projects * @@ -289,7 +307,8 @@ public class RundeckClient implements Serializable { public List getProjects() throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException { return new ApiCall(this).get(new ApiPathBuilder("/projects"), - new ListParser(new ProjectParser(), "result/projects/project")); + new ListParser(createProjectParser(), rootXpath() + + "/projects/project")); } /** @@ -306,7 +325,48 @@ public class RundeckClient implements Serializable { RundeckApiTokenException, IllegalArgumentException { AssertUtil.notBlank(projectName, "projectName is mandatory to get the details of a project !"); return new ApiCall(this).get(new ApiPathBuilder("/project/", projectName), - new ProjectParser("result/projects/project")); + createProjectParser(rootXpath() + + (getApiVersion() < Version.V11.getVersionNumber() + ? "/projects/project" + : "/project" + ))); + } + + /** + * Create a new project, and return the new definition + * + * @param projectName name of the project - mandatory + * @param configuration project configuration properties + * + * @return a {@link RundeckProject} instance - won't be null + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public RundeckProject createProject(String projectName, Map configuration) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException { + + AssertUtil.notBlank(projectName, "projectName is mandatory to get the details of a project !"); + return new ApiCall(this) + .post(new ApiPathBuilder("/projects").xml( + projectDocument(projectName, configuration) + ), createProjectParser(rootXpath() + + (getApiVersion() < Version.V11.getVersionNumber() + ? "/projects/project" + : "/project" + ))); + } + + private Document projectDocument(String projectName, Map configuration) { + RundeckProject project = new RundeckProject(); + project.setName(projectName); + if (null != configuration) { + project.setProjectConfig(new ProjectConfig(configuration)); + } + return new ProjectGenerator(project).generate(); } /* @@ -366,7 +426,7 @@ public class RundeckClient implements Serializable { .param("jobFilter", jobFilter) .param("groupPath", groupPath) .param("idlist", StringUtils.join(jobIds, ",")), - new ListParser(new JobParser(), "result/jobs/job")); + new ListParser(new JobParser(), rootXpath()+"/jobs/job")); } /** @@ -699,7 +759,7 @@ public class RundeckClient implements Serializable { //API v8 request.param("project", rundeckJobsImport.getProject()); } - return new ApiCall(this).post(request, new JobsImportResultParser("result")); + return new ApiCall(this).post(request, new JobsImportResultParser(rootXpath())); } /** @@ -755,7 +815,7 @@ public class RundeckClient implements Serializable { public String deleteJob(String jobId) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, 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")); + return new ApiCall(this).delete(new ApiPathBuilder("/job/", jobId), new StringParser(rootXpath()+"/success/message")); } /** * Delete multiple jobs, identified by the given IDs @@ -773,7 +833,7 @@ public class RundeckClient implements Serializable { throw new IllegalArgumentException("jobIds are mandatory to delete a job"); } return new ApiCall(this).post(new ApiPathBuilder("/jobs/delete").field("ids",jobIds), - new BulkDeleteParser("result/deleteJobs")); + new BulkDeleteParser(rootXpath()+"/deleteJobs")); } /** @@ -798,7 +858,7 @@ public class RundeckClient implements Serializable { if(null!=jobRun.getAsUser()) { apiPath.param("asUser", jobRun.getAsUser()); } - return new ApiCall(this).get(apiPath, new ExecutionParser("result/executions/execution")); + return new ApiCall(this).get(apiPath, new ExecutionParser(rootXpath()+"/executions/execution")); } @@ -896,7 +956,7 @@ public class RundeckClient implements Serializable { if(null!= command.getAsUser()) { apiPath.param("asUser", command.getAsUser()); } - RundeckExecution execution = new ApiCall(this).get(apiPath, new ExecutionParser("result/execution")); + RundeckExecution execution = new ApiCall(this).get(apiPath, new ExecutionParser(rootXpath()+"/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()); } @@ -1030,7 +1090,7 @@ public class RundeckClient implements Serializable { if(null!=script.getAsUser()) { apiPath.param("asUser", script.getAsUser()); } - RundeckExecution execution = new ApiCall(this).post(apiPath, new ExecutionParser("result/execution")); + RundeckExecution execution = new ApiCall(this).post(apiPath, new ExecutionParser(rootXpath()+"/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()); } @@ -1177,7 +1237,7 @@ public class RundeckClient implements Serializable { AssertUtil.notBlank(project, "project is mandatory get all running executions !"); return new ApiCall(this).get(new ApiPathBuilder("/executions/running").param("project", project), new ListParser(new ExecutionParser(), - "result/executions/execution")); + rootXpath()+"/executions/execution")); } /** @@ -1275,7 +1335,7 @@ public class RundeckClient implements Serializable { .param("max", max) .param("offset", offset), new ListParser(new ExecutionParser(), - "result/executions/execution")); + rootXpath()+"/executions/execution")); } /** @@ -1302,7 +1362,7 @@ public class RundeckClient implements Serializable { .param("offset", offset), new PagedResultParser( new ListParser(new ExecutionParser(), "execution"), - "result/executions" + rootXpath()+"/executions" ) ); } @@ -1321,7 +1381,7 @@ public class RundeckClient implements Serializable { RundeckApiTokenException, IllegalArgumentException { AssertUtil.notNull(executionId, "executionId is mandatory to get the details of an execution !"); return new ApiCall(this).get(new ApiPathBuilder("/execution/", executionId.toString()), - new ExecutionParser("result/executions/execution")); + new ExecutionParser(rootXpath()+"/executions/execution")); } /** @@ -1356,7 +1416,7 @@ public class RundeckClient implements Serializable { if(null!=asUser) { apiPath.param("asUser", asUser); } - return new ApiCall(this).get(apiPath, new AbortParser("result/abort")); + return new ApiCall(this).get(apiPath, new AbortParser(rootXpath()+"/abort")); } /* @@ -1548,7 +1608,7 @@ public class RundeckClient implements Serializable { .param("end", end) .param("max", max) .param("offset", offset), - new HistoryParser("result/events")); + new HistoryParser(rootXpath()+"/events")); } /** @@ -1595,7 +1655,7 @@ public class RundeckClient implements Serializable { .param("max", max) .param("offset", offset); - return new ApiCall(this).postOrGet(builder, new HistoryParser("result/events")); + return new ApiCall(this).postOrGet(builder, new HistoryParser(rootXpath()+"/events")); } /* @@ -1773,7 +1833,7 @@ public class RundeckClient implements Serializable { param.param("maxlines", maxlines); } return new ApiCall(this).get(param, - new OutputParser("result/output", createOutputEntryParser())); + new OutputParser(rootXpath()+"/output", createOutputEntryParser())); } /** * Get the execution state of the given execution @@ -1792,7 +1852,7 @@ public class RundeckClient implements Serializable { "/execution/", executionId.toString(), "/state"); - return new ApiCall(this).get(param, new ExecutionStateParser("result/executionState")); + return new ApiCall(this).get(param, new ExecutionStateParser(rootXpath()+"/executionState")); } /** @@ -1834,7 +1894,7 @@ public class RundeckClient implements Serializable { param.param("maxlines", maxlines); } return new ApiCall(this).get(param, - new OutputParser("result/output", createOutputEntryParser())); + new OutputParser(rootXpath()+"/output", createOutputEntryParser())); } /** * Get the execution output of the given execution for the specified step @@ -1875,7 +1935,7 @@ public class RundeckClient implements Serializable { param.param("maxlines", maxlines); } return new ApiCall(this).get(param, - new OutputParser("result/output", createOutputEntryParser())); + new OutputParser(rootXpath()+"/output", createOutputEntryParser())); } /** * Get the execution output of the given execution for the specified step @@ -1919,7 +1979,7 @@ public class RundeckClient implements Serializable { param.param("maxlines", maxlines); } return new ApiCall(this).get(param, - new OutputParser("result/output", createOutputEntryParser())); + new OutputParser(rootXpath()+"/output", createOutputEntryParser())); } @@ -1966,7 +2026,7 @@ public class RundeckClient implements Serializable { if (maxlines > 0) { param.param("maxlines", maxlines); } - return new ApiCall(this).get(param, new OutputParser("result/output", createOutputEntryParser())); + return new ApiCall(this).get(param, new OutputParser(rootXpath()+"/output", createOutputEntryParser())); } /** * Get the execution state output sequence of the given job @@ -1997,7 +2057,7 @@ public class RundeckClient implements Serializable { if(stateOnly) { param.param("stateOnly", true); } - return new ApiCall(this).get(param, new OutputParser("result/output", createOutputEntryParser())); + return new ApiCall(this).get(param, new OutputParser(rootXpath()+"/output", createOutputEntryParser())); } private OutputEntryParser createOutputEntryParser() { @@ -2023,7 +2083,7 @@ public class RundeckClient implements Serializable { */ public RundeckSystemInfo getSystemInfo() throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException { - return new ApiCall(this).get(new ApiPathBuilder("/system/info"), new SystemInfoParser("result/system")); + return new ApiCall(this).get(new ApiPathBuilder("/system/info"), new SystemInfoParser(rootXpath()+"/system")); } /** diff --git a/src/main/java/org/rundeck/api/domain/ProjectConfig.java b/src/main/java/org/rundeck/api/domain/ProjectConfig.java new file mode 100644 index 0000000..ca3548f --- /dev/null +++ b/src/main/java/org/rundeck/api/domain/ProjectConfig.java @@ -0,0 +1,64 @@ +package org.rundeck.api.domain; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * ProjectConfig is ... + * + * @author greg + * @since 2014-02-27 + */ +public class ProjectConfig implements Serializable { + + private static final long serialVersionUID = 1L; + private LinkedHashMap properties = new LinkedHashMap(); + + public ProjectConfig() { + } + + public ProjectConfig(Map properties) { + setProperties(properties); + } + + public void setProperty(final String key, final String value) { + getProperties().put(key, value); + } + + public void addProperties(final Map values) { + getProperties().putAll(values); + } + + public Map getProperties() { + return properties; + } + + public void setProperties(final Map properties) { + this.properties = new LinkedHashMap(properties); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ProjectConfig)) return false; + + ProjectConfig that = (ProjectConfig) o; + + if (properties != null ? !properties.equals(that.properties) : that.properties != null) return false; + + return true; + } + + @Override + public int hashCode() { + return properties != null ? properties.hashCode() : 0; + } + + @Override + public String toString() { + return "ProjectConfig{" + + "properties=" + properties + + '}'; + } +} diff --git a/src/main/java/org/rundeck/api/domain/RundeckProject.java b/src/main/java/org/rundeck/api/domain/RundeckProject.java index c5e3fa4..32e38ea 100644 --- a/src/main/java/org/rundeck/api/domain/RundeckProject.java +++ b/src/main/java/org/rundeck/api/domain/RundeckProject.java @@ -32,6 +32,8 @@ public class RundeckProject implements Serializable { private String resourceModelProviderUrl; + private ProjectConfig projectConfig; + public String getName() { return name; } @@ -56,10 +58,20 @@ public class RundeckProject implements Serializable { this.resourceModelProviderUrl = resourceModelProviderUrl; } + public ProjectConfig getProjectConfig() { + return projectConfig; + } + + public void setProjectConfig(ProjectConfig projectConfig) { + this.projectConfig = projectConfig; + } + @Override public String toString() { - return "RundeckProject [name=" + name + ", description=" + description + ", resourceModelProviderUrl=" - + resourceModelProviderUrl + "]"; + return "RundeckProject [name=" + name + ", description=" + description + + (null!=resourceModelProviderUrl? ", resourceModelProviderUrl=" + resourceModelProviderUrl : "") + + ", config=" + + projectConfig + "]"; } @Override @@ -69,6 +81,7 @@ public class RundeckProject implements Serializable { result = prime * result + ((description == null) ? 0 : description.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + ((resourceModelProviderUrl == null) ? 0 : resourceModelProviderUrl.hashCode()); + result = prime * result + ((projectConfig == null) ? 0 : projectConfig.hashCode()); return result; } @@ -96,7 +109,11 @@ public class RundeckProject implements Serializable { return false; } else if (!resourceModelProviderUrl.equals(other.resourceModelProviderUrl)) return false; + if (projectConfig == null) { + if (other.projectConfig != null) + return false; + } else if (!projectConfig.equals(other.projectConfig)) + return false; return true; } - } diff --git a/src/main/java/org/rundeck/api/parser/ProjectConfigParser.java b/src/main/java/org/rundeck/api/parser/ProjectConfigParser.java new file mode 100644 index 0000000..7e92ffe --- /dev/null +++ b/src/main/java/org/rundeck/api/parser/ProjectConfigParser.java @@ -0,0 +1,43 @@ +package org.rundeck.api.parser; + +import org.dom4j.Node; +import org.rundeck.api.domain.ProjectConfig; + +import java.util.List; + +/** + * ProjectConfigParser parses project "config" element contents + * + * @author greg + * @since 2014-02-27 + */ +public class ProjectConfigParser implements XmlNodeParser { + private String xpath; + + public ProjectConfigParser() { + } + + public ProjectConfigParser(String xpath) { + this.xpath = xpath; + } + + @Override + public ProjectConfig parseXmlNode(Node node) { + Node config1 = getXpath() != null ? node.selectSingleNode(getXpath()) : node; + ProjectConfig config = new ProjectConfig(); + List property = config1.selectNodes("property"); + for (Object o : property) { + Node propnode = (Node) o; + String key = propnode.valueOf("@key"); + String value = propnode.valueOf("@value"); + if (null != key && null != value) { + config.setProperty(key, value); + } + } + return config; + } + + public String getXpath() { + return xpath; + } +} diff --git a/src/main/java/org/rundeck/api/parser/ProjectParser.java b/src/main/java/org/rundeck/api/parser/ProjectParser.java index 999ec70..ef6ca13 100644 --- a/src/main/java/org/rundeck/api/parser/ProjectParser.java +++ b/src/main/java/org/rundeck/api/parser/ProjectParser.java @@ -42,7 +42,7 @@ public class ProjectParser implements XmlNodeParser { @Override public RundeckProject parseXmlNode(Node node) { - Node projectNode = xpath != null ? node.selectSingleNode(xpath) : node; + Node projectNode = getXpath() != null ? node.selectSingleNode(getXpath()) : node; RundeckProject project = new RundeckProject(); @@ -53,4 +53,7 @@ public class ProjectParser implements XmlNodeParser { return project; } + protected String getXpath() { + return xpath; + } } diff --git a/src/main/java/org/rundeck/api/parser/ProjectParserV11.java b/src/main/java/org/rundeck/api/parser/ProjectParserV11.java new file mode 100644 index 0000000..631d941 --- /dev/null +++ b/src/main/java/org/rundeck/api/parser/ProjectParserV11.java @@ -0,0 +1,36 @@ +package org.rundeck.api.parser; + +import org.dom4j.Node; +import org.rundeck.api.domain.ProjectConfig; +import org.rundeck.api.domain.RundeckProject; + +import java.util.List; + +/** + * ProjectParserV11 supports embedded "config" element. + * + * @author greg + * @since 2014-02-27 + */ +public class ProjectParserV11 extends ProjectParser { + public ProjectParserV11() { + } + + public ProjectParserV11(final String xpath) { + super(xpath); + } + + @Override + public RundeckProject parseXmlNode(final Node node) { + final RundeckProject rundeckProject = super.parseXmlNode(node); + final Node projectNode = getXpath() != null ? node.selectSingleNode(getXpath()) : node; + final Node config1 = projectNode.selectSingleNode("config"); + if (config1 == null) { + return rundeckProject; + } + + rundeckProject.setProjectConfig(new ProjectConfigParser().parseXmlNode(config1)); + + return rundeckProject; + } +} diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index 6e755db..c66ea7c 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -82,6 +82,16 @@ public class RundeckClientTest { Assert.assertNull(projects.get(0).getDescription()); } @Test + @Betamax(tape = "create_projectv11") + public void createProject() throws Exception { + + RundeckProject project = createClient(TEST_TOKEN_6,11).createProject("monkey1", null); + Assert.assertEquals("monkey1", project.getName()); + Assert.assertEquals(null, project.getDescription()); + Assert.assertNotNull(project.getProjectConfig()); + + } + @Test @Betamax(tape = "get_history") public void getHistory() throws Exception { final RundeckHistory test = client.getHistory("test"); diff --git a/src/test/java/org/rundeck/api/parser/ProjectConfigParserTest.java b/src/test/java/org/rundeck/api/parser/ProjectConfigParserTest.java new file mode 100644 index 0000000..cee0f6f --- /dev/null +++ b/src/test/java/org/rundeck/api/parser/ProjectConfigParserTest.java @@ -0,0 +1,51 @@ +package org.rundeck.api.parser; + +import org.dom4j.Document; +import org.junit.Assert; +import org.junit.Test; +import org.rundeck.api.domain.ProjectConfig; +import org.rundeck.api.domain.RundeckProject; + +import java.io.InputStream; + +/** + * ProjectConfigParserTest is ... + * + * @author greg + * @since 2014-02-27 + */ +public class ProjectConfigParserTest { + @Test + public void parseProject() throws Exception { + InputStream input = getClass().getResourceAsStream("projectv11.xml"); + Document document = ParserHelper.loadDocument(input); + + ProjectConfig config = new ProjectConfigParser("project/config").parseXmlNode(document); + + Assert.assertEquals(10, config.getProperties().size()); + Assert.assertEquals("ziggy", config.getProperties().get("project.name")); + Assert.assertEquals("false", config.getProperties().get("resources.source.1.config.requireFileExists")); + Assert.assertEquals("privateKey", config.getProperties().get("project.ssh-authentication")); + Assert.assertEquals("jsch-ssh", config.getProperties().get("service.NodeExecutor.default.provider")); + Assert.assertEquals("false", config.getProperties().get("resources.source.1.config.includeServerNode")); + Assert.assertEquals("false", config.getProperties().get("resources.source.1.config.generateFileAutomatically")); + Assert.assertEquals("/var/rundeck/projects/${project.name}/etc/resources.xml", + config.getProperties().get("resources.source.1.config.file")); + Assert.assertEquals("/var/lib/rundeck/.ssh/id_rsa", config.getProperties().get("project.ssh-keypath")); + Assert.assertEquals("jsch-scp", config.getProperties().get("service.FileCopier.default.provider")); + Assert.assertEquals("file", config.getProperties().get("resources.source.1.type")); + /* + + + + + + + + + + + */ + } +} diff --git a/src/test/java/org/rundeck/api/parser/ProjectParserV11Test.java b/src/test/java/org/rundeck/api/parser/ProjectParserV11Test.java new file mode 100644 index 0000000..cff9fc9 --- /dev/null +++ b/src/test/java/org/rundeck/api/parser/ProjectParserV11Test.java @@ -0,0 +1,28 @@ +package org.rundeck.api.parser; + +import org.dom4j.Document; +import org.junit.Assert; +import org.junit.Test; +import org.rundeck.api.domain.RundeckProject; + +import java.io.InputStream; + +/** + * ProjectParserV11Test is ... + * + * @author greg + * @since 2014-02-27 + */ +public class ProjectParserV11Test { + @Test + public void parseProject() throws Exception { + InputStream input = getClass().getResourceAsStream("projectv11.xml"); + Document document = ParserHelper.loadDocument(input); + + RundeckProject project = new ProjectParserV11("project").parseXmlNode(document); + + Assert.assertEquals("ziggy", project.getName()); + Assert.assertNull(project.getDescription()); + Assert.assertNotNull(project.getProjectConfig()); + } +} diff --git a/src/test/resources/betamax/tapes/create_projectv11.yaml b/src/test/resources/betamax/tapes/create_projectv11.yaml new file mode 100644 index 0000000..e928798 --- /dev/null +++ b/src/test/resources/betamax/tapes/create_projectv11.yaml @@ -0,0 +1,26 @@ +!tape +name: create_projectv11 +interactions: +- recorded: 2014-02-27T19:45:35.430Z + request: + method: POST + uri: http://rundeck.local:4440/api/11/projects + 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 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + 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=1h2r5l35j4ynqo19dsm6xa2gv;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PHByb2plY3QgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3Byb2plY3QvbW9ua2V5MSc+CiAgPG5hbWU+bW9ua2V5MTwvbmFtZT4KICA8ZGVzY3JpcHRpb24+PC9kZXNjcmlwdGlvbj4KICA8Y29uZmlnPgogICAgPHByb3BlcnR5IGtleT0ncHJvamVjdC5uYW1lJyB2YWx1ZT0nbW9ua2V5MScgLz4KICAgIDxwcm9wZXJ0eSBrZXk9J3Byb2plY3Quc3NoLWF1dGhlbnRpY2F0aW9uJyB2YWx1ZT0ncHJpdmF0ZUtleScgLz4KICAgIDxwcm9wZXJ0eSBrZXk9J3NlcnZpY2UuTm9kZUV4ZWN1dG9yLmRlZmF1bHQucHJvdmlkZXInIHZhbHVlPSdqc2NoLXNzaCcgLz4KICAgIDxwcm9wZXJ0eSBrZXk9J3Jlc291cmNlcy5zb3VyY2UuMS5jb25maWcuaW5jbHVkZVNlcnZlck5vZGUnIHZhbHVlPSd0cnVlJyAvPgogICAgPHByb3BlcnR5IGtleT0ncmVzb3VyY2VzLnNvdXJjZS4xLmNvbmZpZy5nZW5lcmF0ZUZpbGVBdXRvbWF0aWNhbGx5JyB2YWx1ZT0ndHJ1ZScgLz4KICAgIDxwcm9wZXJ0eSBrZXk9J3Jlc291cmNlcy5zb3VyY2UuMS5jb25maWcuZmlsZScgdmFsdWU9Jy9Vc2Vycy9ncmVnL3J1bmRlY2syZC9wcm9qZWN0cy9tb25rZXkxL2V0Yy9yZXNvdXJjZXMueG1sJyAvPgogICAgPHByb3BlcnR5IGtleT0ncHJvamVjdC5zc2gta2V5cGF0aCcgdmFsdWU9Jy9Vc2Vycy9ncmVnLy5zc2gvaWRfcnNhJyAvPgogICAgPHByb3BlcnR5IGtleT0nc2VydmljZS5GaWxlQ29waWVyLmRlZmF1bHQucHJvdmlkZXInIHZhbHVlPSdqc2NoLXNjcCcgLz4KICAgIDxwcm9wZXJ0eSBrZXk9J3Jlc291cmNlcy5zb3VyY2UuMS50eXBlJyB2YWx1ZT0nZmlsZScgLz4KICA8L2NvbmZpZz4KPC9wcm9qZWN0Pg== diff --git a/src/test/resources/org/rundeck/api/parser/projectv11.xml b/src/test/resources/org/rundeck/api/parser/projectv11.xml new file mode 100644 index 0000000..e47e69d --- /dev/null +++ b/src/test/resources/org/rundeck/api/parser/projectv11.xml @@ -0,0 +1,17 @@ + + ziggy + + + + + + + + + + + + + + From 92eb7acc1531c36711ed3d7672803d3cb30fa7c8 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Thu, 27 Feb 2014 12:44:26 -0800 Subject: [PATCH 06/24] Add get Project/config endpoint --- .../java/org/rundeck/api/RundeckClient.java | 22 ++++++++++++++++- .../org/rundeck/api/RundeckClientTest.java | 11 +++++++++ .../betamax/tapes/get_project_configv11.yaml | 24 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/betamax/tapes/get_project_configv11.yaml diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index 57ae3af..cd3739e 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -349,7 +349,7 @@ public class RundeckClient implements Serializable { RundeckApiException, RundeckApiLoginException, RundeckApiTokenException, IllegalArgumentException { - AssertUtil.notBlank(projectName, "projectName is mandatory to get the details of a project !"); + AssertUtil.notBlank(projectName, "projectName is mandatory to create a project !"); return new ApiCall(this) .post(new ApiPathBuilder("/projects").xml( projectDocument(projectName, configuration) @@ -359,6 +359,26 @@ public class RundeckClient implements Serializable { : "/project" ))); } + /** + * Return the configuration of a project + * + * @param projectName name of the project - mandatory + * + * @return a {@link ProjectConfig} instance - won't be null + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public ProjectConfig getProjectConfig(String projectName) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException { + + AssertUtil.notBlank(projectName, "projectName is mandatory to get the config of a project !"); + return new ApiCall(this) + .get(new ApiPathBuilder("/project/", projectName, "/config"), new ProjectConfigParser("/config")); + } private Document projectDocument(String projectName, Map configuration) { RundeckProject project = new RundeckProject(); diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index c66ea7c..4ab1fe7 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -91,6 +91,17 @@ public class RundeckClientTest { Assert.assertNotNull(project.getProjectConfig()); } + + @Test + @Betamax(tape = "get_project_configv11") + public void getProjectConfig() throws Exception { + ProjectConfig config = createClient(TEST_TOKEN_6, 11).getProjectConfig("monkey1"); + Assert.assertNotNull(config); + Assert.assertNotNull(config.getProperties()); + Assert.assertEquals(9,config.getProperties().size()); + Assert.assertEquals("monkey1", config.getProperties().get("project.name")); + } + @Test @Betamax(tape = "get_history") public void getHistory() throws Exception { diff --git a/src/test/resources/betamax/tapes/get_project_configv11.yaml b/src/test/resources/betamax/tapes/get_project_configv11.yaml new file mode 100644 index 0000000..033aa13 --- /dev/null +++ b/src/test/resources/betamax/tapes/get_project_configv11.yaml @@ -0,0 +1,24 @@ +!tape +name: get_project_configv11 +interactions: +- recorded: 2014-02-27T20:35:47.282Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/project/monkey1/config + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + 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=bo96n10n268hsd1gi9y67nah;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PGNvbmZpZz4KICA8cHJvcGVydHkga2V5PSdwcm9qZWN0Lm5hbWUnIHZhbHVlPSdtb25rZXkxJyAvPgogIDxwcm9wZXJ0eSBrZXk9J3Byb2plY3Quc3NoLWF1dGhlbnRpY2F0aW9uJyB2YWx1ZT0ncHJpdmF0ZUtleScgLz4KICA8cHJvcGVydHkga2V5PSdzZXJ2aWNlLk5vZGVFeGVjdXRvci5kZWZhdWx0LnByb3ZpZGVyJyB2YWx1ZT0nanNjaC1zc2gnIC8+CiAgPHByb3BlcnR5IGtleT0ncmVzb3VyY2VzLnNvdXJjZS4xLmNvbmZpZy5pbmNsdWRlU2VydmVyTm9kZScgdmFsdWU9J3RydWUnIC8+CiAgPHByb3BlcnR5IGtleT0ncmVzb3VyY2VzLnNvdXJjZS4xLmNvbmZpZy5nZW5lcmF0ZUZpbGVBdXRvbWF0aWNhbGx5JyB2YWx1ZT0ndHJ1ZScgLz4KICA8cHJvcGVydHkga2V5PSdyZXNvdXJjZXMuc291cmNlLjEuY29uZmlnLmZpbGUnIHZhbHVlPScvVXNlcnMvZ3JlZy9ydW5kZWNrMmQvcHJvamVjdHMvbW9ua2V5MS9ldGMvcmVzb3VyY2VzLnhtbCcgLz4KICA8cHJvcGVydHkga2V5PSdwcm9qZWN0LnNzaC1rZXlwYXRoJyB2YWx1ZT0nL1VzZXJzL2dyZWcvLnNzaC9pZF9yc2EnIC8+CiAgPHByb3BlcnR5IGtleT0nc2VydmljZS5GaWxlQ29waWVyLmRlZmF1bHQucHJvdmlkZXInIHZhbHVlPSdqc2NoLXNjcCcgLz4KICA8cHJvcGVydHkga2V5PSdyZXNvdXJjZXMuc291cmNlLjEudHlwZScgdmFsdWU9J2ZpbGUnIC8+CjwvY29uZmlnPg== From e548c14b242f393406fb46f22b276d085b7366d2 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 7 Mar 2014 10:07:31 -0800 Subject: [PATCH 07/24] Add setProjectConfig for apiv11 --- src/main/java/org/rundeck/api/ApiCall.java | 36 ++++++++++------ .../java/org/rundeck/api/ApiPathBuilder.java | 13 ++++++ .../java/org/rundeck/api/RundeckClient.java | 25 ++++++++++- .../api/generator/BaseDocGenerator.java | 18 ++++++++ .../api/generator/ProjectConfigGenerator.java | 34 +++++++++++++++ .../api/generator/ProjectGenerator.java | 18 +++----- .../api/generator/XmlDocumentGenerator.java | 29 +++++++++++++ .../org/rundeck/api/RundeckClientTest.java | 14 +++++++ .../generator/ProjectConfigGeneratorTest.java | 41 +++++++++++++++++++ .../api/generator/ProjectGeneratorTest.java | 2 +- .../betamax/tapes/set_project_configv11.yaml | 26 ++++++++++++ 11 files changed, 230 insertions(+), 26 deletions(-) create mode 100644 src/main/java/org/rundeck/api/generator/BaseDocGenerator.java create mode 100644 src/main/java/org/rundeck/api/generator/ProjectConfigGenerator.java create mode 100644 src/main/java/org/rundeck/api/generator/XmlDocumentGenerator.java create mode 100644 src/test/java/org/rundeck/api/generator/ProjectConfigGeneratorTest.java create mode 100644 src/test/resources/betamax/tapes/set_project_configv11.yaml diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index 1dc2059..8d9f857 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -16,19 +16,10 @@ package org.rundeck.api; import org.apache.commons.lang.StringUtils; -import org.apache.http.Header; -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.*; 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.client.methods.*; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.conn.ssl.TrustStrategy; @@ -272,6 +263,27 @@ class ApiCall { public T post(ApiPathBuilder apiPath, XmlNodeParser parser) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException { HttpPost httpPost = new HttpPost(client.getUrl() + client.getApiEndpoint() + apiPath); + return requestWithEntity(apiPath, parser, httpPost); + } + /** + * Execute an HTTP PUT 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) + */ + public T put(ApiPathBuilder apiPath, XmlNodeParser parser) throws RundeckApiException, + RundeckApiLoginException, RundeckApiTokenException { + HttpPut httpPut = new HttpPut(client.getUrl() + client.getApiEndpoint() + apiPath); + return requestWithEntity(apiPath, parser, httpPut); + } + + private T requestWithEntity(ApiPathBuilder apiPath, XmlNodeParser parser, HttpEntityEnclosingRequestBase + httpPost) { if(null!= apiPath.getAccept()) { httpPost.setHeader("Accept", apiPath.getAccept()); } @@ -344,7 +356,7 @@ class ApiCall { * @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, + private ByteArrayInputStream execute(HttpUriRequest request) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException { HttpClient httpClient = instantiateHttpClient(); try { diff --git a/src/main/java/org/rundeck/api/ApiPathBuilder.java b/src/main/java/org/rundeck/api/ApiPathBuilder.java index e749004..bf27ae9 100644 --- a/src/main/java/org/rundeck/api/ApiPathBuilder.java +++ b/src/main/java/org/rundeck/api/ApiPathBuilder.java @@ -27,6 +27,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import org.dom4j.Document; +import org.rundeck.api.generator.XmlDocumentGenerator; import org.rundeck.api.util.ParametersUtil; /** @@ -280,6 +281,18 @@ class ApiPathBuilder { } return this; } + /** + * When POSTing a request, add the given XMl Document as the content of the request. + * + * @param document XMl document to send + * @return this, for method chaining + */ + public ApiPathBuilder xml(final XmlDocumentGenerator document) { + if (document != null) { + xmlDocument = document.generateXmlDocument(); + } + return this; + } /** * @return all attachments to be POSTed, with their names diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index cd3739e..8cf1989 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -25,6 +25,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.ProjectConfigGenerator; import org.rundeck.api.generator.ProjectGenerator; import org.rundeck.api.parser.*; import org.rundeck.api.query.ExecutionQuery; @@ -379,6 +380,28 @@ public class RundeckClient implements Serializable { return new ApiCall(this) .get(new ApiPathBuilder("/project/", projectName, "/config"), new ProjectConfigParser("/config")); } + /** + * Return the configuration of a project + * + * @param projectName name of the project - mandatory + * + * @return a {@link ProjectConfig} instance - won't be null + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public ProjectConfig setProjectConfig(String projectName, Map configuration) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException { + + AssertUtil.notBlank(projectName, "projectName is mandatory to get the config of a project !"); + return new ApiCall(this) + .put(new ApiPathBuilder("/project/", projectName, "/config") + .xml(new ProjectConfigGenerator(new ProjectConfig(configuration))) + , new ProjectConfigParser("/config")); + } private Document projectDocument(String projectName, Map configuration) { RundeckProject project = new RundeckProject(); @@ -386,7 +409,7 @@ public class RundeckClient implements Serializable { if (null != configuration) { project.setProjectConfig(new ProjectConfig(configuration)); } - return new ProjectGenerator(project).generate(); + return new ProjectGenerator(project).generateXmlDocument(); } /* diff --git a/src/main/java/org/rundeck/api/generator/BaseDocGenerator.java b/src/main/java/org/rundeck/api/generator/BaseDocGenerator.java new file mode 100644 index 0000000..46bf574 --- /dev/null +++ b/src/main/java/org/rundeck/api/generator/BaseDocGenerator.java @@ -0,0 +1,18 @@ +package org.rundeck.api.generator; + +import org.dom4j.Document; +import org.dom4j.DocumentFactory; + +/** + * BaseDocGenerator generates a document using the element as the root. + * + * @author greg + * @since 2014-02-27 + */ +public abstract class BaseDocGenerator implements XmlDocumentGenerator { + @Override + public Document generateXmlDocument() { + return DocumentFactory.getInstance().createDocument(generateXmlElement()); + } + +} diff --git a/src/main/java/org/rundeck/api/generator/ProjectConfigGenerator.java b/src/main/java/org/rundeck/api/generator/ProjectConfigGenerator.java new file mode 100644 index 0000000..547b154 --- /dev/null +++ b/src/main/java/org/rundeck/api/generator/ProjectConfigGenerator.java @@ -0,0 +1,34 @@ +package org.rundeck.api.generator; + +import org.dom4j.Document; +import org.dom4j.DocumentFactory; +import org.dom4j.Element; +import org.rundeck.api.domain.ProjectConfig; + +/** + * ProjectConfigGenerator is ... + * + * @author greg + * @since 2014-02-27 + */ +public class ProjectConfigGenerator extends BaseDocGenerator { + private ProjectConfig config; + + public ProjectConfigGenerator(ProjectConfig config) { + this.config = config; + } + + @Override + public Element generateXmlElement() { + Element configEl = DocumentFactory.getInstance().createElement("config"); + if (null != config.getProperties()) { + for (String s : config.getProperties().keySet()) { + Element property = configEl.addElement("property"); + property.addAttribute("key", s); + property.addAttribute("value", config.getProperties().get(s)); + } + } + return configEl; + } + +} diff --git a/src/main/java/org/rundeck/api/generator/ProjectGenerator.java b/src/main/java/org/rundeck/api/generator/ProjectGenerator.java index 37a45a4..8960acf 100644 --- a/src/main/java/org/rundeck/api/generator/ProjectGenerator.java +++ b/src/main/java/org/rundeck/api/generator/ProjectGenerator.java @@ -12,27 +12,21 @@ import org.rundeck.api.domain.RundeckProject; * @author greg * @since 2014-02-27 */ -public class ProjectGenerator { +public class ProjectGenerator extends BaseDocGenerator { RundeckProject project; public ProjectGenerator(RundeckProject project) { this.project = project; } - public Document generate() { - Document projectDom = DocumentFactory.getInstance().createDocument(); - Element rootElem = projectDom.addElement("project"); + @Override + public Element generateXmlElement() { + Element rootElem = DocumentFactory.getInstance().createElement("project"); rootElem.addElement("name").setText(project.getName()); ProjectConfig configuration = project.getProjectConfig(); if (null != configuration) { - - Element config = rootElem.addElement("config"); - for (String s : configuration.getProperties().keySet()) { - Element property = config.addElement("property"); - property.addAttribute("key", s); - property.addAttribute("value", configuration.getProperties().get(s)); - } + rootElem.add(new ProjectConfigGenerator(configuration).generateXmlElement()); } - return projectDom; + return rootElem; } } diff --git a/src/main/java/org/rundeck/api/generator/XmlDocumentGenerator.java b/src/main/java/org/rundeck/api/generator/XmlDocumentGenerator.java new file mode 100644 index 0000000..7d3faf3 --- /dev/null +++ b/src/main/java/org/rundeck/api/generator/XmlDocumentGenerator.java @@ -0,0 +1,29 @@ +package org.rundeck.api.generator; + +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.Node; + +/** + * XmlDocumentGenerator is ... + * + * @author greg + * @since 2014-02-27 + */ +public interface XmlDocumentGenerator { + + /** + * Generate the XML {@link org.dom4j.Node} + * + * @return any object holding the converted value + */ + Element generateXmlElement(); + /** + * Generate the XML {@link org.dom4j.Node} + * + * @param node + * + * @return any object holding the converted value + */ + Document generateXmlDocument(); +} diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index 4ab1fe7..0034aa6 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -101,6 +101,20 @@ public class RundeckClientTest { Assert.assertEquals(9,config.getProperties().size()); Assert.assertEquals("monkey1", config.getProperties().get("project.name")); } + @Test + @Betamax(tape = "set_project_configv11") + public void setProjectConfig() throws Exception { + HashMap config = new HashMap(); + config.put("alphabetty", "spaghetti"); + config.put("blha.blee", "a big amazing thingy so there."); + ProjectConfig result = createClient(TEST_TOKEN_6, 11).setProjectConfig("monkey1", config); + Assert.assertNotNull(result); + Assert.assertNotNull(result.getProperties()); + Assert.assertEquals(3, result.getProperties().size()); + Assert.assertEquals("monkey1", result.getProperties().get("project.name")); + Assert.assertEquals("spaghetti", result.getProperties().get("alphabetty")); + Assert.assertEquals("a big amazing thingy so there.", result.getProperties().get("blha.blee")); + } @Test @Betamax(tape = "get_history") diff --git a/src/test/java/org/rundeck/api/generator/ProjectConfigGeneratorTest.java b/src/test/java/org/rundeck/api/generator/ProjectConfigGeneratorTest.java new file mode 100644 index 0000000..aa6d5da --- /dev/null +++ b/src/test/java/org/rundeck/api/generator/ProjectConfigGeneratorTest.java @@ -0,0 +1,41 @@ +package org.rundeck.api.generator; + +import junit.framework.Assert; +import org.dom4j.Document; +import org.dom4j.Element; +import org.dom4j.io.XMLWriter; +import org.junit.Test; +import org.rundeck.api.domain.ProjectConfig; +import org.rundeck.api.domain.RundeckProject; + +import java.io.UnsupportedEncodingException; + +/** + * ProjectConfigGeneratorTest is ... + * + * @author greg + * @since 2014-02-27 + */ +public class ProjectConfigGeneratorTest { + @Test + public void generate() throws Exception { + ProjectConfig config = new ProjectConfig(); + config.setProperty("abc", "123"); + config.setProperty("monkey.bonanza", "pale\ncomparison"); + + Document doc = new ProjectConfigGenerator(config).generateXmlDocument(); + XMLWriter xmlWriter = new XMLWriter(System.out); + xmlWriter.write(doc); + xmlWriter.flush(); + Element configElement = doc.getRootElement(); + Assert.assertEquals("config", configElement.getName()); + Assert.assertNotNull(configElement.selectSingleNode("property[1]")); + Assert.assertEquals("abc", configElement.selectSingleNode("property[1]/@key").getText()); + Assert.assertEquals("123", configElement.selectSingleNode("property[1]/@value").getText()); + + Assert.assertNotNull(configElement.selectSingleNode("property[2]")); + Assert.assertEquals("monkey.bonanza", configElement.selectSingleNode("property[2]/@key").getText()); + Assert.assertEquals("pale\ncomparison", configElement.selectSingleNode("property[2]/@value").getText()); + + } +} diff --git a/src/test/java/org/rundeck/api/generator/ProjectGeneratorTest.java b/src/test/java/org/rundeck/api/generator/ProjectGeneratorTest.java index a9c5b1b..26beb33 100644 --- a/src/test/java/org/rundeck/api/generator/ProjectGeneratorTest.java +++ b/src/test/java/org/rundeck/api/generator/ProjectGeneratorTest.java @@ -17,7 +17,7 @@ public class ProjectGeneratorTest { RundeckProject project = new RundeckProject(); project.setName("monkey1"); - Document doc = new ProjectGenerator(project).generate(); + Document doc = new ProjectGenerator(project).generateXmlDocument(); Assert.assertEquals("project", doc.getRootElement().getName()); Assert.assertNotNull(doc.selectSingleNode("/project/name")); Assert.assertEquals("monkey1", doc.selectSingleNode("/project/name").getText()); diff --git a/src/test/resources/betamax/tapes/set_project_configv11.yaml b/src/test/resources/betamax/tapes/set_project_configv11.yaml new file mode 100644 index 0000000..573eeaa --- /dev/null +++ b/src/test/resources/betamax/tapes/set_project_configv11.yaml @@ -0,0 +1,26 @@ +!tape +name: set_project_configv11 +interactions: +- recorded: 2014-02-27T21:00:27.197Z + request: + method: PUT + uri: http://rundeck.local:4440/api/11/project/monkey1/config + 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 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + 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=19npj7cd0hpm71nfljn7nlbvh8;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PGNvbmZpZz4KICA8cHJvcGVydHkga2V5PSdwcm9qZWN0Lm5hbWUnIHZhbHVlPSdtb25rZXkxJyAvPgogIDxwcm9wZXJ0eSBrZXk9J2FscGhhYmV0dHknIHZhbHVlPSdzcGFnaGV0dGknIC8+CiAgPHByb3BlcnR5IGtleT0nYmxoYS5ibGVlJyB2YWx1ZT0nYSBiaWcgYW1hemluZyB0aGluZ3kgc28gdGhlcmUuJyAvPgo8L2NvbmZpZz4= From 29459d9ee1195c054fe4f76f682eda1ebd1bece0 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 7 Mar 2014 12:16:10 -0800 Subject: [PATCH 08/24] Support http DELETE with expected 204 no content --- src/main/java/org/rundeck/api/ApiCall.java | 31 ++++++++++++++++--- .../org/rundeck/api/RundeckApiException.java | 22 +++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index 8d9f857..93993ad 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -325,6 +325,22 @@ class ApiCall { RundeckApiLoginException, RundeckApiTokenException { return execute(new HttpDelete(client.getUrl() + client.getApiEndpoint() + apiPath), parser); } + /** + * Execute an HTTP DELETE request to the RunDeck instance, on the given path, and expect a 204 response. + * + * @param apiPath on which we will make the HTTP request - see {@link ApiPathBuilder} + * @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) + */ + public void delete(ApiPathBuilder apiPath) throws RundeckApiException, + RundeckApiLoginException, RundeckApiTokenException { + + InputStream response = execute(new HttpDelete(client.getUrl() + client.getApiEndpoint() + apiPath)); + if(null!=response){ + throw new RundeckApiException("Unexpected Rundeck response content, expected no content!"); + } + } /** * Execute an HTTP request to the RunDeck instance. We will login first, and then execute the API call. At the end, @@ -377,7 +393,8 @@ class ApiCall { // 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) { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode / 100 == 3) { String newLocation = response.getFirstHeader("Location").getValue(); try { EntityUtils.consume(response.getEntity()); @@ -387,22 +404,26 @@ class ApiCall { request = new HttpGet(newLocation); try { response = httpClient.execute(request); + statusCode = response.getStatusLine().getStatusCode(); } 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 && + if (statusCode / 100 != 2) { + if (statusCode == 403 && (client.getToken() != null || client.getSessionID() != null)) { throw new RundeckApiTokenException("Invalid Token or sessionID ! Got HTTP response '" + response.getStatusLine() + "' for " + request.getURI()); } else { - throw new RundeckApiException("Invalid HTTP response '" + response.getStatusLine() + "' for " - + request.getURI()); + throw new RundeckApiException.RundeckApiHttpStatusException("Invalid HTTP response '" + response.getStatusLine() + "' for " + + request.getURI(), statusCode); } } + if(statusCode==204){ + return null; + } if (response.getEntity() == null) { throw new RundeckApiException("Empty RunDeck response ! HTTP status line is : " + response.getStatusLine()); diff --git a/src/main/java/org/rundeck/api/RundeckApiException.java b/src/main/java/org/rundeck/api/RundeckApiException.java index 141e905..2a5b1ca 100644 --- a/src/main/java/org/rundeck/api/RundeckApiException.java +++ b/src/main/java/org/rundeck/api/RundeckApiException.java @@ -82,5 +82,27 @@ public class RundeckApiException extends RuntimeException { super(message, cause); } } + /** + * Error due to unexpected HTTP status + */ + public static class RundeckApiHttpStatusException extends RundeckApiAuthException { + + private static final long serialVersionUID = 1L; + private int statusCode; + + public RundeckApiHttpStatusException(String message, int statusCode) { + super(message); + this.statusCode = statusCode; + } + + public RundeckApiHttpStatusException(String message, Throwable cause, int statusCode) { + super(message, cause); + this.statusCode = statusCode; + } + + public int getStatusCode() { + return statusCode; + } + } } From f49aa63043b253944bffaf642bfc037c033e335c Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 7 Mar 2014 12:22:10 -0800 Subject: [PATCH 09/24] Support keyed config get/set/delete for apiv11 --- .../java/org/rundeck/api/RundeckClient.java | 87 +++++++++++++++++++ .../rundeck/api/domain/ConfigProperty.java | 68 +++++++++++++++ .../ProjectConfigPropertyGenerator.java | 28 ++++++ .../parser/ProjectConfigPropertyParser.java | 41 +++++++++ .../org/rundeck/api/RundeckClientTest.java | 29 +++++++ .../ProjectConfigPropertyParserTest.java | 48 ++++++++++ .../tapes/delete_project_config_keyedv11.yaml | 57 ++++++++++++ .../get_project_config_keyed_dne_v11.yaml | 22 +++++ .../tapes/get_project_config_keyedv11.yaml | 24 +++++ .../tapes/set_project_config_keyedv11.yaml | 26 ++++++ 10 files changed, 430 insertions(+) create mode 100644 src/main/java/org/rundeck/api/domain/ConfigProperty.java create mode 100644 src/main/java/org/rundeck/api/generator/ProjectConfigPropertyGenerator.java create mode 100644 src/main/java/org/rundeck/api/parser/ProjectConfigPropertyParser.java create mode 100644 src/test/java/org/rundeck/api/parser/ProjectConfigPropertyParserTest.java create mode 100644 src/test/resources/betamax/tapes/delete_project_config_keyedv11.yaml create mode 100644 src/test/resources/betamax/tapes/get_project_config_keyed_dne_v11.yaml create mode 100644 src/test/resources/betamax/tapes/get_project_config_keyedv11.yaml create mode 100644 src/test/resources/betamax/tapes/set_project_config_keyedv11.yaml diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index 8cf1989..f32f457 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -26,6 +26,7 @@ import org.rundeck.api.RundeckApiException.RundeckApiTokenException; import org.rundeck.api.domain.*; import org.rundeck.api.domain.RundeckExecution.ExecutionStatus; import org.rundeck.api.generator.ProjectConfigGenerator; +import org.rundeck.api.generator.ProjectConfigPropertyGenerator; import org.rundeck.api.generator.ProjectGenerator; import org.rundeck.api.parser.*; import org.rundeck.api.query.ExecutionQuery; @@ -380,6 +381,92 @@ public class RundeckClient implements Serializable { return new ApiCall(this) .get(new ApiPathBuilder("/project/", projectName, "/config"), new ProjectConfigParser("/config")); } + /** + * Get a single project configuration key + * + * @param projectName name of the project - mandatory + * @param key name of the configuration key + * + * @return value, or null if the value is not set + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public String getProjectConfig(final String projectName, final String key) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException { + + AssertUtil.notBlank(projectName, "projectName is mandatory to get the config of a project !"); + AssertUtil.notBlank(key, "key is mandatory to get the config key value!"); + + ConfigProperty configProperty = null; + try { + configProperty = new ApiCall(this) + .get(new ApiPathBuilder("/project/", projectName, "/config/", key), + new ProjectConfigPropertyParser("/property")); + } catch (RundeckApiException.RundeckApiHttpStatusException e) { + if(404==e.getStatusCode()){ + return null; + } + throw e; + } + return configProperty.getValue(); + } + /** + * Set a single project configuration property value + * + * @param projectName name of the project - mandatory + * @param key name of the configuration property + * @param value value of the property + * + * @return new value + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public String setProjectConfig(final String projectName, final String key, final String value) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException { + + AssertUtil.notBlank(projectName, "projectName is mandatory to set the config of a project !"); + AssertUtil.notBlank(key, "key is mandatory to set the config key value!"); + AssertUtil.notBlank(value, "value is mandatory to set the config key value!"); + + final ConfigProperty configProperty = new ApiCall(this) + .put(new ApiPathBuilder("/project/", projectName, "/config/", key) + .xml(new ProjectConfigPropertyGenerator(new ConfigProperty(key, value))), + new ProjectConfigPropertyParser("/property")); + + return configProperty.getValue(); + } + /** + * Set a single project configuration property value + * + * @param projectName name of the project - mandatory + * @param key name of the configuration property + * @param value value of the property + * + * @return new value + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public void deleteProjectConfig(final String projectName, final String key) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException { + + AssertUtil.notBlank(projectName, "projectName is mandatory to set the config of a project !"); + AssertUtil.notBlank(key, "key is mandatory to set the config key value!"); + + new ApiCall(this).delete(new ApiPathBuilder("/project/", projectName, "/config/", + key).accept("application/xml")); + } /** * Return the configuration of a project * diff --git a/src/main/java/org/rundeck/api/domain/ConfigProperty.java b/src/main/java/org/rundeck/api/domain/ConfigProperty.java new file mode 100644 index 0000000..d15d773 --- /dev/null +++ b/src/main/java/org/rundeck/api/domain/ConfigProperty.java @@ -0,0 +1,68 @@ +package org.rundeck.api.domain; + +import java.io.Serializable; + +/** + * ConfigProperty is a single configuration property key and value. + * + * @author greg + * @since 2014-03-07 + */ +public class ConfigProperty implements Serializable { + + private static final long serialVersionUID = 1L; + private String key; + private String value; + + public ConfigProperty() { + } + + public ConfigProperty(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ConfigProperty)) return false; + + ConfigProperty that = (ConfigProperty) o; + + if (!key.equals(that.key)) return false; + if (!value.equals(that.value)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = key.hashCode(); + result = 31 * result + value.hashCode(); + return result; + } + + @Override + public String toString() { + return "ConfigProperty{" + + "key='" + key + '\'' + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/src/main/java/org/rundeck/api/generator/ProjectConfigPropertyGenerator.java b/src/main/java/org/rundeck/api/generator/ProjectConfigPropertyGenerator.java new file mode 100644 index 0000000..f41766b --- /dev/null +++ b/src/main/java/org/rundeck/api/generator/ProjectConfigPropertyGenerator.java @@ -0,0 +1,28 @@ +package org.rundeck.api.generator; + +import org.dom4j.DocumentFactory; +import org.dom4j.Element; +import org.rundeck.api.domain.ConfigProperty; + +/** + * ProjectConfigPropertyGenerator generates a {@literal } element representing a configuration property. + * + * @author greg + * @since 2014-03-07 + */ +public class ProjectConfigPropertyGenerator extends BaseDocGenerator { + private ConfigProperty property; + + public ProjectConfigPropertyGenerator(ConfigProperty property) { + this.property = property; + } + + @Override + public Element generateXmlElement() { + Element propElem = DocumentFactory.getInstance().createElement("property"); + propElem.addAttribute("key", property.getKey()); + propElem.addAttribute("value", property.getValue()); + + return propElem; + } +} diff --git a/src/main/java/org/rundeck/api/parser/ProjectConfigPropertyParser.java b/src/main/java/org/rundeck/api/parser/ProjectConfigPropertyParser.java new file mode 100644 index 0000000..9a57114 --- /dev/null +++ b/src/main/java/org/rundeck/api/parser/ProjectConfigPropertyParser.java @@ -0,0 +1,41 @@ +package org.rundeck.api.parser; + +import org.dom4j.Node; +import org.rundeck.api.domain.ConfigProperty; + +/** + * ProjectConfigPropertyParser parses a {@literal } element representing + * a configuration property. + * + * @author greg + * @since 2014-03-07 + */ +public class ProjectConfigPropertyParser implements XmlNodeParser { + private String xpath; + + public ProjectConfigPropertyParser() { + } + + public ProjectConfigPropertyParser(final String xpath) { + this.setXpath(xpath); + } + + @Override + public ConfigProperty parseXmlNode(final Node node) { + final Node propnode = getXpath() != null ? node.selectSingleNode(getXpath()) : node; + final String key = propnode.valueOf("@key"); + final String value = propnode.valueOf("@value"); + final ConfigProperty config = new ConfigProperty(); + config.setKey(key); + config.setValue(value); + return config; + } + + public String getXpath() { + return xpath; + } + + public void setXpath(final String xpath) { + this.xpath = xpath; + } +} diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index 0034aa6..ad475fd 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -116,6 +116,35 @@ public class RundeckClientTest { Assert.assertEquals("a big amazing thingy so there.", result.getProperties().get("blha.blee")); } + @Test + @Betamax(tape = "get_project_config_keyedv11") + public void getProjectConfigKeyed() throws Exception { + String value = createClient(TEST_TOKEN_6, 11).getProjectConfig("ABC", "project.name"); + Assert.assertNotNull(value); + Assert.assertEquals("ABC", value); + } + @Test + @Betamax(tape = "get_project_config_keyed_dne_v11") + public void getProjectConfigKeyedDNE() throws Exception { + String value = createClient(TEST_TOKEN_6, 11).getProjectConfig("ABC", "does-not-exist"); + Assert.assertNull(value); + } + @Test + @Betamax(tape = "set_project_config_keyedv11") + public void setProjectConfigKeyed() throws Exception { + String value = createClient(TEST_TOKEN_6, 11).setProjectConfig("ABC", "monkey-burrito", "lemon pie"); + Assert.assertNotNull(value); + Assert.assertEquals("lemon pie", value); + } + @Test + @Betamax(tape = "delete_project_config_keyedv11") + public void deleteProjectConfigKeyed() throws Exception { + RundeckClient client1 = createClient(TEST_TOKEN_6, 11); + Assert.assertEquals("7up", client1.setProjectConfig("ABC", "monkey-burrito", "7up")); + client1.deleteProjectConfig("ABC", "monkey-burrito"); + String value=client1.getProjectConfig("ABC", "monkey-burrito"); + Assert.assertNull(value); + } @Test @Betamax(tape = "get_history") public void getHistory() throws Exception { diff --git a/src/test/java/org/rundeck/api/parser/ProjectConfigPropertyParserTest.java b/src/test/java/org/rundeck/api/parser/ProjectConfigPropertyParserTest.java new file mode 100644 index 0000000..0fcac10 --- /dev/null +++ b/src/test/java/org/rundeck/api/parser/ProjectConfigPropertyParserTest.java @@ -0,0 +1,48 @@ +package org.rundeck.api.parser; + +import junit.framework.Assert; +import org.dom4j.Document; +import org.junit.Test; +import org.junit.runners.JUnit4; +import org.rundeck.api.domain.ConfigProperty; +import org.rundeck.api.domain.ProjectConfig; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * Test + * + * @author greg + * @since 2014-03-07 + */ +public class ProjectConfigPropertyParserTest { + @Test + public void parseFromProject() throws Exception { + InputStream input = getClass().getResourceAsStream("projectv11.xml"); + Document document = ParserHelper.loadDocument(input); + + ConfigProperty config = new ProjectConfigPropertyParser("project/config/property[1]").parseXmlNode(document); + Assert.assertEquals("project.name", config.getKey()); + Assert.assertEquals("ziggy", config.getValue()); + /** + * + + + */ + } + @Test + public void parseProperty() throws Exception { + Document document = ParserHelper.loadDocument(new ByteArrayInputStream(("").getBytes())); + + ConfigProperty config = new ProjectConfigPropertyParser("/property").parseXmlNode(document); + Assert.assertEquals("project.name", config.getKey()); + Assert.assertEquals("ABC", config.getValue()); + /** + * + + + */ + } +} diff --git a/src/test/resources/betamax/tapes/delete_project_config_keyedv11.yaml b/src/test/resources/betamax/tapes/delete_project_config_keyedv11.yaml new file mode 100644 index 0000000..c107f56 --- /dev/null +++ b/src/test/resources/betamax/tapes/delete_project_config_keyedv11.yaml @@ -0,0 +1,57 @@ +!tape +name: delete_project_config_keyedv11 +interactions: +- recorded: 2014-03-07T19:59:51.228Z + request: + method: PUT + uri: http://rundeck.local:4440/api/11/project/ABC/config/monkey-burrito + 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 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + 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=bolnwf54stai1bo049hylrsua;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PHByb3BlcnR5IGtleT0nbW9ua2V5LWJ1cnJpdG8nIHZhbHVlPSc3dXAnIC8+ +- recorded: 2014-03-07T19:59:51.325Z + request: + method: DELETE + uri: http://rundeck.local:4440/api/11/project/ABC/config/monkey-burrito + headers: + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + response: + status: 204 + headers: + Content-Type: text/html;charset=UTF-8 + Server: Jetty(7.6.0.v20120127) +- recorded: 2014-03-07T19:59:51.402Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/project/ABC/config/monkey-burrito + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + response: + status: 404 + headers: + Content-Type: text/xml;charset=UTF-8 + Server: Jetty(7.6.0.v20120127) + X-Rundeck-API-Version: '11' + body: "\n \n property does not exist: monkey-burrito\n \n" diff --git a/src/test/resources/betamax/tapes/get_project_config_keyed_dne_v11.yaml b/src/test/resources/betamax/tapes/get_project_config_keyed_dne_v11.yaml new file mode 100644 index 0000000..d17090c --- /dev/null +++ b/src/test/resources/betamax/tapes/get_project_config_keyed_dne_v11.yaml @@ -0,0 +1,22 @@ +!tape +name: get_project_config_keyed_dne_v11 +interactions: +- recorded: 2014-03-07T20:19:47.533Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/project/ABC/config/does-not-exist + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + 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=2367tnltmmec14cn79ps4fam9;Path=/ + X-Rundeck-API-Version: '11' + body: "\n \n property does not exist: does-not-exist\n \n" diff --git a/src/test/resources/betamax/tapes/get_project_config_keyedv11.yaml b/src/test/resources/betamax/tapes/get_project_config_keyedv11.yaml new file mode 100644 index 0000000..c0ba023 --- /dev/null +++ b/src/test/resources/betamax/tapes/get_project_config_keyedv11.yaml @@ -0,0 +1,24 @@ +!tape +name: get_project_config_keyedv11 +interactions: +- recorded: 2014-03-07T19:50:29.035Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/project/ABC/config/project.name + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + 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=jgign9nyxeyp4istq65l86lp;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PHByb3BlcnR5IGtleT0ncHJvamVjdC5uYW1lJyB2YWx1ZT0nQUJDJyAvPg== diff --git a/src/test/resources/betamax/tapes/set_project_config_keyedv11.yaml b/src/test/resources/betamax/tapes/set_project_config_keyedv11.yaml new file mode 100644 index 0000000..88cfd10 --- /dev/null +++ b/src/test/resources/betamax/tapes/set_project_config_keyedv11.yaml @@ -0,0 +1,26 @@ +!tape +name: set_project_config_keyedv11 +interactions: +- recorded: 2014-03-07T19:59:51.009Z + request: + method: PUT + uri: http://rundeck.local:4440/api/11/project/ABC/config/monkey-burrito + 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 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + 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=3ssp8chdwsuw16hihk5frgpzy;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PHByb3BlcnR5IGtleT0nbW9ua2V5LWJ1cnJpdG8nIHZhbHVlPSdsZW1vbiBwaWUnIC8+ From e8672e025b4f68b3acc0e7f7f64fd73983ef25c0 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 7 Mar 2014 12:35:52 -0800 Subject: [PATCH 10/24] Add deleteProject for apiv11 --- .../java/org/rundeck/api/RundeckClient.java | 17 +++++++++ .../org/rundeck/api/RundeckClientTest.java | 14 ++++++++ .../betamax/tapes/delete_projectv11.yaml | 36 +++++++++++++++++++ 3 files changed, 67 insertions(+) create mode 100644 src/test/resources/betamax/tapes/delete_projectv11.yaml diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index f32f457..1888684 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -361,6 +361,23 @@ public class RundeckClient implements Serializable { : "/project" ))); } + /** + * Delete a project + * + * @param projectName name of the project - mandatory + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public void deleteProject(String projectName) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException { + + AssertUtil.notBlank(projectName, "projectName is mandatory to create a project !"); + new ApiCall(this).delete(new ApiPathBuilder("/project/", projectName)); + } /** * Return the configuration of a project * diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index ad475fd..4396061 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -90,6 +90,20 @@ public class RundeckClientTest { Assert.assertEquals(null, project.getDescription()); Assert.assertNotNull(project.getProjectConfig()); + } + @Test + @Betamax(tape = "delete_projectv11") + public void deleteProject() throws Exception { + RundeckClient client1 = createClient(TEST_TOKEN_6, 11); + client1.deleteProject("delete_me"); + RundeckProject delete_me = null; + try { + delete_me = client1.getProject("delete_me"); + Assert.fail(); + } catch (RundeckApiException.RundeckApiHttpStatusException e) { + Assert.assertEquals(404,e.getStatusCode()); + } + } @Test diff --git a/src/test/resources/betamax/tapes/delete_projectv11.yaml b/src/test/resources/betamax/tapes/delete_projectv11.yaml new file mode 100644 index 0000000..4025402 --- /dev/null +++ b/src/test/resources/betamax/tapes/delete_projectv11.yaml @@ -0,0 +1,36 @@ +!tape +name: delete_projectv11 +interactions: +- recorded: 2014-03-07T20:34:06.758Z + request: + method: DELETE + uri: http://rundeck.local:4440/api/11/project/delete_me + headers: + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + 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=17vo5m2nghd0e1dcg0hqsuxklu;Path=/ +- recorded: 2014-03-07T20:34:06.903Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/project/delete_me + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + response: + status: 404 + headers: + Content-Type: text/xml;charset=UTF-8 + Server: Jetty(7.6.0.v20120127) + X-Rundeck-API-Version: '11' + body: "\n \n project does not exist: delete_me\n \n" From 2ffe87d428049f2d0313302a5eb354afdfbb5d46 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 7 Mar 2014 13:22:02 -0800 Subject: [PATCH 11/24] Update to support writing response content to a stream --- src/main/java/org/rundeck/api/ApiCall.java | 123 ++++++++++++++++++--- 1 file changed, 107 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index 93993ad..f3e7596 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -15,6 +15,7 @@ */ package org.rundeck.api; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.*; import org.apache.http.client.HttpClient; @@ -42,10 +43,7 @@ import org.rundeck.api.parser.XmlNodeParser; import org.rundeck.api.util.AssertUtil; import org.rundeck.api.util.DocumentContentProducer; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; +import java.io.*; import java.net.ProxySelector; import java.security.KeyManagementException; import java.security.KeyStoreException; @@ -356,13 +354,35 @@ class ApiCall { private T execute(HttpRequestBase request, XmlNodeParser 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); + return new ParserHandler(parser).handle(execute(request, new ResultHandler())); } + /** + * 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) + */ + public int get(ApiPathBuilder apiPath, OutputStream outputStream) throws RundeckApiException, + RundeckApiLoginException, RundeckApiTokenException, IOException { + HttpGet request = new HttpGet(client.getUrl() + client.getApiEndpoint() + apiPath); + if (null != apiPath.getAccept()) { + request.setHeader("Accept", apiPath.getAccept()); + } + WriteOutHandler handler = new WriteOutHandler(outputStream); + int wrote = execute(request, handler); + if(handler.thrown!=null){ + throw handler.thrown; + } + return wrote; + } /** * Execute an HTTP request to the RunDeck instance. We will login first, and then execute the API call. * @@ -374,6 +394,83 @@ class ApiCall { */ private ByteArrayInputStream execute(HttpUriRequest request) throws RundeckApiException, RundeckApiLoginException, RundeckApiTokenException { + return execute(request, new ResultHandler() ); + } + + /** + * Handles one type into another + * @param + * @param + */ + private static interface Handler{ + public V handle(T response); + } + + /** + * Handles parsing inputstream via a parser + * @param + */ + private static class ParserHandler implements Handler { + XmlNodeParser parser; + + private ParserHandler(XmlNodeParser parser) { + this.parser = parser; + } + + @Override + public S handle(InputStream response) { + // read and parse the response + return parser.parseXmlNode(ParserHelper.loadDocument(response)); + } + } + + /** + * Handles writing response to an output stream + */ + private static class WriteOutHandler implements Handler { + private WriteOutHandler(OutputStream writeOut) { + this.writeOut = writeOut; + } + + OutputStream writeOut; + IOException thrown; + @Override + public Integer handle(final HttpResponse response) { + try { + return IOUtils.copy(response.getEntity().getContent(), writeOut); + } catch (IOException e) { + thrown=e; + } + return -1; + } + } + + /** + * Handles reading response into a byte array stream + */ + private static class ResultHandler implements Handler { + @Override + public ByteArrayInputStream handle(final HttpResponse response) { + // 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); + } + } + } + /** + * 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 T execute(HttpUriRequest request, Handler handler) throws RundeckApiException, + RundeckApiLoginException, + RundeckApiTokenException { HttpClient httpClient = instantiateHttpClient(); try { // we only need to manually login in case of login-based authentication @@ -428,13 +525,7 @@ class ApiCall { 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); - } + return handler.handle(response); } finally { httpClient.getConnectionManager().shutdown(); } From bea99b1c97157595be8c395ba74487451798abc3 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 7 Mar 2014 13:22:59 -0800 Subject: [PATCH 12/24] Add project export for apiv11 --- .../java/org/rundeck/api/RundeckClient.java | 49 +++++++++++++++++-- .../org/rundeck/api/RundeckClientTest.java | 10 ++++ .../betamax/tapes/export_projectv11.yaml | 23 +++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/test/resources/betamax/tapes/export_projectv11.yaml diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index 1888684..bfca253 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -34,11 +34,7 @@ import org.rundeck.api.util.AssertUtil; import org.rundeck.api.util.PagedResults; import org.rundeck.api.util.ParametersUtil; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; +import java.io.*; import java.util.*; import java.util.concurrent.TimeUnit; @@ -378,6 +374,49 @@ public class RundeckClient implements Serializable { AssertUtil.notBlank(projectName, "projectName is mandatory to create a project !"); new ApiCall(this).delete(new ApiPathBuilder("/project/", projectName)); } + /** + * Convenience method to export the archive of a project to the specified file. + * + * @param projectName name of the project - mandatory + * @param out file to write to + * @return number of bytes written to the stream + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public int exportProject(final String projectName, final File out) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException, IOException { + final FileOutputStream fileOutputStream = new FileOutputStream(out); + try { + return exportProject(projectName, fileOutputStream); + }finally { + fileOutputStream.close(); + } + } + /** + * Export the archive of a project to the specified outputstream + * + * @param projectName name of the project - mandatory + * @return number of bytes written to the stream + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public int exportProject(String projectName, OutputStream out) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException, IOException { + + AssertUtil.notBlank(projectName, "projectName is mandatory to export a project archive!"); + return new ApiCall(this).get( + new ApiPathBuilder("/project/", projectName, "/export") + .accept("application/zip"), + out); + } /** * Return the configuration of a project * diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index 4396061..1b93bfa 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -28,6 +28,7 @@ import org.rundeck.api.query.ExecutionQuery; import org.rundeck.api.util.PagedResults; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.InputStream; import java.util.*; @@ -160,6 +161,15 @@ public class RundeckClientTest { Assert.assertNull(value); } @Test + @Betamax(tape = "export_projectv11") + public void exportProject() throws Exception { + RundeckClient client1 = createClient(TEST_TOKEN_6, 11); + File temp = File.createTempFile("test-archive", ".zip"); + temp.deleteOnExit(); + int i = client1.exportProject("DEF1", temp); + Assert.assertEquals(8705,i); + } + @Test @Betamax(tape = "get_history") public void getHistory() throws Exception { final RundeckHistory test = client.getHistory("test"); diff --git a/src/test/resources/betamax/tapes/export_projectv11.yaml b/src/test/resources/betamax/tapes/export_projectv11.yaml new file mode 100644 index 0000000..595c78d --- /dev/null +++ b/src/test/resources/betamax/tapes/export_projectv11.yaml @@ -0,0 +1,23 @@ +!tape +name: export_projectv11 +interactions: +- recorded: 2014-03-07T21:12:45.024Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/project/DEF1/export + headers: + Accept: application/zip + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + response: + status: 200 + headers: + Content-Disposition: attachment; filename="DEF1-20140307-131244.rdproject.jar" + Content-Type: application/zip + Expires: Thu, 01 Jan 1970 00:00:00 GMT + Server: Jetty(7.6.0.v20120127) + Set-Cookie: JSESSIONID=18wgn0qj6x6ho1vpkwj2logftl;Path=/ + body: !!binary |- + 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NDlhYy1hYmYyLTRkZTdlYzc1ODcyZi54bWxQSwECFAAUAAgICACWaWdENmV0rRoBAAAoAgAAPgAAAAAAAAAAAAAAAACqAwAAcnVuZGVjay1ERUYxL2pvYnMvam9iLTA5NGIwZGNjLWVkNzUtNDE1Zi04NDA5LTMxZTZiMjFiNTAyZC54bWxQSwECFAAUAAgICACWaWdEoSpLBSYBAACbAgAAPgAAAAAAAAAAAAAAAAAwBQAAcnVuZGVjay1ERUYxL2pvYnMvam9iLTJiZTk2NmU3LTQ0MGUtNDg5Ny04YTU5LTZhZDEyNmRmNjAzZS54bWxQSwECFAAUAAgICACWaWdE4FnkVG4BAAAcAwAAPgAAAAAAAAAAAAAAAADCBgAAcnVuZGVjay1ERUYxL2pvYnMvam9iLWU2ZjYxYWUyLWFlZTEtNDYxMC05ZmU5LThjNmRmMmNlOGRiYy54bWxQSwECFAAUAAgICACWaWdE0201rJEBAACJAwAAPgAAAAAAAAAAAAAAAACcCAAAcnVuZGVjay1ERUYxL2pvYnMvam9iLTNmNjg1M2Y4LWI1ODktNGRhOC1iYTc2LTE2MzZlZjgxMWNlNi54bWxQSwECFAAUAAgICACWaWdE9sg0SZEBAACJAwAAPgAAAAAAAAAAAAAAAACZCgAAcnVuZGVjay1ERUYxL2pvYnMvam9iLWFjNzhkZmJiLWE0OWMtNDBhYi1hY2Q5LTk0MDhiZTU2MWU3NC54bWxQSwECFAAUAAgICACWaWdEs3XyZB0CAACGBQAAPgAAAAAAAAAAAAAAAACWDAAAcnVuZGVjay1ERUYxL2pvYnMvam9iLTc1NmM5OWY3LTA0ZmItNDM4Yy04NGY1LTE3MjJkOTY3ZDFjYi54bWxQSwECFAAUAAgICACWaWdEg9BA1bgBAADXAwAAPgAAAAAAAAAAAAAAAAAfDwAAcnVuZGVjay1ERUYxL2pvYnMvam9iLTNhZWM3ZGZiLTk1MDItNDg3Ni04NzYzLTFhYTc4OGM4ZmM5Yy54bWxQSwECFAAUAAgICACWaWdE8UF0QDUBAABpAgAAPgAAAAAAAAAAAAAAAABDEQAAcnVuZGVjay1ERUYxL2pvYnMvam9iLTg1OTZjYjljLTY4MzMtNDNmNy1hMGY3LTQ2NjNkZDhhYmMyOS54bWxQSwECFAAUAAgICACWaWdEdesbFksBAAC+AgAAPgAAAAAAAAAAAAAAAADkEgAAcnVuZGVjay1ERUYxL2pvYnMvam9iLWQ0MDIzYTcyLTU0ZTEtNDFiNS1iNGQ0LTRkZDdlNWQ0ZjVlNC54bWxQSwECFAAUAAgICACWaWdEAAAAAAIAAAAAAAAAGAAAAAAAAAAAAAAAAACbFAAAcnVuZGVjay1ERUYxL2V4ZWN1dGlvbnMvUEsBAhQAFAAICAgAlmlnRC1zICneAQAADgQAACoAAAAAAAAAAAAAAAAA4xQAAHJ1bmRlY2stREVGMS9leGVjdXRpb25zL2V4ZWN1dGlvbi0xNDU3LnhtbFBLAQIUABQACAgIAJZpZ0RkWJKhlgAAAKcBAAApAAAAAAAAAAAAAAAAABkXAABydW5kZWNrLURFRjEvZXhlY3V0aW9ucy9vdXRwdXQtMTQ1Ny5yZGxvZ1BLAQIUABQACAgIAJZpZ0QFJ/wt3gAAAJYCAAAtAAAAAAAAAAAAAAAAAAYYAABydW5kZWNrLURFRjEvZXhlY3V0aW9ucy9zdGF0ZS0xNDU3LnN0YXRlLmpzb25QSwECFAAUAAgICACWaWdEAAAAAAIAAAAAAAAAFQAAAAAAAAAAAAAAAAA/GQAAcnVuZGVjay1ERUYxL3JlcG9ydHMvUEsBAhQAFAAICAgAlmlnRGn21Ec3AQAANQIAACQAAAAAAAAAAAAAAAAAhBkAAHJ1bmRlY2stREVGMS9yZXBvcnRzL3JlcG9ydC0xNDc0LnhtbFBLBQYAAAAAEwATAN4GAAANGwAAAAA= From 97fa962311e938ec39008ae84c0401bdbce6f33e Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Sun, 9 Mar 2014 17:17:06 -0700 Subject: [PATCH 13/24] Support request content sent directly via file or stream --- src/main/java/org/rundeck/api/ApiCall.java | 9 ++++ .../java/org/rundeck/api/ApiPathBuilder.java | 51 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index f3e7596..e285dee 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -24,7 +24,9 @@ import org.apache.http.client.methods.*; 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.BasicHttpEntity; import org.apache.http.entity.EntityTemplate; +import org.apache.http.entity.FileEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.InputStreamBody; @@ -298,6 +300,13 @@ class ApiCall { } catch (UnsupportedEncodingException e) { throw new RundeckApiException("Unsupported encoding: " + e.getMessage(), e); } + }else if(apiPath.getContentStream() !=null && apiPath.getContentType()!=null){ + BasicHttpEntity entity = new BasicHttpEntity(); + entity.setContent(apiPath.getContentStream()); + entity.setContentType(apiPath.getContentType()); + httpPost.setEntity(entity); + }else if(apiPath.getContentFile() !=null && apiPath.getContentType()!=null){ + httpPost.setEntity(new FileEntity(apiPath.getContentFile(), apiPath.getContentType())); }else if(apiPath.getXmlDocument()!=null) { httpPost.setHeader("Content-Type", "application/xml"); httpPost.setEntity(new EntityTemplate(new DocumentContentProducer(apiPath.getXmlDocument()))); diff --git a/src/main/java/org/rundeck/api/ApiPathBuilder.java b/src/main/java/org/rundeck/api/ApiPathBuilder.java index bf27ae9..18a6194 100644 --- a/src/main/java/org/rundeck/api/ApiPathBuilder.java +++ b/src/main/java/org/rundeck/api/ApiPathBuilder.java @@ -15,6 +15,7 @@ */ package org.rundeck.api; +import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; @@ -46,6 +47,9 @@ class ApiPathBuilder { private final Map attachments; private final List form = new ArrayList(); private Document xmlDocument; + private InputStream contentStream; + private File contentFile; + private String contentType; /** Marker for using the right separator between parameters ("?" or "&") */ private boolean firstParamDone = false; @@ -59,6 +63,10 @@ class ApiPathBuilder { public ApiPathBuilder(String... paths) { apiPath = new StringBuilder(); attachments = new HashMap(); + paths(paths); + } + + public ApiPathBuilder paths(String... paths) { if (paths != null) { for (String path : paths) { if (StringUtils.isNotBlank(path)) { @@ -66,6 +74,7 @@ class ApiPathBuilder { } } } + return this; } /** @@ -269,6 +278,36 @@ class ApiPathBuilder { } return this; } + /** + * When POSTing a request, use the given {@link InputStream} as the content of the request. This + * will only add the stream if it is not null. + * + * @param contentType MIME content type ofr hte request + * @param stream content stream + * @return this, for method chaining + */ + public ApiPathBuilder content(final String contentType, final InputStream stream) { + if (stream != null && contentType != null) { + this.contentStream=stream; + this.contentType=contentType; + } + return this; + } + /** + * When POSTing a request, use the given {@link File} as the content of the request. This + * will only add the stream if it is not null. + * + * @param contentType MIME content type ofr hte request + * @param file content from a file + * @return this, for method chaining + */ + public ApiPathBuilder content(final String contentType, final File file) { + if (file != null && contentType != null) { + this.contentFile=file; + this.contentType=contentType; + } + return this; + } /** * When POSTing a request, add the given XMl Document as the content of the request. * @@ -352,6 +391,18 @@ class ApiPathBuilder { return xmlDocument; } + public InputStream getContentStream() { + return contentStream; + } + + public String getContentType() { + return contentType; + } + + public File getContentFile() { + return contentFile; + } + /** * BuildsParameters can add URL or POST parameters to an {@link ApiPathBuilder} * From 69d9c91ef822491637eb4c947b553865408d2825 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Sun, 9 Mar 2014 17:17:25 -0700 Subject: [PATCH 14/24] Add support for project archive import api v11 --- .../java/org/rundeck/api/RundeckClient.java | 38 ++++++++++ .../org/rundeck/api/domain/ArchiveImport.java | 38 ++++++++++ .../api/parser/ArchiveImportParser.java | 36 +++++++++ .../org/rundeck/api/RundeckClientTest.java | 45 ++++++++++- .../tapes/import_project_failure_v11.yaml | 27 +++++++ .../betamax/tapes/import_project_suv11.yaml | 71 ++++++++++++++++++ .../org/rundeck/api/test-archive.zip | Bin 0 -> 8705 bytes 7 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/rundeck/api/domain/ArchiveImport.java create mode 100644 src/main/java/org/rundeck/api/parser/ArchiveImportParser.java create mode 100644 src/test/resources/betamax/tapes/import_project_failure_v11.yaml create mode 100644 src/test/resources/betamax/tapes/import_project_suv11.yaml create mode 100644 src/test/resources/org/rundeck/api/test-archive.zip diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index bfca253..666af9b 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -417,6 +417,44 @@ public class RundeckClient implements Serializable { .accept("application/zip"), out); } + + /** + * Import a archive file to the specified project. + * + * @param projectName name of the project - mandatory + * @param archiveFile zip archive file + * @param includeExecutions if true, import executions defined in the archive, otherwise skip them + * @param preserveJobUuids if true, do not remove UUIDs from imported jobs, otherwise remove them + * + * @return Result of the import request, may contain a list of import error messages + * + * @throws RundeckApiException in case of error when calling the API (non-existent project with this name) + * @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 projectName is blank (null, empty or whitespace) + */ + public ArchiveImport importArchive(final String projectName, final File archiveFile, + final boolean includeExecutions, final boolean preserveJobUuids) throws + RundeckApiException, RundeckApiLoginException, + RundeckApiTokenException, IllegalArgumentException, IOException { + + AssertUtil.notBlank(projectName, "projectName is mandatory to import a project archive!"); + AssertUtil.notNull(archiveFile, "archiveFile is mandatory to import a project archive!"); ; + return callImportProject(projectName, includeExecutions, preserveJobUuids, + new ApiPathBuilder().content("application/zip", archiveFile)); + } + + private ArchiveImport callImportProject(final String projectName, final boolean includeExecutions, final boolean preserveJobUuids, + final ApiPathBuilder param) { + param.paths("/project/", projectName, "/import") + .param("importExecutions", includeExecutions) + .param("jobUuidOption", preserveJobUuids ? "preserve" : "remove"); + return new ApiCall(this).put( + param, + new ArchiveImportParser() + ); + } + /** * Return the configuration of a project * diff --git a/src/main/java/org/rundeck/api/domain/ArchiveImport.java b/src/main/java/org/rundeck/api/domain/ArchiveImport.java new file mode 100644 index 0000000..7be8112 --- /dev/null +++ b/src/main/java/org/rundeck/api/domain/ArchiveImport.java @@ -0,0 +1,38 @@ +package org.rundeck.api.domain; + +import java.util.List; + +/** + * ArchiveImport describes the result of an {@link org.rundeck.api.RundeckClient#importArchive(String, java.io.File, + * boolean, boolean)} request. + * + * @author greg + * @since 2014-03-09 + */ +public class ArchiveImport { + private boolean successful; + private List errorMessages; + + public ArchiveImport(final boolean successful, final List errorMessages) { + this.successful = successful; + this.errorMessages = errorMessages; + } + + /** + * Return true if successful + * @return + */ + public boolean isSuccessful() { + return successful; + } + + + /** + * Return a list of error messages if unsuccessful + * @return + */ + public List getErrorMessages() { + return errorMessages; + } + +} diff --git a/src/main/java/org/rundeck/api/parser/ArchiveImportParser.java b/src/main/java/org/rundeck/api/parser/ArchiveImportParser.java new file mode 100644 index 0000000..b4e7bda --- /dev/null +++ b/src/main/java/org/rundeck/api/parser/ArchiveImportParser.java @@ -0,0 +1,36 @@ +package org.rundeck.api.parser; + +import org.dom4j.Node; +import org.rundeck.api.domain.ArchiveImport; + +import java.util.ArrayList; + +/** + * ArchiveImportParser is ... + * + * @author greg + * @since 2014-03-09 + */ +public class ArchiveImportParser implements XmlNodeParser { + String xpath; + + public ArchiveImportParser() { + } + + public ArchiveImportParser(final String xpath) { + this.xpath = xpath; + } + + @Override + public ArchiveImport parseXmlNode(final Node node) { + final Node importNode = xpath != null ? node.selectSingleNode(xpath) : node; + + boolean issuccess = "successful".equals(importNode.valueOf("/import/@status")); + final ArrayList messages = new ArrayList(); + for (final Object o : importNode.selectNodes("/import/errors/error")) { + messages.add(((Node) o).getText()); + } + + return new ArchiveImport(issuccess, messages); + } +} diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index 1b93bfa..f0aa4b6 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -19,6 +19,7 @@ import betamax.Betamax; import betamax.MatchRule; import betamax.Recorder; import betamax.TapeMode; +import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -27,9 +28,7 @@ import org.rundeck.api.domain.*; import org.rundeck.api.query.ExecutionQuery; import org.rundeck.api.util.PagedResults; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; +import java.io.*; import java.util.*; @@ -167,7 +166,45 @@ public class RundeckClientTest { File temp = File.createTempFile("test-archive", ".zip"); temp.deleteOnExit(); int i = client1.exportProject("DEF1", temp); - Assert.assertEquals(8705,i); + Assert.assertEquals(8705, i); + } + @Test + @Betamax(tape = "import_project_suv11",mode = TapeMode.READ_ONLY) + public void importProjectSuccess() throws Exception { + RundeckClient client1 = createClient(TEST_TOKEN_6, 11); + InputStream resourceAsStream = getClass().getResourceAsStream("test-archive.zip"); + File temp = File.createTempFile("test-archive", ".zip"); + temp.deleteOnExit(); + IOUtils.copy(resourceAsStream, new FileOutputStream(temp)); + ArchiveImport def1 = client1.importArchive("DEF2", temp, true, true); + Assert.assertTrue(def1.isSuccessful()); + Assert.assertEquals(0, def1.getErrorMessages().size()); + + ArchiveImport def2 = client1.importArchive("DEF2", temp, false, true); + Assert.assertTrue(def2.isSuccessful()); + Assert.assertEquals(0, def2.getErrorMessages().size()); + + ArchiveImport def3 = client1.importArchive("DEF2", temp, true, false); + Assert.assertTrue(def3.isSuccessful()); + Assert.assertEquals(0, def3.getErrorMessages().size()); + temp.delete(); + } + @Test + @Betamax(tape = "import_project_failure_v11", mode = TapeMode.READ_ONLY) + public void importProjectFailure() throws Exception { + RundeckClient client1 = createClient(TEST_TOKEN_6, 11); + InputStream resourceAsStream = getClass().getResourceAsStream("test-archive.zip"); + File temp = File.createTempFile("test-archive", ".zip"); + temp.deleteOnExit(); + IOUtils.copy(resourceAsStream, new FileOutputStream(temp)); + ArchiveImport def1 = client1.importArchive("DEF1", temp, false, true); + Assert.assertFalse(def1.isSuccessful()); + Assert.assertEquals(10, def1.getErrorMessages().size()); + Assert.assertEquals("Job at index [1] at archive path: " + + "rundeck-DEF1/jobs/job-6fd1808c-eafb-49ac-abf2-4de7ec75872f.xml had errors: Validation errors: Cannot" + + " create a Job with UUID 6fd1808c-eafb-49ac-abf2-4de7ec75872f: a Job already exists with this UUID. " + + "Change the UUID or delete the other Job.", def1.getErrorMessages().get(0)); + } @Test @Betamax(tape = "get_history") diff --git a/src/test/resources/betamax/tapes/import_project_failure_v11.yaml b/src/test/resources/betamax/tapes/import_project_failure_v11.yaml new file mode 100644 index 0000000..1ddcae5 --- /dev/null +++ b/src/test/resources/betamax/tapes/import_project_failure_v11.yaml @@ -0,0 +1,27 @@ +!tape +name: import_project_failure_v11 +interactions: +- recorded: 2014-03-10T00:01:12.170Z + request: + method: PUT + uri: http://rundeck.local:4440/api/11/project/DEF1/import?importExecutions=false&jobUuidOption=preserve + headers: + Accept: text/xml + Content-Length: '8705' + Content-Type: application/zip + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + body: '' + 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=1q6qo7ev7f5uz11hlfd8e0sa82;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PGltcG9ydCBzdGF0dXM9J2ZhaWxlZCc+CiAgPGVycm9ycyBjb3VudD0nMTAnPgogICAgPGVycm9yPkpvYiBhdCBpbmRleCBbMV0gYXQgYXJjaGl2ZSBwYXRoOiBydW5kZWNrLURFRjEvam9icy9qb2ItNmZkMTgwOGMtZWFmYi00OWFjLWFiZjItNGRlN2VjNzU4NzJmLnhtbCBoYWQgZXJyb3JzOiBWYWxpZGF0aW9uIGVycm9yczogQ2Fubm90IGNyZWF0ZSBhIEpvYiB3aXRoIFVVSUQgNmZkMTgwOGMtZWFmYi00OWFjLWFiZjItNGRlN2VjNzU4NzJmOiBhIEpvYiBhbHJlYWR5IGV4aXN0cyB3aXRoIHRoaXMgVVVJRC4gQ2hhbmdlIHRoZSBVVUlEIG9yIGRlbGV0ZSB0aGUgb3RoZXIgSm9iLjwvZXJyb3I+CiAgICA8ZXJyb3I+Sm9iIGF0IGluZGV4IFsxXSBhdCBhcmNoaXZlIHBhdGg6IHJ1bmRlY2stREVGMS9qb2JzL2pvYi0wOTRiMGRjYy1lZDc1LTQxNWYtODQwOS0zMWU2YjIxYjUwMmQueG1sIGhhZCBlcnJvcnM6IFZhbGlkYXRpb24gZXJyb3JzOiBDYW5ub3QgY3JlYXRlIGEgSm9iIHdpdGggVVVJRCAwOTRiMGRjYy1lZDc1LTQxNWYtODQwOS0zMWU2YjIxYjUwMmQ6IGEgSm9iIGFscmVhZHkgZXhpc3RzIHdpdGggdGhpcyBVVUlELiBDaGFuZ2UgdGhlIFVVSUQgb3IgZGVsZXRlIHRoZSBvdGhlciBKb2IuPC9lcnJvcj4KICAgIDxlcnJvcj5Kb2IgYXQgaW5kZXggWzFdIGF0IGFyY2hpdmUgcGF0aDogcnVuZGVjay1ERUYxL2pvYnMvam9iLTJiZTk2NmU3LTQ0MGUtNDg5Ny04YTU5LTZhZDEyNmRmNjAzZS54bWwgaGFkIGVycm9yczogVmFsaWRhdGlvbiBlcnJvcnM6IENhbm5vdCBjcmVhdGUgYSBKb2Igd2l0aCBVVUlEIDJiZTk2NmU3LTQ0MGUtNDg5Ny04YTU5LTZhZDEyNmRmNjAzZTogYSBKb2IgYWxyZWFkeSBleGlzdHMgd2l0aCB0aGlzIFVVSUQuIENoYW5nZSB0aGUgVVVJRCBvciBkZWxldGUgdGhlIG90aGVyIEpvYi48L2Vycm9yPgogICAgPGVycm9yPkpvYiBhdCBpbmRleCBbMV0gYXQgYXJjaGl2ZSBwYXRoOiBydW5kZWNrLURFRjEvam9icy9qb2ItZTZmNjFhZTItYWVlMS00NjEwLTlmZTktOGM2ZGYyY2U4ZGJjLnhtbCBoYWQgZXJyb3JzOiBWYWxpZGF0aW9uIGVycm9yczogQ2Fubm90IGNyZWF0ZSBhIEpvYiB3aXRoIFVVSUQgZTZmNjFhZTItYWVlMS00NjEwLTlmZTktOGM2ZGYyY2U4ZGJjOiBhIEpvYiBhbHJlYWR5IGV4aXN0cyB3aXRoIHRoaXMgVVVJRC4gQ2hhbmdlIHRoZSBVVUlEIG9yIGRlbGV0ZSB0aGUgb3RoZXIgSm9iLjwvZXJyb3I+CiAgICA8ZXJyb3I+Sm9iIGF0IGluZGV4IFsxXSBhdCBhcmNoaXZlIHBhdGg6IHJ1bmRlY2stREVGMS9qb2JzL2pvYi0zZjY4NTNmOC1iNTg5LTRkYTgtYmE3Ni0xNjM2ZWY4MTFjZTYueG1sIGhhZCBlcnJvcnM6IFZhbGlkYXRpb24gZXJyb3JzOiBDYW5ub3QgY3JlYXRlIGEgSm9iIHdpdGggVVVJRCAzZjY4NTNmOC1iNTg5LTRkYTgtYmE3Ni0xNjM2ZWY4MTFjZTY6IGEgSm9iIGFscmVhZHkgZXhpc3RzIHdpdGggdGhpcyBVVUlELiBDaGFuZ2UgdGhlIFVVSUQgb3IgZGVsZXRlIHRoZSBvdGhlciBKb2IuPC9lcnJvcj4KICAgIDxlcnJvcj5Kb2IgYXQgaW5kZXggWzFdIGF0IGFyY2hpdmUgcGF0aDogcnVuZGVjay1ERUYxL2pvYnMvam9iLWFjNzhkZmJiLWE0OWMtNDBhYi1hY2Q5LTk0MDhiZTU2MWU3NC54bWwgaGFkIGVycm9yczogVmFsaWRhdGlvbiBlcnJvcnM6IENhbm5vdCBjcmVhdGUgYSBKb2Igd2l0aCBVVUlEIGFjNzhkZmJiLWE0OWMtNDBhYi1hY2Q5LTk0MDhiZTU2MWU3NDogYSBKb2IgYWxyZWFkeSBleGlzdHMgd2l0aCB0aGlzIFVVSUQuIENoYW5nZSB0aGUgVVVJRCBvciBkZWxldGUgdGhlIG90aGVyIEpvYi48L2Vycm9yPgogICAgPGVycm9yPkpvYiBhdCBpbmRleCBbMV0gYXQgYXJjaGl2ZSBwYXRoOiBydW5kZWNrLURFRjEvam9icy9qb2ItNzU2Yzk5ZjctMDRmYi00MzhjLTg0ZjUtMTcyMmQ5NjdkMWNiLnhtbCBoYWQgZXJyb3JzOiBWYWxpZGF0aW9uIGVycm9yczogQ2Fubm90IGNyZWF0ZSBhIEpvYiB3aXRoIFVVSUQgNzU2Yzk5ZjctMDRmYi00MzhjLTg0ZjUtMTcyMmQ5NjdkMWNiOiBhIEpvYiBhbHJlYWR5IGV4aXN0cyB3aXRoIHRoaXMgVVVJRC4gQ2hhbmdlIHRoZSBVVUlEIG9yIGRlbGV0ZSB0aGUgb3RoZXIgSm9iLjwvZXJyb3I+CiAgICA8ZXJyb3I+Sm9iIGF0IGluZGV4IFsxXSBhdCBhcmNoaXZlIHBhdGg6IHJ1bmRlY2stREVGMS9qb2JzL2pvYi0zYWVjN2RmYi05NTAyLTQ4NzYtODc2My0xYWE3ODhjOGZjOWMueG1sIGhhZCBlcnJvcnM6IFZhbGlkYXRpb24gZXJyb3JzOiBDYW5ub3QgY3JlYXRlIGEgSm9iIHdpdGggVVVJRCAzYWVjN2RmYi05NTAyLTQ4NzYtODc2My0xYWE3ODhjOGZjOWM6IGEgSm9iIGFscmVhZHkgZXhpc3RzIHdpdGggdGhpcyBVVUlELiBDaGFuZ2UgdGhlIFVVSUQgb3IgZGVsZXRlIHRoZSBvdGhlciBKb2IuPC9lcnJvcj4KICAgIDxlcnJvcj5Kb2IgYXQgaW5kZXggWzFdIGF0IGFyY2hpdmUgcGF0aDogcnVuZGVjay1ERUYxL2pvYnMvam9iLTg1OTZjYjljLTY4MzMtNDNmNy1hMGY3LTQ2NjNkZDhhYmMyOS54bWwgaGFkIGVycm9yczogVmFsaWRhdGlvbiBlcnJvcnM6IENhbm5vdCBjcmVhdGUgYSBKb2Igd2l0aCBVVUlEIDg1OTZjYjljLTY4MzMtNDNmNy1hMGY3LTQ2NjNkZDhhYmMyOTogYSBKb2IgYWxyZWFkeSBleGlzdHMgd2l0aCB0aGlzIFVVSUQuIENoYW5nZSB0aGUgVVVJRCBvciBkZWxldGUgdGhlIG90aGVyIEpvYi48L2Vycm9yPgogICAgPGVycm9yPkpvYiBhdCBpbmRleCBbMV0gYXQgYXJjaGl2ZSBwYXRoOiBydW5kZWNrLURFRjEvam9icy9qb2ItZDQwMjNhNzItNTRlMS00MWI1LWI0ZDQtNGRkN2U1ZDRmNWU0LnhtbCBoYWQgZXJyb3JzOiBWYWxpZGF0aW9uIGVycm9yczogQ2Fubm90IGNyZWF0ZSBhIEpvYiB3aXRoIFVVSUQgZDQwMjNhNzItNTRlMS00MWI1LWI0ZDQtNGRkN2U1ZDRmNWU0OiBhIEpvYiBhbHJlYWR5IGV4aXN0cyB3aXRoIHRoaXMgVVVJRC4gQ2hhbmdlIHRoZSBVVUlEIG9yIGRlbGV0ZSB0aGUgb3RoZXIgSm9iLjwvZXJyb3I+CiAgPC9lcnJvcnM+CjwvaW1wb3J0Pg== diff --git a/src/test/resources/betamax/tapes/import_project_suv11.yaml b/src/test/resources/betamax/tapes/import_project_suv11.yaml new file mode 100644 index 0000000..a8530d8 --- /dev/null +++ b/src/test/resources/betamax/tapes/import_project_suv11.yaml @@ -0,0 +1,71 @@ +!tape +name: import_project_suv11 +interactions: +- recorded: 2014-03-09T23:57:25.471Z + request: + method: PUT + uri: http://rundeck.local:4440/api/11/project/DEF2/import?importExecutions=true&jobUuidOption=preserve + headers: + Accept: text/xml + Content-Length: '8705' + Content-Type: application/zip + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + body: '' + 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=10hrj0jebdc621ukdlal6qqyu3;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PGltcG9ydCBzdGF0dXM9J3N1Y2Nlc3NmdWwnIC8+ +- recorded: 2014-03-09T23:57:26.403Z + request: + method: PUT + uri: http://rundeck.local:4440/api/11/project/DEF2/import?importExecutions=false&jobUuidOption=preserve + headers: + Accept: text/xml + Content-Length: '8705' + Content-Type: application/zip + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + body: '' + response: + status: 200 + headers: + Content-Type: application/xml;charset=UTF-8 + Server: Jetty(7.6.0.v20120127) + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PGltcG9ydCBzdGF0dXM9J3N1Y2Nlc3NmdWwnIC8+ +- recorded: 2014-03-09T23:57:27.155Z + request: + method: PUT + uri: http://rundeck.local:4440/api/11/project/DEF2/import?importExecutions=true&jobUuidOption=remove + headers: + Accept: text/xml + Content-Length: '8705' + Content-Type: application/zip + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: Do4d3NUD5DKk21DR4sNK755RcPk618vn + body: '' + response: + status: 200 + headers: + Content-Type: application/xml;charset=UTF-8 + Server: Jetty(7.6.0.v20120127) + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PGltcG9ydCBzdGF0dXM9J3N1Y2Nlc3NmdWwnIC8+ diff --git a/src/test/resources/org/rundeck/api/test-archive.zip b/src/test/resources/org/rundeck/api/test-archive.zip new file mode 100644 index 0000000000000000000000000000000000000000..e5a1dab3a2692e8004a0cd9f4ef7ec0d0afae89e GIT binary patch literal 8705 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 literal 0 HcmV?d00001 From c12b2f1459cb9521023ac1f4ff1ed7380b5c8729 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 4 Apr 2014 09:55:48 -0700 Subject: [PATCH 15/24] API 11 status --- src/site/confluence/status.confluence | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/site/confluence/status.confluence b/src/site/confluence/status.confluence index 3a3d96b..edf1e5f 100644 --- a/src/site/confluence/status.confluence +++ b/src/site/confluence/status.confluence @@ -93,3 +93,17 @@ h2. RunDeck API version 10 * Execution Output - Retrieve log output for a particular node or step - OK * Execution Info - added successfulNodes and failedNodes detail. - OK * Deprecation: Remove methods deprecated until version 10. - OK + + +h2. RunDeck API version 11 + +[Documentation of the RunDeck API version 11|http://rundeck.org/2.1.0/api/index.html] + +* Project creation - OK +* Get Project configuration - OK +* Set Project configuration - OK +* Get/Set Project configuration keys - OK +* Delete project - OK +* Export project archive - OK +* Import project archive - OK +* ... From b5f4ec8ccd42eb371760c61d13d008e666952bdc Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 4 Apr 2014 09:57:20 -0700 Subject: [PATCH 16/24] Add todos for API v11 --- src/site/confluence/status.confluence | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/site/confluence/status.confluence b/src/site/confluence/status.confluence index edf1e5f..9bc64f9 100644 --- a/src/site/confluence/status.confluence +++ b/src/site/confluence/status.confluence @@ -106,4 +106,10 @@ h2. RunDeck API version 11 * Delete project - OK * Export project archive - OK * Import project archive - OK -* ... +* SSH Key upload - *TODO* +* SSH Key delete - *TODO* +* SSH Key list - *TODO* +* SSH Key get - *TODO* +* API Token create - *TODO* +* API Token list - *TODO* +* API Token delete - *TODO* From 0cb3da88ec2b38d6cbfd06adf64a22225be988aa Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 4 Apr 2014 11:40:24 -0700 Subject: [PATCH 17/24] Allow empty content in request --- src/main/java/org/rundeck/api/ApiCall.java | 2 ++ src/main/java/org/rundeck/api/ApiPathBuilder.java | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index e285dee..07e09f9 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -310,6 +310,8 @@ class ApiCall { }else if(apiPath.getXmlDocument()!=null) { httpPost.setHeader("Content-Type", "application/xml"); httpPost.setEntity(new EntityTemplate(new DocumentContentProducer(apiPath.getXmlDocument()))); + }else if(apiPath.isEmptyContent()){ + //empty content }else { throw new IllegalArgumentException("No Form or Multipart entity for POST content-body"); } diff --git a/src/main/java/org/rundeck/api/ApiPathBuilder.java b/src/main/java/org/rundeck/api/ApiPathBuilder.java index 18a6194..05250ec 100644 --- a/src/main/java/org/rundeck/api/ApiPathBuilder.java +++ b/src/main/java/org/rundeck/api/ApiPathBuilder.java @@ -50,6 +50,7 @@ class ApiPathBuilder { private InputStream contentStream; private File contentFile; private String contentType; + private boolean emptyContent = false; /** Marker for using the right separator between parameters ("?" or "&") */ private boolean firstParamDone = false; @@ -308,6 +309,15 @@ class ApiPathBuilder { } return this; } + /** + * When POSTing a request, send an empty request. + * + * @return this, for method chaining + */ + public ApiPathBuilder emptyContent() { + this.emptyContent=true; + return this; + } /** * When POSTing a request, add the given XMl Document as the content of the request. * @@ -403,6 +413,9 @@ class ApiPathBuilder { return contentFile; } + public boolean isEmptyContent() { + return emptyContent; + } /** * BuildsParameters can add URL or POST parameters to an {@link ApiPathBuilder} * From 0f8e3387192c23942695f9c67d9e92771d153349 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 4 Apr 2014 11:41:09 -0700 Subject: [PATCH 18/24] Support API token endpoint (v11) --- .../java/org/rundeck/api/RundeckClient.java | 64 ++++++++++++++ .../org/rundeck/api/domain/RundeckToken.java | 36 ++++++++ .../api/parser/RundeckTokenParser.java | 33 ++++++++ .../org/rundeck/api/RundeckClientTest.java | 84 +++++++++++++++++++ .../betamax/tapes/api_token_delete.yaml | 36 ++++++++ .../betamax/tapes/api_token_generate.yaml | 25 ++++++ .../betamax/tapes/api_token_get.yaml | 24 ++++++ .../betamax/tapes/api_tokens_list_all.yaml | 24 ++++++ .../betamax/tapes/api_tokens_list_user.yaml | 24 ++++++ 9 files changed, 350 insertions(+) create mode 100644 src/main/java/org/rundeck/api/domain/RundeckToken.java create mode 100644 src/main/java/org/rundeck/api/parser/RundeckTokenParser.java create mode 100644 src/test/resources/betamax/tapes/api_token_delete.yaml create mode 100644 src/test/resources/betamax/tapes/api_token_generate.yaml create mode 100644 src/test/resources/betamax/tapes/api_token_get.yaml create mode 100644 src/test/resources/betamax/tapes/api_tokens_list_all.yaml create mode 100644 src/test/resources/betamax/tapes/api_tokens_list_user.yaml diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index 666af9b..e56bda1 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -2310,6 +2310,70 @@ public class RundeckClient implements Serializable { return new ApiCall(this).get(new ApiPathBuilder("/system/info"), new SystemInfoParser(rootXpath()+"/system")); } + + /* + * API token + */ + + /** + * List API tokens for a user. + * @param user username + * @return list of tokens + * @throws RundeckApiException + */ + public List listApiTokens(final String user) throws RundeckApiException { + AssertUtil.notNull(user, "user is mandatory to list API tokens for a user."); + return new ApiCall(this). + get(new ApiPathBuilder("/tokens/", user), + new ListParser(new RundeckTokenParser(), "/tokens/token")); + } + + /** + * List all API tokens + * @return list of tokens + * @throws RundeckApiException + */ + public List listApiTokens() throws RundeckApiException { + return new ApiCall(this). + get(new ApiPathBuilder("/tokens"), + new ListParser(new RundeckTokenParser(), "/tokens/token")); + } + + /** + * Generate an API token for a user. + * @param user + * @return + * @throws RundeckApiException + */ + public String generateApiToken(final String user) throws RundeckApiException{ + AssertUtil.notNull(user, "user is mandatory to generate an API token for a user."); + RundeckToken result = new ApiCall(this). + post(new ApiPathBuilder("/tokens/", user).emptyContent(), + new RundeckTokenParser("/token")); + return result.getToken(); + } + /** + * Delete an existing token + * @param token + * @return + * @throws RundeckApiException + */ + public boolean deleteApiToken(final String token) throws RundeckApiException{ + AssertUtil.notNull(token, "token is mandatory to delete an API token."); + new ApiCall(this).delete(new ApiPathBuilder("/token/", token)); + return true; + } + /** + * Return user info for an existing token + * @param token + * @return token info + * @throws RundeckApiException + */ + public RundeckToken getApiToken(final String token) throws RundeckApiException{ + AssertUtil.notNull(token, "token is mandatory to get an API token."); + return new ApiCall(this).get(new ApiPathBuilder("/token/", token), new RundeckTokenParser("/token")); + } + /** * @return the URL of the RunDeck instance ("http://localhost:4440", "http://rundeck.your-compagny.com/", etc) */ diff --git a/src/main/java/org/rundeck/api/domain/RundeckToken.java b/src/main/java/org/rundeck/api/domain/RundeckToken.java new file mode 100644 index 0000000..613fc82 --- /dev/null +++ b/src/main/java/org/rundeck/api/domain/RundeckToken.java @@ -0,0 +1,36 @@ +package org.rundeck.api.domain; + +/** + * RundeckToken is ... + * + * @author Greg Schueler + * @since 2014-04-04 + */ +public class RundeckToken { + private String user; + private String token; + + public RundeckToken() { + } + + public RundeckToken(String user, String token) { + this.setUser(user); + this.setToken(token); + } + + public String getUser() { + return user; + } + + public void setUser(String user) { + this.user = user; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } +} diff --git a/src/main/java/org/rundeck/api/parser/RundeckTokenParser.java b/src/main/java/org/rundeck/api/parser/RundeckTokenParser.java new file mode 100644 index 0000000..b48543d --- /dev/null +++ b/src/main/java/org/rundeck/api/parser/RundeckTokenParser.java @@ -0,0 +1,33 @@ +package org.rundeck.api.parser; + +import org.dom4j.Node; +import org.rundeck.api.domain.RundeckToken; + +/** + * RundeckTokenParser is ... + * + * @author Greg Schueler + * @since 2014-04-04 + */ +public class RundeckTokenParser implements XmlNodeParser { + String xpath; + + public RundeckTokenParser() { + } + + public RundeckTokenParser(String xpath) { + this.xpath = xpath; + } + + @Override + public RundeckToken parseXmlNode(Node node) { + Node targetNode = xpath != null ? node.selectSingleNode(xpath) : node; + RundeckToken rundeckToken = new RundeckToken(); + String token = targetNode.valueOf("@id"); + String user = targetNode.valueOf("@user"); + rundeckToken.setToken(token); + rundeckToken.setUser(user); + + return rundeckToken; + } +} diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index f0aa4b6..053ff51 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -55,6 +55,7 @@ public class RundeckClientTest { public static final String TEST_TOKEN_4 = "sN5RRSNvu15DnV6EcNDdc2CkdPcv3s32"; public static final String TEST_TOKEN_5 = "C3O6d5O98Kr6Dpv71sdE4ERdCuU12P6d"; public static final String TEST_TOKEN_6 = "Do4d3NUD5DKk21DR4sNK755RcPk618vn"; + public static final String TEST_TOKEN_7 = "8Dp9op111ER6opsDRkddvE86K9sE499s"; @Rule public Recorder recorder = new Recorder(); @@ -1197,6 +1198,89 @@ public class RundeckClientTest { Assert.assertEquals(RundeckWFExecState.SUCCEEDED,output.getExecutionState()); } + /** + * generate api token + */ + @Test + @Betamax(tape = "api_token_generate", mode = TapeMode.READ_ONLY) + public void generateApiToken() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + String token = client.generateApiToken("bob"); + + Assert.assertNotNull(token); + Assert.assertEquals("MiquQjELTrEaugpmdgAKs1W3a7xonAwU", token); + } + /** + * get api token + */ + @Test + @Betamax(tape = "api_token_get", mode = TapeMode.READ_ONLY) + public void getApiToken() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + RundeckToken token = client.getApiToken("MiquQjELTrEaugpmdgAKs1W3a7xonAwU"); + + Assert.assertNotNull(token); + Assert.assertEquals("MiquQjELTrEaugpmdgAKs1W3a7xonAwU", token.getToken()); + Assert.assertEquals("bob", token.getUser()); + } + /** + * list api tokens for user + */ + @Test + @Betamax(tape = "api_tokens_list_user", mode = TapeMode.READ_ONLY) + public void listApiTokens_user() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + List tokens = client.listApiTokens("bob"); + + Assert.assertNotNull(tokens); + Assert.assertEquals(3, tokens.size()); + Assert.assertEquals("hINp5eGzvYA9UePbUChaKHd5NiRkwWbx", tokens.get(0).getToken()); + Assert.assertEquals("bob", tokens.get(0).getUser()); + Assert.assertEquals("NaNnwVzAHAG83qOS7Wtwh6mjcXViyWUV", tokens.get(1).getToken()); + Assert.assertEquals("bob", tokens.get(1).getUser()); + Assert.assertEquals("MiquQjELTrEaugpmdgAKs1W3a7xonAwU", tokens.get(2).getToken()); + Assert.assertEquals("bob", tokens.get(2).getUser()); + } + /** + * list api tokens all + */ + @Test + @Betamax(tape = "api_tokens_list_all"/*, mode = TapeMode.READ_ONLY*/) + public void listApiTokens() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + List tokens = client.listApiTokens(); + + Assert.assertNotNull(tokens); + Assert.assertEquals(4, tokens.size()); + Assert.assertEquals("8Dp9op111ER6opsDRkddvE86K9sE499s", tokens.get(0).getToken()); + Assert.assertEquals("admin", tokens.get(0).getUser()); + Assert.assertEquals("hINp5eGzvYA9UePbUChaKHd5NiRkwWbx", tokens.get(1).getToken()); + Assert.assertEquals("bob", tokens.get(1).getUser()); + Assert.assertEquals("NaNnwVzAHAG83qOS7Wtwh6mjcXViyWUV", tokens.get(2).getToken()); + Assert.assertEquals("bob", tokens.get(2).getUser()); + Assert.assertEquals("MiquQjELTrEaugpmdgAKs1W3a7xonAwU", tokens.get(3).getToken()); + Assert.assertEquals("bob", tokens.get(3).getUser()); + } + + /** + * get api token + */ + @Test + @Betamax(tape = "api_token_delete"/*, mode = TapeMode.READ_ONLY*/) + public void deleteApiToken() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + + client.deleteApiToken("MiquQjELTrEaugpmdgAKs1W3a7xonAwU"); + + //get should now return 404 + try { + client.getApiToken("MiquQjELTrEaugpmdgAKs1W3a7xonAwU"); + Assert.fail("expected failure"); + } catch (RundeckApiException.RundeckApiHttpStatusException e) { + Assert.assertEquals(404, e.getStatusCode()); + } + } + @Before public void setUp() throws Exception { // not that you can put whatever here, because we don't actually connect to the RunDeck instance diff --git a/src/test/resources/betamax/tapes/api_token_delete.yaml b/src/test/resources/betamax/tapes/api_token_delete.yaml new file mode 100644 index 0000000..92fc6c0 --- /dev/null +++ b/src/test/resources/betamax/tapes/api_token_delete.yaml @@ -0,0 +1,36 @@ +!tape +name: api_token_delete +interactions: +- recorded: 2014-04-04T18:38:18.432Z + request: + method: DELETE + uri: http://rundeck.local:4440/api/11/token/MiquQjELTrEaugpmdgAKs1W3a7xonAwU + headers: + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + 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=j0fidhqqsmlt1qmvaawr52a42;Path=/ +- recorded: 2014-04-04T18:38:18.523Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/token/MiquQjELTrEaugpmdgAKs1W3a7xonAwU + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + response: + status: 404 + headers: + Content-Type: text/xml;charset=UTF-8 + Server: Jetty(7.6.0.v20120127) + X-Rundeck-API-Version: '11' + body: "\n \n Token does not exist: MiquQjELTrEaugpmdgAKs1W3a7xonAwU\n \n" diff --git a/src/test/resources/betamax/tapes/api_token_generate.yaml b/src/test/resources/betamax/tapes/api_token_generate.yaml new file mode 100644 index 0000000..1a5f72d --- /dev/null +++ b/src/test/resources/betamax/tapes/api_token_generate.yaml @@ -0,0 +1,25 @@ +!tape +name: api_token_generate +interactions: +- recorded: 2014-04-04T18:21:07.759Z + request: + method: POST + uri: http://rundeck.local:4440/api/11/tokens/bob + headers: + Accept: text/xml + Content-Length: '0' + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + response: + status: 201 + 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=1gt9t2gch2zff1a0werz1us5wk;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PHRva2VuIGlkPSdNaXF1UWpFTFRyRWF1Z3BtZGdBS3MxVzNhN3hvbkF3VScgdXNlcj0nYm9iJyAvPg== diff --git a/src/test/resources/betamax/tapes/api_token_get.yaml b/src/test/resources/betamax/tapes/api_token_get.yaml new file mode 100644 index 0000000..8377d6f --- /dev/null +++ b/src/test/resources/betamax/tapes/api_token_get.yaml @@ -0,0 +1,24 @@ +!tape +name: api_token_get +interactions: +- recorded: 2014-04-04T18:23:05.986Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/token/MiquQjELTrEaugpmdgAKs1W3a7xonAwU + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + 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=1tdpszk6b3v191p0ng2u94rohw;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PHRva2VuIGlkPSdNaXF1UWpFTFRyRWF1Z3BtZGdBS3MxVzNhN3hvbkF3VScgdXNlcj0nYm9iJyAvPg== diff --git a/src/test/resources/betamax/tapes/api_tokens_list_all.yaml b/src/test/resources/betamax/tapes/api_tokens_list_all.yaml new file mode 100644 index 0000000..f1318ad --- /dev/null +++ b/src/test/resources/betamax/tapes/api_tokens_list_all.yaml @@ -0,0 +1,24 @@ +!tape +name: api_tokens_list_all +interactions: +- recorded: 2014-04-04T18:32:37.397Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/tokens + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + 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=ixag173yjktz1c5o9yrbe5z5a;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PHRva2VucyBjb3VudD0nNCcgYWxsdXNlcnM9J3RydWUnPgogIDx0b2tlbiBpZD0nOERwOW9wMTExRVI2b3BzRFJrZGR2RTg2SzlzRTQ5OXMnIHVzZXI9J2FkbWluJyAvPgogIDx0b2tlbiBpZD0naElOcDVlR3p2WUE5VWVQYlVDaGFLSGQ1TmlSa3dXYngnIHVzZXI9J2JvYicgLz4KICA8dG9rZW4gaWQ9J05hTm53VnpBSEFHODNxT1M3V3R3aDZtamNYVml5V1VWJyB1c2VyPSdib2InIC8+CiAgPHRva2VuIGlkPSdNaXF1UWpFTFRyRWF1Z3BtZGdBS3MxVzNhN3hvbkF3VScgdXNlcj0nYm9iJyAvPgo8L3Rva2Vucz4= diff --git a/src/test/resources/betamax/tapes/api_tokens_list_user.yaml b/src/test/resources/betamax/tapes/api_tokens_list_user.yaml new file mode 100644 index 0000000..15ec26d --- /dev/null +++ b/src/test/resources/betamax/tapes/api_tokens_list_user.yaml @@ -0,0 +1,24 @@ +!tape +name: api_tokens_list_user +interactions: +- recorded: 2014-04-04T18:26:33.394Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/tokens/bob + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + 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=114794elwavo26cx4ugkv7pe7;Path=/ + X-Rundeck-API-Version: '11' + X-Rundeck-API-XML-Response-Wrapper: 'false' + body: !!binary |- + PHRva2VucyBjb3VudD0nMycgdXNlcj0nYm9iJz4KICA8dG9rZW4gaWQ9J2hJTnA1ZUd6dllBOVVlUGJVQ2hhS0hkNU5pUmt3V2J4JyB1c2VyPSdib2InIC8+CiAgPHRva2VuIGlkPSdOYU5ud1Z6QUhBRzgzcU9TN1d0d2g2bWpjWFZpeVdVVicgdXNlcj0nYm9iJyAvPgogIDx0b2tlbiBpZD0nTWlxdVFqRUxUckVhdWdwbWRnQUtzMVczYTd4b25Bd1UnIHVzZXI9J2JvYicgLz4KPC90b2tlbnM+ From c7153a5613156d253901babd52dc4f891cbb306c Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 4 Apr 2014 11:42:29 -0700 Subject: [PATCH 19/24] API token support added (api v11) --- src/site/confluence/status.confluence | 6 +++--- src/test/java/org/rundeck/api/RundeckClientTest.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/site/confluence/status.confluence b/src/site/confluence/status.confluence index 9bc64f9..0dfbb3e 100644 --- a/src/site/confluence/status.confluence +++ b/src/site/confluence/status.confluence @@ -110,6 +110,6 @@ h2. RunDeck API version 11 * SSH Key delete - *TODO* * SSH Key list - *TODO* * SSH Key get - *TODO* -* API Token create - *TODO* -* API Token list - *TODO* -* API Token delete - *TODO* +* API Token create - OK +* API Token list - OK +* API Token delete - OK diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index 053ff51..654334d 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -1245,7 +1245,7 @@ public class RundeckClientTest { * list api tokens all */ @Test - @Betamax(tape = "api_tokens_list_all"/*, mode = TapeMode.READ_ONLY*/) + @Betamax(tape = "api_tokens_list_all", mode = TapeMode.READ_ONLY) public void listApiTokens() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); List tokens = client.listApiTokens(); @@ -1266,7 +1266,7 @@ public class RundeckClientTest { * get api token */ @Test - @Betamax(tape = "api_token_delete"/*, mode = TapeMode.READ_ONLY*/) + @Betamax(tape = "api_token_delete", mode = TapeMode.READ_ONLY) public void deleteApiToken() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); From 459b498d35a5e6ec52c90f8ea59659ece498d27c Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 4 Apr 2014 13:47:01 -0700 Subject: [PATCH 20/24] Add support for SSH key management (api v11) --- src/main/java/org/rundeck/api/ApiCall.java | 57 ++++- .../java/org/rundeck/api/ApiPathBuilder.java | 11 + .../org/rundeck/api/RundeckApiException.java | 38 ++++ .../java/org/rundeck/api/RundeckClient.java | 137 +++++++++++- .../api/domain/BaseSSHKeyResource.java | 61 +++++ .../api/domain/BaseStorageResource.java | 86 +++++++ .../rundeck/api/domain/SSHKeyResource.java | 23 ++ .../rundeck/api/domain/StorageResource.java | 54 +++++ .../rundeck/api/parser/BaseXpathParser.java | 29 +++ .../api/parser/SSHKeyResourceParser.java | 26 +++ .../api/parser/StorageResourceParser.java | 61 +++++ .../org/rundeck/api/RundeckClientTest.java | 209 ++++++++++++++++++ .../betamax/tapes/ssh_key_delete.yaml | 36 +++ .../tapes/ssh_key_get_data_private.yaml | 22 ++ .../tapes/ssh_key_get_data_public.yaml | 22 ++ .../betamax/tapes/ssh_key_get_private.yaml | 22 ++ .../betamax/tapes/ssh_key_get_public.yaml | 22 ++ .../betamax/tapes/ssh_key_list_directory.yaml | 21 ++ .../betamax/tapes/ssh_key_list_root.yaml | 21 ++ .../betamax/tapes/ssh_key_store_private.yaml | 25 +++ .../betamax/tapes/ssh_key_store_public.yaml | 25 +++ 21 files changed, 1002 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/rundeck/api/domain/BaseSSHKeyResource.java create mode 100644 src/main/java/org/rundeck/api/domain/BaseStorageResource.java create mode 100644 src/main/java/org/rundeck/api/domain/SSHKeyResource.java create mode 100644 src/main/java/org/rundeck/api/domain/StorageResource.java create mode 100644 src/main/java/org/rundeck/api/parser/BaseXpathParser.java create mode 100644 src/main/java/org/rundeck/api/parser/SSHKeyResourceParser.java create mode 100644 src/main/java/org/rundeck/api/parser/StorageResourceParser.java create mode 100644 src/test/resources/betamax/tapes/ssh_key_delete.yaml create mode 100644 src/test/resources/betamax/tapes/ssh_key_get_data_private.yaml create mode 100644 src/test/resources/betamax/tapes/ssh_key_get_data_public.yaml create mode 100644 src/test/resources/betamax/tapes/ssh_key_get_private.yaml create mode 100644 src/test/resources/betamax/tapes/ssh_key_get_public.yaml create mode 100644 src/test/resources/betamax/tapes/ssh_key_list_directory.yaml create mode 100644 src/test/resources/betamax/tapes/ssh_key_list_root.yaml create mode 100644 src/test/resources/betamax/tapes/ssh_key_store_private.yaml create mode 100644 src/test/resources/betamax/tapes/ssh_key_store_public.yaml diff --git a/src/main/java/org/rundeck/api/ApiCall.java b/src/main/java/org/rundeck/api/ApiCall.java index 07e09f9..71a9808 100644 --- a/src/main/java/org/rundeck/api/ApiCall.java +++ b/src/main/java/org/rundeck/api/ApiCall.java @@ -387,10 +387,14 @@ class ApiCall { if (null != apiPath.getAccept()) { request.setHeader("Accept", apiPath.getAccept()); } - WriteOutHandler handler = new WriteOutHandler(outputStream); - int wrote = execute(request, handler); - if(handler.thrown!=null){ - throw handler.thrown; + final WriteOutHandler writeOutHandler = new WriteOutHandler(outputStream); + Handler handler = writeOutHandler; + if(null!=apiPath.getRequiredContentType()){ + handler = new RequireContentTypeHandler(apiPath.getRequiredContentType(), handler); + } + final int wrote = execute(request, handler); + if(writeOutHandler.thrown!=null){ + throw writeOutHandler.thrown; } return wrote; } @@ -435,6 +439,51 @@ class ApiCall { } } + /** + * Handles writing response to an output stream + */ + private static class ChainHandler implements Handler { + Handler chain; + private ChainHandler(Handler chain) { + this.chain=chain; + } + @Override + public T handle(final HttpResponse response) { + return chain.handle(response); + } + } + + /** + * Handles writing response to an output stream + */ + private static class RequireContentTypeHandler extends ChainHandler { + String contentType; + + private RequireContentTypeHandler(final String contentType, final Handler chain) { + super(chain); + this.contentType = contentType; + } + + @Override + public T handle(final HttpResponse response) { + final Header firstHeader = response.getFirstHeader("Content-Type"); + final String[] split = firstHeader.getValue().split(";"); + boolean matched=false; + for (int i = 0; i < split.length; i++) { + String s = split[i]; + if (this.contentType.equalsIgnoreCase(s.trim())) { + matched=true; + break; + } + } + if(!matched) { + throw new RundeckApiException.RundeckApiHttpContentTypeException(firstHeader.getValue(), + this.contentType); + } + return super.handle(response); + } + } + /** * Handles writing response to an output stream */ diff --git a/src/main/java/org/rundeck/api/ApiPathBuilder.java b/src/main/java/org/rundeck/api/ApiPathBuilder.java index 05250ec..7ef79ee 100644 --- a/src/main/java/org/rundeck/api/ApiPathBuilder.java +++ b/src/main/java/org/rundeck/api/ApiPathBuilder.java @@ -50,6 +50,7 @@ class ApiPathBuilder { private InputStream contentStream; private File contentFile; private String contentType; + private String requiredContentType; private boolean emptyContent = false; /** Marker for using the right separator between parameters ("?" or "&") */ @@ -416,6 +417,16 @@ class ApiPathBuilder { public boolean isEmptyContent() { return emptyContent; } + + public ApiPathBuilder requireContentType(String contentType) { + this.requiredContentType=contentType; + return this; + } + + public String getRequiredContentType() { + return requiredContentType; + } + /** * BuildsParameters can add URL or POST parameters to an {@link ApiPathBuilder} * diff --git a/src/main/java/org/rundeck/api/RundeckApiException.java b/src/main/java/org/rundeck/api/RundeckApiException.java index 2a5b1ca..5a2de8b 100644 --- a/src/main/java/org/rundeck/api/RundeckApiException.java +++ b/src/main/java/org/rundeck/api/RundeckApiException.java @@ -105,4 +105,42 @@ public class RundeckApiException extends RuntimeException { } } + /** + * Error due to unexpected HTTP content-type + */ + public static class RundeckApiHttpContentTypeException extends RundeckApiAuthException { + + private static final long serialVersionUID = 1L; + private String contentType; + private String requiredContentType; + + public RundeckApiHttpContentTypeException(final String contentType, + final String requiredContentType) { + super("Unexpected content-type: '" + contentType + "', expected: '" + requiredContentType + "'"); + this.contentType = contentType; + this.requiredContentType = requiredContentType; + } + public RundeckApiHttpContentTypeException(final String message, final String contentType, + final String requiredContentType) { + super(message); + this.contentType = contentType; + this.requiredContentType = requiredContentType; + } + + public RundeckApiHttpContentTypeException(final String message, final Throwable cause, final String contentType, + final String requiredContentType) { + super(message, cause); + this.contentType = contentType; + this.requiredContentType = requiredContentType; + } + + public String getContentType() { + return contentType; + } + + public String getRequiredContentType() { + return requiredContentType; + } + } + } diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index e56bda1..b0baa4c 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -19,8 +19,6 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.dom4j.Document; -import org.dom4j.DocumentFactory; -import org.dom4j.Element; import org.rundeck.api.RundeckApiException.RundeckApiLoginException; import org.rundeck.api.RundeckApiException.RundeckApiTokenException; import org.rundeck.api.domain.*; @@ -83,6 +81,8 @@ public class RundeckClient implements Serializable { private static final long serialVersionUID = 1L; public static final String JOBS_IMPORT = "/jobs/import"; + public static final String STORAGE_ROOT_PATH = "/storage/"; + public static final String SSH_KEY_PATH = "ssh-key/"; /** * Supported version numbers @@ -2374,6 +2374,139 @@ public class RundeckClient implements Serializable { return new ApiCall(this).get(new ApiPathBuilder("/token/", token), new RundeckTokenParser("/token")); } + /** + * Store an SSH key file + * @param path ssh key storage path, must start with "ssh-key/" + * @param keyfile key file + * @param privateKey true to store private key, false to store public key + * @return the SSH key resource + * @throws RundeckApiException + */ + public SSHKeyResource storeSshKey(final String path, final File keyfile, boolean privateKey) throws RundeckApiException{ + AssertUtil.notNull(path, "path is mandatory to store an SSH key."); + AssertUtil.notNull(keyfile, "keyfile is mandatory to store an SSH key."); + if (!path.startsWith(SSH_KEY_PATH)) { + throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + } + return new ApiCall(this).post( + new ApiPathBuilder(STORAGE_ROOT_PATH, path).content( + privateKey ? "application/octet-stream" : "application/pgp-keys", + keyfile + ), + new SSHKeyResourceParser("/resource") + ); + } + + /** + * Get metadata for an SSH key file + * + * @param path ssh key storage path, must start with "ssh-key/" + * + * @return the ssh key resource + * + * @throws RundeckApiException if there is an error, or if the path is a directory not a file + */ + public SSHKeyResource getSshKey(final String path) throws RundeckApiException { + AssertUtil.notNull(path, "path is mandatory to get an SSH key."); + if (!path.startsWith(SSH_KEY_PATH)) { + throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + } + SSHKeyResource storageResource = new ApiCall(this).get( + new ApiPathBuilder(STORAGE_ROOT_PATH, path), + new SSHKeyResourceParser("/resource") + ); + if (storageResource.isDirectory()) { + throw new RundeckApiException("SSH Key Path is a directory: " + path); + } + return storageResource; + } + + /** + * Get content for a public SSH key file + * @param path ssh key storage path, must start with "ssh-key/" + * @param out outputstream to write data to + * + * @return length of written data + * @throws RundeckApiException + */ + public int getPublicSshKeyContent(final String path, final OutputStream out) throws + RundeckApiException, IOException { + AssertUtil.notNull(path, "path is mandatory to get an SSH key."); + if (!path.startsWith(SSH_KEY_PATH)) { + throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + } + try { + return new ApiCall(this).get( + new ApiPathBuilder(STORAGE_ROOT_PATH, path) + .accept("application/pgp-keys") + .requireContentType("application/pgp-keys"), + out + ); + } catch (RundeckApiException.RundeckApiHttpContentTypeException e) { + throw new RundeckApiException("Requested SSH Key path was not a Public key: " + path, e); + } + } + + /** + * Get content for a public SSH key file + * @param path ssh key storage path, must start with "ssh-key/" + * @param out file to write data to + * @return length of written data + * @throws RundeckApiException + */ + public int getPublicSshKeyContent(final String path, final File out) throws + RundeckApiException, IOException { + final FileOutputStream fileOutputStream = new FileOutputStream(out); + try { + return getPublicSshKeyContent(path, fileOutputStream); + }finally { + fileOutputStream.close(); + } + } + + /** + * List contents of root SSH key directory + * + * @return list of SSH key resources + * @throws RundeckApiException + */ + public List listSshKeyDirectoryRoot() throws RundeckApiException { + return listSshKeyDirectory(SSH_KEY_PATH); + } + /** + * List contents of SSH key directory + * + * @param path ssh key storage path, must start with "ssh-key/" + * + * @throws RundeckApiException if there is an error, or if the path is a file not a directory + */ + public List listSshKeyDirectory(final String path) throws RundeckApiException { + AssertUtil.notNull(path, "path is mandatory to get an SSH key."); + if (!path.startsWith(SSH_KEY_PATH)) { + throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + } + SSHKeyResource storageResource = new ApiCall(this).get( + new ApiPathBuilder(STORAGE_ROOT_PATH, path), + new SSHKeyResourceParser("/resource") + ); + if(!storageResource.isDirectory()) { + throw new RundeckApiException("SSH key path is not a directory path: " + path); + } + return storageResource.getDirectoryContents(); + } + + /** + * Delete an SSH key file + * @param path a path to a SSH key file, must start with "ssh-key/" + */ + public void deleteSshKey(final String path){ + AssertUtil.notNull(path, "path is mandatory to delete an SSH key."); + if (!path.startsWith(SSH_KEY_PATH)) { + throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + } + new ApiCall(this).delete(new ApiPathBuilder(STORAGE_ROOT_PATH, path)); + } + /** * @return the URL of the RunDeck instance ("http://localhost:4440", "http://rundeck.your-compagny.com/", etc) */ diff --git a/src/main/java/org/rundeck/api/domain/BaseSSHKeyResource.java b/src/main/java/org/rundeck/api/domain/BaseSSHKeyResource.java new file mode 100644 index 0000000..b47d878 --- /dev/null +++ b/src/main/java/org/rundeck/api/domain/BaseSSHKeyResource.java @@ -0,0 +1,61 @@ +package org.rundeck.api.domain; + +import org.rundeck.api.RundeckClient; +import org.rundeck.api.parser.StorageResourceParser; + +import java.util.ArrayList; +import java.util.List; + +/** + * BaseSSHKeyResource is ... + * + * @author Greg Schueler + * @since 2014-04-04 + */ +public class BaseSSHKeyResource extends BaseStorageResource implements SSHKeyResource { + private boolean privateKey; + + public BaseSSHKeyResource() { + } + + + public boolean isPrivateKey() { + return privateKey; + } + + public void setPrivateKey(boolean privateKey) { + this.privateKey = privateKey; + } + + ArrayList sshKeyResources = new ArrayList(); + + @Override + public void setDirectoryContents(List directoryContents) { + for (StorageResource directoryContent : directoryContents) { + sshKeyResources.add(from(directoryContent)); + } + } + + @Override + public List getDirectoryContents() { + return sshKeyResources; + } + + public static BaseSSHKeyResource from(final StorageResource source) { + final BaseSSHKeyResource baseSshKeyResource = new BaseSSHKeyResource(); + baseSshKeyResource.setDirectory(source.isDirectory()); + baseSshKeyResource.setPath(source.getPath()); + baseSshKeyResource.setName(source.getName()); + baseSshKeyResource.setMetadata(source.getMetadata()); + baseSshKeyResource.setUrl(source.getUrl()); + if (!baseSshKeyResource.isDirectory()) { + baseSshKeyResource.setPrivateKey( + null != baseSshKeyResource.getMetadata() && "private".equals(baseSshKeyResource.getMetadata().get + ("Rundeck-ssh-key-type")) + ); + } else if (null != source.getDirectoryContents()) { + baseSshKeyResource.setDirectoryContents(source.getDirectoryContents()); + } + return baseSshKeyResource; + } +} diff --git a/src/main/java/org/rundeck/api/domain/BaseStorageResource.java b/src/main/java/org/rundeck/api/domain/BaseStorageResource.java new file mode 100644 index 0000000..6639ab4 --- /dev/null +++ b/src/main/java/org/rundeck/api/domain/BaseStorageResource.java @@ -0,0 +1,86 @@ +package org.rundeck.api.domain; + +import java.util.List; +import java.util.Map; + +/** + * BaseStorageResource is ... + * + * @author Greg Schueler + * @since 2014-04-04 + */ +public class BaseStorageResource implements StorageResource { + private String path; + private String url; + private String name; + private Map metadata; + private boolean directory; + private List directoryContents; + + public BaseStorageResource() { + } + + @Override + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + @Override + public boolean isDirectory() { + return directory; + } + + public void setDirectory(boolean directory) { + this.directory = directory; + } + + @Override + public List getDirectoryContents() { + return directoryContents; + } + + public void setDirectoryContents(List directoryContents) { + this.directoryContents = directoryContents; + } + + @Override + public String toString() { + return "BaseStorageResource{" + + "path='" + path + '\'' + + ", url='" + url + '\'' + + ", name='" + name + '\'' + + ", directory=" + directory + + '}'; + } +} diff --git a/src/main/java/org/rundeck/api/domain/SSHKeyResource.java b/src/main/java/org/rundeck/api/domain/SSHKeyResource.java new file mode 100644 index 0000000..33bbbd5 --- /dev/null +++ b/src/main/java/org/rundeck/api/domain/SSHKeyResource.java @@ -0,0 +1,23 @@ +package org.rundeck.api.domain; + +import java.util.List; + +/** + * SSHKeyResource represents a directory or an SSH key file + * + * @author Greg Schueler + * @since 2014-04-04 + */ +public interface SSHKeyResource extends StorageResource { + /** + * Return true if this is a file and is a private SSH key file. + * @return + */ + public boolean isPrivateKey(); + + /** + * Return the list of SSH Key resources if this is a directory + * @return + */ + public List getDirectoryContents(); +} diff --git a/src/main/java/org/rundeck/api/domain/StorageResource.java b/src/main/java/org/rundeck/api/domain/StorageResource.java new file mode 100644 index 0000000..be02412 --- /dev/null +++ b/src/main/java/org/rundeck/api/domain/StorageResource.java @@ -0,0 +1,54 @@ +package org.rundeck.api.domain; + +import java.util.List; +import java.util.Map; + +/** + * StorageResource represents a directory or a file + * + * @author Greg Schueler + * @since 2014-04-04 + */ +public interface StorageResource { + /** + * Return the storage path for this resource + * + * @return + */ + public String getPath(); + + /** + * Return the URL for this resource + * + * @return + */ + public String getUrl(); + + /** + * Return the file name if this is a file + * + * @return + */ + public String getName(); + + /** + * Return the metadata for this file if this is a file + * + * @return + */ + public Map getMetadata(); + + /** + * Return true if this is a directory, false if this is a file + * + * @return + */ + public boolean isDirectory(); + + /** + * Return the list of directory contents if this is a directory + * + * @return + */ + public List getDirectoryContents(); +} diff --git a/src/main/java/org/rundeck/api/parser/BaseXpathParser.java b/src/main/java/org/rundeck/api/parser/BaseXpathParser.java new file mode 100644 index 0000000..26be541 --- /dev/null +++ b/src/main/java/org/rundeck/api/parser/BaseXpathParser.java @@ -0,0 +1,29 @@ +package org.rundeck.api.parser; + +import org.dom4j.Node; + +/** + * BaseXpathParser is ... + * + * @author Greg Schueler + * @since 2014-04-04 + */ +public abstract class BaseXpathParser implements XmlNodeParser { + private String xpath; + + public BaseXpathParser() { + } + + public BaseXpathParser(String xpath) { + + this.xpath = xpath; + } + + public abstract T parse(Node node); + + @Override + public T parseXmlNode(Node node) { + Node selectedNode = xpath != null ? node.selectSingleNode(xpath) : node; + return parse(selectedNode); + } +} diff --git a/src/main/java/org/rundeck/api/parser/SSHKeyResourceParser.java b/src/main/java/org/rundeck/api/parser/SSHKeyResourceParser.java new file mode 100644 index 0000000..5485d31 --- /dev/null +++ b/src/main/java/org/rundeck/api/parser/SSHKeyResourceParser.java @@ -0,0 +1,26 @@ +package org.rundeck.api.parser; + +import org.dom4j.Node; +import org.rundeck.api.RundeckClient; +import org.rundeck.api.domain.BaseSSHKeyResource; +import org.rundeck.api.domain.SSHKeyResource; + +/** + * SSHKeyResourceParser is ... + * + * @author Greg Schueler + * @since 2014-04-04 + */ +public class SSHKeyResourceParser extends BaseXpathParser implements XmlNodeParser { + public SSHKeyResourceParser() { + } + + public SSHKeyResourceParser(String xpath) { + super(xpath); + } + + @Override + public SSHKeyResource parse(Node node) { + return BaseSSHKeyResource.from(new StorageResourceParser().parse(node)); + } +} diff --git a/src/main/java/org/rundeck/api/parser/StorageResourceParser.java b/src/main/java/org/rundeck/api/parser/StorageResourceParser.java new file mode 100644 index 0000000..f1c5e6a --- /dev/null +++ b/src/main/java/org/rundeck/api/parser/StorageResourceParser.java @@ -0,0 +1,61 @@ +package org.rundeck.api.parser; + +import org.dom4j.Element; +import org.dom4j.Node; +import org.rundeck.api.domain.BaseStorageResource; +import org.rundeck.api.domain.StorageResource; + +import java.util.HashMap; + +/** + * StorageResourceParser is ... + * + * @author Greg Schueler + * @since 2014-04-04 + */ +public class StorageResourceParser extends BaseXpathParser { + private BaseStorageResource holder; + public StorageResourceParser() { + + } + public StorageResourceParser(BaseStorageResource holder){ + this.holder=holder; + } + + public StorageResourceParser(String xpath) { + super(xpath); + } + + @Override + public StorageResource parse(Node node) { + String path = node.valueOf("@path"); + String type = node.valueOf("@type"); + String url = node.valueOf("@url"); + BaseStorageResource storageResource = null == holder ? new BaseStorageResource() : holder; + storageResource.setDirectory("directory".equals(type)); + storageResource.setPath(path); + storageResource.setUrl(url); + + if (storageResource.isDirectory()) { + if (node.selectSingleNode("contents") != null) { + storageResource.setDirectoryContents(new ListParser(new StorageResourceParser(), + "contents/resource").parseXmlNode(node)); + } + } else { + String name = node.valueOf("@name"); + storageResource.setName(name); + + Node meta = node.selectSingleNode("resource-meta"); + HashMap metamap = new HashMap(); + if (null != meta) { + Element metaEl = (Element) meta; + for (Object o : metaEl.elements()) { + Element sub = (Element) o; + metamap.put(sub.getName(), sub.getText().trim()); + } + } + storageResource.setMetadata(metamap); + } + return storageResource; + } +} diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index 654334d..f989e69 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -1280,6 +1280,215 @@ public class RundeckClientTest { Assert.assertEquals(404, e.getStatusCode()); } } + /** + * Store ssh key + */ + @Test + @Betamax(tape = "ssh_key_store_private", mode = TapeMode.READ_ONLY) + public void storeSshKey_private() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + File temp = File.createTempFile("test-key", ".tmp"); + temp.deleteOnExit(); + FileOutputStream out = new FileOutputStream(temp); + try{ + out.write("test1".getBytes()); + }finally { + out.close(); + } + SSHKeyResource storageResource = client.storeSshKey("ssh-key/test/example/file1.pem", temp, true); + Assert.assertNotNull(storageResource); + Assert.assertFalse(storageResource.isDirectory()); + Assert.assertTrue(storageResource.isPrivateKey()); + Assert.assertEquals("file1.pem", storageResource.getName()); + Assert.assertEquals("ssh-key/test/example/file1.pem", storageResource.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file1.pem", + storageResource.getUrl()); + Assert.assertEquals(0, storageResource.getDirectoryContents().size()); + Map metadata = storageResource.getMetadata(); + Assert.assertNotNull(metadata); + Assert.assertEquals("application/octet-stream", metadata.get("Rundeck-content-type")); + Assert.assertEquals("private", metadata.get("Rundeck-ssh-key-type")); + } + /** + * Store ssh key + */ + @Test + @Betamax(tape = "ssh_key_store_public", mode = TapeMode.READ_ONLY) + public void storeSshKey_public() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + File temp = File.createTempFile("test-key", ".tmp"); + temp.deleteOnExit(); + FileOutputStream out = new FileOutputStream(temp); + try{ + out.write("test1".getBytes()); + }finally { + out.close(); + } + SSHKeyResource storageResource = client.storeSshKey("ssh-key/test/example/file2.pub", temp, false); + Assert.assertNotNull(storageResource); + Assert.assertFalse(storageResource.isDirectory()); + Assert.assertFalse(storageResource.isPrivateKey()); + Assert.assertEquals("file2.pub", storageResource.getName()); + Assert.assertEquals("ssh-key/test/example/file2.pub", storageResource.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file2.pub", + storageResource.getUrl()); + Assert.assertEquals(0, storageResource.getDirectoryContents().size()); + Map metadata = storageResource.getMetadata(); + Assert.assertNotNull(metadata); + Assert.assertEquals("application/pgp-keys", metadata.get("Rundeck-content-type")); + Assert.assertEquals("public", metadata.get("Rundeck-ssh-key-type")); + } + /** + * get ssh key + */ + @Test + @Betamax(tape = "ssh_key_get_public", mode = TapeMode.READ_ONLY) + public void getSshKey_public() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + SSHKeyResource storageResource = client.getSshKey("ssh-key/test/example/file2.pub"); + Assert.assertNotNull(storageResource); + Assert.assertFalse(storageResource.isDirectory()); + Assert.assertFalse(storageResource.isPrivateKey()); + Assert.assertEquals("file2.pub", storageResource.getName()); + Assert.assertEquals("ssh-key/test/example/file2.pub", storageResource.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file2.pub", + storageResource.getUrl()); + Assert.assertEquals(0, storageResource.getDirectoryContents().size()); + Map metadata = storageResource.getMetadata(); + Assert.assertNotNull(metadata); + Assert.assertEquals("application/pgp-keys", metadata.get("Rundeck-content-type")); + Assert.assertEquals("public", metadata.get("Rundeck-ssh-key-type")); + } + /** + * get ssh key + */ + @Test + @Betamax(tape = "ssh_key_get_private", mode = TapeMode.READ_ONLY) + public void getSshKey_private() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + SSHKeyResource storageResource = client.getSshKey("ssh-key/test/example/file1.pem"); + Assert.assertNotNull(storageResource); + Assert.assertFalse(storageResource.isDirectory()); + Assert.assertTrue(storageResource.isPrivateKey()); + Assert.assertEquals("file1.pem", storageResource.getName()); + Assert.assertEquals("ssh-key/test/example/file1.pem", storageResource.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file1.pem", + storageResource.getUrl()); + Assert.assertEquals(0, storageResource.getDirectoryContents().size()); + Map metadata = storageResource.getMetadata(); + Assert.assertNotNull(metadata); + Assert.assertEquals("application/octet-stream", metadata.get("Rundeck-content-type")); + Assert.assertEquals("private", metadata.get("Rundeck-ssh-key-type")); + } + /** + * get ssh key data + */ + @Test + @Betamax(tape = "ssh_key_get_data_private", mode = TapeMode.READ_ONLY) + public void getSshKeyData_private() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + File temp = File.createTempFile("test-key", ".tmp"); + temp.deleteOnExit(); + try { + int data = client.getPublicSshKeyContent("ssh-key/test/example/file1.pem", temp); + Assert.fail("expected failure"); + } catch (RundeckApiException e) { + Assert.assertEquals("Requested SSH Key path was not a Public key: ssh-key/test/example/file1.pem", + e.getMessage()); + } + } + /** + * get ssh key data + */ + @Test + @Betamax(tape = "ssh_key_get_data_public", mode = TapeMode.READ_ONLY) + public void getSshKeyData_public() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + File temp = File.createTempFile("test-key", ".tmp"); + temp.deleteOnExit(); + int length = client.getPublicSshKeyContent("ssh-key/test/example/file2.pub", temp); + Assert.assertEquals(5, length); + } + /** + * list directory + */ + @Test + @Betamax(tape = "ssh_key_list_directory", mode = TapeMode.READ_ONLY) + public void listSshKeyDirectory() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + List list = client.listSshKeyDirectory("ssh-key/test/example"); + Assert.assertEquals(2, list.size()); + SSHKeyResource storageResource1 = list.get(0); + SSHKeyResource storageResource2 = list.get(1); + + Assert.assertFalse(storageResource2.isDirectory()); + Assert.assertTrue(storageResource2.isPrivateKey()); + Assert.assertEquals("file1.pem", storageResource2.getName()); + Assert.assertEquals("ssh-key/test/example/file1.pem", storageResource2.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file1.pem", storageResource2.getUrl()); + Assert.assertNotNull(storageResource2.getMetadata()); + + Assert.assertEquals("application/octet-stream", storageResource2.getMetadata().get("Rundeck-content-type")); + Assert.assertEquals("private", storageResource2.getMetadata().get("Rundeck-ssh-key-type")); + + Assert.assertFalse(storageResource1.isDirectory()); + Assert.assertFalse(storageResource1.isPrivateKey()); + Assert.assertEquals("file2.pub", storageResource1.getName()); + Assert.assertEquals("ssh-key/test/example/file2.pub", storageResource1.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file2.pub", + storageResource1.getUrl()); + Assert.assertNotNull(storageResource1.getMetadata()); + Assert.assertEquals("application/pgp-keys", storageResource1.getMetadata().get("Rundeck-content-type")); + Assert.assertEquals("public", storageResource1.getMetadata().get("Rundeck-ssh-key-type")); + } + /** + * list root + */ + @Test + @Betamax(tape = "ssh_key_list_root", mode = TapeMode.READ_ONLY) + public void listSshKeyDirectoryRoot() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + List list = client.listSshKeyDirectoryRoot(); + Assert.assertEquals(2, list.size()); + SSHKeyResource storageResource0 = list.get(0); + SSHKeyResource storageResource1 = list.get(1); + + Assert.assertFalse(storageResource0.isDirectory()); + Assert.assertTrue(storageResource0.isPrivateKey()); + Assert.assertEquals("test1.pem", storageResource0.getName()); + Assert.assertEquals("ssh-key/test1.pem", storageResource0.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test1.pem", storageResource0.getUrl()); + Assert.assertNotNull(storageResource0.getMetadata()); + + Assert.assertEquals("application/octet-stream", storageResource0.getMetadata().get("Rundeck-content-type")); + Assert.assertEquals("private", storageResource0.getMetadata().get("Rundeck-ssh-key-type")); + + Assert.assertTrue(storageResource1.toString(), storageResource1.isDirectory()); + Assert.assertEquals(null, storageResource1.getName()); + Assert.assertEquals("ssh-key/test", storageResource1.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test", + storageResource1.getUrl()); + Assert.assertNull(storageResource1.getMetadata()); + + + } + + /** + * delete ssh key + */ + @Test + @Betamax(tape = "ssh_key_delete", mode = TapeMode.READ_ONLY) + public void deleteSshKey() throws Exception { + final RundeckClient client = createClient(TEST_TOKEN_7, 11); + client.deleteSshKey("ssh-key/test/example/file2.pub"); + + try { + client.getSshKey("ssh-key/test/example/file2.pub"); + Assert.fail("expected failure"); + } catch (RundeckApiException.RundeckApiHttpStatusException e) { + Assert.assertEquals(404,e.getStatusCode()); + } + } @Before public void setUp() throws Exception { diff --git a/src/test/resources/betamax/tapes/ssh_key_delete.yaml b/src/test/resources/betamax/tapes/ssh_key_delete.yaml new file mode 100644 index 0000000..fd46eae --- /dev/null +++ b/src/test/resources/betamax/tapes/ssh_key_delete.yaml @@ -0,0 +1,36 @@ +!tape +name: ssh_key_delete +interactions: +- recorded: 2014-04-04T23:16:02.256Z + request: + method: DELETE + uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + headers: + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + 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=3rsjs6vicpc619b1uy7oshp4y;Path=/ +- recorded: 2014-04-04T23:16:02.372Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + response: + status: 404 + headers: + Content-Type: application/xml;charset=utf-8 + Server: Jetty(7.6.0.v20120127) + body: !!binary |- + PGVycm9yPnJlc291cmNlIG5vdCBmb3VuZDogL3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUyLnB1YjwvZXJyb3I+ diff --git a/src/test/resources/betamax/tapes/ssh_key_get_data_private.yaml b/src/test/resources/betamax/tapes/ssh_key_get_data_private.yaml new file mode 100644 index 0000000..e5e742b --- /dev/null +++ b/src/test/resources/betamax/tapes/ssh_key_get_data_private.yaml @@ -0,0 +1,22 @@ +!tape +name: ssh_key_get_data_private +interactions: +- recorded: 2014-04-04T19:50:59.155Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file1.pem + headers: + Accept: application/pgp-keys + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + response: + status: 200 + headers: + Content-Type: application/json;charset=UTF-8 + Expires: Thu, 01 Jan 1970 00:00:00 GMT + Server: Jetty(7.6.0.v20120127) + Set-Cookie: JSESSIONID=1gzu37lkjr0fitxhf5fgkgsfu;Path=/ + body: !!binary |- + eyJwYXRoIjoic3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTEucGVtIiwidHlwZSI6ImZpbGUiLCJuYW1lIjoiZmlsZTEucGVtIiwidXJsIjoiaHR0cDovL2RpZ25hbi5sb2NhbDo0NDQwL2FwaS8xMS9zdG9yYWdlL3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUxLnBlbSIsIm1ldGEiOnsiUnVuZGVjay1jb250ZW50LXR5cGUiOiJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iLCJSdW5kZWNrLWNvbnRlbnQtc2l6ZSI6IjUiLCJSdW5kZWNrLWNvbnRlbnQtbWFzayI6ImNvbnRlbnQiLCJSdW5kZWNrLXNzaC1rZXktdHlwZSI6InByaXZhdGUifX0= diff --git a/src/test/resources/betamax/tapes/ssh_key_get_data_public.yaml b/src/test/resources/betamax/tapes/ssh_key_get_data_public.yaml new file mode 100644 index 0000000..1874e71 --- /dev/null +++ b/src/test/resources/betamax/tapes/ssh_key_get_data_public.yaml @@ -0,0 +1,22 @@ +!tape +name: ssh_key_get_data_public +interactions: +- recorded: 2014-04-04T20:20:44.331Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + headers: + Accept: application/pgp-keys + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + response: + status: 200 + headers: + Content-Type: application/pgp-keys + Expires: Thu, 01 Jan 1970 00:00:00 GMT + Server: Jetty(7.6.0.v20120127) + Set-Cookie: JSESSIONID=1mrub15qsorpf10cisx24h8h03;Path=/ + body: !!binary |- + dGVzdDE= diff --git a/src/test/resources/betamax/tapes/ssh_key_get_private.yaml b/src/test/resources/betamax/tapes/ssh_key_get_private.yaml new file mode 100644 index 0000000..fa0ff2c --- /dev/null +++ b/src/test/resources/betamax/tapes/ssh_key_get_private.yaml @@ -0,0 +1,22 @@ +!tape +name: ssh_key_get_private +interactions: +- recorded: 2014-04-04T19:47:29.880Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file1.pem + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + 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=nc5p0he3nw19e4gegidc4bs7;Path=/ + body: !!binary |- + PHJlc291cmNlIHBhdGg9J3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUxLnBlbScgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uvc3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTEucGVtJyBuYW1lPSdmaWxlMS5wZW0nPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9vY3RldC1zdHJlYW08L1J1bmRlY2stY29udGVudC10eXBlPjxSdW5kZWNrLWNvbnRlbnQtc2l6ZT41PC9SdW5kZWNrLWNvbnRlbnQtc2l6ZT48UnVuZGVjay1jb250ZW50LW1hc2s+Y29udGVudDwvUnVuZGVjay1jb250ZW50LW1hc2s+PFJ1bmRlY2stc3NoLWtleS10eXBlPnByaXZhdGU8L1J1bmRlY2stc3NoLWtleS10eXBlPjwvcmVzb3VyY2UtbWV0YT48L3Jlc291cmNlPg== diff --git a/src/test/resources/betamax/tapes/ssh_key_get_public.yaml b/src/test/resources/betamax/tapes/ssh_key_get_public.yaml new file mode 100644 index 0000000..8bede8d --- /dev/null +++ b/src/test/resources/betamax/tapes/ssh_key_get_public.yaml @@ -0,0 +1,22 @@ +!tape +name: ssh_key_get_public +interactions: +- recorded: 2014-04-04T19:47:29.626Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + 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=r6p6fl87ftrb1mkwwi0pcak5i;Path=/ + body: !!binary |- + PHJlc291cmNlIHBhdGg9J3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUyLnB1YicgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uvc3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTIucHViJyBuYW1lPSdmaWxlMi5wdWInPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9wZ3Ata2V5czwvUnVuZGVjay1jb250ZW50LXR5cGU+PFJ1bmRlY2stY29udGVudC1zaXplPjU8L1J1bmRlY2stY29udGVudC1zaXplPjxSdW5kZWNrLXNzaC1rZXktdHlwZT5wdWJsaWM8L1J1bmRlY2stc3NoLWtleS10eXBlPjwvcmVzb3VyY2UtbWV0YT48L3Jlc291cmNlPg== diff --git a/src/test/resources/betamax/tapes/ssh_key_list_directory.yaml b/src/test/resources/betamax/tapes/ssh_key_list_directory.yaml new file mode 100644 index 0000000..e49f941 --- /dev/null +++ b/src/test/resources/betamax/tapes/ssh_key_list_directory.yaml @@ -0,0 +1,21 @@ +!tape +name: ssh_key_list_directory +interactions: +- recorded: 2014-04-04T20:33:07.968Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + response: + status: 200 + 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=1stwtoatsosy91ca0gcrzde698;Path=/ + body: application/pgp-keys5publicapplication/octet-stream5contentprivate diff --git a/src/test/resources/betamax/tapes/ssh_key_list_root.yaml b/src/test/resources/betamax/tapes/ssh_key_list_root.yaml new file mode 100644 index 0000000..dcefe5e --- /dev/null +++ b/src/test/resources/betamax/tapes/ssh_key_list_root.yaml @@ -0,0 +1,21 @@ +!tape +name: ssh_key_list_root +interactions: +- recorded: 2014-04-04T20:41:16.501Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/ssh-key/ + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + response: + status: 200 + 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=5sj36vytg4y1182mziim4868b;Path=/ + body: application/octet-stream1679contentprivate diff --git a/src/test/resources/betamax/tapes/ssh_key_store_private.yaml b/src/test/resources/betamax/tapes/ssh_key_store_private.yaml new file mode 100644 index 0000000..6ef2def --- /dev/null +++ b/src/test/resources/betamax/tapes/ssh_key_store_private.yaml @@ -0,0 +1,25 @@ +!tape +name: ssh_key_store_private +interactions: +- recorded: 2014-04-04T19:30:35.367Z + request: + method: POST + uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file1.pem + headers: + Accept: text/xml + Content-Length: '5' + Content-Type: application/octet-stream + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + body: '' + response: + status: 201 + 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=ktmwc4h53xfud6v2ch67x5p9;Path=/ + body: !!binary |- + PHJlc291cmNlIHBhdGg9J3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUxLnBlbScgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uvc3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTEucGVtJyBuYW1lPSdmaWxlMS5wZW0nPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9vY3RldC1zdHJlYW08L1J1bmRlY2stY29udGVudC10eXBlPjxSdW5kZWNrLWNvbnRlbnQtc2l6ZT41PC9SdW5kZWNrLWNvbnRlbnQtc2l6ZT48UnVuZGVjay1jb250ZW50LW1hc2s+Y29udGVudDwvUnVuZGVjay1jb250ZW50LW1hc2s+PFJ1bmRlY2stc3NoLWtleS10eXBlPnByaXZhdGU8L1J1bmRlY2stc3NoLWtleS10eXBlPjwvcmVzb3VyY2UtbWV0YT48L3Jlc291cmNlPg== diff --git a/src/test/resources/betamax/tapes/ssh_key_store_public.yaml b/src/test/resources/betamax/tapes/ssh_key_store_public.yaml new file mode 100644 index 0000000..8a1b18a --- /dev/null +++ b/src/test/resources/betamax/tapes/ssh_key_store_public.yaml @@ -0,0 +1,25 @@ +!tape +name: ssh_key_store_public +interactions: +- recorded: 2014-04-04T19:34:02.683Z + request: + method: POST + uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + headers: + Accept: text/xml + Content-Length: '5' + Content-Type: application/pgp-keys + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + body: '' + response: + status: 201 + 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=2l3g8m0tvwef19jn2bu23bzk6;Path=/ + body: !!binary |- + PHJlc291cmNlIHBhdGg9J3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUyLnB1YicgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uvc3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTIucHViJyBuYW1lPSdmaWxlMi5wdWInPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9wZ3Ata2V5czwvUnVuZGVjay1jb250ZW50LXR5cGU+PFJ1bmRlY2stY29udGVudC1zaXplPjU8L1J1bmRlY2stY29udGVudC1zaXplPjxSdW5kZWNrLXNzaC1rZXktdHlwZT5wdWJsaWM8L1J1bmRlY2stc3NoLWtleS10eXBlPjwvcmVzb3VyY2UtbWV0YT48L3Jlc291cmNlPg== From 89452e731a3faea711b70442ae6311ad88e7bee2 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Fri, 4 Apr 2014 16:39:52 -0700 Subject: [PATCH 21/24] Update ssh key api todo --- src/site/confluence/status.confluence | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/site/confluence/status.confluence b/src/site/confluence/status.confluence index 0dfbb3e..3042fa8 100644 --- a/src/site/confluence/status.confluence +++ b/src/site/confluence/status.confluence @@ -106,10 +106,10 @@ h2. RunDeck API version 11 * Delete project - OK * Export project archive - OK * Import project archive - OK -* SSH Key upload - *TODO* -* SSH Key delete - *TODO* -* SSH Key list - *TODO* -* SSH Key get - *TODO* +* SSH Key upload - OK +* SSH Key delete - OK +* SSH Key list - OK +* SSH Key get - OK * API Token create - OK * API Token list - OK * API Token delete - OK From a59246bdf3e1064abb1086205016fa0481651137 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Wed, 23 Apr 2014 16:18:41 -0700 Subject: [PATCH 22/24] Api sync for key storage * change ssh-key/ to keys/ in api paths --- .../java/org/rundeck/api/RundeckClient.java | 92 +++++++------- ...HKeyResource.java => BaseKeyResource.java} | 23 ++-- .../{SSHKeyResource.java => KeyResource.java} | 6 +- .../api/parser/SSHKeyResourceParser.java | 11 +- .../org/rundeck/api/RundeckClientTest.java | 112 +++++++++--------- .../{ssh_key_delete.yaml => key_delete.yaml} | 4 +- ...private.yaml => key_get_data_private.yaml} | 5 +- ...a_public.yaml => key_get_data_public.yaml} | 2 +- .../betamax/tapes/key_get_private.yaml | 23 ++++ ...ey_get_public.yaml => key_get_public.yaml} | 5 +- .../betamax/tapes/key_list_directory.yaml | 21 ++++ .../betamax/tapes/key_list_root.yaml | 21 ++++ ...re_private.yaml => key_store_private.yaml} | 5 +- ...tore_public.yaml => key_store_public.yaml} | 5 +- .../betamax/tapes/ssh_key_get_private.yaml | 22 ---- .../betamax/tapes/ssh_key_list_directory.yaml | 21 ---- .../betamax/tapes/ssh_key_list_root.yaml | 21 ---- 17 files changed, 200 insertions(+), 199 deletions(-) rename src/main/java/org/rundeck/api/domain/{BaseSSHKeyResource.java => BaseKeyResource.java} (65%) rename src/main/java/org/rundeck/api/domain/{SSHKeyResource.java => KeyResource.java} (68%) rename src/test/resources/betamax/tapes/{ssh_key_delete.yaml => key_delete.yaml} (86%) rename src/test/resources/betamax/tapes/{ssh_key_get_data_private.yaml => key_get_data_private.yaml} (54%) rename src/test/resources/betamax/tapes/{ssh_key_get_data_public.yaml => key_get_data_public.yaml} (88%) create mode 100644 src/test/resources/betamax/tapes/key_get_private.yaml rename src/test/resources/betamax/tapes/{ssh_key_get_public.yaml => key_get_public.yaml} (50%) create mode 100644 src/test/resources/betamax/tapes/key_list_directory.yaml create mode 100644 src/test/resources/betamax/tapes/key_list_root.yaml rename src/test/resources/betamax/tapes/{ssh_key_store_private.yaml => key_store_private.yaml} (50%) rename src/test/resources/betamax/tapes/{ssh_key_store_public.yaml => key_store_public.yaml} (53%) delete mode 100644 src/test/resources/betamax/tapes/ssh_key_get_private.yaml delete mode 100644 src/test/resources/betamax/tapes/ssh_key_list_directory.yaml delete mode 100644 src/test/resources/betamax/tapes/ssh_key_list_root.yaml diff --git a/src/main/java/org/rundeck/api/RundeckClient.java b/src/main/java/org/rundeck/api/RundeckClient.java index b0baa4c..8462dba 100644 --- a/src/main/java/org/rundeck/api/RundeckClient.java +++ b/src/main/java/org/rundeck/api/RundeckClient.java @@ -82,7 +82,7 @@ public class RundeckClient implements Serializable { private static final long serialVersionUID = 1L; public static final String JOBS_IMPORT = "/jobs/import"; public static final String STORAGE_ROOT_PATH = "/storage/"; - public static final String SSH_KEY_PATH = "ssh-key/"; + public static final String STORAGE_KEYS_PATH = "keys/"; /** * Supported version numbers @@ -2375,18 +2375,18 @@ public class RundeckClient implements Serializable { } /** - * Store an SSH key file - * @param path ssh key storage path, must start with "ssh-key/" + * Store an key file + * @param path ssh key storage path, must start with "keys/" * @param keyfile key file * @param privateKey true to store private key, false to store public key - * @return the SSH key resource + * @return the key resource * @throws RundeckApiException */ - public SSHKeyResource storeSshKey(final String path, final File keyfile, boolean privateKey) throws RundeckApiException{ - AssertUtil.notNull(path, "path is mandatory to store an SSH key."); - AssertUtil.notNull(keyfile, "keyfile is mandatory to store an SSH key."); - if (!path.startsWith(SSH_KEY_PATH)) { - throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + public KeyResource storeKey(final String path, final File keyfile, boolean privateKey) throws RundeckApiException{ + AssertUtil.notNull(path, "path is mandatory to store an key."); + AssertUtil.notNull(keyfile, "keyfile is mandatory to store an key."); + if (!path.startsWith(STORAGE_KEYS_PATH)) { + throw new IllegalArgumentException("key storage path must start with: " + STORAGE_KEYS_PATH); } return new ApiCall(this).post( new ApiPathBuilder(STORAGE_ROOT_PATH, path).content( @@ -2398,42 +2398,42 @@ public class RundeckClient implements Serializable { } /** - * Get metadata for an SSH key file + * Get metadata for an key file * - * @param path ssh key storage path, must start with "ssh-key/" + * @param path ssh key storage path, must start with "keys/" * * @return the ssh key resource * * @throws RundeckApiException if there is an error, or if the path is a directory not a file */ - public SSHKeyResource getSshKey(final String path) throws RundeckApiException { - AssertUtil.notNull(path, "path is mandatory to get an SSH key."); - if (!path.startsWith(SSH_KEY_PATH)) { - throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + public KeyResource getKey(final String path) throws RundeckApiException { + AssertUtil.notNull(path, "path is mandatory to get an key."); + if (!path.startsWith(STORAGE_KEYS_PATH)) { + throw new IllegalArgumentException("key storage path must start with: " + STORAGE_KEYS_PATH); } - SSHKeyResource storageResource = new ApiCall(this).get( + KeyResource storageResource = new ApiCall(this).get( new ApiPathBuilder(STORAGE_ROOT_PATH, path), new SSHKeyResourceParser("/resource") ); if (storageResource.isDirectory()) { - throw new RundeckApiException("SSH Key Path is a directory: " + path); + throw new RundeckApiException("Key Path is a directory: " + path); } return storageResource; } /** - * Get content for a public SSH key file - * @param path ssh key storage path, must start with "ssh-key/" + * Get content for a public key file + * @param path ssh key storage path, must start with "keys/" * @param out outputstream to write data to * * @return length of written data * @throws RundeckApiException */ - public int getPublicSshKeyContent(final String path, final OutputStream out) throws + public int getPublicKeyContent(final String path, final OutputStream out) throws RundeckApiException, IOException { - AssertUtil.notNull(path, "path is mandatory to get an SSH key."); - if (!path.startsWith(SSH_KEY_PATH)) { - throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + AssertUtil.notNull(path, "path is mandatory to get an key."); + if (!path.startsWith(STORAGE_KEYS_PATH)) { + throw new IllegalArgumentException("key storage path must start with: " + STORAGE_KEYS_PATH); } try { return new ApiCall(this).get( @@ -2443,66 +2443,66 @@ public class RundeckClient implements Serializable { out ); } catch (RundeckApiException.RundeckApiHttpContentTypeException e) { - throw new RundeckApiException("Requested SSH Key path was not a Public key: " + path, e); + throw new RundeckApiException("Requested Key path was not a Public key: " + path, e); } } /** - * Get content for a public SSH key file - * @param path ssh key storage path, must start with "ssh-key/" + * Get content for a public key file + * @param path ssh key storage path, must start with "keys/" * @param out file to write data to * @return length of written data * @throws RundeckApiException */ - public int getPublicSshKeyContent(final String path, final File out) throws + public int getPublicKeyContent(final String path, final File out) throws RundeckApiException, IOException { final FileOutputStream fileOutputStream = new FileOutputStream(out); try { - return getPublicSshKeyContent(path, fileOutputStream); + return getPublicKeyContent(path, fileOutputStream); }finally { fileOutputStream.close(); } } /** - * List contents of root SSH key directory + * List contents of root key directory * - * @return list of SSH key resources + * @return list of key resources * @throws RundeckApiException */ - public List listSshKeyDirectoryRoot() throws RundeckApiException { - return listSshKeyDirectory(SSH_KEY_PATH); + public List listKeyDirectoryRoot() throws RundeckApiException { + return listKeyDirectory(STORAGE_KEYS_PATH); } /** - * List contents of SSH key directory + * List contents of key directory * - * @param path ssh key storage path, must start with "ssh-key/" + * @param path ssh key storage path, must start with "keys/" * * @throws RundeckApiException if there is an error, or if the path is a file not a directory */ - public List listSshKeyDirectory(final String path) throws RundeckApiException { - AssertUtil.notNull(path, "path is mandatory to get an SSH key."); - if (!path.startsWith(SSH_KEY_PATH)) { - throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + public List listKeyDirectory(final String path) throws RundeckApiException { + AssertUtil.notNull(path, "path is mandatory to get an key."); + if (!path.startsWith(STORAGE_KEYS_PATH)) { + throw new IllegalArgumentException("key storage path must start with: " + STORAGE_KEYS_PATH); } - SSHKeyResource storageResource = new ApiCall(this).get( + KeyResource storageResource = new ApiCall(this).get( new ApiPathBuilder(STORAGE_ROOT_PATH, path), new SSHKeyResourceParser("/resource") ); if(!storageResource.isDirectory()) { - throw new RundeckApiException("SSH key path is not a directory path: " + path); + throw new RundeckApiException("key path is not a directory path: " + path); } return storageResource.getDirectoryContents(); } /** - * Delete an SSH key file - * @param path a path to a SSH key file, must start with "ssh-key/" + * Delete an key file + * @param path a path to a key file, must start with "keys/" */ - public void deleteSshKey(final String path){ - AssertUtil.notNull(path, "path is mandatory to delete an SSH key."); - if (!path.startsWith(SSH_KEY_PATH)) { - throw new IllegalArgumentException("SSH key storage path must start with: " + SSH_KEY_PATH); + public void deleteKey(final String path){ + AssertUtil.notNull(path, "path is mandatory to delete an key."); + if (!path.startsWith(STORAGE_KEYS_PATH)) { + throw new IllegalArgumentException("key storage path must start with: " + STORAGE_KEYS_PATH); } new ApiCall(this).delete(new ApiPathBuilder(STORAGE_ROOT_PATH, path)); } diff --git a/src/main/java/org/rundeck/api/domain/BaseSSHKeyResource.java b/src/main/java/org/rundeck/api/domain/BaseKeyResource.java similarity index 65% rename from src/main/java/org/rundeck/api/domain/BaseSSHKeyResource.java rename to src/main/java/org/rundeck/api/domain/BaseKeyResource.java index b47d878..c1ed3b7 100644 --- a/src/main/java/org/rundeck/api/domain/BaseSSHKeyResource.java +++ b/src/main/java/org/rundeck/api/domain/BaseKeyResource.java @@ -1,21 +1,18 @@ package org.rundeck.api.domain; -import org.rundeck.api.RundeckClient; -import org.rundeck.api.parser.StorageResourceParser; - import java.util.ArrayList; import java.util.List; /** - * BaseSSHKeyResource is ... + * BaseKeyResource is ... * * @author Greg Schueler * @since 2014-04-04 */ -public class BaseSSHKeyResource extends BaseStorageResource implements SSHKeyResource { +public class BaseKeyResource extends BaseStorageResource implements KeyResource { private boolean privateKey; - public BaseSSHKeyResource() { + public BaseKeyResource() { } @@ -27,22 +24,22 @@ public class BaseSSHKeyResource extends BaseStorageResource implements SSHKeyRes this.privateKey = privateKey; } - ArrayList sshKeyResources = new ArrayList(); + ArrayList keyResources = new ArrayList(); @Override public void setDirectoryContents(List directoryContents) { for (StorageResource directoryContent : directoryContents) { - sshKeyResources.add(from(directoryContent)); + keyResources.add(from(directoryContent)); } } @Override - public List getDirectoryContents() { - return sshKeyResources; + public List getDirectoryContents() { + return keyResources; } - public static BaseSSHKeyResource from(final StorageResource source) { - final BaseSSHKeyResource baseSshKeyResource = new BaseSSHKeyResource(); + public static BaseKeyResource from(final StorageResource source) { + final BaseKeyResource baseSshKeyResource = new BaseKeyResource(); baseSshKeyResource.setDirectory(source.isDirectory()); baseSshKeyResource.setPath(source.getPath()); baseSshKeyResource.setName(source.getName()); @@ -51,7 +48,7 @@ public class BaseSSHKeyResource extends BaseStorageResource implements SSHKeyRes if (!baseSshKeyResource.isDirectory()) { baseSshKeyResource.setPrivateKey( null != baseSshKeyResource.getMetadata() && "private".equals(baseSshKeyResource.getMetadata().get - ("Rundeck-ssh-key-type")) + ("Rundeck-key-type")) ); } else if (null != source.getDirectoryContents()) { baseSshKeyResource.setDirectoryContents(source.getDirectoryContents()); diff --git a/src/main/java/org/rundeck/api/domain/SSHKeyResource.java b/src/main/java/org/rundeck/api/domain/KeyResource.java similarity index 68% rename from src/main/java/org/rundeck/api/domain/SSHKeyResource.java rename to src/main/java/org/rundeck/api/domain/KeyResource.java index 33bbbd5..ff1f7e1 100644 --- a/src/main/java/org/rundeck/api/domain/SSHKeyResource.java +++ b/src/main/java/org/rundeck/api/domain/KeyResource.java @@ -3,12 +3,12 @@ package org.rundeck.api.domain; import java.util.List; /** - * SSHKeyResource represents a directory or an SSH key file + * KeyResource represents a directory or an SSH key file * * @author Greg Schueler * @since 2014-04-04 */ -public interface SSHKeyResource extends StorageResource { +public interface KeyResource extends StorageResource { /** * Return true if this is a file and is a private SSH key file. * @return @@ -19,5 +19,5 @@ public interface SSHKeyResource extends StorageResource { * Return the list of SSH Key resources if this is a directory * @return */ - public List getDirectoryContents(); + public List getDirectoryContents(); } diff --git a/src/main/java/org/rundeck/api/parser/SSHKeyResourceParser.java b/src/main/java/org/rundeck/api/parser/SSHKeyResourceParser.java index 5485d31..091ab2b 100644 --- a/src/main/java/org/rundeck/api/parser/SSHKeyResourceParser.java +++ b/src/main/java/org/rundeck/api/parser/SSHKeyResourceParser.java @@ -1,9 +1,8 @@ package org.rundeck.api.parser; import org.dom4j.Node; -import org.rundeck.api.RundeckClient; -import org.rundeck.api.domain.BaseSSHKeyResource; -import org.rundeck.api.domain.SSHKeyResource; +import org.rundeck.api.domain.BaseKeyResource; +import org.rundeck.api.domain.KeyResource; /** * SSHKeyResourceParser is ... @@ -11,7 +10,7 @@ import org.rundeck.api.domain.SSHKeyResource; * @author Greg Schueler * @since 2014-04-04 */ -public class SSHKeyResourceParser extends BaseXpathParser implements XmlNodeParser { +public class SSHKeyResourceParser extends BaseXpathParser implements XmlNodeParser { public SSHKeyResourceParser() { } @@ -20,7 +19,7 @@ public class SSHKeyResourceParser extends BaseXpathParser implem } @Override - public SSHKeyResource parse(Node node) { - return BaseSSHKeyResource.from(new StorageResourceParser().parse(node)); + public KeyResource parse(Node node) { + return BaseKeyResource.from(new StorageResourceParser().parse(node)); } } diff --git a/src/test/java/org/rundeck/api/RundeckClientTest.java b/src/test/java/org/rundeck/api/RundeckClientTest.java index f989e69..fb01b80 100644 --- a/src/test/java/org/rundeck/api/RundeckClientTest.java +++ b/src/test/java/org/rundeck/api/RundeckClientTest.java @@ -1284,8 +1284,8 @@ public class RundeckClientTest { * Store ssh key */ @Test - @Betamax(tape = "ssh_key_store_private", mode = TapeMode.READ_ONLY) - public void storeSshKey_private() throws Exception { + @Betamax(tape = "key_store_private", mode = TapeMode.READ_ONLY) + public void storeKey_private() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); File temp = File.createTempFile("test-key", ".tmp"); temp.deleteOnExit(); @@ -1295,26 +1295,26 @@ public class RundeckClientTest { }finally { out.close(); } - SSHKeyResource storageResource = client.storeSshKey("ssh-key/test/example/file1.pem", temp, true); + KeyResource storageResource = client.storeKey("keys/test/example/file1.pem", temp, true); Assert.assertNotNull(storageResource); Assert.assertFalse(storageResource.isDirectory()); Assert.assertTrue(storageResource.isPrivateKey()); Assert.assertEquals("file1.pem", storageResource.getName()); - Assert.assertEquals("ssh-key/test/example/file1.pem", storageResource.getPath()); - Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file1.pem", + Assert.assertEquals("keys/test/example/file1.pem", storageResource.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/keys/test/example/file1.pem", storageResource.getUrl()); Assert.assertEquals(0, storageResource.getDirectoryContents().size()); Map metadata = storageResource.getMetadata(); Assert.assertNotNull(metadata); Assert.assertEquals("application/octet-stream", metadata.get("Rundeck-content-type")); - Assert.assertEquals("private", metadata.get("Rundeck-ssh-key-type")); + Assert.assertEquals("private", metadata.get("Rundeck-key-type")); } /** * Store ssh key */ @Test - @Betamax(tape = "ssh_key_store_public", mode = TapeMode.READ_ONLY) - public void storeSshKey_public() throws Exception { + @Betamax(tape = "key_store_public", mode = TapeMode.READ_ONLY) + public void storeKey_public() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); File temp = File.createTempFile("test-key", ".tmp"); temp.deleteOnExit(); @@ -1324,76 +1324,76 @@ public class RundeckClientTest { }finally { out.close(); } - SSHKeyResource storageResource = client.storeSshKey("ssh-key/test/example/file2.pub", temp, false); + KeyResource storageResource = client.storeKey("keys/test/example/file2.pub", temp, false); Assert.assertNotNull(storageResource); Assert.assertFalse(storageResource.isDirectory()); Assert.assertFalse(storageResource.isPrivateKey()); Assert.assertEquals("file2.pub", storageResource.getName()); - Assert.assertEquals("ssh-key/test/example/file2.pub", storageResource.getPath()); - Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file2.pub", + Assert.assertEquals("keys/test/example/file2.pub", storageResource.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/keys/test/example/file2.pub", storageResource.getUrl()); Assert.assertEquals(0, storageResource.getDirectoryContents().size()); Map metadata = storageResource.getMetadata(); Assert.assertNotNull(metadata); Assert.assertEquals("application/pgp-keys", metadata.get("Rundeck-content-type")); - Assert.assertEquals("public", metadata.get("Rundeck-ssh-key-type")); + Assert.assertEquals("public", metadata.get("Rundeck-key-type")); } /** * get ssh key */ @Test - @Betamax(tape = "ssh_key_get_public", mode = TapeMode.READ_ONLY) - public void getSshKey_public() throws Exception { + @Betamax(tape = "key_get_public", mode = TapeMode.READ_ONLY) + public void getKey_public() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); - SSHKeyResource storageResource = client.getSshKey("ssh-key/test/example/file2.pub"); + KeyResource storageResource = client.getKey("keys/test/example/file2.pub"); Assert.assertNotNull(storageResource); Assert.assertFalse(storageResource.isDirectory()); Assert.assertFalse(storageResource.isPrivateKey()); Assert.assertEquals("file2.pub", storageResource.getName()); - Assert.assertEquals("ssh-key/test/example/file2.pub", storageResource.getPath()); - Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file2.pub", + Assert.assertEquals("keys/test/example/file2.pub", storageResource.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/keys/test/example/file2.pub", storageResource.getUrl()); Assert.assertEquals(0, storageResource.getDirectoryContents().size()); Map metadata = storageResource.getMetadata(); Assert.assertNotNull(metadata); Assert.assertEquals("application/pgp-keys", metadata.get("Rundeck-content-type")); - Assert.assertEquals("public", metadata.get("Rundeck-ssh-key-type")); + Assert.assertEquals("public", metadata.get("Rundeck-key-type")); } /** * get ssh key */ @Test - @Betamax(tape = "ssh_key_get_private", mode = TapeMode.READ_ONLY) - public void getSshKey_private() throws Exception { + @Betamax(tape = "key_get_private", mode = TapeMode.READ_ONLY) + public void getKey_private() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); - SSHKeyResource storageResource = client.getSshKey("ssh-key/test/example/file1.pem"); + KeyResource storageResource = client.getKey("keys/test/example/file1.pem"); Assert.assertNotNull(storageResource); Assert.assertFalse(storageResource.isDirectory()); Assert.assertTrue(storageResource.isPrivateKey()); Assert.assertEquals("file1.pem", storageResource.getName()); - Assert.assertEquals("ssh-key/test/example/file1.pem", storageResource.getPath()); - Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file1.pem", + Assert.assertEquals("keys/test/example/file1.pem", storageResource.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/keys/test/example/file1.pem", storageResource.getUrl()); Assert.assertEquals(0, storageResource.getDirectoryContents().size()); Map metadata = storageResource.getMetadata(); Assert.assertNotNull(metadata); Assert.assertEquals("application/octet-stream", metadata.get("Rundeck-content-type")); - Assert.assertEquals("private", metadata.get("Rundeck-ssh-key-type")); + Assert.assertEquals("private", metadata.get("Rundeck-key-type")); } /** * get ssh key data */ @Test - @Betamax(tape = "ssh_key_get_data_private", mode = TapeMode.READ_ONLY) - public void getSshKeyData_private() throws Exception { + @Betamax(tape = "key_get_data_private", mode = TapeMode.READ_ONLY) + public void getKeyData_private() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); File temp = File.createTempFile("test-key", ".tmp"); temp.deleteOnExit(); try { - int data = client.getPublicSshKeyContent("ssh-key/test/example/file1.pem", temp); + int data = client.getPublicKeyContent("keys/test/example/file1.pem", temp); Assert.fail("expected failure"); } catch (RundeckApiException e) { - Assert.assertEquals("Requested SSH Key path was not a Public key: ssh-key/test/example/file1.pem", + Assert.assertEquals("Requested Key path was not a Public key: keys/test/example/file1.pem", e.getMessage()); } } @@ -1401,72 +1401,72 @@ public class RundeckClientTest { * get ssh key data */ @Test - @Betamax(tape = "ssh_key_get_data_public", mode = TapeMode.READ_ONLY) - public void getSshKeyData_public() throws Exception { + @Betamax(tape = "key_get_data_public", mode = TapeMode.READ_ONLY) + public void getKeyData_public() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); File temp = File.createTempFile("test-key", ".tmp"); temp.deleteOnExit(); - int length = client.getPublicSshKeyContent("ssh-key/test/example/file2.pub", temp); + int length = client.getPublicKeyContent("keys/test/example/file2.pub", temp); Assert.assertEquals(5, length); } /** * list directory */ @Test - @Betamax(tape = "ssh_key_list_directory", mode = TapeMode.READ_ONLY) - public void listSshKeyDirectory() throws Exception { + @Betamax(tape = "key_list_directory", mode = TapeMode.READ_ONLY) + public void listKeyDirectory() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); - List list = client.listSshKeyDirectory("ssh-key/test/example"); + List list = client.listKeyDirectory("keys/test/example"); Assert.assertEquals(2, list.size()); - SSHKeyResource storageResource1 = list.get(0); - SSHKeyResource storageResource2 = list.get(1); + KeyResource storageResource1 = list.get(0); + KeyResource storageResource2 = list.get(1); Assert.assertFalse(storageResource2.isDirectory()); Assert.assertTrue(storageResource2.isPrivateKey()); Assert.assertEquals("file1.pem", storageResource2.getName()); - Assert.assertEquals("ssh-key/test/example/file1.pem", storageResource2.getPath()); - Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file1.pem", storageResource2.getUrl()); + Assert.assertEquals("keys/test/example/file1.pem", storageResource2.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/keys/test/example/file1.pem", storageResource2.getUrl()); Assert.assertNotNull(storageResource2.getMetadata()); Assert.assertEquals("application/octet-stream", storageResource2.getMetadata().get("Rundeck-content-type")); - Assert.assertEquals("private", storageResource2.getMetadata().get("Rundeck-ssh-key-type")); + Assert.assertEquals("private", storageResource2.getMetadata().get("Rundeck-key-type")); Assert.assertFalse(storageResource1.isDirectory()); Assert.assertFalse(storageResource1.isPrivateKey()); Assert.assertEquals("file2.pub", storageResource1.getName()); - Assert.assertEquals("ssh-key/test/example/file2.pub", storageResource1.getPath()); - Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test/example/file2.pub", + Assert.assertEquals("keys/test/example/file2.pub", storageResource1.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/keys/test/example/file2.pub", storageResource1.getUrl()); Assert.assertNotNull(storageResource1.getMetadata()); Assert.assertEquals("application/pgp-keys", storageResource1.getMetadata().get("Rundeck-content-type")); - Assert.assertEquals("public", storageResource1.getMetadata().get("Rundeck-ssh-key-type")); + Assert.assertEquals("public", storageResource1.getMetadata().get("Rundeck-key-type")); } /** * list root */ @Test - @Betamax(tape = "ssh_key_list_root", mode = TapeMode.READ_ONLY) - public void listSshKeyDirectoryRoot() throws Exception { + @Betamax(tape = "key_list_root", mode = TapeMode.READ_ONLY) + public void listKeyDirectoryRoot() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); - List list = client.listSshKeyDirectoryRoot(); + List list = client.listKeyDirectoryRoot(); Assert.assertEquals(2, list.size()); - SSHKeyResource storageResource0 = list.get(0); - SSHKeyResource storageResource1 = list.get(1); + KeyResource storageResource0 = list.get(0); + KeyResource storageResource1 = list.get(1); Assert.assertFalse(storageResource0.isDirectory()); Assert.assertTrue(storageResource0.isPrivateKey()); Assert.assertEquals("test1.pem", storageResource0.getName()); - Assert.assertEquals("ssh-key/test1.pem", storageResource0.getPath()); - Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test1.pem", storageResource0.getUrl()); + Assert.assertEquals("keys/test1.pem", storageResource0.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/keys/test1.pem", storageResource0.getUrl()); Assert.assertNotNull(storageResource0.getMetadata()); Assert.assertEquals("application/octet-stream", storageResource0.getMetadata().get("Rundeck-content-type")); - Assert.assertEquals("private", storageResource0.getMetadata().get("Rundeck-ssh-key-type")); + Assert.assertEquals("private", storageResource0.getMetadata().get("Rundeck-key-type")); Assert.assertTrue(storageResource1.toString(), storageResource1.isDirectory()); Assert.assertEquals(null, storageResource1.getName()); - Assert.assertEquals("ssh-key/test", storageResource1.getPath()); - Assert.assertEquals("http://dignan.local:4440/api/11/storage/ssh-key/test", + Assert.assertEquals("keys/test", storageResource1.getPath()); + Assert.assertEquals("http://dignan.local:4440/api/11/storage/keys/test", storageResource1.getUrl()); Assert.assertNull(storageResource1.getMetadata()); @@ -1477,13 +1477,13 @@ public class RundeckClientTest { * delete ssh key */ @Test - @Betamax(tape = "ssh_key_delete", mode = TapeMode.READ_ONLY) - public void deleteSshKey() throws Exception { + @Betamax(tape = "key_delete", mode = TapeMode.READ_ONLY) + public void deleteKey() throws Exception { final RundeckClient client = createClient(TEST_TOKEN_7, 11); - client.deleteSshKey("ssh-key/test/example/file2.pub"); + client.deleteKey("keys/test/example/file2.pub"); try { - client.getSshKey("ssh-key/test/example/file2.pub"); + client.getKey("keys/test/example/file2.pub"); Assert.fail("expected failure"); } catch (RundeckApiException.RundeckApiHttpStatusException e) { Assert.assertEquals(404,e.getStatusCode()); diff --git a/src/test/resources/betamax/tapes/ssh_key_delete.yaml b/src/test/resources/betamax/tapes/key_delete.yaml similarity index 86% rename from src/test/resources/betamax/tapes/ssh_key_delete.yaml rename to src/test/resources/betamax/tapes/key_delete.yaml index fd46eae..b52ed7f 100644 --- a/src/test/resources/betamax/tapes/ssh_key_delete.yaml +++ b/src/test/resources/betamax/tapes/key_delete.yaml @@ -4,7 +4,7 @@ interactions: - recorded: 2014-04-04T23:16:02.256Z request: method: DELETE - uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + uri: http://rundeck.local:4440/api/11/storage/keys/test/example/file2.pub headers: Host: rundeck.local:4440 Proxy-Connection: Keep-Alive @@ -20,7 +20,7 @@ interactions: - recorded: 2014-04-04T23:16:02.372Z request: method: GET - uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + uri: http://rundeck.local:4440/api/11/storage/keys/test/example/file2.pub headers: Accept: text/xml Host: rundeck.local:4440 diff --git a/src/test/resources/betamax/tapes/ssh_key_get_data_private.yaml b/src/test/resources/betamax/tapes/key_get_data_private.yaml similarity index 54% rename from src/test/resources/betamax/tapes/ssh_key_get_data_private.yaml rename to src/test/resources/betamax/tapes/key_get_data_private.yaml index e5e742b..ef0f58f 100644 --- a/src/test/resources/betamax/tapes/ssh_key_get_data_private.yaml +++ b/src/test/resources/betamax/tapes/key_get_data_private.yaml @@ -4,7 +4,7 @@ interactions: - recorded: 2014-04-04T19:50:59.155Z request: method: GET - uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file1.pem + uri: http://rundeck.local:4440/api/11/storage/keys/test/example/file1.pem headers: Accept: application/pgp-keys Host: rundeck.local:4440 @@ -19,4 +19,5 @@ interactions: Server: Jetty(7.6.0.v20120127) Set-Cookie: JSESSIONID=1gzu37lkjr0fitxhf5fgkgsfu;Path=/ body: !!binary |- - eyJwYXRoIjoic3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTEucGVtIiwidHlwZSI6ImZpbGUiLCJuYW1lIjoiZmlsZTEucGVtIiwidXJsIjoiaHR0cDovL2RpZ25hbi5sb2NhbDo0NDQwL2FwaS8xMS9zdG9yYWdlL3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUxLnBlbSIsIm1ldGEiOnsiUnVuZGVjay1jb250ZW50LXR5cGUiOiJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iLCJSdW5kZWNrLWNvbnRlbnQtc2l6ZSI6IjUiLCJSdW5kZWNrLWNvbnRlbnQtbWFzayI6ImNvbnRlbnQiLCJSdW5kZWNrLXNzaC1rZXktdHlwZSI6InByaXZhdGUifX0= + eyJwYXRoIjoia2V5cy90ZXN0L2V4YW1wbGUvZmlsZTEucGVtIiwidHlwZSI6ImZpbGUiLCJuYW1lIjoiZmlsZTEucGVtIiwidXJsIjoiaHR0cDovL2RpZ25hbi5sb2NhbDo0NDQwL2FwaS8xMS9zdG9yYWdlL2tleXMvdGVzdC9leGFtcGxlL2ZpbGUxLnBlbSIsIm1ldGEiOnsiUnVuZGVjay1jb250ZW50LXR5cGUiOiJhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0iLCJSdW5kZWNrLWNvbnRlbnQtc2l6ZSI6IjUiLCJSdW5kZWNrLWNvbnRlbnQtbWFzayI6ImNvbnRlbnQiLCJSdW5kZWNrLWtleS10eXBlIjoicHJpdmF0ZSJ9fQo= + diff --git a/src/test/resources/betamax/tapes/ssh_key_get_data_public.yaml b/src/test/resources/betamax/tapes/key_get_data_public.yaml similarity index 88% rename from src/test/resources/betamax/tapes/ssh_key_get_data_public.yaml rename to src/test/resources/betamax/tapes/key_get_data_public.yaml index 1874e71..054599f 100644 --- a/src/test/resources/betamax/tapes/ssh_key_get_data_public.yaml +++ b/src/test/resources/betamax/tapes/key_get_data_public.yaml @@ -4,7 +4,7 @@ interactions: - recorded: 2014-04-04T20:20:44.331Z request: method: GET - uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + uri: http://rundeck.local:4440/api/11/storage/keys/test/example/file2.pub headers: Accept: application/pgp-keys Host: rundeck.local:4440 diff --git a/src/test/resources/betamax/tapes/key_get_private.yaml b/src/test/resources/betamax/tapes/key_get_private.yaml new file mode 100644 index 0000000..57802a8 --- /dev/null +++ b/src/test/resources/betamax/tapes/key_get_private.yaml @@ -0,0 +1,23 @@ +!tape +name: ssh_key_get_private +interactions: +- recorded: 2014-04-04T19:47:29.880Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/keys/test/example/file1.pem + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + 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=nc5p0he3nw19e4gegidc4bs7;Path=/ + body: !!binary |- + PHJlc291cmNlIHBhdGg9J2tleXMvdGVzdC9leGFtcGxlL2ZpbGUxLnBlbScgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uva2V5cy90ZXN0L2V4YW1wbGUvZmlsZTEucGVtJyBuYW1lPSdmaWxlMS5wZW0nPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9vY3RldC1zdHJlYW08L1J1bmRlY2stY29udGVudC10eXBlPjxSdW5kZWNrLWNvbnRlbnQtc2l6ZT41PC9SdW5kZWNrLWNvbnRlbnQtc2l6ZT48UnVuZGVjay1jb250ZW50LW1hc2s+Y29udGVudDwvUnVuZGVjay1jb250ZW50LW1hc2s+PFJ1bmRlY2sta2V5LXR5cGU+cHJpdmF0ZTwvUnVuZGVjay1rZXktdHlwZT48L3Jlc291cmNlLW1ldGE+PC9yZXNvdXJjZT4K + diff --git a/src/test/resources/betamax/tapes/ssh_key_get_public.yaml b/src/test/resources/betamax/tapes/key_get_public.yaml similarity index 50% rename from src/test/resources/betamax/tapes/ssh_key_get_public.yaml rename to src/test/resources/betamax/tapes/key_get_public.yaml index 8bede8d..32c2510 100644 --- a/src/test/resources/betamax/tapes/ssh_key_get_public.yaml +++ b/src/test/resources/betamax/tapes/key_get_public.yaml @@ -4,7 +4,7 @@ interactions: - recorded: 2014-04-04T19:47:29.626Z request: method: GET - uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + uri: http://rundeck.local:4440/api/11/storage/keys/test/example/file2.pub headers: Accept: text/xml Host: rundeck.local:4440 @@ -19,4 +19,5 @@ interactions: Server: Jetty(7.6.0.v20120127) Set-Cookie: JSESSIONID=r6p6fl87ftrb1mkwwi0pcak5i;Path=/ body: !!binary |- - PHJlc291cmNlIHBhdGg9J3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUyLnB1YicgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uvc3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTIucHViJyBuYW1lPSdmaWxlMi5wdWInPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9wZ3Ata2V5czwvUnVuZGVjay1jb250ZW50LXR5cGU+PFJ1bmRlY2stY29udGVudC1zaXplPjU8L1J1bmRlY2stY29udGVudC1zaXplPjxSdW5kZWNrLXNzaC1rZXktdHlwZT5wdWJsaWM8L1J1bmRlY2stc3NoLWtleS10eXBlPjwvcmVzb3VyY2UtbWV0YT48L3Jlc291cmNlPg== + PHJlc291cmNlIHBhdGg9J2tleXMvdGVzdC9leGFtcGxlL2ZpbGUyLnB1YicgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uva2V5cy90ZXN0L2V4YW1wbGUvZmlsZTIucHViJyBuYW1lPSdmaWxlMi5wdWInPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9wZ3Ata2V5czwvUnVuZGVjay1jb250ZW50LXR5cGU+PFJ1bmRlY2stY29udGVudC1zaXplPjU8L1J1bmRlY2stY29udGVudC1zaXplPjxSdW5kZWNrLWtleS10eXBlPnB1YmxpYzwvUnVuZGVjay1rZXktdHlwZT48L3Jlc291cmNlLW1ldGE+PC9yZXNvdXJjZT4N + diff --git a/src/test/resources/betamax/tapes/key_list_directory.yaml b/src/test/resources/betamax/tapes/key_list_directory.yaml new file mode 100644 index 0000000..fb4c6f6 --- /dev/null +++ b/src/test/resources/betamax/tapes/key_list_directory.yaml @@ -0,0 +1,21 @@ +!tape +name: key_list_directory +interactions: +- recorded: 2014-04-04T20:33:07.968Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/keys/test/example + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + response: + status: 200 + 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=1stwtoatsosy91ca0gcrzde698;Path=/ + body: application/pgp-keys5publicapplication/octet-stream5contentprivate diff --git a/src/test/resources/betamax/tapes/key_list_root.yaml b/src/test/resources/betamax/tapes/key_list_root.yaml new file mode 100644 index 0000000..b040c3e --- /dev/null +++ b/src/test/resources/betamax/tapes/key_list_root.yaml @@ -0,0 +1,21 @@ +!tape +name: key_list_root +interactions: +- recorded: 2014-04-04T20:41:16.501Z + request: + method: GET + uri: http://rundeck.local:4440/api/11/storage/keys/ + headers: + Accept: text/xml + Host: rundeck.local:4440 + Proxy-Connection: Keep-Alive + User-Agent: RunDeck API Java Client 11 + X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s + response: + status: 200 + 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=5sj36vytg4y1182mziim4868b;Path=/ + body: application/octet-stream1679contentprivate diff --git a/src/test/resources/betamax/tapes/ssh_key_store_private.yaml b/src/test/resources/betamax/tapes/key_store_private.yaml similarity index 50% rename from src/test/resources/betamax/tapes/ssh_key_store_private.yaml rename to src/test/resources/betamax/tapes/key_store_private.yaml index 6ef2def..1a5d0b0 100644 --- a/src/test/resources/betamax/tapes/ssh_key_store_private.yaml +++ b/src/test/resources/betamax/tapes/key_store_private.yaml @@ -4,7 +4,7 @@ interactions: - recorded: 2014-04-04T19:30:35.367Z request: method: POST - uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file1.pem + uri: http://rundeck.local:4440/api/11/storage/keys/test/example/file1.pem headers: Accept: text/xml Content-Length: '5' @@ -22,4 +22,5 @@ interactions: Server: Jetty(7.6.0.v20120127) Set-Cookie: JSESSIONID=ktmwc4h53xfud6v2ch67x5p9;Path=/ body: !!binary |- - PHJlc291cmNlIHBhdGg9J3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUxLnBlbScgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uvc3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTEucGVtJyBuYW1lPSdmaWxlMS5wZW0nPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9vY3RldC1zdHJlYW08L1J1bmRlY2stY29udGVudC10eXBlPjxSdW5kZWNrLWNvbnRlbnQtc2l6ZT41PC9SdW5kZWNrLWNvbnRlbnQtc2l6ZT48UnVuZGVjay1jb250ZW50LW1hc2s+Y29udGVudDwvUnVuZGVjay1jb250ZW50LW1hc2s+PFJ1bmRlY2stc3NoLWtleS10eXBlPnByaXZhdGU8L1J1bmRlY2stc3NoLWtleS10eXBlPjwvcmVzb3VyY2UtbWV0YT48L3Jlc291cmNlPg== + PHJlc291cmNlIHBhdGg9J2tleXMvdGVzdC9leGFtcGxlL2ZpbGUxLnBlbScgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uva2V5cy90ZXN0L2V4YW1wbGUvZmlsZTEucGVtJyBuYW1lPSdmaWxlMS5wZW0nPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9vY3RldC1zdHJlYW08L1J1bmRlY2stY29udGVudC10eXBlPjxSdW5kZWNrLWNvbnRlbnQtc2l6ZT41PC9SdW5kZWNrLWNvbnRlbnQtc2l6ZT48UnVuZGVjay1jb250ZW50LW1hc2s+Y29udGVudDwvUnVuZGVjay1jb250ZW50LW1hc2s+PFJ1bmRlY2sta2V5LXR5cGU+cHJpdmF0ZTwvUnVuZGVjay1rZXktdHlwZT48L3Jlc291cmNlLW1ldGE+PC9yZXNvdXJjZT4N + diff --git a/src/test/resources/betamax/tapes/ssh_key_store_public.yaml b/src/test/resources/betamax/tapes/key_store_public.yaml similarity index 53% rename from src/test/resources/betamax/tapes/ssh_key_store_public.yaml rename to src/test/resources/betamax/tapes/key_store_public.yaml index 8a1b18a..fdd7638 100644 --- a/src/test/resources/betamax/tapes/ssh_key_store_public.yaml +++ b/src/test/resources/betamax/tapes/key_store_public.yaml @@ -4,7 +4,7 @@ interactions: - recorded: 2014-04-04T19:34:02.683Z request: method: POST - uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file2.pub + uri: http://rundeck.local:4440/api/11/storage/keys/test/example/file2.pub headers: Accept: text/xml Content-Length: '5' @@ -22,4 +22,5 @@ interactions: Server: Jetty(7.6.0.v20120127) Set-Cookie: JSESSIONID=2l3g8m0tvwef19jn2bu23bzk6;Path=/ body: !!binary |- - PHJlc291cmNlIHBhdGg9J3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUyLnB1YicgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uvc3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTIucHViJyBuYW1lPSdmaWxlMi5wdWInPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9wZ3Ata2V5czwvUnVuZGVjay1jb250ZW50LXR5cGU+PFJ1bmRlY2stY29udGVudC1zaXplPjU8L1J1bmRlY2stY29udGVudC1zaXplPjxSdW5kZWNrLXNzaC1rZXktdHlwZT5wdWJsaWM8L1J1bmRlY2stc3NoLWtleS10eXBlPjwvcmVzb3VyY2UtbWV0YT48L3Jlc291cmNlPg== + PHJlc291cmNlIHBhdGg9J2tleXMvdGVzdC9leGFtcGxlL2ZpbGUyLnB1YicgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uva2V5cy90ZXN0L2V4YW1wbGUvZmlsZTIucHViJyBuYW1lPSdmaWxlMi5wdWInPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9wZ3Ata2V5czwvUnVuZGVjay1jb250ZW50LXR5cGU+PFJ1bmRlY2stY29udGVudC1zaXplPjU8L1J1bmRlY2stY29udGVudC1zaXplPjxSdW5kZWNrLWtleS10eXBlPnB1YmxpYzwvUnVuZGVjay1rZXktdHlwZT48L3Jlc291cmNlLW1ldGE+PC9yZXNvdXJjZT4K + diff --git a/src/test/resources/betamax/tapes/ssh_key_get_private.yaml b/src/test/resources/betamax/tapes/ssh_key_get_private.yaml deleted file mode 100644 index fa0ff2c..0000000 --- a/src/test/resources/betamax/tapes/ssh_key_get_private.yaml +++ /dev/null @@ -1,22 +0,0 @@ -!tape -name: ssh_key_get_private -interactions: -- recorded: 2014-04-04T19:47:29.880Z - request: - method: GET - uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example/file1.pem - headers: - Accept: text/xml - Host: rundeck.local:4440 - Proxy-Connection: Keep-Alive - User-Agent: RunDeck API Java Client 11 - X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s - 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=nc5p0he3nw19e4gegidc4bs7;Path=/ - body: !!binary |- - PHJlc291cmNlIHBhdGg9J3NzaC1rZXkvdGVzdC9leGFtcGxlL2ZpbGUxLnBlbScgdHlwZT0nZmlsZScgdXJsPSdodHRwOi8vZGlnbmFuLmxvY2FsOjQ0NDAvYXBpLzExL3N0b3JhZ2Uvc3NoLWtleS90ZXN0L2V4YW1wbGUvZmlsZTEucGVtJyBuYW1lPSdmaWxlMS5wZW0nPjxyZXNvdXJjZS1tZXRhPjxSdW5kZWNrLWNvbnRlbnQtdHlwZT5hcHBsaWNhdGlvbi9vY3RldC1zdHJlYW08L1J1bmRlY2stY29udGVudC10eXBlPjxSdW5kZWNrLWNvbnRlbnQtc2l6ZT41PC9SdW5kZWNrLWNvbnRlbnQtc2l6ZT48UnVuZGVjay1jb250ZW50LW1hc2s+Y29udGVudDwvUnVuZGVjay1jb250ZW50LW1hc2s+PFJ1bmRlY2stc3NoLWtleS10eXBlPnByaXZhdGU8L1J1bmRlY2stc3NoLWtleS10eXBlPjwvcmVzb3VyY2UtbWV0YT48L3Jlc291cmNlPg== diff --git a/src/test/resources/betamax/tapes/ssh_key_list_directory.yaml b/src/test/resources/betamax/tapes/ssh_key_list_directory.yaml deleted file mode 100644 index e49f941..0000000 --- a/src/test/resources/betamax/tapes/ssh_key_list_directory.yaml +++ /dev/null @@ -1,21 +0,0 @@ -!tape -name: ssh_key_list_directory -interactions: -- recorded: 2014-04-04T20:33:07.968Z - request: - method: GET - uri: http://rundeck.local:4440/api/11/storage/ssh-key/test/example - headers: - Accept: text/xml - Host: rundeck.local:4440 - Proxy-Connection: Keep-Alive - User-Agent: RunDeck API Java Client 11 - X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s - response: - status: 200 - 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=1stwtoatsosy91ca0gcrzde698;Path=/ - body: application/pgp-keys5publicapplication/octet-stream5contentprivate diff --git a/src/test/resources/betamax/tapes/ssh_key_list_root.yaml b/src/test/resources/betamax/tapes/ssh_key_list_root.yaml deleted file mode 100644 index dcefe5e..0000000 --- a/src/test/resources/betamax/tapes/ssh_key_list_root.yaml +++ /dev/null @@ -1,21 +0,0 @@ -!tape -name: ssh_key_list_root -interactions: -- recorded: 2014-04-04T20:41:16.501Z - request: - method: GET - uri: http://rundeck.local:4440/api/11/storage/ssh-key/ - headers: - Accept: text/xml - Host: rundeck.local:4440 - Proxy-Connection: Keep-Alive - User-Agent: RunDeck API Java Client 11 - X-RunDeck-Auth-Token: 8Dp9op111ER6opsDRkddvE86K9sE499s - response: - status: 200 - 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=5sj36vytg4y1182mziim4868b;Path=/ - body: application/octet-stream1679contentprivate From 0191bd34ff63d722271170b632241006168432af Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Wed, 23 Apr 2014 16:37:11 -0700 Subject: [PATCH 23/24] Update status and changes docs --- src/changes/changes.xml | 16 ++++++++++++++++ src/site/confluence/status.confluence | 8 ++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index ba52cb2..825c4ca 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -22,6 +22,22 @@ Vincent Behar + + Project creation + Get Project configuration + Set Project configuration + Get/Set Project configuration keys + Delete project + Export project archive + Import project archive + Key file upload + Key file delete + Key file list + Key file get + API Token create + API Token list + API Token delete + Execution State - Retrieve workflow step and node state information diff --git a/src/site/confluence/status.confluence b/src/site/confluence/status.confluence index 3042fa8..39a2185 100644 --- a/src/site/confluence/status.confluence +++ b/src/site/confluence/status.confluence @@ -106,10 +106,10 @@ h2. RunDeck API version 11 * Delete project - OK * Export project archive - OK * Import project archive - OK -* SSH Key upload - OK -* SSH Key delete - OK -* SSH Key list - OK -* SSH Key get - OK +* Key file upload - OK +* Key file delete - OK +* Key file list - OK +* Key file get - OK * API Token create - OK * API Token list - OK * API Token delete - OK From 7146aca1297fcb1f4d186b35d02800df56185ce7 Mon Sep 17 00:00:00 2001 From: Greg Schueler Date: Wed, 23 Apr 2014 17:51:51 -0700 Subject: [PATCH 24/24] Update version to 11.0-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d284be1..eebf968 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ org.rundeck rundeck-api-java-client - 10.1-SNAPSHOT + 11.0-SNAPSHOT jar RunDeck API - Java Client Java client for the RunDeck REST API