Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up[FIXED JENKINS-42999] Allow bindings to specify their required context #34
Conversation
Not all bindings require a workspace, and those that don't should be able to be used in `withCredentials` outside of a `node` block. This adds `BindingDescriptor.getRequiredContext()`, defaulting to the four contexts that were previously required by `BindingStep`, but allowing override. `BindingStep` will now throw a `MissingContextVariableException` if any of the `MultiBinding`s used have a required context that cannot be satisfied, as well as the normal potential `MissingContextVariableException` for `BindingStep.DescriptorImpl.getRequiredContext()`. In addition, bumped `workflow-step-api` to 2.9 and moved `BindingStep` and friends to non-deprecated code paths while I was here.
reviewbybees
commented
Mar 21, 2017
|
This pull request originates from a CloudBees employee. At CloudBees, we require that all pull requests be reviewed by other CloudBees employees before we seek to have the change accepted. If you want to learn more about our process please see this explanation. |
|
|
||
| FilePath workspace = getContext().get(FilePath.class); | ||
| Launcher launcher = getContext().get(Launcher.class); | ||
| TaskListener listener = getContext().get(TaskListener.class); |
This comment has been minimized.
This comment has been minimized.
rsandell
Mar 22, 2017
Member
why not fail quickly here as well since you claim to require a TaskListener below in getRequiredContext?
This comment has been minimized.
This comment has been minimized.
|
|
| @@ -44,6 +54,19 @@ | |||
|
|
|||
| protected abstract Class<C> type(); | |||
|
|
|||
| /** | |||
| * Returns the context {@link MultiBinding} needs to access. Defaults to {@link Run}, {@link FilePath}, | |||
| * {@link Launcher} and {@link TaskListener}. | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| return new Execution(this, context); | ||
| } | ||
|
|
||
| public static final class Execution extends StepExecution { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| return new Execution(this, context); | ||
| } | ||
|
|
||
| public static final class Execution extends StepExecution { |
This comment has been minimized.
This comment has been minimized.
jglick
Mar 23, 2017
Member
For running-build compatibility you must continue to extend AbstractStepExecutionImpl; see the Javadoc for nondeprecated constructor.
This comment has been minimized.
This comment has been minimized.
|
|
||
| @Override public boolean start() throws Exception { | ||
| Run<?,?> run = getContext().get(Run.class); | ||
| if (run == null) { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| throw new MissingContextVariableException(Run.class); | ||
| } | ||
| TaskListener listener = getContext().get(TaskListener.class); | ||
| if (listener == null) { |
This comment has been minimized.
This comment has been minimized.
| @@ -177,9 +206,14 @@ private Object readResolve() throws ObjectStreamException { | |||
|
|
|||
| @Override protected void finished(StepContext context) throws Exception { | |||
| Exception xx = null; | |||
| Run<?,?> run = context.get(Run.class); | |||
| if (run == null) { | |||
This comment has been minimized.
This comment has been minimized.
| if (v == null) { | ||
| throw new MissingContextVariableException(requiredContext); | ||
| } | ||
| } | ||
| MultiBinding.MultiEnvironment environment = binding.bind(run, workspace, launcher, listener); |
This comment has been minimized.
This comment has been minimized.
jglick
Mar 23, 2017
Member
Hmm. So MultiBinding.bind’s signature should be changed to make workspace and launcher @Nullable, with Javadoc explaining whether the implementation can or cannot rely on them being present. Same for Unbinder. (listener should be marked @Nonnull like build already was.)
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
jglick
Mar 24, 2017
Member
Yes—whether or not an implementer actually needs to check for null depends on factors outside of the reasonable scope of static analysis: how they implemented another method.
This comment has been minimized.
This comment has been minimized.
| * (say in freestyle or in workflow). | ||
| * @see StepContext#get(Class) | ||
| */ | ||
| public Set<? extends Class<?>> getRequiredContext() { |
This comment has been minimized.
This comment has been minimized.
jglick
Mar 23, 2017
Member
This does not really make sense as an API. There are two legitimate choices:
Run+TaskListener+FilePath+Launcher(the default)Run+TaskListener(the new mode)
You could not usefully drop, say, Run or TaskListener—they will still be required. And you could not meaningfully request some additional context—you will indeed make the step fail if not called in that context, but you have no way of accessing the contextual object! (Since a MultiBinding.bind just gets fixed parameters.)
So I would just scrap this API and replace it with a simple
public boolean requiresWorkspace() {
return true;
}
This comment has been minimized.
This comment has been minimized.
| WorkflowJob p = story.j.jenkins.createProject(WorkflowJob.class, "p"); | ||
| p.setDefinition(new CpsFlowDefinition("" | ||
| + "withCredentials([usernamePassword(usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD', credentialsId: '" + credentialsId + "')]) {\n" | ||
| + " node {\n" // We still need to enter a node to get the actual creds via the file. |
This comment has been minimized.
This comment has been minimized.
jglick
Mar 23, 2017
Member
No need. Try something like
withCredentials([usernameColonPassword(variable: 'USERPASS', credentialsId: '…')]) {
semaphore '…'
echo THEVAR.toUpperCase()
}which should print BOB:S3CR3T.
This comment has been minimized.
This comment has been minimized.
| WorkflowRun b = p.scheduleBuild2(0).waitForStart(); | ||
| story.j.assertBuildStatus(Result.FAILURE, story.j.waitForCompletion(b)); | ||
| story.j.assertLogNotContains("We should fail before getting here", b); | ||
| // We can't guarantee whether this will fail due to missing FilePath.class or Launcher.class. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| throw new MissingContextVariableException(requiredContext); | ||
| } | ||
| if (binding.getDescriptor().requiresWorkspace() && workspace == null) { | ||
| throw new MissingContextVariableException(FilePath.class); |
This comment has been minimized.
This comment has been minimized.
jglick
Mar 24, 2017
Member
Technically you should also check for Launcher, though in practice they will both be present or neither.
This comment has been minimized.
This comment has been minimized.
| private static FilePath secretsDir(FilePath workspace) { | ||
| return tempDir(workspace).child("secretFiles"); | ||
| @CheckForNull | ||
| private static FilePath secretsDir(@CheckForNull FilePath workspace) { |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| // needs to be executable so we can list the contents | ||
| secret.chmod(0700); | ||
| if (secrets == null) { | ||
| throw new IOException("Can't proceed with null workspace"); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| @@ -107,7 +107,8 @@ public StepExecution start(StepContext context) throws Exception { | |||
| Map<String,String> overrides = new HashMap<String,String>(); | |||
| List<MultiBinding.Unbinder> unbinders = new ArrayList<MultiBinding.Unbinder>(); | |||
| for (MultiBinding<?> binding : step.bindings) { | |||
| if (binding.getDescriptor().requiresWorkspace() && workspace == null) { | |||
| if (binding.getDescriptor().requiresWorkspace() && | |||
| (workspace == null || launcher == null)) { | |||
| throw new MissingContextVariableException(FilePath.class); | |||
This comment has been minimized.
This comment has been minimized.
jglick
Mar 30, 2017
Member
Misleading in case FilePath but not Launcher is present; again this is only theoretical.
This comment has been minimized.
This comment has been minimized.
abayer
Mar 30, 2017
Author
Member
Yeah - I decided to go with FilePath as the one I errored on fairly arbitrarily.
| @@ -177,6 +199,7 @@ private Object readResolve() throws ObjectStreamException { | |||
|
|
|||
| @Override protected void finished(StepContext context) throws Exception { | |||
| Exception xx = null; | |||
|
|
|||
This comment has been minimized.
This comment has been minimized.
| @@ -62,8 +69,7 @@ | |||
| // needs to be writable so we can delete its contents | |||
| // needs to be executable so we can list the contents | |||
| secret.chmod(0700); | |||
| } | |||
| else { | |||
| } else { | |||
abayer commentedMar 21, 2017
JENKINS-42999
Not all bindings require a workspace, and those that don't should be
able to be used in
withCredentialsoutside of anodeblock. Thisadds
BindingDescriptor.getRequiredContext(), defaulting to the fourcontexts that were previously required by
BindingStep, but allowingoverride.
BindingStepwill now throw aMissingContextVariableExceptionif any of theMultiBindings usedhave a required context that cannot be satisfied, as well as the
normal potential
MissingContextVariableExceptionforBindingStep.DescriptorImpl.getRequiredContext().In addition, bumped
workflow-step-apito 2.9 and movedBindingStepand friends to non-deprecated code paths while I was here.
cc @reviewbybees esp @jglick @stephenc @rsandell