/**
* The MIT License
* Copyright (c) 2014 Halil-Cem Guersoy and all contributors
*
* 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.jenkinsci.plugins.artifactpromotion;
import hudson.Extension;
import hudson.Launcher;
import hudson.model.BuildListener;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import hudson.util.Secret;
import hudson.util.ListBoxModel;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.jenkinsci.plugins.artifactpromotion.exception.PromotionException;
import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException;
import org.jenkinsci.plugins.tokenmacro.TokenMacro;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
/**
* In this class we encapsulate the process of moving an artifact from one
* repository into another one.
*
* @author Halil-Cem Guersoy (hcguersoy@gmail.com)
*
*/
public class ArtifactPromotionBuilder extends Builder {
/**
* The POM extension.
*/
public static final String POMTYPE = "pom";
private final String groupId;
private final String artifactId;
private final String classifier;
private final String version;
private final String extension;
/**
* The location of the local repository system relative to the workspace. In
* this repository the downloaded artifact will be saved.
*/
private final String localRepoLocation = "target" + File.separator
+ "local-repo";
/**
* Name of the promoter class.
*/
private final String promoterClass;
// Fields for UI
/**
* The repository there the artifact is. In a normal case a staging
* repository.
*/
private final String stagingRepository;
/**
* The User for the staging Repository
*/
private final String stagingUser;
/**
* The staging secret. We should still save the passwords using the
* credentials plugin but its so bad documented :-(
*/
private final Secret stagingPW;
/**
* The user for the release repo.
*/
private final String releaseUser;
/**
* The release repo secret
*/
private final Secret releasePW;
/**
* The repository into the artifact has to be moved.
*/
private final String releaseRepository;
/**
* Flag to write more info in the job console.
*/
private final boolean debug;
/**
* If true don't delete the artifact from the source repository.
*/
private boolean skipDeletion;
/**
* The default constructor. The parameters are injected by jenkins builder
* and are the same as the (private) fields.
*
* @param groupId
* The groupId of the artifact
* @param artifactId
* The artifactId of the artifact.
* @param classifier
* The classifier of the artifact.
* @param version
* The version of the artifact.
* @param extension
* The file extension of the artifact.
* @param stagingRepository
* The URL of the staging repository.
* @param stagingUser
* User to be used on staging repo.
* @param stagingPW
* Password to be used on staging repo.
* @param releaseUser
* User to be used on release repo.
* @param releasePW
* Password to be used on release repo.
* @param releaseRepository
* The URL of the staging repository
* @param promoterClass
* The vendor specific class which is used for the promotion, e.g. for NexusOSS
* @param debug
* Flag for debug output. Currently not used.
*/
@DataBoundConstructor
public ArtifactPromotionBuilder(String groupId, String artifactId, String classifier,
String version, String extension, String stagingRepository,
String stagingUser, Secret stagingPW, String releaseUser,
Secret releasePW, String releaseRepository, String promoterClass,
boolean debug, boolean skipDeletion) {
this.groupId = groupId;
this.artifactId = artifactId;
this.classifier = classifier;
this.version = version;
this.extension = extension == null ? "jar" : extension;
this.stagingRepository = stagingRepository;
this.stagingUser = stagingUser;
this.stagingPW = stagingPW;
this.releaseUser = releaseUser;
this.releasePW = releasePW;
this.releaseRepository = releaseRepository;
this.debug = debug;
this.promoterClass = promoterClass;
this.skipDeletion = skipDeletion;
}
@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener) {
PrintStream logger = listener.getLogger();
AbstractPromotor artifactPromotor = null;
// Initialize the promoter class
// moved to here as the constructor of the builder is bypassed by stapler
try {
if (debug) {
logger.println("Used promoter class: " + promoterClass);
}
artifactPromotor = (AbstractPromotor) Jenkins.getInstance()
.getExtensionList(this.promoterClass).iterator().next();
} catch (ClassNotFoundException e) {
logger.println("ClassNotFoundException - unable to pick correct promotor class: " + e );
throw new RuntimeException(e);
}
if (artifactPromotor == null) {
logger.println("artifactPromotor is null - ABORTING!");
return false;
}
artifactPromotor.setListener(listener);
Map<PromotionBuildTokens, String> expandedTokens = expandTokens(build,
listener);
if (expandedTokens == null) {
logger.println("Could not expand tokens - ABORTING!");
return false;
}
artifactPromotor.setExpandedTokens(expandedTokens);
artifactPromotor.setReleasePassword(releasePW);
artifactPromotor.setReleaseUser(releaseUser);
artifactPromotor.setStagingPassword(stagingPW);
artifactPromotor.setStagingUser(stagingUser);
artifactPromotor.setSkipDeletion(skipDeletion);
String localRepoPath = build.getWorkspace() + File.separator
+ this.localRepoLocation;
artifactPromotor.setLocalRepositoryURL(localRepoPath);
if (debug) {
logger.println("Local repository path: [" + localRepoPath + "]");
}
try {
artifactPromotor.callPromotor(launcher.getChannel());
} catch (PromotionException e) {
logger.println(e.getMessage());
return false;
}
return true;
}
/**
* Expands needed build tokens
*
* @param build
* @param listener
* @return Map<PromotionBuildTokens, String> of expanded tokens
* @throws TokenExpansionException
* if token expansion fails
*/
private Map<PromotionBuildTokens, String> expandTokens(
AbstractBuild<?, ?> build, BuildListener listener) {
PrintStream logger = listener.getLogger();
Map<PromotionBuildTokens, String> tokens = new HashMap<PromotionBuildTokens, String>();
try {
tokens.put(PromotionBuildTokens.GROUP_ID,
TokenMacro.expandAll(build, listener, groupId));
tokens.put(PromotionBuildTokens.ARTIFACT_ID,
TokenMacro.expandAll(build, listener, artifactId));
tokens.put(PromotionBuildTokens.CLASSIFIER,
TokenMacro.expandAll(build, listener, classifier));
tokens.put(PromotionBuildTokens.VERSION,
TokenMacro.expandAll(build, listener, version));
tokens.put(PromotionBuildTokens.EXTENSION, "".equals(extension) ? "jar" :
TokenMacro.expandAll(build, listener, extension));
tokens.put(PromotionBuildTokens.STAGING_REPOSITORY,
TokenMacro.expandAll(build, listener, stagingRepository));
tokens.put(PromotionBuildTokens.RELEASE_REPOSITORY,
TokenMacro.expandAll(build, listener, releaseRepository));
} catch (MacroEvaluationException mee) {
logger.println("Could not evaluate a makro" + mee);
return null;
} catch (IOException ioe) {
logger.println("Got an IOException during evaluation of a makro token"
+ ioe);
return null;
} catch (InterruptedException ie) {
logger.println("Got an InterruptedException during avaluating a makro token"
+ ie);
return null;
}
return tokens;
}
@Override
public ArtifactPromotionDescriptorImpl getDescriptor() {
return (ArtifactPromotionDescriptorImpl) super.getDescriptor();
}
/**
* Descriptor for {@link ArtifactPromotionBuilder}.
*/
@Extension
public static final class ArtifactPromotionDescriptorImpl extends
BuildStepDescriptor<Builder> {
/**
* In order to load the persisted global configuration, you have to call
* load() in the constructor.
*/
public ArtifactPromotionDescriptorImpl() {
load();
}
// Form validation
/**
* Performs on-the-fly validation of the form field 'name'.
*
* @param value
* This parameter receives the value that the user has typed.
* @return Indicates the outcome of the validation. This is sent to the
* browser.
*/
public FormValidation doCheckArtifactId(@QueryParameter String value) {
if (value.length() == 0)
return FormValidation.error("Please set an ArtifactId!");
return FormValidation.ok();
}
public FormValidation doCheckGroupId(@QueryParameter String value) {
if (value.length() == 0)
return FormValidation.error("Please set a GroupId!");
return FormValidation.ok();
}
public FormValidation doCheckVersion(@QueryParameter String value) {
if (value.length() == 0)
return FormValidation
.error("Please set a Version for your artifact!");
return FormValidation.ok();
}
public FormValidation doCheckStagingRepository(
@QueryParameter String value) {
return checkURI(value);
}
public FormValidation doCheckReleaseRepository(
@QueryParameter String value) {
return checkURI(value);
}
/**
* This method checks originally the URL if it is valid. On the way to
* support tokens this behavior is build out. It will be reactivated
* after a general refactoring for better token macro support.
*
* TODO implment a URL validation which works with token macro plugin
*
* @param value
* @return
*/
private FormValidation checkURI(String value) {
if (value.length() == 0) {
return FormValidation
.error("Please set an URL for the staging repository!");
}
return FormValidation.ok();
}
// TODO connectivity tests
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}
/**
* This human readable name is used in the configuration screen.
*/
public String getDisplayName() {
return "Single Artifact Promotion";
}
@Override
public boolean configure(StaplerRequest req, JSONObject formData)
throws FormException {
// To persist global configuration information,
// set that to properties and call save().
// useFrench = formData.getBoolean("useFrench");
// ^Can also use req.bindJSON(this, formData);
// (easier when there are many fields; need set* methods for this,
// like setUseFrench)
// save();
return super.configure(req, formData);
}
/**
* Generates LisBoxModel for available Repository systems
*
* @return available Promoters as ListBoxModel
*/
public ListBoxModel doFillPromoterClassItems() {
ListBoxModel promoterModel = new ListBoxModel();
for (Promotor promotor : Jenkins.getInstance()
.getExtensionList(Promotor.class)) {
promoterModel.add(promotor.getDescriptor().getDisplayName(), promotor
.getClass().getCanonicalName());
}
return promoterModel;
}
}
public String getGroupId() {
return groupId;
}
public String getArtifactId() {
return artifactId;
}
public String getClassifier() {
return classifier;
}
public String getVersion() {
return version;
}
public String getExtension() {
return extension;
}
public String getStagingRepository() {
return stagingRepository;
}
public String getStagingUser() {
return stagingUser;
}
public Secret getStagingPW() {
return stagingPW;
}
public String getReleaseUser() {
return releaseUser;
}
public Secret getReleasePW() {
return releasePW;
}
public String getReleaseRepository() {
return releaseRepository;
}
public boolean isDebug() {
return debug;
}
public boolean isSkipDeletion() {
return skipDeletion;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("ArtifactPromotionBuilder [POMTYPE=");
builder.append(POMTYPE);
builder.append(", groupId=");
builder.append(groupId);
builder.append(", artifactId=");
builder.append(artifactId);
builder.append(", classifier=");
builder.append(classifier);
builder.append(", version=");
builder.append(version);
builder.append(", extension=");
builder.append(extension);
builder.append(", localRepoLocation=");
builder.append(localRepoLocation);
builder.append(", stagingRepository=");
builder.append(stagingRepository);
builder.append(", stagingUser=");
builder.append(stagingUser);
builder.append(", releaseUser=");
builder.append(releaseUser);
builder.append(", releaseRepository=");
builder.append(releaseRepository);
builder.append(", debug=");
builder.append(debug);
builder.append(", skipDeletion=");
builder.append(skipDeletion);
builder.append("]");
return builder.toString();
}
}