I'm developing my first plugin for Jenkins that will add some additional permissions to Jenkins' matrix based security authorization.
I'm developing the plugin in NetBeans 8.1. The plugin can build and deploy to Jenkins 1.625.3 and I can see my permission show up in the matrix table.
The plugin has a class that extends the RunListener<AbstractBuild> extension point. I override the setUpEnvironment method and in this method I'm trying to see if the user that caused the build has my new permissions.
Unfortunately, every time I call User.get(username).hasPermission(permission), the result is true. I've simplified the testing by creating two users:
adminuser: has the Administer permission
devuser: currently just have overall read and no other checkboxes checked.
If I put a debug break in my setUpEnvironment method, and add the following watch, the result is true:
User.get("devuser").hasPermission(hudson.model.Hudson.ADMINISTER)
Intuitively, I look at the code above and think hasPermission is based on the User returned by the get method. However, I'm starting to suspect that it doesn't matter that hasPermission is called on the user object, the security principle is some system user with uber access.
Can someone point me in the right direction?
Thanks!
Matrix Screenshot
Debug Watches
The problem with is that User.hasPermission(Permission p) calls ACL.hasPermission(Permission p) which in fact runs:
return hasPermission(Jenkins.getAuthentication(),p);
Therefore permissions are not checked for loaded User but for current User used to execute this code.
If you run below code from Script Console:
println instance.getAuthorizationStrategy().
hasPermission("devuser", hudson.model.Hudson.ADMINISTER)
println instance.getAuthorizationStrategy().getACL(User.get("devuser")).
hasPermission(User.get("devuser").impersonate(), hudson.model.Hudson.ADMINISTER)
println instance.getAuthorizationStrategy().getACL(User.get("devuser")).
hasPermission(User.get("devuser").impersonate(), hudson.model.Hudson.ADMINISTER)
println instance.getAuthorizationStrategy().getACL(User.get("devuser")).
hasPermission(hudson.model.Hudson.ADMINISTER)
println instance.getAuthorizationStrategy().getACL(User.current()).
hasPermission(hudson.model.Hudson.ADMINISTER)
it will return:
false
false
false
true
true
As a "workaround" try to obtain authorization strategy directly from Jenkins object and execute hasPermission(...) method from it:
def instance = Jenkins.getInstance()
instance.getAuthorizationStrategy().hasPermission("devuser", Jenkins.ADMINISTER)
Related
First off the setup in question:
A Jenkins Instance with several build nodes and on prem Azure-Devops server containing the Git Repositories.
The Repo in question is too large to always build on push for all branches and all devs, so a small workaround was done:
The production branches have a polling enabled twice a day (because of testing duration which is handled downstream more builds would not help with quality)
All other branches have their automated building suppressed. They still can start it manually for Builds/Deployments/Unittests if they so choose.
The jenkinsfile has parameterization for which platforms to build, on prod* all the platforms are true, on all other branches false.
This helps because else the initial build of a feature branch would always build/deploy locally all platforms which would take too much of a load on the server infrastructure.
I added a service endpoint for Jenkins in the Azure Devops, added a Buildvalidation .yml - this basically works because when I call the sourcebranch of the pull request with the merge commitID i added a parameter
isPullRequestBuild which contains the ID of the PR.
snippet of the yml:
- task: JenkinsQueueJob#2
inputs:
serverEndpoint: 'MyServerEndpoint'
jobName: 'MyJob'
isMultibranchJob: true
captureConsole: true
capturePipeline: true
isParameterizedJob: true
multibranchPipelineBranch: $(System.PullRequest.SourceBranch)
jobParameters: |
stepsToPerform=Build
runUnittest=true
pullRequestID=$(System.PullRequest.PullRequestId)
Snippet of the Jenkinsfile:
def isPullRequest = false
if ( params.pullRequestID?.trim() )
{
isPullRequest = true
//do stuff to change how the pipeline should react.
}
In the jenkinsfile I look whether the parameter is not empty and reset the platforms to build to basically all and to run the unittests.
The problem is: if the branch has never run, Jenkins does not already know the parameter in the first run, so it is ignored, building nothing, and returning with 0 because "nothing had to be done".
Is there any way to only run the jenkins build if it hasnt run already?
Or is it possible to get information from the remote call if this was the build with ID 1?
The only other thing would be to Call the Jenkins via web api and check for the last successful build, but in that case I would have have the token somewhere stored in source control.
Am I missing something obvious here? I dont want to trigger the feature branch builds to do nothing more than once, because Devs could lose useful information about their started builds/deployments.
Any ideas appreciated
To whom it may concern with similar problems:
In the end I used the following workaround:
The Jenkins Endpoint is called via a user that only is used for automated builds. So, in case that this user triggered the build, I set everything to run a Pull Request Validation, even if it is the first build. Along the lines of
def causes = currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')
if (causes != null)
{
def buildCauses= readJSON text: currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause').toString()
buildCauses.each
{
buildCause ->
if (buildCause['userId'] == "theNameOfMyBuildUser")
{
triggeredByAzureDevops = true
}
}
}
getBuildcauses must be allowed to run by a Jenkins Admin for that to work.
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.
I have a script in the init.groovy.d/ directory which runs when Jenkins starts. I want to look for some job runs and stop them.
All seems to be working fine except when I enable matrix security (which we need to use on our production system).
The relevant groovy code is:
def busyExecutors = Jenkins.instance.computers.collect {
c -> c.executors.findAll { it.isBusy() } }.flatten()
def jobsFound = []
busyExecutors.each { e ->
job = e.getCurrentExecutable()
if ( e.getElapsedTime() > max_run_time_usec ) {
logger.info("${job.getUrl()} timed out - killing it")
job.setDescription("Timed out") // <----- trouble!
e.doStop()
}
}
But I'm getting this error
hudson.security.AccessDeniedException2: anonymous is missing the Run/Update permission
Really don't want to grant anonymous this permission to make this work.
Any ideas on how to get the scripts in init.groovy.d to run with say administrator permissions or as another user that I can then grant the permissions I need?
You can elevate your privileges in the script by adding the following:
hudson.security.ACL.impersonate(hudson.security.ACL.SYSTEM)
That would allow you to set the build description.
How can I check whether the build is triiggered by timer or by the user.
I have a build which is scheduled to run and there times I run it manually also. Can I get who started the run from a variable or something in the same run.
We can use "BUILD_CAUSE" variable for getting the information about who initiated the run
There is Jenkins plugin which allows you to identify, among others, how the build was triggered.
In the features section of the link above you can see:
Build Cause Run
the build step depending on the cause of the build e.g. triggered by timer, user, scm-change,...
If you have a shared pipeline library, create a file startedByTimer.groovy:
#NonCPS
def call() {
boolean startedByTimer = false
def buildCauses = currentBuild.rawBuild.getCauses()
for (buildCause in buildCauses) {
if ("${buildCause}".contains("hudson.triggers.TimerTrigger\$TimerTriggerCause")) {
startedByTimer = true
}
}
return startedByTimer
}
As #Bachu already answered build cause is available in ${BUILD_CAUSE} environment variable.
Moreover for each trigger type is corresponding environment variable which can be interpreted as boolean.
For builds triggered by timer it is ${BUILD_CAUSE_TIMERTRIGGER} which for builds triggered by timer will be set to true.
Another solution would be to use the user build vars plugin:
def buildUser
wrap([$class: 'BuildUser']) {
buildUser = env.BUILD_USER
}
buildUser will contain a user name when the pipeline is triggered manually, and null otherwise.
If using pipeline 2.0, you can use:
if(manager.logContains("Started by timer")){
echo "This build was triggered by a timer."
}
You can check those information in Jenkins Console output.
Look at the console Output
The first line will be:
Started by user xxx (if a human started it)
or
Started by an SCM change (if a change in version control triggered it)
or
Started by timer (if it was scheduled)
When you set up a Jenkins job various test result plugins will show regressions if the latest build is worse than the previous one.
We have many jobs for many projects on our Jenkins and we wanted to avoid having a 'job per branch' set up. So currently we are using a parameterized build to build eg different development branches using a single job.
But that means when I build a new branch any regressions are measured against the previous build, which may be for a different branch. What I really want is to measure regressions in a feature branch against the latest build of the master branch.
I thought we should probably set up a separate 'master' build alongside the parameterized 'branches' build. But I still can't see how I would compare results between jobs. Is there any plugin that can help?
UPDATE
I have started experimenting in the Script Console to see if I could write a post-build script... I have managed to get the latest build of master branch in my parameterized job... I can't work out how to get to the test results from the build object though.
The data I need is available in JSON at
http://<jenkins server>/job/<job name>/<build number>/testReport/api/json?pretty=true
...if I could just get at this data structure it would be great!
I tried using JsonSlurper to load the json via HTTP but I get 403, I guess because my script has no auth session.
I guess I could load the xml test results from disk and parse them in my script, it just seems a bit stupid when Jenkins has already done this.
I eventually managed to achieve everything I wanted, using a Groovy script in the Groovy Postbuild Plugin
I did a lot of exploring using the script console http://<jenkins>/script and also the Jenkins API class docs are handy.
Everyone's use is going to be a bit different as you have to dig down into the build plugins to get the info you need, but here's some bits of my code which may help.
First get the build you want:
def getProject(projectName) {
// in a postbuild action use `manager.hudson`
// in the script web console use `Jenkins.instance`
def project = manager.hudson.getItemByFullName(projectName)
if (!project) {
throw new RuntimeException("Project not found: $projectName")
}
project
}
// CloudBees folder plugin is supported, you can use natural paths:
project = getProject('MyFolder/TestJob')
build = project.getLastCompletedBuild()
The main test results (jUnit etc) seem to be available directly on the build as:
result = build.getTestResultAction()
// eg
failedTestNames = result.getFailedTests().collect{ test ->
test.getFullName()
}
To get the more specialised results from eg Violations plugin or Cobertura code coverage you have to look for a specific build action.
// have a look what's available:
build.getActions()
You'll see a list of stuff like:
[hudson.plugins.git.GitTagAction#2b4b8a1c,
hudson.scm.SCMRevisionState$None#40d6dce2,
hudson.tasks.junit.TestResultAction#39c99826,
jenkins.plugins.show_build_parameters.ShowParametersBuildAction#4291d1a5]
These are instances, the part in front of the # sign is the class name so I used that to make this method for getting a specific action:
def final VIOLATIONS_ACTION = hudson.plugins.violations.ViolationsBuildAction
def final COVERAGE_ACTION = hudson.plugins.cobertura.CoberturaBuildAction
def getAction(build, actionCls) {
def action = build.getActions().findResult { act ->
actionCls.isInstance(act) ? act : null
}
if (!action) {
throw new RuntimeException("Action not found in ${build.getFullDisplayName()}: ${actionCls.getSimpleName()}")
}
action
}
violations = getAction(build, VIOLATIONS_ACTION)
// you have to explore a bit more to find what you're interested in:
pylint_count = violations?.getReport()?.getViolations()?."pylint"
coverage = getAction(build, COVERAGE_ACTION)?.getResults()
// if you println it looks like a map but it's really an Enum of Ratio objects
// convert to something nicer to work with:
coverage_map = coverage.collectEntries { key, val -> [key.name(), val.getPercentageFloat()] }
With these building blocks I was able to put together a post-build script which compared the results for two 'unrelated' build jobs, then using the Groovy Postbuild plugin's helper methods to set the build status.
Hope this helps someone else.