Skip to content

Commit 3bce6ef

Browse files
committed
Support withChecks
1 parent ebf8537 commit 3bce6ef

File tree

3 files changed

+292
-5
lines changed

3 files changed

+292
-5
lines changed

pom.xml

+22-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>org.jenkins-ci.plugins</groupId>
66
<artifactId>plugin</artifactId>
7-
<version>4.6</version>
7+
<version>4.13</version>
88
<relativePath />
99
</parent>
1010
<artifactId>pipeline-input-step</artifactId>
@@ -39,16 +39,17 @@
3939
<properties>
4040
<revision>2.13</revision>
4141
<changelist>-SNAPSHOT</changelist>
42-
<jenkins.version>2.176.4</jenkins.version>
42+
<jenkins.version>2.204.6</jenkins.version>
4343
<java.level>8</java.level>
44+
<checks-api.version>1.2.0</checks-api.version>
4445
<useBeta>true</useBeta>
4546
</properties>
4647
<dependencyManagement>
4748
<dependencies>
4849
<dependency>
4950
<groupId>io.jenkins.tools.bom</groupId>
50-
<artifactId>bom-2.176.x</artifactId>
51-
<version>11</version>
51+
<artifactId>bom-2.204.x</artifactId>
52+
<version>18</version>
5253
<type>pom</type>
5354
<scope>import</scope>
5455
</dependency>
@@ -75,6 +76,16 @@
7576
<groupId>org.jenkins-ci.plugins</groupId>
7677
<artifactId>credentials</artifactId>
7778
</dependency>
79+
<dependency>
80+
<groupId>io.jenkins.plugins</groupId>
81+
<artifactId>plugin-util-api</artifactId>
82+
<version>1.6.0</version>
83+
</dependency>
84+
<dependency>
85+
<groupId>io.jenkins.plugins</groupId>
86+
<artifactId>checks-api</artifactId>
87+
<version>${checks-api.version}</version>
88+
</dependency>
7889
<dependency>
7990
<groupId>org.jenkins-ci.plugins.workflow</groupId>
8091
<artifactId>workflow-cps</artifactId>
@@ -116,5 +127,12 @@
116127
<artifactId>credentials-binding</artifactId>
117128
<scope>test</scope>
118129
</dependency>
130+
<dependency>
131+
<groupId>io.jenkins.plugins</groupId>
132+
<artifactId>checks-api</artifactId>
133+
<version>${checks-api.version}</version>
134+
<classifier>tests</classifier>
135+
<scope>test</scope>
136+
</dependency>
119137
</dependencies>
120138
</project>

src/main/java/org/jenkinsci/plugins/workflow/support/steps/input/InputStepExecution.java

+77-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.cloudbees.plugins.credentials.builds.CredentialsParameterBinder;
55
import com.google.common.collect.Sets;
66
import com.google.inject.Inject;
7+
import edu.hm.hafner.util.VisibleForTesting;
78
import hudson.FilePath;
89
import hudson.Util;
910
import hudson.console.HyperlinkNote;
@@ -20,15 +21,22 @@
2021
import hudson.security.ACL;
2122
import hudson.security.ACLContext;
2223
import hudson.security.SecurityRealm;
23-
import hudson.security.Permission;
2424
import hudson.util.HttpResponses;
25+
import io.jenkins.plugins.checks.api.ChecksConclusion;
26+
import io.jenkins.plugins.checks.api.ChecksDetails;
27+
import io.jenkins.plugins.checks.api.ChecksOutput;
28+
import io.jenkins.plugins.checks.api.ChecksPublisher;
29+
import io.jenkins.plugins.checks.api.ChecksPublisherFactory;
30+
import io.jenkins.plugins.checks.api.ChecksStatus;
31+
import io.jenkins.plugins.checks.steps.ChecksInfo;
2532
import jenkins.model.IdStrategy;
2633
import jenkins.model.Jenkins;
2734
import net.sf.json.JSONArray;
2835
import net.sf.json.JSONObject;
2936
import org.acegisecurity.Authentication;
3037
import org.acegisecurity.GrantedAuthority;
3138
import org.apache.commons.lang.StringUtils;
39+
import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider;
3240
import org.jenkinsci.plugins.workflow.steps.AbstractStepExecutionImpl;
3341
import org.jenkinsci.plugins.workflow.support.actions.PauseAction;
3442
import org.jenkinsci.plugins.workflow.graph.FlowNode;
@@ -45,10 +53,13 @@
4553
import java.util.HashSet;
4654
import java.util.List;
4755
import java.util.Map;
56+
import java.util.Optional;
4857
import java.util.Set;
4958
import java.util.concurrent.TimeoutException;
5059
import java.util.logging.Level;
5160
import java.util.logging.Logger;
61+
import java.util.stream.Collectors;
62+
5263
import jenkins.util.Timer;
5364
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException;
5465

