Custom function in Delcarative Pipeline? - jenkins

I have this simple pipeline (I'm using a more complex but this one reproduces it):
def test(String name) {
println test
}
pipeline {
agent {
dockerfile {
label "jenkins-lnx-slave3"
args "--user root:root"
dir "CICD"
}
}
stages {
stage ('Test') {
steps {
test(name: 'Hello')
}
}
}
}
And Jenkins is giving me this "No Such DSL method 'test' found" error:
> [Pipeline] } [Pipeline] // withEnv [Pipeline] } [Pipeline] // node
> [Pipeline] End of Pipeline java.lang.NoSuchMethodError: No such DSL
> method 'test' found among steps [ArtifactoryGradleBuild,
This goes on and on for many lines. Am I doing the custom function correctly?
Anyone have any ideas?

You're trying to call test with a named argument and that's causing the breakage. This post explains what's happening when you try to use named params in groovy.
Additionally, you have a typo inside test:
println test should be println name.

Related

Jenkins Pipeline throws a java.io.NotSerializableException: org.jenkinsci.plugins.workflow.job.WorkflowJob within a NonCPS method

I'm trying to get the data of another job within a #NonCPS method. It fails with a NotSerializableException even though the failing method is declared as #NonCPS.
The script is as follows - it only tries to get another job's data using its name and build number:
#!groovy
import jenkins.model.Jenkins
node('build_agent') {
wrap([$class: 'AnsiColorBuildWrapper', 'colorMapName': 'xterm']) {
stage('Get job details') {
name = "another_jenkins_job"
job = Hudson.instance.getJob(name)
job_data = Jenkins.instance.getItemByFullName(job.fullName)
jobNumber = "1234"
println 'Job: ' + job.fullName
getUpstreamJobData(job.fullName.trim(), buildNumber.trim())
}
}
#NonCPS
def getUpstreamJobData(jobName, jobNumber) {
println "Getting build #${jobNumber} from job ${jobName}"
Jenkins.getInstance().getItemByFullName(jobName).getBuildByNumber(jobNumber)
}
The script fails, however, within the #NonCPS method getUpstreamJobData:
[Pipeline] echo
Job: another_jenkins_job
[Pipeline] echo
Getting build #1234 from job another_jenkins_job
[Pipeline] echo
Getting build #1234 from job another_jenkins_job
[Pipeline] echo
Getting build #1234 from job another_jenkins_job
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // wrap
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
an exception which occurred:
in field delegate
in field closures
in object org.jenkinsci.plugins.workflow.cps.CpsThreadGroup#1f5bb62e
Caused: java.io.NotSerializableException: org.jenkinsci.plugins.workflow.job.WorkflowJob
at org.jboss.marshalling.river.RiverMarshaller.doWriteObject(RiverMarshaller.java:860)
at org.jboss.marshalling.river.BlockMarshaller.doWriteObject(BlockMarshaller.java:65)
at org.jboss.marshalling.river.BlockMarshaller.writeObject(BlockMarshaller.java:56)
...
My understanding is that this failure usually happens when we return a non-serializable object outside of a Non-CPS method. However, the method here doesn't return anything yet, and it declared as NonCPS. Additionally the print statements seem to indicate that the method is executed 3 times while it is only called once.
I had the very same issue because of missing def in front of one assignment inside a #NonCPS method.
It seems, every assignment must be prefixed with def, otherwise job works as expected, but fails at the end when trying to save the finished build state.
So which part of your code is not serializable?
Why do you think the exception is thrown from you #NonSCP method?
I think that those two look very suspicious
job = Hudson.instance.getJob(name)
job_data = Jenkins.instance.getItemByFullName(job.fullName)

Jenkins declarative pipeline lock name with variable

I have a declarative Jenkins Pipeline with a lock, e. g.
pipeline {
environment {
BRANCH = 'master'
}
agent any
stages{
stage('stage') {
options {
lock(resource: "lock-${env.BRANCH}")
}
steps {
echo "Something"
}
}
}
}
But when I execute the pipeline, in the log it says
[Pipeline] lock
Trying to acquire lock on [lock-null]
Lock acquired on [lock-null]
[Pipeline] {
[Pipeline] echo
master
[Pipeline] }
Lock released on resource [lock-null]
The environment variable seems to be not set when the lock-name is evaluated, but when the echo argument is evaluated, it is set correctly.
This answer to a somewhat related question gave the hint to use a lazily evaluated GString instead of a normal GString. Trying this:
pipeline {
environment {
BRANCH = 'master'
}
agent any
stages{
stage('stage') {
options {
lock(resource: "lock-${->env.BRANCH}" as String)
}
steps {
echo "${->env.BRANCH}" as String
}
}
}
}
gives me the following log messages
[Pipeline] lock
Trying to acquire lock on [[no resource/label specified - probably a bug]]
Lock acquired on [[no resource/label specified - probably a bug]]
[Pipeline] {
[Pipeline] echo
master
[Pipeline] }
Lock released on resource [[no resource/label specified - probably a bug]]
So, it looks like the variable can't be resolved correctly.
The problem I want to solve is, creating a multibranch-pipeline which has a lock on a stage. But when the lock has a name, which is not dependend on the branchname, only one branch of the pipeline can run in parallel in this stage.
How can I solve this?
You could just use lock as a step instead of an option:
pipeline {
environment {
BRANCH = 'master'
}
agent any
stages{
stage('stage') {
steps {
lock("lock-${env.BRANCH}" as String) {
echo "${env.BRANCH}" as String
}
}
}
}
}
As within steps the variable env.BRANCH is set, this should work.
Also see documentation for lock step.
I recently ran into this myself. ${env} is not accessible in the options block, but ${currentBuild} is. So what I did was first was println("$currentBuild") and get the name of the class. From that, I googled the Java source code docs and followed the functions until I got what I needed.
In my case, I wanted the ${env.NODE_NAME} and I ended up with ${currentBuild.getRawBuild().getExecutor().getOwner().getDisplayName()}

#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 variable, different executors

I'm not a Jenkins guru so please be patient. :-)
I have a pipeline, something nearly as simple as this:
def hash = ''
node {
stage('Checkout') {
…
}
stage('Build') {
…
}
stage('Tests') {
…
}
}
stage('Ask deploy') {
input 'Deploy?'
}
node {
stage('Deploy') {
}
}
I want to set the value of the hash variable in the first node and read it in the next if the manual input is positive. Is this possible and safe? Is this the correct approach?
Note that there are multiple executors and manual input involved. In the Jenkins docs it is hinted for a node that:
As soon as an executor is free on a node, the steps will run.
This means that the two nodes may run in different executors, correct? Do they still share the same global variables? Thanks in advance for any clarifications!
If you have multiple slaves in Jenkins, the pipeline will be launch in one of this slaves. Every slave is different.
Every stage in you pipeline will be launch in the same slave so if you have the variable "hash" at the first line of your pipeline you wouldn't have problem to read it in all your pipeline but if you have to access to this variable value from a different build you can not access.
If you need a global variable to read it in different builds you can define a global variable using the Global Variables String Parameter Plugin
The hash variable is global and its value is available in the different executors which seems logical to me. So it looks like what I do is OK and it will work this way unless I miss something.
Here is how I've verified that (details skipped for brevity):
I've created a similar pipeline and killed the executor which ran the first node:
def gitHash;
node {
withCredentials(...) {
//Step 1:
//Check out from the SCM
stage('Prepare') {
echo "Checking out the project from source control.."
scmInfo = checkout scm
gitHash = scmInfo.GIT_COMMIT
echo "Project checked out, the GIT hash of the last commit is: ${gitHash}"
}
}
}
stage('Ask deploy') {
input 'Deploy?'
}
node {
withCredentials(...) {
stage('Deploy') {
echo "TODO, hash ${gitHash}"
}
}
}
The output from Jenkins is the following (details skipped):
Obtained Jenkinsfile from 7adc4bb98524b31de93e0c1ae16bf967ca3df47c
Running on jnlp-13775fa128a47 in /root/workspace/...
[Pipeline] {
[Pipeline] withCredentials
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Prepare)
[Pipeline] echo
Project checked out, the GIT hash of the last commit is: 7adc4bb98524b31de93e0c1ae16bf967ca3df47c
[Pipeline] }
[Pipeline] // stage
[Pipeline] stage
[Pipeline] stage
[Pipeline] { (Ask deploy)
[Pipeline] input
Deploy?
Proceed or Abort
Approved by admin
[Pipeline] }
[Pipeline] // stage
[Pipeline] node
Running on jnlp-1383bdf520c9d in /root/workspace/...
[Pipeline] {
[Pipeline] withCredentials
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Deploy)
[Pipeline] echo
TODO, hash 7adc4bb98524b31de93e0c1ae16bf967ca3df47c
[Pipeline] End of Pipeline
Finished: SUCCESS
As seen the first node runs on executor jnlp-13775fa128a47 the second is on jnlp-1383bdf520c9d but the value of the globally scoped variable can be read there.

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")

Resources