diff --git a/pom.xml b/pom.xml
index a7358819..6b5d1322 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@
org.jenkins-ci.plugins
plugin
- 2.30
+ 2.31
org.jenkins-ci.plugins.workflow
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/actions/ArgumentsAction.java b/src/main/java/org/jenkinsci/plugins/workflow/actions/ArgumentsAction.java
index f3404c1d..f3d646a5 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/actions/ArgumentsAction.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/actions/ArgumentsAction.java
@@ -25,20 +25,25 @@
package org.jenkinsci.plugins.workflow.actions;
import com.google.common.primitives.Primitives;
-import org.apache.commons.collections.CollectionUtils;
-import org.jenkinsci.plugins.workflow.graph.FlowNode;
-import org.jenkinsci.plugins.workflow.graph.StepNode;
-import org.jenkinsci.plugins.workflow.steps.Step;
-import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nonnull;
+import hudson.PluginManager;
+import hudson.model.Describable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import org.apache.commons.collections.CollectionUtils;
+import org.jenkinsci.plugins.structs.describable.DescribableModel;
+import org.jenkinsci.plugins.structs.describable.UninstantiatedDescribable;
+import org.jenkinsci.plugins.workflow.graph.FlowNode;
+import org.jenkinsci.plugins.workflow.graph.StepNode;
+import org.jenkinsci.plugins.workflow.steps.Step;
+import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
/**
* Stores some or all of the arguments used to create and configure the {@link Step} executed by a {@link FlowNode}.
@@ -49,6 +54,8 @@
*/
public abstract class ArgumentsAction implements PersistentAction {
+ private static final Logger LOGGER = Logger.getLogger(ArgumentsAction.class.getName());
+
/** Used as a placeholder marker for {@link Step} arguments not stored for various reasons. */
public enum NotStoredReason {
/** Denotes an unsafe value that cannot be stored/displayed due to sensitive info. */
@@ -128,7 +135,7 @@ public Map getArguments() {
if (args.isEmpty()) {
return Collections.emptyMap();
} else {
- return Collections.unmodifiableMap(getArgumentsInternal());
+ return Collections.unmodifiableMap(args);
}
}
@@ -158,6 +165,7 @@ public Map getFilteredArguments() {
HashMap filteredArguments = new HashMap();
for (Map.Entry entry : internalArgs.entrySet()) {
if (entry.getValue() != null && !(entry.getValue() instanceof NotStoredReason)) {
+ // TODO this is incorrect: value could be a Map/List with some nested entries that are NotStoredReason
filteredArguments.put(entry.getKey(), entry.getValue());
}
}
@@ -241,6 +249,7 @@ static boolean checkArgumentsLackPlaceholders(@Nonnull Map namedA
if (ob instanceof NotStoredReason) {
return false;
}
+ // TODO this would need to also check nested Map/List arguments
}
return true;
}
@@ -253,4 +262,47 @@ public boolean isUnmodifiedArguments() {
// Cacheable, but arguments lists will be quite short and this is unlikely to get invoked heavily.
return checkArgumentsLackPlaceholders(this.getArgumentsInternal());
}
+
+ /**
+ * Like {@link #getArguments(FlowNode)} but attempting to resolve actual classes.
+ * If you need to reconstruct actual classes of nested objects (for example to pass to {@link PluginManager#whichPlugin}),
+ * it is not trivial to get this information from the form in which they were supplied to {@link DescribableModel#instantiate}.
+ * For example, nested objects (where present and not a {@link NotStoredReason}) might have been
+ *
+ * - an {@link UninstantiatedDescribable}, if given by a symbol
+ *
- a {@link Map}, if given by {@link DescribableModel#CLAZZ}
+ *
- a {@link Describable}, if constructed directly (rare)
+ *
+ * This method will instead attempt to return a normalized tree using {@link UninstantiatedDescribable} in all those cases.
+ * You could use {@link UninstantiatedDescribable#getModel} (where available) and {@link DescribableModel#getType} to access live classes.
+ * Where information is missing, this will just return the best it can.
+ */
+ @Nonnull
+ public static Map getResolvedArguments(@Nonnull FlowNode n) {
+ ArgumentsAction aa = n.getPersistentAction(ArgumentsAction.class);
+ if (aa == null) {
+ return Collections.emptyMap();
+ }
+ Map args = aa.getArgumentsInternal();
+ if (n instanceof StepNode) {
+ StepDescriptor d = ((StepNode) n).getDescriptor();
+ if (d != null) {
+ try {
+ return resolve(new DescribableModel<>(d.clazz), args).getArguments();
+ } catch (Exception x) { // all sorts of things could go wrong here
+ LOGGER.log(Level.FINE, "coult not resolve " + args + " for " + d.clazz.getName(), x);
+ // TODO this does not handle NotStoredReason’s well
+ // more robust to recursively traverse the tree and use e.g. DescribableModel.resolveClass to populate details
+ // (without actually attempting to instantiate any of the classes)
+ }
+ }
+ }
+ return args;
+ }
+ // helper method to capture generic type
+ private static UninstantiatedDescribable resolve(DescribableModel model, Map arguments) throws Exception {
+ return model.uninstantiate2(model.instantiate(arguments));
+ }
+
+
}