Jenkins pipeline shared library can't invoke method in src directory - jenkins

I want to invoke method of src directory from vars directory, which it works in IDE. But it seems not work in Jenkins.
1.project structure
├── src
│ └── foo
│ └── DemoClass.groovy
└── vars
└── varDemo.groovy
2.Content of DemoClass.groovy
#!groovy
package foo
class DemoClass {
def testDemoMethod() {
println("src DemoClass testDemoMethod")
}
}
3.Content of varDemo.groovy
#!groovy
import foo.DemoClass
def testVarsDemo() {
println("vars varDemo.groovy testVarsDemo")
}
def testVarsInvokeDemoMethod() {
println("vars varDemo.groovy testVarsInvokeDemoMethod")
def demoClass = new DemoClass()
demoClass.testDemoMethod()
println("end vars varDemo.groovy testVarsInvokeDemoMethod")
}
4.Jenkins pipeline
#Library('tools') _
varDemo.testVarsDemo()
varDemo.testVarsInvokeDemoMethod()
5.execute result in pipeline
> git checkout -f b6176268be99abe300d514e1703ff8a08e3ef8da
Commit message: "test"
> git rev-list --no-walk c1a50961228ca071d43134854548841a056e16c9 # timeout=10
[Pipeline] echo
vars varDemo.groovy testVarsDemo
[Pipeline] echo
vars varDemo.groovy testVarsInvokeDemoMethod
[Pipeline] echo
end vars varDemo.groovy testVarsInvokeDemoMethod
[Pipeline] End of Pipeline
It seem like demoClass.testDemoMethod() not work. Why can't invoke demoClass.testDemoMethod()? If I want to invoke the method in src directory, what should I do? Thank you!

Without reimplementing your code sections locally, Here are some differences I notice between yours and mine that is working.
JENKINSFILE
I don't have a space before the underscore on my #Library line
Immediately after my #Library line I am importing my shared library class that implements the methods I want to call. In your case this would be import foo.DemoClass
My call to my method is of the form (new DemoClass(config, this)).testVarsInvokeDemoMethod()
SHARED LIBRARY CLASSES
I don't have #!groovy in any of my groovy classes.
My class is public and implements Serializable
Hopefully one of these difference is the source of why its not getting called.

We need to follow the below structure
src/packagename/GroovyFile.groovy
vars/callFromJenkinsPipelne.groovy
The jenkins pipeline should be like this .
library('libracyname#Branchname')
callFromJenkinsPipelne()
Inside the vars file ( callFromJenkinsPipelne) , we can call the groovy file which is in src folder
//code in vars folder groovy file will be like this
for callFromJenkinsPipelne groovy file
def call(){
GroovyFile groovyFile = new com.jenkins.mypackagename.GroovyFile()
groovyFile.methodName(parameters)
}

Related

Is it possible to compile jenkins pipeline into jar for shared library to use it?

Here's my question, I want to compile jenkins pipeline into jar for shared library to import and use it, so that I can protect my jenkins pipeline source code.
As we know, shared library can use third-party libraries, I write my own third-party library and compile into jar then make shared library to import and use it, but I don't know how to use jenkins pipeline steps in my own third-party library.
Here's what I did:
I create my own third-party library write by groovy language and compile it into jar, source code like this:
// src/main/groovy/DemoLibrary.groovy
// I want this library to run jenkins pipeline step
package com.example
import org.jenkinsci.plugins.workflow.steps.durable_task.ShellStep
class DemoLibrary {
// this function can run command in jenkins master node
// runShell(script: script, cwd: cwd)
// return: process
def runShell(args) {
def cmd = args.script
if (args.cwd && args.cwd != "") {
cmd = "cd ${args.cwd} && ${cmd}"
}
def cmds = ['bash', '-c', cmd]
def proc = cmds.execute()
proc.waitFor()
if (proc.exitValue() != 0) {
throw new Exception("[ERROR] run shell error: ${proc.err.text}")
}
return proc
}
// i want this function to call jenkins "sh" step, but i don't know how to get StepContext in shared library
// runStepShell(script: script, context: context)
// return: stepExecution
def runStepShell(args) {
def shellStep = new ShellStep(args.script)
def stepExecution = shellStep.start(args.context)
retrun stepExecution
}
}
I create my shared library, source code like this:
// vars/demoLibrary.groovy
#Grab('com.example:demo-library:0.1.0')
#Field demoLib = new DemoLibrary()
def demoStage() {
docker.image("alpine:latest").inside("--user 1000:1000") {
def script = "hostname"
// run step "sh" can show the hostname of the docker container
sh script: script
// but run runShell show the hostname of the jenkins master
def proc = demoLib.runShell(script: script)
echo "${proc.text}"
// how can i get the docker stepContext to make my third-party library to run jenkins sh step?
demoLib.rrunStepShell(script: script, context: context)
}
}
Is it possible I can call jenkins steps in my own third-party library? This stucked me for several days. Thx

Groovy in Jenkins pipeline - create a file with content

I am using Jenkins' shared library and my Jenkins file has a stage like this:
stage('sonarqube') {
when { branch 'master' }
steps {
generateUnitTestsReport()
}
}
I want to keep programmers' repos clean from scripts that create various reports, so my idea is to keep definitions of scripts in a shared library, and then, during step execution, create a file with the content.
For instance (file generateUnitTestsReport.groovy in shared library):
def call(){
def SCRIPT_CONTENT = '''
#!/bin/bash
#SCRIPT CONTENT
'''
sh '''echo''' +SCRIPT_CONTENT+ ''' > ut-report.sh'''
sh 'chmod +x ./ut-report.sh'
}
but it doesn't work like this. I also tried Groovy's new File, but no luck there either. How could this be done (note that this is a Jenkins slave node)?