@@ -74,6 +85,10 @@ public class InputStepExecution extends AbstractStepExecutionImpl implements Mod
7485

7586
@Override
7687
public boolean start() throws Exception {
88+
if (getChecksName().isPresent()) {
89+
getPublisher().publish(extractChecksDetailsStart());
90+
}
91+
7792
// record this input
7893
getPauseAction().add(this);
7994

@@ -186,6 +201,10 @@ public HttpResponse proceed(@CheckForNull Map<String,Object> params) {
186201
}
187202
node.addAction(new InputSubmittedAction(approverId, params));
188203

204+
if (getChecksName().isPresent()) {
205+
getPublisher().publish(extractChecksDetailsProceed(user, params));
206+
}
207+
189208
Object v;
190209
if (params != null && params.size() == 1) {
191210
v = params.values().iterator().next();
@@ -402,5 +421,62 @@ private Object convert(String name, ParameterValue v) throws IOException, Interr
402421
}
403422
}
404423

424+
Optional<String> getChecksName() {
425+
try {
426+
return Optional.ofNullable(getContext().get(ChecksInfo.class))
427+
.map(ChecksInfo::getName);
428+
} catch (IOException | InterruptedException e) {
429+
return Optional.empty();
430+
}
431+
}
432+
433+
private ChecksPublisher getPublisher() {
434+
return ChecksPublisherFactory.fromRun(run, listener);
435+
}
436+
437+
@VisibleForTesting
438+
ChecksDetails extractChecksDetailsStart() {
439+
assert getChecksName().isPresent();
440+
ChecksOutput output = new ChecksOutput.ChecksOutputBuilder()
441+
.withTitle("Input requested")
442+
.withSummary(input.getMessage())
443+
.build();
444+
return new ChecksDetails.ChecksDetailsBuilder()
445+
.withName(getChecksName().get())
446+
.withStatus(ChecksStatus.COMPLETED)
447+
.withConclusion(ChecksConclusion.ACTION_REQUIRED)
448+
.withDetailsURL(DisplayURLProvider.get().getRoot() + run.getUrl() + getPauseAction().getUrlName() + "/")
449+
.withOutput(output)
450+
.build();
451+
}
452+
453+
@VisibleForTesting
454+
ChecksDetails extractChecksDetailsProceed(@CheckForNull User user, @CheckForNull Map<String, Object> parameters) {
455+
assert getChecksName().isPresent();
456+
457+
ChecksOutput.ChecksOutputBuilder outputBuilder = new ChecksOutput.ChecksOutputBuilder()
458+
.withTitle("Input provided");
459+
if (user != null) {
460+
outputBuilder.withSummary("Approved by " + user.getDisplayName());
461+
}
462+
if (parameters != null) {
463+
outputBuilder.withText(extractChecksText(parameters));
464+
}
465+
return new ChecksDetails.ChecksDetailsBuilder()
466+
.withName(getChecksName().get())
467+
.withStatus(ChecksStatus.COMPLETED)
468+
.withConclusion(ChecksConclusion.SUCCESS)
469+
.withDetailsURL(DisplayURLProvider.get().getRunURL(run))
470+
.withOutput(outputBuilder.build())
471+
.build();
472+
}
473+
474+
@VisibleForTesting
475+
String extractChecksText(Map<String, Object> parameters) {
476+
return parameters.entrySet().stream()
477+
.map(entry -> String.format("%s: %s", entry.getKey(), entry.getValue()))
478+
.collect(Collectors.joining("\n"));
479+
}
480+
405481
private static final long serialVersionUID = 1L;
406482
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package org.jenkinsci.plugins.workflow.support.steps.input;
2+
3+
import com.gargoylesoftware.htmlunit.html.HtmlPage;
4+
import hudson.model.Job;
5+
import hudson.model.Result;
6+
import hudson.model.queue.QueueTaskFuture;
7+
import io.jenkins.plugins.checks.api.ChecksConclusion;
8+
import io.jenkins.plugins.checks.api.ChecksDetails;
9+
import io.jenkins.plugins.checks.api.ChecksOutput;
10+
import io.jenkins.plugins.checks.api.ChecksStatus;
11+
import io.jenkins.plugins.checks.util.CapturingChecksPublisher;
12+
import jenkins.model.Jenkins;
13+
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
14+
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
15+
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
16+
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
17+
import org.junit.After;
18+
import org.junit.Assert;
19+
import org.junit.Before;
20+
import org.junit.Rule;
21+
import org.junit.Test;
22+
import org.jvnet.hudson.test.JenkinsRule;
23+
import org.jvnet.hudson.test.MockAuthorizationStrategy;
24+
import org.jvnet.hudson.test.TestExtension;
25+
26+
import java.net.URI;
27+
import java.util.List;
28+
29+
public class InputStepWithChecksTest extends Assert {
30+
31+
private final String CHECKS_NAME = "Input Checks";
32+
private final String INPUT_ID = "InputChecks";
33+
private final String USER = "bob";
34+
35+
@Rule
36+
public JenkinsRule j = new JenkinsRule();
37+
38+
@TestExtension
39+
public static final CapturingChecksPublisher.Factory PUBLISHER_FACTORY = new CapturingChecksPublisher.Factory();
40+
41+
@Before
42+
public void setUpUsers() {
43+
j.jenkins.setSecurityRealm(j.createDummySecurityRealm());
44+
j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy()
45+
.grant(Jenkins.READ, Job.READ, Job.BUILD).everywhere().to(USER));
46+
}
47+
48+
@After
49+
public void cleanupFactory() {
50+
PUBLISHER_FACTORY.getPublishedChecks().clear();
51+
}
52+
53+
private List<ChecksDetails> runAndSubmit(String script) throws Exception {
54+
return run(script, false);
55+
}
56+
57+
private List<ChecksDetails> runAndAbort(String script) throws Exception {
58+
return run(script, true);
59+
}
60+
61+
private List<ChecksDetails> run(String script, boolean abort) throws Exception {
62+
63+
WorkflowJob job = j.createProject(WorkflowJob.class);
64+
job.setDefinition(new CpsFlowDefinition(script, true));
65+
66+
QueueTaskFuture<WorkflowRun> q = job.scheduleBuild2(0);
67+
WorkflowRun run = q.getStartCondition().get();
68+
CpsFlowExecution e = (CpsFlowExecution) run.getExecutionPromise().get();
69+
70+
while (run.getAction(InputAction.class) == null) {
71+
e.waitForSuspension();
72+
}
73+
74+
List<ChecksDetails> checksDetails = PUBLISHER_FACTORY.getPublishedChecks();
75+
76+
assertEquals(2, PUBLISHER_FACTORY.getPublishedChecks().size());
77+
78+
ChecksDetails waiting = checksDetails.get(1);
79+
assertTrue(waiting.getDetailsURL().isPresent());
80+
81+
String url = waiting.getDetailsURL().get();
82+
83+
assertTrue(url.contains(j.getURL().getHost()));
84+
85+
String inputUrl = j.getURL().toURI().relativize(new URI(url)).toString();
86+
87+
JenkinsRule.WebClient c = j.createWebClient();
88+
c.login(USER);
89+
HtmlPage p = c.goTo(inputUrl);
90+
j.submit(p.getFormByName(INPUT_ID), abort ? "abort" : "proceed");
91+
92+
q.get();
93+
j.assertBuildStatus(abort ? Result.ABORTED : Result.SUCCESS, j.waitForCompletion(run));
94+
95+
return PUBLISHER_FACTORY.getPublishedChecks();
96+
}
97+
98+
@Test
99+
public void publishChecksWithNoParameters() throws Exception {
100+
String script = "" +
101+
"withChecks('" + CHECKS_NAME + "') {\n" +
102+
" input message: 'Can you hear me?', id: '" + INPUT_ID + "'\n" +
103+
"}";
104+
105+
List<ChecksDetails> checksDetails = runAndSubmit(script);
106+
107+
assertEquals(3, checksDetails.size());
108+
109+
ChecksDetails started = checksDetails.get(0);
110+
assertTrue(started.getName().isPresent());
111+
assertEquals(CHECKS_NAME, started.getName().get());
112+
113+
ChecksDetails waiting = checksDetails.get(1);
114+
assertTrue(waiting.getName().isPresent());
115+
assertEquals(CHECKS_NAME, waiting.getName().get());
116+
assertEquals(ChecksStatus.COMPLETED, waiting.getStatus());
117+
assertEquals(ChecksConclusion.ACTION_REQUIRED, waiting.getConclusion());
118+
assertTrue(waiting.getDetailsURL().isPresent());
119+
assertTrue(waiting.getOutput().isPresent());
120+
121+
ChecksOutput waitingOutput = waiting.getOutput().get();
122+
assertTrue(waitingOutput.getTitle().isPresent());
123+
assertEquals("Input requested", waitingOutput.getTitle().get());
124+
assertTrue(waitingOutput.getSummary().isPresent());
125+
assertEquals("Can you hear me?", waitingOutput.getSummary().get());
126+
assertFalse(waitingOutput.getText().isPresent());
127+
128+
ChecksDetails complete = checksDetails.get(2);
129+
assertTrue(complete.getName().isPresent());
130+
assertEquals(CHECKS_NAME, complete.getName().get());
131+
assertEquals(ChecksStatus.COMPLETED, complete.getStatus());
132+
assertEquals(ChecksConclusion.SUCCESS, complete.getConclusion());
133+
assertTrue(complete.getOutput().isPresent());
134+
135+
ChecksOutput completeOutput = complete.getOutput().get();
136+
assertTrue(completeOutput.getTitle().isPresent());
137+
assertEquals("Input provided", completeOutput.getTitle().get());
138+
assertTrue(completeOutput.getSummary().isPresent());
139+
assertEquals("Approved by bob", completeOutput.getSummary().get());
140+
assertFalse(completeOutput.getText().isPresent());
141+
}
142+
143+
@Test
144+
public void publishCheckWithParameters() throws Exception {
145+
String defaultValue = "A Sensible Default";
146+
String paramName = "STRING_PARAM";
147+
String script = "" +
148+
"withChecks('" + CHECKS_NAME + "') {\n" +
149+
" input message: 'Can you hear me?',\n" +
150+
" id: '" + INPUT_ID + "',\n" +
151+
" parameters: [string(defaultValue: '" + defaultValue + "', name: '" + paramName + "')]\n" +
152+
"}";
153+
154+
List<ChecksDetails> checksDetails = runAndSubmit(script);
155+
assertEquals(3, checksDetails.size());
156+
157+
ChecksDetails waiting = checksDetails.get(1);
158+
assertTrue(waiting.getOutput().isPresent());
159+
160+
ChecksOutput waitingOutput = waiting.getOutput().get();
161+
assertFalse(waitingOutput.getText().isPresent());
162+
163+
ChecksDetails complete = checksDetails.get(2);
164+
assertTrue(complete.getOutput().isPresent());
165+
166+
ChecksOutput completeOutput = complete.getOutput().get();
167+
assertTrue(completeOutput.getText().isPresent());
168+
assertEquals(String.format("%s: %s", paramName, defaultValue), completeOutput.getText().get());
169+
}
170+
171+
@Test
172+
public void publishCheckWithAbort() throws Exception {
173+
String script = "" +
174+
"withChecks('" + CHECKS_NAME + "') {\n" +
175+
" input message: 'Can you hear me?', id: '" + INPUT_ID + "'\n" +
176+
"}";
177+
178+
List<ChecksDetails> checksDetails = runAndAbort(script);
179+
180+
assertEquals(3, checksDetails.size());
181+
182+
ChecksDetails complete = checksDetails.get(2);
183+
assertEquals(ChecksStatus.COMPLETED, complete.getStatus());
184+
assertEquals(ChecksConclusion.FAILURE, complete.getConclusion());
185+
assertTrue(complete.getOutput().isPresent());
186+
187+
ChecksOutput completeOutput = complete.getOutput().get();
188+
assertTrue(completeOutput.getSummary().isPresent());
189+
assertEquals("occurred while executing withChecks step.", completeOutput.getSummary().get());
190+
assertTrue(completeOutput.getText().isPresent());
191+
assertEquals("org.jenkinsci.plugins.workflow.steps.FlowInterruptedException", completeOutput.getText().get());
192+
}
193+
}

0 commit comments

Comments
 (0)