Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIXED JENKINS-42999] Allow bindings to specify their required context #34

Merged
merged 4 commits into from Mar 30, 2017

Conversation

@abayer
Copy link
Member

abayer commented Mar 21, 2017

JENKINS-42999

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 MultiBindings 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.

cc @reviewbybees esp @jglick @stephenc @rsandell

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
Copy link

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.

Copy link
@rsandell

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.

Copy link
@abayer

abayer Mar 22, 2017

Author Member

Good point, fixing.

Copy link
Member

rsandell left a comment

🐝

@@ -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.

Copy link
@jglick

jglick Mar 23, 2017

Member

Oxford comma!!!

This comment has been minimized.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

=)

return new Execution(this, context);
}

public static final class Execution extends StepExecution {

This comment has been minimized.

Copy link
@jglick

jglick Mar 23, 2017

Member

Need no longer be public.

This comment has been minimized.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Okiedokie.

return new Execution(this, context);
}

public static final class Execution extends StepExecution {

This comment has been minimized.

Copy link
@jglick

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.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Owie. Okiedokie.


@Override public boolean start() throws Exception {
Run<?,?> run = getContext().get(Run.class);
if (run == null) {

This comment has been minimized.

Copy link
@jglick

jglick Mar 23, 2017

Member

Impossible—we have declared this in getRequiredContext.

This comment has been minimized.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Yup, cleaning up.

throw new MissingContextVariableException(Run.class);
}
TaskListener listener = getContext().get(TaskListener.class);
if (listener == null) {

This comment has been minimized.

Copy link
@jglick

jglick Mar 23, 2017

Member

Ditto.

@@ -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.

Copy link
@jglick

jglick Mar 23, 2017

Member

ditto

if (v == null) {
throw new MissingContextVariableException(requiredContext);
}
}
MultiBinding.MultiEnvironment environment = binding.bind(run, workspace, launcher, listener);

This comment has been minimized.

Copy link
@jglick

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.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Gotcha.

This comment has been minimized.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Wait, @Nullable, not @CheckForNull?

This comment has been minimized.

Copy link
@jglick

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.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Fair point!

* (say in freestyle or in workflow).
* @see StepContext#get(Class)
*/
public Set<? extends Class<?>> getRequiredContext() {

This comment has been minimized.

Copy link
@jglick

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.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Works for me.

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.

Copy link
@jglick

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.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Ah-ha - nice.

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.

Copy link
@jglick

jglick Mar 23, 2017

Member

Well if you adopt my suggested API you can predict that.

This comment has been minimized.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

=)

throw new MissingContextVariableException(requiredContext);
}
if (binding.getDescriptor().requiresWorkspace() && workspace == null) {
throw new MissingContextVariableException(FilePath.class);

This comment has been minimized.

Copy link
@jglick

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.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Ok.

private static FilePath secretsDir(FilePath workspace) {
return tempDir(workspace).child("secretFiles");
@CheckForNull
private static FilePath secretsDir(@CheckForNull FilePath workspace) {

This comment has been minimized.

Copy link
@jglick

jglick Mar 24, 2017

Member

Revert. It will not be null.

This comment has been minimized.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Ok.

// 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.

Copy link
@jglick

jglick Mar 24, 2017

Member

Revert. It cannot be null.

This comment has been minimized.

Copy link
@abayer

abayer Mar 24, 2017

Author Member

Ok.

@jglick
jglick approved these changes Mar 30, 2017
@@ -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.

Copy link
@jglick

jglick Mar 30, 2017

Member

Misleading in case FilePath but not Launcher is present; again this is only theoretical.

This comment has been minimized.

Copy link
@abayer

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.

Copy link
@jglick

jglick Mar 30, 2017

Member

gratuitous

@@ -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 {

This comment has been minimized.

Copy link
@jglick

jglick Mar 30, 2017

Member

gratuitous

@jglick jglick merged commit d31ff50 into jenkinsci:master Mar 30, 2017
1 check passed
1 check passed
Jenkins This pull request looks good
Details
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

4 participants
You can’t perform that action at this time.