I'm setting up a new build. Running a simple shell command works perfectly, like below:
stage("Demo") {
sh "echo 'Hi There'"
}
I have been trying to "package" my shell scripts into their own classes just to neaten things up a bit. The problem is that when trying to execute the same exact shell script from within a class, jenkins fails the builds with:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException:
unclassified method java.lang.Class sh java.lang.String
This is a simple example that fails for me after moving the above method into its own class:
stage('Demo stage') {
Tools.PrintMe("Hi There")
}
public class Tools {
public static void PrintMe(String message) {
sh "echo " + message
}
}
There is also no option provided in the script manager to Whitelist this rejected method.
Is there a way to get around this? Or is there a limitation that I'm not aware of?
#Crait to make a call of predefined steps in your own class you need to path script object to you class.
So, try this:
stage('Demo stage') {
Tools.PrintMe(this, "Hi There")
}
public class Tools {
public static void PrintMe(def script, String message) {
script.sh "echo " + message
}
}
As #sshepel pointed out above, code executing in a plain script is not in the same context as code inside a class. I resolved it in a similar way to above by creating a static reference to the script object and then executing against that in my classes.
//Set the static reference in the script
Script.environment = this
public class Script {
public static environment
}
public class Tools {
public static void PrintMe(String message) {
Script.environment.sh "echo " + message
}
}
I did it this way to avoid polluting method signatures with passing the script object around. The downside is that all my classes will have a dependency on having "Script.environment = this" set.
Related
I'm writing a shared library that will get used in Pipelines.
class Deployer implements Serializable {
def steps
Deployer(steps) {
this.steps = steps
}
def deploy(env) {
// convert environment from steps to list
def process = "ls -l".execute(envlist, null)
process.consumeProcessOutput(output, error)
process.waitFor()
println output
println error
}
}
In the Jenkinsfile, I import the library, call the class and execute the deploy function inside a script section:
stage('mystep') {
steps {
script {
def deployer = com.mypackage.HelmDeployer("test")
deployer.deploy()
}
}
}
However, no output or errors are printed on the Console log.
Is it possible to execute stuff inside a shared library class? If so, how, and what am I doing wrong?
Yes, it is possible but not really an obvious solution. Every call that is usually done in the Jenkinsfile but was moved to the shared-library needs to reference the steps object you passed.
You can also reference the Jenkins environment by calling steps.env.
I will give you a short example:
class Deployer implements Serializable {
def steps
Deployer(steps) {
this.steps = steps
}
def callMe() {
// Always call the steps object
steps.echo("Test")
steps.echo("${steps.env.BRANCH_NAME}")
steps.sh("ls -al")
// Your command could look something like this:
// def process = steps.sh(script: "ls -l", returnStdout: true).execute(steps.env, null)
...
}
}
You also have to import the object of the shared library and create an instance of it. Define the following outside of your Pipeline.
import com.mypackage.Deployer // path is relative to your src/ folder of the shared library
def deployer = new Deployer(this) // 'this' references to the step object of the Jenkins
Then you can call it in your pipeline as the following:
... script { deployer.test() } ...
]I am a junior dev trying to lear about Jenkins, I have been learning on my own for a couple of months. Currently I have a pipeline (just for learning purposes) which runs static analysis on a folder, and then publish it, I have been able to send a report through email using jelly templates, from there I realized it is posbile to instantiate the classes of a plugin to use its methods so I went to the cppcheck javadoc and did some trial and error so I can get some values of my report and then do something else with them, so I had something like this in my pipeline:
pipeline {
agent any
stages {
stage('analysis') {
steps {
script{
bat'cppcheck "E:/My_project/Source/" --xml --xml-version=2 . 2> cppcheck.xml'
}
}
}
stage('Test'){
steps {
script {
publishCppcheck pattern:'cppcheck.xml'
for (action in currentBuild.rawBuild.getActions()) {
def name = action.getClass().getName()
if (name == 'org.jenkinsci.plugins.cppcheck.CppcheckBuildAction') {
def cppcheckaction = action
def totalErrors = cppcheckaction.getResult().report.getNumberTotal()
println totalErrors
def warnings = cppcheckaction.getResult().statistics.getNumberWarningSeverity()
println warnings
}
}
}
}
}
}
}
which output is:
[Pipeline] echo
102
[Pipeline] echo
4
My logic (wrongly) tells me that if I can access to the report and statistics classes like that and uses their methods getNumberTotal() and getNumberWarningSeverity() respectively, therefore I should be able to also access the DiffState class in the same way and use the valueOf() method to get an enum of the new errors. But adding this to my pipeline:
def nueva = cppcheckaction.getResult().diffState.valueOf(NEW)
println nueva
Gives me an error:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: No such field found: field org.jenkinsci.plugins.cppcheck.CppcheckBuildAction diffState
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.unclassifiedField(SandboxInterceptor.java:425)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onGetProperty(SandboxInterceptor.java:409)
...
I can see in the javadoc there is a diffState class with a valueOf() method, but I cannot access to it is therre any other way to get the new errors between the last build and the current one?
I see 2 issues that could be causing this:
CppcheckResult doesn't have a member variable diffState so you can't access it obviously
If you check the javadoc of CppcheckResult the class does have:
private CppcheckReport report;
public CppcheckStatistics getReport()
and
private CppcheckStatistics statistics;
public CppcheckStatistics getStatistics()
there is no member (and getter method) for diffState so maybe try to call:
/**
* Get differences between current and previous statistics.
*
* #return the differences
*/
public CppcheckStatistics getDiff(){
my suggestion: cppcheckaction.getResult().getDiff().valueOf(NEW). Furthermore CppcheckWorkspaceFile does have a method getDiffState().
Please have a look at the script approval of your Jenkins (see here).
The syntax error might appear because Jenkins (Groovy Sandbox) blocks the execution of an (for the Jenkins) "unknown" and potential dangerous method.
Jenkins settings - Script Approval - Approve your blocked method
I would like to integrate a Global library into my build flow. I have written a basic function
srv/core/jenkins/Checks.groovy:
package core.jenkins
class Checks implements Serializable {
def script
Checks(script) {
this.script = script
}
def fileExists(){
script.echo "File exists in the repo."
}
}
And it is exposed as a global var
vars/fileExisits.groovy:
def call() {
new core.jenkins.Checks(this).fileExists()
}
While configuring the Global Shared Library settings in Jenkins, I have the following settings:
Now in my jenkinsfile, Im doing something like this:
pipeline {
agent { label 'master' }
stages {
stage('Check for md files'){
steps {
sh 'echo hello'
script {
checks.fileExists()
}
}
}
}
}
This always gives the error
groovy.lang.MissingPropertyException: No such property: checks for class: groovy.lang.Binding
at groovy.lang.Binding.getVariable(Binding.java:63)
at
For it to work, I have to add the lines to the top of my Jenkinsfile
import core.jenkins.Checks
def checks = new Checks(this)
Is there a way for me to invoke the function fileExists from a library without having to add the above 2 lines always ?
Just replace:
checks.fileExists()
with:
fileExists()
All Groovy scripts that implements def call() methods and are stored in the vars/ folder can be triggered by their script file name. Alternatively, if you would like to keep checks.fileExists() syntax, then you need to create vars/checks.groovy script file and implement def fileExists() method inside of it.
I'm iterating through building a Jenkins pipeline shared library, so my Jenkinsfile is a little cleaner.
I'm using the following page for guidance: https://jenkins.io/doc/book/pipeline/shared-libraries/ .
I first defined several methods in individual files, like "vars/methodName.groovy", with a "call()" method in the code. This works ok, and I particularly note that "println" calls in these methods are seen in the Jenkins console output.
I then decided I wanted to save some state between method calls, so I added a new file in "vars" named "uslutils.groovy" that begins like this (minus some required imports):
class uslutils implements Serializable {
I defined some "with<property>" methods that set a property and return this.
I then wrote a "public String toString()" method in "uslutils" that looks something like this:
public String toString() {
println "Inside uslutils.toString()."
return "[currentBuild[${currentBuild}] mechIdCredentials[${mechIdCredentials}] " +
"baseStashURL[${baseStashURL}] jobName[${jobName}] codeBranch[${codeBranch}] " +
"buildURL[${buildURL}] pullRequestURL[${pullRequestURL}] qBotUserID[${qBotUserID}] " +
"qBotPassword[${qBotPassword}]]"
}
Then, inside my Jenkinsfile, after setting the uslutils properties, I added a line like this:
println "uslutils[${uslutils}]"
Then, I ran my job, and the curious thing that happened is that I didn't see the "uslutils" line, or the Inside uslutils.toString(). line. However, I did modify the one functional method I've added so far to "uslutils" (besides the "with" methods), which returns a string value, and I just added an "x" to the value. My Jenkinsfile was printing the result from that, and it did show the additional "x".
Note that no errors occurred here, it just seemed to omit the println output from within the shared library class, and even stranger, omitted the output from the println call in the Jenkinsfile that was implicitly calling the uslutils.toString() method. Note that the println calls in the original call() methods WERE seen in the console output.
Any ideas what might be happening here?
Update:
I now have the following lines in my Jenkinsfile (among others):
println "uslutils.qBotPassword[${uslutils.qBotPassword}]"
println "uslutils[${uslutils}]"
println "uslutils.toString()[${uslutils.toString()}]"
And to repeat, here is the "uslutils.toString()" method:
public String toString() {
println "Inside uslutils.toString()."
return "[currentBuild[${currentBuild}] mechIdCredentials[${mechIdCredentials}] " +
"baseStashURL[${baseStashURL}] jobName[${jobName}] codeBranch[${codeBranch}] " +
"codeURL[${codeURL}] buildURL[${buildURL}] pullRequestURL[${pullRequestURL}] qBotUserID[${qBotUserID}] " +
"qBotPassword[${qBotPassword}]]"
}
Here are corresponding lines of output from the build:
[Pipeline] echo
uslutils.qBotPassword[...]
[Pipeline] echo
uslutils.toString()[[currentBuild[org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper#41fb2c94] mechIdCredentials[121447d5-0fe4-470d-b785-6ce88225ef01] baseStashURL[https://...] jobName[unified-service-layer-build-pipeline] codeBranch[master] codeURL[ssh://git#...] buildURL[http://...] pullRequestURL[] qBotUserID[...] qBotPassword[...]]
As you can see, the line attempting to print "uslutils[${uslutils}]" was simply ignored. The line attempting to print "uslutils.toString()[${uslutils.toString()}]" did render, but also note that the Inside uslutils.toString(). did not render.
I'm still looking for an explanation for this behavior, but perhaps this summarizes it more succinctly.
I did some digging and found this issue, https://issues.jenkins-ci.org/browse/JENKINS-41953, basically in normal pipeline script println is aliased to echo step. But when you're in a class, e.g. outside of the pipeline CPS, then the echo step isn't available and the println is ignored (since, as I understand it, there is no logger available).
What you can do is to propagate the script environment into your class methods using a variable and call echo through the variable (found solution in this thread). Like this:
class A {
Script script;
public void a() {
script.echo("Hello")
}
}
def a = new A(script:this)
echo "Calling A.a()"
a.a()
outputs:
Started by user jon
[Pipeline] echo
Calling A.a()
[Pipeline] echo
Hello
[Pipeline] End of Pipeline
Finished: SUCCESS
Which is what we want. For comparison, here is without propagation:
class A {
public void a() {
println "Hello"
}
}
def a = new A()
echo "Calling A.a()"
a.a()
Gives:
Started by user jon
[Pipeline] echo
Calling A.a()
[Pipeline] End of Pipeline
Finished: SUCCESS
I want to refactor my Jenkins pipeline script into classes for readability and reuse.
The problem is i get exceptions when doing so.
Let's look at a simple example:
When i run
echo currentBuild.toString()
everything is fine
But when i extract it into a class as so:
class MyClass implements Serializable {
def runBuild() {
echo currentBuild.toString()
}
}
new MyClass().runBuild()
i get an exception:
Started by user admin
Replayed #196
[Pipeline] End of Pipeline
groovy.lang.MissingPropertyException: No such property: currentBuild for class: MyClass
What is the proper way of extracting pipeline code in to classes?
You are on the right way, but the problem is that you didn't pass the script object to the instance of your class and was trying to call method which is not defined in the class that you have created.
Here is one way to solve this:
// Jenkins file or pipeline scripts editor in your job
new MyClass(this).runBuild()
// Class declaration
class MyClass implements Serializable {
def script
MyClass(def script) {
this.script=script
}
def runBuild() {
script.echo script.currentBuild.toString()
}
}
your code missing declare class field script
class MyClass implements Serializable {
def script
MyClass(def script) {
this.script=script
}
def runBuild() {
script.echo script.currentBuild.toString()
}
}
this code should be ok #bram