Besides hacking or coding Workflow Engine scripts directly inside a little bit „stupid“ editor (in reality it looks like a HTML input field) Jenkins provides another possibility: Loading scripts from SCM.
If you choose in Job configuration page „Groovy CPS DSL from SCM“ instead „Groovy CPS DSL“ you will get additional configuration options (see figure below)
As many other projects we choose Git as „SCM technology“. Additionally we are using a quit simple Maven project hosted on Bitbucket for our example but this doesn’t matter right now (it contains the parent.pom which we will use for more advanced examples later). Right now we need only one file from this repository (it looked too crazy for me to create a separate repository for one file ;-).
The interesting part you find close to bottom line: The path to Workflow script "flow1.groovy"
. For our "Hello World"
example it will looks like:
echo( "Hello World" );
After saving configuration and starting a „build“ you should get a similar output in the Job console:
Essentially Workflow scripts are coded in a DSL based on Groovy (a more or less well known scripting language related to Java [01]). Therefore you can use any valid Groovy „construct“ for coding your workflow. The DSL is implemented in or by a set of Jenkins plugins providing additional functionality, e.g. the „step echo“ we used to write „Hello World“ to console output. You can read more about in the Jenkins Workflow Engine Tutorial [02].
Storing Workflow scripts in SCM has the advantage that they are handled and versioned as any other source code. And if we store them together with all other sources for this project than we don’t need any additional information to find the right script.
But there is one disadvantage: If you have to develop the Workflow script itself you are faced with a long round trip cycle (assuming you are using Git):
1. change Workflow file (e.g. flow1.groovy)
2. commit it to local repository
3. push it to remote repository
4. start/build Jenkins job and watch what happens in job console
Doesn’t look nice …
To avoid the round trip over the source code repository we could try to apply the changes to the local copy of the Workflow script (usually located in ${JENKINS_HOME}/jobs/${PROJECT_NAME}/workspace or workspace@script). But this doesn’t work because the changes will be overwritten during next synchronization between remote and local Git repository. And no way to switch this feature off.
From a bird view it seems that dividing synchronization and running „real“ build process could do the trick … Other project or job types, e.g. Freestyle or Maven, are offering this possibility. You are able to configure SCM (where to get sources from) and the „build“ independently.
Because I’m not a „Jenkins hacker“ (can’t extend/change plugins directly) I had to look for a workaround. It took me some time but finally I decided to spread the functionality over two Workflow scripts.
First one is used for bootstrapping the build process and should be more or less reusable for other projects too (I hope ;-). Instead using SCM functionality provided by Jenkins Workflow as explained before, I code the bootstrapping part directly on job configuration page as „Groovy CPS DSL“.
For better readability I repeat the script:
def flow node{ echo "bootstrap function begin" git url: 'https://bitbucket.org/mindapproach/demo-parentpom.git' flow = load "flow.groovy" echo "bootstrap function end" } flow.build()
Let’s talk a little bit why this script looks a bit more complex than expected.
First „workflow steps“ like „git“ and others need some kind of „Launcher context“. Therefore we have to wrap them into a „node“ which providing necessary information. Btw, if not you will be faced with following or similar error message
„Git Workflow step“ is used to create and synchronise local with remote source repository. Nothing special.
Real magic is done by „load Workflow step“ which loads (surprise, surprise 😉 ) a file from workspace and runs it as Groovy source code. The file can either contain statements at top level or can define functions and return „this“. Such a function would be the right place for our real „build code“ (see below).
Additionally, using such a function give us the possibility to divide between loading and running the function. This may be helpful if we later want use different contexts during building our project (e.g. for integration tests). To be prepared I call the „build()“ function outside of the scope of the original node.
Finally let’s have a look at „flow.groovy“ – the „real“ build script maintained together with our other project sources:
// flow.groovy: def build(){ echo "build() function begin" node{ echo "Hallo from flow.groovy" } echo "build() function end" } return this;
The script defines a function called „build()“ which acquire an own launcher context (a workspace) by using a „node step“. Later we will add some more useful code than simply echo-ing some text.
After first check out or synchronization of source code you may comment out the git step. Now you can edit Workflow script „flow.groovy“ and build your project as often as you want.
Summary:
Even it works but it is a (dirty) workaround with some disadvantages:
the build code inside „flow.groovy“ needs own „node“ statement and
have to contain a build() function
Right now, the build in its narrow sense is bound to the node there the sources are checked out
May be I missed something (any feedback will be welcome) – from my current point of view and knowledge I would vote for a style like aforementioned project types „Freestyle“ or „Maven“ provide.
Appendix:
Unfortunately, loading a script from a script checked out via „Groovy CPS DSL from SCM“ doesn’t work …
If you replace in figure 1 the script „flow1.groovy“ with „bootstrap.groovy“ (see below), which in turn calls another script named „flow.groovy“, and „build“ the project – it will fail because flow.groovy can’t be found.
[UPDATE]
// bootstrap.groovy def flow node{ echo "bootstrap function begin" echo "pwd: " + pwd() flow = load "flow.groovy" echo "bootstrap function end" } flow.build()
[/UPDATE]
Reason: Workflow step „load“ expects a directory named „workspace“ which it use as root for searching the script. But „Groovy CPS DSL from SCM“ creates a directory named „workspace@script“ …
Following snipped I took from console output from my local test environment:
Cloning repository https://bitbucket.org/mindapproach/demo-parentpom.git; git init /var/jenkins_home/jobs/Test03/workspace@script # timeout=10 [...] java.io.FileNotFoundException: /var/jenkins_home/jobs/Test03/workspace/flow.groovy (No such file or directory)