How do you load a groovy file and execute it - jenkins

I have a jenkinsfile dropped into the root of my project and would like to pull in a groovy file for my pipeline and execute it. The only way that I've been able to get this to work is to create a separate project and use the fileLoader.fromGit command. I would like to do
def pipeline = load 'groovy-file-name.groovy'
pipeline.pipeline()

If your Jenkinsfile and groovy file in one repository and Jenkinsfile is loaded from SCM you have to do:
Example.Groovy
def exampleMethod() {
//do something
}
def otherExampleMethod() {
//do something else
}
return this
JenkinsFile
node {
def rootDir = pwd()
def exampleModule = load "${rootDir}#script/Example.Groovy "
exampleModule.exampleMethod()
exampleModule.otherExampleMethod()
}

If you have pipeline which loads more than one groovy file and those groovy files also share things among themselves:
JenkinsFile.groovy
def modules = [:]
pipeline {
agent any
stages {
stage('test') {
steps {
script{
modules.first = load "first.groovy"
modules.second = load "second.groovy"
modules.second.init(modules.first)
modules.first.test1()
modules.second.test2()
}
}
}
}
}
first.groovy
def test1(){
//add code for this method
}
def test2(){
//add code for this method
}
return this
second.groovy
import groovy.transform.Field
#Field private First = null
def init(first) {
First = first
}
def test1(){
//add code for this method
}
def test2(){
First.test2()
}
return this

You have to do checkout scm (or some other way of checkouting code from SCM) before doing load.

Thanks #anton and #Krzysztof Krasori, It worked fine if I combined checkout scm and exact source file
Example.Groovy
def exampleMethod() {
println("exampleMethod")
}
def otherExampleMethod() {
println("otherExampleMethod")
}
return this
JenkinsFile
node {
// Git checkout before load source the file
checkout scm
// To know files are checked out or not
sh '''
ls -lhrt
'''
def rootDir = pwd()
println("Current Directory: " + rootDir)
// point to exact source file
def example = load "${rootDir}/Example.Groovy"
example.exampleMethod()
example.otherExampleMethod()
}

Very useful thread, had the same problem, solved following you.
My problem was: Jenkinsfile -> call a first.groovy -> call second.groovy
Here my solution:
Jenkinsfile
node {
checkout scm
//other commands if you have
def runner = load pwd() + '/first.groovy'
runner.whateverMethod(arg1,arg2)
}
first.groovy
def first.groovy(arg1,arg2){
//whatever others commands
def caller = load pwd() + '/second.groovy'
caller.otherMethod(arg1,arg2)
}
NB: args are optional, add them if you have or leave blank.
Hope this could helps further.

In case the methods called on your loaded groovy script come with their own node blocks, you should not call those methods from within the node block loading the script. Otherwise you'd be blocking the outer node for no reason.
So, building on #Shishkin's answer, that could look like
Example.Groovy
def exampleMethod() {
node {
//do something
}
}
def otherExampleMethod() {
node {
//do something else
}
}
return this
Jenkinsfile
def exampleModule
node {
checkout scm // could not get it running w/o checkout scm
exampleModule = load "script/Example.Groovy"
}
exampleModule.exampleMethod()
exampleModule.otherExampleMethod()
Jenkinsfile using readTrusted
When running a recent Jenkins, you will be able to use readTrusted to read a file from the scm containing the Jenkinsfile without running a checkout - or a node block:
def exampleModule = evaluate readTrusted("script/Example.Groovy")
exampleModule.exampleMethod()
exampleModule.otherExampleMethod()

Related

How to pass parameters in a stage call in Jenkinsfile

Actually my Jenkinsfile looks like this:
#Library('my-libs') _
myPipeline{
my_build_stage(project: 'projectvalue', tag: '1.0' )
my_deploy_stage()
}
I am trying to pass these two variables (project and tag) to my build_stage.groovy, but it is not working.
What is the correct syntax to be able to use $params.project or $params.tag in my_build_stage.groovy?
Please see the below code which will pass parameters.
In your Jenkinsfile write below code:
// Global variable is used to get data from groovy file(shared library file)
def mylibrary
def PROJECT_VALUE= "projectvalue"
def TAG = 1
pipeline{
agent{}
stages{
stage('Build') {
steps {
script {
// Load Shared library Groovy file mylibs.Give your path of mylibs file which will contain all your function definitions
mylibrary= load 'C:\\Jenkins\\mylibs'
// Call function my_build stage and pass parameters
mylibrary.my_build_stage(PROJECT_VALUE, TAG )
}
}
}
stage('Deploy') {
steps {
script {
// Call function my_deploy_stage
mylibrary.my_deploy_stage()
}
}
}
}
}
Create a file named : mylibs(groovy file)
#!groovy
// Write or add Functions(definations of stages) which will be called from your jenkins file
def my_build_stage(PROJECT_VALUE,TAG_VALUE)
{
echo "${PROJECT_VALUE} : ${TAG_VALUE}"
}
def my_deploy_stage()
{
echo "In deploy stage"
}
return this

