/*
* The MIT License
*
* Copyright (c) 2103, Peter Hayes
*
* 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 hudson.plugins.promoted_builds.parameters;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.AutoCompletionCandidates;
import hudson.model.Item;
import hudson.model.Job;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterValue;
import hudson.model.Run;
import hudson.model.SimpleParameterDefinition;
import hudson.plugins.promoted_builds.JobPropertyImpl;
import hudson.plugins.promoted_builds.PromotedBuildAction;
import hudson.plugins.promoted_builds.PromotedProjectAction;
import hudson.plugins.promoted_builds.PromotionProcess;
import hudson.plugins.promoted_builds.util.ItemPathResolver;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import hudson.util.ListBoxModel.Option;
import hudson.util.RunList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
/**
* Defines a parameter that allows the user to select a promoted build
* from a drop down list.
* <p>
* Remarks on addressing:
* Starting from TODO, the field also supports folders and the relative addressing (JENKINS-25011).
* See {@link ItemPathResolver#getByPath(java.lang.String, hudson.model.Item, java.lang.Class)}
* for the documentation.
* </p>
* @author Pete Hayes
*/
public class PromotedBuildParameterDefinition extends SimpleParameterDefinition {
/**
* Absolute path to the item starting from the root element.
* See format clarification in the {@link PromotedBuildParameterDefinition}.
*/
private final String projectName;
private final String promotionProcessName;
@DataBoundConstructor
public PromotedBuildParameterDefinition(String name, String jobName, String process, String description) {
super(name, description);
this.projectName = jobName;
this.promotionProcessName = process;
}
@Override
public PromotedBuildParameterValue createValue(StaplerRequest req, JSONObject jo) {
PromotedBuildParameterValue value = req.bindJSON(PromotedBuildParameterValue.class, jo);
value.setPromotionProcessName(promotionProcessName);
value.setDescription(getDescription());
return value;
}
public PromotedBuildParameterValue createValue(String value) {
PromotedBuildParameterValue p = new PromotedBuildParameterValue(getName(), value, getDescription());
p.setPromotionProcessName(promotionProcessName);
return p;
}
@Override
public PromotedBuildParameterValue getDefaultParameterValue() {
final List builds = getBuilds();
if (builds.isEmpty()) {
return null;
}
return createValue(((Run) builds.get(0)).getExternalizableId());
}
@Override
public ParameterDefinition copyWithDefaultValue(ParameterValue defaultValue) {
if (defaultValue instanceof PromotedBuildParameterValue) {
PromotedBuildParameterValue value = (PromotedBuildParameterValue) defaultValue;
return new PromotedBuildParameterDefinition(getName(), value.getRunId(), value.getPromotionProcessName(), getDescription());
} else {
return this;
}
}
/**
* Absolute path to the item starting from the root element.
*/
@Exported
public String getJobName() {
return projectName;
}
@Exported
public String getProcess() {
return promotionProcessName;
}
/**
* Gets a list of promoted builds for the project.
* @return List of {@link AbstractBuild}s, which have been promoted
* @deprecated This method retrieves the base item for relative addressing from
* the {@link StaplerRequest}. The relative addressing may be malfunctional if
* you use this method outside {@link StaplerRequest}s.
* Use {@link #getRuns(hudson.model.Item)} instead
*/
@Nonnull
@Deprecated
public List getBuilds() {
// Try to get ancestor from the object, otherwise pass null and disable the relative addressing
final StaplerRequest currentRequest = Stapler.getCurrentRequest();
final Item item = currentRequest != null ? currentRequest.findAncestorObject(Item.class) : null;
return getRuns(item);
}
/**
* Gets a list of promoted builds for the project.
* @param base Base item for the relative addressing
* @return List of {@link AbstractBuild}s, which have been promoted.
* May return an empty list if {@link Jenkins} instance is not ready
* @since 2.22
*/
@Nonnull
public List<Run<?,?>> getRuns(@CheckForNull Item base) {
final List<Run<?,?>> runs = new ArrayList<Run<?,?>>();
final Jenkins jenkins = Jenkins.getInstance();
if (jenkins == null) {
return runs;
}
// JENKINS-25011: also look for jobs in folders.
final AbstractProject<?,?> job = ItemPathResolver.getByPath(projectName, base, AbstractProject.class);
if (job == null) {
return runs;
}
PromotedProjectAction promotedProjectAction = job.getAction(PromotedProjectAction.class);
if (promotedProjectAction == null) {
return runs;
}
for (Run<?,?> run : job.getBuilds()) {
List<PromotedBuildAction> actions = run.getActions(PromotedBuildAction.class);
for (PromotedBuildAction buildAction : actions) {
if (buildAction.contains(promotionProcessName)) {
runs.add(run);
break;
}
}
}
return runs;
}
@Extension
public static class DescriptorImpl extends ParameterDescriptor {
@Override
public String getDisplayName() {
return "Promoted Build Parameter";
}
@Override
public String getHelpFile() {
return "/plugin/promoted-builds/parameter/promotion.html";
}
@Override
public ParameterDefinition newInstance(StaplerRequest req, JSONObject formData) throws FormException {
return req.bindJSON(PromotedBuildParameterDefinition.class, formData);
}
/**
* Checks the job name.
*/
public FormValidation doCheckJobName(@AncestorInPath Item project, @QueryParameter String value ) {
final Jenkins jenkins = Jenkins.getInstance();
if (jenkins == null) {
return FormValidation.error("Jenkins instance is not ready");
}
if (!project.hasPermission(Item.CONFIGURE) && project.hasPermission(Item.EXTENDED_READ)) {
return FormValidation.ok();
}
project.checkPermission(Item.CONFIGURE);
if (StringUtils.isNotBlank(value)) {
// JENKINS-25011: also look for jobs in folders.
final AbstractProject p = ItemPathResolver.getByPath(value, project, AbstractProject.class);
if (p==null) {
// suggest full name so that getBuilds() can find item.
AbstractProject nearest = AbstractProject.findNearest(value, project.getParent());
return FormValidation.error( nearest != null
? hudson.tasks.Messages.BuildTrigger_NoSuchProject(value, nearest.getFullName())
: hudson.plugins.promoted_builds.Messages.Shared_noSuchProject(value));
}
}
return FormValidation.ok();
}
@Restricted(NoExternalUse.class)
public AutoCompletionCandidates doAutoCompleteJobName(@AncestorInPath @CheckForNull Item project,
@QueryParameter String value) {
final AutoCompletionCandidates candidates = new AutoCompletionCandidates();
final Jenkins jenkins = Jenkins.getInstance();
if (jenkins == null || project == null || !project.hasPermission(Item.CONFIGURE)) {
return candidates;
}
// JENKINS-25011: look for jobs in all folders.
//TODO: remove prefixes
for (AbstractProject job: jenkins.getAllItems(AbstractProject.class)) {
if (job.getFullName().contains(value)) {
if (job.hasPermission(Item.READ)) {
// suggest full name so that getBuilds() can find item.
candidates.add(job.getFullName());
}
}
}
return candidates;
}
/**
* Fills in the available promotion processes.
*/
public ListBoxModel doFillProcessItems(@AncestorInPath Job defaultJob, @QueryParameter("jobName") String jobName) {
final ListBoxModel r = new ListBoxModel();
final Jenkins jenkins = Jenkins.getInstance();
if (jenkins == null) {
return r;
}
if (!defaultJob.hasPermission(Item.CONFIGURE) && defaultJob.hasPermission(Item.EXTENDED_READ)) {
return r;
}
defaultJob.checkPermission(Item.CONFIGURE);
AbstractProject<?,?> j = null;
if (jobName != null) {
j = ItemPathResolver.getByPath(jobName, defaultJob, AbstractProject.class);
}
if (j!=null) {
JobPropertyImpl pp = j.getProperty(JobPropertyImpl.class);
if (pp!=null) {
for (PromotionProcess proc : pp.getActiveItems()) {
// Note: why not list all items instead of active ones?
// this would allow to configure the job even
// if a promotion hasn't happened (yet).
r.add(new Option(proc.getDisplayName(),proc.getName()));
}
}
}
return r;
}
}
}