How to access global variables inside groovy class files?

I have the following directory structure
(root)
+- src # Groovy source files
| +- org
| +- foo
| +- Bar.groovy # for org.foo.Bar class
+- vars
| +- foo.groovy # for global 'foo' variable
And I have the following lines of code in the following files
Bar.groovy
package org.foo
class Bar implements Serializable {
def config
def script
Bar(script, config){
this.script = script
this.config = config
}
def accessGlobalVars(){
this.script.echo "${foo.GlobalVar}" // throws an err
}
}
foo.groovy
import groovy.transform.Field
#Field GlobalVar="Hello World!"
I'm able to access the variable in the Jenkinsfile inside the script block
echo "${foo.GlobalVar}"
Is it possible to access the same variable inside the class since the vars folder is at src folder level?
The "${foo.GlobalVar}" in your example code is trying to resolve the foo against the Bar type. Since there is no foo reference it cannot be resolved and you will probably get something like a MissingPropertyException.
Jenkins Pipeline Groovy does some different compilation and binding using the Groovy compilation and runtime APIs. The global variables can also be resolved through the script object through its dynamic GroovyObject APIs (demonstrated in the source code here). The easiest way is to let the dynamic Groovy object resolution implemented by Jenkins resolve your variable.
Change your line from:
this.script.echo "${foo.GlobalVar}"
To instead lookup the foo from the script object (which I am presuming is the this or script in your Jenkins pipeline by new Foo(this, config)):
this.script.echo "${this.script.foo.GlobalVar}"

Jenkins Shared Library Global/Singleton Class

I am looking for some guidance on the design of a Jenkins Shared Library, with respect to using a class, initializing it, and then being able to use that instance in any of my vars files.
Structure
src
- foo
- class
- Configuration.groovy
vars
- cicd.groovy
- doMore.groovy
Class
The following is a class I would like to initialize once, but then use anywhere, without having to pass it into each vars function, or reinitialize every time.
package foo.class
public class Configuration {
public String Foo
public String Bar
}
Vars
In my cicd.groovy vars file, I have something like this:
#!groovy
import foo.class.Configuration
def call () {
return initCicd()
}
def initCicd() {
configuration = new Configuration()
configuration.Foo = 'FOO'
return configuration
}
But, in other vars files like doMore.groovy, I would like to use the same configuration instance.
#!groovy
import foo.class.Configuration
def call () {
println configuration.Foo
}
Is there a Singleton pattern that that works in Jenkins Shared Library, or a way to reference an instance across vars files or steps? If possible, please share an example.
Thanks!
You can simply use Groovy's #Singleton annotation for your Configuration class and use Configuration.instance wherever you want to access configuration settings. Consider following example:
.
├── src
│   └── foo
│   └── Configuration.groovy
└── vars
├── cicd.groovy
└── doMore.groovy
src/foo/Configuration.groovy
package foo
#Singleton
class Configuration {
public String foo = 'foo_123'
public String bar = 'bar_456'
}
vars/cicd.groovy
#!groovy
import foo.Configuration
def call() {
return initCicd()
}
def initCicd() {
println Configuration.instance.foo
return Configuration.instance
}
vars/doMore.groovy
#!groovy
import foo.Configuration
def call() {
println Configuration.instance.bar
}
In the pipeline script I simply call:
cicd()
doMore()
And I get something like that in the console log:
Loading library default_jenkins_libs#master
Attempting to resolve master from remote references...
> git --version # timeout=10
> git ls-remote -h -t file:///var/jenkins_home/libraries # timeout=10
Found match: refs/heads/master revision 4fa988ccde542d77d19febd72f532ef996971a5d
> git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
> git config remote.origin.url file:///var/jenkins_home/libraries # timeout=10
Fetching without tags
Fetching upstream changes from file:///var/jenkins_home/libraries
> git --version # timeout=10
> git fetch --no-tags --progress file:///var/jenkins_home/libraries +refs/heads/*:refs/remotes/origin/*
Checking out Revision 4fa988ccde542d77d19febd72f532ef996971a5d (master)
> git config core.sparsecheckout # timeout=10
> git checkout -f 4fa988ccde542d77d19febd72f532ef996971a5d
Commit message: "update"
> git rev-list --no-walk 39890b4ca39bf32ebde8c7ad143b110bf16cf6b3 # timeout=10
[Pipeline] echo
foo_123
[Pipeline] echo
bar_456
[Pipeline] End of Pipeline
Finished: SUCCESS
The one downside of using singletons is that they can get modified anywhere and this change is populated to all callers.

How to call external Groovy files from Jenkins Build Flow Plugin script?

A JobDSL script can use a Groovy file in the same directory. For example, Git.groovy with contents like:
class Git extends Closure<Void> {
final String git
def Git(final String git = '/usr/bin/git') {
super(null)
this.git = git
}
def call(ArrayList<String> command, File dir = null) {
final gitCommand = [git, *command].execute(null, dir)
gitCommand.waitFor()
}
}
can be used by a JobDSL script:
final git = new Git()
git(['clone', ...])
But when the same thing is tried in a Build Flow script, it emits something like:
Script1.groovy: 49: unable to resolve class Git
This happens even if the Build Flow script has Flow run needs a workspace set.
How can a Build Flow script re-use common code?

Resources