How to call a Jenkinsfile or a Jenkins Job from another Jenkinsfile?

On a scripted pipeline written in Groovy, I have 2 Jenkinsfiles namely - Jenkinsfile1 and Jenkinsfile2.
Is it possible to call Jenkinsfile2 from Jenkinsfile1.
Lets say following is my Jenkinsfile1
#!groovy
stage('My build') {
node('my_build_node') {
def some_output = True
if (some_output) {
// How to call Jenkinsfile2 here?
}
}
}
How do I call Jenkinsfile2 above when output has a value which is not empty?
Or is it possible to call another Jenkins job which uses Jenkinsfile2?
Your question wasn't quite clear to me. If you just want to load and evaluate some piece of Groovy code into yours, you can use load() (as #JoseAO previously stated). Apart from his example, if your file (Jenkinsfile2.groovy) has a call() method, you can use it directly, like this:
node('master') {
pieceOfCode = load 'Jenkinsfile2.groovy'
pieceOfCode()
pieceOfCode.bla()
}
Now, if you want to trigger another job, you can use the build() step, even if you're not using a declarative pipeline. The thing is that the pipeline you're calling must be created in Jenkins, because build() takes as an parameter the job name, not the pipeline filename. Here's an example of how to call a job named pipeline2:
node('master') {
build 'pipeline2'
}
Now, as for your question "How do I call Jenkinsfile2 above when output has a value which is not empty?", if I understood correctly, you're trying to run some shell command, and if it's empty, you'll load the Jenkinsfile/pipeline. Here's how to achieve that:
// Method #1
node('master') {
try {
sh 'my-command-goes-here'
build 'pipeline2' // if you're trying to call another job
// If you're trying to load and evaluate a piece of code
pieceOfCode = load 'Jenkinsfile2.groovy'
pieceOfCode()
pieceOfCode.bla()
}
catch(Exception e) {
print("${e}")
}
}
// Method #2
node('master') {
def commandResult = sh script: 'my-command-goes-here', returnStdout: true
if (commandResult.length() != 0) {
build 'pipeline2' // if you're trying to call another job
// If you're trying to load and evaluate a piece of code
pieceOfCode = load 'Jenkinsfile2.groovy'
pieceOfCode()
pieceOfCode.bla()
}
else {
print('Something went bad with the command.')
}
}
Best regards.
For example, your Jenkisfile2 it's my "pipeline2.groovy".
def pipeline2 = load (env.PATH_PIPELINE2 + '/pipeline2.groovy')
pipeline2.method()

Use files as input to Jenkins JobDSL

