Skip to content

Commit

Permalink
[FIXED JENKINS-30411] - Add the PROMOTION_ENV token macro
Browse files Browse the repository at this point in the history
Promotion is an independent job type, which has its own environment. Several promotion-oriented build steps may need an environment or artifacts of the original builds, so they may call the TokenMacro resolution against the build.

In particular cases we're interested to support environment variables from both Promotion and original build. In such case it would be great to have a PROMOTION_ENV macro, which finds the source promotion job by the current executor context. In such case ENV and PROMOTION_ENV macros will allow to extract variables from both jobs.
  • Loading branch information
oleg-nenashev committed Sep 11, 2015
1 parent 54fd9a0 commit 3c38412
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 0 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Expand Up @@ -99,6 +99,12 @@
<version>3.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>token-macro</artifactId>
<version>1.10</version>
<optional>true</optional>
</dependency>
</dependencies>

<build>
Expand Down
@@ -0,0 +1,76 @@
/*
* The MIT License
*
* Copyright (c) 2015 Oleg Nenashev.
*
* 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.tokenmacro;

import com.google.common.collect.ListMultimap;
import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.Executor;
import hudson.model.Queue;
import hudson.model.TaskListener;
import hudson.plugins.promoted_builds.Promotion;
import java.io.IOException;
import java.util.Map;
import org.jenkinsci.plugins.tokenmacro.DataBoundTokenMacro;
import org.jenkinsci.plugins.tokenmacro.DataBoundTokenMacro.Parameter;
import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException;

/**
* Retrieves an environment variable from the {@link Promotion} build.
* In many cases the macro will behave similarly to
* @author Oleg Nenashev
*/
@Extension(optional = true)
public class PromotedEnvVarTokenMacro extends DataBoundTokenMacro {

@Parameter(required=true)
public String var = "";

@Override
public String evaluate(AbstractBuild<?, ?> build, TaskListener listener, String macroName)
throws MacroEvaluationException, IOException, InterruptedException {

Executor currentExecutor = Executor.currentExecutor();
if (currentExecutor == null) {
return null;
}

Queue.Executable executable = currentExecutor.getCurrentExecutable();
if (!(executable instanceof Promotion)) {
return ""; // Nothing to do if it is not promotion
}

Map<String, String> env = ((Promotion)executable).getEnvironment(listener);
if(env.containsKey(var)){
return env.get(var);
}
return "";
}

@Override
public boolean acceptsMacroName(String macroName) {
return macroName.equals("PROMOTION_ENV");
}

}
@@ -0,0 +1,11 @@
<j:jelly xmlns:j="jelly:core">
<dt>$${PROMOTION_ENV,var="VARIABLENAME"}</dt>
<dd>
Expands to an environment variable (specified here as VARIABLENAME) from the
<a href="https://wiki.jenkins-ci.org/display/JENKINS/Promoted+Builds+Plugin">Promotion build</a> environment.
If current build is not a promotion, returns empty string.
<p/>
Note that this does not include any variables set by the build scripts themselves,
only those set by Jenkins and other plugins.
</dd>
</j:jelly>
@@ -0,0 +1,169 @@
/*
* The MIT License
*
* Copyright (c) 2015 CloudBees, Inc..
*
* 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.tokenmacro;

import hudson.EnvVars;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Build;
import hudson.model.BuildListener;
import hudson.model.FreeStyleBuild;
import hudson.model.FreeStyleProject;
import hudson.model.ParameterValue;
import hudson.model.StringParameterDefinition;
import hudson.model.StringParameterValue;
import hudson.model.TaskListener;
import hudson.model.User;
import hudson.plugins.promoted_builds.JobPropertyImpl;
import hudson.plugins.promoted_builds.Promotion;
import hudson.plugins.promoted_builds.PromotionProcess;
import hudson.plugins.promoted_builds.conditions.ManualCondition;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.util.StreamTaskListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import org.acegisecurity.context.SecurityContextHolder;
import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException;
import org.jenkinsci.plugins.tokenmacro.TokenMacro;
import static org.junit.Assert.assertEquals;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.MockFolder;
import org.jvnet.hudson.test.TestExtension;
import org.kohsuke.stapler.DataBoundConstructor;

/**
* Tests for {@link PromotedEnvVarTokenMacro}.
* @author Oleg Nenashev
*/
public class PromotedEnvVarTokenMacroTest {

@Rule
public final JenkinsRule r = new JenkinsRule();

@Test
public void testEnvironmentVariableExpansion() throws Exception {
// Assemble
r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
User u = User.get("foo");
u.setFullName("Foobar");

SecurityContextHolder.getContext().setAuthentication(u.impersonate());

MockFolder parent = r.createFolder("Folder");
FreeStyleProject project = parent.createProject(FreeStyleProject.class, "Project");

JobPropertyImpl promotionProperty = new JobPropertyImpl(project);
PromotionProcess promotionProcess = promotionProperty.addProcess("promo");
promotionProcess.conditions.clear();
ManualCondition manualCondition = new ManualCondition();
manualCondition.getParameterDefinitions().add(new StringParameterDefinition("PROMOTION_PARAM", "defaultValue"));
promotionProcess.conditions.add(manualCondition);
Action approvalAction = new ManualCondition.ManualApproval(promotionProcess.getName(),
new LinkedList<ParameterValue>());
TokenMacroExpressionRecorder recorder = new TokenMacroExpressionRecorder("${PROMOTION_ENV,var=\"PROMOTION_PARAM\"}");
promotionProcess.getBuildSteps().add(recorder);

// Act & promote
FreeStyleBuild build = project.scheduleBuild2(0).get();
build.addAction(approvalAction);
build.save();
Promotion promotion = promotionProcess.considerPromotion2(build,
Arrays.asList((ParameterValue)new StringParameterValue("PROMOTION_PARAM", "FOO"))).get();

// Check results
EnvVars env = promotion.getEnvironment(TaskListener.NULL);
assertEquals("The PROMOTION_PARAM variable has not been injected"
, "FOO", env.get("PROMOTION_PARAM"));
assertEquals("The promotion variable value has not been resolved by the PROMOTION_PARAM macro"
, "FOO", recorder.getCaptured());
}

private static class TokenMacroExpressionRecorder extends Recorder {

private final String expression;
private transient String captured;

@DataBoundConstructor
public TokenMacroExpressionRecorder(String expression) {

this.expression = expression;
}

public String getCaptured() {
return captured;
}

public String getExpression() {
return expression;
}

@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}

@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
if (build instanceof Promotion) {
AbstractBuild<?, ?> target = ((Promotion)build).getTarget();
return performWithParentBuild(build, listener);
}
return false;
}

private boolean performWithParentBuild(AbstractBuild<?, ?> build, BuildListener listener)
throws InterruptedException, IOException {
try {
captured = TokenMacro.expand(build, listener, expression);
} catch (MacroEvaluationException ex) {
throw new IOException(ex);
}
return true;
}

@TestExtension("testEnvironmentVariableExpansion")
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {

@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}

@Override
public String getDisplayName() {
return "Perform Token Macro expression using the parent build";
}
}
}
}

0 comments on commit 3c38412

Please sign in to comment.