Here at work, we’re working on a migration project, from Jenkins (which we’ve been using as a scheduler) to JAMS Scheduler. In Jenkins we have a lot of Groovy scripts, and we have them in source control. So, to make the migration as effortless as possible, we wanted to use them “as-is”, right out of source control.
The solution I found was:
- On the JAMS agent, install the subversion command line client
- Also on the JAMS agent, install groovy
- Create a job that gets (“checks out”) the latest scripts every evening from source control in a specific directory; let’s call it c:\jobs
- Create a JAMS Execution Method called Groovy (see below)
- Create the Jenkins jobs in JAMS, one by one. In the source box, only write the full path of the groovy script, e.g. c:\jobs\TransferOrders.groovy
#4 is where the magic happens. The execution method is defined as a Powershell method. In the template, there’s code that (suprise) calls groovy. The powershell code is the following (see if you can spot a couple of tricks):
#
# Source: DotJim blog (https://dandraka.com)
# Jim Andrakakis, December 2018
#
Import-Module JAMS
# the job's source is supposed to contain ONLY
# the full path to the groovy script, without quotes
$groovy = "C:\app\groovy-2.5.4\bin\groovy.bat"
$groovyScript="<<JAMS.Current.Source>>"
Write-Host "[JAMS-GROOVY] Running script $groovyScript via $groovy"
if ((Test-Path -Path $groovy) -ne $true)
{
Write-Error "[JAMS-GROOVY] Groovy executable $groovy not found, is Groovy installed?"
}
if ((Test-Path -Path $groovyScript) -ne $true)
{
Write-Error "[JAMS-GROOVY] Source file $groovyScript not found"
}
$currentJob = Get-JAMSEntry {JAMS.JAMSEntry}
$currentJobParams = $currentJob.Parameters
$currentJobParamNames = $currentJobParams.Keys
foreach($n in $currentJobParamNames)
{
[string]$v = $currentJobParams[$n].Value
# look for replacement tokens
# in the form of <<ParamName>>
foreach($r in $currentJobParamNames)
{
if ($v.Contains("<<$r>>"))
{
[string]$replVal = $currentJobParams[$r].Value
$v = $v.Replace("<<$r>>", $replVal)
}
}
Write-Host "[JAMS-GROOVY] Setting parameter $n = $v"
[Environment]::SetEnvironmentVariable($n, $v, "Process")
}
# execute the script in groovy
& $groovy $groovyScript
Write-Host "[JAMS-GROOVY] script finished"
Two tricks to note here:
- Almost all our groovy scripts have parameters; Jenkins inserts the parameters as environment variables so the scripts can do:
myVar = System.getenv()['myVar']
The first powershell loop does exactly that; it maps all the job’s parameters, defined or inherited, as environment variables, so the scripts can continue to work happily, no change needed.
- The second trick is actually an enhancement. As the scripts get promoted though our environments (development > test > integration test > production) some parts of the parameters change –but not all of them.
For example, let’s say there’s a parameter for an inputDirectory.
In the development server, it has the value c:\documents\dev\input. In test, it’s c:\documents\test\input, in integration test it’s c:\documents\intg\input and in production c:\documents\prod\input.
What we can do now is have a folder-level parameter, defined on the JAMS folder where our job definitions are –which is not transferred from
environment to environment. And we can have job-defined parameters that, using the familiar JAMS <<param>> notation, get their values substituted.
So, for example, let’s say I define a folder parameter named “SERVERLEVEL”, which will have the value of “dev” in development, “test” in test etc. In the job, I define another parameter called inputDirectory. This will have the value c:\documents\<<SERVERLEVEL>>\input.
Et voilà! Now we can promote the jobs from environment to environment, completely unchanged. In Jenkins we couldn’t do that; we had to define different values for parameters in dev, in test etc.
Here’s the export xml of the execution method:
<?xml version="1.0" encoding="utf-8"?>
<JAMSObjects>
<method
name="Groovy"
type="Routine">
<description><![CDATA[Run a pre-fetched groovy script. The job's source should contain the full path to the groovy script.
Note: in the "Bad regex pattern", the execution methon looks for "Caught:" to try to undertand whether
groovy encountered an exception or not. Here's an example of the groovy output of a script where
an unhandled exception occured:
Hello, world!
Caught: java.lang.NullPointerException: Cannot invoke method test() on null object
java.lang.NullPointerException: Cannot invoke method test() on null object
at test1.run(test1.groovy:4)]]></description>
<template><![CDATA[Import-Module JAMS
# the job's source is supposed to contain ONLY
# the full path to the groovy script, without quotes
$groovy = "C:\app\groovy-2.5.4\bin\groovy.bat"
$groovyScript="<<JAMS.Current.Source>>"
Write-Host "[JAMS-GROOVY] Running script $groovyScript via $groovy"
if ((Test-Path -Path $groovy) -ne $true)
{
Write-Error "[JAMS-GROOVY] Groovy executable $groovy not found, is Groovy installed?"
}
if ((Test-Path -Path $groovyScript) -ne $true)
{
Write-Error "[JAMS-GROOVY] Source file $groovyScript not found"
}
$currentJob = Get-JAMSEntry {JAMS.JAMSEntry}
$currentJobParams = $currentJob.Parameters
$currentJobParamNames = $currentJobParams.Keys
foreach($n in $currentJobParamNames)
{
[string]$v = $currentJobParams[$n].Value
# look for replacement tokens
# in the form of <<ParamName>>
foreach($r in $currentJobParamNames)
{
if ($v.Contains("<<$r>>"))
{
[string]$replVal = $currentJobParams[$r].Value
$v = $v.Replace("<<$r>>", $replVal)
}
}
Write-Host "[JAMS-GROOVY] Setting parameter $n = $v"
[Environment]::SetEnvironmentVariable($n, $v, "Process")
}
# execute the script in groovy
& $groovy $groovyScript
Write-Host "[JAMS-GROOVY] script finished"]]></template>
<properties>
<property
name="HostAssemblyName"
typename="System.String"
value="JAMSPSHost" />
<property
name="HostClassName"
typename="System.String"
value="MVPSI.JAMS.Host.PowerShell.JAMSPSHost" />
<property
name="StartAssemblyName"
typename="System.String"
value="" />
<property
name="StartClassName"
typename="System.String"
value="" />
<property
name="EditAssemblyName"
typename="System.String"
value="" />
<property
name="EditClassName"
typename="System.String"
value="" />
<property
name="ViewAssemblyName"
typename="System.String"
value="" />
<property
name="ViewClassName"
typename="System.String"
value="" />
<property
name="BadPattern"
typename="System.String"
value="^Caught\:" />
<property
name="ExitCodeHandling"
typename="MVPSI.JAMS.ExitCodeHandling"
value="ZeroIsGood" />
<property
name="GoodPattern"
typename="System.String"
value="" />
<property
name="SpecificInformational"
typename="System.String"
value="" />
<property
name="SpecificValues"
typename="System.String"
value="" />
<property
name="SpecificWarning"
typename="System.String"
value="" />
<property
name="Force32Bit"
typename="System.Boolean"
value="false" />
<property
name="ForceV2"
typename="System.Boolean"
value="false" />
<property
name="HostLocally"
typename="System.Boolean"
value="false" />
<property
name="Interactive"
typename="System.Boolean"
value="false" />
<property
name="NoBOM"
typename="System.Boolean"
value="false" />
<property
name="SourceFormat"
typename="MVPSI.JAMS.SourceFormat"
value="Text" />
<property
name="EditAfterStart"
typename="System.Boolean"
value="false" />
<property
name="EditSource"
typename="System.Boolean"
value="false" />
<property
name="Extension"
typename="System.String"
value="ps1" />
<property
name="JobModule"
typename="System.String"
value="" />
<property
name="SnapshotSource"
typename="System.Boolean"
value="false" />
<property
name="Redirect"
typename="MVPSI.JAMS.Redirect"
value="All" />
<property
name="HostSubDirectory"
typename="System.String"
value="" />
<property
name="HostExecutable"
typename="System.String"
value="JAMSHost.exe" />
</properties>
</method>
</JAMSObjects>