Get console Logger (or TaskListener) from Pipeline script method - jenkins

If I have a Pipeline script method in Pipeline script (Jenkinsfile), my Global Pipeline Library's vars/ or in a src/ class, how can obtain the OutputStream for the console log? I want to write directly to the console log.
I know I can echo or println, but for this purpose I need to write without the extra output that yields. I also need to be able to pass the OutputStream to something else.
I know I can call TaskListener.getLogger() if I can get the TaskListener (really hudson.util.StreamTaskListener) instance, but how?
I tried:
I've looked into manager.listener.logger (from the groovy postbuild plugin) and in the early-build context I'm calling from it doesn't yield an OutputStream that writes to the job's Console Log.
echo "listener is a ${manager.listener} - ${manager.listener.getClass().getName()} from ${manager} and has a ${manager.listener.logger} of class ${manager.listener.logger.getClass().getName()}"
prints
listener is a hudson.util.LogTaskListener#420c55c4 - hudson.util.LogTaskListener from org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildRecorder$BadgeManager#58ac0c55 and has a java.io.PrintStream#715b9f99 of class java.io.PrintStream
I know you can get it from a StepContext via context.get(TaskListener.class) but I'm not in a Step, I'm in a CpsScript (i.e. WorkflowScript i.e. Jenkinsfile).
Finding it from a CpsFlowExecution obtained from the DSL instance registered as the steps script-property, but I couldn't work out how to discover the TaskListener that's passed to it when it's created
How is it this hard? What am I missing? There's so much indirect magic I find it incredibly hard to navigate the system.
BTW, I'm aware direct access is blocked by Script Security, but I can create #Whitelisted methods, and anything in a global library's vars/ is always whitelisted anyway.

You can access the build object from the Jenkins root object:
def listener = Jenkins.get()
.getItemByFullName(env.JOB_NAME)
.getBuildByNumber(Integer.parseInt(env.BUILD_NUMBER))
.getListener()
def logger = listener.getLogger() as PrintStream
logger.println("Listener: ${listener} Logger: ${logger}")
Result:
Listener: CloseableTaskListener[org.jenkinsci.plugins.workflow.log.BufferedBuildListener#6e9e6a16 / org.jenkinsci.plugins.workflow.log.BufferedBuildListener#6e9e6a16] Logger: java.io.PrintStream#423efc01

After banging my head against this problem for a couple days I think I have a solution:
CpsThreadGroup.current().execution.owner.listener
It's ugly, and I don't know if it's correct or if there's a better way, but seems to work.

Related

Expand variable inside Jenkins pipeline tool directive

I would like to be able to programmatically decide which tool will be installed in an Agent for a Jenkins pipeline.
This is something I have that's working today:
withEnv(["JAVA_HOME=${tool 'OPENJDK11'}",
"PATH+JAVA=${tool 'OPENJDK11'}"]) {
... do stuff ...
}
So I have a global tool OPENJDK11 installed, along with OPENJDK14, and now I would like to change the Groovy script to be able to decide which JDK to install.
So before the part above I have saved the name of the tool in a variable jdkToInstall, how am I able to reference this variable inside the tool directive?
I have tried:
${tool '${jdkToInstall}'} and ${tool '$jdkToInstall'}.
That doesn't expand my variable, so I get an error message saying it can't find the tool "$jdkToInstall".
I also tried with string concatenation, but that ended up with a similar error message with my plus and everything.
It is sufficient to expand (${}) the variable only once. Following works as expected:
withEnv(["JAVA_HOME=${tool jdkToInstall}", "PATH+JAVA=${tool jdkToInstall}"]) {
... do stuff ...
}

Log messages are not showing up in groovy jenkins script

I'm in need to extend my jenkins scripts with logging functionality and tried to use java.util.logging for this purpose. The following snippet shows what I already did.
import java.util.logging.*
#NonCPS
def tryLogging() {
println("Start")
Logger logger = Logger.getLogger("Test")
logger.setLevel(Level.INFO);
logger.setUseParentHandlers(false);
ConsoleHandler handler = new java.util.logging.ConsoleHandler()
handler.setFormatter(new SimpleFormatter())
logger.addHandler(handler)
logger.info("Hello")
logger.severe("Severe")
println("End")
}
tryLogging()
My console log now says the following:
[Pipeline] Start of Pipeline
[Pipeline] echo
Start
[Pipeline] echo
End
There is really no visible log message and I don't know, what I'm doing wrong. Can anyone here explain me how to make the console log visible? I'm also unsure if I have to use this #NonCPS here?
[Pipeline] End of Pipeline
Finished: SUCCESS
ConsoleHandler prints to System.err. Modify your script to use
System.err.println("Start") and see if 'Start' still shows up in the console. If it doesn't then the problem is the ConsoleHandler is writing to a stream you can't see.
Another thing to change is that you'll want to ensure your named logger can't be garbage collected. Depending the Java version you are using the logger can be garbage collected and you can lose your attached handler.
I'm also unsure if I have to use this #NonCPS here?
What is #NonCPS annotation in Jenkins pipeline script has information that you might find helpful.

Jenkins file can we use the IF statement

in Jenkins file one of the variable is having the comma separated values like below.
infra_services=[abc,def,xyz]
when I write the below code it was throwing an error.
if ("{$Infra_Services}".contains("xyz"))
then
echo "$Infra_Services"
fi
yes you can do if statements in a Jenkinsfile. However if you are using declarative pipeline you need to brace it with the step script.
Your issue comes from the fact you did not put any double quotes around "abc" and all the elements of your array
infra_services=[abc,def,xyz]
​
A second error will raise after you fix this. If infra_services is an array, to manipulate it you should not try to cast it as string. It should throw when you do "{$Infra_Services}"
here is a working example
​def Infra_Services = ["abc","def","xyz"]
if (Infra_Services.contains("xyz")) {
println "found"
}​​
My advice is to test your groovy before running it on jenkins, you will gain precious time. Here is a good online groovy console I use to test my code. running the groovy console from terminal is an alternative
https://groovyconsole.appspot.com/

Failures in init.groovy.d scripts: null values returned

I'm trying to get Jenkins set up, with configuration, within a Docker environment. Per a variety of sources, it appears the suggested method is to insert scripts into JENKINS_HOME/init.groovy.d. I've taken scripts from places like the Jenkins wiki and made slight changes. They're only partially working. Here is one of them:
import java.util.logging.ConsoleHandler
import java.util.logging.FileHandler
import java.util.logging.SimpleFormatter
import java.util.logging.LogManager
import jenkins.model.Jenkins
// Log into a file
println("extralogging.groovy")
def RunLogger = LogManager.getLogManager().getLogger("hudson.model.Run")
def logsDir = new File("/var/log/jenkins")
if (!logsDir.exists()) { logsDir.mkdirs() }
FileHandler handler = new FileHandler(logsDir.absolutePath+"/jenkins-%g.log", 1024 * 1024, 10, true);
handler.setFormatter(new SimpleFormatter());
RunLogger.addHandler(handler)
This script fails on the last line, RunLogger.addHandler(handler).
2019-12-20 19:25:18.231+0000 [id=30] WARNING j.util.groovy.GroovyHookScript#execute: Failed to run script file:/var/lib/jenkins/init.groovy.d/02-extralogging.groovy
java.lang.NullPointerException: Cannot invoke method addHandler() on null object
I've had a number of other scripts return NULL objects from various gets similar to this one:
def RunLogger = LogManager.getLogManager().getLogger("hudson.model.Run")
My goal is to be able to develop (locally) a Jenkins implementation and then hand it to our sysops guys. Later, as I add pipelines and what not, I'd like to be able to also work on them in a local Jenkins configuration and then hand something for import into production Jenkins.
I'm not sure how to produce API documentation so I can chase this myself. Maybe I need to stop doing it this way and just grab the files that get modified when I do this via the GUI and just stuff the files into the right place.
Suggestions?

Evaluate large Groovy script in in GroovyShell

I'm using GroovyConsole to evaluate scripts I get from external sources. So the code to evaluate is dynamic and I don't have control over it. Actually is written into a database and I have to read it as a String. Not perfect, but that's how it is.
What I'm doing right now:
private GroovyShell shell
def processScript( def script){
if (script) {
try{
shell.evaluate (script, 'some_random_name')
}catch( e ){
log.warn "Could not process script: $e"
}
}
}
This usually works. But now we got a large script (~3000 LOC) and it throws java.lang.RuntimeException: Method code too large! because the script is larger than 64K.
I tried to dump the script into a file and use a BufferedReader, but it throws the same Exception.
So is there a better way to evaluate dynamic Groovy code from within a Groovy method?
The problem is your script reach the java limit for a method. I think the only solution is to split your script in many scripts in some way.
See this answer

Resources