Skip to content

Commit 8f3d2ac

Browse files
committed
Rewrite rebuild actions using javascript java injection
First version of rebuild that includes all the job parameters. Some improvements should be done like have just one common.jelly but not clear how jelly include logic works. Tested using gerrit-trigger Signed-off-by: Michael Trimarchi <michael@amarulasolutions.com>
1 parent 0687136 commit 8f3d2ac

File tree

11 files changed

+409
-4
lines changed

11 files changed

+409
-4
lines changed

src/main/java/io/jenkins/plugins/pipelinegraphview/utils/AbstractPipelineViewAction.java

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,54 @@
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
44
import com.fasterxml.jackson.databind.ObjectMapper;
5+
6+
import edu.umd.cs.findbugs.annotations.CheckForNull;
7+
import edu.umd.cs.findbugs.annotations.NonNull;
8+
import hudson.ExtensionList;
59
import hudson.model.Action;
610
import hudson.model.BallColor;
711
import hudson.model.ParametersAction;
812
import hudson.model.ParametersDefinitionProperty;
13+
import hudson.model.PasswordParameterValue;
14+
import hudson.model.Cause;
15+
import hudson.model.CauseAction;
16+
import hudson.model.Failure;
17+
import hudson.model.Item;
18+
import hudson.model.queue.QueueTaskFuture;
19+
import hudson.model.Queue;
920
import hudson.security.Permission;
1021
import hudson.util.HttpResponses;
22+
import jenkins.model.ParameterizedJobMixIn;
23+
import jenkins.scm.api.SCMRevisionAction;
24+
25+
import java.io.IOException;
26+
import java.util.ArrayList;
27+
import java.util.Collections;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.TreeMap;
1131
import java.util.concurrent.ExecutionException;
1232
import java.util.concurrent.TimeoutException;
1333
import java.util.logging.Level;
1434
import java.util.logging.Logger;
1535
import net.sf.json.JSONObject;
1636
import org.jenkins.ui.icon.IconSpec;
37+
import org.jenkinsci.plugins.scriptsecurity.scripts.ApprovalContext;
38+
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
39+
import org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedUsageException;
40+
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage;
41+
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
42+
import org.jenkinsci.plugins.workflow.cps.replay.OriginalLoadedScripts;
43+
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
44+
import org.jenkinsci.plugins.workflow.flow.FlowExecutionOwner;
1745
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
46+
import org.kohsuke.accmod.Restricted;
47+
import org.kohsuke.accmod.restrictions.NoExternalUse;
1848
import org.kohsuke.stapler.HttpResponse;
1949
import org.kohsuke.stapler.StaplerRequest2;
2050
import org.kohsuke.stapler.WebMethod;
51+
import org.kohsuke.stapler.bind.JavaScriptMethod;
52+
import org.kohsuke.stapler.interceptor.RequirePOST;
2153

