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>