Jenkins lts : Problem with groovy script path in jenkins workspace - jenkins

I just upgraded the jenkins version of our development factory to lts. We were previously in an old 2.x version
In fact we have a stage to load all scripts :
stage('Load Utilities Scripts') {
checkout scm
echo "${job_name}"
modules.messages = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/slack_functions.groovy"
modules.ansible = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/ansible_functions.groovy"
modules.jenkins = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/jenkins_functions.groovy"
modules.pic_env = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/pic_env.groovy"
modules.maven = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/maven_functions.groovy"
modules.git = load "${jenkins_home}/workspace/${job_name}#script/pipelines/utils/git_functions.groovy"
}
it worked perfectly well except that now the scripts are positioned in
/var/jenkins_home/workspace/SAGES2_BUILD_AUTO#script/357f3acc22b0fbb05a66735d46d7d7eb950a2e836ab3762b1905784bc550ee5e on builtin
/var/jenkins_home/workspace/SAGES2_BUILD_AUTO on builtin
I guess it's a security evolution (i know groovy script plugin on jenkins are often deprecated but it's another problem).
How can i get this string : 357f3acc22b0fbb05a66735d46d7d7eb950a2e836ab3762b1905784bc550ee5e ? Is there any vars like job_name or jenkins_home ? Who generates this token and where ? Can i have the hand on this string ?
What does it represent ?

You can get the current directory and build the absolute path by appending the relative part.
modules.messages = load pwd() + "#script/pipelines/utils/slack_functions.groovy"
Or something like this
modules.messages = load "${WORKSPACE}#script/pipelines/utils/slack_functions.groovy"
It seems this has being introduced as a security improvement. So you will have to work around this like below.
script {
// Locate the jenkins folder
// This is done because there is a new sub-folder (like : 17a4ba1ed1ce777b18c5...)
git_jenkins_folder = sh (
script: "find " + WORKSPACE + "#script -type d -name 'jenkins' -printf '%T# %Tc %p\n' | sort -rn | head -1 | cut -d' ' -f9",
returnStdout: true
).trim()
// Load the groovy methods in groovy files
slack = load git_jenkins_folder + "/pipelines/utils/slack_functions.groovy"
}
Credit: Why does Jenkins creates a subfolder within the workspace#script folder to checkout git code instead of the workspace#script itself?

Related

Jenkinsfile shell command not using env variables as expected

