Skip to content
Permalink
Browse files

Merge pull request #151 from jglick/ArgumentsAction.getResolvedArgume…

…nts-JENKINS-31582

[JENKINS-31582] Using ArgumentsAction.getResolvedArguments to better handle JENKINS-45109
  • Loading branch information...
jglick committed Jun 29, 2017
2 parents 4f703cf + c75550f commit b78991523e2ff0868610b770bd3719452452fb2d
@@ -28,7 +28,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>2.30</version>
<version>2.31</version>
<relativePath />
</parent>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
@@ -78,7 +78,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-api</artifactId>
<version>2.16</version>
<version>2.18</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
@@ -225,6 +225,7 @@ Object sanitizeListAndRecordMutation(@Nonnull List objects, @CheckForNull EnvVar
* (including the Step or UninstantiatedDescribable)
*/
@CheckForNull
@SuppressWarnings("unchecked")
Object sanitizeObjectAndRecordMutation(@CheckForNull Object o, @CheckForNull EnvVars vars) {
// Package scoped so we can test it directly
Object tempVal = o;
@@ -254,7 +255,13 @@ Object sanitizeObjectAndRecordMutation(@CheckForNull Object o, @CheckForNull Env
if (modded != tempVal) {
// Sanitization stripped out some values, so we need to record that and return modified version
this.isUnmodifiedBySanitization = false;
return modded;
if (o instanceof UninstantiatedDescribable) {
// Need to retain the symbol.
UninstantiatedDescribable ud = (UninstantiatedDescribable) o;
return new UninstantiatedDescribable(ud.getSymbol(), ud.getKlass(), (Map<String, ?>) modded);
} else {
return modded;
}
} else { // Any mutation was just from exploding step/uninstantiated describable, and we can just use the original
return o;
}
@@ -25,6 +25,7 @@
package org.jenkinsci.plugins.workflow.cps.nodes;

import hudson.model.Action;
import hudson.model.Describable;
import hudson.model.Descriptor;
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
import org.jenkinsci.plugins.workflow.graph.AtomNode;
@@ -34,8 +35,10 @@

import java.io.ObjectStreamException;
import java.util.Collections;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.jenkinsci.plugins.structs.SymbolLookup;
import org.jenkinsci.plugins.structs.describable.DescribableModel;
import org.jenkinsci.plugins.structs.describable.DescribableParameter;
@@ -84,19 +87,11 @@ protected Object readResolve() throws ObjectStreamException {
if (d == null) {
return null;
}
if (d.isMetaStep()) {
DescribableParameter p = new DescribableModel<>(d.clazz).getFirstRequiredParameter();
if (p != null) {
Object arg = ArgumentsAction.getArguments((FlowNode) node).get(p.getName());
if (arg instanceof UninstantiatedDescribable) {
String symbol = ((UninstantiatedDescribable) arg).getSymbol();
if (symbol != null) {
Descriptor<?> descriptor = SymbolLookup.get().findDescriptor(p.getErasedType(), symbol);
if (descriptor != null) {
return descriptor.getDisplayName();
}
} // TODO to support $class it might be better to go through DescribableModel.resolveClass, if it were public; cf. discussion in JENKINS-31582
}
Class<?> delegateType = getDelegateType((FlowNode) node, d);
if (delegateType != null && Describable.class.isAssignableFrom(delegateType)) {
Descriptor<?> descriptor = Jenkins.getInstance().getDescriptor(delegateType.asSubclass(Describable.class));
if (descriptor != null) {
return descriptor.getDisplayName();
}
}
return d.getDisplayName();
@@ -113,16 +108,11 @@ protected String getTypeDisplayName() {
if (d == null) {
return null;
}
if (d.isMetaStep()) {
DescribableParameter p = new DescribableModel<>(d.clazz).getFirstRequiredParameter();
if (p != null) {
Object arg = ArgumentsAction.getArguments((FlowNode) node).get(p.getName());
if (arg instanceof UninstantiatedDescribable) {
String symbol = ((UninstantiatedDescribable) arg).getSymbol();
if (symbol != null) {
return symbol;
}
}
Class<?> delegateType = getDelegateType((FlowNode) node, d);
if (delegateType != null) {
Set<String> symbols = SymbolLookup.getSymbolValue(delegateType);
if (!symbols.isEmpty()) {
return symbols.iterator().next();
}
}
return d.getFunctionName();
@@ -133,4 +123,24 @@ protected String getTypeFunctionName() {
String fn = effectiveFunctionName(this);
return fn != null ? fn : descriptorId;
}

/**
* @return for example {@code JUnitResultArchiver} given {@code junit '…'}
*/
private static @CheckForNull Class<?> getDelegateType(@Nonnull FlowNode node, @Nonnull StepDescriptor d) {
if (d.isMetaStep()) {
DescribableParameter p = new DescribableModel<>(d.clazz).getFirstRequiredParameter();
if (p != null) {
Object arg = ArgumentsAction.getResolvedArguments(node).get(p.getName());
if (arg instanceof UninstantiatedDescribable) {
DescribableModel<?> delegateModel = ((UninstantiatedDescribable) arg).getModel();
if (delegateModel != null) {
return delegateModel.getType();
}
}
}
}
return null;
}

}
@@ -13,6 +13,7 @@
import hudson.Functions;
import hudson.XmlFile;
import hudson.model.Action;
import hudson.tasks.ArtifactArchiver;
import org.apache.commons.lang.RandomStringUtils;
import org.jenkinsci.plugins.credentialsbinding.impl.BindingStep;
import org.jenkinsci.plugins.workflow.actions.ArgumentsAction;
@@ -40,10 +41,10 @@
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import static org.hamcrest.Matchers.*;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.JenkinsRule;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -53,6 +54,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.jenkinsci.plugins.structs.describable.UninstantiatedDescribable;

/**
* Tests the input sanitization and step persistence here
@@ -170,12 +172,12 @@ public void testRecursiveSanitizationOfContent() {

// Test explosion of Step & UninstantiatedDescribable objects
Step mystep = new EchoStep("I have a "+secretUsername);
Map<String, Object> singleSanitization = (Map<String,Object>)(impl.sanitizeObjectAndRecordMutation(mystep, env));
Map<String, ?> singleSanitization = (Map<String,Object>)(impl.sanitizeObjectAndRecordMutation(mystep, env));
Assert.assertEquals(1, singleSanitization.size());
Assert.assertEquals(ArgumentsAction.NotStoredReason.MASKED_VALUE, singleSanitization.get("message"));
Assert.assertFalse(impl.isUnmodifiedArguments());
impl.isUnmodifiedBySanitization = true;
singleSanitization = (Map<String,Object>)(impl.sanitizeObjectAndRecordMutation(mystep.getDescriptor().uninstantiate(mystep), env));
singleSanitization = ((UninstantiatedDescribable) (impl.sanitizeObjectAndRecordMutation(mystep.getDescriptor().uninstantiate(mystep), env))).getArguments();
Assert.assertEquals(1, singleSanitization.size());
Assert.assertEquals(ArgumentsAction.NotStoredReason.MASKED_VALUE, singleSanitization.get("message"));
Assert.assertFalse(impl.isUnmodifiedArguments());
@@ -394,7 +396,7 @@ public void testUnusualStepInstantiations() throws Exception {
" node('master') { \n" +
" writeFile text: 'hello world', file: 'msg.out'\n" +
" step([$class: 'ArtifactArchiver', artifacts: 'msg.out', fingerprint: false])\n "+
" withEnv(['CUSTOM=val']) {\n"+ //Symbol-based, because withEnv is a metastep
" withEnv(['CUSTOM=val']) {\n"+ //Symbol-based, because withEnv is a metastep; TODO huh? no it is not
" echo env.CUSTOM\n"+
" }\n"+
"}"
@@ -423,4 +425,25 @@ public void testUnusualStepInstantiations() throws Exception {
Assert.assertEquals("CUSTOM=val", (String)((ArrayList) ob).get(0));
testDeserialize(run.getExecution());
}

@Test
public void testReallyUnusualStepInstantiations() throws Exception {
WorkflowJob job = r.jenkins.createProject(WorkflowJob.class, "unusualInstantiation");
job.setDefinition(new CpsFlowDefinition(
" node() {\n" +
" writeFile text: 'hello world', file: 'msg.out'\n" +
" step(new hudson.tasks.ArtifactArchiver('msg.out'))\n" + // note, not whitelisted
"}", false));
WorkflowRun run = r.buildAndAssertSuccess(job);
LinearScanner scan = new LinearScanner();

FlowNode testNode = scan.findFirstMatch(run.getExecution().getCurrentHeads().get(0), new NodeStepTypePredicate("step"));
ArgumentsAction act = testNode.getPersistentAction(ArgumentsAction.class);
Assert.assertNotNull(act);
Object delegate = act.getArgumentValue("delegate");
Assert.assertThat(delegate, instanceOf(ArtifactArchiver.class));
Assert.assertEquals("msg.out", ((ArtifactArchiver) delegate).getArtifacts());
Assert.assertFalse(((ArtifactArchiver) delegate).isFingerprint());
}

}
@@ -29,6 +29,8 @@
import hudson.model.Result;
import hudson.tasks.junit.JUnitResultArchiver;
import java.util.List;
import java.util.logging.Level;
import org.apache.commons.lang.StringUtils;
import static org.hamcrest.Matchers.*;
import org.jenkinsci.plugins.configfiles.buildwrapper.ConfigFileBuildWrapper;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
@@ -39,16 +41,19 @@
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import static org.junit.Assert.*;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.LoggerRule;

public class StepNodeTest {

@ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
@Rule public JenkinsRule r = new JenkinsRule();
@Rule public LoggerRule logging = new LoggerRule().record(StepAtomNode.class, Level.FINE);

@Issue("JENKINS-45109")
@Test public void metastepConsole() throws Exception {
@@ -78,4 +83,87 @@
r.assertLogContains("[Pipeline] // configFileProvider", b);
}

@Test public void metastepConsoleShellClass() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node {\n" +
" wrap([$class: 'ConfigFileBuildWrapper', managedFiles: []]) {\n" +
" writeFile text: '''<testsuite name='a'><testcase name='c'><error>failed</error></testcase></testsuite>''', file: 'x.xml'\n" +
" step([$class: 'JUnitResultArchiver', testResults: 'x.xml'])\n" +
" }\n" +
"}", true));
WorkflowRun b = r.assertBuildStatus(Result.UNSTABLE, p.scheduleBuild2(0));
List<FlowNode> coreStepNodes = new DepthFirstScanner().filteredNodes(b.getExecution(), new NodeStepTypePredicate("step"));
assertThat(coreStepNodes, hasSize(1));
assertEquals("junit", coreStepNodes.get(0).getDisplayFunctionName());
assertEquals(r.jenkins.getDescriptor(JUnitResultArchiver.class).getDisplayName(), coreStepNodes.get(0).getDisplayName());
List<FlowNode> coreWrapperStepNodes = new DepthFirstScanner().filteredNodes(b.getExecution(), Predicates.and(new NodeStepTypePredicate("wrap"), new Predicate<FlowNode>() {
@Override public boolean apply(FlowNode n) {
return n instanceof StepStartNode && !((StepStartNode) n).isBody();
}
}));
assertThat(coreWrapperStepNodes, hasSize(1));
assertEquals("configFileProvider", coreWrapperStepNodes.get(0).getDisplayFunctionName());
assertEquals(r.jenkins.getDescriptor(ConfigFileBuildWrapper.class).getDisplayName() + " : Start", coreWrapperStepNodes.get(0).getDisplayName());
r.assertLogContains("[Pipeline] junit", b);
r.assertLogContains("[Pipeline] configFileProvider", b);
r.assertLogContains("[Pipeline] // configFileProvider", b);
}

@Test public void metastepConsoleRaw() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition(
"node {\n" +
" wrap(new org.jenkinsci.plugins.configfiles.buildwrapper.ConfigFileBuildWrapper([])) {\n" +
" writeFile text: '''<testsuite name='a'><testcase name='c'><error>failed</error></testcase></testsuite>''', file: 'x.xml'\n" +
" step(new hudson.tasks.junit.JUnitResultArchiver('x.xml'))\n" +
" }\n" +
"}", false));
WorkflowRun b = r.assertBuildStatus(Result.UNSTABLE, p.scheduleBuild2(0));
List<FlowNode> coreStepNodes = new DepthFirstScanner().filteredNodes(b.getExecution(), new NodeStepTypePredicate("step"));
assertThat(coreStepNodes, hasSize(1));
assertEquals("junit", coreStepNodes.get(0).getDisplayFunctionName());
assertEquals(r.jenkins.getDescriptor(JUnitResultArchiver.class).getDisplayName(), coreStepNodes.get(0).getDisplayName());
List<FlowNode> coreWrapperStepNodes = new DepthFirstScanner().filteredNodes(b.getExecution(), Predicates.and(new NodeStepTypePredicate("wrap"), new Predicate<FlowNode>() {
@Override public boolean apply(FlowNode n) {
return n instanceof StepStartNode && !((StepStartNode) n).isBody();
}
}));
assertThat(coreWrapperStepNodes, hasSize(1));
assertEquals("configFileProvider", coreWrapperStepNodes.get(0).getDisplayFunctionName());
assertEquals(r.jenkins.getDescriptor(ConfigFileBuildWrapper.class).getDisplayName() + " : Start", coreWrapperStepNodes.get(0).getDisplayName());
r.assertLogContains("[Pipeline] junit", b);
r.assertLogContains("[Pipeline] configFileProvider", b);
r.assertLogContains("[Pipeline] // configFileProvider", b);
}

@Ignore("TODO ArgumentsAction.getResolvedArguments does not yet handle NotStoredReason sensibly")
@Test public void metastepConsoleNotStoredArgument() throws Exception {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
String spaces = StringUtils.repeat(" ", 1025); // cf. ArgumentsAction.MAX_RETAINED_LENGTH
p.setDefinition(new CpsFlowDefinition(
"node {\n" +
" configFileProvider([]) {\n" +
" writeFile text: '''<testsuite name='a'><testcase name='c'><error>failed</error></testcase></testsuite>''', file: 'x.xml'\n" +
" junit 'x.xml," + spaces + "'\n" +
" }\n" +
"}", true));
WorkflowRun b = r.assertBuildStatus(Result.UNSTABLE, p.scheduleBuild2(0));
List<FlowNode> coreStepNodes = new DepthFirstScanner().filteredNodes(b.getExecution(), new NodeStepTypePredicate("step"));
assertThat(coreStepNodes, hasSize(1));
assertEquals("junit", coreStepNodes.get(0).getDisplayFunctionName());
assertEquals(r.jenkins.getDescriptor(JUnitResultArchiver.class).getDisplayName(), coreStepNodes.get(0).getDisplayName());
List<FlowNode> coreWrapperStepNodes = new DepthFirstScanner().filteredNodes(b.getExecution(), Predicates.and(new NodeStepTypePredicate("wrap"), new Predicate<FlowNode>() {
@Override public boolean apply(FlowNode n) {
return n instanceof StepStartNode && !((StepStartNode) n).isBody();
}
}));
assertThat(coreWrapperStepNodes, hasSize(1));
assertEquals("configFileProvider", coreWrapperStepNodes.get(0).getDisplayFunctionName());
assertEquals(r.jenkins.getDescriptor(ConfigFileBuildWrapper.class).getDisplayName() + " : Start", coreWrapperStepNodes.get(0).getDisplayName());
r.assertLogContains("[Pipeline] junit", b);
r.assertLogContains("[Pipeline] configFileProvider", b);
r.assertLogContains("[Pipeline] // configFileProvider", b);
}

}

0 comments on commit b789915

Please sign in to comment.
You can’t perform that action at this time.