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)); + } + + }