Using loaded script in Jenkinsfile - jenkins

I had a script that was being executed as a jenkins pipeline and it was working fine. I wanted to reuse it for multiple environments so I moved all the code to functions and load them from multiple files.
Library file - Healthcheck:
#!groovy
#NonCPS
def check(type) {
stage "prepare"
echo "TEST1"
props = readProperties file:'build.properties'
echo "TEST2"
stage "queues"
checkQueues()
}
#NonCPS
def checkQueues() {
txt = "http://ltxl0207.sgdcelab.sabre.com:8161/api/jolokia/read/org.apache.activemq:brokerName=localhost,destinationName=!/tss!/trip_source_updates,destinationType=Queue,type=Broker/QueueSize".toURL().getText(requestProperties: [Authorization: "Basic " + "admin:admin".getBytes().encodeBase64().toString()])
json = new groovy.json.JsonSlurper().parseText(txt)
echo "Got response: " + txt
}
return this;
File that uses it - Healthcheck-dev:
#!groovy
node {
checkout scm
healthcheck = load 'Healthcheck'
healthcheck.check('DEV')
}
And the trouble is that the script doesn't get pass readProperties and the prepare stage it just stops there, ignoring the queues stage:
[Pipeline] load
[Pipeline] { (Healthcheck)
[Pipeline] }
[Pipeline] // load
[Pipeline] stage (prepare)
Entering stage prepare
Proceeding
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
What I'm doing wrong? When I move the code to single file it works correctly.

Have you tried to execute your pipeline with your 2 files ("Healthcheck" & "Healtcheck-dev") being in the same repository ? Because when you load a script from another SCM repo like you seem to be doing, Jenkins actually creates another directory #script or such for your loaded script.
You might need to do something like this to load your general script from the correct workspace :
node {
checkout scm
dir("${projectWorkspace}#script") {
healthcheck = load 'Healthcheck'
healthcheck.check('DEV')
}
}
with ${projectWorkspace} being the original workspace for your build.

Related

Can I assign a node dynamically within an iterative loop in Jenkins?

