How do you modify Jenkins configuration from a shared-library? - jenkins

Jenkins allows us to grab a Singelton of the running instance with jenkins.get(). I'm trying to build a class in my shared-library that CRUD's the cloud providers. My code looks like this.
#NonCPS
def create(){
Jenkins jenkins = Jenkins.getInstance()
// logic to create cloud
jenkins.clouds.add(tmpCloud)
jenkins.save()
}
#NonCPS
def delete(){
Jenkins jenkins = Jenkins.getInstance()
def newlist = jenkins.clouds.findAll{ it.getDisplayName() != cloud }
if(newlist){
jenkins.clouds.clear()
}
for ( int i = 0; i < newlist.size; i++ ) {
jenkins.clouds.add(newlist[i])
}
jenkins.save()
}
If I run just the create() function the code works as expected, same if I run just the delete(), but if I run both in the same job like this.
cloud.create()
cloud.delete()
Then only the create will work, the delete wont error but it wont do anything either. Is it possible to reinitialize a singleton? I feel like that is what I need to do. I have tried saving the jenkins instance as a field of my class, but that leads to a hudson serializable error. I dont see a way to pass the jenkins singleton around because of CPS issues.

Related

Executing shell commands from inside Pipeline Shared Library

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() } ...

Is it possible to use build user vars plugin in a Jenkins shared library?

I'm implementing functionality to expose the triggering user for a Jenkins pipeline to our CD system, so I've grabbed the build user vars plugin: https://plugins.jenkins.io/build-user-vars-plugin/
The way the plugin seems to work is that you wrap the code you need the variables exposed to, like so:
wrap([$class: 'BuildUser']) {
userId = env.BUILD_USER_ID
}
I tried this on a general pipeline and just echoed it out, all was well.
I then tried implementing it in our shared library so that this would happen on all calls to CD, and I'm hitting an error.
wrap([$class: 'BuildUser']) {
jobBuildUrl ="${jobBuildUrl}&USER_ID=${env.BUILD_USER_ID}"
}
[2021-08-19T10:20:22.852Z] hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: com.company.jenkins.pipelines.BuildManager.wrap() is applicable for argument types: (java.util.LinkedHashMap, org.jenkinsci.plugins.workflow.cps.CpsClosure2) values: [[$class:BuildUser], org.jenkinsci.plugins.workflow.cps.CpsClosure2#1c9a210c]
Is there any way to use this plugin code within a shared library? If so how?
I'm lead to believe not, but I thought it was worth the asking. For reference, there is this outstanding issue: https://issues.jenkins.io/browse/JENKINS-44741
Side note, I'm trying to do this without touching everybody's pipelines themselves. If this is impossible with this plugin, I'll probably just implement my own version of it within the shared lib.
That plugin is hard to use and has many issues, one of them is with shared libraries.
Instead just implement it by yourself, it is relatively easy and allows you much more control over the logic of what is done, error handling and which parameters you return.
You can use something like the following:
/**
* Get the last upstream build that triggered the current build
* #return Build object (org.jenkinsci.plugins.workflow.job.WorkflowRun) representing the upstream build
*/
#NonCPS
def getUpstreamBuild() {
def build = currentBuild.rawBuild
def upstreamCause
while (upstreamCause = build.getCause(hudson.model.Cause$UpstreamCause)) {
build = upstreamCause.upstreamRun
}
return build
}
/**
* Get the properties of the build Cause (the event that triggered the build)
* #param upstream If true (Default) return the cause properties of the last upstream job (If the build was triggered by another job or by a chain of jobs)
* #return Map representing the properties of the user that triggered the current build.
* Contains keys: USER_NAME, USER_ID
*/
#NonCPS
def getUserCauseProperties(Boolean upstream = true) {
def build = upstream ? getUpstreamBuild() : currentBuild.rawBuild
def cause = build.getCause(hudson.model.Cause$UserIdCause)
if (cause) {
return ['USER_NAME': cause.userName, 'USER_ID': cause.userId]
}
println "Job was not started by a user, it was ${build.getCauses()[0].shortDescription}"
return [:]
}
I was also not able to use wrap([$class: 'BuildUser']) into groovy method. Therefore I think I found alternative way which seems to do the trick. You may get the username and userid (which might return email, depends on your setup) by just the following:
def build = currentBuild.rawBuild
def cause = build.getCause(hudson.model.Cause.UserIdCause.class)
def userName = cause.getUserName() // or use getUserId()

How to get the current Jenkins pipeline StepContext

I have a step in a pipeline that pulls objects from the context and uses them. However, I need to access those objects outside of the steps to feed into different steps, and the second step doesn't expose it.
stage() {
steps {
script {
def status = waitForQualityGate()
// Use the taskId
}
}
}
}
The waitForQualityGate() call only returns a boolean, so I can't access it there.
I could instead manually initialize the step, like so:
script {
def qualityGate = new WaitForQualityGateStep()
def taskId = qualityGate.getTaskId()
}
but the taskId is null. If I try to run the start methods manually on the step:
script {
def qualityGate = new WaitForQualityGateStep()
qualityGate.start().start()
def taskId = qualityGate.getTaskId()
}
It fails with the message:
java.lang.IllegalStateException: you must either pass in a StepContext to the StepExecution constructor, or have the StepExecution be created automatically
The WaitForQualityGateStep has the info I need, but I can't initialize it without having a StepContext (which is an Abstract class). How can I get one from the pipeline?
You can define the variable before the pipeline and in the step just set its value. This way the variable is visible across the pipeline.
I still have no idea how to manually get a step context to manually execute a step, but in case anyone else finds this by trying to get information out of the Sonar plugin, this is how I got the task ID that I needed.
def output = sh(script: "mvn sonar:sonar", returnStdout: true)
echo output // The capture prevents printing to console
def taskUri = output.find(~'/api/ce/task\\?id=[\\w-]*')

Jenkins 2.0 Pipeline and Job DSL

I have a Jenkins Pipeline Job which has this code :
import hudson.model.*
import hudson.util.*
import hudson.scm.*
import hudson.scm.SubversionChangeLogSet.LogEntry
stage 'Build'
node('master'){
svn 'http://mysvn/url'
def build = Thread.currentThread()?.executable
def changeSet= build.getChangeSet()
.
.
}
The code is with unchecked 'sandbox' (as it presented on the picture).
and I get this error :
groovy.lang.MissingPropertyException: No such property: executable for class: java.lang.Thread
I am not familiar with the syntax for Thread.currentThread()?.executable
what does the '?' operator means.
I google it and found out about jenkins job-dsl plugin and didn't find anything about this operator.
I also tried the Script Console Plugin at : http://localhost:8080/script
and I fail for the same reason.
Does Pipeline Plugin support the Jenkins DSL-JOB ? should I import something in order to make it work ?
Here is the related ticket and the answer from cloudbees.. Ping back from there:
def changeLogSets = currentBuild.rawBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
echo "${entry.commitId} by ${entry.author} on ${new Date(entry.timestamp)}: ${entry.msg}"
def files = new ArrayList(entry.affectedFiles)
for (int k = 0; k < files.size(); k++) {
def file = files[k]
echo " ${file.editType.name} ${file.path}"
}
}
}
It's a groovy operator, '?' is to prevent NullpointerExceptions. It does the following only if the first is not null.
The Sandbox feature is to prevent certain calls so anybody can add scripts without admin approval, but very limited...
def build = Thread.currentThread()?.executable
Firstly, the above sentence can be explained like this,
Thread.currentThread() will get the current running thread, in normal case, it will be a instance of Jenkins Executor class, there is a attribute inside this class,
/**
* {#link hudson.model.Queue.Executable} being executed right now, or null if the executor is idle.
*/
#GuardedBy("lock")
private Queue.Executable executable;
Jenkins AbstractBuild implement this interface, so it means you will actually get AbstractBuild instance back.
However, this sentence is not work for pipeline related jobs since pipe line project have different structure compared with old Jenkins jobs. It don't extend AbstractBuild class.
This is why your script is not working.
About your requirement, since there is no AbstrctBuild class, so a lot of methods are actually cannot be used, like the one you used.
No idea if there is a smart way to get the changset inside pipeline job, or maybe you need restructure your job to adapt pipeline plugin.
Br,
Tim

How to programmatically trigger a specific jenkins build managed via github-organization?

I am using the github-organization plugin to manage jenkins jobs from github but I discovered that Jenkins API does not report these builds.
In fact the API list the entire organization as a single job.
How can I build a specific repository and branch using the API?
To be clear, I am looking for some groovy code to add inside the Jenkinsfile
#!groovy
stage 'test-downstream'
node {
def job = build job: 'some-job'
}
Now, the problem is that Jenkins is seeing the entire organization as a single job!
If I use Jenkins API to retrieve the jobs, it will return only the organization, and not all the repositories and jobs inside it.
I suspect that's because the way this plugin was implemented and I suppose that I need to give some extra parameters in order to specify which repository and branch I want to build inside the organization.... building an organization does not make much sense.
The question is vague, but I am guessing “API” in this context means the REST API to trigger builds. You can use for example
curl -X POST -u user:apitoken http://jenkins/job/yourorg/job/yourrepo/job/master/build
The following code trigger job via System Groovy build step. Please note that system groovy always run on master so passing info from previous build steps might be tricky.
import jenkins.model.*
import hudson.model.*
import java.util.concurrent.*
def run_job(job_name) {
def currentBuild = Thread.currentThread().executable
def jenkins = jenkins.model.Jenkins.getInstance();
def job = jenkins.getItemByFullName(job_name);
if (job == null)
throw new hudson.AbortException("Cannot find job:" + job_name);
def params =[
new StringParameterValue('PARAMETER1', "invoke 1 param1"),
new StringParameterValue('PARAMETER2', ",invoke 1 param2")
]
def paramsAction = new ParametersAction(params)
def cause = new hudson.model.Cause.UpstreamCause(currentBuild)
def causeAction = new hudson.model.CauseAction(cause)
def future_build = job.scheduleBuild2(0,causeAction,paramsAction);
def running_build = future_build.waitForStart()
return running_build
}
run_job("runner1")

Resources