From cc5eb36405d3c1e34290f7172c0f3ef917f07647 Mon Sep 17 00:00:00 2001 From: Patrick Schlebusch Date: Wed, 24 Apr 2019 18:15:29 +0200 Subject: [PATCH] Add support for build wrappers in promotions This commit adds support for build wrappers in the promotion processes. The build wrappers can be configured in a new "Promotion Environment" section of the promotion process configuration. This is useful e.g. in order to be able to prepare an environment or inject secrets for the promotion. There are a number of issues calling for this support: [FIXED JENKINS-23977] [FIXED JENKINS-25526] [FIXED JENKINS-43656] For [JENKINS-14169] this change might provide a workaround by allowing to inject environment vars for the promotion either from the configuration or by injecting them from a file passed to the promotion process. --- .../plugins/promoted_builds/Promotion.java | 68 +++++++++++-------- .../promoted_builds/PromotionProcess.java | 63 ++++++++++++----- .../PromotionProcess/process-config.jelly | 11 +++ .../promoted_builds/PromotionProcessTest.java | 1 + 4 files changed, 97 insertions(+), 46 deletions(-) diff --git a/src/main/java/hudson/plugins/promoted_builds/Promotion.java b/src/main/java/hudson/plugins/promoted_builds/Promotion.java index ddd8fa22..394e1adc 100644 --- a/src/main/java/hudson/plugins/promoted_builds/Promotion.java +++ b/src/main/java/hudson/plugins/promoted_builds/Promotion.java @@ -166,8 +166,8 @@ public EnvVars getEnvironment(TaskListener listener) throws IOException, Interru return e; } - - + + /** * Get a user name of the person, who triggered the promotion. * The method tries various sources like {@link UserIdCause} or {@link ManualCondition.Badge}. @@ -189,7 +189,7 @@ public String getUserName() { return nameFromUserIdCause; } - //fallback to badge lookup for compatibility + //fallback to badge lookup for compatibility for (PromotionBadge badget : getStatus().getBadges()) { if (badget instanceof ManualCondition.Badge) { final String nameFromBadge = ((ManualCondition.Badge) badget).getUserName(); @@ -200,14 +200,14 @@ public String getUserName() { } return Jenkins.ANONYMOUS.getName(); } - + /** * Gets ID of the {@link User}, who triggered the promotion. * The method tries various sources like {@link UserIdCause} or {@link ManualCondition.Badge}. * @return ID of the user who triggered the promotion. * If the search fails, returns ID of {@link User#getUnknown()}. - * @since 2.22 + * @since 2.22 */ @Nonnull public String getUserId() { @@ -227,7 +227,7 @@ public String getUserId() { return idFromUserIdCause; } - //fallback to badge lookup for compatibility + //fallback to badge lookup for compatibility for (PromotionBadge badget : getStatus().getBadges()) { if (badget instanceof ManualCondition.Badge) { final String idFromBadge = ((ManualCondition.Badge) badget).getUserId(); @@ -240,7 +240,7 @@ public String getUserId() { } public List getParameterValues(){ - List values=new ArrayList(); + List values=new ArrayList(); ParametersAction parametersAction=getParametersActions(this); if (parametersAction!=null){ ManualCondition manualCondition=(ManualCondition) getProject().getPromotionCondition(ManualCondition.class.getName()); @@ -253,8 +253,8 @@ public List getParameterValues(){ } return values; } - - //fallback to badge lookup for compatibility + + //fallback to badge lookup for compatibility for (PromotionBadge badget:getStatus().getBadges()){ if (badget instanceof ManualCondition.Badge){ return ((ManualCondition.Badge) badget).getParameterValues(); @@ -262,7 +262,7 @@ public List getParameterValues(){ } return Collections.emptyList(); } - + /** * Gets parameter definitions from the {@link ManualCondition}. * @return List of parameter definitions to be presented. @@ -275,7 +275,7 @@ public List getParameterDefinitionsWithValue(){ if (manualCondition == null) { return definitions; } - + for (ParameterValue pvalue:getParameterValues()){ ParameterDefinition pdef=manualCondition.getParameterDefinition(pvalue.getName()); if (pdef == null) { @@ -301,11 +301,11 @@ public void run() { protected class RunnerImpl extends AbstractRunner { final Promotion promotionRun; - + RunnerImpl(final Promotion promotionRun) { this.promotionRun = promotionRun; } - + @Override protected Lease decideWorkspace(Node n, WorkspaceList wsl) throws InterruptedException, IOException { if (getTarget() == null) { @@ -321,7 +321,7 @@ protected Lease decideWorkspace(Node n, WorkspaceList wsl) throws InterruptedExc return Lease.createDummyLease( rootPath.child(getEnvironment(listener).expand(customWorkspace))); } - + TopLevelItem item = (TopLevelItem)getTarget().getProject(); FilePath workspace = n.getWorkspaceFor(item); if (workspace == null) { @@ -360,10 +360,11 @@ protected Result doRun(BuildListener listener) throws Exception { if(!preBuild(listener,project.getBuildSteps())) return Result.FAILURE; - + try { + List wrappers = new ArrayList(project.getBuildWrappers().values()); List params=getParameterValues(); - + if (params!=null){ for(ParameterValue value : params) { BuildWrapper wrapper=value.createBuildWrapper(Promotion.this); @@ -375,20 +376,27 @@ protected Result doRun(BuildListener listener) throws Exception { } } } - + + for( BuildWrapper w : wrappers ) { + Environment e = w.setUp(Promotion.this, launcher, listener); + if(e == null) + return Result.FAILURE; + buildEnvironments.add(e); + } + if(!build(listener,project.getBuildSteps(),target)) return Result.FAILURE; - + return null; } finally { boolean failed = false; - + for(int i = buildEnvironments.size()-1; i >= 0; i--) { if (!buildEnvironments.get(i).tearDown(Promotion.this,listener)) { failed=true; } } - + if(failed) return Result.FAILURE; } @@ -458,7 +466,7 @@ private boolean preBuild(BuildListener listener, List steps) { } return true; } - + } public static final PermissionGroup PERMISSIONS = new PermissionGroup(Promotion.class, Messages._Promotion_Permissions_Title()); @@ -480,9 +488,9 @@ public boolean equals(Object obj) { final Promotion other = (Promotion) obj; return this.getId().equals(other.getId()); } - - - + + + /** * Factory method for creating {@link ParametersAction} * @param parameters @@ -511,7 +519,7 @@ public static void buildParametersAction(@Nonnull List actions, } private static final Logger LOGGER = Logger.getLogger(Promotion.class.getName()); - + /** * Action, which stores promotion parameters. * This class allows defining custom parameters filtering logic, which is @@ -520,12 +528,12 @@ public static void buildParametersAction(@Nonnull List actions, */ @Restricted(NoExternalUse.class) public static class PromotionParametersAction extends ParametersAction { - + private List unfilteredParameters; - + private PromotionParametersAction(List params) { // Pass the parameters upstairs - super(params); + super(params); unfilteredParameters = params; } @@ -533,13 +541,13 @@ private PromotionParametersAction(List params) { public List getParameters() { return Collections.unmodifiableList(filter(unfilteredParameters)); } - + private List filter(List params) { // buildToBePromoted::getParameters() invokes the secured method, hence all // parameters from the promoted build are safe. return params; } - + public static PromotionParametersAction buildFor( @Nonnull AbstractBuild buildToBePromoted, @CheckForNull List promotionParams) { diff --git a/src/main/java/hudson/plugins/promoted_builds/PromotionProcess.java b/src/main/java/hudson/plugins/promoted_builds/PromotionProcess.java index 5623c157..b22c6b75 100644 --- a/src/main/java/hudson/plugins/promoted_builds/PromotionProcess.java +++ b/src/main/java/hudson/plugins/promoted_builds/PromotionProcess.java @@ -11,6 +11,7 @@ import hudson.model.Action; import hudson.model.AutoCompletionCandidates; import hudson.model.ParameterValue; +import hudson.model.BuildableItemWithBuildWrappers; import hudson.model.Cause; import hudson.model.Cause.UserCause; import hudson.model.DependencyGraph; @@ -39,6 +40,8 @@ import hudson.tasks.BuildStep; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; +import hudson.tasks.BuildWrapper; +import hudson.tasks.BuildWrappers; import hudson.tasks.Publisher; import hudson.util.DescribableList; import hudson.util.FormValidation; @@ -55,8 +58,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.annotation.CheckForNull; @@ -67,7 +72,8 @@ * * @author Kohsuke Kawaguchi */ -public final class PromotionProcess extends AbstractProject implements Saveable, Describable { + // BuildableItem, LazyBuildMixIn.LazyLoadingJob, ParameterizedJobMixIn.ParameterizedJob +public final class PromotionProcess extends AbstractProject implements Saveable, Describable, BuildableItemWithBuildWrappers { /** * {@link PromotionCondition}s. All have to be met for a build to be promoted. @@ -92,6 +98,10 @@ public final class PromotionProcess extends AbstractProject buildSteps = new ArrayList(); + private volatile DescribableList> buildWrappers; + private static final AtomicReferenceFieldUpdater buildWrappersSetter + = AtomicReferenceFieldUpdater.newUpdater(PromotionProcess.class,DescribableList.class,"buildWrappers"); + /*package*/ PromotionProcess(JobPropertyImpl property, String name) { super(property, name); } @@ -137,6 +147,7 @@ public void doSetName(String name) { buildSteps = (List)Descriptor.newInstancesFromHeteroList( req, c, "buildStep", (List) PromotionProcess.getAll()); + getBuildWrappersList().rebuild(req, c, BuildWrappers.getFor(this)); icon = c.getString("icon"); if (c.optBoolean("hasAssignedLabel")) { assignedLabel = Util.fixEmptyAndTrim(c.optString("assignedLabelString")); @@ -205,6 +216,21 @@ public DescribableList> getPublishersList() { return new DescribableList>(this); } + public AbstractProject asProject() { + return this; + } + + public Map,BuildWrapper> getBuildWrappers() { + return getBuildWrappersList().toMap(); + } + + public DescribableList> getBuildWrappersList() { + if(buildWrappers == null) { + buildWrappersSetter.compareAndSet(this,null,new DescribableList>(this)); + } + return buildWrappers; + } + protected Class getBuildClass() { return Promotion.class; } @@ -259,7 +285,7 @@ public String getCustomWorkspace() { * Get the icon name, without the extension. It will always return a non null * and non empty string, as "star-gold" is used for compatibility * for older promotions configurations. - * + * * @return the icon name */ public String getIcon() { @@ -269,20 +295,20 @@ public String getIcon() { public String getIsVisible(){ return isVisible; } - + public boolean isVisible(){ if (isVisible == null) return true; - + AbstractProject job = getOwner(); - + if (job == null) return true; - + String expandedIsVisible = isVisible; EnvVars environment = getDefaultParameterValuesAsEnvVars(job); if (environment != null){ expandedIsVisible = environment.expand(expandedIsVisible); } - + if (expandedIsVisible == null){ return true; } @@ -306,12 +332,12 @@ private static EnvVars getDefaultParameterValuesAsEnvVars(AbstractProject owner) } EnvVars.resolve(envVars); } - + return envVars; } /** * Handle compatibility with pre-1.8 configs. - * + * * @param sIcon * the name of the icon used by this promotion; if null or empty, * we return the gold icon for compatibility with previous releases @@ -361,8 +387,8 @@ public List getUnmetConditions(AbstractBuild build) { /** * Checks if all the conditions to promote a build is met. - * - * @param build Build to be checked + * + * @param build Build to be checked * @return * {@code null} if promotion conditions are not met. * otherwise returns a list of badges that record how the promotion happened. @@ -394,7 +420,7 @@ public boolean considerPromotion(AbstractBuild build) throws IOException { * @param build Build to be promoted * @return * {@code null} if the build was not promoted, otherwise Future that kicks in when the build is completed. - * @throws IOException + * @throws IOException */ @CheckForNull public Future considerPromotion2(AbstractBuild build) throws IOException { @@ -412,7 +438,7 @@ public Future considerPromotion2(AbstractBuild build) throws IO return considerPromotion2(build, params); } - + @CheckForNull public Future considerPromotion2(AbstractBuild build, List params) throws IOException { if (!isActive()) @@ -447,7 +473,7 @@ public void promote(AbstractBuild build, Cause cause, PromotionBadge... bad public void promote(AbstractBuild build, Cause cause, Status qualification) throws IOException { promote2(build,cause,qualification); } - + /** * Promote the given build by using the given qualification. * @@ -460,7 +486,7 @@ public void promote(AbstractBuild build, Cause cause, Status qualification) public Future promote2(AbstractBuild build, Cause cause, Status qualification) throws IOException { return promote2(build, cause, qualification, null); } - + /** * Promote the given build by using the given qualification. * @@ -518,7 +544,7 @@ public boolean scheduleBuild(@Nonnull AbstractBuild build, @Nonnull Cause c * @return Future result or {@code null} if the promotion cannot be scheduled */ @CheckForNull - public Future scheduleBuild2(@Nonnull AbstractBuild build, + public Future scheduleBuild2(@Nonnull AbstractBuild build, Cause cause, @CheckForNull List params) { List actions = new ArrayList(); actions.add(Promotion.PromotionParametersAction.buildFor(build, params)); @@ -708,6 +734,11 @@ public List> getApplicableBuildSteps() { return PromotionProcess.getAll(); } + // exposed for Jelly + public List> getApplicableBuildWrappers(AbstractProject project) { + return project == null ? new ArrayList>(BuildWrapper.all()) : BuildWrappers.getFor(project); + } + @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", justification = "exposed for Jelly") public final Class promotionProcessType = PromotionProcess.class; diff --git a/src/main/resources/hudson/plugins/promoted_builds/PromotionProcess/process-config.jelly b/src/main/resources/hudson/plugins/promoted_builds/PromotionProcess/process-config.jelly index 7605511d..34908bc8 100644 --- a/src/main/resources/hudson/plugins/promoted_builds/PromotionProcess/process-config.jelly +++ b/src/main/resources/hudson/plugins/promoted_builds/PromotionProcess/process-config.jelly @@ -56,6 +56,17 @@ + + + + + + + + +