In my Jenkinsfile I want to dynamically find the unity version using a python script like so:
environment {
UNITY_EDITOR = bat(script: "py $WORKSPACE/get_versions.py --unity", returnStdout: true).trim()
UNITY_BASE = "C:/Program Files/Unity/Hub/Editor/$UNITY_EDITOR/Editor/Unity.exe"
UNITY_WRAPPER = "UnityBatchWrapper -silent-crashes -no-dialogs -batchmode -quit -unityPath \"$UNITY_BASE\""
}
post {
always {
script {
echo "Returning license"
licenseReturnStatus = bat (
script: "$UNITY_WRAPPER -returnlicense",
returnStatus: true
) == 0
}
}
From other stackoverflow answers this seems like it should work, but instead my Jenkins job errors out during the post-build step because $UNITY_WRAPPER isn't defined:
groovy.lang.MissingPropertyException: No such property: UNITY_WRAPPER for class: groovy.lang.Binding
I'm thinking the batch step is what's failing, even though Jenkins doesn't complain about it. I've also tried using $env.WORKSPACE and %WORKSPACE% and that doesn't work either.
I'm beginning to think $WORKSPACE doesn't exist til after the environments step...
Turns out I didn't have Python installed since it was an ephemeral GCP builder and I hadn't updated the node label yet.
For anyone reading this that has trouble with bat commands - be sure to put an # sign in front of your command like "#py ..." or else the command will be echoed in the output. Also trim your output so it doesn't have CRLF in it.

How to get jenkins job description using shell

I wanted to take the description given for each job in jenkins. Is there any way of getting it by shell script?
Access your jenkins master node:
ssh <jenkins_user>#<jenkins_master_node>
Locate then go to jobs directory:
cd /jenkins/jobs/
Then do this:
find . -type f -iname config.xml | xargs grep -m1 "description"
Basically every Jenkins job you create has a respective config.xml on your master with all the information you entered in the Jenkins UI. One of the entries in this config.xml is a description tag that you write when making a job. I have limited the output to only show the first description in the config.xml files. You can remove the -m1 to see all the descriptions.
Use Groovy from the script console.
items = Jenkins.instance.allItems.findAll {
// Gets all jobs in the portfolio
it instanceof hudson.model.FreeStyleProject
}
items.each {
println it.fullName + " : " + it.description
}
return

Jenkins pipeline script to copy or move file to another destination

I am preparing a Jenkins pipeline script in Groovy language. I would like to move all files and folders to another location. As Groovy supports Java so I used below java code to perform the operation.
pipeline{
agent any
stages{
stage('Organise Files'){
steps{
script{
File sourceFolder = new File("C:\\My-Source");
File destinationFolder = new File("C:\\My-Destination");
File[] listOfFiles = sourceFolder.listFiles();
echo "Files Total: " + listOfFiles.length;
for (File file : listOfFiles) {
if (file.isFile()) {
echo file.getName()
Files.copy(Paths.get(file.path), Paths.get("C:\\My-Destination"));
}
}
}
}
}
}
}
This code throws the bellow exception:
groovy.lang.MissingPropertyException: No such property: Files for
class: WorkflowScript
I tried with below code too, but it's not working either.
FileUtils.copyFile(file.path, "C:\\My-Destination");
Finally, I did try with java I/O Stream to perform the operation and the code is bellow:
def srcStream = new File("C:\\My-Source\\**\\*").newDataInputStream()
def dstStream = new File("C:\\My-Destination").newDataOutputStream()
dstStream << srcStream
srcStream.close()
dstStream.close()
But it's not working either and throws the below exception:
java.io.FileNotFoundException: C:\My-Source (Access is denied)
Can anyone suggest me how to solve the problem and please also let me know how can I delete the files from the source location after copy or move it? One more thing, during the copy can I filter some folder and files using wildcard? Please also let me know that.
Don't execute these I/O functions using plain Java/Groovy. Even if you get this running, this will always be executed on the master and not the build agents. Use pipeline steps also for this, for example:
bat("xcopy C:\\My-Source C:\\My-Destination /O /X /E /H /K")
or using the File Operations Plugin
fileOperations([fileCopyOperation(
excludes: '',
flattenFiles: false,
includes: 'C:\\My-Source\\**',
targetLocation: "C:\\My-Destination"
)]).
I assume I didn't hit the very right syntax for Windows paths here in my examples, but I hope you get the point.

Job-dsl script reading from root directory(/) instead of workspace - Jenkins

I have a job dsl script in which i am locating a directory. However it is trying to find that directory from / and not looking inside workspace.
Also i tried to print the current working directory, its displaying / only
String workingDir = System.getProperty("user.dir");
println "current directory is "
println workingDir;
Output :
current directory is
/
Just fixed it.
I was using "user.dir" which is by default "/" directory(manage jenkins -> system information -> system properties)
I added this and it worked
workingDir = "${WORKSPACE}" + "/some_directory"

Jenkins Declarative Pipeline, run groovy script on slave agent

I have a Jenkins declarative pipeline I have been running on the Jenkins master and it works fine. However, now that I have moved to trying to execute this on a slave node, the groovy scripts which are called in the pipeline can not access the files in the workspace.
My jenkinsfile looks like this...
pipeline {
agent {
label {
label "windows"
customWorkspace "WS-${env.BRANCH_NAME}"
}
}
stages {
stage('InitialSetup') {
steps {
"${env.WORKSPACE}/JenkinsScripts/myScript.groovy"
}
}
}
I can see on the slave that it is creating the workspace, doing the checkout from git, and executing the script correctly. However, if something in the script try's to interact with the files in the workspace it fails.
If I have something simple like this...
def updateFile(String filename) {
echo env.NODE_NAME
filename = "${env.WORKSPACE}/path/to/file"
def myFile = new File(filename)
<do other things with the file>
}
...it says it can not find the file specified. It gives me the path it is looking for and I can confirm the file exists, and that the code runs when just building on the master.
Why can the script not find the files this way when in can just running on the master node? I added the "echo env.NODE_NAME" command into my groovy file and it says the script is executing on the correct node.
Thanks.
Turns out Groovy File commands are considered insecure, and although they will run on the master, they will not run on the slave. If you call them from a script that has the agent set to another node, it will still execute the command just fine, just on the master node, not the agent. Here's an excerpt of an article post https://support.cloudbees.com/hc/en-us/articles/230922508-Pipeline-Files-manipulation
The operation with File class are run on master, so only works if build is run on master, in this example I create a file and check if I can access it on a node with method exists, it does not exist because the new File(file) is executed on master, to check this I search for folder Users that exist on my master but not in the node.
stage 'file move wrong way'
//it only works on master
node('slave') {
def ws = pwd()
def context = ws + "/testArtifact"
def file = ws + '/file'
sh 'touch ' + file
sh 'ls ' + ws
echo 'File on node : ' + new File(file).exists()
echo 'Users : ' + new File('/Users').exists()
sh 'mv ' + file + ' ' + context
sh 'ls ' + ws
}
To execute file manipulation command we recommend to use native commands.
This is a simple example of operations in shell
stage 'Create file'
sh 'touch test.txt'
stage 'download file'
def out='$(pwd)/download/maven.tgz'
sh 'mkdir -p ./download'
sh 'curl -L http://ftp.cixug.es/apache/maven/maven-3/3.3.9/binaries/apache-maven-3.3.9-bin.tar.gz -o ' + out
stage 'move/rename'
def newName = 'mvn.tgz'
sh 'mkdir -p $(pwd)/other'
sh 'mv ' + out + ' ' + newName
sh 'cp ' + newName + ' ' + out
}
I run into this same issue recently. I had a python file that runs and writes the results to a JSON file. I was trying to access the JSON file to retrieve the data from there. Here is the code I was using inside a stage block of a declarative pipeline:
script {
def jsonSlurper = new JsonSlurper()
def fileParsed = new File("parameters.json")
def dataJSON = jsonSlurper.parse(fileParsed)
}
As everyone stated already, the above was failing with FileNotFoundException because anything inside script{} will only run on master and not the agent.
To work around the issue, I have used the Pipeline Utility Steps plugin (reference: https://plugins.jenkins.io/pipeline-utility-steps/ -- How to use: https://www.jenkins.io/doc/pipeline/steps/pipeline-utility-steps/#writejson-write-json-to-a-file-in-the-workspace)
The plugin will allow you to do any read/write operation on multiple file formats.
Here is an example of the code I used after installing the plugin:
script {
def props = readJSON file: 'parameters.json'
println("just read it..")
println(props)
}
Note: I was using jenkins 2.249.1
I have implemented the code which automatically installs Groovy on slave (for scripted pipeline). Perhaps this solution is a little bit cumbersome, but pipelines don't offer any other way to achieve the same functionality as "Execute Groovy Script" stuff from the old Jenkins, because the plugin https://wiki.jenkins.io/display/JENKINS/Groovy+plugin is not supported yet for pipeline.
import hudson.tools.InstallSourceProperty;
import hudson.tools.ToolProperty;
import hudson.tools.ToolPropertyDescriptor;
import hudson.tools.ToolDescriptor;
import hudson.tools.ToolInstallation;
import hudson.tools.ToolInstaller;
import hudson.util.DescribableList;
import hudson.plugins.groovy.GroovyInstaller;
import hudson.plugins.groovy.GroovyInstallation;
/*
Installs Groovy on the node.
The idea was taken from: https://devops.lv/2016/12/05/jenkins-groovy-auto-installer/
and https://github.com/jenkinsci/jenkins-scripts/blob/master/scriptler/configMavenAutoInstaller.groovy
COMMENT 1: If we use this code directly (not as a separate method) then we get
java.io.NotSerializableException: hudson.plugins.groovy.GroovyInstaller
COMMENT 2: For some reason inst.getExecutable(channel) returns null. I use inst.forNode(node, null).getExecutable(channel) instead.
TODO: Check if https://jenkinsci.github.io/job-dsl-plugin/#method/javaposse.jobdsl.dsl.helpers.step.MultiJobStepContext.groovyCommand
works better.
*/
#NonCPS
def installGroovyOnSlave(String version) {
if ((version == null) || (version == "")) {
version = "2.4.7" // some default should be
}
/* Set up properties for our new Groovy installation */
def node = Jenkins.getInstance().slaves.find({it.name == env.NODE_NAME})
def proplist = new DescribableList<ToolProperty<?>, ToolPropertyDescriptor>()
def installers = new ArrayList<GroovyInstaller>()
def autoInstaller = new GroovyInstaller(version)
installers.add(autoInstaller)
def InstallSourceProperty isp = new InstallSourceProperty(installers)
proplist.add(isp)
def inst = new GroovyInstallation("Groovy", "", proplist)
/* Download and install */
autoInstaller.performInstallation(inst, node, null)
/* Define and add our Groovy installation to Jenkins */
def descriptor = Jenkins.getInstance().getDescriptor("hudson.plugins.groovy.Groovy")
descriptor.setInstallations(inst)
descriptor.save()
/* Output the current Groovy installation's path, to verify that it is ready for use */
def groovyInstPath = getGroovyExecutable(version)
println("Groovy " + version + " is installed in the node " + node.getDisplayName())
}
/* Returns the groovy executable path on the current node
If version is specified tries to find the specified version of groovy,
otherwise returns the first groovy installation that was found.
*/
#NonCPS
def getGroovyExecutable(String version=null) {
def node = Jenkins.getInstance().slaves.find({it.name == env.NODE_NAME})
def channel = node.getComputer().getChannel()
for (ToolInstallation tInstallation : Jenkins.getInstance().getDescriptor("hudson.plugins.groovy.Groovy").getInstallations()) {
if (tInstallation instanceof GroovyInstallation) {
if ((version == null) || (version == "")) {
// any version is appropriate for us
return tInstallation.forNode(node, null).getExecutable(channel)
}
// otherwise check for version
for (ToolProperty prop in tInstallation.getProperties()) {
if (prop instanceof InstallSourceProperty) {
for (ToolInstaller tInstaller: prop.installers) {
if (
(tInstaller instanceof GroovyInstaller) &&
(tInstaller.id.equals(version))
)
return tInstallation.forNode(node, null).getExecutable(channel)
}
}
}
}
}
return null
}
/* Wrapper function. Returns the groovy executable path as getGroovyExecutable()
but additionally tries to install if the groovy installation was not found.
*/
def getGroovy(String version=null) {
def installedGroovy = getGroovyExecutable(version)
if (installedGroovy != null) {
return installedGroovy
} else {
installGroovyOnSlave(version)
}
return getGroovyExecutable(version)
}
Just put these 3 methods to your pipeline script and you will be able to get the Groovy executable path with the help of the method getGroovy(). If it is not installed yet then the installation will be done automatically. You can test this code with the simple pipeline, like this:
// Main
parallel(
'Unix' : {
node ('build-unix') {
sh(getGroovy() + ' --version')
}
},
'Windows' : {
node ('build-win') {
bat(getGroovy() + ' --version')
}
}
)
For me the output was:
[build-unix] Groovy Version: 2.4.7 JVM: 1.8.0_222 Vendor: Private Build OS: Linux
[build-win] Groovy Version: 2.4.7 JVM: 11.0.1 Vendor: Oracle Corporation OS: Windows 10
To work with files on the slave workspace use the readFile, writeFile, findFiles etc steps.
Or if they are large as FloatingCoder said use native tooling; which may be running a groovy script.
A workaround could be load the library via sh command in Jenkinsfile.
So, if you use in Jenkinsfile:
sh 'groovy libraryName.groovy'
You can load the lib locally and in this way you can store File on the slave node.
Even without pipelines, there is no option to restrict a job based on slave agent label. So, I think, pipelines are only for master node execution.
Starting from release 2.4 of the Groovy plugin there is withGroovy step available which sets up the environment on the agent so that you can do sh 'groovy yourscript.groovy' with expected environments. It also enables limited interaction between Pipeline and groovy script.
See https://www.jenkins.io/doc/pipeline/steps/groovy/ for some details about the step.

Resources