I am trying to use Jenkins' JobDSL plugin to programatically create jobs. However, I want to be able to define the parameters in a file. According to docs on distributed builds, this may not be possible. Anyone have any idea how I can achieve this? I could use the readFileFromWorkspace method but I still need to iterate over all files provided and run JobDSL x times. JobDSL code below. The important part I am struggling with is the first 15 lines or so.
#!groovy
import groovy.io.FileType
def list = []
hudson.FilePath workspace = hudson.model.Executor.currentExecutor().getCurrentWorkspace()
def dir = new File(workspace.getRemote() + "/pipeline/applications")
dir.eachFile (FileType.FILES) { file ->
list << file
}
list.each {
println (it.path)
def properties = new Properties()
this.getClass().getResource( it.path ).withInputStream {
properties.load(it)
}
def _git_key_id = 'jenkins'
consumablesRoot = '//pipeline_test'
application_folder = "${consumablesRoot}/" + properties._application_name
// Create the branch_indexer
def jobName = "${application_folder}/branch_indexer"
folder(consumablesRoot) {
description("Ensure consumables folder is in place")
}
folder(application_folder) {
description("Ensure app folder in consumables spaces is in place.")
}
job(jobName) {
println("in the branch_indexer: ${GIT_BRANCH}")
label('master')
/* environmentVariables(
__pipeline_code_repo: properties."__pipeline_code_repo",
__pipeline_code_branch: properties."__pipeline_code_branch",
__pipeline_scripts_code_repo: properties."__pipeline_scripts_code_repo",
__pipeline_scripts_code_branch: properties."__pipeline_scripts_code_branch",
__gcp_template_code_repo: properties."__gcp_template_code_repo",
__gcp_template_code_branch: properties."__gcp_template_code_branch",
_git_key_id: _git_key_id,
_application_id: properties."_application_id",
_application_name: properties."_application_name",
_business_mnemonic: properties."_business_mnemonic",
_control_repo: properties."_control_repo",
_project_name: properties."_project_name"
)*/
scm {
git {
remote {
url(control_repo)
name('control_repo')
credentials(_git_key_id)
}
remote {
url(pipeline_code_repo)
name('pipeline_pipelines')
credentials(_git_key_id)
}
}
}
triggers {
scm('#daily')
}
steps {
//ensure that the latest code from the pipeline source code repo has been pulled
shell("git ls-remote --heads control_repo | cut -d'/' -f3 | sort > .branches")
shell("git checkout -f pipeline_pipelines/" + properties."pipeline_code_branch")
//get the last branch from the control_repo repo
shell("""
git for-each-ref --sort=-committerdate refs/remotes | grep -i control_repo | head -n 1 > .last_branch
""")
dsl(['pipeline/branch_indexer.groovy'])
}
}
// Start the branch_indexer
queue(jobName)
}
In case someone else ends up here in search of a simple method for reading only one parameter file, use readFileFromWorkspace (as mentioned by #CodyK):
def file = readFileFromWorkspace(relative_path_to_file)
If the file contains a parameter called your_param, you can read it using ConfigSlurper:
def config = new ConfigSlurper().parse(file)
def your_param = config.getProperty("your_param")
Was able to get it working with this piece of code:
hudson.FilePath workspace = hudson.model.Executor.currentExecutor().getCurrentWorkspace()
// Build a list of all config files ending in .properties
def cwd = hudson.model.Executor.currentExecutor().getCurrentWorkspace().absolutize()
def configFiles = new FilePath(cwd, 'pipeline/applications').list('*.properties')
configFiles.each { file ->
def properties = new Properties()
def content = readFileFromWorkspace(file.getRemote())
properties.load(new StringReader(content))

Using FilePath to access workspace on slave in Jenkins pipeline

I need to check for the existence of a certain .exe file in my workspace as part of my pipeline build job. I tried to use the below Groovy script from my Jenkinsfile to do the same. But I think the File class by default tries to look for the workspace directory on jenkins master and fails.
#com.cloudbees.groovy.cps.NonCPS
def checkJacoco(isJacocoEnabled) {
new File(pwd()).eachFileRecurse(FILES) { it ->
if (it.name == 'jacoco.exec' || it.name == 'Jacoco.exec')
isJacocoEnabled = true
}
}
How to access the file system on slave using Groovy from inside the Jenkinsfile?
I also tried the below code. But I am getting No such property: build for class: groovy.lang.Binding error. I also tried to use the manager object instead. But get the same error.
#com.cloudbees.groovy.cps.NonCPS
def checkJacoco(isJacocoEnabled) {
channel = build.workspace.channel
rootDirRemote = new FilePath(channel, pwd())
println "rootDirRemote::$rootDirRemote"
rootDirRemote.eachFileRecurse(FILES) { it ->
if (it.name == 'jacoco.exec' || it.name == 'Jacoco.exec') {
println "Jacoco Exists:: ${it.path}"
isJacocoEnabled = true
}
}
Had the same problem, found this solution:
import hudson.FilePath;
import jenkins.model.Jenkins;
node("aSlave") {
writeFile file: 'a.txt', text: 'Hello World!';
listFiles(createFilePath(pwd()));
}
def createFilePath(path) {
if (env['NODE_NAME'] == null) {
error "envvar NODE_NAME is not set, probably not inside an node {} or running an older version of Jenkins!";
} else if (env['NODE_NAME'].equals("master")) {
return new FilePath(path);
} else {
return new FilePath(Jenkins.getInstance().getComputer(env['NODE_NAME']).getChannel(), path);
}
}
#NonCPS
def listFiles(rootPath) {
print "Files in ${rootPath}:";
for (subPath in rootPath.list()) {
echo " ${subPath.getName()}";
}
}
The important thing here is that createFilePath() ins't annotated with #NonCPS since it needs access to the env variable. Using #NonCPS removes access to the "Pipeline goodness", but on the other hand it doesn't require that all local variables are serializable.
You should then be able to do the search for the file inside the listFiles() method.

How to tell Jenkins "Build every project in folder X"?

I have set up some folders (Using Cloudbees Folder Plugin).
It sounds like the simplest possible command to be able to tell Jenkins: Build every job in Folder X.
I do not want to have to manually create a comma-separated list of every job in the folder. I do not want to add to this list whenever I want to add a job to this folder. I simply want it to find all the jobs in the folder at run time, and try to build them.
I'm not finding a plugin that lets me do that.
I've tried using the Build Pipeline Plugin, the Bulk Builder Plugin, the MultiJob plugin, and a few others. None seem to support the use case I'm after. I simply want any Job in the folder to be built. In other words, adding a job to this build is as simple as creating a job in this folder.
How can I achieve this?
I've been using Jenkins for some years and I've not found a way of doing what you're after.
The best I've managed is:
I have a "run every job" job (which contains a comma-separated list of all the jobs you want).
Then I have a separate job that runs periodically and updates the "run every job" job as new projects come and go.
One way to do this is to create a Pipeline job that runs Groovy script to enumerate all jobs in the current folder and then launch them.
The version below requires the sandbox to be disabled (so it can access Jenkins.instance).
def names = jobNames()
for (i = 0; i < names.size(); i++) {
build job: names[i], wait: false
}
#NonCPS
def jobNames() {
def project = Jenkins.instance.getItemByFullName(currentBuild.fullProjectName)
def childItems = project.parent.items
def targets = []
for (i = 0; i < childItems.size(); i++) {
def childItem = childItems[i]
if (!childItem instanceof AbstractProject) continue;
if (childItem.fullName == project.fullName) continue;
targets.add(childItem.fullName)
}
return targets
}
If you use Pipeline libraries, then the following is much nicer (and does not require you to allow a Groovy sandbox escape:
Add the following to your library:
package myorg;
public String runAllSiblings(jobName) {
def names = siblingProjects(jobName)
for (def i = 0; i < names.size(); i++) {
build job: names[i], wait: false
}
}
#NonCPS
private List siblingProjects(jobName) {
def project = Jenkins.instance.getItemByFullName(jobName)
def childItems = project.parent.items
def targets = []
for (def i = 0; i < childItems.size(); i++) {
def childItem = childItems[i]
if (!childItem instanceof AbstractProject) continue;
if (childItem.fullName == jobName) continue;
targets.add(childItem.fullName)
}
return targets
}
And then create a pipeline with the following code:
(new myorg.JobUtil()).runAllSiblings(currentBuild.fullProjectName)
Yes, there are ways to simplify this further, but it should give you some ideas.
I developed a Groovy script that does this. It works very nicely. There are two Jobs, initBuildAll, which runs the groovy script and then launches the 'buildAllJobs' jobs. In my setup, I launch the InitBuildAll script daily. You could trigger it another way that works for you. We aren't full up CI, so daily is good enough for us.
One caveat: these jobs are all independent of one another. If that's not your situation, this may need some tweaking.
These jobs are in a separate Folder called MultiBuild. The jobs to be built are in a folder called Projects.
import com.cloudbees.hudson.plugins.folder.Folder
import javax.xml.transform.stream.StreamSource
import hudson.model.AbstractItem
import hudson.XmlFile
import jenkins.model.Jenkins
Folder findFolder(String folderName) {
for (folder in Jenkins.instance.items) {
if (folder.name == folderName) {
return folder
}
}
return null
}
AbstractItem findItem(Folder folder, String itemName) {
for (item in folder.items) {
if (item.name == itemName) {
return item
}
}
null
}
AbstractItem findItem(String folderName, String itemName) {
Folder folder = findFolder(folderName)
folder ? findItem(folder, itemName) : null
}
String listProjectItems() {
Folder projectFolder = findFolder('Projects')
StringBuilder b = new StringBuilder()
if (projectFolder) {
for (job in projectFolder.items.sort{it.name.toUpperCase()}) {
b.append(',').append(job.fullName)
}
return b.substring(1) // dump the initial comma
}
return b.toString()
}
File backupConfig(XmlFile config) {
File backup = new File("${config.file.absolutePath}.bak")
FileWriter fw = new FileWriter(backup)
config.writeRawTo(fw)
fw.close()
backup
}
boolean updateMultiBuildXmlConfigFile() {
AbstractItem buildItemsJob = findItem('MultiBuild', 'buildAllProjects')
XmlFile oldConfig = buildItemsJob.getConfigFile()
String latestProjectItems = listProjectItems()
String oldXml = oldConfig.asString()
String newXml = oldXml;
println latestProjectItems
println oldXml
def mat = newXml =~ '\\<projects\\>(.*)\\<\\/projects\\>'
if (mat){
println mat.group(1)
if (mat.group(1) == latestProjectItems) {
println 'no Change'
return false;
} else {
// there's a change
File backup = backupConfig(oldConfig)
def newProjects = "<projects>${latestProjectItems}</projects>"
newXml = mat.replaceFirst(newProjects)
XmlFile newConfig = new XmlFile(oldConfig.file)
FileWriter nw = new FileWriter(newConfig.file)
nw.write(newXml)
nw.close()
println newXml
println 'file updated'
return true
}
}
false
}
void reloadMultiBuildConfig() {
AbstractItem job = findItem('MultiBuild', 'buildAllProjects')
def configXMLFile = job.getConfigFile();
def file = configXMLFile.getFile();
InputStream is = new FileInputStream(file);
job.updateByXml(new StreamSource(is));
job.save();
println "MultiBuild Job updated"
}
if (updateMultiBuildXmlConfigFile()) {
reloadMultiBuildConfig()
}
A slight variant on Wayne Booth's "run every job" approach. After a little head scratching I was able to define a "run every job" in Job DSL format.
The advantage being I can maintain my job configuration in version control. e.g.
job('myfolder/build-all'){
publishers {
downstream('myfolder/job1')
downstream('myfolder/job2')
downstream('myfolder/job2')
}
}
Pipeline Job
When running as a Pipeline job you may use something like:
echo jobNames.join('\n')
jobNames.each {
build job: it, wait: false
}
#NonCPS
def getJobNames() {
def project = Jenkins.instance.getItemByFullName(currentBuild.fullProjectName)
project.parent.items.findAll {
it.fullName != project.fullName && it instanceof hudson.model.Job
}.collect { it.fullName }
}
Script Console
Following code snippet can be used from the script console to schedule all jobs in some folder:
import hudson.model.AbstractProject
Jenkins.instance.getAllItems(AbstractProject.class).each {
if(it.fullName =~ 'path/to/folder') {
(it as AbstractProject).scheduleBuild2(0)
}
}
With some modification you'd be able to create a jenkins shared library method (requires to run outside the sandbox and needs #NonCPS), like:
import hudson.model.AbstractProject
#NonCPS
def triggerItemsInFolder(String folderPath) {
Jenkins.instance.getAllItems(AbstractProject.class).each {
if(it.fullName =~ folderPath) {
(it as AbstractProject).scheduleBuild2(0)
}
}
}
Reference pipeline script to run a parent job that would trigger other jobs as suggested by #WayneBooth
pipeline {
agent any
stages {
stage('Parallel Stage') {
parallel {
stage('Parallel 1') {
steps {
build(job: "jenkins_job_1")
}
}
stage('Parallel 2') {
steps {
build(job: "jenkins_job_2")
}
}
}
}
}
The best way to run an ad-hoc command like that would be using the Script Console (can be found under Manage Jenkins).
The console allows running Groovy Script - the script controls Jenkins functionality. The documentation can be found under Jenkins JavaDoc.
A simple script triggering immediately all Multi-Branch Pipeline projects under the given folder structure (in this example folder/subfolder/projectName):
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject
import hudson.model.Cause.UserIdCause
Jenkins.instance.getAllItems(WorkflowMultiBranchProject.class).findAll {
return it.fullName =~ '^folder/subfolder/'
}.each {
it.scheduleBuild(0, new UserIdCause())
}
The script was tested against Jenkins 2.324.

Resources