All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jvnet.hudson.plugins.m2release.M2ReleaseBuildWrapper Maven / Gradle / Ivy

/*
 * The MIT License
 * 
 * Copyright (c) 2009, 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;

import hudson.Extension;
import hudson.Launcher;
import hudson.Util;
import hudson.maven.AbstractMavenProject;
import hudson.maven.MavenBuild;
import hudson.maven.MavenModule;
import hudson.maven.MavenModuleSet;
import hudson.maven.MavenModuleSetBuild;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildListener;
import hudson.model.Descriptor;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.security.Permission;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrapperDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import hudson.util.FormValidation.URLCheck;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Map;

import javax.servlet.ServletException;

import net.sf.json.JSONObject;

import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import org.sonatype.nexus.restlight.common.RESTLightClientException;
import org.sonatype.nexus.restlight.stage.StageClient;
import org.sonatype.nexus.restlight.stage.StageRepository;

/**
 * Wraps a {@link MavenBuild} to be able to run the maven release plugin on demand, with the
 * ability to auto close a Nexus Pro Staging Repo
 * 
 * @author James Nord
 * @version 0.3
 * @since 0.1
 */
public class M2ReleaseBuildWrapper extends BuildWrapper {

	private transient boolean             doRelease           = false;
	private transient boolean             closeNexusStage     = true;
	private transient Map versions;
	private transient boolean             appendHudsonBuildNumber;
	private transient String              repoDescription;
	
	public String                         releaseGoals        = DescriptorImpl.DEFAULT_RELEASE_GOALS;
	

	@DataBoundConstructor
	public M2ReleaseBuildWrapper(String releaseGoals) {
		super();
		this.releaseGoals = releaseGoals;
	}


	@Override
	public Environment setUp(AbstractBuild build, Launcher launcher, final BuildListener listener)
	                                                                                              throws IOException,
	                                                                                              InterruptedException {
		final String originalGoals;
		MavenModuleSet mmSet;
		final String mavenOpts;
		
		synchronized (getModuleSet(build)) {
			if (!doRelease) {
				// we are not performing a release so don't need a custom tearDown.
				return new Environment() {
					/** intentionally blank */
				};
			}
			// reset for the next build.
			doRelease = false;

			mmSet = getModuleSet(build);
			if (mmSet != null) {
				originalGoals = mmSet.getGoals();
				mmSet.setGoals(releaseGoals);

				if (versions != null) {
					mmSet.setGoals(generateVersionString(build.getNumber()) + releaseGoals);
				}
				else {
					mmSet.setGoals(releaseGoals);
				}
			}
			else {
				// can this be so?
				originalGoals = null;
			}
			mavenOpts = mmSet.getMavenOpts();
		}
		
		return new Environment() {

			@Override
			public void buildEnvVars(java.util.Map env) {
				if (mavenOpts != null && !env.containsKey("MAVEN_OPTS")) {
					env.put("MAVEN_OPTS", mavenOpts);
				}
			};


			@Override
			public boolean tearDown(AbstractBuild bld, BuildListener lstnr) throws IOException,
			                                                               InterruptedException {
				boolean retVal = true;
				// TODO only re-set the build goals if they are still releaseGoals to avoid mid-air collisions.
				final MavenModuleSet mmSet = getModuleSet(bld);
				final boolean localcloseStage;
				synchronized (mmSet) {
					mmSet.setGoals(originalGoals);
					// get a local variable so we don't have to synchronise on mmSet any more than we have to.
					localcloseStage = closeNexusStage;
					versions = null;
				}

				if (localcloseStage) {
					// nexus client tries to load the vocab using the contextClassLoader.
					ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
					ClassLoader newLoader = StageClient.class.getClassLoader();
					Thread.currentThread().setContextClassLoader(newLoader);
					try {
						StageClient client = new StageClient(getDescriptor().getNexusURL(),
						                                     getDescriptor().getNexusUser(),
						                                     getDescriptor().getNexusPassword());
						MavenModule rootModule = mmSet.getRootModule();
						// TODO add support for a user supplied comment.
						StageRepository repo = client.getOpenStageRepositoryForUser(rootModule.getModuleName().groupId,
						                                                            rootModule.getModuleName().artifactId,
						                                                            repoDescription);
						if (repo != null) {
							lstnr.getLogger().append("[M2Release] Closing repository " + repo.getRepositoryId() + " at "
							                             + repo.getUrl());
							client.finishRepository(repo, "Stage for: " + rootModule.getDisplayName());
							lstnr.getLogger().append("[M2Release] Closed staging repository.");
						}
						else {
							retVal = false;
							lstnr.fatalError("[M2Release] Could not find nexus stage repository for project.\n");
						}
					}
					catch (RESTLightClientException ex) {
						lstnr.fatalError("[M2Release] Could not close repository , %s\n", ex.toString());
						retVal = false;
					}
					finally {
						Thread.currentThread().setContextClassLoader(originalLoader);
					}
				}
				return retVal;
			}
		};
	}


