use of org.jenkinsci.plugins.workflow.steps.Step in project workflow-cps-plugin by jenkinsci.
the class DSL method invokeStep.
/**
* When {@link #invokeMethod(String, Object)} is calling a {@link StepDescriptor}
*/
protected Object invokeStep(StepDescriptor d, Object args) {
final NamedArgsAndClosure ps = parseArgs(args, d);
CpsThread thread = CpsThread.current();
FlowNode an;
// TODO: generalize the notion of Step taking over the FlowNode creation.
boolean hack = d instanceof ParallelStep.DescriptorImpl || d instanceof LoadStep.DescriptorImpl;
if (ps.body == null && !hack) {
an = new StepAtomNode(exec, d, thread.head.get());
// TODO: use CPS call stack to obtain the current call site source location. See JENKINS-23013
thread.head.setNewHead(an);
} else {
an = new StepStartNode(exec, d, thread.head.get());
thread.head.setNewHead(an);
}
final CpsStepContext context = new CpsStepContext(d, thread, handle, an, ps.body);
Step s;
boolean sync;
ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
try {
d.checkContextAvailability(context);
Thread.currentThread().setContextClassLoader(CpsVmExecutorService.ORIGINAL_CONTEXT_CLASS_LOADER.get());
s = d.newInstance(ps.namedArgs);
try {
// No point storing empty arguments, and ParallelStep is a special case where we can't store its closure arguments
if (ps.namedArgs != null && !(ps.namedArgs.isEmpty()) && isKeepStepArguments() && !(s instanceof ParallelStep)) {
// Get the environment variables to find ones that might be credentials bindings
Computer comp = context.get(Computer.class);
EnvVars allEnv = new EnvVars(context.get(EnvVars.class));
if (comp != null && allEnv != null) {
allEnv.entrySet().removeAll(comp.getEnvironment().entrySet());
}
an.addAction(new ArgumentsActionImpl(ps.namedArgs, allEnv));
}
} catch (Exception e) {
// Avoid breaking execution because we can't store some sort of crazy Step argument
LOGGER.log(Level.WARNING, "Error storing the arguments for step: " + d.getFunctionName(), e);
}
// Persist the node - block start and end nodes do their own persistence.
CpsFlowExecution.maybeAutoPersistNode(an);
StepExecution e = s.start(context);
thread.setStep(e);
sync = e.start();
} catch (Exception e) {
if (e instanceof MissingContextVariableException)
reportMissingContextVariableException(context, (MissingContextVariableException) e);
context.onFailure(e);
s = null;
sync = true;
} finally {
Thread.currentThread().setContextClassLoader(originalLoader);
}
if (sync) {
assert context.bodyInvokers.isEmpty() : "If a step claims synchronous completion, it shouldn't invoke body";
if (context.getOutcome() == null) {
context.onFailure(new AssertionError("Step " + s + " claimed to have ended synchronously, but didn't set the result via StepContext.onSuccess/onFailure"));
}
thread.setStep(null);
// we just move on accordingly
if (an instanceof StepStartNode) {
// no body invoked, so EndNode follows StartNode immediately.
thread.head.setNewHead(new StepEndNode(exec, (StepStartNode) an, an));
}
thread.head.markIfFail(context.getOutcome());
return context.replay();
} else {
// if it's in progress, suspend it until we get invoked later.
// when it resumes, the CPS caller behaves as if this method returned with the resume value
Continuable.suspend(new ThreadTaskImpl(context));
// so the execution will never reach here.
throw new AssertionError();
}
}
use of org.jenkinsci.plugins.workflow.steps.Step in project workflow-cps-plugin by jenkinsci.
the class ArgumentsActionImplTest method testRecursiveSanitizationOfContent.
@Test
public void testRecursiveSanitizationOfContent() {
EnvVars env = new EnvVars();
String secretUsername = "secretuser";
// assume secretuser is a bound credential
env.put("USERVARIABLE", secretUsername);
Set<String> sensitiveVariables = new HashSet<>();
sensitiveVariables.add("USERVARIABLE");
int maxLen = ArgumentsActionImpl.getMaxRetainedLength();
ArgumentsActionImpl impl = new ArgumentsActionImpl(sensitiveVariables);
String oversizedString = generateStringOfSize(maxLen + 10);
// Simplest masking of secret and oversized value
Assert.assertEquals("${USERVARIABLE}", impl.sanitizeObjectAndRecordMutation(secretUsername, env));
Assert.assertFalse(impl.isUnmodifiedArguments());
impl.isUnmodifiedBySanitization = true;
Assert.assertEquals(ArgumentsAction.NotStoredReason.OVERSIZE_VALUE, impl.sanitizeObjectAndRecordMutation(oversizedString, env));
Assert.assertFalse(impl.isUnmodifiedArguments());
impl.isUnmodifiedBySanitization = true;
// Test explosion of Step & UninstantiatedDescribable objects
Step mystep = new EchoStep("I have a " + secretUsername);
Map<String, ?> singleSanitization = (Map<String, Object>) (impl.sanitizeObjectAndRecordMutation(mystep, env));
Assert.assertEquals(1, singleSanitization.size());
Assert.assertEquals("I have a ${USERVARIABLE}", singleSanitization.get("message"));
Assert.assertFalse(impl.isUnmodifiedArguments());
impl.isUnmodifiedBySanitization = true;
singleSanitization = ((UninstantiatedDescribable) (impl.sanitizeObjectAndRecordMutation(mystep.getDescriptor().uninstantiate(mystep), env))).getArguments();
Assert.assertEquals(1, singleSanitization.size());
Assert.assertEquals("I have a ${USERVARIABLE}", singleSanitization.get("message"));
Assert.assertFalse(impl.isUnmodifiedArguments());
impl.isUnmodifiedBySanitization = true;
// Maps
HashMap<String, Object> dangerous = new HashMap<>();
dangerous.put("name", secretUsername);
Object sanitized = impl.sanitizeMapAndRecordMutation(dangerous, env);
Assert.assertNotEquals(sanitized, dangerous);
assertThat(sanitized, instanceOf(Map.class));
Map<String, Object> sanitizedMap = (Map<String, Object>) sanitized;
Assert.assertEquals("${USERVARIABLE}", sanitizedMap.get("name"));
Assert.assertFalse(impl.isUnmodifiedArguments());
impl.isUnmodifiedBySanitization = true;
// String is no longer dangerous
Object identical = impl.sanitizeMapAndRecordMutation(dangerous, new EnvVars());
Assert.assertEquals(identical, dangerous);
Assert.assertTrue(impl.isUnmodifiedArguments());
// Lists
List unsanitizedList = Arrays.asList("cheese", null, secretUsername);
Object sanitized2 = impl.sanitizeListAndRecordMutation(unsanitizedList, env);
assertThat(sanitized2, instanceOf(List.class));
List sanitizedList = (List) sanitized2;
Assert.assertEquals(3, sanitizedList.size());
Assert.assertFalse(impl.isUnmodifiedArguments());
Assert.assertEquals("${USERVARIABLE}", sanitizedList.get(2));
impl.isUnmodifiedBySanitization = true;
Assert.assertEquals(unsanitizedList, impl.sanitizeObjectAndRecordMutation(unsanitizedList, new EnvVars()));
Assert.assertEquals(unsanitizedList, impl.sanitizeListAndRecordMutation(unsanitizedList, new EnvVars()));
}
use of org.jenkinsci.plugins.workflow.steps.Step in project workflow-cps-plugin by jenkinsci.
the class DSL method reportAmbiguousStepInvocation.
private void reportAmbiguousStepInvocation(CpsStepContext context, StepDescriptor d, @Nullable TaskListener listener) {
if (listener != null) {
List<String> ambiguousClassNames = StepDescriptor.all().stream().filter(sd -> sd.getFunctionName().equals(d.getFunctionName())).map(sd -> sd.clazz.getName()).collect(Collectors.toList());
String message = String.format("Warning: Invoking ambiguous Pipeline Step ā%1$sā (%2$s). " + "ā%1$sā could refer to any of the following steps: %3$s. " + "You can invoke steps by class name instead to avoid ambiguity. " + "For example: steps.'%2$s'(...)", d.getFunctionName(), d.clazz.getName(), ambiguousClassNames);
listener.getLogger().println(message);
return;
}
LOGGER.log(Level.FINE, "Unable to report ambiguous step invocation for: " + d.getFunctionName());
}
use of org.jenkinsci.plugins.workflow.steps.Step in project workflow-cps-plugin by jenkinsci.
the class DSL method invokeStep.
/**
* When {@link #invokeMethod(String, Object)} is calling a {@link StepDescriptor}
* @param d The {@link StepDescriptor} being invoked.
* @param name The name used to invoke the step. May be {@link StepDescriptor#getFunctionName}, a symbol as in {@link StepDescriptor#metaStepsOf}, or {@code d.clazz.getName()}.
* @param args The arguments passed to the step.
*/
protected Object invokeStep(StepDescriptor d, String name, Object args) {
final NamedArgsAndClosure ps = parseArgs(args, d);
CpsThread thread = CpsThread.current();
FlowNode an;
// TODO: generalize the notion of Step taking over the FlowNode creation.
boolean hack = d instanceof ParallelStep.DescriptorImpl || d instanceof LoadStep.DescriptorImpl;
if (ps.body == null && !hack) {
if (!(d.getClass().getName().equals("org.jenkinsci.plugins.workflow.support.steps.StageStep$DescriptorImpl")) && d.takesImplicitBlockArgument()) {
throw new IllegalStateException(String.format("%s step must be called with a body", name));
} else {
an = new StepAtomNode(exec, d, thread.head.get());
}
} else {
an = new StepStartNode(exec, d, thread.head.get());
}
final CpsStepContext context = new CpsStepContext(d, thread, handle, an, ps.body);
EnvVars allEnv = null;
Set<String> sensitiveVariables = Collections.emptySet();
try {
allEnv = context.get(EnvVars.class);
EnvironmentExpander envExpander = context.get(EnvironmentExpander.class);
if (envExpander != null) {
sensitiveVariables = new HashSet<>(envExpander.getSensitiveVariables());
}
} catch (IOException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Unable to retrieve environment variables", e);
}
// Ensure ArgumentsAction is attached before we notify even synchronous listeners:
ArgumentsActionImpl argumentsAction = null;
try {
// No point storing empty arguments, and ParallelStep is a special case where we can't store its closure arguments
if (ps.namedArgs != null && !(ps.namedArgs.isEmpty()) && isKeepStepArguments() && !(d instanceof ParallelStep.DescriptorImpl)) {
// Get the environment variables to find ones that might be credentials bindings
Computer comp = context.get(Computer.class);
if (comp != null && allEnv != null) {
allEnv.entrySet().removeAll(comp.getEnvironment().entrySet());
}
argumentsAction = new ArgumentsActionImpl(ps.namedArgs, allEnv, sensitiveVariables);
an.addAction(argumentsAction);
}
} catch (Exception e) {
// Avoid breaking execution because we can't store some sort of crazy Step argument
LOGGER.log(Level.WARNING, "Error storing the arguments for step: " + d.getFunctionName(), e);
}
// TODO: use CPS call stack to obtain the current call site source location. See JENKINS-23013
thread.head.setNewHead(an);
Step s;
boolean sync;
ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
try {
TaskListener listener = context.get(TaskListener.class);
logInterpolationWarnings(name, argumentsAction, ps.interpolatedStrings, allEnv, sensitiveVariables, listener);
if (unreportedAmbiguousFunctions.remove(name)) {
reportAmbiguousStepInvocation(context, d, listener);
}
d.checkContextAvailability(context);
Thread.currentThread().setContextClassLoader(CpsVmExecutorService.ORIGINAL_CONTEXT_CLASS_LOADER.get());
if (Util.isOverridden(StepDescriptor.class, d.getClass(), "newInstance", Map.class)) {
s = d.newInstance(ps.namedArgs);
} else {
DescribableModel<? extends Step> stepModel = DescribableModel.of(d.clazz);
s = stepModel.instantiate(ps.namedArgs, listener);
}
// Persist the node - block start and end nodes do their own persistence.
CpsFlowExecution.maybeAutoPersistNode(an);
// Call any registered StepListeners.
for (StepListener sl : ExtensionList.lookup(StepListener.class)) {
try {
sl.notifyOfNewStep(s, context);
} catch (Throwable e) {
LOGGER.log(Level.WARNING, "failed to notify step listener before starting " + s.getDescriptor().getFunctionName(), e);
}
}
if (!context.isCompleted()) {
StepExecution e = s.start(context);
thread.setStep(e);
sync = e.start();
} else {
s = null;
sync = true;
}
} catch (Exception e) {
context.onFailure(e);
s = null;
sync = true;
} finally {
Thread.currentThread().setContextClassLoader(originalLoader);
}
if (sync) {
assert context.withBodyInvokers(List::isEmpty) : "If a step claims synchronous completion, it shouldn't invoke body";
if (context.getOutcome() == null) {
context.onFailure(new AssertionError("Step " + s + " claimed to have ended synchronously, but didn't set the result via StepContext.onSuccess/onFailure"));
}
thread.setStep(null);
// we just move on accordingly
if (an instanceof StepStartNode) {
// no body invoked, so EndNode follows StartNode immediately.
thread.head.setNewHead(new StepEndNode(exec, (StepStartNode) an, an));
}
thread.head.markIfFail(context.getOutcome());
return context.replay();
} else {
// if it's in progress, suspend it until we get invoked later.
// when it resumes, the CPS caller behaves as if this method returned with the resume value
Continuable.suspend(name, new ThreadTaskImpl(context));
// so the execution will never reach here.
throw new AssertionError();
}
}
use of org.jenkinsci.plugins.workflow.steps.Step in project workflow-cps-plugin by jenkinsci.
the class ArgumentsActionImpl method sanitizeObjectAndRecordMutation.
/**
* Recursively sanitize a single object by:
* - Exploding {@link Step}s and {@link UninstantiatedDescribable}s into their Maps to sanitize
* - Removing unsafe strings using {@link #replaceSensitiveVariables(String, EnvVars, Set)} and replace with the variable name
* - Removing oversized objects using {@link #isOversized(Object)} and replacing with {@link NotStoredReason#OVERSIZE_VALUE}
* While making an effort not to retain needless copies of objects and to re-use originals where possible
* (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;
DescribableModel m = null;
if (tempVal instanceof Step) {
// Ugly but functional used for legacy syntaxes with metasteps
m = DescribableModel.of(tempVal.getClass());
tempVal = ((Step) tempVal).getDescriptor().defineArguments((Step) tempVal);
} else if (tempVal instanceof UninstantiatedDescribable) {
tempVal = ((UninstantiatedDescribable) tempVal).toMap();
} else if (tempVal instanceof Describable) {
// Raw Describables may not be safe to store, so we should explode it
try {
m = DescribableModel.of(tempVal.getClass());
tempVal = m.uninstantiate2(o).toMap();
} catch (RuntimeException x) {
// usually NoStaplerConstructorException, but could also be misc. UnsupportedOperationException
LOGGER.log(Level.FINE, "failed to store " + tempVal, x);
this.isUnmodifiedBySanitization = false;
return NotStoredReason.UNSERIALIZABLE;
}
}
if (isOversized(tempVal)) {
this.isUnmodifiedBySanitization = false;
return NotStoredReason.OVERSIZE_VALUE;
}
if (!isStorableType(tempVal)) {
// If we're not a legal type to store, then don't.
this.isUnmodifiedBySanitization = false;
return NotStoredReason.UNSERIALIZABLE;
}
Object modded = tempVal;
if (modded instanceof Map) {
// Recursive sanitization, oh my!
modded = sanitizeMapAndRecordMutation((Map) modded, vars, false);
} else if (modded instanceof List) {
modded = sanitizeListAndRecordMutation((List) modded, vars);
} else if (modded != null && modded.getClass().isArray()) {
Class componentType = modded.getClass().getComponentType();
if (!componentType.isPrimitive()) {
// Object arrays get recursively sanitized
modded = sanitizeArrayAndRecordMutation((Object[]) modded, vars);
} else {
// Primitive arrays aren't a valid type here
this.isUnmodifiedBySanitization = true;
return NotStoredReason.UNSERIALIZABLE;
}
} else if (modded instanceof String && vars != null && !vars.isEmpty()) {
String replaced = replaceSensitiveVariables((String) modded, vars, sensitiveVariables);
if (!replaced.equals(modded)) {
this.isUnmodifiedBySanitization = false;
}
return replaced;
}
if (modded != tempVal) {
// Sanitization stripped out some values, so we need to record that and return modified version
this.isUnmodifiedBySanitization = false;
if (o instanceof Describable && !(o instanceof Step)) {
// Return an UninstantiatedDescribable for the input Describable with masking applied to arguments
// We're skipping steps because for those we want to return the raw arguments anyway...
UninstantiatedDescribable rawUd = m.uninstantiate2(o);
return new UninstantiatedDescribable(rawUd.getSymbol(), rawUd.getKlass(), (Map<String, ?>) modded);
} else 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 if (o instanceof Describable && tempVal instanceof Map) {
// Handle oddball cases where Describable is passed in directly and we need to uninstantiate.
UninstantiatedDescribable rawUd = m.uninstantiate2(o);
return rawUd;
} else {
// Any mutation was just from exploding step/uninstantiated describable, and we can just use the original
return o;
}
}
Aggregations