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

Export some data from InputAction to the HTTP API #120

Merged
merged 3 commits into from
Nov 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.flow.FlowExecutionList;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

/**
* Records the pending inputs required.
*/
@ExportedBean
public class InputAction implements RunAction2 {

private static final Logger LOGGER = Logger.getLogger(InputAction.class.getName());
Expand Down Expand Up @@ -106,6 +109,7 @@ public String getIconFileName() {
}
}

@Exported
@Override
public String getDisplayName() {
if (ids == null || ids.isEmpty()) {
Expand Down Expand Up @@ -142,6 +146,7 @@ public synchronized InputStepExecution getExecution(String id) throws Interrupte
return null;
}

@Exported
public synchronized List<InputStepExecution> getExecutions() throws InterruptedException, TimeoutException {
loadExecutions();
if (executions == null) {
Expand All @@ -150,6 +155,11 @@ public synchronized List<InputStepExecution> getExecutions() throws InterruptedE
return new ArrayList<InputStepExecution>(executions);
}

@Exported
public boolean isWaitingForInput() throws InterruptedException, TimeoutException {
return !getExecutions().isEmpty();
}

/**
* Called when {@link InputStepExecution} is completed to remove it from the active input list.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,15 @@
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

/**
* {@link Step} that pauses for human input.
*
* @author Kohsuke Kawaguchi
*/
@ExportedBean(defaultVisibility = 2)
public class InputStep extends AbstractStepImpl implements Serializable {

private static final boolean ALLOW_POTENTIALLY_UNSAFE_IDS = SystemProperties.getBoolean(InputStep.class.getName() + ".ALLOW_UNSAFE_IDS");
Expand Down Expand Up @@ -95,12 +98,14 @@ public void setId(String id) {
this.id = _id;
}

@Exported
public String getId() {
if (id==null)
id = capitalize(Util.getDigestOf(message));
return id;
}

@Exported
public String getSubmitter() {
return submitter;
}
Expand All @@ -109,6 +114,7 @@ public String getSubmitter() {
this.submitter = Util.fixEmptyAndTrim(submitter);
}

@Exported
public String getSubmitterParameter() { return submitterParameter; }

@DataBoundSetter public void setSubmitterParameter(String submitterParameter) {
Expand All @@ -130,6 +136,7 @@ private String capitalize(String id) {
/**
* Caption of the OK button.
*/
@Exported
public String getOk() {
return ok!=null ? ok : Messages.proceed();
}
Expand All @@ -138,6 +145,7 @@ public String getOk() {
this.ok = Util.fixEmptyAndTrim(ok);
}

@Exported
public List<ParameterDefinition> getParameters() {
return parameters;
}
Expand All @@ -146,6 +154,7 @@ public List<ParameterDefinition> getParameters() {
this.parameters = parameters;
}

@Exported
public String getMessage() {
return message;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;

import edu.umd.cs.findbugs.annotations.CheckForNull;
Expand All @@ -61,6 +63,7 @@
/**
* @author Kohsuke Kawaguchi
*/
@ExportedBean(defaultVisibility = 2)
public class InputStepExecution extends AbstractStepExecutionImpl implements ModelObject {

private static final Logger LOGGER = Logger.getLogger(InputStepExecution.class.getName());
Expand Down Expand Up @@ -142,10 +145,12 @@ public void stop(Throwable cause) throws Exception {
});
}

@Exported
public String getId() {
return input.getId();
}

@Exported
public InputStep getInput() {
return input;
}
Expand All @@ -165,6 +170,7 @@ private TaskListener getListener() throws IOException, InterruptedException {
/**
* If this input step has been decided one way or the other.
*/
@Exported
public boolean isSettled() {
return outcome!=null;
}
Expand All @@ -180,7 +186,7 @@ private InputAction getPauseAction() throws IOException, InterruptedException {
return a;
}

@Override
@Override @Exported
public String getDisplayName() {
String message = getInput().getMessage();
if (message.length()<32) return message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@
import hudson.util.FormValidation.Kind;
import hudson.util.Secret;
import jenkins.model.IdStrategy;
import jenkins.model.InterruptedBuildAction;
import jenkins.model.Jenkins;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
Expand All @@ -78,6 +79,7 @@

import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import org.jvnet.hudson.test.MockAuthorizationStrategy;
Expand All @@ -87,9 +89,8 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
Expand Down Expand Up @@ -611,5 +612,86 @@ public void test_unsafe_ids_generate_formValidation() throws Exception {
assertThat("> should be rejected", d.doCheckId("this-is-also>-not-ok"), JenkinsMatchers.hasKind(Kind.ERROR));
}

@Test
public void test_api_contains_waitingForInput() throws Exception {
//set up dummy security real
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo (also probably gratuitous comment)

j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
// job setup
WorkflowJob foo = j.jenkins.createProject(WorkflowJob.class, "foo");
foo.setDefinition(new CpsFlowDefinition(StringUtils.join(Arrays.asList(
"def x = input message:'Continue?';",
"echo(\"after: ${x}\");"),"\n"),true));

// get the build going, and wait until workflow pauses
QueueTaskFuture<WorkflowRun> q = foo.scheduleBuild2(0);
WorkflowRun b = q.getStartCondition().get();
Comment on lines +626 to +627
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can be collapsed using (IIRC) waitForStart.

j.waitForMessage("Continue?", b);

final JenkinsRule.WebClient webClient = j.createWebClient();
JenkinsRule.JSONWebResponse json = webClient.getJSON(b.getUrl() + "api/json?depth=1");
JSONArray actions = json.getJSONObject().getJSONArray("actions");
Optional<Object> obj = actions.stream().filter(oo ->
((JSONObject)oo).get("_class").equals("org.jenkinsci.plugins.workflow.support.steps.input.InputAction")
).findFirst();
assertTrue(obj.isPresent());
JSONObject o = (JSONObject)obj.get();
assertTrue(o.has("waitingForInput"));
assertTrue(o.getBoolean("waitingForInput"));

InputAction inputAction = b.getAction(InputAction.class);
InputStepExecution is = inputAction.getExecutions().get(0);
HtmlPage p = webClient.getPage(b, inputAction.getUrlName());
j.submit(p.getFormByName(is.getId()), "proceed");

json = webClient.getJSON(b.getUrl() + "api/json?depth=1");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use depth. Use tree instead.

actions = json.getJSONObject().getJSONArray("actions");
obj = actions.stream().filter(oo ->
((JSONObject)oo).get("_class").equals("org.jenkinsci.plugins.workflow.support.steps.input.InputAction")
).findFirst();
assertTrue(obj.isPresent());
o = (JSONObject)obj.get();
assertTrue(o.has("waitingForInput"));
assertFalse(o.getBoolean("waitingForInput"));
}

@Test
public void test_api_contains_details() throws Exception {
//set up dummy security real
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
// job setup
WorkflowJob foo = j.jenkins.createProject(WorkflowJob.class, "foo");
foo.setDefinition(new CpsFlowDefinition(StringUtils.join(Arrays.asList(
"def chosen = input message: 'Can we settle on this thing?', ok: 'Yep', parameters: [choice(choices: ['Apple', 'Blueberry', 'Banana'], description: 'The fruit in question.', name: 'fruit')], submitter: 'bobby', submitterParameter: 'dd'",
"echo(\"after: ${x}\");"),"\n"),true));

// get the build going, and wait until workflow pauses
QueueTaskFuture<WorkflowRun> q = foo.scheduleBuild2(0);
WorkflowRun b = q.getStartCondition().get();
j.waitForMessage("Can we settle on this thing?", b);

final JenkinsRule.WebClient webClient = j.createWebClient();
final JenkinsRule.JSONWebResponse json = webClient.getJSON(b.getUrl() + "api/json?depth=2");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

final JSONArray actions = json.getJSONObject().getJSONArray("actions");
final Optional<Object> obj = actions.stream().filter(oo ->
((JSONObject)oo).get("_class").equals("org.jenkinsci.plugins.workflow.support.steps.input.InputAction")
).findFirst();
assertTrue(obj.isPresent());
final JSONObject o = (JSONObject)obj.get();
assertTrue(o.has("waitingForInput"));
assertTrue(o.getBoolean("waitingForInput"));

assertTrue(o.has("executions"));
JSONObject exs = o.getJSONArray("executions").getJSONObject(0);
assertEquals("Can we settle on this thing?", exs.getString("displayName"));
assertTrue(exs.has("input"));
JSONObject input = exs.getJSONObject("input");
assertEquals("Can we settle on this thing?", input.getString("displayName"));
assertEquals("Yep", input.getString("ok"));
assertEquals("bobby", input.getString("submitter"));
assertTrue(input.has("parameters"));
JSONObject param = input.getJSONArray("parameters").getJSONObject(0);
assertEquals("fruit", param.getString("name"));
assertEquals("ChoiceParameterDefinition", param.getString("type"));
assertThat(param.getJSONArray("choices").toArray(), arrayContaining("Apple", "Blueberry", "Banana"));
}
}