I am building a Jenkins pipeline for a scenario where I'll have to use specific Jenkins agents inside remote data centers to deploy my code to those data centers. This is due to firewall restrictions on some ports, specifically WinRM is blocked between some of our global data centers.
Our deploys are written so that a single deploy stage can deploy to any number of environments, specified by the user's passed-in parameters. The stage loops through the environments and calls a generic deploy script for each one.
I know how to specify an agent by its label or other closure in a stage's definition:
stage ('a stage') {
agent { label 'some agent label' }
steps { ...
but in this case, i am solving for deploying to multiple environments in one deploy stage, each of which will require its own agent.
I can, of course, specify a unique stage for each env, and use a when clause to run it when appropriate, but that's messy.
What I'd like to do is tell the pipeline what agent(s) to use for the deploy stage inside inside the deploy stage, and be able to use multiple agents within that single stage, determined dynamically based on the parameters of the run.
I'd originally found this answer on SO, which gave me the idea of acquiring a node inside the stage, and not with the agent declaration. It doesn't show the acquisition inside a script block, but I'd initially read it that way, and that gave me the idea to try acquiring the node inside a script. And once you're there, it's a small leap to try doing it in a loop.
To prove it, I print some local environment variables from the agent to prove that we're switching agents, inside the stage, inside the loop. I'm also passing a file to each agent to prove that I can pass the files through the firewall.
Note that to even connect to the agent behind the firewall, we had to open the port that is defined in the Jenkins global security config, inbound to the agent from the controller (aka master), and https (443) outbound to the controller. The inbound port is configured to be static.
pipeline {
agent none
stages {
stage ('init') {
agent any
steps {
writeFile file: 'tester', text: 'i am a test file'
stash includes: 'tester', name: 'tester'
}
}
stage ('get agents') {
steps {
script {
['Agent1', 'Agent2'].each { agent ->
node (agent) {
echo "I am agent `${NODE_NAME}`\nMy labels are `${NODE_LABELS}`"
unstash 'tester'
echo "the content of the file is `${readFile 'tester'}`"
}
}
}
}
}
}
}
Which outputs:
Started by user Maximilian Cascone Admin
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] Start of Pipeline
[Pipeline] stage
[Pipeline] { (init)
[Pipeline] node
Running on Agent1 in /mnt/data/jenkins/workspace/Sandbox/mcascone/dynamic-agents
[Pipeline] {
[Pipeline] writeFile
[Pipeline] stash
Stashed 1 file(s)
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] { (get agents)
[Pipeline] script
[Pipeline] {
[Pipeline] node
Running on Agent2 in D:\workspace\Sandbox\mcascone\dynamic-agents
[Pipeline] {
[Pipeline] echo
I am agent `Agent2`
My labels are `Cider Redgate Windows Worker02 ant chef npm relativity wix`
[Pipeline] unstash
[Pipeline] readFile
[Pipeline] echo
the content of the file is `i am a test file`
[Pipeline] }
[Pipeline] // node
[Pipeline] node
Running on Agent1 in C:\jenkins\workspace\Sandbox\mcascone\dynamic-agents
[Pipeline] {
[Pipeline] echo
I am agent `Agent1`
My labels are `Itar Agent1`
[Pipeline] unstash
[Pipeline] readFile
[Pipeline] echo
the content of the file is `i am a test file`
[Pipeline] }
[Pipeline] // node
[Pipeline] }
[Pipeline] // script
[Pipeline] }
[Pipeline] // stage
[Pipeline] End of Pipeline
Finished: SUCCESS
So I've proven that I can get multiple agents dynamically within a single stage. Next step would be elevating this into a shared step, so it can be called without the script block and make the pipeline nice and neat. But as a POC, this is a great achievement. I don't believe I've seen this elsewhere.
The answer might not fit all needs you have, but dynamic generation of stage and on that basis you can assign / execute the generate stages with following way.
def agents = ['master', 'agent1', 'agent2']
def generateStage(nodeLabel) {
return {
stage("Runs on ${nodeLabel}") {
node(nodeLabel) {
echo "Running on ${nodeLabel}"
}
}
}
}
def parallelStagesMap = agents.collectEntries {
["${it}" : generateStage(it)]
}
pipeline {
agent none
stages {
stage('non-parallel stage') {
steps {
echo 'This stage will be executed first.'
}
}
stage('parallel stage') {
steps {
script {
parallel parallelStagesMap
}
}
}
}
}
Furthermore you can use collectEntries, out of the function box parallelStagesMap, in this way you can use each collect entry for different stage and can dynamically assign nodes in the stage, and in function generateStage you need to do modification as per your requirement.
If you wanted to execute these stages sequentially, then remove parallel from script.generateStage contains return which is imporant without that, pipeline will not work as expected.

Create new Jenkins jobs using Pipeline Job and Groovy script

I have Jenkins pipeline Job with parameters (name, group, taskNumber)
I need to write pipeline script which will call groovy script (this one?: https://github.com/peterjenkins1/jenkins-scripts/blob/master/add-job.groovy)
I want to create new job (with name name_group_taskNamber) every times when I build main Pipeline Job.
I don't understand:
Where do I need to put may groovy script ?
How does Pipeline script should look like? :
node{
stage('Build'){
def pipeline = load "CreateJob.groovy"
pipeline.run()
}
}
You can use and configure a shared library like here (a git repo): https://github.com/lvthillo/shared-library . You need to configure this in your Jenkins global configuration.
It contains a folder vars/. Here you can manage pipelines and groovy scripts like my slackNotifier.groovy. The script is just a groovy script to print the build result in Slack.
In the jenkins pipeline job we will import our shared library:
#Library('name-of-shared-pipeline-library')_
mavenPipeline {
//define parameters
}
In the case above also the pipeline is in the shared library but this isn't necessary.
You can just write your pipeline in the job itself and call only the function from the pipeline like this:
This is the script in the shared library:
// vars/sayHello.groovy
def call(String name = 'human') {
echo "Hello, ${name}."
}
And in your pipeline:
final Lib= library('my-shared-library')
...
stage('stage name'){
echo "output"
Lib.sayHello.groovy('Peter')
}
...
EDIT:
In new declarative pipelines you can use:
pipeline {
agent { node { label 'xxx' } }
options {
buildDiscarder(logRotator(numToKeepStr: '3', artifactNumToKeepStr: '1'))
}
stages {
stage('test') {
steps {
sh 'echo "execute say hello script:"'
sayHello("Peter")
}
}
}
post {
always {
cleanWs()
}
}
}
def sayHello(String name = 'human') {
echo "Hello, ${name}."
}
output:
[test] Running shell script
+ echo 'execute say hello script:'
execute say hello script:
[Pipeline] echo
Hello, Peter.
[Pipeline] }
[Pipeline] // stage
We do it by using the https://wiki.jenkins.io/display/JENKINS/Jobcopy+Builder+plugin, try build another step in pipeline script and pass the parms which are to be considered

#NonCPS stops after first build step

In a #NonCPS annotated function, only code up to the very first jenkins build step is executed. Does anyone have the same problem? Am I missing something? I am using Jenkins LTS... just sayin' (2.73.2).
This is my code:
#NonCPS
def hello() {
println 'Output "hello":'
sh 'echo Hello'
println 'Output "World":'
sh 'echo World'
}
node {
stage('Test') {
hello()
}
}
I would expect this code to run properly, but the output is the following:
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/Sandbox/pipeline-test
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Test)
[Pipeline] echo
Output "hello":
[Pipeline] sh
[pipeline-test] Running shell script
+ echo Hello
Hello
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
You cannot run build steps inside #NonCPS methods. Pipeline scripts are considered "serializable", allowing them to be durable across system failures etc. Only a subset of the capabilities of groovy used by pipeline scripts is serializable - for anything that is not, you use #NonCPS to execute it.
Essentially, your #NonCPS method needs to do its business and return data back to the "safe", serialized execution stack.
In your particular example code I see no reason why hello() has to be #NonCPS at all - I can only assume your real function is doing something more complex.
(Edit)
Having just looked at your question history and the original script; I don't know if this is still the case with the latest versions but when I was writing our scripts ~6 months ago, each { thing -> iteration was not serializable.

Jenkins pipeline - How to iterate through a list

I'm required to read values from a file in my pipeline. I'm using split() which puts them into an Array. I need to put them into an Arraylist so I'm using Arrays.asList(). The problem I'm having is I'm unable to use the size() or length() methods so I cannot make a for loop such as
for (ii = 0; ii < var.length; ii++)
or
for (ii = 0; ii < var.size; ii++)
because I get error: unclassified field java.util.Arrays$ArrayList length
So I tried to use a for each loop, but when I take some action (like ls command for example) in my finally block it only iterates 1 time. But if I just run the command 'echo' it iterates for each element like it's supposed to. Any advice on how to modify my code to get it to iterate for each element in the list when using any command?
Works correctly....
node{
wrap([$class: 'ConfigFileBuildWrapper', managedFiles: [[fileId: 'dest_hosts.txt', targetLocation: '', variable: 'DEST_HOST']]]) {
HOST = Arrays.asList(readFile(env.DEST_HOST).split("\\r?\\n"))
deploy(HOST)
}
}
#NonCPS
def deploy(host){
for (String target : host){
try {
echo target
}
finally {
echo target
}
}
}
OUTPUT (iterates for each element):
[Pipeline] node
Running on <obfuscated>
[Pipeline] {
[Pipeline] wrap
provisoning config files...
copy managed file [<obfuscated>] to file:/var/lib/jenkins/<obfuscated>
[Pipeline] {
[Pipeline] readFile
[Pipeline] echo
www.testhost.com
[Pipeline] echo
www.testhost.com
[Pipeline] echo
www.testhost2.com
[Pipeline] echo
www.testhost2.com
[Pipeline] }
Deleting 1 temporary files
[Pipeline] // wrap
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
But if I take some action such as 'ls -l' it only iterates 1 time
node{
wrap([$class: 'ConfigFileBuildWrapper', managedFiles: [[fileId: 'dest_hosts.txt', targetLocation: '', variable: 'DEST_HOST']]]) {
HOST = Arrays.asList(readFile(env.DEST_HOST).split("\\r?\\n"))
deploy(HOST)
}
}
#NonCPS
def deploy(host){
for (String target : host){
try {
echo target
}
finally {
sh 'ls -l'
}
}
}
OUTPUT (only iterates 1 time):
[Pipeline] node
Running on <obfuscated>
[Pipeline] {
[Pipeline] wrap
provisoning config files...
copy managed file [<obfuscated>] to file:/var/lib/jenkins/<obfuscated>
[Pipeline] {
[Pipeline] readFile
[Pipeline] echo
www.testhost.com
[Pipeline] sh
[sandbox%2Fpipeline-test-new1] Running shell script
+ ls -l
total 8
-rw-r--r-- 1 jenkins jenkins 10 Jun 17 16:07 someFile
[Pipeline] }
Deleting 1 temporary files
[Pipeline] // wrap
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS
ArrayList (and generally Lists) don't have a length or size field, they have a size() method. So use that in the for:
for (ii = 0; ii < var.size(); ii++)
I prefer this solution:
node('master') {
stage('Test 1: loop of echo statements') {
echo_all(abcs)
}
}
#NonCPS // has to be NonCPS or the build breaks on the call to .each
def echo_all(list) {
list.each { item ->
echo "Hello ${item}"
}
}
If you use a declarative pipeline, you have to wrap the call in a script statement:
stage('master') {
steps {
script {
echo_all(abcs);
}
}
As per this tutorial: https://github.com/jenkinsci/pipeline-plugin/blob/master/TUTORIAL.md#serializing-local-variables
...a method marked with the annotation #NonCPS... will be treated as “native” by the Pipeline engine, and its local variables never saved. However it may not make any calls to Pipeline steps
In your case, the sh call is a pipeline step operation, which you apparently can't perform from within a #NonCPS annotated method.
Regarding turning an array into a List, then since we're in Groovy land you could just use the .toList() method on the array.
I cannot tell you PRECISELY why, as I've not figured out how to find useful information about Jenkins without spending hours googling, but I can tell you this:
For a moment I thought you can make it run fine by adding 'echo line' AFTER the sh 'echo $line', but that turned out to be caused by Jenkins running a PREVIOUS version of the script...
I tried all sorts of things and none of them worked, then I found this:
Why an each loop in a Jenkinsfile stops at first iteration
Its a known bug in Jenkins pipeline!
(the known bug is JENKINS-26481, which says "At least some closures are executed only once inside of Groovy CPS DSL scripts managed by the workflow plugin")

How to retrieve current workspace using Jenkins Pipeline Groovy script?

I am trying to get the current workspace of my Jenkins build using a Groovy pipeline script:
node('master') {
// PULL IN ENVIRONMENT VARIABLES
// Jenkins makes these variables available for each job it runs
def buildNumber = env.BUILD_NUMBER
def workspace = env.WORKSPACE
def buildUrl = env.BUILD_URL
// PRINT ENVIRONMENT TO JOB
echo "workspace directory is ${workspace}"
echo "build URL is ${env.BUILD_URL}"
}
It returns:
[Pipeline] Allocate node : Start
Running on master in /Users/Shared/Jenkins/Home/jobs/test/workspace
[Pipeline] node {
[Pipeline] echo
workspace directory is null
[Pipeline] echo
build URL is http://localhost:8080/job/test/5/
[Pipeline] } //node
[Pipeline] Allocate node : End
[Pipeline] End of Pipeline
Finished: SUCCESS
For me just ${WORKSPACE} worked without even initializing the variable workspace.
There is no variable included for that yet, so you have to use shell-out-read-file method:
sh 'pwd > workspace'
workspace = readFile('workspace').trim()
Or (if running on master node):
workspace = pwd()
I think you can also execute the pwd() function on the particular node:
node {
def PWD = pwd();
...
}
A quick note for anyone who is using bat in the job and needs to access Workspace:
It won't work.
$WORKSPACE https://issues.jenkins-ci.org/browse/JENKINS-33511 as mentioned here only works with PowerShell. So your code should have powershell for execution
stage('Verifying Workspace') {
powershell label: '', script: 'dir $WORKSPACE'
}
I have successfully used as shown below in Jenkinsfile:
steps {
script {
def reportPath = "${WORKSPACE}/target/report"
...
}
}
This is where you can find the answer in the job-dsl-plugin code.
Basically you can do something like this:
readFileFromWorkspace('src/main/groovy/com/groovy/jenkins/scripts/enable_safehtml.groovy')
In Jenkins pipeline script, I am using
targetDir = workspace
Works perfect for me. No need to use ${WORKSPACE}
The key is that, this works if used within double quotes instead of single quotes, below is my code and this worked!
script {
echo 'Entering Stage - Nexus Upload'
def artefactPath = "${WORKSPACE}/build/deploy/identityiq.war"
echo "printing the path ${artefactPath}"
}

Resources