From f5b043a105405948f564f887565bc5dae44a3b67 Mon Sep 17 00:00:00 2001 From: James Nord Date: Wed, 19 Jun 2013 14:08:28 +0100 Subject: [PATCH] Update staging to work with Nexus 2.4+ Nexus 2.4 introduced aynchronous notifications for staging in a non backwards compatable way. This adds support for the new way but uses does not enable it if the server version is < 2.4. Change-Id: I168214d824be094c7bcfac50b29d4dc6e014fd67 --- .gitignore | 2 + pom.xml | 33 +- .../m2release/M2ReleaseBuildWrapper.java | 87 +-- .../hudson/plugins/m2release/nexus/Stage.java | 103 +++- .../plugins/m2release/nexus/StageAction.java | 12 +- .../plugins/m2release/nexus/StageClient.java | 505 +++++++++++++----- .../m2release/nexus/StageException.java | 4 - ...ctConfig-numberOfReleaseBuildsToKeep.html} | 0 .../m2release/nexus/StageClientTest.java | 451 ++++++++++++++++ .../plugins/m2release/nexus/StageTest.java | 95 ++-- .../activity__closed_failed.xml | 396 ++++++++++++++ .../stageClientTest/activity__closed_ok.xml | 491 +++++++++++++++++ .../stageClientTest/profile_repositories.xml | 97 ++++ .../repository__transitioned.xml | 24 + .../repository__transitioning.xml | 24 + .../stageClientTest/status__bad_perms.xml | 374 +++++++++++++ .../stageClientTest/status__ok_perms.xml | 377 +++++++++++++ 17 files changed, 2821 insertions(+), 254 deletions(-) rename src/main/webapp/{help-projectConfig-help-numberOfReleaseBuildsToKeep.html => help-projectConfig-numberOfReleaseBuildsToKeep.html} (100%) create mode 100644 src/test/java/org/jvnet/hudson/plugins/m2release/nexus/StageClientTest.java create mode 100644 src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/activity__closed_failed.xml create mode 100644 src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/activity__closed_ok.xml create mode 100644 src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/profile_repositories.xml create mode 100644 src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/repository__transitioned.xml create mode 100644 src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/repository__transitioning.xml create mode 100644 src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/status__bad_perms.xml create mode 100644 src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/status__ok_perms.xml diff --git a/.gitignore b/.gitignore index bb0e15d..71ec356 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ bin # Build output target /work + +.clover \ No newline at end of file diff --git a/pom.xml b/pom.xml index 09c6640..6fcabf5 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,6 @@ org.jenkins-ci.main maven-plugin - compile org.apache.maven.release @@ -68,20 +67,30 @@ - - org.jenkins-ci.plugins - dashboard-view - 2.0 - true - - org.apache.maven maven-core diff --git a/src/main/java/org/jvnet/hudson/plugins/m2release/M2ReleaseBuildWrapper.java b/src/main/java/org/jvnet/hudson/plugins/m2release/M2ReleaseBuildWrapper.java index 3f02be7..c5a2b86 100644 --- a/src/main/java/org/jvnet/hudson/plugins/m2release/M2ReleaseBuildWrapper.java +++ b/src/main/java/org/jvnet/hudson/plugins/m2release/M2ReleaseBuildWrapper.java @@ -195,38 +195,6 @@ public boolean tearDown(@SuppressWarnings("rawtypes") AbstractBuild bld, BuildLi lstnr.getLogger().println("[M2Release] its only a dryRun, no need to mark it for keep"); } - int buildsKept = 0; - if (bld.getResult() != null && bld.getResult().isBetterOrEqualTo(Result.SUCCESS) && !args.isDryRun()) { - if (numberOfReleaseBuildsToKeep > 0 || numberOfReleaseBuildsToKeep == -1) { - // keep this build. - lstnr.getLogger().println("[M2Release] assigning keep build to current build."); - bld.keepLog(); - buildsKept++; - } - // the value may have changed since a previous release so go searching... - - for (Run run : (RunList) (bld.getProject().getBuilds())) { - if (isSuccessfulReleaseBuild(run)) { - if (bld.getNumber() != run.getNumber()) { // not sure we still need this check.. - if (shouldKeepBuildNumber(numberOfReleaseBuildsToKeep, buildsKept)) { - if (!run.isKeepLog()) { - lstnr.getLogger().println( - "[M2Release] assigning keep build to build " + run.getNumber()); - run.keepLog(true); - } - } - else { - if (run.isKeepLog()) { - lstnr.getLogger().println( - "[M2Release] removing keep build from build " + run.getNumber()); - run.keepLog(false); - } - } - } - } - } - } - if (args.isCloseNexusStage() && !args.isDryRun()) { StageClient client = new StageClient(new URL(getDescriptor().getNexusURL()), getDescriptor() .getNexusUser(), getDescriptor().getNexusPassword()); @@ -243,7 +211,7 @@ public boolean tearDown(@SuppressWarnings("rawtypes") AbstractBuild bld, BuildLi } else { lstnr.getLogger().println("[M2Release] Dropping repository " + stage); - client.closeStage(stage, args.getRepoDescription()); + client.dropStage(stage); lstnr.getLogger().println("[M2Release] Dropped staging repository."); } } @@ -253,11 +221,51 @@ public boolean tearDown(@SuppressWarnings("rawtypes") AbstractBuild bld, BuildLi } } catch (StageException ex) { - lstnr.fatalError("[M2Release] Could not close repository , %s\n", ex.toString()); + lstnr.fatalError("[M2Release] Could not close repository , %1$s\n", ex.getMessage()); + ex.printStackTrace(lstnr.getLogger()); log.error("[M2Release] Could not close repository", ex); retVal = false; } } + int buildsKept = 0; + if (bld.getResult() != null && bld.getResult().isBetterOrEqualTo(Result.SUCCESS) && !args.isDryRun()) { + if (numberOfReleaseBuildsToKeep > 0 || numberOfReleaseBuildsToKeep == -1) { + // keep this build. + lstnr.getLogger().println("[M2Release] assigning keep build to current build."); + bld.keepLog(); + buildsKept++; + } + + // the value may have changed since a previous release so go searching... + log.debug("looking for extra release builds to lock/unlock."); + for (Run run : (RunList) (bld.getProject().getBuilds())) { + log.debug("checking build #{}", run.getNumber()); + if (isSuccessfulReleaseBuild(run)) { + log.debug("build #{} was successful.", run.getNumber()); + if (bld.getNumber() != run.getNumber()) { // not sure we still need this check.. + if (shouldKeepBuildNumber(numberOfReleaseBuildsToKeep, buildsKept)) { + buildsKept++; + if (!run.isKeepLog()) { + lstnr.getLogger().println( + "[M2Release] assigning keep build to build " + run.getNumber()); + run.keepLog(true); + } + } + else { + if (run.isKeepLog()) { + lstnr.getLogger().println( + "[M2Release] removing keep build from build " + run.getNumber()); + run.keepLog(false); + } + } + } + } + else { + log.debug("build #{} was NOT successful release build.", run.getNumber()); + } + } + } + return retVal; } @@ -478,6 +486,9 @@ public static class DescriptorImpl extends BuildWrapperDescriptor { public DescriptorImpl() { super(M2ReleaseBuildWrapper.class); load(); + if (nexusURL != null && !nexusURL.endsWith("/")) { + nexusURL = nexusURL + "/"; + } } @@ -492,8 +503,8 @@ public boolean configure(StaplerRequest staplerRequest, JSONObject json) throws if (nexusSupport) { JSONObject nexusParams = json.getJSONObject("nexusSupport"); //$NON-NLS-1$ nexusURL = Util.fixEmpty(nexusParams.getString("nexusURL")); //$NON-NLS-1$ - if (nexusURL != null && nexusURL.endsWith("/")) { //$NON-NLS-1$ - nexusURL = nexusURL.substring(0, nexusURL.length() - 1); + if (nexusURL != null && !nexusURL.endsWith("/")) { //$NON-NLS-1$ + nexusURL = nexusURL + "/"; } nexusUser = Util.fixEmpty(nexusParams.getString("nexusUser")); //$NON-NLS-1$ nexusPassword = nexusParams.getString("nexusPassword"); //$NON-NLS-1$ @@ -545,8 +556,8 @@ public FormValidation doUrlCheck(@QueryParameter String urlValue, return FormValidation.ok(); } final String testURL; - if (urlValue.endsWith("/")) { - testURL = urlValue.substring(0, urlValue.length() - 1); + if (!urlValue.endsWith("/")) { + testURL = urlValue + "/"; } else { testURL = urlValue; diff --git a/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/Stage.java b/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/Stage.java index f5bb730..6391f96 100644 --- a/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/Stage.java +++ b/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/Stage.java @@ -1,40 +1,107 @@ +/* + * The MIT License + * + * Copyright (c) 2010, NDS Group Ltd., James Nord + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ package org.jvnet.hudson.plugins.m2release.nexus; /** * A holder class to contain information about a Nexus Professional Staging repository. */ public class Stage { - + private String profileID; private String stageID; - + + /** * Construct a new Stage to represent a Nexus Professional Staging repository. + * * @param profileID the id of the staging profile that this stage is associated with. * @param stageID the id for this stage repository. */ public Stage(String profileID, String stageID) { - super(); - this.profileID = profileID; - this.stageID = stageID; - } + super(); + this.profileID = profileID; + this.stageID = stageID; + } + /** - * @return the profileID that this stage is associated with. - */ - public String getProfileID() { - return profileID; - } + * @return the profileID that this stage is associated with. + */ + public String getProfileID() { + return profileID; + } + /** - * @return the unique stageID for this stage - */ - public String getStageID() { - return stageID; - } - + * Return the StageID that this stage represents. StageIDs are recycled by Nexus, so this is only valid for + * the lifetime of this stage repository. + * + * @return the unique stageID for this stage. + */ + public String getStageID() { + return stageID; + } + + @Override public String toString() { - return String.format("Stage[profileId=%s, stageId=%s", profileID, stageID); + return String.format("Stage[profileId=%s, stageId=%s]", profileID, stageID); } + + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((profileID == null) ? 0 : profileID.hashCode()); + result = prime * result + ((stageID == null) ? 0 : stageID.hashCode()); + return result; + } + + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Stage other = (Stage) obj; + if (profileID == null) { + if (other.profileID != null) + return false; + } + else if (!profileID.equals(other.profileID)) + return false; + if (stageID == null) { + if (other.stageID != null) + return false; + } + else if (!stageID.equals(other.stageID)) + return false; + return true; + } + } diff --git a/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageAction.java b/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageAction.java index c38ef54..05501e9 100644 --- a/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageAction.java +++ b/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageAction.java @@ -27,9 +27,13 @@ import java.net.URL; public enum StageAction { - CLOSE("%1$s/service/local/staging/profiles/%2$s/finish"), - //PROMOTE("%1$s/service/local/staging/profiles/%2$s/promote"), - DROP("%1$s/service/local/staging/profiles/%2$s/drop"); + CLOSE("service/local/staging/profiles/%1$s/finish"), + /** not yet supported. */ + PROMOTE("service/local/staging/profiles/%1$s/promote"), + // release is just a promote without a target profile + RELEASE("service/local/staging/profiles/%1$s/promote"), + //START("service/local/staging/profiles/%1$s/start"), + DROP("service/local/staging/profiles/%1$s/drop"); /** * Template for the URL for this action. @@ -55,7 +59,7 @@ private StageAction(String urlTemplate) { * @throws MalformedURLException if the URL is invalid */ public URL getURL(URL baseURL, Stage stage) throws MalformedURLException { - return new URL(String.format(urlTemplate, baseURL, stage.getProfileID(), stage.getStageID())); + return new URL(baseURL, String.format(urlTemplate, stage.getProfileID(), stage.getStageID())); } } \ No newline at end of file diff --git a/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageClient.java b/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageClient.java index e201495..5aa9340 100644 --- a/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageClient.java +++ b/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageClient.java @@ -29,16 +29,20 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; +import java.net.URLConnection; import java.util.ArrayList; import java.util.List; +import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathException; +import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.commons.codec.binary.Base64; @@ -50,7 +54,8 @@ import org.xml.sax.SAXException; /** - * The Stage client acts as the interface to Nexus Pro staging via the Nexus REST APIs. + * The Stage client acts as the interface to Nexus Pro staging via the Nexus REST APIs. A single StageClient + * is not thread safe. * * @author James Nord * @version 0.5 @@ -59,12 +64,23 @@ public class StageClient { private Logger log = LoggerFactory.getLogger(StageClient.class); + /** XPath instance for running xpath queries */ + private XPath xpath; + + /** The base URL of the Nexus service. */ private URL nexusURL; + + /** The username passed to Nexus for authentication. */ private String username; + + /** The password passed to Nexus for authentication. */ private String password; + private transient String nexusVersion; + /** * Create a new StageClient to handle communicating to a Nexus Pro server Staging suite. + * * @param nexusURL the base URL for the Nexus server. * @param username user name to use with staging privileges. * @param password password for the user. @@ -73,17 +89,23 @@ public StageClient(URL nexusURL, String username, String password) { this.nexusURL = nexusURL; this.username = username; this.password = password; + // XPathFactory is not thread safe. + XPathFactory factory; + synchronized (XPathFactory.class) { + factory = XPathFactory.newInstance(); + } + synchronized (factory) { + xpath = factory.newXPath(); + } } + /** * Get the ID for the Staging repository that holds the specified GAV. * - * @param groupId - * groupID to search for. - * @param artifactId - * artifactID to search for. - * @param version - * version of the group/artifact to search for - may be null. + * @param groupId groupID to search for. + * @param artifactId artifactID to search for. + * @param version version of the group/artifact to search for - may be null. * @return the stageID or null if no machine stage was found. * @throws StageException if any issue occurred whilst locating the open stage. */ @@ -99,44 +121,98 @@ public Stage getOpenStageID(String group, String artifact, String version) throw } else { // multiple stages match!!! - log.warn("Found a matching stage ({}) for {}:{} but already found a matchine one ({})", new Object[] {testStage, group, artifact, stage}); + log.warn("Found a matching stage ({}) for {}:{} but already found a matchine one ({})", + new Object[] {testStage, group, artifact, stage}); } } } return stage; } + /** * Close the specified stage. * - * @param stage - * the stage to close. - * @throws StageException - * if any issue occurred whilst closing the stage. + * @param stage the stage to close. + * @throws StageException if any issue occurred whilst closing the stage. */ public void closeStage(Stage stage, String description) throws StageException { performStageAction(StageAction.CLOSE, stage, description); + if (isAsyncClose()) { + waitForActionToComplete(stage); + // check the action completed successfully and no rules failed. + URL url = getActivityURL(stage); + Document doc = getDocument(url); + // last stagingActivity that was a close + String xpathExpr = "(/list/stagingActivity[name='close'])[last()]"; + Node lastCloseNode = (Node) evaluateXPath(xpathExpr, doc, XPathConstants.NODE); + if (lastCloseNode == null) { + throw new StageException("Stage activity completed but no close action was recorded!"); + } + Node closed = + (Node) evaluateXPath("events/stagingActivityEvent[name='repositoryClosed']", lastCloseNode, + XPathConstants.NODE); + if (closed != null) { + // we have successfully closed the repository + return; + } + Node failed = + (Node) evaluateXPath("events/stagingActivityEvent[name='repositoryCloseFailed']", + lastCloseNode, XPathConstants.NODE); + if (failed == null) { + throw new StageException( + "Close stage action was signalled as completed, but was not recorded as either failed or succeeded!"); + } + StringBuilder failureMessage = + new StringBuilder("Closing stage ").append(stage.getStageID()).append(" failed.\n"); + String cause = + (String) evaluateXPath("properties/stagingProperty[name='cause']/value", failed, + XPathConstants.STRING); + failureMessage.append('\t').append(cause); + NodeList failedRules = + (NodeList) evaluateXPath("events/stagingActivityEvent[name='ruleFailed']/properties/stagingProperty[name='failureMessage']/value", + lastCloseNode, XPathConstants.NODESET); + for (int i = 0; i < failedRules.getLength(); i++) { + failureMessage.append("\n\t"); + failureMessage.append(failedRules.item(i).getTextContent()); + } + throw new StageException(failureMessage.toString()); + } } + /** * Drop the stage from Nexus staging. * - * @param stage - * the Stage to drop. - * @throws StageException - * if any issue occurred whilst dropping the stage. + * @param stage the Stage to drop. + * @throws StageException if any issue occurred whilst dropping the stage. */ public void dropStage(Stage stage) throws StageException { performStageAction(StageAction.DROP, stage, null); + // no need to wait for this to complete as there is no way to tell! } + /** - * Promote the stage from Nexus staging into the default repository for the stage. + * Release the stage from Nexus staging into the default repository for the stage. This does not drop stage + * repository after a successful release. * - * @param stage - * the Stage to promote. - * @throws StageException - * if any issue occurred whilst promoting the stage. + * @param stage the Stage to promote. + * @throws StageException if any issue occurred whilst promoting the stage. + */ + public void releaseStage(Stage stage) throws StageException { + performStageAction(StageAction.RELEASE, stage, null); + if (isAsyncClose()) { + waitForActionToComplete(stage); + } + } + + + /** + * Promote the stage from Nexus staging into the specified profile. + * + * @param stage the Stage to promote. + * @throws StageException if any issue occurred whilst promoting the stage. */ public void promoteStage(Stage stage) throws StageException { throw new UnsupportedOperationException("not implemented"); @@ -144,6 +220,42 @@ public void promoteStage(Stage stage) throws StageException { // performStageAction(StageAction.PROMOTE, stage, null); } + + /** + * Completion of the stage action is asynchronous - so poll until the action completed. + * + * @param stage the stage to wait until the previous action is completed. + * @throws StageException + */ + protected void waitForActionToComplete(Stage stage) throws StageException { + log.debug("Waiting for {} to finish transitioning.", stage); + int i = 0; + boolean transitioning = false; + try { + final URL activityUrl = getRepositoryURL(stage); + do { + Document doc = getDocument(activityUrl); + String status = + (String) evaluateXPath("/stagingProfileRepository/transitioning", doc, + XPathConstants.STRING); + transitioning = Boolean.valueOf(status).booleanValue(); + if (transitioning) { + i++; + Thread.sleep(500L); + if (i % 100 == 0) { + log.debug("Still waiting for {} to finish transitioning.", stage); + } + // TODO should we ever time out? + } + } while (transitioning); + } + catch (InterruptedException ex) { + throw new StageException(ex); + } + + } + + /** * Check if we have the required permissions for nexus staging. * @@ -152,99 +264,159 @@ public void promoteStage(Stage stage) throws StageException { */ public void checkAuthentication() throws StageException { try { - URL url = new URL(nexusURL.toString() + "/service/local/status"); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - addAuthHeader(conn); - int status = conn.getResponseCode(); - if (status == 200) { - DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - Document doc = builder.parse(conn.getInputStream()); - /* - * check for the following permissions: - */ - String[] requiredPerms = new String[] {"nexus:stagingprofiles", "nexus:stagingfinish", - // "nexus:stagingpromote", - "nexus:stagingdrop"}; + URL url = new URL(nexusURL, "service/local/status"); + Document doc = getDocument(url); - XPath xpath = XPathFactory.newInstance().newXPath(); - for (String perm : requiredPerms) { - String expression = "//clientPermissions/permissions/permission[id=\"" + perm + "\"]/value"; - Node node = (Node) xpath.evaluate(expression, doc, XPathConstants.NODE); - if (node == null) { - throw new StageException("Invalid reponse from server - is the URL a Nexus Professional server?"); - } - int val = Integer.parseInt(node.getTextContent()); - if (val == 0) { - throw new StageException("User has insufficient privaledges to perform staging actions (" + perm - + ")"); - } + /* + * check for the following permissions: + */ + String[] requiredPerms = + new String[] {"nexus:stagingprofiles", "nexus:stagingfinish", "nexus:stagingprofilerepos", + "nexus:stagingpromote", "nexus:stagingdrop"}; + + for (String perm : requiredPerms) { + String expression = "//clientPermissions/permissions/permission[id=\"" + perm + "\"]/value"; + Node node = (Node) evaluateXPath(expression, doc, XPathConstants.NODE); + if (node == null) { + throw new StageException( + "Invalid reponse from server - is the URL a Nexus Professional server?"); } - } - else { - drainOutput(conn); - if (status == HttpURLConnection.HTTP_UNAUTHORIZED) { - throw new IOException("Incorrect username / password supplied."); - } - else if (status == HttpURLConnection.HTTP_NOT_FOUND) { - throw new IOException("Service not found - is this a Nexus server?"); - } - else { - throw new IOException("Server returned error code " + status + "."); + int val = Integer.parseInt(node.getTextContent()); + if (val == 0) { + throw new StageException("User has insufficient privileges to perform staging actions (" + + perm + ")"); } } } - catch (IOException ex) { + catch (MalformedURLException ex) { throw createStageExceptionForIOException(nexusURL, ex); } - catch (XPathException ex) { - throw new StageException(ex); + } + + + /** + * Retrieve the Nexus servers version. + * + * @return the String representation of the server version. + * @throws StageException if we could not obtain the nexus server version. + */ + protected String getServerVersion() throws StageException { + if (nexusVersion == null) { + try { + URL url = new URL(nexusURL, "service/local/status"); + Document doc = getDocument(url); + Node node = (Node) evaluateXPath("//version", doc, XPathConstants.NODE); + if (node == null) { + throw new StageException( + "Invalid reponse from server - is the URL a Nexus Professional server?"); + } + nexusVersion = node.getTextContent(); + log.debug("This nexus server has version: {}", nexusVersion); + return nexusVersion; + } + catch (MalformedURLException ex) { + throw createStageExceptionForIOException(nexusURL, ex); + } } - catch (ParserConfigurationException ex) { - throw new StageException(ex); + return nexusVersion; + } + + /** + * Checks if this Nexus server uses asynchronous stage actions. + * + * @param version the version of this server + * @return true if this server uses asynchronous stage actions (i.e. the server is 2.4 or newer). + * @throws StageException if we could not retreive the server version. + */ + protected boolean isAsyncClose() throws StageException { + String version = getServerVersion(); + return isAsyncClose(version); + } + + /** + * Checks if this Nexus server uses asynchronous stage actions. + * + * @param version the version of this server + * @return true if this server uses asynchronous stage actions (i.e. the server is 2.4 or newer). + */ + protected boolean isAsyncClose(String version) { + String[] versionArr = version.split("\\."); + if (Integer.parseInt(versionArr[0]) > 2) { + return true; } - catch (SAXException ex) { - throw new StageException(ex); + if (Integer.parseInt(versionArr[0]) == 2) { + if (Integer.parseInt(versionArr[1]) >= 4) { + return true; + } } + return false; } public List getOpenStageIDs() throws StageException { log.debug("retreiving list of stages"); try { - List openStages = new ArrayList(); - URL url = new URL(nexusURL.toString() + "/service/local/staging/profiles"); - + URL url = new URL(nexusURL, "service/local/staging/profile_repositories"); Document doc = getDocument(url); - - String profileExpression = "//stagingProfile/id"; - XPath xpathProfile = XPathFactory.newInstance().newXPath(); - NodeList profileNodes = (NodeList) xpathProfile.evaluate(profileExpression, doc, XPathConstants.NODESET); - for (int i = 0; i < profileNodes.getLength(); i++) { - Node profileNode = profileNodes.item(i); - String profileID = profileNode.getTextContent(); - - String statgeExpression = "../stagingRepositoryIds/string"; - XPath xpathStage = XPathFactory.newInstance().newXPath(); - NodeList stageNodes = (NodeList) xpathStage.evaluate(statgeExpression, profileNode, - XPathConstants.NODESET); - for (int j = 0; j < stageNodes.getLength(); j++) { - Node stageNode = stageNodes.item(j); - // XXX need to also get the stage profile - openStages.add(new Stage(profileID, stageNode.getTextContent())); - } - } - return openStages; + return getOpenStageIDs(doc); } - catch (IOException ex) { + catch (MalformedURLException ex) { throw createStageExceptionForIOException(nexusURL, ex); } - catch (XPathException ex) { - throw new StageException(ex); - } } - public boolean checkStageForGAV(Stage stage, String group, String artifact, String version) - throws StageException { + + /** + * Parses a stagingRepositories element to obtain the list of open stages. + * + * @param doc the stagingRepositories to parse. + * @return a List of open stages. + * @throws XPathException if the XPath expression is invalid. + */ + protected List getOpenStageIDs(Document doc) throws StageException { + List stages = new ArrayList(); + + NodeList stageRepositories = + (NodeList) evaluateXPath("//stagingProfileRepository", doc, XPathConstants.NODESET); + for (int i = 0; i < stageRepositories.getLength(); i++) { + + Node stageRepo = stageRepositories.item(i); + + Node type = (Node) evaluateXPath("./type", stageRepo, XPathConstants.NODE); + // type will be "open" or "closed" + if ("open".equals(type.getTextContent())) { + Node profileId = (Node) evaluateXPath("./profileId", stageRepo, XPathConstants.NODE); + Node repoId = (Node) evaluateXPath("./repositoryId", stageRepo, XPathConstants.NODE); + + stages.add(new Stage(profileId.getTextContent(), repoId.getTextContent())); + } + } + return stages; + } + + + /** + * Evaluate the xPath expression on the given node. + * + * @param expression the expression to evaluate. + * @param node the node on which the xpath should be performed. + * @param type the return type of the expression. + * @return the resulting object from the xpath query + * @throws StageException If expression cannot be evaluated. + */ + private Object evaluateXPath(String expression, Node node, QName type) throws StageException { + try { + xpath.reset(); + return xpath.evaluate(expression, node, type); + } + catch (XPathExpressionException ex) { + throw new StageException("Could not evaluate xPath expression (" + expression + ')', ex); + } + } + + + public boolean + checkStageForGAV(Stage stage, String group, String artifact, String version) throws StageException { // do we always know the version??? // to browse an open repo // /service/local/repositories/${stageID}/content/... @@ -254,12 +426,14 @@ public boolean checkStageForGAV(Stage stage, String group, String artifact, Stri try { URL url; if (version == null) { - url = new URL(nexusURL.toString() + "/service/local/repositories/" + stage.getStageID() + "/content/" - + group.replace('.', '/') + '/' + artifact + "/?isLocal"); + url = + new URL(nexusURL, "service/local/repositories/" + stage.getStageID() + "/content/" + + group.replace('.', '/') + '/' + artifact + "/?isLocal"); } else { - url = new URL(nexusURL.toString() + "/service/local/repositories/" + stage.getStageID() + "/content/" - + group.replace('.', '/') + '/' + artifact + '/' + version + "/?isLocal"); + url = + new URL(nexusURL, "service/local/repositories/" + stage.getStageID() + "/content/" + + group.replace('.', '/') + '/' + artifact + '/' + version + "/?isLocal"); } HttpURLConnection conn = (HttpURLConnection) url.openConnection(); addAuthHeader(conn); @@ -274,7 +448,8 @@ else if (response == HttpURLConnection.HTTP_NOT_FOUND) { // not this repo } else { - log.warn("Server returned HTTP status {} when we only expected a 200 or 404.", Integer.toString(response)); + log.warn("Server returned HTTP status {} when we only expected a 200 or 404.", + Integer.toString(response)); } conn.disconnect(); } @@ -285,12 +460,20 @@ else if (response == HttpURLConnection.HTTP_NOT_FOUND) { } - private Document getDocument(URL url) throws StageException { + /** + * Retrieve and parse an XML file from the given URL. + * + * @param url the URL where the XML document can be obtained. + * @return the parsed Document. + * @throws StageException if there was an issue obtaining or parsing the document. + */ + protected Document getDocument(URL url) throws StageException { try { HttpURLConnection conn = (HttpURLConnection) url.openConnection(); addAuthHeader(conn); + conn.setRequestProperty("Accept", "application/xml"); int status = conn.getResponseCode(); - if (status == 200) { + if (status == HttpURLConnection.HTTP_OK) { DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = builder.parse(conn.getInputStream()); conn.disconnect(); @@ -298,8 +481,11 @@ private Document getDocument(URL url) throws StageException { } else { drainOutput(conn); - if (status == 401) { - throw new IOException("Incorrect Crediantials for " + url.toString()); + if (status == HttpURLConnection.HTTP_UNAUTHORIZED) { + throw new IOException("Incorrect username / password supplied."); + } + else if (status == HttpURLConnection.HTTP_NOT_FOUND) { + throw new IOException("Document not found - is this a Nexus server?"); } else { throw new IOException("Server returned error code " + status + " for " + url.toString()); @@ -318,36 +504,54 @@ private Document getDocument(URL url) throws StageException { } + /** * Construct the XML message for a promoteRequest. * - * @param stage - * The stage to target - * @param description - * the description (used for promote - ignored otherwise) + * @param stage The stage to target + * @param description the description (used for promote - ignored otherwise) + * @param autodrop Boolean.TRUE or Boolean.FALSE sets the autoDropAfterRelease to + * the appropriate value. null omits the value. * @return The XML for the promoteRequest. + * @throws StageException if we could not determine if this server support async close or not. */ - private String createPromoteRequestPayload(Stage stage, String description) { + protected String createPromoteRequestPayload(Stage stage, String description, Boolean autodrop) throws StageException { // TODO? this is missing the targetRepoID which is needed for promote... - // XXX lets hope that the description never contains "]]>" + // if the description contains a CDATA END tag then split it across multiple CDATA sections. + String escapedDescr = (description == null) ? "" : description; + if (escapedDescr.contains("]]>")) { + escapedDescr = escapedDescr.replace("]]>", "]]]]>"); + } + if (autodrop != null) { + return String.format("%s%s", + autodrop.toString(), stage.getStageID(), escapedDescr); + } return String.format("%s", - stage.getStageID(), description); + stage.getStageID(), escapedDescr); } - + /** * Perform a staging action. + * * @param action the action to perform. * @param stage the stage on which to perform the action. - * @param description description to pass to the server for the action (e.g. the description of the stage repo). + * @param description description to pass to the server for the action (e.g. the description of the stage + * repo). * @throws StageException if an exception occurs whilst performing the action. */ - private void performStageAction(StageAction action, Stage stage, String description) throws StageException { + protected void + performStageAction(StageAction action, Stage stage, String description) throws StageException { log.debug("Performing action {} on stage {}", new Object[] {action, stage}); try { URL url = action.getURL(nexusURL, stage); - String payload = createPromoteRequestPayload(stage, description); - + String payload; + if (action == StageAction.PROMOTE && isAsyncClose()) { + payload = createPromoteRequestPayload(stage, description, Boolean.FALSE); + } + else { + payload = createPromoteRequestPayload(stage, description, null); + } byte[] payloadBytes = payload.getBytes("UTF-8"); int contentLen = payloadBytes.length; @@ -365,32 +569,36 @@ private void performStageAction(StageAction action, Stage stage, String descript out.flush(); int status = conn.getResponseCode(); - log.debug("Server returned HTTP Status {} for {} stage request to {}.", new Object[] { Integer.toString(status), - action.name(), stage }); + log.debug("Server returned HTTP Status {} for {} stage request to {}.", + new Object[] {Integer.toString(status), action.name(), stage}); if (status == HttpURLConnection.HTTP_CREATED) { drainOutput(conn); conn.disconnect(); } else { - log.warn("Server returned HTTP Status {} for {} stage request to {}.", - new Object[] { Integer.toString(status), action.name(), stage }); + log.warn("Server returned HTTP Status {} for {} stage request to {}.", + new Object[] {Integer.toString(status), action.name(), stage}); drainOutput(conn); conn.disconnect(); throw new IOException(String.format("server responded with status:%s", Integer.toString(status))); } } catch (IOException ex) { - String message = String.format("Failed to perform %s action to nexus stage(%s)", action.name(), stage.toString()); + String message = + String.format("Failed to perform %s action to nexus stage(%s)", action.name(), + stage.toString()); throw new StageException(message, ex); } } + /** * Add the BASIC Authentication header to the HTTP connection. + * * @param conn the HTTP URL Connection */ - private void addAuthHeader(HttpURLConnection conn) { + private void addAuthHeader(URLConnection conn) { // java.net.Authenticator is brain damaged as it is global and no way to delegate for just one server... try { String auth = username + ":" + password; @@ -399,10 +607,11 @@ private void addAuthHeader(HttpURLConnection conn) { // Base64 adds a trailing newline - just strip it as whitespace is illegal in Base64 String encodedAuth = new Base64().encodeToString(auth.getBytes("ISO-8859-1")).trim(); conn.setRequestProperty("Authorization", "Basic " + encodedAuth); - log.debug("Encoded Authentication is: "+encodedAuth); + log.debug("Encoded Authentication is: " + encodedAuth); } catch (UnsupportedEncodingException ex) { - String msg = "JVM does not conform to java specification. Mandatory CharSet ISO-8859-1 is not available."; + String msg = + "JVM does not conform to java specification. Mandatory CharSet ISO-8859-1 is not available."; log.error(msg); throw new RuntimeException(msg, ex); } @@ -411,7 +620,7 @@ private void addAuthHeader(HttpURLConnection conn) { private StageException createStageExceptionForIOException(URL url, IOException ex) { if (ex instanceof StageException) { - return (StageException)ex; + return (StageException) ex; } if (ex.getMessage().equals(url.toString())) { // Sun JRE (and probably others too) often return just the URL in the error. @@ -421,12 +630,16 @@ private StageException createStageExceptionForIOException(URL url, IOException e return new StageException(ex.getMessage(), ex); } } - + + private void drainOutput(HttpURLConnection conn) throws IOException { - // for things like unauthorised (401) we won't have any content and getting the inputStream will - // cause an IOException as we are in error - but there is no really way to tell this so check the + // for things like unauthorised (401) we won't have any content and getting the inputStream will + // cause an IOException as we are in error - but there is no really way to tell this so check the // length instead. if (conn.getContentLength() > 0) { + if (conn.getContentLength() < 1024) { + byte[] data = new byte[conn.getConnectTimeout()]; + } if (conn.getErrorStream() != null) { IOUtils.skip(conn.getErrorStream(), conn.getContentLength()); } @@ -435,4 +648,46 @@ private void drainOutput(HttpURLConnection conn) throws IOException { } } } + + + /** + * Get the URL used to query the activity on the specified Stage. + * + * @param stage the stage to query activity for. + * @return a new URL for querying the activity. + * @throws StageException if the URL is invalid. + */ + private URL getActivityURL(Stage stage) throws StageException { + return constructURL("service/local/staging/repository/%1$s/activity", stage); + } + + + /** + * Get the URL used to query the specified Stage. + * + * @param stage the stage to query activity for. + * @return a new URL for querying the activity. + * @throws StageException if the URL is invalid. + */ + private URL getRepositoryURL(Stage stage) throws StageException { + return constructURL("service/local/staging/repository/%1$s", stage); + } + + + /** + * Format a URL based on the specified stage and format. + * + * @param stage the stage to query activity for. + * @param format a format string. "%1" is the stageID %2 is the profileID. + * @return a new URL constructed from the Stage and the format.. + * @throws StageException if the URL is invalid. + */ + private URL constructURL(String format, Stage stage) throws StageException { + try { + return new URL(nexusURL, String.format(format, stage.getStageID())); + } + catch (MalformedURLException ex) { + throw createStageExceptionForIOException(nexusURL, ex); + } + } } diff --git a/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageException.java b/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageException.java index 25d7cbd..924c1b1 100644 --- a/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageException.java +++ b/src/main/java/org/jvnet/hudson/plugins/m2release/nexus/StageException.java @@ -68,8 +68,4 @@ public StageException(Throwable cause) { super(cause); } - @Override - public String toString() { - return "StageException " + super.toString(); - } } diff --git a/src/main/webapp/help-projectConfig-help-numberOfReleaseBuildsToKeep.html b/src/main/webapp/help-projectConfig-numberOfReleaseBuildsToKeep.html similarity index 100% rename from src/main/webapp/help-projectConfig-help-numberOfReleaseBuildsToKeep.html rename to src/main/webapp/help-projectConfig-numberOfReleaseBuildsToKeep.html diff --git a/src/test/java/org/jvnet/hudson/plugins/m2release/nexus/StageClientTest.java b/src/test/java/org/jvnet/hudson/plugins/m2release/nexus/StageClientTest.java new file mode 100644 index 0000000..6369c75 --- /dev/null +++ b/src/test/java/org/jvnet/hudson/plugins/m2release/nexus/StageClientTest.java @@ -0,0 +1,451 @@ +/* + * Copyright (c) NDS Limited 2013. + * All rights reserved. + * No part of this program may be reproduced, translated or transmitted, + * in any form or by any means, electronic, mechanical, photocopying, + * recording or otherwise, or stored in any retrieval system of any nature, + * without written permission of the copyright holder. + */ + +/* + * Created on 13 Jun 2013 by nordj + */ +package org.jvnet.hudson.plugins.m2release.nexus; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import com.sun.net.httpserver.BasicAuthenticator; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpContext; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.hasXPath; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@SuppressWarnings("restriction") +public class StageClientTest { + + private Stage testStage = new Stage("profile-1", "stage-1"); + private URL testURL; + + + public StageClientTest() throws MalformedURLException { + testURL = new URL("http://127.0.1.2:3456/nexus/"); + } + + + /** + * Tests that the wait successfully blocks until the repository is no longer transitioning + */ + @Test + public void waitForActionToCompleteTest() throws Exception { + final Document transitioning = getDocument("stageClientTest/repository__transitioning.xml"); + final Document transitioned = getDocument("stageClientTest/repository__transitioned.xml"); + + StageClient spy = spy(new StageClient(testURL, "username", "password")); + + doAnswer(new Answer() { + + private int calls = 1; + + + public Document answer(InvocationOnMock invocation) { + return (calls++ % 3 == 0) ? transitioned : transitioning; + } + }).when(spy).getDocument(any(URL.class)); + spy.waitForActionToComplete(testStage); + + verify(spy, times(3)).getDocument(any(URL.class)); + } + + + /** + * Test when a close fails that the appropriate StageException is thrown. Does not test the performing of + * the action - but the parsing of the results. + */ + @Test + public void closeFailureThrowsExceptionTest() throws Exception { + Document doc = getDocument("stageClientTest/activity__closed_failed.xml"); + + StageClient spy = spy(new StageClient(new URL("http://localhost:1234/nexus/"), "username", "password")); + + doNothing().when(spy).performStageAction(any(StageAction.class), same(testStage), any(String.class)); + doNothing().when(spy).waitForActionToComplete(testStage); + doReturn(doc).when(spy).getDocument(any(URL.class)); + doReturn(Boolean.TRUE).when(spy).isAsyncClose(); + try { + spy.closeStage(testStage, "myDescription"); + } + catch (StageException ex) { + assertThat("Cause should not be present as this should be a rule failure.", ex.getCause(), + is(nullValue())); + assertThat(ex.getMessage(), startsWith("Closing stage stage-1 failed.")); + assertThat(ex.getMessage(), containsString("One or more rules have failed")); + assertThat(ex.getMessage(), + containsString("Artifact is not unique: 'asd:asd:123' exists in repository 'releases'")); + } + } + + + /** + * Test when a close fails that the appropriate StageException is thrown. Does not test the performing of + * the action - but the parsing of the results. + */ + @Test + public void closeSucessTest() throws Exception { + Document doc = getDocument("stageClientTest/activity__closed_ok.xml"); + + StageClient spy = spy(new StageClient(new URL("http://localhost:1234/nexus"), "username", "password")); + + doNothing().when(spy).performStageAction(any(StageAction.class), same(testStage), any(String.class)); + doNothing().when(spy).waitForActionToComplete(testStage); + doReturn(doc).when(spy).getDocument(any(URL.class)); + doReturn(Boolean.TRUE).when(spy).isAsyncClose(); + + // no exception should be thrown here! + spy.closeStage(testStage, "myDescription"); + } + + + @Test + public void getServerVersionTest() throws Exception { + final Document okPerms = getDocument("stageClientTest/status__ok_perms.xml"); + + StageClient spy = spy(new StageClient(testURL, "username", "password")); + + doReturn(okPerms).when(spy).getDocument(any(URL.class)); + + String version = spy.getServerVersion(); + assertThat("Icorrect version", version, is("2.5.0-04")); + } + + + @Test + public void isAsyncCloseTest() throws Exception { + StageClient sc = new StageClient(testURL, "username", "password"); + assertThat("2.4.0-02 should be async", sc.isAsyncClose("2.4.0-03"), is(true)); + assertThat("2.5.0-04 should be async", sc.isAsyncClose("2.5.0-04"), is(true)); + assertThat("3.1.0-07 should be async", sc.isAsyncClose("3.1.0-07"), is(true)); + assertThat("2.3.23-02 should not be async", sc.isAsyncClose("2.3.23-02"), is(false)); + } + + + /** + * Tests that the getDocument function works correctly when authorised. + */ + @Test + public void getDocumentTest() throws Exception { + String response = "James was here"; + HttpServer httpServer = createAuthenticatingHttpServer(response, "testuser", "testpassword", "/nexus/"); + try { + httpServer.start(); + URL url = + new URL("http", httpServer.getAddress().getHostName(), httpServer.getAddress().getPort(), + "/nexus/"); + StageClient client = new StageClient(url, "testuser", "testpassword"); + Document doc = client.getDocument(url); + // check we have the correct document returned. + assertThat(doc.getFirstChild(), is(notNullValue())); + assertThat(doc.getFirstChild().getNodeName(), is("hello")); + assertThat(doc.getFirstChild().getTextContent(), is("James was here")); + } + finally { + httpServer.stop(0); + } + } + + + /** + * Tests that the getDocument function works correctly when not authorised. + */ + @Test + public void getDocumentUnAuthorised() throws Exception { + String response = "James was here"; + HttpServer httpServer = createAuthenticatingHttpServer(response, "testuser", "testpassword", "/nexus/"); + try { + httpServer.start(); + URL url = + new URL("http", httpServer.getAddress().getHostName(), httpServer.getAddress().getPort(), + "/nexus/"); + StageClient client = new StageClient(url, "testuser", "testpassword"); + Document doc = client.getDocument(url); + } + catch (StageException ex) { + assertThat(ex.getMessage(), containsString("Incorrect username / password")); + } + finally { + httpServer.stop(0); + } + } + + + @Test + public void testPromotionEscaping() throws Exception { + String text = " String &wibble"; + StageClient spy = spy(new StageClient(testURL, "ignored", "ignored")); + + String xmlStr = spy.createPromoteRequestPayload(new Stage("profile-1234", "stage-1234"), text, Boolean.FALSE); + + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = builder.parse(new ByteArrayInputStream(xmlStr.getBytes("UTF-8"))); + NodeList list = doc.getElementsByTagName("description"); + assertThat(list.getLength(), is(1)); + assertThat(list.item(0).getTextContent(), is(text)); + } + + @Test + public void testPromotionAsync() throws Exception { + StageClient spy = spy(new StageClient(testURL, "ignored", "ignored")); + + String xmlStr = spy.createPromoteRequestPayload(new Stage("profile-1234", "stage-1234"), "description", Boolean.FALSE); + + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = builder.parse(new ByteArrayInputStream(xmlStr.getBytes("UTF-8"))); + assertThat(doc, hasXPath("/promoteRequest/data/autoDropAfterRelease", is("false"))); + } + + @Test + public void testPromotionNonAsync() throws Exception { + StageClient spy = spy(new StageClient(testURL, "ignored", "ignored")); + + String xmlStr = spy.createPromoteRequestPayload(new Stage("profile-1234", "stage-1234"), "description", null); + + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = builder.parse(new ByteArrayInputStream(xmlStr.getBytes("UTF-8"))); + assertThat(doc, not(hasXPath("/promoteRequest/data/autoDropAfterRelease"))); + } + + + /** + * Tests that the successful authentication works correctly. + */ + @Test + public void authenticationPassWithCorrectPermissionsTest() throws Exception { + final Document okPerms = getDocument("stageClientTest/status__ok_perms.xml"); + + StageClient spy = spy(new StageClient(testURL, "username", "password")); + + doReturn(okPerms).when(spy).getDocument(any(URL.class)); + + spy.checkAuthentication(); + } + + + /** + * Tests that the successful authentication with not enough privileges works correctly. + */ + @Test + public void authenticationPassWithIncorrectPermissionsTest() throws Exception { + final Document okPerms = getDocument("stageClientTest/status__bad_perms.xml"); + + StageClient spy = spy(new StageClient(testURL, "username", "password")); + + doReturn(okPerms).when(spy).getDocument(any(URL.class)); + + try { + spy.checkAuthentication(); + fail("Exception should have been thrown"); + } + catch (StageException ex) { + assertThat(ex.getMessage(), containsString("insufficient privileges to perform staging actions")); + } + } + + + /** + * Tests that the successful authentication with not enough privileges works correctly. + */ + @Test + public void authenticationFailTest() throws Exception { + HttpServer httpServer = + createAuthenticatingHttpServer("bogus", "testuser", "testpassword", "/nexus/service/local/status"); + + try { + httpServer.start(); + URL url = + new URL("http", httpServer.getAddress().getHostName(), httpServer.getAddress().getPort(), + "/nexus/"); + StageClient client = new StageClient(url, "testuser", "wrongpassword"); + client.checkAuthentication(); + fail("Exception should have been thrown."); + } + catch (StageException ex) { + assertThat(ex.getMessage(), containsString("Incorrect username / password")); + } + finally { + httpServer.stop(0); + } + } + + + @Test + public void openStagesMethodShouldReturnCorrectStages() throws Exception { + Stage expectedStage1 = new Stage("3e1e1bad64f", "test-001"); + Stage expectedStage2 = new Stage("3e1e1bad64f", "test-005"); + + Document doc = getDocument("stageClientTest/profile_repositories.xml"); + + StageClient spy = spy(new StageClient(testURL, "username", "password")); + + doReturn(doc).when(spy).getDocument(any(URL.class)); + + List stages = spy.getOpenStageIDs(); + + assertThat(stages, hasSize(2)); + assertThat(stages, hasItems(expectedStage1, expectedStage2)); + + } + + + @Test + public void checkStageForGAVReturnsCorrectStage() throws Exception { + List stages = new ArrayList(); + stages.add(new Stage("profile1", "profile1-1001")); + stages.add(new Stage("profile1", "profile1-1002")); + stages.add(new Stage("profile2", "profile2-1001")); + stages.add(new Stage("profile2", "profile2-1002")); + Stage targetStage = stages.get(2); + + HttpServer httpServer = + createAuthenticatingHttpServer("", "username", "password", + "/nexus/service/local/repositories/profile2-1001/content/org/example/test/test/1.2.3-4/"); + + try { + httpServer.start(); + URL url = + new URL("http", httpServer.getAddress().getHostName(), httpServer.getAddress().getPort(), + "/nexus/"); + StageClient sc = new StageClient(url, "username", "password"); + for (Stage stage : stages) { + boolean found = sc.checkStageForGAV(stage, "org.example.test", "test", "1.2.3-4"); + assertEquals("Incorrect stage match (" + stage + ").", (stage == targetStage), found); + } + } + finally { + httpServer.stop(0); + } + } + + + /** + * Creates an HTTP Server bound to a random port on 127.0.0.1. The Caller must start and stop this server + * when it is no longer required. + * + * @param text the text to return with the request. + * @param user the username that must be sent to match authentication. + * @param pass the password that must be sent to match authentication. + * @param requestPath the path for which the text should be returned - other paths will result in a 404 + * response. + * @return The newly created (and started) HTTP Server. + */ + private HttpServer createAuthenticatingHttpServer(final String text, + final String username, + final String password, + final String requestPath) throws IOException { + HttpServer httpServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 1); + + HttpContext ctx = httpServer.createContext("/"); + + BasicAuthenticator authenticator = new BasicAuthenticator("my realm") { + + @Override + public boolean checkCredentials(String user, String pass) { + return (username.equals(user) && password.equals(pass)); + } + + }; + + ctx.setAuthenticator(authenticator); + + HttpHandler handler = new HttpHandler() { + + public void handle(HttpExchange exchange) throws IOException { + String path = exchange.getRequestURI().getPath(); + if (path.equals(requestPath)) { + byte[] data = text.getBytes("UTF-8"); + Headers headers = exchange.getResponseHeaders(); + + if (exchange.getRequestMethod().equals("POST")) { + exchange.sendResponseHeaders(HttpURLConnection.HTTP_CREATED, -1); + OutputStream os = exchange.getResponseBody(); + os.close(); + } + else { + headers.add("Content-Type", "application/xml; charset=UTF-8"); + exchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, data.length); + if (exchange.getRequestMethod().equals("HEAD")) { + // bug in sun HTTP Server - warning produces here should not exist. + OutputStream os = exchange.getResponseBody(); + os.close(); + } + else { + OutputStream os = exchange.getResponseBody(); + os.write(data); + os.close(); + } + + } + } + else { + Headers headers = exchange.getResponseHeaders(); + headers.add("Content-Type", "application/xml; charset=UTF-8"); + exchange.sendResponseHeaders(HttpURLConnection.HTTP_NOT_FOUND, -1); + OutputStream os = exchange.getResponseBody(); + os.close(); + } + } + }; + ctx.setHandler(handler); + return httpServer; + } + + + private static Document getDocument(String testResource) throws ParserConfigurationException, + SAXException, IOException { + URL url = StageClientTest.class.getResource(testResource); + assertThat("resource not found for: " + testResource, url, is(notNullValue())); + + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + return builder.parse(url.openStream()); + } + +} diff --git a/src/test/java/org/jvnet/hudson/plugins/m2release/nexus/StageTest.java b/src/test/java/org/jvnet/hudson/plugins/m2release/nexus/StageTest.java index b1d2b97..20a69d1 100644 --- a/src/test/java/org/jvnet/hudson/plugins/m2release/nexus/StageTest.java +++ b/src/test/java/org/jvnet/hudson/plugins/m2release/nexus/StageTest.java @@ -1,88 +1,77 @@ package org.jvnet.hudson.plugins.m2release.nexus; -import java.net.MalformedURLException; import java.net.URL; -import java.util.List; import junit.framework.Assert; +import org.junit.Assume; import org.junit.Ignore; import org.junit.Test; -@Ignore("requires infrastructure.") + +import static org.hamcrest.core.Is.is; + public class StageTest { - // TODO start an embedded server instead to server these files so we also test http auth access? + // TODO start an embedded server instead to server these files so we also + // test http auth access? private static final URL NEXUS_URL; - + static { - try { - //NEXUS_URL = new URL("http://localhost:8081/nexus"); + NEXUS_URL = StageTest.class.getResource("stageTest/"); + /* + try { + // NEXUS_URL = new URL("http://localhost:8081/nexus"); // NEXUS_URL = new URL("http://192.168.1.65:8081/nexus"); - //NEXUS_URL = StageTest.class.getResource("stageTest"); - } - catch (MalformedURLException e) { - throw new RuntimeException("Impossible Condition", e); - } - } - - - @Test - public void testValidAuth() throws Exception { - StageClient client = new StageClient(NEXUS_URL, "admin", "admin123"); - client.checkAuthentication(); + } catch (java.net.MalformedURLException e) { + throw new RuntimeException("Impossible Condition", e); + } + */ } - @Test(expected=Exception.class) - public void testInvalidAuth() throws Exception { - StageClient client = new StageClient(NEXUS_URL, "bob", "jones"); - client.checkAuthentication(); - } - - - @Test - public void testStage() throws Exception { - StageClient client = new StageClient(NEXUS_URL, "admin", "admin123"); - List stages = client.getOpenStageIDs(); - Assert.assertEquals("incorrect number of stages returned", 2, stages.size()); - Assert.assertEquals("Incorrect stage returned", "3e1e1bad64f", stages.get(0).getProfileID()); - Assert.assertEquals("Incorrect stage returned", "test-001", stages.get(0).getStageID()); - Assert.assertEquals("Incorrect stage returned", "3e1e1bad64f", stages.get(1).getProfileID()); - Assert.assertEquals("Incorrect stage returned", "test-005", stages.get(1).getStageID()); - } - @Test + @Ignore("requres test setup") public void testSpecificStage() throws Exception { + Assume.assumeThat(NEXUS_URL.getProtocol(), is("file")); + StageClient client = new StageClient(NEXUS_URL, "admin", "admin123"); - + // group and artifact don't exist Stage stage = client.getOpenStageID("invalid", "bogus", "1.2.3-4"); Assert.assertNull("Stage returned but we should not have one", stage); - + // group and artifact exist but at different version stage = client.getOpenStageID("com.test.testone", "test", "1.0.2"); Assert.assertNull("Stage returned but we should not have one", stage); - + // full gav match stage = client.getOpenStageID("com.test.testone", "test", "1.0.0"); - Assert.assertEquals("Incorrect stage returned", "test-005", stage.getStageID()); - + Assert.assertEquals("Incorrect stage returned", "test-005", + stage.getStageID()); + // match group and artifact for any version stage = client.getOpenStageID("com.test.testone", "test", null); - Assert.assertEquals("Incorrect stage returned", "test-005", stage.getStageID()); - } - - @Test - public void testCloseStage() throws Exception { - StageClient client = new StageClient(NEXUS_URL, "admin", "admin123"); - Stage stage = client.getOpenStageID("com.test.testone", "test", "1.0.0"); - Assert.assertNotNull("Stage is null", stage); - client.closeStage(stage, "Test stage closing from StageClient"); + Assert.assertEquals("Incorrect stage returned", "test-005", + stage.getStageID()); } @Test - public void testDropStage() throws Exception { + @Ignore("requres test setup") + public void testCloseStage() throws Exception { + Assume.assumeThat(NEXUS_URL.getProtocol(), is("http")); StageClient client = new StageClient(NEXUS_URL, "admin", "admin123"); - Stage stage = client.getOpenStageID("com.test.testone", "test", "1.0.0"); + Stage stage = client + .getOpenStageID("com.test.testone", "test", "1.0.0"); + Assert.assertNotNull("Stage is null", stage); + client.closeStage(stage, "Test stage closing from StageClient"); + } + + @Test + @Ignore("requres test setup") + public void testDropStage() throws Exception { + Assume.assumeThat(NEXUS_URL.getProtocol(), is("http")); + StageClient client = new StageClient(NEXUS_URL, "admin", "admin123"); + Stage stage = client + .getOpenStageID("com.test.testone", "test", "1.0.0"); Assert.assertNotNull("Stage is null", stage); client.dropStage(stage); } diff --git a/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/activity__closed_failed.xml b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/activity__closed_failed.xml new file mode 100644 index 0000000..43fad51 --- /dev/null +++ b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/activity__closed_failed.xml @@ -0,0 +1,396 @@ + + + open + 2013-06-12T08:31:08.703-07:00 + 2013-06-12T08:31:08.911-07:00 + + + 2013-06-12T08:31:08.908-07:00 + repositoryCreated + 0 + + + id + testprofile-1002 + + + user + admin + + + ip + 127.0.0.1 + + + + + + + close + 2013-06-12T08:31:09.677-07:00 + admin + 127.0.0.1 + 2013-06-12T08:31:09.749-07:00 + + + 2013-06-12T08:31:09.678-07:00 + rulesEvaluate + 0 + + + id + 24d6c427e4f + + + rule + uniq-staging + + + rule + checksum-staging + + + rule + no-system-scope-in-pom-staging + + + rule.disabled + sources-staging + + + + + 2013-06-12T08:31:09.679-07:00 + ruleEvaluate + 0 + + + typeId + checksum-staging + + + + + 2013-06-12T08:31:09.694-07:00 + rulePassed + 0 + + + typeId + checksum-staging + + + + + 2013-06-12T08:31:09.695-07:00 + ruleEvaluate + 0 + + + typeId + no-system-scope-in-pom-staging + + + + + 2013-06-12T08:31:09.708-07:00 + rulePassed + 0 + + + typeId + no-system-scope-in-pom-staging + + + + + 2013-06-12T08:31:09.709-07:00 + ruleEvaluate + 0 + + + typeId + uniq-staging + + + + + 2013-06-12T08:31:09.729-07:00 + ruleFailed + 1 + + + typeId + uniq-staging + + + failureMessage + Artifact is not unique: 'asd:asd:123' exists in repository 'releases' + + + + + 2013-06-12T08:31:09.730-07:00 + rulesFailed + 1 + + + id + 24d6c427e4f + + + failureCount + 1 + + + + + 2013-06-12T08:31:09.736-07:00 + rulesEvaluate + 0 + + + id + nx-internal-ruleset + + + rule + RepositoryWritePolicy + + + + + 2013-06-12T08:31:09.737-07:00 + ruleEvaluate + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T08:31:09.745-07:00 + rulePassed + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T08:31:09.747-07:00 + rulesPassed + 0 + + + id + nx-internal-ruleset + + + + + 2013-06-12T08:31:09.748-07:00 + repositoryCloseFailed + 1 + + + id + testprofile-1002 + + + cause + com.sonatype.nexus.staging.StagingRulesFailedException: One or more rules have failed + + + + + + + close + 2013-06-12T09:12:22.037-07:00 + admin + 127.0.0.1 + 2013-06-12T09:12:22.124-07:00 + + + 2013-06-12T09:12:22.039-07:00 + rulesEvaluate + 0 + + + id + 24d6c427e4f + + + rule + uniq-staging + + + rule + checksum-staging + + + rule + no-system-scope-in-pom-staging + + + rule.disabled + sources-staging + + + + + 2013-06-12T09:12:22.041-07:00 + ruleEvaluate + 0 + + + typeId + checksum-staging + + + + + 2013-06-12T09:12:22.062-07:00 + rulePassed + 0 + + + typeId + checksum-staging + + + + + 2013-06-12T09:12:22.065-07:00 + ruleEvaluate + 0 + + + typeId + uniq-staging + + + + + 2013-06-12T09:12:22.095-07:00 + ruleFailed + 1 + + + typeId + uniq-staging + + + failureMessage + Artifact is not unique: 'asd:asd:123' exists in repository 'releases' + + + + + 2013-06-12T09:12:22.097-07:00 + ruleEvaluate + 0 + + + typeId + no-system-scope-in-pom-staging + + + + + 2013-06-12T09:12:22.108-07:00 + rulePassed + 0 + + + typeId + no-system-scope-in-pom-staging + + + + + 2013-06-12T09:12:22.111-07:00 + rulesFailed + 1 + + + id + 24d6c427e4f + + + failureCount + 1 + + + + + 2013-06-12T09:12:22.113-07:00 + rulesEvaluate + 0 + + + id + nx-internal-ruleset + + + rule + RepositoryWritePolicy + + + + + 2013-06-12T09:12:22.115-07:00 + ruleEvaluate + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T09:12:22.118-07:00 + rulePassed + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T09:12:22.120-07:00 + rulesPassed + 0 + + + id + nx-internal-ruleset + + + + + 2013-06-12T09:12:22.122-07:00 + repositoryCloseFailed + 1 + + + id + testprofile-1002 + + + cause + com.sonatype.nexus.staging.StagingRulesFailedException: One or more rules have failed + + + + + + \ No newline at end of file diff --git a/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/activity__closed_ok.xml b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/activity__closed_ok.xml new file mode 100644 index 0000000..4667286 --- /dev/null +++ b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/activity__closed_ok.xml @@ -0,0 +1,491 @@ + + + open + 2013-06-12T08:31:08.703-07:00 + 2013-06-12T08:31:08.911-07:00 + + + 2013-06-12T08:31:08.908-07:00 + repositoryCreated + 0 + + + id + testprofile-1002 + + + user + admin + + + ip + 127.0.0.1 + + + + + + + close + 2013-06-12T08:31:09.677-07:00 + admin + 127.0.0.1 + 2013-06-12T08:31:09.749-07:00 + + + 2013-06-12T08:31:09.678-07:00 + rulesEvaluate + 0 + + + id + 24d6c427e4f + + + rule + uniq-staging + + + rule + checksum-staging + + + rule + no-system-scope-in-pom-staging + + + rule.disabled + sources-staging + + + + + 2013-06-12T08:31:09.679-07:00 + ruleEvaluate + 0 + + + typeId + checksum-staging + + + + + 2013-06-12T08:31:09.694-07:00 + rulePassed + 0 + + + typeId + checksum-staging + + + + + 2013-06-12T08:31:09.695-07:00 + ruleEvaluate + 0 + + + typeId + no-system-scope-in-pom-staging + + + + + 2013-06-12T08:31:09.708-07:00 + rulePassed + 0 + + + typeId + no-system-scope-in-pom-staging + + + + + 2013-06-12T08:31:09.709-07:00 + ruleEvaluate + 0 + + + typeId + uniq-staging + + + + + 2013-06-12T08:31:09.729-07:00 + ruleFailed + 1 + + + typeId + uniq-staging + + + failureMessage + Artifact is not unique: 'asd:asd:123' exists in repository 'releases' + + + + + 2013-06-12T08:31:09.730-07:00 + rulesFailed + 1 + + + id + 24d6c427e4f + + + failureCount + 1 + + + + + 2013-06-12T08:31:09.736-07:00 + rulesEvaluate + 0 + + + id + nx-internal-ruleset + + + rule + RepositoryWritePolicy + + + + + 2013-06-12T08:31:09.737-07:00 + ruleEvaluate + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T08:31:09.745-07:00 + rulePassed + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T08:31:09.747-07:00 + rulesPassed + 0 + + + id + nx-internal-ruleset + + + + + 2013-06-12T08:31:09.748-07:00 + repositoryCloseFailed + 1 + + + id + testprofile-1002 + + + cause + com.sonatype.nexus.staging.StagingRulesFailedException: One or more rules have failed + + + + + + + close + 2013-06-12T09:12:22.037-07:00 + admin + 127.0.0.1 + 2013-06-12T09:12:22.124-07:00 + + + 2013-06-12T09:12:22.039-07:00 + rulesEvaluate + 0 + + + id + 24d6c427e4f + + + rule + uniq-staging + + + rule + checksum-staging + + + rule + no-system-scope-in-pom-staging + + + rule.disabled + sources-staging + + + + + 2013-06-12T09:12:22.041-07:00 + ruleEvaluate + 0 + + + typeId + checksum-staging + + + + + 2013-06-12T09:12:22.062-07:00 + rulePassed + 0 + + + typeId + checksum-staging + + + + + 2013-06-12T09:12:22.065-07:00 + ruleEvaluate + 0 + + + typeId + uniq-staging + + + + + 2013-06-12T09:12:22.095-07:00 + ruleFailed + 1 + + + typeId + uniq-staging + + + failureMessage + Artifact is not unique: 'asd:asd:123' exists in repository 'releases' + + + + + 2013-06-12T09:12:22.097-07:00 + ruleEvaluate + 0 + + + typeId + no-system-scope-in-pom-staging + + + + + 2013-06-12T09:12:22.108-07:00 + rulePassed + 0 + + + typeId + no-system-scope-in-pom-staging + + + + + 2013-06-12T09:12:22.111-07:00 + rulesFailed + 1 + + + id + 24d6c427e4f + + + failureCount + 1 + + + + + 2013-06-12T09:12:22.113-07:00 + rulesEvaluate + 0 + + + id + nx-internal-ruleset + + + rule + RepositoryWritePolicy + + + + + 2013-06-12T09:12:22.115-07:00 + ruleEvaluate + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T09:12:22.118-07:00 + rulePassed + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T09:12:22.120-07:00 + rulesPassed + 0 + + + id + nx-internal-ruleset + + + + + 2013-06-12T09:12:22.122-07:00 + repositoryCloseFailed + 1 + + + id + testprofile-1002 + + + cause + com.sonatype.nexus.staging.StagingRulesFailedException: One or more rules have failed + + + + + + + close + 2013-06-12T09:12:42.955-07:00 + admin + 127.0.0.1 + 2013-06-12T09:12:43.125-07:00 + + + 2013-06-12T09:12:42.959-07:00 + rulesEvaluate + 0 + + + id + 24d6c427e4f + + + rule.disabled + uniq-staging + + + rule.disabled + checksum-staging + + + rule.disabled + no-system-scope-in-pom-staging + + + rule.disabled + sources-staging + + + + + 2013-06-12T09:12:42.963-07:00 + rulesEvaluate + 0 + + + id + nx-internal-ruleset + + + rule + RepositoryWritePolicy + + + + + 2013-06-12T09:12:42.966-07:00 + ruleEvaluate + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T09:12:42.970-07:00 + rulePassed + 0 + + + typeId + RepositoryWritePolicy + + + + + 2013-06-12T09:12:42.974-07:00 + rulesPassed + 0 + + + id + nx-internal-ruleset + + + + + 2013-06-12T09:12:43.123-07:00 + repositoryClosed + 0 + + + id + testprofile-1002 + + + + + + \ No newline at end of file diff --git a/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/profile_repositories.xml b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/profile_repositories.xml new file mode 100644 index 0000000..2e48957 --- /dev/null +++ b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/profile_repositories.xml @@ -0,0 +1,97 @@ + + + + 3e1e1bad64f + test + repository + test-001 + test-001 (u:deployuser, a:127.0.1.2) + open + release + deploy_release + Maven 3.0.4 + 127.0.3.1 + http://127.0.0.1/content/repositories/test-001 + Tue Apr 30 16:17:52 BST 2013 + n/a + maven2 + releases + My Releases + + + 3e1e1bad64f + test + repository + test-005 + test-005 (u:deployuser, a:127.0.1.2) + open + release + deploy_release + Maven 3.0.4 + 127.0.3.1 + http://127.0.0.1/content/repositories/test-005 + Thu May 02 11:39:43 BST 2013 + n/a + maven2 + releases + My Releases + + + 3e1e1bad64f + test + repository + test-002 + test-002 (u:deployuser, a:127.0.1.2) + closed + release + deploy_release + Maven 3.0.4 + 127.0.3.1 + http://127.0.0.1/content/repositories/test-002 + Mon Apr 08 08:30:53 BST 2013 + Mon Apr 08 08:36:54 BST 2013 + A description of stage 002 + maven2 + releases + My Releases + + + 3e1e1bad64f + test + repository + test-003 + test-003 (u:deployuser, a:127.0.1.2) + closed + release + deploy_release + Maven 3.0.4 + 127.0.3.1 + http://127.0.0.1/content/repositories/test-003 + Tue Apr 09 15:00:21 BST 2013 + Tue Apr 09 15:24:41 BST 2013 + A description of stage 003 + maven2 + releases + My Releases + + + 3e1e1bad64f + test + repository + test-004 + test-004 (u:deployuser, a:127.0.1.2) + closed + release + deploy_release + Maven 3.0.4 + 127.0.3.1 + http://127.0.0.1/content/repositories/test-004 + Fri Apr 12 13:50:05 BST 2013 + Fri Apr 12 14:05:57 BST 2013 + A description of stage 004 + maven2 + releases + My Releases + + + \ No newline at end of file diff --git a/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/repository__transitioned.xml b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/repository__transitioned.xml new file mode 100644 index 0000000..1aae963 --- /dev/null +++ b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/repository__transitioned.xml @@ -0,0 +1,24 @@ + + 256fd5bb949 + testprofile + repository + testprofile-1002 + closed + release + admin + Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36 + 127.0.0.1 + http://127.0.0.1:8081/nexus/content/repositories/testprofile-1002 + 2013-06-12T08:31:08.703-07:00 + Wed Jun 12 08:31:08 PDT 2013 + 1371051068703 + 2013-06-13T08:41:49.110-07:00 + Thu Jun 13 08:41:49 PDT 2013 + 1371138109110 + mydescription + maven2 + releases + Releases + 4 + false + \ No newline at end of file diff --git a/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/repository__transitioning.xml b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/repository__transitioning.xml new file mode 100644 index 0000000..0a7b23a --- /dev/null +++ b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/repository__transitioning.xml @@ -0,0 +1,24 @@ + + 256fd5bb949 + testprofile + repository + testprofile-1002 + open + release + admin + Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36 + 127.0.0.1 + http://127.0.0.1:8081/nexus/content/repositories/testprofile-1002 + 2013-06-12T08:31:08.703-07:00 + Wed Jun 12 08:31:08 PDT 2013 + 1371051068703 + 2013-06-13T08:41:49.110-07:00 + Thu Jun 13 08:41:49 PDT 2013 + 1371138109110 + mydescription + maven2 + releases + Releases + 4 + true + \ No newline at end of file diff --git a/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/status__bad_perms.xml b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/status__bad_perms.xml new file mode 100644 index 0000000..f614d04 --- /dev/null +++ b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/status__bad_perms.xml @@ -0,0 +1,374 @@ + + + Sonatype Nexus Professional + Sonatype Nexus&trade; Professional Edition, Version: 2.5.0-04 + 2.5.0-04 + 2.5.0-04 + Professional + PRO + http://links.sonatype.com/products/nexus/pro/attributions + http://links.sonatype.com/products/nexus/pro/store + http://links.sonatype.com/products/nexus/pro/eula + STARTED + STANDALONE + 2013-06-13 14:38:12.315 UTC + 2013-06-13 14:38:20.528 UTC + 2013-06-13 14:38:20.527 UTC + false + false + false + + false + + + nexus:healthcheck + 1 + + + nexus:stagingdrop + 0 + + + nexus:authentication + 0 + + + security:userssetpw + 0 + + + security:users + 0 + + + nexus:stagingstart + 0 + + + security:privilegetypes + 0 + + + security:userschangepw + 0 + + + nexus:capabilities + 0 + + + nexus:pgpcache + 0 + + + security:usersforgotid + 9 + + + nexus:index + 1 + + + nexus:targets + 0 + + + nexus:stagingruletypes + 0 + + + nexus:settingstemplates + 0 + + + nexus:ssl:truststore + 0 + + + nexus:metadata + 0 + + + nexus:smartproxy:pub-sub + 0 + + + nexus:smartproxy:trusted-keys + 0 + + + nexus:stagingfinish + 0 + + + nexus:yumAlias + 0 + + + nexus:stagingrulesets + 0 + + + nexus:componentrealmtypes + 0 + + + nexus:logconfig + 0 + + + nexus:usertoken:users + 0 + + + nexus:browseremote + 1 + + + nexus:configuration + 0 + + + nexus:repositories + 1 + + + nexus:pgpconfig + 0 + + + nexus:repositorymirrorsstatus + 0 + + + security:* + 0 + + + nexus:archivefiles + 1 + + + nexus:logs + 0 + + + nexus:usertoken:user + 0 + + + nexus:componentsrepotypes + 1 + + + nexus:stagingupload + 0 + + + nexus:command + 0 + + + security:usersreset + 0 + + + nexus:repositorymirrors + 0 + + + nexus:usertoken:settings + 0 + + + nexus:feeds + 0 + + + nexus:metrics-endpoints + 0 + + + nexus:attributes + 0 + + + security:ldapconfig + 0 + + + nexus:stagingprofilerepos + 0 + + + nexus:stagingdeploy + 0 + + + nexus:artifact + 1 + + + nexus:healthchecksummary + 1 + + + nexus:tasksrun + 0 + + + nexus:settings + 0 + + + nexus:wastebasket + 0 + + + nexus:procurementtree + 0 + + + nexus:stagingprofileorder + 0 + + + nexus:tasktypes + 0 + + + nexus:licensing + 0 + + + nexus:repotemplates + 0 + + + nexus:healthcheckalerts + 3 + + + nexus:procurementresolution + 0 + + + nexus:smartproxy:settings + 0 + + + security:privileges + 0 + + + nexus:tasks + 0 + + + nexus:stagingbundleupload + 0 + + + nexus:yumVersionedRepositories + 0 + + + nexus:* + 0 + + + nexus:repositorypredefinedmirrors + 0 + + + security:componentsuserlocatortypes + 0 + + + nexus:routes + 0 + + + security:roles + 0 + + + nexus:repogroups + 1 + + + nexus:componentscheduletypes + 0 + + + security:usersforgotpw + 9 + + + nexus:stagingprofiles + 0 + + + nexus:cache + 0 + + + nexus:usertoken:current + 0 + + + apikey:access + 0 + + + nexus:identify + 1 + + + nexus:repostatus + 1 + + + nexus:procurementrepos + 0 + + + nexus:stagingpromote + 0 + + + nexus:dependency-report + 1 + + + nexus:capabilityTypes + 0 + + + nexus:repometa + 0 + + + nexus:componentscontentclasses + 1 + + + nexus:healthcheckdetail + 0 + + + nexus:status + 1 + + + nexus:pluginconsoleplugininfos + 0 + + + + http://127.0.0.1:8081/nexus + true + false + false + + \ No newline at end of file diff --git a/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/status__ok_perms.xml b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/status__ok_perms.xml new file mode 100644 index 0000000..a2fd6da --- /dev/null +++ b/src/test/resources/org/jvnet/hudson/plugins/m2release/nexus/stageClientTest/status__ok_perms.xml @@ -0,0 +1,377 @@ + + + + Sonatype Nexus Professional + Sonatype Nexus&trade; Professional Edition, Version: 2.5.0-04 + 2.5.0-04 + 2.5.0-04 + Professional + PRO + http://links.sonatype.com/products/nexus/pro/attributions + http://links.sonatype.com/products/nexus/pro/store + http://links.sonatype.com/products/nexus/pro/eula + STARTED + STANDALONE + 2013-06-13 14:38:12.315 UTC + 2013-06-13 14:38:20.528 UTC + 2013-06-13 14:38:20.527 UTC + false + false + false + + true + admin + default + + + nexus:healthcheck + 15 + + + nexus:stagingdrop + 15 + + + nexus:authentication + 15 + + + security:userssetpw + 15 + + + security:users + 15 + + + nexus:stagingstart + 15 + + + security:privilegetypes + 15 + + + security:userschangepw + 15 + + + nexus:capabilities + 15 + + + nexus:pgpcache + 15 + + + nexus:targets + 15 + + + nexus:index + 15 + + + security:usersforgotid + 15 + + + nexus:stagingruletypes + 15 + + + nexus:settingstemplates + 15 + + + nexus:ssl:truststore + 15 + + + nexus:metadata + 15 + + + nexus:stagingfinish + 15 + + + nexus:smartproxy:pub-sub + 15 + + + nexus:yumAlias + 15 + + + nexus:smartproxy:trusted-keys + 15 + + + nexus:componentrealmtypes + 15 + + + nexus:stagingrulesets + 15 + + + nexus:logconfig + 15 + + + nexus:usertoken:users + 15 + + + nexus:browseremote + 15 + + + nexus:configuration + 15 + + + nexus:repositories + 15 + + + nexus:pgpconfig + 15 + + + nexus:repositorymirrorsstatus + 15 + + + security:* + 15 + + + nexus:archivefiles + 15 + + + nexus:logs + 15 + + + nexus:usertoken:user + 15 + + + nexus:componentsrepotypes + 15 + + + nexus:stagingupload + 15 + + + nexus:command + 15 + + + nexus:usertoken:settings + 15 + + + nexus:repositorymirrors + 15 + + + security:usersreset + 15 + + + nexus:feeds + 15 + + + nexus:metrics-endpoints + 15 + + + nexus:attributes + 15 + + + security:ldapconfig + 15 + + + nexus:stagingprofilerepos + 15 + + + nexus:artifact + 15 + + + nexus:stagingdeploy + 15 + + + nexus:healthchecksummary + 15 + + + nexus:tasksrun + 15 + + + nexus:settings + 15 + + + nexus:wastebasket + 15 + + + nexus:procurementtree + 15 + + + nexus:stagingprofileorder + 15 + + + nexus:tasktypes + 15 + + + nexus:licensing + 15 + + + nexus:repotemplates + 15 + + + nexus:healthcheckalerts + 15 + + + nexus:procurementresolution + 15 + + + nexus:smartproxy:settings + 15 + + + security:privileges + 15 + + + nexus:tasks + 15 + + + nexus:stagingbundleupload + 15 + + + nexus:yumVersionedRepositories + 15 + + + nexus:* + 15 + + + nexus:repositorypredefinedmirrors + 15 + + + security:componentsuserlocatortypes + 15 + + + nexus:routes + 15 + + + security:roles + 15 + + + nexus:repogroups + 15 + + + nexus:componentscheduletypes + 15 + + + security:usersforgotpw + 15 + + + nexus:stagingprofiles + 15 + + + nexus:cache + 15 + + + nexus:usertoken:current + 15 + + + apikey:access + 15 + + + nexus:identify + 15 + + + nexus:procurementrepos + 15 + + + nexus:repostatus + 15 + + + nexus:stagingpromote + 15 + + + nexus:dependency-report + 15 + + + nexus:capabilityTypes + 15 + + + nexus:componentscontentclasses + 15 + + + nexus:repometa + 15 + + + nexus:healthcheckdetail + 15 + + + nexus:status + 15 + + + nexus:pluginconsoleplugininfos + 15 + + + + http://127.0.0.1:8081/nexus + true + false + false + + \ No newline at end of file