22
33import com .fasterxml .jackson .core .JsonProcessingException ;
44import 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 ;
59import hudson .model .Action ;
610import hudson .model .BallColor ;
711import hudson .model .ParametersAction ;
812import 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 ;
920import hudson .security .Permission ;
1021import 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 ;
1131import java .util .concurrent .ExecutionException ;
1232import java .util .concurrent .TimeoutException ;
1333import java .util .logging .Level ;
1434import java .util .logging .Logger ;
1535import net .sf .json .JSONObject ;
1636import 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 ;
1745import org .jenkinsci .plugins .workflow .job .WorkflowRun ;
46+ import org .kohsuke .accmod .Restricted ;
47+ import org .kohsuke .accmod .restrictions .NoExternalUse ;
1848import org .kohsuke .stapler .HttpResponse ;
1949import org .kohsuke .stapler .StaplerRequest2 ;
2050import org .kohsuke .stapler .WebMethod ;
51+ import org .kohsuke .stapler .bind .JavaScriptMethod ;
52+ import org .kohsuke .stapler .interceptor .RequirePOST ;
2153
2254public 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 }
0 commit comments