2254
public abstract class AbstractPipelineViewAction implements Action, IconSpec {
2355
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
@@ -26,6 +58,100 @@ public abstract class AbstractPipelineViewAction implements Action, IconSpec {
2658
protected final transient PipelineGraphApi api;
2759
protected final transient WorkflowRun run;
2860

61+
/** Fetches execution, blocking if needed while we wait for some of the loading process. */
62+
@Restricted(NoExternalUse.class)
63+
public @CheckForNull CpsFlowExecution getExecutionBlocking() {
64+
FlowExecutionOwner owner = ((FlowExecutionOwner.Executable) run).asFlowExecutionOwner();
65+
if (owner == null) {
66+
return null;
67+
}
68+
try {
69+
FlowExecution exec = owner.get();
70+
return exec instanceof CpsFlowExecution ? (CpsFlowExecution) exec : null;
71+
} catch (IOException ioe) {
72+
LOGGER.log(Level.WARNING, "Error fetching execution for replay", ioe);
73+
}
74+
return null;
75+
}
76+
77+
/** @see CpsFlowExecution#getScript */
78+
/* accessible to Jelly */ public String getOriginalScript() {
79+
CpsFlowExecution execution = (CpsFlowExecution) getExecutionBlocking();
80+
return execution != null ? execution.getScript() : "???";
81+
}
82+
83+
/** @see CpsFlowExecution#getLoadedScripts */
84+
/* accessible to Jelly */ public Map<String,String> getOriginalLoadedScripts() {
85+
CpsFlowExecution execution = (CpsFlowExecution) getExecutionBlocking();
86+
if (execution == null) { // ?
87+
return Collections.<String,String>emptyMap();
88+
}
89+
Map<String,String> scripts = new TreeMap<>();
90+
for (OriginalLoadedScripts replayer : ExtensionList.lookup(OriginalLoadedScripts.class)) {
91+
scripts.putAll(replayer.loadScripts(execution));
92+
}
93+
return scripts;
94+
}
95+
96+
private boolean hasPasswordParameter() {
97+
ParametersAction pa = run.getAction(ParametersAction.class);
98+
return pa != null && pa.getParameters().stream().anyMatch(PasswordParameterValue.class::isInstance);
99+
}
100+
101+
private static final Iterable<Class<? extends Action>> COPIED_ACTIONS = List.of(
102+
ParametersAction.class,
103+
SCMRevisionAction.class
104+
);
105+
106+
/**
107+
* For whitebox testing.
108+
* @param replacementMainScript main script; replacement for {@link #getOriginalScript}
109+
* @param replacementLoadedScripts auxiliary scripts, keyed by class name; replacement for {@link #getOriginalLoadedScripts}
110+
* @return a way to wait for the replayed build to complete
111+
*/
112+
@SuppressWarnings("rawtypes")
113+
public @CheckForNull QueueTaskFuture/*<Run>*/ run(@NonNull String replacementMainScript, @NonNull Map<String,String> replacementLoadedScripts) {
114+
Queue.Item item = run2(replacementMainScript, replacementLoadedScripts);
115+
return item == null ? null : item.getFuture();
116+
}
117+
118+
/**
119+
* For use in projects that want initiate a replay via the Java API.
120+
*
121+
* @param replacementMainScript main script; replacement for {@link #getOriginalScript}
122+
* @param replacementLoadedScripts auxiliary scripts, keyed by class name; replacement for {@link #getOriginalLoadedScripts}
123+
* @return build queue item
124+
*/
125+
public @CheckForNull Queue.Item run2(@NonNull String replacementMainScript, @NonNull Map<String,String> replacementLoadedScripts) {
126+
List<Action> actions = new ArrayList<>();
127+
CpsFlowExecution execution = getExecutionBlocking();
128+
if (execution == null) {
129+
return null;
130+
}
131+
132+
if (!execution.isSandbox()) {
133+
ScriptApproval.get().configuring(replacementMainScript,GroovyLanguage.get(), ApprovalContext.create(), true);
134+
try {
135+
ScriptApproval.get().using(replacementMainScript, GroovyLanguage.get());
136+
} catch (UnapprovedUsageException e) {
137+
throw new Failure("The script is not approved.");
138+
}
139+
}
140+
141+
actions.add(new ReplayFlowFactoryAction(replacementMainScript, replacementLoadedScripts, execution.isSandbox()));
142+
actions.add(new CauseAction(new Cause.UserIdCause(), new ReplayCause(run)));
143+
144+
if (hasPasswordParameter()) {
145+
throw new Failure("Replay is not allowed when password parameters are used.");
146+
}
147+
148+
for (Class<? extends Action> c : COPIED_ACTIONS) {
149+
actions.addAll(run.getActions(c));
150+
}
151+
152+
return ParameterizedJobMixIn.scheduleBuild2(run.getParent(), 0, actions.toArray(new Action[actions.size()]));
153+
}
154+
29155
public AbstractPipelineViewAction(WorkflowRun target) {
30156
this.api = new PipelineGraphApi(target);
31157
this.run = target;
@@ -57,6 +183,23 @@ public boolean isParameterized() {
57183
return property != null && !property.getParameterDefinitions().isEmpty();
58184
}
59185

186+
/**
187+
* Handles the rebuild request and redirects to parameterized
188+
* and non parameterized build when needed.
189+
* @throws ExecutionException
190+
* @throws IOException
191+
*/
192+
@RequirePOST
193+
@JavaScriptMethod
194+
public void doRebuildjob() throws IOException, ExecutionException {
195+
if (run != null) {
196+
run.checkPermission(Item.BUILD);
197+
if (run(getOriginalScript(), getOriginalLoadedScripts()) == null) {
198+
throw new IOException(run.getParent().getFullName() + " is not buildable");
199+
}
200+
}
201+
}
202+
60203
public String getFullBuildDisplayName() {
61204
return run.getFullDisplayName();
62205
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2016 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package io.jenkins.plugins.pipelinegraphview.utils;
26+
27+
import hudson.ExtensionPoint;
28+
import java.util.Collections;
29+
import java.util.Map;
30+
import edu.umd.cs.findbugs.annotations.NonNull;
31+
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
32+
33+
/**
34+
* Defines which scripts are eligible to be replaced by {@link ReplayAction#run}.
35+
*/
36+
public abstract class OriginalLoadedScripts implements ExtensionPoint {
37+
38+
/**
39+
* Finds scripts which are eligible for replacement.
40+
* @param execution a build
41+
* @return a map from Groovy class names to their original texts, as in {@link ReplayAction#replace}
42+
*/
43+
public @NonNull Map<String,String> loadScripts(@NonNull CpsFlowExecution execution) {
44+
return Collections.emptyMap();
45+
}
46+
47+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright 2016 CloudBees, Inc.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
package io.jenkins.plugins.pipelinegraphview.utils;
26+
27+
import hudson.console.ModelHyperlinkNote;
28+
import hudson.model.Cause;
29+
import hudson.model.Run;
30+
import hudson.model.TaskListener;
31+
import edu.umd.cs.findbugs.annotations.CheckForNull;
32+
import edu.umd.cs.findbugs.annotations.NonNull;
33+
34+
/**
35+
* Marker that a run is a replay of an earlier one.
36+
*/
37+
public class ReplayCause extends Cause {
38+
39+
private final int originalNumber;
40+
private transient Run<?,?> run;
41+
42+
ReplayCause(@NonNull Run<?,?> original) {
43+
this.originalNumber = original.getNumber();
44+
}
45+
46+
@Override public void onAddedTo(Run run) {
47+
super.onAddedTo(run);
48+
this.run = run;
49+
}
50+
51+
@Override public void onLoad(Run<?,?> run) {
52+
super.onLoad(run);
53+
this.run = run;
54+
}
55+
56+
public Run<?,?> getRun() {
57+
return run;
58+
}
59+
60+
public int getOriginalNumber() {
61+
return originalNumber;
62+
}
63+
64+
public @CheckForNull Run<?,?> getOriginal() {
65+
return run.getParent().getBuildByNumber(originalNumber);
66+
}
67+
68+
@Override public String getShortDescription() {
69+
return "Replayed " + getOriginalNumber();
70+
}
71+
72+
@Override public void print(TaskListener listener) {
73+
Run<?,?> original = getOriginal();
74+
if (original != null) {
75+
listener.getLogger().println("Replayed " + getOriginalNumber());
76+
} else {
77+
super.print(listener); // same, without hyperlink
78+
}
79+
}
80+
81+
}

0 commit comments

Comments
 (0)