	void enableRelease() {
		doRelease = true;
	}


	void setVersions(Map versions) {
		// expects a map of key="-Dproject.rel.${m.moduleName}" value="version"
		this.versions = versions;
	}


	public void setAppendHudsonBuildNumber(boolean appendHudsonBuildNumber) {
		this.appendHudsonBuildNumber = appendHudsonBuildNumber;
	}

	public void setCloseNexusStage(boolean closeNexusStage) {
		this.closeNexusStage = closeNexusStage;
	}

	public void setRepoDescription(String repoDescription) {
		this.repoDescription = repoDescription;
	}


	private String generateVersionString(int buildNumber) {
		// -Dproject.rel.org.mycompany.group.project=version ....
		StringBuilder sb = new StringBuilder();
		for (String key : versions.keySet()) {
			sb.append(key);
			sb.append('=');
			sb.append(versions.get(key));
			if (appendHudsonBuildNumber && key.startsWith("-Dproject.rel")) { //$NON-NLS-1$
				sb.append('-');
				sb.append(buildNumber);
			}
			sb.append(' ');
		}
		return sb.toString();
	}


	private String getReleaseVersion(MavenModule moduleName) {
		String retVal = null;
		String key = "-Dproject.rel." + moduleName.getModuleName().toString();
		retVal = versions.get(key);
		if (retVal == null) {
			// we are auto versioning...
			retVal = moduleName.getVersion().replace("-SNAPSHOT", ""); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return retVal;
	}


	private MavenModuleSet getModuleSet(AbstractBuild build) {
		if (build instanceof MavenBuild) {
			MavenBuild m2Build = (MavenBuild) build;
			MavenModule mm = m2Build.getProject();
			MavenModuleSet mmSet = mm.getParent();
			return mmSet;
		}
		else if (build instanceof MavenModuleSetBuild) {
			MavenModuleSetBuild m2moduleSetBuild = (MavenModuleSetBuild) build;
			MavenModuleSet mmSet = m2moduleSetBuild.getProject();
			return mmSet;
		}
		else {
			return null;
		}
	}


	@Override
	public Action getProjectAction(AbstractProject job) {
		return new M2ReleaseAction((MavenModuleSet) job);
	}

	public static boolean hasReleasePermission(AbstractProject job) {
		return job.hasPermission(DescriptorImpl.CREATE_RELEASE);
	}


	public static void checkReleasePermission(AbstractProject job) {
		job.checkPermission(DescriptorImpl.CREATE_RELEASE);
	}

  /**
	 * Hudson defines a method {@link Builder#getDescriptor()}, which returns the corresponding
	 * {@link Descriptor} object. Since we know that it's actually {@link DescriptorImpl}, override the method
	 * and give a better return type, so that we can access {@link DescriptorImpl} methods more easily. This is
	 * not necessary, but just a coding style preference.
	 */
	@Override
	public DescriptorImpl getDescriptor() {
		// see Descriptor javadoc for more about what a descriptor is.
		return (DescriptorImpl) super.getDescriptor();
	}


	@Extension
	public static class DescriptorImpl extends BuildWrapperDescriptor {

		public static final String     DEFAULT_RELEASE_GOALS = "-Dresume=false release:prepare release:perform"; //$NON-NLS-1$
		public static final Permission CREATE_RELEASE        = new Permission(Item.PERMISSIONS,
		                                                                      "Release", //$NON-NLS-1$
		                                                                      Messages._CreateReleasePermission_Description(),
		                                                                      Hudson.ADMINISTER); 

		private boolean nexusSupport  = false;
		private String  nexusURL      = null;
		private String  nexusUser     = "deployment";                                    //$NON-NLS-1$
		private String  nexusPassword = "deployment123";                                 //$NON-NLS-1$
		


		public DescriptorImpl() {
			super(M2ReleaseBuildWrapper.class);
			load();
		}


		@Override
		public boolean isApplicable(AbstractProject item) {
			return (item instanceof AbstractMavenProject);
		}

		@Override
		public boolean configure(StaplerRequest staplerRequest, JSONObject json) throws FormException {
			nexusSupport = json.containsKey("nexusSupport"); //$NON-NLS-1$
			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);
				}
				nexusUser = Util.fixEmpty(nexusParams.getString("nexusUser")); //$NON-NLS-1$
				nexusPassword = nexusParams.getString("nexusPassword"); //$NON-NLS-1$
			}
			save();
			return true; // indicate that everything is good so far
		}

		@Override
		public String getDisplayName() {
			return Messages.Wrapper_DisplayName();
		}


		public String getNexusURL() {
			return nexusURL;
		}


		public String getNexusUser() {
			return nexusUser;
		}


		public String getNexusPassword() {
			return nexusPassword;
		}


		public boolean isNexusSupport() {
			return nexusSupport;
		}

		/**
		 * Checks if the Nexus URL exists and we can authenticate against it.
		 */
		public FormValidation doUrlCheck(@QueryParameter String urlValue, 
		                                  @QueryParameter String usernameValue,
		                                  @QueryParameter String passwordValue) throws IOException,
		                                                                      ServletException {
			// this method can be used to check if a file exists anywhere in the file system,
			// so it should be protected.
			if (!Hudson.getInstance().hasPermission(Hudson.ADMINISTER)) {
				return FormValidation.ok();
			}
			
			urlValue = Util.fixEmptyAndTrim(urlValue);
			if (urlValue == null) {
				return FormValidation.ok();
			}
			final String testURL;
			if (urlValue.endsWith("/")) {
				testURL = urlValue.substring(0, urlValue.length() - 1);
			}
			else {
				testURL = urlValue;
			}

			FormValidation httpCheck = new URLCheck() {
				@Override
				protected FormValidation check() throws IOException, ServletException{
					String v = testURL;
					URL url= null;
					try {
						url = new URL(v);
						if (!(url.getProtocol().equals("http") || url.getProtocol().equals("https"))) {
							return FormValidation.error("protocol must be http or https");
						}
						HttpURLConnection con = (HttpURLConnection) url.openConnection();
						int status = con.getResponseCode();
						con.disconnect();
						if (status != 200) {
							return FormValidation.error("Error communicating with server (web server returned " + status + ")");
						}
						if (findText(open(url), "Sonatype Nexus™ Professional Edition")) { //$NON-NLS-1$
							return FormValidation.ok();
						}
						else if (findText(open(url), "Sonatype Nexus")) { //$NON-NLS-1$
							return FormValidation.error("This is a valid Nexus URL but it looks like Nexus OSS Edition");
						}
						else {
							return FormValidation.error("This is a valid URL but it doesn't look like Nexus Professional");
						}
					}
					catch (MalformedURLException ex) {
						return FormValidation.error(v + " is not a valid URL");
					}
					catch (UnknownHostException ex) {
						return FormValidation.error("Could not resolve host \"" + url.getHost() + '\"');
					}
					catch (IOException e) {
						return handleIOException(v, e);
					}
				}
			}.check();
			switch (httpCheck.kind) {
				case ERROR:
				case WARNING:
					return httpCheck;
				case OK:
					// keep checking
					break;
			}
			try {
				StageClient client = new StageClient(testURL, usernameValue, passwordValue);
				client.getOpenStageRepositoriesForUser();
			}
			catch (RESTLightClientException e) {
				FormValidation stageError = FormValidation.error(e.getMessage());
				stageError.initCause(e);
				return stageError; 
			}
			return FormValidation.ok();
		}
		

	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy