484 | * For example, 485 | * 486 | *
487 | * build("job1") 488 | * x = extension.'foobar' // foobar is the name of the plugin 489 | * x.someMethod() 490 | *491 | */ 492 | def getExtension() { 493 | return new DynamicExtensionLoader(this); 494 | } 495 | 496 | def propertyMissing(String name) { 497 | throw new MissingPropertyException("Property ${name} doesn't exist."); 498 | } 499 | 500 | def methodMissing(String name, Object args) { 501 | throw new MissingMethodException(name, this.class, args); 502 | } 503 | } 504 | 505 | class DynamicExtensionLoader { 506 | FlowDelegate outer; 507 | 508 | DynamicExtensionLoader(FlowDelegate outer) { 509 | this.outer = outer 510 | } 511 | 512 | def propertyMissing(name) { 513 | def v = BuildFlowDSLExtension.all().findResult { 514 | BuildFlowDSLExtension ext -> ext.createExtension(name, outer) 515 | } 516 | if (v==null) 517 | throw new UnsupportedOperationException("No such extension available: "+name) 518 | return v; 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /src/main/groovy/com/cloudbees/plugins/flow/JobInvocation.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013, CloudBees, Inc., Nicolas De Loof. 5 | * Cisco Systems, Inc., a California corporation 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | package com.cloudbees.plugins.flow; 27 | 28 | import hudson.model.* 29 | import hudson.model.queue.QueueTaskFuture; 30 | import jenkins.model.Jenkins; 31 | 32 | import java.util.concurrent.ExecutionException; 33 | import java.util.concurrent.Future; 34 | import java.util.concurrent.locks.Lock; 35 | import java.util.concurrent.locks.ReentrantLock; 36 | import java.util.concurrent.locks.Condition; 37 | import java.util.logging.Logger; 38 | 39 | import java.text.DateFormat; 40 | 41 | /** 42 | * @author Nicolas De Loof 43 | */ 44 | public class JobInvocation { 45 | 46 | private static final Logger LOGGER = Logger.getLogger(JobInvocation.class.getName()); 47 | 48 | private final String name; 49 | private int buildNumber; 50 | 51 | // The FlowRun build that initiated this job 52 | private transient final FlowRun run; 53 | 54 | private transient AbstractProject, ? extends AbstractBuild, ?>> project; 55 | 56 | private transient Run build; 57 | 58 | private transient QueueTaskFuture extends AbstractBuild, ?>> future; 59 | 60 | private final Lock lock; 61 | private final Condition finalizedCond; 62 | 63 | // Whether the build has started. If true, this.build should be set. 64 | private boolean started; 65 | // Whether the build has completed 66 | private boolean completed; 67 | // Whether the build has completed 68 | private boolean finalized; 69 | 70 | private final int uid 71 | 72 | public JobInvocation(FlowRun run, AbstractProject project) { 73 | this.uid = run.buildIndex.getAndIncrement() 74 | this.run = run; 75 | this.name = project.getFullName(); 76 | this.project = project; 77 | this.lock = new ReentrantLock(); 78 | this.finalizedCond = lock.newCondition(); 79 | } 80 | 81 | public JobInvocation(FlowRun run, String name) { 82 | this(run, getProjectByName(run, name)); 83 | } 84 | 85 | private static AbstractProject getProjectByName(FlowRun run, String name) { 86 | final ItemGroup context = run.getProject().getParent() 87 | AbstractProject item = Jenkins.getInstance().getItem(name, (ItemGroup) context, AbstractProject.class); 88 | if (item == null) { 89 | throw new JobNotFoundException("Item " + name + " not found (or isn't a job)."); 90 | } 91 | return item; 92 | } 93 | 94 | /* package */ JobInvocation run(Cause cause, List
true
if the run was aborted
109 | */
110 | /* package */ boolean abort() {
111 | def aborted = false
112 | if (!started) {
113 | // Need to search the queue for the correct job and cancel it in
114 | // the queue.
115 | def queue = Jenkins.instance.queue
116 | for (queueItem in queue.items) {
117 | if (future == queueItem.getFuture()) {
118 | aborted = queue.cancel(queueItem)
119 | break;
120 | }
121 | }
122 | }
123 | else if (!completed) {
124 | // as the task has already started we want to be kinder in recording the cause.
125 | def cause = new FlowAbortedCause(flowRun);
126 | def executor = build.executor ?: build.oneOffExecutor;
127 | if (executor != null) {
128 | executor.interrupt(Result.ABORTED, cause)
129 | aborted = true;
130 | }
131 | }
132 | return aborted;
133 | }
134 |
135 | /**
136 | * Delegate method calls we don't implement to the actual {@link Run} so that DSL feels like a Run object has been
137 | * returned, but we can lazy-resolve the actual Run object and add some helper methods
138 | */
139 | def invokeMethod(String name, args) {
140 | // Calling $name with $args on actual Run object
141 | getBuild()."$name"(*args)
142 | }
143 |
144 | def propertyMissing(String name) {
145 | // Retrieve property $name from actual Run object
146 | return getBuild()."$name"
147 | }
148 |
149 | public Result getResult() throws ExecutionException, InterruptedException {
150 | waitForCompletion();
151 | return getBuild().getResult();
152 | }
153 |
154 | public String getResultString() throws ExecutionException, InterruptedException {
155 | return getResult().toString().toLowerCase();
156 | }
157 |
158 | /* package */ Run getFlowRun() {
159 | return run;
160 | }
161 |
162 | /* package */ void buildStarted(Run build) {
163 | this.started = true;
164 | this.build = build;
165 | this.buildNumber = build.getNumber();
166 | }
167 |
168 | /* package */ void buildCompleted() {
169 | this.completed = true;
170 | }
171 |
172 | /* package */ void buildFinalized() {
173 | this.lock.lock();
174 | try {
175 | this.finalized = true;
176 | this.finalizedCond.signal();
177 | } finally {
178 | this.lock.unlock();
179 | }
180 | }
181 |
182 | public String getName() {
183 | return name;
184 | }
185 |
186 | public String getDisplayName() {
187 | return (build != null ? build.displayName : "");
188 | }
189 |
190 | public boolean isStarted() {
191 | return started;
192 | }
193 |
194 | public boolean isCompleted() {
195 | return completed;
196 | }
197 |
198 | public boolean isFinalized() {
199 | return finalized;
200 | }
201 |
202 | public String getBuildUrl() {
203 | return this.getBuild() != null ? this.getBuild().getAbsoluteUrl() : null;
204 | }
205 |
206 | public String getStartTime() {
207 | String formattedStartTime = "";
208 | if (getBuild().getTime() != null) {
209 | formattedStartTime = DateFormat.getDateTimeInstance(
210 | DateFormat.SHORT,
211 | DateFormat.SHORT)
212 | .format(getBuild().getTime());
213 | }
214 | return formattedStartTime;
215 | }
216 |
217 | public Run getBuild() throws ExecutionException, InterruptedException {
218 | if (build == null) {
219 | if (future != null) {
220 | // waiting for build to run
221 | build = future.get();
222 | buildNumber = build.getNumber();
223 | } else if (buildNumber > 0) {
224 | // loaded from persistent store
225 | build = getProject().getBuildByNumber(buildNumber);
226 | }
227 | }
228 | return build;
229 | }
230 |
231 | public AbstractProject, ? extends AbstractBuild, ?>> getProject() {
232 | if (project == null)
233 | project = Jenkins.getInstance().getItemByFullName(name, AbstractProject.class);
234 | return project;
235 | }
236 |
237 | public String toString() {
238 | return "${name} ${displayName}"
239 | }
240 |
241 | public Run waitForStart() throws ExecutionException, InterruptedException {
242 | return future.waitForStart();
243 | }
244 |
245 | public void waitForCompletion() throws ExecutionException, InterruptedException {
246 | if (!completed) {
247 | if (future != null) {
248 | future.get();
249 | } else {
250 | throw new RuntimeException("Can't wait for completion.");
251 | }
252 | }
253 | }
254 |
255 | public void waitForFinalization() throws ExecutionException, InterruptedException {
256 | if (!finalized) {
257 | this.lock.lock();
258 | try {
259 | this.finalizedCond.await();
260 | } finally {
261 | this.lock.unlock();
262 | }
263 | }
264 | }
265 |
266 | String getId() {
267 | return "build-" + uid;
268 | }
269 |
270 | /**
271 | * Initial vertex for the build DAG. To be used by FlowRun constructor to initiate the DAG
272 | */
273 | static class Start extends JobInvocation {
274 |
275 | public Start(FlowRun run) {
276 | super(run, run.getProject());
277 | }
278 | }
279 |
280 | @Override
281 | boolean equals(Object obj) {
282 | if (!(obj instanceof JobInvocation)) return false
283 | JobInvocation other = (JobInvocation) obj
284 | return hashCode() == other.hashCode();
285 | }
286 |
287 | @Override
288 | int hashCode() {
289 | return uid;
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/src/main/groovy/com/cloudbees/plugins/flow/SandBox.groovy:
--------------------------------------------------------------------------------
1 | package com.cloudbees.plugins.flow
2 |
3 | /**
4 | * @author Nicolas De Loof
5 | */
6 | class SandBox {
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/cloudbees/plugins/flow/BuildFlow.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2013, CloudBees, Inc., Nicolas De Loof.
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 com.cloudbees.plugins.flow;
26 |
27 | import hudson.Extension;
28 | import hudson.model.*;
29 | import hudson.model.Descriptor.FormException;
30 | import hudson.model.Queue.FlyweightTask;
31 | import hudson.tasks.BuildStep;
32 | import hudson.tasks.BuildStepDescriptor;
33 | import hudson.tasks.Fingerprinter;
34 | import hudson.tasks.Publisher;
35 | import hudson.Util;
36 | import hudson.util.AlternativeUiTextProvider;
37 | import hudson.util.DescribableList;
38 | import hudson.util.FormValidation;
39 | import jenkins.model.Jenkins;
40 | import jenkins.triggers.SCMTriggerItem;
41 |
42 | import java.io.IOException;
43 | import java.util.List;
44 | import java.util.Map;
45 | import java.util.Set;
46 | import java.util.HashSet;
47 |
48 | import javax.servlet.ServletException;
49 |
50 | import net.sf.json.JSONObject;
51 |
52 | import org.kohsuke.stapler.QueryParameter;
53 | import org.kohsuke.stapler.StaplerRequest;
54 | import org.kohsuke.stapler.StaplerResponse;
55 | import org.codehaus.groovy.control.MultipleCompilationErrorsException;
56 | import groovy.lang.GroovyShell;
57 |
58 | /**
59 | * Defines the orchestration logic for a build flow as a succession of jobs to be executed and chained together
60 | *
61 | * @author Nicolas De loof
62 | */
63 | public class BuildFlow extends Project