├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── pom.xml └── src ├── main ├── groovy │ └── com │ │ └── cloudbees │ │ └── plugins │ │ └── flow │ │ ├── FlowDSL.groovy │ │ ├── JobInvocation.groovy │ │ └── SandBox.groovy ├── java │ └── com │ │ └── cloudbees │ │ └── plugins │ │ └── flow │ │ ├── BuildFlow.java │ │ ├── BuildFlowAction.java │ │ ├── BuildFlowDSLExtension.java │ │ ├── BuildFlowPlugin.java │ │ ├── CouldNotScheduleJobException.java │ │ ├── FlowAbortedCause.java │ │ ├── FlowCause.java │ │ ├── FlowIcon.java │ │ ├── FlowListener.java │ │ ├── FlowRun.java │ │ ├── FlowState.java │ │ ├── JobExecutionFailureException.java │ │ └── JobNotFoundException.java ├── resources │ ├── com │ │ └── cloudbees │ │ │ └── plugins │ │ │ └── flow │ │ │ ├── BuildFlow │ │ │ ├── configure-entries.jelly │ │ │ ├── help-buildNeedsWorkspace.jelly │ │ │ ├── help-dsl.jelly │ │ │ ├── help-dslFile.jelly │ │ │ ├── index.jelly │ │ │ ├── newJobDetail.jelly │ │ │ └── newJobDetail.properties │ │ │ ├── BuildFlowAction │ │ │ └── badge.jelly │ │ │ ├── FlowAbortedCause │ │ │ ├── summary.groovy │ │ │ └── summary.properties │ │ │ ├── FlowCause │ │ │ ├── description.jelly │ │ │ └── description.properties │ │ │ ├── FlowRun │ │ │ └── main.jelly │ │ │ └── Messages.properties │ └── index.jelly └── webapp │ └── images │ ├── 16x16 │ └── flow.png │ ├── 24x24 │ └── flow.png │ ├── 32x32 │ └── flow.png │ ├── 48x48 │ └── flow.png │ └── LICENSE └── test ├── .DS_Store ├── groovy └── com │ └── cloudbees │ └── plugins │ └── flow │ ├── AbortTest.groovy │ ├── BindingTest.groovy │ ├── BuildFlowDSLExtensionTest.groovy │ ├── BuildTest.groovy │ ├── CombinationTest.groovy │ ├── ConcurrencyTest.groovy │ ├── DSLTestCase.groovy │ ├── FlowCauseTest.groovy │ ├── GuardTest.groovy │ ├── IgnoreTest.groovy │ ├── ParallelTest.groovy │ ├── PipelineTest.groovy │ ├── RetryTest.groovy │ └── UpstreamTest.groovy └── java └── com └── cloudbees └── plugin └── flow ├── BlockingBuilder.java ├── ConfigurableFailureBuilder.java ├── TestDSLExtension.java └── UnstableBuilder.java /.gitignore: -------------------------------------------------------------------------------- 1 | /.settings 2 | /.classpath 3 | /.project 4 | /target 5 | /work 6 | .DS_Store 7 | *.iml 8 | *.ipr 9 | *.iws 10 | .idea 11 | .gradle 12 | build 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [Unreleased][unreleased] 5 | - cancel the correct queued build (pull request #61) 6 | - threads in parallel blocks now have names (pull request #54) 7 | - remove comma in the "caused by" section (pull request #62) 8 | - fix abort in parallel ignore blocks (pull request #64) 9 | - links to builds now have a context menu (pull request #60) 10 | 11 | ## [0.18] - (released Jun. 10, 2015) 12 | - display the tree of upstream causes (pull request #56) 13 | - now depends on jenkins core **1.509.1** (LTS) 14 | 15 | ## [0.17] - (released Nov. 26, 2014) 16 | - swap dependency with buildgraph-view (revert from 0.13, see #50). 17 | - FlowCause now derives from UpstreamCause (pull request #53) 18 | 19 | ## [0.16] - (released Oct. 08, 2014) 20 | - add syntax highlighting in DSL textarea (pull request #52) 21 | 22 | ## [0.15] - (released Sep. 10, 2014) 23 | - fix missing displayName (pull request #51) 24 | 25 | ## [0.14] - (released Sep. 09, 2014) 26 | - enable test-jar for plugins leveraging the extension point. 27 | - use build.displayName in JobInvocation.toString (pull request #29) 28 | 29 | ## [0.13] - (released Sep. 09, 2014) 30 | - read DSL from a file (pull request #47) 31 | - fix buildgraph when using 2nd level flows (pull request #50) 32 | - swap dependency with buildgraph-view (pull request #50) 33 | 34 | ## [0.12] - (released May 14, 2014) 35 | - wait for build to be finalized 36 | - fixed-width font in DSL box 37 | - print stack traces when parallel builds fail 38 | - restore ability to use a workspace, as a checkbox in flow configuration (useful for SCM using workspace) 39 | 40 | ## [0.11.1] - (released Apr. 08, 2014) 41 | - no changes (added the compatibility warning to update center) 42 | 43 | ## [0.11] - (released Apr. 08, 2014) 44 | - plugin re-licensed to MIT 45 | - build flow no longer has a workspace 46 | - Validation of the DSL when configuring the flow 47 | - If a build could not be scheduled show the underlying cause 48 | - extensions can contribute to dsl help 49 | - aborting a flow causes all jobs scheduled and started by the flow to be aborted 50 | - retry is configurable 51 | - misc tweaks to UI and small fixes 52 | 53 | ## [0.10] - (released Aug. 08, 2013) 54 | - add support for SimpleParameters (parameter that can be set from a String) 55 | - mechanism to define DSL extensions 56 | - visualization moved to build-graph-view plugin 57 | - minor fixes 58 | 59 | ## [0.8] - (released Feb. 11, 2013) 60 | - Fix folder support 61 | - Basic flow visualization support (thanks to ~gregory144) 62 | - Alternative map-style way to define parallel executions (thanks to Jeremy Voorhis) 63 | 64 | ## [0.7] - (released Jan. 11, 2013) 65 | - Add support for ignore(Result) 66 | 67 | ## [0.6] - (released November 24, 2012) 68 | - Enable "read job" permissions for Anonymous (issue #14027) 69 | - Print errors as .. errors 70 | - Better failed test reporting 71 | - Use transient ref to Job/Build … 72 | - Fix a NullPointer to render FlowCause after jenkins restart 73 | - Use futures for synchronization plus publisher support plus console println cleanup (Pull request #14 from coreyoconnor) 74 | - Call to Parametrized jobs correctly use their default values (Pull request #16 from dbaeli) 75 | 76 | ## [0.5] - (released September 03, 2012) 77 | - fixed support for publishers (issue #14411) 78 | - improved job configuration UI (dedicated section, help, prepare code mirror integration) 79 | - improved error message 80 | 81 | ## [0.4] - (released June 28, 2012) 82 | - fixed cast error parsing DSL (Collections$UnmodifiableRandomAccessList' to class 'long') on some version of Jenkins 83 | - add groovy bindings for current build as "build", console output as "out", environment variables Map as "env", and triggered parameters of current build as "params" 84 | - fixed bug when many "Parameters" links were shown for each triggered parameter on build page 85 | 86 | ## [0.3] - (released April 12, 2012) 87 | add support for hierarchical project structure (typically : cloudbees folders plugin) 88 | 89 | ## [0.2] - (released April 09, 2012) 90 | - changed parallel syntax to support nested flows concurrent execution 91 | - fixed serialization issues 92 | 93 | ## [0.1] - (released April 03, 2012) 94 | - initial release with DSL-based job orchestration 95 | 96 | 97 | [unreleased]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.18...HEAD 98 | [0.19]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.18...build-flow-plugin-0.19 99 | [0.18]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.17...build-flow-plugin-0.18 100 | [0.17]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.16...build-flow-plugin-0.17 101 | [0.16]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.15...build-flow-plugin-0.16 102 | [0.15]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.14...build-flow-plugin-0.15 103 | [0.14]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.13...build-flow-plugin-0.14 104 | [0.13]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.12...build-flow-plugin-0.13 105 | [0.12]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.11...build-flow-plugin-0.12 106 | [0.11.1]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.11...build-flow-plugin-0.11.1 107 | [0.11]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.10...build-flow-plugin-0.11 108 | [0.10]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.9...build-flow-plugin-0.10 109 | [0.9]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.8...build-flow-plugin-0.9 110 | [0.8]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.7...build-flow-plugin-0.8 111 | [0.7]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.6...build-flow-plugin-0.7 112 | [0.6]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.5...build-flow-plugin-0.6 113 | [0.5]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.4...build-flow-plugin-0.5 114 | [0.4]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.3...build-flow-plugin-0.4 115 | [0.3]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.2...build-flow-plugin-0.3 116 | [0.2]: https://github.com/jenkinsci/build-flow-plugin/compare/build-flow-plugin-0.1...build-flow-plugin-0.2 117 | [0.1]: https://github.com/jenkinsci/build-flow-plugin/compare/385cb81801653e232cabfa302329c915e2a72c50...build-flow-plugin-0.1 118 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Please take a moment to review the following documents: 2 | * https://wiki.jenkins-ci.org/display/JENKINS/Beginners+Guide+to+Contributing 3 | * https://wiki.jenkins-ci.org/display/JENKINS/Extend+Jenkins 4 | 5 | # For security issues: 6 | * https://wiki.jenkins-ci.org/display/SECURITY/SECURITY+issues+in+plugins 7 | * in short, do not post information publicly (disclosure) until a fix made available publicly. 8 | 9 | # Jenkins is about testing: 10 | * please add tests to cover each new feature; pull requests may be rejected. 11 | * please architect the code so it is easier to test. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013, CloudBees, Inc., Nicolas De Loof. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Deprecated 2 | ========== 3 | 4 | You should consider the pipeline plugin as a replacement as it is actively maintained: 5 | * https://wiki.jenkins-ci.org/display/JENKINS/Pipeline+Plugin 6 | * https://github.com/jenkinsci/pipeline-plugin 7 | 8 | 9 | Jenkins Build Flow Plugin 10 | ========================= 11 | 12 | This Jenkins plugin allows managing jobs orchestration using a dedicated DSL, extracting the flow logic from jobs. 13 | 14 | [![Build Status](https://jenkins.ci.cloudbees.com/job/plugins/job/build-flow-plugin/badge/icon)](https://jenkins.ci.cloudbees.com/job/plugins/job/build-flow-plugin/) 15 | 16 | ## Sample Build Flow Content ## 17 | 18 | parallel ( 19 | { 20 | guard { 21 | build("job1A") 22 | } rescue { 23 | build("job1B") 24 | } 25 | }, 26 | { 27 | retry 3, { 28 | build("job2") 29 | } 30 | } 31 | ) 32 | 33 | See the documentation and release notes at [Build Flow Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Build+Flow+Plugin) on the Jenkins Wiki for more information. 34 | 35 | Other informations: 36 | * Bug Tracker for known issues and expectations : [Jenkins Build Flow Component](https://issues.jenkins-ci.org/browse/JENKINS/component/16533) 37 | * Discussions on this plugin are hosted on [jenkins-user mailing list](https://wiki.jenkins-ci.org/display/JENKINS/Mailing+Lists) 38 | 39 | 40 | Configuration 41 | ============= 42 | 43 | After installing the plugin, you'll get a new Entry in the job creation wizard to create a Flow. Use the DSL editor to define the flow. 44 | 45 | Basics 46 | ======= 47 | 48 | The DSL defines the sequence of jobs to be built : 49 | 50 | build( "job1" ) 51 | build( "job2" ) 52 | build( "job3" ) 53 | 54 | You can pass parameters to jobs, and get the resulting `AbstractBuild` when required : 55 | 56 | b = build( "job1", param1: "foo", param2: "bar" ) 57 | build( "job2", param1: b.build.number ) 58 | build(param1: "xxx", param2: "yyy", param3: "zzz", "job3") 59 | build(param1: "xxx", "job4", param2: "yyy", param3: "zzz") 60 | def myBuildParams = [param1:"xxx", param2:"yyy", param3:"zzz"] 61 | build(myBuildParams, "job5") 62 | 63 | 64 | Environment variables from a job can be obtained using the following, which is especially useful for getting things like the checkout revision used by the SCM plugin (`P4_CHANGELIST`, `GIT_REVISION`, etc) : 65 | 66 | def revision = b.environment.get( "GIT_REVISION" ) 67 | 68 | You can also access some pre-defined variables in the DSL : 69 | * `build` the current flow execution 70 | * `out` the flow build console 71 | * `env` the flow environment, as a Map 72 | * `params` triggered parameters 73 | * `upstream` the upstream job, assuming the flow has been triggered as a downstream job for another job. 74 | 75 | For example: 76 | 77 | // output values 78 | out.println 'Triggered Parameters Map:' 79 | out.println params 80 | out.println 'Build Object Properties:' 81 | build.properties.each { out.println "$it.key -> $it.value" } 82 | 83 | // output git commit info (git plugin) 84 | out.println build.environment.get('GIT_COMMIT') 85 | 86 | // use it in the flow 87 | build("job1", parent_param1: params["param1"]) 88 | build("job2", parent_workspace:build.workspace) 89 | build(params, "job3") 90 | 91 | 92 | ## Guard / Rescue ## 93 | You may need to run a cleanup job after a job (or set of jobs) whenever they succeeded or not. The `guard`/`rescue` structure is designed for this use-case. It works mostly like a try+finally block in Java language : 94 | 95 | guard { 96 | build( "this_job_may_fail" ) 97 | } rescue { 98 | build( "cleanup" ) 99 | } 100 | 101 | The flow result will then be the worst of the guarded job(s) result and the rescue ones 102 | 103 | ## Ignore ## 104 | You may also want to just ignore result of some job, that are optional for your build flow. You can use `ignore` block for this purpose : 105 | 106 | ignore(FAILURE) { 107 | build( "send_twitter_notification" ) 108 | } 109 | 110 | The flow will disregard the triggered build status if it's better than the configured result. This allows you to ignore `UNSTABLE` < `FAILURE` < `ABORTED` 111 | 112 | ## Retry ## 113 | You can ask the flow to `retry` a job a few times until success. This is equivalent to the retry-failed-job plugin : 114 | 115 | retry ( 3 ) { 116 | build( "this_job_may_fail" ) 117 | } 118 | 119 | ## Parallel ## 120 | The flow is strictly sequential, but let you run a set of jobs in parallel and wait for completion when using a `parallel` call. This is equivalent to the join plugin : 121 | 122 | parallel ( 123 | // job 1, 2 and 3 will be scheduled in parallel. 124 | { build("job1") }, 125 | { build("job2") }, 126 | { build("job3") } 127 | ) 128 | 129 | // job4 will be triggered after jobs 1, 2 and 3 complete 130 | build("job4") 131 | 132 | compared to join plugin, parallel can be used for more complex workflows where the parallel branches can sequentially chain multiple jobs : 133 | 134 | parallel ( 135 | { 136 | build("job1A") 137 | build("job1B") 138 | build("job1C") 139 | }, 140 | { 141 | build("job2A") 142 | build("job2B") 143 | build("job2C") 144 | } 145 | ) 146 | 147 | you also can "name" parallel executions, so you can later use reference to extract parameters / status : 148 | 149 | join = parallel ([ 150 | first: { build("job1") }, 151 | second: { build("job2") }, 152 | third: { build("job3") } 153 | ]) 154 | 155 | // now, use results from parallel execution 156 | build("job4", 157 | param1: join.first.result.name, 158 | param2: join.second.lastBuild.parent.name) 159 | 160 | and this can be combined with other orchestration keywords : 161 | 162 | parallel ( 163 | { 164 | guard { 165 | build("job1A") 166 | } rescue { 167 | build("job1B") 168 | } 169 | }, 170 | { 171 | retry 3, { 172 | build("job2") 173 | } 174 | } 175 | ) 176 | 177 | ### Dynamically generate parallel jobs 178 | 179 | You can also generate the jobs you want to execute in parallel: 180 | 181 | listOfJobs = [ "job1", "job2", "job3" ] 182 | 183 | parallel ( 184 | listOfJobs.collect { job -> 185 | { -> build(job) } 186 | } 187 | ) 188 | 189 | Or based on a parameter: 190 | 191 | // Assuming your job has a build parameter called "workerParameters" 192 | // Each parameter will be given to a new job 193 | jobParameters = build.properties.buildVariables.workerParameters 194 | .split(",") 195 | .collect { param -> param.trim() } 196 | 197 | parallel ( 198 | jobParameters.collect { param -> 199 | { -> build("worker", buildParam:param) } 200 | } 201 | ) 202 | 203 | Extension Point 204 | =============== 205 | 206 | Other plugins that expose themselves to the build flow can be accessed with extension.'plugin-name' 207 | 208 | So the plugin foobar might be accessed like: 209 | 210 | def x = extension.'my-plugin-name' 211 | x.aMethodOnFoobarObject() 212 | 213 | ## Implementing Extension ## 214 | 215 | Write the extension in your plugin 216 | 217 | @Extension(optional = true) 218 | public class MyBuildFlowDslExtension extends BuildFlowDSLExtension { 219 | 220 | /** 221 | * The extensionName to use for the extension. 222 | */ 223 | public static final String EXTENSION_NAME = "my-plugin-name"; 224 | 225 | @Override 226 | public Object createExtension(String extensionName, FlowDelegate dsl) { 227 | if (EXTENSION_NAME.equals(extensionName)) { 228 | return new MyBuildFlowDsl(dsl); 229 | } 230 | return null; 231 | } 232 | } 233 | 234 | Write the actual extension 235 | 236 | public class MyBuildFlowDsl { 237 | private FlowDelegate dsl; 238 | 239 | /** 240 | * Standard constructor. 241 | * @param dsl the delegate. 242 | */ 243 | public MyBuildFlowDsl(FlowDelegate dsl) { 244 | this.dsl = dsl; 245 | } 246 | 247 | /** 248 | * World. 249 | */ 250 | public void hello() { 251 | ((PrintStream)dsl.getOut()).println("Hello World"); 252 | } 253 | 254 | } 255 | 256 | ## Plugins implementing extension points ## 257 | 258 | searching github for `BuildFlowDSLExtension`: 259 | * https://github.com/jniesen/build-flow-json-parser-extension-plugin 260 | * https://github.com/dnozay/build-flow-toolbox-plugin 261 | * https://github.com/jenkinsci/external-resource-dispatcher-plugin 262 | * https://github.com/jniesen/build-flow-http-extension-plugin 263 | * https://github.com/jenkinsci/buildflow-extensions-plugin 264 | 265 | 266 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.jenkins-ci.jpi' version '0.18.0' 3 | id 'groovy' 4 | } 5 | 6 | version = '0.7-SNAPSHOT' 7 | description = 'Allows multiple SCM plugins to be used in a job.' 8 | group = 'org.jenkins-ci.plugins' 9 | 10 | sourceCompatibility = '1.6' 11 | targetCompatibility = '1.6' 12 | 13 | // Remove all directories from sourceSets.main.java.srcDirs 14 | // so that everything compiles with the Groovy compiler. 15 | // Some Java files inherit from classes in Groovy files. 16 | sourceSets.main.java.srcDirs = [ "" ] 17 | sourceSets.main.groovy.srcDirs += ["src/main/java"] 18 | 19 | // Only compile properties with with Java compiler. 20 | // This is for Messages.properties 21 | sourceSets.main.java.srcDirs = [ "src/main/resources/*.properties" ] 22 | 23 | // Make sure that generated Java files get compiled by the Java compiler 24 | // This is for Messages.java 25 | sourceSets.main.java.srcDirs += [ "build/generated-src/localizer" ] 26 | 27 | //license { 28 | // header = file('LICENSE') 29 | //} 30 | 31 | 32 | jenkinsPlugin { 33 | // version of Jenkins core this plugin depends on, must be 1.420 or later 34 | coreVersion = '1.609.1' 35 | shortName = 'build-flow-plugin' 36 | displayName = 'Build Flow plugin' 37 | url = 'https://wiki.jenkins-ci.org/display/JENKINS/Build+Flow+Plugin' 38 | gitHubUrl = 'https://github.com/jenkinsci/build-flow-plugin.git' 39 | 40 | // use the plugin class loader before the core class loader 41 | pluginFirstClassLoader = true 42 | 43 | // the developers section is optional, and corresponds to the POM developers section 44 | developers { 45 | developer { 46 | id 'rodrigc' 47 | name 'Craig Rodrigues' 48 | email 'rodrigc@FreeBSD.org' 49 | } 50 | } 51 | } 52 | 53 | allprojects { 54 | tasks.withType(JavaCompile) { 55 | options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked" 56 | } 57 | tasks.withType(GroovyCompile) { 58 | options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked" 59 | } 60 | } 61 | 62 | dependencies { 63 | compile "org.jgrapht:jgrapht-jdk1.5:0.7.3" 64 | compile 'com.cloudbees:groovy-cps:1.7@jar' 65 | jenkinsPlugins 'org.jenkins-ci.plugins:matrix-project:1.7@jar' 66 | jenkinsPlugins 'org.jenkins-ci.plugins:junit:1.13@jar' 67 | 68 | // Test dependencies 69 | jenkinsTest "org.jenkins-ci.plugins:ant:1.3@jar" 70 | jenkinsTest "org.mockito:mockito-all:1.8.5@jar" 71 | jenkinsTest 'org.jenkins-ci.plugins:script-security:1.19@jar' 72 | jenkinsTest 'org.jenkins-ci.main:maven-plugin:2.12.1@jar' 73 | jenkinsTest 'org.jenkins-ci.plugins:javadoc:1.3@jar' 74 | jenkinsTest 'org.jenkins-ci.plugins:mailer:1.17@jar' 75 | 76 | // Test dependencies for pipeline plugin 77 | jenkinsTest "org.jenkins-ci.plugins:durable-task:1.10@jar" 78 | jenkinsTest "org.jenkins-ci.plugins.workflow:workflow-api:1.13@jar" 79 | jenkinsTest "org.jenkins-ci.plugins.workflow:workflow-basic-steps:1.13@jar" 80 | jenkinsTest "org.jenkins-ci.plugins.workflow:workflow-cps:1.13@jar" 81 | jenkinsTest "org.jenkins-ci.plugins.workflow:workflow-durable-task-step:1.13@jar" 82 | jenkinsTest "org.jenkins-ci.plugins.workflow:workflow-job:1.13@jar" 83 | jenkinsTest "org.jenkins-ci.plugins.workflow:workflow-scm-step:1.13@jar" 84 | jenkinsTest "org.jenkins-ci.plugins.workflow:workflow-step-api:1.13@jar" 85 | jenkinsTest "org.jenkins-ci.plugins.workflow:workflow-support:1.13@jar" 86 | } 87 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/build-flow-plugin/d8d559258577e5009c9db95ec7e866e4cedc99e1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon May 16 12:38:44 PDT 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 4.0.0 27 | 28 | org.jenkins-ci.plugins 29 | plugin 30 | 1.609.1 31 | 32 | 33 | com.cloudbees.plugins 34 | build-flow-plugin 35 | 0.21-SNAPSHOT 36 | hpi 37 | Build Flow plugin 38 | Manages job orchestration as a build flow 39 | https://wiki.jenkins-ci.org/display/JENKINS/Build+Flow+Plugin 40 | 41 | 42 | 43 | MIT 44 | http://opensource.org/licenses/MIT 45 | repo 46 | 47 | 48 | 49 | 50 | 4.12 51 | 1.13 52 | 53 | 54 | 55 | 56 | rodrigc 57 | Craig Rodrigues 58 | 59 | 60 | 61 | 62 | scm:git:git://git@github.com/jenkinsci/build-flow-plugin.git 63 | scm:git:https://github.com/jenkinsci/build-flow-plugin.git 64 | https://github.com/jenkinsci/build-flow-plugin 65 | HEAD 66 | 67 | 68 | 69 | 70 | 71 | maven-release-plugin 72 | 2.3.2 73 | 74 | 75 | org.apache.maven.scm 76 | maven-scm-provider-gitexe 77 | 1.8.1 78 | 79 | 80 | 81 | 82 | maven-jar-plugin 83 | 2.5 84 | 85 | 86 | 87 | test-jar 88 | 89 | 90 | 91 | 92 | 93 | org.codehaus.gmaven 94 | gmaven-plugin 95 | 1.4 96 | 97 | 1.8 98 | 99 | 100 | 101 | 102 | generateStubs 103 | compile 104 | generateTestStubs 105 | testCompile 106 | 107 | 108 | 109 | 110 | 111 | org.jenkins-ci.tools 112 | maven-hpi-plugin 113 | true 114 | 115 | 0.11 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | org.codehaus.groovy 124 | groovy-all 125 | 1.8.5 126 | provided 127 | 128 | 129 | org.jgrapht 130 | jgrapht-jdk1.5 131 | 0.7.3 132 | 133 | 134 | 135 | junit 136 | junit 137 | ${junit.version} 138 | test 139 | 140 | 141 | 142 | org.mockito 143 | mockito-all 144 | 1.8.5 145 | test 146 | 147 | 148 | 149 | 150 | org.jenkins-ci.plugins.workflow 151 | workflow-job 152 | ${pipeline.version} 153 | test 154 | 155 | 156 | org.jenkins-ci.plugins.workflow 157 | workflow-basic-steps 158 | ${pipeline.version} 159 | test 160 | 161 | 162 | org.jenkins-ci.plugins.workflow 163 | workflow-cps 164 | ${pipeline.version} 165 | test 166 | 167 | 168 | org.jenkins-ci.plugins.workflow 169 | workflow-durable-task-step 170 | ${pipeline.version} 171 | test 172 | 173 | 174 | 175 | 176 | 177 | repo.jenkins-ci.org 178 | http://repo.jenkins-ci.org/public/ 179 | 180 | true 181 | 182 | 183 | false 184 | 185 | 186 | 187 | 188 | 189 | repo.jenkins-ci.org 190 | http://repo.jenkins-ci.org/public/ 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /src/main/groovy/com/cloudbees/plugins/flow/FlowDSL.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.AbortException 29 | import hudson.console.ModelHyperlinkNote 30 | import hudson.model.* 31 | import hudson.security.ACL 32 | import hudson.slaves.EnvironmentVariablesNodeProperty 33 | import hudson.slaves.NodeProperty 34 | import hudson.util.spring.ClosureScript 35 | import jenkins.model.Jenkins 36 | 37 | import org.acegisecurity.context.SecurityContextHolder 38 | import org.codehaus.groovy.control.CompilerConfiguration 39 | import org.codehaus.groovy.control.customizers.ImportCustomizer 40 | 41 | import java.util.concurrent.* 42 | import java.util.logging.Logger 43 | 44 | import static hudson.model.Result.FAILURE 45 | import static hudson.model.Result.SUCCESS 46 | 47 | public class FlowDSL { 48 | 49 | def void executeFlowScript(FlowRun flowRun, String dsl, BuildListener listener) { 50 | // Retrieve the upstream build if the flow was triggered by another job 51 | Run upstream = null; 52 | flowRun.causes.each{ cause -> 53 | if (cause instanceof Cause.UpstreamCause) { 54 | Job job = Jenkins.instance.getItemByFullName(cause.upstreamProject) 55 | upstream = job?.getBuildByNumber(cause.upstreamBuild) 56 | // TODO handle matrix jobs ? 57 | } 58 | } 59 | 60 | def envMap = [:] 61 | def getEnvVars = { NodeProperty nodeProperty -> 62 | if (nodeProperty instanceof EnvironmentVariablesNodeProperty) { 63 | envMap.putAll( nodeProperty.envVars ); 64 | } 65 | } 66 | Jenkins.instance.globalNodeProperties.each(getEnvVars) 67 | flowRun.builtOn.nodeProperties.each(getEnvVars) 68 | 69 | // TODO : add restrictions for System.exit, etc ... 70 | FlowDelegate flow = new FlowDelegate(flowRun, listener, upstream, envMap) 71 | 72 | 73 | // parse the script in such a way that it delegates to the flow object as default 74 | def cc = new CompilerConfiguration(); 75 | cc.scriptBaseClass = ClosureScript.class.name; 76 | def ic = new ImportCustomizer() 77 | ic.addStaticStars(Result.class.name) 78 | cc.addCompilationCustomizers(ic) 79 | 80 | ClosureScript dslScript = (ClosureScript)new GroovyShell(Jenkins.instance.pluginManager.uberClassLoader,new Binding(),cc).parse(dsl) 81 | dslScript.setDelegate(flow); 82 | 83 | try { 84 | dslScript.run() 85 | } catch(JobExecutionFailureException e) { 86 | listener.println("flow failed to complete : " + flowRun.state.result) 87 | } 88 | catch (AbortException e) { 89 | // aborted should not cause any logging. 90 | killRunningJobs(flowRun, listener) 91 | } 92 | catch (InterruptedException e) { 93 | // aborted should not cause any logging. 94 | killRunningJobs(flowRun, listener) 95 | } catch (Exception e) { 96 | listener.error("Failed to run DSL Script") 97 | e.printStackTrace(listener.getLogger()) 98 | throw e; 99 | } 100 | } 101 | 102 | private void killRunningJobs(FlowRun flowRun, BuildListener listener) { 103 | flowRun.state.result = Executor.currentExecutor().abortResult(); 104 | Executor.currentExecutor().recordCauseOfInterruption(flowRun, listener); 105 | 106 | def graph = flowRun.jobsGraph 107 | graph.vertexSet().each() { ji -> 108 | if (flowRun.project != ji.project) { 109 | // Our project is the fist JobInvocation and we would just be aborting ourselves again. 110 | println("aborting ${ji.name}") 111 | ji.abort() 112 | } 113 | } 114 | // wait until all the downstream builds have aborted. 115 | // we do this in a separate block as aborting a job may take some time to complete. 116 | graph.vertexSet().each() { ji -> 117 | if (ji.started && ! ji.completed) { 118 | if (flowRun.project != ji.project) { 119 | // Our project is the fist JobInvocation and we can't schedule ourself to run 120 | // so we would have started but have no build. 121 | // so don't wait for our self which will cause an exception. 122 | println("Waiting for ${ji.name} to finish...") 123 | 124 | ji.waitForCompletion() 125 | } 126 | } 127 | } 128 | listener.getLogger().println(hudson.model.Messages.Run_BuildAborted()); 129 | } 130 | // TODO define a parseFlowScript to validate flow DSL and maintain jobs dependencygraph 131 | } 132 | 133 | @SuppressWarnings("GroovyUnusedDeclaration") 134 | public class FlowDelegate { 135 | 136 | private static final Logger LOGGER = Logger.getLogger(FlowDelegate.class.getName()); 137 | def List causes 138 | def FlowRun flowRun 139 | BuildListener listener 140 | int indent = 0 141 | private Run upstream; 142 | private Map env; 143 | 144 | public FlowDelegate(FlowRun flowRun, BuildListener listener, upstream, env) { 145 | this.flowRun = flowRun 146 | this.listener = listener 147 | causes = flowRun.causes 148 | this.upstream = upstream 149 | this.env = env 150 | } 151 | 152 | def getOut() { 153 | return listener.logger 154 | } 155 | 156 | // TODO Assuring proper indent should be done in the listener? 157 | def synchronized println_with_indent(Closure f) { 158 | for (int i = 0; i < indent; ++i) { 159 | out.print(" ") 160 | } 161 | f() 162 | out.println() 163 | } 164 | 165 | def println(String s) { 166 | println_with_indent { out.print(s) } 167 | } 168 | 169 | def fail() { 170 | // Stop the flow execution 171 | throw new JobExecutionFailureException() 172 | } 173 | 174 | def build(String jobName) { 175 | build([:], jobName) 176 | } 177 | 178 | def getBuild() { 179 | return flowRun; 180 | } 181 | 182 | def getParams() { 183 | return flowRun.buildVariables; 184 | } 185 | 186 | /** 187 | * Upstream build that triggered this flow execution, if any. 188 | */ 189 | Run getUpstream() { 190 | return upstream; 191 | } 192 | 193 | /** 194 | * Environment variables that the build gets from its context. 195 | */ 196 | Map getEnv() { 197 | return env 198 | } 199 | 200 | /** 201 | * Check flow status and stop if unexpected failure is detected 202 | */ 203 | private void statusCheck() { 204 | if (flowRun.state.result.isWorseThan(SUCCESS)) { 205 | fail() 206 | } 207 | } 208 | 209 | def build(Map args, String jobName) { 210 | statusCheck() 211 | // ask for job with name ${name} 212 | JobInvocation job = new JobInvocation(flowRun, jobName) 213 | Job p = job.getProject() 214 | println("Schedule job " + ModelHyperlinkNote.encodeTo(p)) 215 | 216 | flowRun.schedule(job, getActions(p,args)); 217 | Run r = job.waitForStart() 218 | println("Build " + ModelHyperlinkNote.encodeTo('/'+ r.getUrl(), r.getFullDisplayName()) + " started") 219 | 220 | if (null == r) { 221 | println("Failed to start ${jobName}.") 222 | fail(); 223 | } 224 | 225 | flowRun.waitForCompletion(job); 226 | // [JENKINS-22960] wait for build to be finalized. 227 | flowRun.waitForFinalization(job); 228 | println(ModelHyperlinkNote.encodeTo('/'+ r.getUrl(), r.getFullDisplayName()) 229 | + " completed ${r.result.isWorseThan(SUCCESS) ? " : " + r.result : ""}") 230 | return job; 231 | } 232 | 233 | def getActions(Job job, Map args) { 234 | 235 | List originalActions = job.getActions(); 236 | 237 | List jobParams = null; 238 | for(Action action:originalActions) { 239 | if (action instanceof ParametersDefinitionProperty) { 240 | ParametersDefinitionProperty parametersAction = (ParametersDefinitionProperty) action; 241 | jobParams = parametersAction.getParameterDefinitions(); 242 | } 243 | } 244 | 245 | List actions = new ArrayList(); 246 | List params = []; 247 | Set addedParams = new HashSet(); 248 | for (Map.Entry param: args) { 249 | String paramName = param.key 250 | Object paramValue = param.value 251 | if (paramValue instanceof Closure) { 252 | paramValue = getClosureValue(paramValue) 253 | } 254 | //Use pre-defined parameter type if it exists and it's simple 255 | if (jobParams != null) { 256 | for (ParameterDefinition originalParam: jobParams) { 257 | if (originalParam instanceof SimpleParameterDefinition) { 258 | if (paramName.equals(originalParam.name)) { 259 | try { 260 | params.add(originalParam.createValue(paramValue)); 261 | addedParams.add(paramName); 262 | } catch (Exception e) { 263 | //This usually means that createValue(String) is 264 | //unimplemented and we can't use the definition. 265 | } 266 | } 267 | } 268 | } 269 | } 270 | if (addedParams.contains(paramName)) { 271 | //Added the parameter already, so go on to the next one 272 | continue; 273 | } 274 | if (paramValue instanceof Boolean) { 275 | params.add(new BooleanParameterValue(paramName, (Boolean) paramValue)) 276 | } 277 | else { 278 | params.add(new StringParameterValue(paramName, paramValue.toString())) 279 | } 280 | addedParams.add(paramName); 281 | //TODO For now we only support String and boolean parameters 282 | } 283 | 284 | /* Add default values from defined params in the target job */ 285 | 286 | if (jobParams != null) { 287 | 288 | for (ParameterDefinition originalParam: jobParams) { 289 | 290 | String paramName = originalParam.getName() 291 | ParameterValue originalParamValue = originalParam.getDefaultParameterValue(); 292 | 293 | if (addedParams.contains(paramName)){ 294 | //Already filled parameter 295 | continue; 296 | } 297 | 298 | params.add(originalParamValue) 299 | } 300 | } 301 | 302 | //Additionnal parameters not available in the target job 303 | actions.add(new ParametersAction(params)); 304 | return actions 305 | } 306 | 307 | def getClosureValue(closure) { 308 | return closure() 309 | } 310 | 311 | def guard(guardedClosure) { 312 | statusCheck() 313 | def deleg = this; 314 | [ rescue : { rescueClosure -> 315 | rescueClosure.delegate = deleg 316 | rescueClosure.resolveStrategy = Closure.DELEGATE_FIRST 317 | 318 | try { 319 | println("guard {") 320 | ++indent 321 | guardedClosure() 322 | } finally { 323 | --indent 324 | // Force result to SUCCESS so that rescue closure will execute 325 | Result r = flowRun.state.result 326 | flowRun.state.result = SUCCESS 327 | println("} rescue {") 328 | ++indent 329 | try { 330 | rescueClosure() 331 | } finally { 332 | --indent 333 | println("}") 334 | } 335 | // restore result, as the worst from guarded and rescue closures 336 | flowRun.state.result = r.combine(flowRun.state.result) 337 | } 338 | } ] 339 | } 340 | 341 | def ignore(Result result, closure) { 342 | statusCheck() 343 | Result r = flowRun.state.result 344 | def closureException = null 345 | try { 346 | println("ignore("+result+") {") 347 | ++indent 348 | closure() 349 | } 350 | catch ( Exception ex ) { 351 | closureException = ex 352 | } 353 | finally { 354 | // rethrow if there was a non-JobExecutionFailureException Exception 355 | if ( closureException != null && !(closureException instanceof JobExecutionFailureException) ) { 356 | throw closureException 357 | } 358 | 359 | final boolean ignore = flowRun.state.result.isBetterOrEqualTo(result) 360 | if (ignore) { 361 | // restore result 362 | println("// ${flowRun.state.result} ignored") 363 | flowRun.state.result = r 364 | } 365 | --indent 366 | println("}") 367 | 368 | if (ignore) return // hides JobExecutionFailureException that may have been thrown running the closure 369 | } 370 | } 371 | 372 | def retry(int attempts, worstAllowed='SUCCESS', retryClosure) { 373 | statusCheck() 374 | Result origin = flowRun.state.result 375 | int i = 0; 376 | Result worstAllowedResult = Result.fromString(worstAllowed) 377 | while( attempts-- > 0) { 378 | // Restore the pre-retry result state to ignore failures 379 | flowRun.state.result = origin 380 | i++; 381 | println("retry (attempt $i) {") 382 | ++indent 383 | 384 | retryClosure() 385 | 386 | --indent 387 | 388 | if (flowRun.state.result.isBetterOrEqualTo(worstAllowedResult)) { 389 | println("}") 390 | return; 391 | } 392 | 393 | println("} // failed") 394 | } 395 | } 396 | 397 | // allows syntax like : parallel(["Kohsuke","Nicolas"].collect { name -> return { build("job1", param1:name) } }) 398 | def List parallel(Collection closures) { 399 | parallel(closures as Closure[]) 400 | } 401 | 402 | // allows collecting job status by name rather than by index 403 | // inspired by https://github.com/caolan/async#parallel 404 | def Map parallel(Map args) { 405 | def keys = new ArrayList() 406 | def closures = new ArrayList() 407 | args.entrySet().each { e -> 408 | keys.add(e.key) 409 | closures.add(e.value) 410 | } 411 | def results = new LinkedHashMap() 412 | def flowStates = parallel(closures) // as List 413 | flowStates.eachWithIndex { v, i -> results[keys[i]] = v } 414 | results 415 | } 416 | 417 | def List parallel(Closure ... closures) { 418 | statusCheck() 419 | // TODO use NamingThreadFactory since Jenkins 1.541 420 | ExecutorService pool = Executors.newCachedThreadPool(new ThreadFactory() { 421 | public Thread newThread(Runnable r) { 422 | def thread = Executors.defaultThreadFactory().newThread(r); 423 | thread.name = "BuildFlow parallel statement thread for " + flowRun.parent.fullName; 424 | return thread; 425 | } 426 | }); 427 | Set upstream = flowRun.state.lastCompleted 428 | Set lastCompleted = Collections.synchronizedSet(new HashSet()) 429 | def results = new CopyOnWriteArrayList() 430 | def tasks = new ArrayList>() 431 | 432 | println("parallel {") 433 | ++indent 434 | 435 | def current_state = flowRun.state 436 | try { 437 | 438 | closures.each {closure -> 439 | Closure track_closure = { 440 | def ctx = ACL.impersonate(ACL.SYSTEM) 441 | try { 442 | flowRun.state = new FlowState(SUCCESS, upstream) 443 | closure() 444 | lastCompleted.addAll(flowRun.state.lastCompleted) 445 | return flowRun.state 446 | } finally { 447 | SecurityContextHolder.setContext(ctx) 448 | } 449 | } 450 | 451 | tasks.add(pool.submit(track_closure as Callable)) 452 | } 453 | 454 | tasks.each {task -> 455 | try { 456 | def final_state = task.get() 457 | Result result = final_state.result 458 | results.add(final_state) 459 | current_state.result = current_state.result.combine(result) 460 | } catch(ExecutionException e) 461 | { 462 | // TODO perhaps rethrow? 463 | current_state.result = FAILURE 464 | listener.error("Failed to run DSL Script") 465 | e.printStackTrace(listener.getLogger()) 466 | } 467 | } 468 | 469 | pool.shutdown() 470 | pool.awaitTermination(1, TimeUnit.DAYS) 471 | current_state.lastCompleted =lastCompleted 472 | } finally { 473 | flowRun.state = current_state 474 | --indent 475 | println("}") 476 | } 477 | return results 478 | } 479 | 480 | /** 481 | * Access the build flow DSL extensions that come from other plugins. 482 | * 483 | *

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> project; 55 | 56 | private transient Run build; 57 | 58 | private transient QueueTaskFuture> 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 actions) { 95 | future = project.scheduleBuild2(project.getQuietPeriod(), cause, actions); 96 | if (future == null) { 97 | // XXX this will mark the build as failed - perhaps aborting would be a better option? 98 | throw new CouldNotScheduleJobException("Could not schedule job " 99 | + project.getName() +", ensure it is not already queued with the same parameters or is not disabled"); 100 | } 101 | return this; 102 | } 103 | 104 | /** 105 | * Makes an attempt to abort the run. 106 | * If the run has already started then an attempt is made to abort it, if the job has not yet started then 107 | * it is removed from the queue. 108 | * @return 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> 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 implements TopLevelItem, FlyweightTask, SCMTriggerItem { 64 | 65 | private final FlowIcon icon = new FlowIcon(); 66 | 67 | private String dsl; 68 | private String dslFile; 69 | 70 | private boolean buildNeedsWorkspace; 71 | 72 | 73 | public BuildFlow(ItemGroup parent, String name) { 74 | super(parent, name); 75 | } 76 | 77 | public String getDsl() { 78 | return dsl; 79 | } 80 | 81 | public void setDsl(String dsl) { 82 | this.dsl = dsl; 83 | } 84 | 85 | public boolean getBuildNeedsWorkspace() { 86 | return buildNeedsWorkspace; 87 | } 88 | 89 | public void setBuildNeedsWorkspace(boolean buildNeedsWorkspace) { 90 | this.buildNeedsWorkspace = buildNeedsWorkspace; 91 | } 92 | 93 | public String getDslFile() { 94 | return dslFile; 95 | } 96 | 97 | public void setDslFile(String dslFile) { 98 | this.dslFile = dslFile; 99 | } 100 | 101 | @Override 102 | protected void submit(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException, FormException { 103 | super.submit(req, rsp); 104 | JSONObject json = req.getSubmittedForm(); 105 | this.buildNeedsWorkspace = json.containsKey("buildNeedsWorkspace"); 106 | if (Jenkins.getInstance().hasPermission(Jenkins.RUN_SCRIPTS)) { 107 | this.dsl = json.getString("dsl"); 108 | if (this.buildNeedsWorkspace) { 109 | JSONObject o = json.getJSONObject("buildNeedsWorkspace"); 110 | this.dslFile = Util.fixEmptyAndTrim(o.getString("dslFile")); 111 | } else { 112 | this.dslFile = null; 113 | } 114 | } 115 | } 116 | 117 | @Extension 118 | public static final BuildFlowDescriptor DESCRIPTOR = new BuildFlowDescriptor(); 119 | 120 | public BuildFlowDescriptor getDescriptor() { 121 | return DESCRIPTOR; 122 | } 123 | 124 | @Override 125 | public String getPronoun() { 126 | return AlternativeUiTextProvider.get(PRONOUN, this, Messages.BuildFlow_Messages()); 127 | } 128 | 129 | 130 | public static class BuildFlowDescriptor extends AbstractProjectDescriptor { 131 | @Override 132 | public String getDisplayName() { 133 | return Messages.BuildFlow_Messages(); 134 | } 135 | 136 | @Override 137 | public TopLevelItem newInstance(ItemGroup parent, String name) { 138 | return new BuildFlow(parent, name); 139 | } 140 | 141 | public FormValidation doCheckDsl(@QueryParameter String value) { 142 | // Require RUN_SCRIPTS permission, otherwise print a warning that no edits are possible 143 | if (!Jenkins.getInstance().hasPermission(Jenkins.RUN_SCRIPTS)) { 144 | return FormValidation.warning(Messages.BuildFlow_InsufficientPermissions()); 145 | } 146 | 147 | try { 148 | new GroovyShell().parse(value); 149 | } 150 | catch (MultipleCompilationErrorsException e) { 151 | return FormValidation.error( e.getMessage()); 152 | } 153 | return FormValidation.ok(); 154 | } 155 | 156 | } 157 | 158 | @Override 159 | protected Class getBuildClass() { 160 | return FlowRun.class; 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/BuildFlowAction.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.model.BuildBadgeAction; 28 | 29 | import hudson.model.Action; 30 | 31 | /** 32 | * @author Nicolas De loof 33 | */ 34 | public class BuildFlowAction implements BuildBadgeAction { 35 | 36 | private final FlowRun flow; 37 | 38 | public BuildFlowAction(FlowRun flow) { 39 | this.flow = flow; 40 | } 41 | 42 | public FlowRun getFlow() { 43 | return flow; 44 | } 45 | 46 | public String getTooltip() { 47 | //FIXME use bundle 48 | return "This build was triggered by a flow"; 49 | } 50 | 51 | public String getIconFileName() { 52 | return "/plugin/build-flow-plugin/images/16x16/flow.png"; 53 | } 54 | 55 | public String getDisplayName() { 56 | return Messages.BuildFlowAction_Messages(); 57 | } 58 | 59 | public String getUrlName() { 60 | //FIXME flow is not persisted so it is null when reloading action from previous build 61 | return flow.getBuildFlow().getAbsoluteUrl(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/BuildFlowDSLExtension.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.ExtensionList; 28 | import hudson.ExtensionPoint; 29 | import jenkins.model.Jenkins; 30 | 31 | /** 32 | * Extends BuildFlow DSL by inserting additional methods/properties. 33 | * 34 | * @author Kohsuke Kawaguchi 35 | */ 36 | public abstract class BuildFlowDSLExtension implements ExtensionPoint { 37 | /** 38 | * Creates a new DSL extension object. 39 | * 40 | * @param extensionName 41 | * String that identifies the extension. 42 | * This string should primarily the artifact ID of the plugin, 43 | * such as "external-resource-dispatcher". The consist naming between extension 44 | * and plugin name makes it easier for users to use extensions 45 | * from an untyped scripting environment of Groovy. 46 | * 47 | * If a plugin needs to expose multiple extensions from a single plugin, 48 | * append suffix to the artifact ID, for example "external-resource-dispatcher.manager" 49 | * 50 | * @return 51 | * null if this extension doesn't understand the specified extension name. 52 | */ 53 | public abstract Object createExtension(String extensionName, FlowDelegate dsl); 54 | 55 | /** 56 | * All the installed extensions. 57 | */ 58 | public static ExtensionList all() { 59 | return Jenkins.getInstance().getExtensionList(BuildFlowDSLExtension.class); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/BuildFlowPlugin.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.Plugin; 28 | 29 | /** 30 | * @author Nicolas De loof 31 | */ 32 | public class BuildFlowPlugin extends Plugin { 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/CouldNotScheduleJobException.java: -------------------------------------------------------------------------------- 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 | /* 27 | * To change this template, choose Tools | Templates 28 | * and open the template in the editor. 29 | */ 30 | package com.cloudbees.plugins.flow; 31 | 32 | /** 33 | * 34 | * @author Julia S.Simon 35 | */ 36 | class CouldNotScheduleJobException extends RuntimeException { 37 | 38 | public CouldNotScheduleJobException(String message) { 39 | super(message); 40 | } 41 | 42 | public CouldNotScheduleJobException(Exception e) { 43 | super(e); 44 | } 45 | 46 | public CouldNotScheduleJobException(String message, Exception e) { 47 | super(message, e); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/FlowAbortedCause.java: -------------------------------------------------------------------------------- 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 | package com.cloudbees.plugins.flow; 26 | 27 | 28 | import jenkins.model.CauseOfInterruption; 29 | import jenkins.model.Jenkins; 30 | 31 | public class FlowAbortedCause extends CauseOfInterruption { 32 | 33 | /** The BuildFlow project that caused the abort */ 34 | private final String project; 35 | /** The build number of the project that caused the abort. */ 36 | private final int build; 37 | 38 | public FlowAbortedCause(FlowRun flowRun) { 39 | this.project = flowRun.getParent().getFullName(); 40 | this.build = flowRun.getNumber(); 41 | } 42 | 43 | public String getAbortedBuildFlow() { 44 | return project; 45 | } 46 | 47 | public int getAbortedBuildNumber() { 48 | return build; 49 | } 50 | 51 | @Override 52 | public String getShortDescription() { 53 | return "Job aborted as " + project + "#" + build +" was aborted."; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/FlowCause.java: -------------------------------------------------------------------------------- 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.Cause; 29 | import hudson.model.Run; 30 | import jenkins.model.Jenkins; 31 | 32 | /** 33 | * @author Nicolas De Loof 34 | */ 35 | public class FlowCause extends Cause.UpstreamCause { 36 | 37 | private transient FlowRun flowRun; 38 | 39 | private JobInvocation associatedJob; 40 | 41 | private final String cause; 42 | 43 | public FlowCause(FlowRun flowRun, JobInvocation associatedJob) { 44 | super((Run)flowRun); 45 | this.flowRun = flowRun; 46 | this.cause = flowRun.getParent().getFullName() + "#" + flowRun.getNumber(); 47 | this.associatedJob = associatedJob; 48 | } 49 | 50 | public FlowRun getFlowRun() { 51 | // TODO(mattmoor): If we upgrade to 1.505+ we can replace usage 52 | // of this with UpstreamCause#getUpstreamRun(). 53 | if (this.flowRun == null) { 54 | int i = cause.lastIndexOf('#'); 55 | BuildFlow flow = Jenkins.getInstance().getItemByFullName(cause.substring(0,i), BuildFlow.class); 56 | flowRun = flow.getBuildByNumber(Integer.parseInt(cause.substring(i+1))); 57 | } 58 | return flowRun; 59 | } 60 | 61 | public JobInvocation getAssociatedJob() { 62 | return associatedJob; 63 | } 64 | 65 | public String getBuildFlow() { 66 | int i = cause.lastIndexOf('#'); 67 | return cause.substring(0,i); 68 | } 69 | 70 | public int getBuildNumber() { 71 | int i = cause.lastIndexOf('#'); 72 | return Integer.parseInt(cause.substring(i+1)); 73 | } 74 | 75 | @Override 76 | public String getShortDescription() { 77 | return "Started by build flow " + cause; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/FlowIcon.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.model.AbstractStatusIcon; 28 | 29 | import org.kohsuke.stapler.Stapler; 30 | 31 | /** 32 | * @author Nicolas De loof 33 | */ 34 | public class FlowIcon extends AbstractStatusIcon { 35 | 36 | public String getImageOf(String size) { 37 | return Stapler.getCurrentRequest().getContextPath()+"/plugin/build-flow-plugin/images/"+size+"/flow.png"; 38 | } 39 | 40 | public String getDescription() { 41 | return Messages.FlowIcon_Messages(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/FlowListener.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 java.util.List; 28 | 29 | import hudson.Extension; 30 | import hudson.model.TaskListener; 31 | import hudson.model.Run; 32 | import hudson.model.Cause; 33 | import hudson.model.listeners.RunListener; 34 | 35 | @Extension 36 | public class FlowListener extends RunListener> { 37 | 38 | @Override 39 | public synchronized void onStarted(Run startedBuild, 40 | TaskListener listener) { 41 | List causes = startedBuild.getCauses(); 42 | for (Cause cause : causes) { 43 | if (cause instanceof FlowCause) { 44 | ((FlowCause) cause).getAssociatedJob().buildStarted( 45 | startedBuild); 46 | } 47 | } 48 | } 49 | 50 | @Override 51 | public synchronized void onCompleted(Run finishedBuild, 52 | TaskListener listener) { 53 | List causes = finishedBuild.getCauses(); 54 | for (Cause cause : causes) { 55 | if (cause instanceof FlowCause) { 56 | ((FlowCause) cause).getAssociatedJob().buildCompleted(); 57 | } 58 | } 59 | } 60 | 61 | @Override 62 | public synchronized void onFinalized(Run finalizedBuild) { 63 | List causes = finalizedBuild.getCauses(); 64 | for (Cause cause : causes) { 65 | if (cause instanceof FlowCause) { 66 | ((FlowCause) cause).getAssociatedJob().buildFinalized(); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/FlowRun.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 static hudson.model.Result.FAILURE; 28 | import static hudson.model.Result.SUCCESS; 29 | import hudson.Util; 30 | import hudson.model.Action; 31 | import hudson.model.Build; 32 | import hudson.model.BuildListener; 33 | import hudson.model.Result; 34 | import hudson.model.Run; 35 | 36 | import java.io.File; 37 | import java.io.IOException; 38 | import java.util.Collections; 39 | import java.util.Comparator; 40 | import java.util.LinkedList; 41 | import java.util.List; 42 | import java.util.concurrent.ExecutionException; 43 | import java.util.concurrent.atomic.AtomicInteger; 44 | import java.util.logging.Logger; 45 | 46 | import org.jgrapht.DirectedGraph; 47 | import org.jgrapht.ext.DOTExporter; 48 | import org.jgrapht.graph.SimpleDirectedGraph; 49 | import org.kohsuke.stapler.StaplerRequest; 50 | import org.kohsuke.stapler.StaplerResponse; 51 | 52 | /** 53 | * Maintain the state of execution of a build flow as a chain of triggered jobs 54 | * 55 | * @author Nicolas De loof 56 | */ 57 | public class FlowRun extends Build { 58 | 59 | private static final Logger LOGGER = Logger.getLogger(FlowRun.class.getName()); 60 | 61 | private String dsl; 62 | private String dslFile; 63 | 64 | private boolean buildNeedsWorkspace; 65 | 66 | private JobInvocation.Start startJob; 67 | 68 | private DirectedGraph jobsGraph; 69 | 70 | private transient ThreadLocal state = new ThreadLocal(); 71 | 72 | private transient AtomicInteger buildIndex = new AtomicInteger(1); 73 | 74 | public FlowRun(BuildFlow job, File buildDir) throws IOException { 75 | super(job, buildDir); 76 | setup(job); 77 | } 78 | 79 | public FlowRun(BuildFlow job) throws IOException { 80 | super(job); 81 | setup(job); 82 | } 83 | 84 | private void setup(BuildFlow job) { 85 | if (jobsGraph == null) { 86 | jobsGraph = new SimpleDirectedGraph(JobEdge.class); 87 | } 88 | if (startJob == null) { 89 | startJob = new JobInvocation.Start(this); 90 | } 91 | this.dsl = job.getDsl(); 92 | this.dslFile = job.getDslFile(); 93 | this.buildNeedsWorkspace = job.getBuildNeedsWorkspace(); 94 | startJob.buildStarted(this); 95 | jobsGraph.addVertex(startJob); 96 | state.set(new FlowState(SUCCESS, startJob)); 97 | } 98 | 99 | /* package */ void schedule(JobInvocation job, List actions) throws ExecutionException, InterruptedException { 100 | addBuild(job); 101 | job.run(new FlowCause(this, job), actions); 102 | } 103 | 104 | /* package */ Run waitForCompletion(JobInvocation job) throws ExecutionException, InterruptedException { 105 | job.waitForCompletion(); 106 | getState().setResult(job.getResult()); 107 | return job.getBuild(); 108 | } 109 | 110 | /* package */ Run waitForFinalization(JobInvocation job) throws ExecutionException, InterruptedException { 111 | job.waitForFinalization(); 112 | getState().setResult(job.getResult()); 113 | return job.getBuild(); 114 | } 115 | 116 | /* package */ FlowState getState() { 117 | return state.get(); 118 | } 119 | 120 | /* package */ void setState(FlowState s) { 121 | state.set(s); 122 | } 123 | 124 | public DirectedGraph getJobsGraph() { 125 | return jobsGraph; 126 | } 127 | 128 | public JobInvocation getStartJob() { 129 | return startJob; 130 | } 131 | 132 | public BuildFlow getBuildFlow() { 133 | return project; 134 | } 135 | 136 | public void doGetDot(StaplerRequest req, StaplerResponse rsp) throws IOException { 137 | new DOTExporter().export(rsp.getWriter(), jobsGraph); 138 | } 139 | 140 | public synchronized void addBuild(JobInvocation job) throws ExecutionException, InterruptedException { 141 | jobsGraph.addVertex(job); 142 | for (JobInvocation up : state.get().getLastCompleted()) { 143 | String edge = up.getId() + " => " + job.getId(); 144 | LOGGER.fine("added build to execution graph " + edge); 145 | jobsGraph.addEdge(up, job, new JobEdge(up, job)); 146 | } 147 | state.get().setLastCompleted(job); 148 | } 149 | 150 | @Override 151 | public void run() { 152 | if (buildNeedsWorkspace) { 153 | execute(new BuildWithWorkspaceRunnerImpl(dsl, dslFile)); 154 | } else { 155 | execute(new FlyweightTaskRunnerImpl(dsl)); 156 | } 157 | } 158 | 159 | protected class BuildWithWorkspaceRunnerImpl extends BuildExecution { 160 | 161 | private final String dsl; 162 | private final String dslFile; 163 | 164 | public BuildWithWorkspaceRunnerImpl(String dsl, String dslFile) { 165 | this.dsl = dsl; 166 | this.dslFile = dslFile; 167 | } 168 | 169 | protected Result doRun(BuildListener listener) throws Exception { 170 | if(!preBuild(listener, project.getPublishersList())) 171 | return FAILURE; 172 | 173 | try { 174 | setResult(SUCCESS); 175 | if (dslFile != null) { 176 | listener.getLogger().printf("[build-flow] reading DSL from file '%s'\n", dslFile); 177 | String fileContent = getWorkspace().child(dslFile).readToString(); 178 | new FlowDSL().executeFlowScript(FlowRun.this, fileContent, listener); 179 | } else { 180 | new FlowDSL().executeFlowScript(FlowRun.this, dsl, listener); 181 | } 182 | } finally { 183 | boolean failed=false; 184 | for( int i=buildEnvironments.size()-1; i>=0; i-- ) { 185 | if (!buildEnvironments.get(i).tearDown(FlowRun.this,listener)) { 186 | failed=true; 187 | } 188 | } 189 | if (failed) return Result.FAILURE; 190 | } 191 | return getState().getResult(); 192 | } 193 | 194 | @Override 195 | public void post2(BuildListener listener) throws IOException, InterruptedException { 196 | if(!performAllBuildSteps(listener, project.getPublishersList(), true)) 197 | setResult(FAILURE); 198 | } 199 | 200 | @Override 201 | public void cleanUp(BuildListener listener) throws Exception { 202 | performAllBuildSteps(listener, project.getPublishersList(), false); 203 | FlowRun.this.startJob.buildCompleted(); 204 | super.cleanUp(listener); 205 | } 206 | } 207 | 208 | protected class FlyweightTaskRunnerImpl extends RunExecution { 209 | 210 | private final String dsl; 211 | 212 | public FlyweightTaskRunnerImpl(String dsl) { 213 | this.dsl = dsl; 214 | } 215 | 216 | @Override 217 | public Result run(BuildListener listener) throws Exception, RunnerAbortedException { 218 | setResult(SUCCESS); 219 | new FlowDSL().executeFlowScript(FlowRun.this, dsl, listener); 220 | return getState().getResult(); 221 | } 222 | 223 | @Override 224 | public void post(BuildListener listener) throws Exception { 225 | FlowRun.this.startJob.buildCompleted(); 226 | } 227 | 228 | @Override 229 | public void cleanUp(BuildListener listener) throws Exception { 230 | 231 | } 232 | } 233 | 234 | public static class JobEdge { 235 | 236 | private JobInvocation source; 237 | private JobInvocation target; 238 | 239 | public JobEdge(JobInvocation source, JobInvocation target) { 240 | this.source = source; 241 | this.target = target; 242 | } 243 | 244 | public JobInvocation getSource() { 245 | return source; 246 | } 247 | 248 | public JobInvocation getTarget() { 249 | return target; 250 | } 251 | 252 | } 253 | 254 | } 255 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/FlowState.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.model.Result; 28 | 29 | import java.util.Collections; 30 | import java.util.Set; 31 | 32 | /** 33 | * @author Nicolas De Loof 34 | */ 35 | public class FlowState { 36 | 37 | private Result result; 38 | 39 | private Set lastCompleted; 40 | 41 | public FlowState(Result result, Set previous) { 42 | assert result != null; 43 | this.result = result; 44 | setLastCompleted(previous); 45 | } 46 | 47 | public FlowState(Result result, JobInvocation previous) { 48 | assert result != null; 49 | this.result = result; 50 | setLastCompleted(previous); 51 | } 52 | 53 | public Result getResult() { 54 | return result; 55 | } 56 | 57 | public void setResult(Result result) { 58 | assert result != null; 59 | this.result = result; 60 | } 61 | 62 | public Set getLastCompleted() { 63 | return lastCompleted; 64 | } 65 | 66 | public void setLastCompleted(JobInvocation lastCompleted) { 67 | this.lastCompleted = Collections.singleton(lastCompleted); 68 | } 69 | 70 | public void setLastCompleted(Set lastCompleted) { 71 | this.lastCompleted = lastCompleted; 72 | } 73 | 74 | public JobInvocation getLastBuild() { 75 | return this.lastCompleted.iterator().next(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/JobExecutionFailureException.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 | /** 28 | * @author Nicolas De Loof 29 | */ 30 | public class JobExecutionFailureException extends Exception { 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/cloudbees/plugins/flow/JobNotFoundException.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 | public class JobNotFoundException extends RuntimeException { 28 | 29 | public JobNotFoundException(String mess) { 30 | super(mess); 31 | } 32 | 33 | public JobNotFoundException(Exception e) { 34 | super(e); 35 | } 36 | 37 | public JobNotFoundException(String mess, Exception e) { 38 | super(mess, e); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/BuildFlow/configure-entries.jelly: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 |
53 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/BuildFlow/help-buildNeedsWorkspace.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 |
27 |
28 | Checking this box will let the build use a workspace and will be 29 | an AbstractBuild rather and a RunExecution (flyweight task). 30 |
31 |
32 |
-------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/BuildFlow/help-dsl.jelly: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 |
28 |
29 | Type in the Groovy DSL that describes how you invoke various other jobs in what order. 30 | For available DSL constructs, refer to 31 | the Wiki page. 32 | For Groovy and its syntax, see Groovy User Guide. 33 |
34 | 35 | 36 | 37 |
38 |
-------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/BuildFlow/help-dslFile.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 |
27 | Specify a file path relative to the workspace of the build where the DSL 28 | will be read. This option only works if "Flow run needs a workspace" is 29 | checked. 30 |
31 |
32 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/BuildFlow/index.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | 29 |

30 | ${it.pronoun} ${it.displayName}

31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 |
39 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/BuildFlow/newJobDetail.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 | ${%body} 27 |
-------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/BuildFlow/newJobDetail.properties: -------------------------------------------------------------------------------- 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 | body=\ 26 | A Build Flow can manage job orchestration as a dedicated entity, in a centralized way with complex \ 27 | orchestration and without polluting the jobs with various plugins to handle the upstream-downstream chain. 28 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/BuildFlowAction/badge.jelly: -------------------------------------------------------------------------------- 1 | 24 | 25 | [flow] -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/FlowAbortedCause/summary.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013, Cisco Systems, Inc., a California corporation 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 | package com.cloudbees.plugins.flow.FlowAbortedCause 25 | 26 | def proj = app.getItemByFullName(my.abortedBuildFlow); 27 | def build = proj == null ? null : proj.getBuildByNumber(my.abortedBuildNumber); 28 | 29 | if (build != null) { 30 | return raw(_("message", rootURL, proj.url, my.abortedBuildFlow, my.abortedBuildNumber )) 31 | } 32 | if (proj != null) { 33 | return raw(_("message_no_build", rootURL, proj.url, my.abortedBuildFlow, my.abortedBuildNumber )) 34 | } 35 | return raw(_("message_no_job", rootURL, null, my.abortedBuildFlow, my.abortedBuildNumber )) -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/FlowAbortedCause/summary.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2013, Cisco Systems, Inc., a California corporation 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 | message=Aborted as build flow {2} build number {3} was aborted. 25 | message_no_build=Aborted as build flow {2} build number {3} was aborted. 26 | message_no_job=Aborted as build flow "{2}" build number {3} was aborted. -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/FlowCause/description.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | ${%message(rootURL, proj.url, it.buildFlow, buildNumberString)} 12 | 13 | 14 | ${%message_no_build(rootURL, proj.url, it.buildFlow, buildNumberString)} 15 | 16 | 17 | 18 | 19 | ${%message_no_job(rootURL, null, it.buildFlow, buildNumberString)} 20 | 21 | 22 | 23 |
24 | 25 |
    26 | 27 |
  • 28 | 29 |
  • 30 |
    31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/FlowCause/description.properties: -------------------------------------------------------------------------------- 1 | # 2 | # The MIT License 3 | # 4 | # Copyright (c) 2013, Cisco Systems, Inc., a California corporation 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 | message=Started by build flow {2} build number {3}. 25 | message_no_build=Started by build flow {2} build number {3}. 26 | message_no_job=Started by build flow "{2}" build number {3}. 27 | caused_by=Originally caused by: 28 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/FlowRun/main.jelly: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/com/cloudbees/plugins/flow/Messages.properties: -------------------------------------------------------------------------------- 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 | BuildFlow.Messages=Build Flow 26 | BuildFlowAction.Messages=Build Flow 27 | BuildFlowBootstrapper.Messages=Launch build flow 28 | FlowIcon.Messages=Build Flow 29 | BuildFlow.InvalidDSL=Invalid DSL 30 | BuildFlow.InsufficientPermissions=You do not have the Run Scripts permission necessary to edit this field. 31 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 24 |
25 | Manage jobs orchestration as a dedicated "build flow" top level item 26 |
-------------------------------------------------------------------------------- /src/main/webapp/images/16x16/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/build-flow-plugin/d8d559258577e5009c9db95ec7e866e4cedc99e1/src/main/webapp/images/16x16/flow.png -------------------------------------------------------------------------------- /src/main/webapp/images/24x24/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/build-flow-plugin/d8d559258577e5009c9db95ec7e866e4cedc99e1/src/main/webapp/images/24x24/flow.png -------------------------------------------------------------------------------- /src/main/webapp/images/32x32/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/build-flow-plugin/d8d559258577e5009c9db95ec7e866e4cedc99e1/src/main/webapp/images/32x32/flow.png -------------------------------------------------------------------------------- /src/main/webapp/images/48x48/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/build-flow-plugin/d8d559258577e5009c9db95ec7e866e4cedc99e1/src/main/webapp/images/48x48/flow.png -------------------------------------------------------------------------------- /src/main/webapp/images/LICENSE: -------------------------------------------------------------------------------- 1 | License: CC-BY-SA 3.0 or LGPL 2 | http://openiconlibrary.sourceforge.net/gallery2/?./Icons/actions/code-class.png -------------------------------------------------------------------------------- /src/test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/build-flow-plugin/d8d559258577e5009c9db95ec7e866e4cedc99e1/src/test/.DS_Store -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/AbortTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013, Cisco Systems, Inc., a California corporation 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 | package com.cloudbees.plugins.flow 25 | 26 | import hudson.model.Job 27 | import hudson.model.Result 28 | import jenkins.model.Jenkins 29 | import hudson.model.Cause.UserIdCause; 30 | import hudson.model.ParametersAction; 31 | import hudson.model.StringParameterValue; 32 | import org.junit.Test 33 | 34 | /** 35 | * Tests that when a flow is aborted is it reported correctly. 36 | * @author James Nord 37 | */ 38 | class AbortTest extends DSLTestCase { 39 | 40 | 41 | /** 42 | * Tests that when a Flow is aborted it correctly aborts jobs that it started. 43 | */ 44 | @Test 45 | public void testThatAbortAbortsStartedJobs() { 46 | File f1 = new File("target", "${name.getMethodName()}_job1.lock") 47 | f1.mkdirs() 48 | f1.createNewFile() 49 | 50 | def job1 = createBlockingJob("job1", f1) 51 | def job2 = jenkinsRule.createFreeStyleProject("job2") 52 | 53 | def future = schedule(""" 54 | build("job1") 55 | build("job2") 56 | """) 57 | 58 | def flow = future.waitForStart() 59 | // wait for job1 to start 60 | while (!job1.building) { 61 | Thread.sleep(10L) 62 | } 63 | println("job has started") 64 | 65 | // abort the flow 66 | println("aborting...") 67 | flow.oneOffExecutor.interrupt(Result.ABORTED) 68 | println("aborting request sent...") 69 | // wait for the flow to finish executing. 70 | future.get(); 71 | 72 | jenkinsRule.assertBuildStatus(Result.ABORTED, flow) 73 | jenkinsRule.assertBuildStatus(Result.ABORTED, job1.lastBuild) 74 | assertDidNotRun(job2) 75 | } 76 | 77 | /** 78 | * Tests that when a Flow is aborted it correctly aborts jobs that it started 79 | * in a parallel block. 80 | */ 81 | @Test 82 | public void testThatAbortAbortsStartedJobs_ParallelBlock() { 83 | File f1 = new File("target", "${name.getMethodName()}_job1.lock") 84 | File f2 = new File("target", "${name.getMethodName()}_job2.lock") 85 | f1.mkdirs() 86 | f1.createNewFile() 87 | f2.mkdirs() 88 | f2.createNewFile() 89 | 90 | Job job1 = createBlockingJob("job1", f1) 91 | Job job2 = createBlockingJob("job2", f2) 92 | 93 | def future = schedule(""" 94 | parallel( 95 | { build("job1") }, 96 | { build("job2") }, 97 | ) 98 | build.state.result = FAILURE 99 | fail() 100 | """) 101 | 102 | def flow = future.waitForStart() 103 | // wait for job1 to start 104 | while (!job1.building) { 105 | Thread.sleep(10L) 106 | } 107 | println("job has started") 108 | 109 | // abort the flow 110 | println("aborting...") 111 | flow.oneOffExecutor.interrupt(Result.ABORTED) 112 | println("aborting request sent...") 113 | // wait for the flow to finish executing. 114 | future.get(); 115 | 116 | jenkinsRule.assertBuildStatus(Result.ABORTED, job1.lastBuild) 117 | jenkinsRule.assertBuildStatus(Result.ABORTED, job2.lastBuild) 118 | jenkinsRule.assertBuildStatus(Result.ABORTED, flow) 119 | } 120 | 121 | /** 122 | * Ensures an ignored parallel block does not supress the aborted job status. 123 | */ 124 | @Test 125 | public void testThatAbortAbortsStartedJobs_IgnoredParallel() { 126 | File f1 = new File("target", "${name.getMethodName()}_job1.lock") 127 | File f2 = new File("target", "${name.getMethodName()}_job2.lock") 128 | f1.mkdirs() 129 | f1.createNewFile() 130 | f2.mkdirs() 131 | f2.createNewFile() 132 | 133 | Job job1 = createBlockingJob("job1", f1) 134 | Job job2 = createBlockingJob("job2", f2) 135 | 136 | def future = schedule(""" 137 | ignore(ABORTED) { 138 | parallel( 139 | { build("job1") }, 140 | { build("job2") }, 141 | ) 142 | } 143 | build.state.result = FAILURE 144 | fail() 145 | """) 146 | 147 | def flow = future.waitForStart() 148 | // wait for job1 to start 149 | while (!job1.building) { 150 | Thread.sleep(10L) 151 | } 152 | println("job has started") 153 | 154 | // abort the flow 155 | println("aborting...") 156 | flow.oneOffExecutor.interrupt(Result.ABORTED) 157 | println("aborting request sent...") 158 | // wait for the flow to finish executing. 159 | future.get(); 160 | 161 | jenkinsRule.assertBuildStatus(Result.ABORTED, job1.lastBuild) 162 | jenkinsRule.assertBuildStatus(Result.ABORTED, job2.lastBuild) 163 | jenkinsRule.assertBuildStatus(Result.ABORTED, flow) 164 | } 165 | 166 | /** 167 | * Tests that when a Flow is aborted it correctly aborts jobs that it queued. 168 | */ 169 | @Test 170 | public void testThatAbortAbortsQueuedJobs() { 171 | File f1 = new File("target", "${name.getMethodName()}_job1.lock") 172 | f1.createNewFile() 173 | 174 | Job job1 = createBlockingJob("job1", f1) 175 | 176 | BuildFlow flow = new BuildFlow(Jenkins.instance, name.getMethodName()) 177 | flow.concurrentBuild = true; 178 | flow.onCreatedFromScratch() 179 | flow.dsl = """ build("job1", param1: build.number) """ 180 | 181 | // Start an initially blocked job to to create a queue. 182 | def sfr1 = job1.scheduleBuild2(0,new UserIdCause(), new ParametersAction(new StringParameterValue("param1", "first"))); 183 | def fr1 = sfr1.waitForStart() 184 | 185 | println("Starting build flows...") 186 | // Start three concurrent flows queueing up a job1, 187 | // wait for the job1 to queue up before starting the next 188 | // flow to ensure a consistent queue. 189 | def queue = Jenkins.instance.queue; 190 | def flows = [] 191 | for (i in 1..3) { 192 | def scheduled = flow.scheduleBuild2(0) 193 | def future = scheduled.waitForStart() 194 | while (queue.getItems(job1).size() < i ) { 195 | Thread.sleep(10L); 196 | } 197 | flows.add([flow:scheduled, future: future]) 198 | } 199 | 200 | // abort the second flow 201 | println("aborting flow #2...") 202 | flows[1].future.oneOffExecutor.interrupt(Result.ABORTED) 203 | println("aborting request sent...") 204 | // wait for the flow to finish executing. 205 | jenkinsRule.assertBuildStatus(Result.ABORTED, flows[1].flow.get()) 206 | 207 | // Release the blocked job 208 | System.out.println("releasing jobs") 209 | f1.delete() 210 | 211 | jenkinsRule.waitUntilNoActivityUpTo(25000) 212 | 213 | assertRan(job1, 3, Result.SUCCESS) 214 | 215 | // Needs to assert that only the aborted build, (param1:2), is missing 216 | // since no records exist when queued builds are cancelled. 217 | assertHasParameter(job1.builds[2], 'param1', 'first') 218 | assertHasParameter(job1.builds[1], 'param1', '1') 219 | assertHasParameter(job1.builds[0], 'param1', '3') 220 | 221 | // Assert that non aborted flows succeeded. 222 | jenkinsRule.assertBuildStatusSuccess(flows[0].flow.get()) 223 | jenkinsRule.assertBuildStatusSuccess(flows[2].flow.get()) 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/BindingTest.groovy: -------------------------------------------------------------------------------- 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.model.Result 28 | 29 | import static hudson.model.Result.SUCCESS 30 | import static hudson.model.Result.FAILURE 31 | import hudson.model.Job 32 | import jenkins.model.Jenkins 33 | import org.junit.Ignore 34 | import org.junit.Test 35 | import hudson.slaves.EnvironmentVariablesNodeProperty 36 | import hudson.slaves.EnvironmentVariablesNodeProperty.Entry 37 | 38 | class BindingTest extends DSLTestCase { 39 | 40 | @Test 41 | public void testBindings() { 42 | Job job1 = createJob("job1") 43 | def flow = run(""" 44 | out.println("yeah") 45 | build("job1", param1: build.number) 46 | """) 47 | def build = assertSuccess(job1) 48 | assertHasParameter(build, "param1", "1") 49 | assert SUCCESS == flow.result 50 | } 51 | 52 | @Test 53 | public void testEnvBinding() { 54 | jenkinsRule.jenkins.globalNodeProperties.add(new EnvironmentVariablesNodeProperty(new Entry("someGlobalProperty", "expectedGlobalPropertyValue"))) 55 | jenkinsRule.jenkins.nodeProperties.add(new EnvironmentVariablesNodeProperty(new Entry("someNodeProperty", "expectedNodePropertyValue"))) 56 | 57 | Job job1 = createJob("job1") 58 | def flow = run(""" 59 | out.println("yeah") 60 | build("job1", param1: env["someGlobalProperty"], param2: env["someNodeProperty"]) 61 | """) 62 | def build = assertSuccess(job1) 63 | assertHasParameter(build, "param1", "expectedGlobalPropertyValue") 64 | assertHasParameter(build, "param2", "expectedNodePropertyValue") 65 | assert SUCCESS == flow.result 66 | } 67 | 68 | @Test 69 | public void testBuiltinImports() { 70 | def flow = run(""" 71 | println "test="+SUCCESS.class.name; 72 | """) 73 | assert SUCCESS == flow.result 74 | assert flow.log.contains("test="+ Result.SUCCESS.class.name) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/BuildFlowDSLExtensionTest.groovy: -------------------------------------------------------------------------------- 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 com.cloudbees.plugin.flow.TestDSLExtension 28 | import hudson.model.Result 29 | import org.junit.Test 30 | 31 | class BuildFlowDSLExtensionTest extends DSLTestCase { 32 | 33 | @Test 34 | public void testUseExtension() { 35 | BuildFlowDSLExtension.all().add(new TestDSLExtension()) 36 | def flow = run(""" 37 | x = extension.test123 38 | println "name="+x.name 39 | println "dsl="+x.dsl.class.name 40 | """) 41 | System.out.println flow.log 42 | assert Result.SUCCESS == flow.result 43 | assert flow.log.contains("name=test123") 44 | assert flow.log.contains("dsl="+FlowDelegate.class.name) 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/BuildTest.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.Result 29 | import org.jvnet.hudson.test.Issue 30 | 31 | import static hudson.model.Result.SUCCESS 32 | import static hudson.model.Result.FAILURE 33 | import jenkins.model.Jenkins 34 | import hudson.model.Job 35 | import hudson.model.Action 36 | import hudson.model.ParametersAction 37 | import hudson.model.ParameterValue 38 | import hudson.model.StringParameterValue 39 | import hudson.model.ParametersDefinitionProperty 40 | import hudson.model.ParameterDefinition 41 | import hudson.model.StringParameterDefinition 42 | import hudson.model.FreeStyleProject 43 | 44 | import static hudson.model.Result.UNSTABLE 45 | import org.junit.Test 46 | 47 | class BuildTest extends DSLTestCase { 48 | 49 | @Test 50 | public void testUnknownJob() { 51 | def flow = run(""" 52 | build("unknown") 53 | """) 54 | assert FAILURE == flow.result 55 | jenkinsRule.assertLogContains("Item unknown not found (or isn't a job).", flow) 56 | } 57 | 58 | @Test 59 | public void testDisabledJob() { 60 | def disabledJob = createJob("disabledJob") 61 | disabledJob.disable() 62 | 63 | def flow = run(""" 64 | build("disabledJob") 65 | """) 66 | 67 | jenkinsRule.assertBuildStatus(FAILURE, flow); 68 | jenkinsRule.assertLogContains("Could not schedule job disabledJob, ensure it is not already queued with the same parameters or is not disabled", flow) 69 | } 70 | 71 | @Test 72 | public void testSingleBuild() { 73 | Job job1 = createJob("job1") 74 | def flow = run(""" 75 | build("job1") 76 | """) 77 | assertSuccess(job1) 78 | assert SUCCESS == flow.result 79 | } 80 | 81 | @Test 82 | public void testBuildWithParams() { 83 | Job job1 = createJob("job1") 84 | def flow = run(""" 85 | build("job1", 86 | param1: "one", 87 | param2: "two") 88 | """) 89 | def build = assertSuccess(job1) 90 | assertHasParameter(build, "param1", "one") 91 | assertHasParameter(build, "param2", "two") 92 | assert SUCCESS == flow.result 93 | } 94 | 95 | @Test 96 | public void testBuildWithParamsAsMap() { 97 | Job job1 = createJob("job1") 98 | def flow = run(""" 99 | def params = ["param1": "one", "param2": "two"] 100 | build(params, "job1") 101 | """) 102 | def build = assertSuccess(job1) 103 | assertHasParameter(build, "param1", "one") 104 | assertHasParameter(build, "param2", "two") 105 | assert SUCCESS == flow.result 106 | } 107 | 108 | @Test 109 | public void testJobFailure() { 110 | Job willFail = createFailJob("willFail"); 111 | def flow = run(""" 112 | build("willFail") 113 | build("willNotRun") 114 | """) 115 | assertFailure(willFail) 116 | assert FAILURE == flow.result 117 | } 118 | 119 | @Test 120 | public void testJobUnstable() { 121 | Job unstable = createUnstableJob("unstable"); 122 | def flow = run(""" 123 | build("unstable") 124 | build("willNotRun") 125 | """) 126 | assertUnstable(unstable) 127 | assert UNSTABLE == flow.result 128 | } 129 | 130 | @Test 131 | public void testSequentialBuilds() { 132 | def jobs = createJobs(["job1", "job2", "job3"]) 133 | def flow = run(""" 134 | build("job1") 135 | build("job2") 136 | build("job3") 137 | """) 138 | assertAllSuccess(jobs) 139 | assert SUCCESS == flow.result 140 | println flow.jobsGraph.edgeSet() 141 | } 142 | 143 | @Test 144 | public void testSequentialBuildsWithFailure() { 145 | def jobs = createJobs(["job1", "job2", "job3"]) 146 | def willFail = createFailJob("willFail") 147 | def notRan = createJob("notRan") 148 | def flow = run(""" 149 | build("job1") 150 | build("job2") 151 | build("job3") 152 | build("willFail") 153 | build("notRan") 154 | """) 155 | assertAllSuccess(jobs) 156 | assertFailure(willFail) 157 | assertDidNotRun(notRan) 158 | assert FAILURE == flow.result 159 | println flow.jobsGraph.edgeSet() 160 | } 161 | 162 | @Test 163 | public void testParametersFromBuild() { 164 | Job job1 = createJob("job1") 165 | Job job2 = createJob("job2") 166 | def flow = run(""" 167 | b = build("job1") 168 | build("job2", 169 | param1: b.result.name, 170 | param2: b.name) 171 | """) 172 | assertSuccess(job1) 173 | def build = assertSuccess(job2) 174 | assertHasParameter(build, "param1", "SUCCESS") 175 | assertHasParameter(build, "param2", "job1") 176 | assert SUCCESS == flow.result 177 | } 178 | 179 | @Test 180 | public void testParametersFromBuildWithDefaultValues() { 181 | FreeStyleProject job1 = createJob("job1") 182 | def parametersDefinitions = new ParametersDefinitionProperty(new StringParameterDefinition("param1", "0"), new StringParameterDefinition("param2", "0")) 183 | job1.addProperty(parametersDefinitions) 184 | 185 | def flow = run(""" 186 | b = build("job1", 187 | param1:"1", 188 | param3:"3") 189 | """) 190 | 191 | def build = assertSuccess(job1) 192 | assertHasParameter(build, "param1", "1") 193 | assertHasParameter(build, "param2", "0") 194 | assertHasParameter(build, "param3", "3") 195 | assert SUCCESS == flow.result 196 | } 197 | 198 | @Issue("JENKINS-17199") 199 | @Test 200 | public void testImportStatement() { 201 | def flow = run(""" 202 | import java.util.Date; 203 | println "Hello from date: "+new Date(); 204 | """) 205 | assert SUCCESS == flow.result 206 | assert flow.log.contains("Hello from date: ") 207 | } 208 | 209 | @Test 210 | public void testParallelThreadName() { 211 | BuildFlow flow = new BuildFlow(Jenkins.instance, "project_name") 212 | flow.dsl = """ 213 | parallel( 214 | { println Thread.currentThread().name } 215 | ); 216 | """ 217 | flow.onCreatedFromScratch() // need this to updateTransientActions 218 | def build = flow.scheduleBuild2(0).get() 219 | 220 | assert SUCCESS == build.result 221 | assert build.log.contains("BuildFlow parallel statement thread for project_name") 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/CombinationTest.groovy: -------------------------------------------------------------------------------- 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.model.Cause 28 | import hudson.model.FreeStyleBuild 29 | import hudson.model.Job 30 | 31 | import static hudson.model.Result.SUCCESS 32 | import static hudson.model.Result.FAILURE 33 | import org.junit.Test 34 | 35 | public class CombinationTest extends DSLTestCase { 36 | 37 | @Test 38 | public void testRetryWithGuard() { 39 | Job job1 = createJob("job1") 40 | Job willFail = createFailJob("willFail") 41 | def flow = run(""" 42 | retry(3) { 43 | guard { 44 | build("job1") 45 | } rescue { 46 | build("willFail") 47 | } 48 | } 49 | """) 50 | assertRan(job1, 3, SUCCESS) 51 | assertRan(willFail, 3, FAILURE) 52 | assert FAILURE == flow.result 53 | } 54 | 55 | @Test 56 | public void testGuardWithParallel() { 57 | def jobs = createJobs(["job1", "job2", "rescue"]) 58 | Job willFail = createFailJob("willFail") 59 | def flow = run(""" 60 | guard { 61 | parallel ( 62 | { build("job1") }, 63 | { build("job2") }, 64 | { build("willFail") } 65 | ) 66 | } rescue { 67 | build("rescue") 68 | } 69 | """) 70 | assertAllSuccess(jobs) 71 | assertRan(willFail, 1, FAILURE) 72 | assert FAILURE == flow.result 73 | } 74 | 75 | @Test 76 | public void testParallelSubFlows() { 77 | def jobs = createJobs(["job1", "job2", "job3"]) 78 | Job willFail = createFailJob("willFail") 79 | Job job5 = createJob("job5") 80 | 81 | def flow = run(""" 82 | parallel ( 83 | { 84 | guard { 85 | build("job1") 86 | } rescue { 87 | build("job2") 88 | } 89 | }, 90 | { 91 | build("job3") 92 | retry(3) { 93 | build("willFail") 94 | } 95 | } 96 | ) 97 | build("job5") 98 | """) 99 | assertAllSuccess(jobs) 100 | assertRan(willFail, 3, FAILURE) 101 | assertDidNotRun(job5) 102 | assert FAILURE == flow.result 103 | println flow.jobsGraph.edgeSet() 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/ConcurrencyTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013, Cisco Systems, Inc., a California corporation 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 | package com.cloudbees.plugins.flow 25 | 26 | import hudson.model.Cause 27 | import hudson.model.ParametersDefinitionProperty 28 | import hudson.model.RunParameterDefinition 29 | import jenkins.model.Jenkins 30 | 31 | import static hudson.model.Result.FAILURE 32 | import static hudson.model.Result.SUCCESS 33 | import org.junit.Test 34 | import static org.junit.Assert.assertTrue 35 | 36 | class ConcurrencyTest extends DSLTestCase { 37 | 38 | /** 39 | * Test that triggering multiple builds of the same concurrent build is ok. 40 | * This relies on the default quiet period of 5 seconds. 41 | */ 42 | @Test 43 | public void testConcurrency() { 44 | Jenkins.instance.numExecutors = 8 45 | Jenkins.instance.reload() 46 | 47 | File f1 = new File("target", "concjob1.block") 48 | // this will prevent job1 from running. 49 | f1.mkdirs(); 50 | f1.createNewFile(); 51 | 52 | def concjob1 = createBlockingJob("concjob1", f1) 53 | concjob1.concurrentBuild = true 54 | concjob1.properties.put(ParametersDefinitionProperty.DescriptorImpl, 55 | new ParametersDefinitionProperty(new RunParameterDefinition("param1", name.getMethodName(), "ignored", null))) 56 | 57 | BuildFlow flow = new BuildFlow(Jenkins.instance, name.getMethodName()) 58 | flow.concurrentBuild = true; 59 | flow.dsl = """ build("concjob1", param1: build.number) """ 60 | flow.onCreatedFromScratch() 61 | 62 | def sfr1 = flow.scheduleBuild2(0, new Cause.UserIdCause()) 63 | def fr1 = sfr1.waitForStart() 64 | 65 | def sfr2 = flow.scheduleBuild2(0, new Cause.UserIdCause()) 66 | def fr2 = sfr2.waitForStart() 67 | 68 | def sfr3 = flow.scheduleBuild2(0, new Cause.UserIdCause()) 69 | def fr3 = sfr3.waitForStart() 70 | 71 | def sfr4 = flow.scheduleBuild2(0, new Cause.UserIdCause()) 72 | def fr4 = sfr4.waitForStart() 73 | 74 | // we have a 5 second quiet period! 75 | // This sleep does not add to the execution time of the test as we just wait for the builds to complete anyway. 76 | Thread.sleep(6000L); 77 | println("releasing jobs") 78 | f1.delete(); 79 | 80 | // wait for all the flows to finish. 81 | sfr1.get(); 82 | sfr2.get(); 83 | sfr3.get(); 84 | sfr4.get(); 85 | 86 | assertRan(flow, 4, SUCCESS); 87 | assertRan(concjob1, 4, SUCCESS); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/DSLTestCase.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 com.cloudbees.plugin.flow.UnstableBuilder 29 | import com.cloudbees.plugins.flow.FlowDSL 30 | import hudson.model.Result 31 | import org.jvnet.hudson.test.FailureBuilder 32 | import hudson.model.AbstractBuild 33 | import hudson.model.ParametersAction 34 | import com.cloudbees.plugins.flow.BuildFlow 35 | import jenkins.model.Jenkins 36 | import static hudson.model.Result.SUCCESS 37 | import java.util.concurrent.Future 38 | import java.util.concurrent.TimeUnit 39 | import static hudson.model.Result.FAILURE 40 | import java.util.logging.Logger 41 | import java.util.logging.Level 42 | import java.util.logging.Handler 43 | import java.util.logging.ConsoleHandler 44 | import com.cloudbees.plugins.flow.JobInvocation 45 | import com.cloudbees.plugins.flow.FlowDelegate 46 | import hudson.Launcher 47 | import hudson.model.BuildListener 48 | import hudson.tasks.Builder 49 | import com.cloudbees.plugin.flow.ConfigurableFailureBuilder 50 | import com.cloudbees.plugin.flow.BlockingBuilder 51 | import hudson.model.Job 52 | import org.junit.Assert 53 | import org.junit.Rule 54 | import org.junit.rules.TestName 55 | import org.jvnet.hudson.test.JenkinsRule 56 | 57 | 58 | import static hudson.model.Result.UNSTABLE 59 | 60 | abstract class DSLTestCase { 61 | @Rule 62 | public JenkinsRule jenkinsRule = new JenkinsRule() 63 | 64 | @Rule 65 | public TestName name = new TestName() 66 | 67 | def createJob = {String name -> 68 | return jenkinsRule.createFreeStyleProject(name); 69 | } 70 | 71 | def createJobs = { names -> 72 | def jobs = [] 73 | names.each { 74 | jobs.add(createJob(it)) 75 | } 76 | return jobs 77 | } 78 | 79 | def createFailJob = {String name, int failures = Integer.MAX_VALUE -> 80 | def job = createJob(name) 81 | job.getBuildersList().add(new ConfigurableFailureBuilder(failures)); 82 | return job 83 | } 84 | 85 | def createUnstableJob = {String name -> 86 | def job = createJob(name) 87 | job.getBuildersList().add(new UnstableBuilder()); 88 | job.onCreatedFromScratch() // need this to updateTransientActions 89 | return job 90 | } 91 | 92 | def createBlockingJob = {String name, File file = BlockingBuilder.DEFAULT_FILE -> 93 | def job = createJob(name) 94 | job.getBuildersList().add(new BlockingBuilder(file)); 95 | job.onCreatedFromScratch() // need this to updateTransientActions 96 | return job 97 | } 98 | 99 | def run = { script -> 100 | BuildFlow flow = new BuildFlow(Jenkins.instance, name.getMethodName()) 101 | flow.dsl = script 102 | flow.onCreatedFromScratch() // need this to updateTransientActions 103 | return flow.scheduleBuild2(0).get() 104 | } 105 | 106 | def runWithWorkspace = { script -> 107 | BuildFlow flow = new BuildFlow(Jenkins.instance, name.getMethodName()) 108 | flow.dsl = script 109 | flow.buildNeedsWorkspace = true 110 | flow.onCreatedFromScratch() // need this to updateTransientActions 111 | return flow.scheduleBuild2(0).get() 112 | } 113 | 114 | def schedule = { script -> 115 | BuildFlow flow = new BuildFlow(Jenkins.instance, name.getMethodName()) 116 | flow.dsl = script 117 | flow.onCreatedFromScratch() // need this to updateTransientActions 118 | return flow.scheduleBuild2(0) 119 | } 120 | 121 | def runWithCause = { script, cause -> 122 | BuildFlow flow = new BuildFlow(Jenkins.instance, name.getMethodName()) 123 | flow.dsl = script 124 | flow.onCreatedFromScratch() // need this to updateTransientActions 125 | return flow.scheduleBuild2(0, cause).get() 126 | } 127 | 128 | def assertSuccess = { job -> 129 | Assert.assertNotNull("job ${job.name} didn't run", job.builds.lastBuild) 130 | assert SUCCESS == job.builds.lastBuild.result 131 | return job.builds.lastBuild 132 | } 133 | 134 | def assertDidNotRun = { job -> 135 | assert 0 == job.builds.size() 136 | } 137 | 138 | def assertAllSuccess = { jobs -> 139 | jobs.each { 140 | Assert.assertNotNull("job ${it.name} didn't run", it.builds.lastBuild) 141 | assert SUCCESS == it.builds.lastBuild.result 142 | } 143 | } 144 | 145 | def assertFailure = { job -> 146 | assert FAILURE == job.builds.lastBuild.result 147 | } 148 | 149 | def assertUnstable = { job -> 150 | assert UNSTABLE == job.builds.lastBuild.result 151 | } 152 | 153 | def assertException(Class exClass, Closure closure) { 154 | def thrown = false 155 | try { 156 | closure() 157 | } catch (Exception e) { 158 | if (exClass.isAssignableFrom(e.getClass())) { 159 | thrown = true 160 | } 161 | } 162 | assert thrown 163 | } 164 | 165 | void assertHasParameter(Job job, String name, String value) { 166 | assertHasParameter(job.builds.lastBuild, name, value) 167 | } 168 | 169 | void assertHasParameter(AbstractBuild build, String name, String value) { 170 | boolean found = false 171 | build.actions.each {action -> 172 | if (action instanceof ParametersAction) 173 | if (action.getParameter(name)?.value == value) { 174 | found = true 175 | return 176 | } 177 | } 178 | Assert.assertTrue("build don't have expected parameter set " + name + "=" + value, found) 179 | } 180 | 181 | void assertRan(Job job, int times, Result result) { 182 | assert job.builds.size() == times 183 | job.builds.each { build -> 184 | assert build.result == result 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/FlowCauseTest.groovy: -------------------------------------------------------------------------------- 1 | package com.cloudbees.plugins.flow 2 | 3 | import com.gargoylesoftware.htmlunit.html.HtmlPage 4 | import hudson.model.Cause 5 | import hudson.model.CauseAction 6 | import org.junit.Rule 7 | import org.junit.Test 8 | import org.jvnet.hudson.test.JenkinsRule 9 | 10 | import static org.junit.Assert.assertTrue 11 | 12 | class FlowCauseTest { 13 | 14 | @Rule 15 | public JenkinsRule j = new JenkinsRule() 16 | 17 | protected BuildFlow createFlow(String name, String dsl) { 18 | def job = j.jenkins.createProject(BuildFlow, name) 19 | job.dsl = dsl 20 | job.save() 21 | return job 22 | } 23 | 24 | /** 25 | * Given a pipeline A --> FLOW --> B, then the build for a B build must display Flow <-- A. 26 | */ 27 | @Test 28 | void 'Flow cause not blocking the upstream causes'() { 29 | // Jobs 30 | def a = createFlow('A', 'build("B")') 31 | def b = createFlow('B', 'build("C")') 32 | def c = createFlow('C', '') 33 | 34 | // Triggers A 35 | j.assertBuildStatusSuccess(a.scheduleBuild2(0, new Cause.UserIdCause())) 36 | 37 | // Checks the builds 38 | j.assertBuildStatusSuccess(b.builds.lastBuild) 39 | 40 | // Last build in the sequence 41 | def build = c.builds.lastBuild 42 | j.assertBuildStatusSuccess(build) 43 | 44 | // Checks the cause tree 45 | def causeAction = build.actions.find { it instanceof CauseAction } as CauseAction 46 | assert causeAction != null: "A cause action must be associated with the build" 47 | assert causeAction.causes.size() == 1 48 | def cause = causeAction.causes.first() as Cause.UpstreamCause 49 | assert cause.shortDescription == 'Started by build flow B#1' 50 | def upstreamCause = cause.upstreamCauses.first() as Cause.UpstreamCause 51 | assert upstreamCause.shortDescription == 'Started by upstream project "A" build number 1' 52 | 53 | // Goes to the build status page of C 54 | HtmlPage page = j.createWebClient().goTo(c.builds.lastBuild.url) 55 | assert page.titleText == 'C #1 [Jenkins]' 56 | 57 | // Just checking the different causes are in the page (a bit primitive) 58 | def bPosition = page.body.textContent.indexOf('Started by build flow B') 59 | assertTrue(bPosition > 0) 60 | assertTrue(page.body.textContent.indexOf('Started by upstream project A build number 1') > bPosition) 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/GuardTest.groovy: -------------------------------------------------------------------------------- 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 static hudson.model.Result.SUCCESS 28 | import hudson.model.Job 29 | import static hudson.model.Result.FAILURE 30 | import org.junit.Test 31 | 32 | class GuardTest extends DSLTestCase { 33 | 34 | @Test 35 | public void testGuardPass() { 36 | def jobs = createJobs(["job1", "job2", "job3", "clean"]) 37 | def ret = run(""" 38 | guard { 39 | build("job1") 40 | build("job2") 41 | build("job3") 42 | } rescue { 43 | build("clean") 44 | } 45 | """) 46 | assertAllSuccess(jobs) 47 | assert SUCCESS == ret.result 48 | } 49 | 50 | /*@Test 51 | public void testGuardWithFail() { 52 | Job job1 = createJob("job1"); 53 | def failure = createFailJob("fails") 54 | Job job3 = createJob("job3"); 55 | Job clean = createJob("clean"); 56 | 57 | def ret = run(""" 58 | guard { 59 | build("job1") 60 | build("fails") 61 | build("job3") 62 | } rescue { 63 | build("clean") 64 | } 65 | """) 66 | assertSuccess(job1) 67 | assertFailure(failure) 68 | assertDidNotRun(job3) 69 | assertSuccess(clean) 70 | assert FAILURE == ret.result 71 | } 72 | 73 | def successBuildPar = """ 74 | def g, r 75 | guard { 76 | build("job1") 77 | par = parallel { 78 | a = build("job2") 79 | assert !a.future.done 80 | b = build("job3") 81 | assert !b.future.done 82 | } 83 | par.values().each { 84 | assert it.future.done 85 | } 86 | g = true 87 | } rescue { 88 | build("clean") 89 | r = true 90 | } 91 | assert g 92 | assert r 93 | """ 94 | 95 | @Test 96 | public void testGuardPassPar() { 97 | def jobs = createJobs(["job1", "job2", "job3", "clean"]) 98 | def ret = run(successBuildPar) 99 | assertAllSuccess(jobs) 100 | assert SUCCESS == ret.result 101 | } 102 | 103 | def successBuildParRetry = """ 104 | def a = 0 105 | def g, r 106 | guard { 107 | build("job1") 108 | par = parallel { 109 | job1 = build("job2") 110 | assert !job1.future.done 111 | job2 = build("job3") 112 | assert !job2.future.done 113 | } 114 | par.values().each { 115 | assert it.future.done 116 | } 117 | 2.times retry { 118 | build("job3") 119 | a++ 120 | } 121 | g = true 122 | } rescue { 123 | build("clean") 124 | r = true 125 | } 126 | assert a == 1 127 | """ 128 | @Test 129 | public void testGuardPassParRetry() { 130 | def jobs = createJobs(["job1", "job2", "job3", "clean"]) 131 | def ret = run(successBuildParRetry) 132 | assertAllSuccess(jobs) 133 | assert SUCCESS == ret.result 134 | }*/ 135 | } 136 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/IgnoreTest.groovy: -------------------------------------------------------------------------------- 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.model.FreeStyleProject 28 | import hudson.model.Job 29 | import hudson.model.ParametersDefinitionProperty 30 | import hudson.model.StringParameterDefinition 31 | 32 | import static hudson.model.Result.* 33 | import org.junit.Test 34 | 35 | class IgnoreTest extends DSLTestCase { 36 | 37 | @Test 38 | public void testIgnoreJobFailure() { 39 | Job willFail = createFailJob("willFail"); 40 | Job wontRun= createJob("wontRun"); 41 | def flow = run(""" 42 | ignore(FAILURE) { 43 | build("willFail") 44 | build("wontRun") 45 | } 46 | """) 47 | assertFailure(willFail) 48 | assertDidNotRun(wontRun) 49 | assert SUCCESS == flow.result 50 | } 51 | 52 | @Test 53 | public void testIgnoreJobUnstable() { 54 | Job unstable = createUnstableJob("unstable"); 55 | Job wontRun= createJob("wontRun"); 56 | def flow = run(""" 57 | ignore(UNSTABLE) { 58 | build("unstable") 59 | build("wontRun") 60 | } 61 | """) 62 | assertUnstable(unstable) 63 | assertDidNotRun(wontRun) 64 | assert SUCCESS == flow.result 65 | } 66 | 67 | @Test 68 | public void testIgnoreJobUnstableButFailureFailure() { 69 | Job willFail = createFailJob("willFail"); 70 | Job wontRun= createJob("wontRun"); 71 | def flow = run(""" 72 | ignore(UNSTABLE) { 73 | build("willFail") 74 | build("wontRun") 75 | } 76 | """) 77 | assertFailure(willFail) 78 | assertDidNotRun(wontRun) 79 | assert FAILURE == flow.result 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/ParallelTest.groovy: -------------------------------------------------------------------------------- 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 static hudson.model.Result.SUCCESS 28 | import hudson.model.Result 29 | import static hudson.model.Result.FAILURE 30 | import org.junit.Test 31 | 32 | class ParallelTest extends DSLTestCase { 33 | 34 | @Test 35 | public void testParallel() { 36 | def jobs = createJobs(["job1", "job2", "job3", "job4"]) 37 | def flow = run(""" 38 | parallel( 39 | { build("job1") }, 40 | { build("job2") }, 41 | { build("job3") } 42 | ) 43 | build("job4") 44 | """) 45 | assertAllSuccess(jobs) 46 | assert SUCCESS == flow.result 47 | println flow.jobsGraph.edgeSet() 48 | } 49 | 50 | @Test 51 | public void testFailOnParallelFailed() { 52 | createJobs(["job1", "job2"]) 53 | createFailJob("willFail") 54 | def job4 = createJob("job4") 55 | def flow = run(""" 56 | parallel ( 57 | { build("job1") }, 58 | { build("job2") }, 59 | { build("willfail") } 60 | ) 61 | build("job4") 62 | """) 63 | assertDidNotRun(job4) 64 | assert FAILURE == flow.result 65 | println flow.jobsGraph.edgeSet() 66 | } 67 | 68 | 69 | @Test 70 | public void testFailOnJobSequenceFailed() { 71 | def jobs = createJobs(["job1", "job2", "job3"]) 72 | createFailJob("willFail") 73 | def job4 = createJob("job4") 74 | def flow = run(""" 75 | parallel ( 76 | { 77 | build("job1") 78 | build("job2") 79 | }, 80 | { 81 | build("job3") 82 | build("willfail") 83 | } 84 | ) 85 | build("job4") 86 | """) 87 | assertDidNotRun(job4) 88 | assertAllSuccess(jobs) 89 | assert FAILURE == flow.result 90 | println flow.jobsGraph.edgeSet() 91 | } 92 | 93 | @Test 94 | public void testGetParallelResults() { 95 | def jobs = createJobs(["job1", "job2", "job3"]) 96 | def job4 = createJob("job4") 97 | def flow = run(""" 98 | join = parallel ( 99 | { build("job1") }, 100 | { build("job2") }, 101 | { build("job3") } 102 | ) 103 | build("job4", 104 | r1: join[0].result.name, 105 | r2: join[1].lastBuild.parent.name) 106 | """) 107 | assertAllSuccess(jobs) 108 | assertSuccess(job4) 109 | assertHasParameter(job4, "r1", "SUCCESS") 110 | assertHasParameter(job4, "r2", "job2") 111 | assert SUCCESS == flow.result 112 | println flow.jobsGraph.edgeSet() 113 | } 114 | 115 | @Test 116 | public void testParallelMap() { 117 | def jobs = createJobs(["job1", "job2", "job3"]) 118 | def job4 = createJob("job4") 119 | def flow = run(""" 120 | join = parallel ([ 121 | first: { build("job1") }, 122 | second: { build("job2") }, 123 | third: { build("job3") } 124 | ]) 125 | build("job4", 126 | r1: join.first.result.name, 127 | r2: join.second.lastBuild.parent.name) 128 | """) 129 | assertAllSuccess(jobs) 130 | assertSuccess(job4) 131 | assertHasParameter(job4, "r1", "SUCCESS") 132 | assertHasParameter(job4, "r2", "job2") 133 | assert SUCCESS == flow.result 134 | println flow.jobsGraph.edgeSet() 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/PipelineTest.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, Tom Fenelly, Craig Rodrigues 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 | package com.cloudbees.plugins.flow 25 | 26 | import com.cloudbees.plugin.flow.ConfigurableFailureBuilder 27 | 28 | import hudson.model.FreeStyleBuild 29 | import hudson.model.FreeStyleProject 30 | import hudson.model.ParametersAction 31 | import hudson.model.ParametersDefinitionProperty 32 | import hudson.model.Job 33 | import hudson.model.Result 34 | import hudson.model.Run 35 | 36 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition 37 | import org.jenkinsci.plugins.workflow.job.WorkflowJob 38 | import org.jenkinsci.plugins.workflow.job.WorkflowRun 39 | import org.junit.Assert 40 | import org.junit.Rule 41 | import org.junit.Test 42 | import org.jvnet.hudson.test.JenkinsRule 43 | 44 | import java.io.IOException 45 | import java.util.List 46 | 47 | public class PipelineTest extends DSLTestCase { 48 | 49 | @Test 50 | public void test_SuccessfulPipeline() throws Exception { 51 | // From a pipeline, build a job that succeeds 52 | def proj1 = jenkinsRule.createFreeStyleProject("proj1") 53 | def build_flow1 = createFlow("build_flow1", 54 | """build 'proj1'""") 55 | def pipeline_1 = createPipeline("pipeline_proj1", 56 | """build job: 'build_flow1'""") 57 | def pipeline_1_result = pipeline_1.scheduleBuild2(0).get() 58 | jenkinsRule.assertBuildStatusSuccess(pipeline_1_result) 59 | jenkinsRule.assertBuildStatusSuccess(proj1.getLastBuild()) 60 | } 61 | 62 | @Test 63 | public void test_FailedPipeline() throws Exception { 64 | // From a pipeline, build a job that fails 65 | def proj2 = jenkinsRule.createFreeStyleProject("proj2") 66 | proj2.getBuildersList().add(new ConfigurableFailureBuilder(1)) 67 | def build_flow2 = createFlow("build_flow2", 68 | """build 'proj2'""") 69 | def pipeline_2 = createPipeline("pipeline_proj2", 70 | """build job: 'build_flow2'""") 71 | def pipeline_2_result = pipeline_2.scheduleBuild2(0).get() 72 | jenkinsRule.assertBuildStatus(Result.FAILURE, pipeline_2_result) 73 | jenkinsRule.assertBuildStatus(Result.FAILURE, proj2.getLastBuild()) 74 | } 75 | 76 | private def createPipeline(String name, String script) throws IOException { 77 | def job = jenkinsRule.jenkins.createProject(WorkflowJob.class, name) 78 | job.setDefinition(new CpsFlowDefinition("node {" + script + "}", true)) 79 | return job 80 | } 81 | 82 | private BuildFlow createFlow(String name, String dsl) { 83 | def job = jenkinsRule.jenkins.createProject(BuildFlow, name) 84 | job.dsl = dsl 85 | return job 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/RetryTest.groovy: -------------------------------------------------------------------------------- 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.model.Result 28 | import static hudson.model.Result.SUCCESS 29 | import static hudson.model.Result.UNSTABLE 30 | import static hudson.model.Result.FAILURE 31 | import hudson.model.Job 32 | import org.junit.Test 33 | 34 | class RetryTest extends DSLTestCase { 35 | 36 | @Test 37 | public void testNoRetry() { 38 | Job job1 = createJob("job1") 39 | def flow = run(""" 40 | retry(3) { 41 | build("job1") 42 | } 43 | """) 44 | assertRan(job1, 1, SUCCESS) 45 | assert SUCCESS == flow.result 46 | } 47 | 48 | @Test 49 | public void testRetry() { 50 | def job1 = createFailJob("willFail") 51 | def flow = run(""" 52 | retry(3) { 53 | build("willFail") 54 | } 55 | """) 56 | assertRan(job1, 3, FAILURE) 57 | assert FAILURE == flow.result 58 | } 59 | 60 | @Test 61 | public void testRetryThenSuccess() { 62 | testRetryThenSuccess(""" 63 | retry(3) { 64 | build("willFail2times") 65 | } 66 | """) 67 | } 68 | 69 | @Test 70 | public void testNoQuotesNotation() { 71 | testRetryThenSuccess(""" 72 | retry 3, { 73 | build("willFail2times") 74 | } 75 | """) 76 | } 77 | 78 | /* 79 | @Test 80 | public void testNTimesNotation() { 81 | testRetryThenSuccess(""" 82 | 3.times retry { 83 | build("willFail2times") 84 | } 85 | """) 86 | } 87 | */ 88 | 89 | public void testRetryThenSuccess(String script) { 90 | def job1 = createFailJob("willFail2times", 2) 91 | def flow = run(script) 92 | assert job1.builds.size() == 3 93 | assert job1.builds[2].result == FAILURE 94 | assert job1.builds[1].result == FAILURE 95 | assert job1.builds[0].result == SUCCESS 96 | 97 | assert SUCCESS == flow.result 98 | println flow.jobsGraph.edgeSet() 99 | } 100 | 101 | @Test 102 | public void testRetryGuard() { 103 | def fail = createFailJob("willFail") 104 | def rescue = createJob("rescue") 105 | def flow = run(""" 106 | retry(3) { 107 | guard { 108 | build("willFail") 109 | } rescue { 110 | build("rescue") 111 | } 112 | } 113 | """) 114 | 115 | assertRan(fail, 3, FAILURE) 116 | assertRan(rescue, 3, SUCCESS) 117 | assert FAILURE == flow.result 118 | println flow.jobsGraph.edgeSet() 119 | } 120 | 121 | @Test 122 | public void testNoRetryUnstable() { 123 | Job job1 = createUnstableJob("job1") 124 | def flow = run(""" 125 | retry(3, 'UNSTABLE') { 126 | build("job1") 127 | } 128 | """) 129 | assertRan(job1, 1, UNSTABLE) 130 | assert UNSTABLE == flow.result 131 | } 132 | 133 | 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/test/groovy/com/cloudbees/plugins/flow/UpstreamTest.groovy: -------------------------------------------------------------------------------- 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.model.Cause 28 | 29 | import static hudson.model.Result.SUCCESS 30 | import hudson.model.Job 31 | import org.junit.Test 32 | 33 | class UpstreamTest extends DSLTestCase { 34 | 35 | @Test 36 | public void testUpstream() { 37 | 38 | Job job1 = createJob("job1") 39 | def root = jenkinsRule.createFreeStyleProject("root").createExecutable() 40 | def cause = new Cause.UpstreamCause(root) 41 | def run = runWithCause(""" 42 | build("job1", 43 | param1: upstream.parent.name, 44 | param2: upstream.number) 45 | """, cause) 46 | def build = assertSuccess(job1) 47 | assertHasParameter(build, "param1", "root") 48 | assertHasParameter(build, "param2", "1") 49 | 50 | assert SUCCESS == run.result 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/cloudbees/plugin/flow/BlockingBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2013, Cisco Systems, Inc., a California corporation 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 | package com.cloudbees.plugin.flow; 25 | 26 | import hudson.Extension; 27 | import hudson.Launcher; 28 | import hudson.model.AbstractBuild; 29 | import hudson.model.BuildListener; 30 | import hudson.model.Descriptor; 31 | import hudson.tasks.Builder; 32 | import net.sf.json.JSONObject; 33 | import org.kohsuke.stapler.StaplerRequest; 34 | 35 | import java.io.File; 36 | 37 | import static hudson.model.Result.ABORTED; 38 | import static hudson.model.Result.FAILURE; 39 | import static hudson.model.Result.SUCCESS; 40 | 41 | /** 42 | * A Builder that will block until a file exists. 43 | * 44 | * @author James Nord 45 | */ 46 | public class BlockingBuilder extends Builder { 47 | 48 | public final static File DEFAULT_FILE = new File("target", "build_block.txt"); 49 | 50 | public final File file; 51 | 52 | BlockingBuilder(File file) { 53 | this.file = file; 54 | } 55 | 56 | 57 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) { 58 | System.out.println("Blocking Builder in build " + build.getFullDisplayName() + "starting"); 59 | try { 60 | System.out.println("Blocking Builder in build " + build.getFullDisplayName() + "waiting"); 61 | while (file.exists()) { 62 | Thread.sleep(10L); 63 | } 64 | build.setResult(SUCCESS); 65 | } catch (InterruptedException ex) { 66 | build.setResult(ABORTED); 67 | } 68 | System.out.println("Blocking Builder in build " + build.getFullDisplayName() + " completing " + build.getResult()); 69 | return true; 70 | } 71 | 72 | @Extension 73 | public static final class DescriptorImpl extends Descriptor { 74 | public String getDisplayName() { 75 | return "Blocking builder"; 76 | } 77 | 78 | public BlockingBuilder newInstance(StaplerRequest req, JSONObject data) { 79 | return new BlockingBuilder(DEFAULT_FILE); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/cloudbees/plugin/flow/ConfigurableFailureBuilder.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.plugin.flow; 26 | 27 | import hudson.Extension; 28 | import hudson.Launcher; 29 | import hudson.model.AbstractBuild; 30 | import hudson.model.BuildListener; 31 | import hudson.model.Descriptor; 32 | import hudson.model.Result; 33 | import hudson.tasks.Builder; 34 | import net.sf.json.JSONObject; 35 | import org.kohsuke.stapler.StaplerRequest; 36 | 37 | import static hudson.model.Result.FAILURE; 38 | import static hudson.model.Result.SUCCESS; 39 | 40 | /** 41 | * @author Nicolas De Loof 42 | */ 43 | public class ConfigurableFailureBuilder extends Builder { 44 | 45 | private int failures; 46 | 47 | ConfigurableFailureBuilder(int failures) { 48 | this.failures = failures; 49 | } 50 | 51 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) { 52 | build.setResult(failures-- > 0 ? FAILURE : SUCCESS); 53 | return true; 54 | } 55 | 56 | @Extension 57 | public static final class DescriptorImpl extends Descriptor { 58 | public String getDisplayName() { 59 | return "Configurable failure"; 60 | } 61 | public ConfigurableFailureBuilder newInstance(StaplerRequest req, JSONObject data) { 62 | return new ConfigurableFailureBuilder(0); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/cloudbees/plugin/flow/TestDSLExtension.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.plugin.flow; 26 | 27 | import com.cloudbees.plugins.flow.BuildFlowDSLExtension; 28 | import com.cloudbees.plugins.flow.FlowDelegate; 29 | import hudson.Extension; 30 | 31 | import java.util.Map; 32 | import java.util.TreeMap; 33 | 34 | /** 35 | * @author Kohsuke Kawaguchi 36 | */ 37 | @Extension 38 | public class TestDSLExtension extends BuildFlowDSLExtension { 39 | @Override 40 | public Object createExtension(String extensionName, FlowDelegate dsl) { 41 | Map m = new TreeMap(); 42 | m.put("dsl",dsl); 43 | m.put("name",extensionName); 44 | return m; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/cloudbees/plugin/flow/UnstableBuilder.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.plugin.flow; 26 | 27 | import hudson.Extension; 28 | import hudson.Launcher; 29 | import hudson.model.AbstractBuild; 30 | import hudson.model.BuildListener; 31 | import hudson.model.Descriptor; 32 | import hudson.tasks.Builder; 33 | import net.sf.json.JSONObject; 34 | import org.kohsuke.stapler.StaplerRequest; 35 | 36 | import static hudson.model.Result.FAILURE; 37 | import static hudson.model.Result.SUCCESS; 38 | import static hudson.model.Result.UNSTABLE; 39 | 40 | /** 41 | * @author Nicolas De Loof 42 | */ 43 | public class UnstableBuilder extends Builder { 44 | 45 | public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) { 46 | build.setResult(UNSTABLE); 47 | return true; 48 | } 49 | 50 | @Extension 51 | public static final class DescriptorImpl extends Descriptor { 52 | public String getDisplayName() { 53 | return "Set Unstable"; 54 | } 55 | } 56 | } 57 | --------------------------------------------------------------------------------