[!TIP|label:references:]

  • currentBuild.class == Class RunWrapper
  • currentBuild.rawBuild.class == jenkins.model.Jenkins.instance.getItemByFullName(String).getBuildByNumber(int).class == Class WorkflowRun

[!TIP|style:flat|label:get builds|icon:fa fa-bullhorn]

  • inherited from hudson.model.Job :
    • WorkflowJob.getBuilds() : [marslo/sandbox #2, marslo/sandbox #1]
    • WorkflowJob.getBuildsAsMap() : [2:marslo/sandbox #2, 1:marslo/sandbox #1]
    • WorkflowJob.getLastFailedBuild() : get last failed build
    • WorkflowJob.getLastStableBuild() : get last stable build
    • WorkflowJob.getLastSuccessfulBuild() : get last successful build
    • WorkflowJob.getLastUnstableBuild() : get last unstalbe build
    • WorkflowJob.getLastUnsuccessfulBuild() : get last unsuccessful build
    • WorkflowJob.getLastCompletedBuild() : get last completed only build
  • from org.jenkinsci.plugins.workflow.job.WorkflowJob :
    • WorkflowJob.getBuildByNumber(int n) : get specific build by number
    • WorkflowJob.getFirstBuild() : get first build
    • WorkflowJob.getLastBuild() : get last build
    • WorkflowJob.getNearestBuild(int n) : gets the youngest build #m that satisfies n<=m.
    • WorkflowJob.getNearestOldBuild(int n) : gets the latest build #m that satisfies m<=n.

get build

get WorkflowRun by build number

final String JOB_NAME  = 'marslo/sandbox'
final int BUILD_NUMBER = 6458

def build = jenkins.model.Jenkins.instance
                   .getItemByFullName( JOB_NAME )
                   .getBuildByNumber( BUILD_NUMBER )

get build details

import org.jenkinsci.plugins.workflow.job.WorkflowJob
import hudson.util.RunList

WorkflowJob job = jenkins.model.Jenkins.instance.getItemByFullName( '/marslo/sandbox/test' )
RunList runList = job.getBuilds()

println """
                  all builds : ${job.getBuildsAsMap().collect{ k, v -> k }}
                build exists : ${job.getBuilds().collect { it.id }.contains( 25.toString() )}

             completedOnly() : ${runList.completedOnly().collect{ it.id }}
               failureOnly() : ${runList.failureOnly().collect{ it.id }}
              getLastBuild() : ${runList.getLastBuild()}
             getFirstBuild() : ${runList.getFirstBuild()}

     getLastCompletedBuild() : ${job.getLastCompletedBuild()}
        getLastFailedBuild() : ${job.getLastFailedBuild()}
        getLastStableBuild() : ${job.getLastStableBuild()}
    getLastSuccessfulBuild() : ${job.getLastSuccessfulBuild()}
      getLastUnstableBuild() : ${job.getLastUnstableBuild()}
  getLastUnsuccessfulBuild() : ${job.getLastUnsuccessfulBuild()}
                 isInQueue() : ${job.isInQueue()}
           getActions.causes : ${runList.collect{ run -> run.id + ': ' + run.getActions( jenkins.model.InterruptedBuildAction.class ).causes.flatten().collect{ it.class.simpleName } }}
"""
  • result:

                      all builds : [42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
                    build exists : true
    
                 completedOnly() : [42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
                   failureOnly() : [42, 41, 40, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
                  getLastBuild() : marslo/abort #42
                 getFirstBuild() : marslo/abort #1
    
         getLastCompletedBuild() : marslo/abort #42
            getLastFailedBuild() : marslo/abort #32
            getLastStableBuild() : marslo/abort #39
        getLastSuccessfulBuild() : marslo/abort #39
          getLastUnstableBuild() : null
      getLastUnsuccessfulBuild() : marslo/abort #42
                     isInQueue() : false
               getActions.causes : [42: [ExceededTimeout], 41: [ExceededTimeout], 40: [ExceededTimeout], 36: [ExceededTimeout], 35: [ExceededTimeout], 34: [ExceededTimeout], 33: [ExceededTimeout], 32: [], 31: [], 30: [ExceededTimeout], 29: [], 28: [], 27: [], 26: [], 24: [], 23: [], 22: [ExceededTimeout], 21: [], 20: [], 19: [ExceededTimeout], 18: [ExceededTimeout], 17: [], 16: [], 15: [], 14: [], 13: [], 12: [], 11: [], 10: [ExceededTimeout], 9: [ExceededTimeout], 8: [UserInterruption], 7: [ExceededTimeout], 6: [UserInterruption], 5: [UserInterruption], 4: [ExceededTimeout], 3: [ExceededTimeout], 2: [UserInterruption], 1: [ExceededTimeout]]
    

get culprits

println build.getCulprits()

setup next build number

jenkins.model.Jenkins.instance
       .getItemByFullName( '/path/to/job' )
       .updateNextBuildNumber( n )

build cause

trigger cause

import org.jenkinsci.plugins.parameterizedscheduler.ParameterizedTimerTriggerCause
import hudson.model.Cause.UpstreamCause
import org.jenkinsci.plugins.workflow.cps.replay.ReplayCause
import com.sonyericsson.rebuild.RebuildCause
import hudson.model.Cause.UserIdCause
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritCause
import org.jenkinsci.plugins.workflow.job.WorkflowJob
import hudson.util.RunList

WorkflowJob job = jenkins.model.Jenkins.instance.getItemByFullName( '/path/to/job' )
RunList builds  = job.getBuilds()

builds.each { build ->
  println build.number
  println build.getCauses()
  println build.getCauses().collect { it.getClass().getCanonicalName() }
  println "byUser        : ${build.getCause( UserIdCause.class  ) && true        }"
  println "byRebuild     : ${build.getCause( RebuildCause.class ) && true        }"
  println "byReplay      : ${build.getCause( ReplayCause.class  ) && true        }"
  println "byUpstream    : ${build.getCause( Cause.UpstreamCause.class ) && true }"
  println "byGerritCause : ${build.getCause( GerritCause.class ) && true         }"
}

"DONE"

get build cause

List<String> projects = [ 'project-1', 'project-2', 'project-n' ]
jeniins.model.jenkins.model.Jenkins.instance.getAllItems( Job.class ).findAll {
  projects.any { p -> it.fullName.startsWith(p) }
}.each {
  println it.name + "\t -> " + it.fullName + "\t ~> " +
          ( it.getLastBuild()?.getCauses()?.collect { it.getClass().getCanonicalName() }?.join(', ') ?: 'no build' )
}

"DONE"
  • result

    user-trigger -> marslo/user-trigger ~> hudson.model.Cause.UserIdCause
    sandbox      -> marslo/sandbox      ~> hudson.model.Cause.UserIdCause, org.jenkinsci.plugins.workflow.cps.replay.ReplayCause
    whitebox     -> marslo/whitebox     ~> org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause, hudson.model.Cause.UserIdCause, com.sonyericsson.rebuild.RebuildCause
    replay       -> marslo/reply        ~> org.jenkinsci.plugins.workflow.cps.replay.ReplayCause, hudson.model.Cause.UserIdCause, com.sonyericsson.rebuild.RebuildCause
    no-build     -> marslo/no-build     ~> no build
    rebuild      -> marslo/rebuild      ~> org.jenkinsci.plugins.workflow.cps.replay.ReplayCause, hudson.model.Cause.UserIdCause, com.sonyericsson.rebuild.RebuildCause
    upstream     -> marslo/upstream     ~> org.jenkinsci.plugins.workflow.support.steps.build.BuildUpstreamCause
    Result: DONE
    
  • or

    List<String> projects = [ 'project-1', 'project-2', 'project-n' ]
    jenkins.model.Jenkins.instance.getAllItems( Job.class ).findAll {
      projects.any { p -> it.fullName.startsWith(p) }
    }.each {
      println it.name + "\t -> " +
              it.fullName + " :\n\t\t" +
              (
                it.getLastBuild()?.getCauses()?.collect { it.getClass().getCanonicalName() }.join('\n\t\t') ?: 'no-builds'
              )
    }
    "DONE"
    

get builds cause (within 24 hours)

import hudson.model.Job
import hudson.model.Run
import java.util.Calendar
import jenkins.model.Jenkins

final List<String> PROJECTS = [ 'project-1' ]
final long BENCH_MARK       = 1*24*60*60*1000
final Calendar RIGHT_NOW    = Calendar.getInstance()

jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
  PROJECTS.any { job.fullName.startsWith(it) }
}.collectEntries { Job job ->
  [
    ( job.fullName ) : job.builds.findAll { Run run ->
                         ( RIGHT_NOW.getTimeInMillis() - run.getStartTimeInMillis() ) <= BENCH_MARK
                       }.collectEntries { Run run ->
                         [
                           (run.id) : run.getCauses()?.collect { it.getClass().getSimpleName() }?.join(' -> ')
                         ]
                       }

  ]
}.collectEntries { k, v ->
  [
    (k) : v.groupBy( {it.value} )
           .collectEntries{ x, y -> [ (x) : y.keySet() ] }
  ]
}.each{ k, v ->
  println ">> ${k} : ${v.values().flatten().size()}"
  println v.collect { "\t${it.key} : \n\t\t${it.value.join('\n\t\t')}" }.join('\n')
}

"DONE"
  • result:
    >> project-1 : 4
       BuildUpstreamCause :
         122
         123
       BuildUpstreamCause ->  UserIdCause ->  RebuildCause :
         124
       UserIdCause ->  ReplayCause :
         125
    Result: DONE
    

details on Cause.UserIdCause

jenkins.model.Jenkins.instance
       .getItemByFullName( JOB_NAME )
       .getBuildByNumber( JOB_NUMBER )
       .getActions( hudson.model.CauseAction.class )
       .causes
       .flatten()
       .each { c ->
          println c.userId
          println c.getUserName()
          println c.getShortDescription()
          println c.getUserUrl()
       }

GerritCause

import org.jenkinsci.plugins.workflow.job.WorkflowJob
import hudson.util.RunList
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritCause
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritUserCause
import com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.GerritManualCause
import com.sonymobile.tools.gerrit.gerritevents.dto.events.CommentAdded
import com.sonymobile.tools.gerrit.gerritevents.dto.events.RefUpdated
import com.sonymobile.tools.gerrit.gerritevents.dto.events.PatchsetCreated

WorkflowJob job = jenkins.model.Jenkins.instance.getItemByFullName( '/path/to/pipeline' )
RunList builds  = job.getBuilds()

builds.each { build ->
  if ( build.getCause( GerritCause.class ) && true ) {
    println build.number
    println "context           : ${build.getCause( GerritCause.class ).getContext()}"
    println "  build number    : ${build.getCause( GerritCause.class ).getContext().getThisBuild().getBuildNumber().toString()}"
    println "  project name    : ${build.getCause( GerritCause.class ).getContext().getThisBuild().getProjectId()}"
    println "description       : ${build.getCause( GerritCause.class ).getShortDescription()}"
    println "url               : ${build.getCause( GerritCause.class ).getUrl()}"
    println "events            : ${build.getCause( GerritCause.class ).getEvent().getClass()}"
    println "  byCommentAdded  : " +
            ( build.getCause(GerritCause.class).getEvent() instanceof CommentAdded ).toString()
    println "  byRefUpdated    : " +
            ( build.getCause(GerritCause.class).getEvent() instanceof RefUpdated ).toString()
    println "  byPathUpdated   : " +
            ( build.getCause(GerritCause.class).getEvent() instanceof PatchsetCreated).toString()
    println "  server hostname : " +
            build.getCause( GerritCause.class ).getEvent().getProvider().host
    println "  server port     : " +
            build.getCause( GerritCause.class ).getEvent().getProvider().port
    println "  server version  : " +
            build.getCause( GerritCause.class ).getEvent().getProvider().version
    println '.'*10
    println "userCause?        : ${build.getCause( GerritUserCause.class ) && true}"
    if (build.getCause( GerritUserCause.class ) && true) {
      println "  comments        : " +
              build.getCause( GerritUserCause.class )?.getEvent().comment
      println "  approvals       : " +
              build.getCause( GerritUserCause.class )?.getEvent().approvals
      println "  change          : " +
              build.getCause( GerritUserCause.class )?.getEvent().change
      println "  usernmae        : " +
              build.getCause( GerritUserCause.class )?.getUserName()
      println "  description     : " +
              build.getCause( GerritUserCause.class )?.getShortGerritDescription()
      println "  changer account : " +
              build.getCause( GerritUserCause.class )?.getEvent().getAccount().name
      println "  changer email   : " +
              build.getCause( GerritUserCause.class )?.getEvent().getAccount().email
    }
    println '.'*10
    println "manualCause?      : ${build.getCause( GerritManualCause.class ) && true}"
    println '~'*30
  }
}

"DONE"
Result
  10121
  context           : com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.data.TriggerContext@6fbdc2a5
    build number    : 10121
    project name    : /path/to/project
  description       : Retriggered by user marslo for Gerrit: https://my.gerrit.com/c/my/project/+/72667
  url               : https://my.gerrit.com/c/my/project/+/72667
  events            : class com.sonymobile.tools.gerrit.gerritevents.dto.events.CommentAdded
    byCommentAdded  : true
    byRefUpdated    : false
    byPathUpdated   : false
    server hostname : my.gerrit.com
    server port     : 29418
    server version  : 2.16.27-RP-1.10.1.1
  ..........
  userCause?        : true
    comments        : Patch Set 104: Code-Review+2
    approvals       : [Approval: Verified 0, Approval: Code-Review 2]
    change          : Change-Id for #72667: I68e47b094e142a63cec7c698094320d79f5350f8
    usernmae        : marslo
    description     : Retriggered by user marslo for Gerrit: https://my.gerrit.com/c/my/project/+/72667
    changer account : Marslo Jiao
    changer email   : marslo@gmail.com
  ..........
  manualCause?      : false
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  10120
  context           : com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.data.TriggerContext@26ab2c12
    build number    : 10120
    project name    : /path/to/project
  description       : Triggered by Gerrit: https://my.gerrit.com/c/my/project/+/123069
  url               : https://my.gerrit.com/c/my/project/+/123069
  events            : class com.sonymobile.tools.gerrit.gerritevents.dto.events.CommentAdded
    byCommentAdded  : true
    byRefUpdated    : false
    byPathUpdated   : false
    server hostname : my.gerrit.com
    server port     : 29418
    server version  : 2.16.27-RP-1.10.1.1
  ..........
  userCause?        : false
  ..........
  manualCause?      : false
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

abort cause

jenkins.model.Jenkins.instance
       .getItemByFullName( JOB_NAME )
       .getBuildByNumber( BUILD_NUMBER )
       .getActions( jenkins.model.InterruptedBuildAction.class )
       .causes
       .flatten()

builds aborted by timeout

import org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution

jenkins.model.Jenkins.instance
       .getItemByFullName( JOB_NAME )
       .getBuildByNumber( BUILD_NUMBER )
       .getActions( jenkins.model.InterruptedBuildAction.class )
       .causes
       .flatten()
       .any { it instanceof TimeoutStepExecution.ExceededTimeout }
  • or

    import org.jenkinsci.plugins.workflow.steps.TimeoutStepExecution
    import org.jenkinsci.plugins.workflow.job.WorkflowJob
    
    WorkflowJob job = jenkins.model.Jenkins.instance.getItemByFullName( JOB_NAME )
    job.builds.findAll { Run run ->
      BUILD_NUMBER.toString() == run.id
    }.collect { Run run -> run.getActions( jenkins.model.InterruptedBuildAction.class )
                              .causes
                              .flatten()
                              .any { it instanceof TimeoutStepExecution.ExceededTimeout }
    }
    

abort by userId CauseOfInterruption.UserInterruption

import org.jenkinsci.plugins.workflow.support.steps.build.BuildTriggerCancelledCause
import jenkins.model.CauseOfInterruption

jenkins.model.Jenkins.instance
       .getItemByFullName( JOB_NAME )
       .getBuildByNumber( BUILD_NUMBER )
       .getActions( jenkins.model.InterruptedBuildAction.class )
       .causes
       .flatten()
       .any { c ->
                  [ CauseOfInterruption.UserInterruption, BuildTriggerCancelledCause ].any {
                    it.isAssignableFrom( c.getClass() )
                  }
            }
  • get user details

    import jenkins.model.CauseOfInterruption
    
    jenkins.model.Jenkins.instance
           .getItemByFullName( '/marslo/sandbox/abort' )
           .getBuildByNumber(159)
           .getActions( jenkins.model.InterruptedBuildAction.class )
           .causes
           .flatten()
           .findAll { c -> [ CauseOfInterruption.UserInterruption ].any { it.isAssignableFrom(c.getClass()) } }
           .each { c ->
             User user = c.user
             println c.getShortDescription()
             println "${user.fullName} : ${user.absoluteUrl}"
             currentBuild.description = 'ABORTED by user : <a href="${user.absoluteUrl}">' +
                                        "${user.displayName} [ ${user.id} ]</a>"
             currentBuild.result = 'NOT_BUILT'
           }
    

get all abort causes

import org.jenkinsci.plugins.workflow.job.WorkflowJob

WorkflowJob job = jenkins.model.Jenkins.instance.getItemByFullName( JOB_NAME )
job.builds.findAll { Run run ->
  run.getActions( jenkins.model.InterruptedBuildAction.class ).causes
}.collect{ Run run ->
  List c = run.getActions( jenkins.model.InterruptedBuildAction.class ).causes
  "#${run.id} : ${c.flatten().collect{ it.class.simpleName }.first()}"
}.join('\n')
  • result:
    #42 : ExceededTimeout
    #41 : ExceededTimeout
    #40 : ExceededTimeout
    #36 : ExceededTimeout
    #35 : ExceededTimeout
    #34 : ExceededTimeout
    #33 : ExceededTimeout
    #30 : ExceededTimeout
    #22 : ExceededTimeout
    #19 : ExceededTimeout
    #18 : ExceededTimeout
    #10 : ExceededTimeout
    #9 : ExceededTimeout
    #8 : UserInterruption
    #7 : ExceededTimeout
    #6 : UserInterruption
    #5 : UserInterruption
    #4 : ExceededTimeout
    #3 : ExceededTimeout
    #2 : UserInterruption
    #1 : ExceededTimeout
    

build log

get console output

jenkins.model.Jenkins.instance
       .getItemByFullName( JOB_NAME )
       .getBuildByNumber( BUILD_NUMBER )
       .logFile
       .text

check whether if log kept

import org.jenkinsci.plugins.workflow.job.WorkflowJob

workflowJob job = Jenkins.getInstance().getItemByFullName( '/sandbox/job' )
job.builds.findAll { Run run -> run.isKeepLog() }
          .collect { Run run -> run.id }
  • list build number and start timestamp

    List<String> projects = [ 'project-1', 'project-2', 'project-n' ]
    
    jenkins.model.Jenkins.instance
           .getAllItems( Job.class )
           .findAll { projects.any { p -> it.fullName.startsWith(p) } }
           .findAll { job -> job.builds.any { Run run -> run.isKeepLog() } }
           .collectEntries {[
              ( it ) : it.builds.findAll { Run run -> run.isKeepLog() }
           ]}
           .each { k, v ->
             println '>> ' + k.fullName.padRight(35) + ': \n\t - ' +
               [
                 v.id,
                 v.collect{ new Date(it.startTimeInMillis).format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") }
                 // v.collect { groovy.time.TimeCategory.minus( new Date(), it.getTime() ) }
                 // v.collect { it.getTime() }
               ].transpose()
                .collectEntries()
                .collect{ "${it.key.padRight(5)} : ${it.value}" }
                .join('\n\t - ')
           }
    
    "DONE"
    
    -- result --
    v.collect{ new Date(it.startTimeInMillis).format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") } :
    >> marslo/sample
       - 454   : 2024-02-13T10:16:31.563Z
       - 36    : 2023-10-18T03:39:19.496Z
       - 32    : 2023-10-18T03:39:05.577Z
    
    v.collect { groovy.time.TimeCategory.minus( new Date(), it.getTime() ) } :
    >> marslo/sample
       - 454   : 1 days, 7 hours, 27 minutes, 13.394 seconds
       - 36    : 119 days, 15 hours, 4 minutes, 25.450 seconds
       - 32    : 119 days, 15 hours, 4 minutes, 39.369 seconds
    
    v.collect { it.getTime() }
    >> marslo/sample                          :
       - 454   : Tue Feb 13 10:16:31 PST 2024
       - 36    : Wed Oct 18 03:39:19 PDT 2023
       - 32    : Wed Oct 18 03:39:05 PDT 2023
    
  • or

    List<String> projects = [ 'project-1', 'project-2', 'project-n' ]
    
    jenkins.model.Jenkins.instance
           .getAllItems( Job.class )
           .findAll { projects.any { p -> it.fullName.startsWith(p) } }
           .collectEntries {[
              ( it.fullName.toString() ) : it.builds.findAll { Run run -> run.isKeepLog() }
                                                    .collect { Run run -> run.id }
           ]}
           .findAll { it.value }
           .each { k, v ->
              println "~~> ${k}:\n\t#${v.join('\n\t#')}"
           }
    "DONE"
    

check last N logs

[!NOTE|label:original log:]

21:19:37  Running on Jenkins in /var/jenkins_home/workspace/marslo/desc
[Pipeline] {
[Pipeline] sh
21:19:37  + sleep 10
21:19:47
21:19:47  real  0m10.003s
21:19:47  user  0m0.000s
21:19:47  sys   0m0.003s
[Pipeline] }
[Pipeline] // node
[Pipeline] }
21:19:47
[Pipeline] // ansiColor
[Pipeline] }
[Pipeline] // timestamps
[Pipeline] End of Pipeline
Finished: SUCCESS
jenkins.model.Jenkins.instance.getItemByFullName( <JOB_PATTERN> )
                .getBuildByNumber( <BUILD_NUMBER> )
                .getLog( 30 )
                .find{ it.contains('real\t') }
                .split( '\t' )
                .last()
                .trim()

// Result: 0m10.003s

build parameters

get build parameters

def job = Jenkins.getInstance().getItemByFullName( '/marlso/sandbox' )
job.builds.each { Run run ->
  String parameters = run?.getAction(ParametersAction.class)?.parameters?.collectEntries {
    [ it.name, it.value ]
  }.collect { k, v -> "\t\t${k}\t: ${v}" }
   .join('\n')
  println "#${run.id}: ${parameters}"
}

"DONE"
  • result:

    #7:     id  : marslo
        gender  : female
    #6:     id  : marslo
        gender  : female
    #5:     id  : marslo
        gender  : female
    #4:     id  : marslo
        gender  : female
    #3:     name  : marslo
        gender  : female
    #2:     name  : marslo
        gender  : female
    #1:
    
  • or via action instanceof ParametersAction

    def job = Jenkins.getInstance().getItemByFullName( 'others-tests/sandbox' )
    job.getBuilds().each { Run build ->
      String parameters = build?.actions.find{ it instanceof ParametersAction }?.parameters?.collectEntries {
        [ it.name, it.value ]
      }.collect { k, v -> "\t\t${k}\t: ${v}" }
       .join('\n')
      println "#${build.getId()}: ${parameters}"
    }
    
  • or by using Job Parameter Summary Plugin

    import hudson.model.*
    
    for( item in Hudson.instance.items ) {
      prop = item.getProperty( ParametersDefinitionProperty.class )
      if( prop ) {
        println( "--- Parameters for " + item.name + " ---" )
        for( param in prop.getParameterDefinitions() ) {
          try {
            println( param.name + " " + param.defaultValue )
          }
          catch( Exception e ) {
            println( param.name )
          }
        }
        println()
      }
    }
    
    • result:

      --- Parameters for GRA-00-UploadIntoClearCase ---
      ONCSDAP1_USER jdoe
      ONCSDAP1_PASSWORD
      GRA_ZIP_NAME GRA_Delivery_r.2.0.0_28-5_impl.zip
      GRA_RELEASE n.28
      
      --- Parameters for IN8-03-DeployXnetWebAppToRecette ---
      STOP_START_AS
      

get builds parameters

[!TIP] running following snippet in Jenkinsfile

params.each { param ->
  println " ~~> '${param.key.trim()}' -> '${param.value}' "
}
  • or : Jenkins : Parameterized System Groovy script
    def parameters = currentBuild.rawBuild?.getActions(ParametersAction.class)?.parameters
    parameters.each {
      println " ~~> ${it.name} -> ${it.value} -> ${it.description ?: ''} "
      println "-" * 20
    }
    
    • or
      def parameters = currentBuild.rawBuild?.actions.find{ it instanceof ParametersAction }?.parameters
      

get wanted parameter values in builds

final String PARAM = 'id'
Map params = [:]

def job = Jenkins.getInstance().getItemByFullName( 'others-tests/sandbox' )
job.getBuilds().each { Run build ->
  params."${build.getId()}" = build?.getActions(ParametersAction.class)?.parameters?.collectEntries {
                                [ it.name , it.value ]
                              }
}

println params.collect { k , v ->
  "build #${k} ~~> ${v ? "${PARAM} : ${v.getOrDefault(PARAM, "No '${PARAM}' found")}" : 'No Params Found'}"
}.join('\n')
  • result:
    build #7 ~~> id : marslo
    build #6 ~~> id : marslo
    build #5 ~~> id : marslo
    build #4 ~~> id : marslo
    build #3 ~~> id : No 'id' found
    build #2 ~~> id : No 'id' found
    build #1 ~~> No Params Found
    

get only String type parameters

Map params = build?.getAction( ParametersAction.class )
                   .parameters?.findAll{ it instanceof StringParameterValue }?.dump()
  • or

    Map params = build?.getAction( ParametersAction.class )
                       .parameters?.findAll{ it instanceof StringParameterValue }?.collectEntries {
                         [ it.name, it.value ]
                       }
    
  • or

    Map params = build?.actions
                       .find{ it instanceof ParametersAction }?.parameters?.findAll{ it instanceof StringParameterValue }?.dump()
    

retrieving parameters and triggering another build

import hudson.model.*
import hudson.AbortException
import hudson.console.HyperlinkNote
import java.util.concurrent.CancellationException

// Retrieve parameters of the current build
def foo = build.buildVariableResolver.resolve("FOO")
println "FOO=$foo"

// Start another job
def job = Hudson.instance.getJob('MyJobName')
def anotherBuild
try {
    def params = [
      new StringParameterValue('FOO', foo),
    ]
    def future = job.scheduleBuild2(0, new Cause.UpstreamCause(build), new ParametersAction(params))
    println "Waiting for the completion of " + HyperlinkNote.encodeTo('/' + job.url, job.fullDisplayName)
    anotherBuild = future.get()
} catch (CancellationException x) {
    throw new AbortException("${job.fullDisplayName} aborted.")
}
println HyperlinkNote.encodeTo('/' + anotherBuild.url, anotherBuild.fullDisplayName) + " completed. Result was " + anotherBuild.result

// Check that it succeeded
build.result = anotherBuild.result
if (anotherBuild.result != Result.SUCCESS && anotherBuild.result != Result.UNSTABLE) {
    // We abort this build right here and now.
    throw new AbortException("${anotherBuild.fullDisplayName} failed.")
}

// Do something with the output.
// On the contrary to Parameterized Trigger Plugin, you may now do something from that other build instance.
// Like the parsing the build log (see http://javadoc.jenkins-ci.org/hudson/model/FreeStyleBuild.html )
// You probably may also wish to update the current job's environment.
build.addAction(new ParametersAction(new StringParameterValue('BAR', '3')))

get queue jobs parameters

[!NOTE|label:refernece:]

def q = jenkins.model.Jenkins.instance.queue
q.items.each {
  println("${it.task.name}:")
  println("Parameters: ${it.params}")
}

build changesets

code clone via DSL

checkout([
  $class: 'GitSCM',
  branches: [[ name: 'refs/heads/sandbox' ]],
  browser: [
    $class: 'GitWeb',
    repoUrl: 'https://my.gerrit.com/path/to/repo'
  ],
  userRemoteConfigs: [[
      credentialsId: 'SSHCredential',
      url: 'git://my.gerrit.com/path/to/repo.git'
  ]]
])

jenkins.model.Jenkins.instance
       .getItemByFullName( '/marslo/up' )
       .getBuildByNumber( 195 )
       .changeSets
       .each {
         it.items.each { i ->
           println """
             \n-----------------------------\n
                      paths : ${i.paths}
              parent commit : ${i.parentCommit}
                   commitId : ${i.commitId} : ${i.revision}
                     author : ${i.author}
                 authorName : ${i.authorName}
                authorEmail : ${i.authorEmail}
              committerTime : ${i.committerTime}
                    message : ${i.msg}
                    repoUrl : ${it.browser?.repoUrl ?: ''}
             affected files :
               \t\t${i.affectedFiles.collect{ f -> f.editType.name + ' : ' + f.path }.join('\n\t\t\t\t')}
           """
         }
       }
  • get changeSets to List<Map<String, String>>:

    def getChangeSets( String name, int nubmer ) {
      jenkins.model.Jenkins.instance
             .getItemByFullName( name )
             .getBuildByNumber( number )
             .changeSets
    }
    
    def changeSetsEntry( def changeSets ) {
      List<Map<String, String>> changeSetsEntry = []
      changeSets.collect { it.items.collect { i ->
        changeSetsEntry += [
          'parentCommit'  : i.parentCommit      ,
          'commitId'      : i.commitId          ,
          'revision'      : i.revision          ,
          'author'        : i.author.toString() ,
          'authorName'    : i.authorName        ,
          'authorEmail'   : i.authorEmail       ,
          'committerTime' : i.committerTime     ,
          'msgAnnotated'  : i.msg               ,
          'affectedFiles' : i.affectedFiles.collect{ f -> ['editType' : f.editType.name, 'path' : f.path ] }
        ]
      }}
      return changeSetsEntry
    }
    
    // testing
    List<Map<String, String>> totalChangeSets = []
    totalChangeSets += changeSetsEntry( getChangeSets('/path/to/pipeline', 123) )
    totalChangeSets += changeSetsEntry( getChangeSets('/path/to/pipeline', 456) )
    totalChangeSets.each {
      println """
        it.getClass()
        it.author
        it.commitId
      """
    }
    

SCM info

jenkins.model.Jenkins.instance
       .getItemByFullName( '/path/to/pipeline' )
       .getBuildByNumber( n )
       .SCMs
       .each {
         println """
           .......................
               repoUrl : ${it.userRemoteConfigs.url.join(',')}
               remotes : ${it.userRemoteConfigs.name.join(',')}
              branches : ${it.branches.join(',')}
           .......................
         """
       }

get repo url

references:

def job = jenkins.model.Jenkins.instance
                 .getItemByFullName( '/path/to/pipeline' )
                 .getBuildByNumber( n )

job.changeSets
   .each {
     it.items.each { i ->
       println """
         \n-----------------------------\n
          repoUrl : ${ it.browser.repoUrl }
          repoUrl : ${ job.SCMs.collect { s -> s.browser?.repoUrl }.findAll().join() }
              url : ${ job.SCMs.collect { s -> s.browser?.url }.findAll().join() }
      ormalizeUrl : ${ job.SCMs.collect { s -> s.browser?.normalizeUrl } }
      absoluteUrl : ${ job.SCMs.collect { s -> s.browser?.getChangeSetLink(i) }.findAll().join() }
         commitId : ${i.commitId} : ${i.revision}
       """
     }
   }
  • result:

    -----------------------------
    
              repoUrl : https://my.gerrit.com/admin/repos/sandbox
              repoUrl : https://my.gerrit.com/admin/repos/sandbox
                  url : https://my.gerrit.com/admin/repos/sandbox
          ormalizeUrl : [null, false]
          absoluteUrl : https://my.gerrit.com/admin/repos/sandbox?a=commit&h=095e4470964ee8ca6ab50ceea7acf88094dc08d4
             commitId : 095e4470964ee8ca6ab50ceea7acf88094dc08d4 : 095e4470964ee8ca6ab50ceea7acf88094dc08d4
    

build time

more details can be found in

[!TIP|label:references]

final long NOW       = System.currentTimeMillis()
final int BENCH_MARK = 1*24*60*60*1000
println """
                               NOW : ${NOW}
                  NOW - BENCH_MARK : ${NOW - BENCH_MARK}
                Date(NOW).format() : ${new Date(NOW).format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")}
     Date(NOW-BENCH_MARK).format() : ${new Date(NOW - BENCH_MARK).format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")}
"""
 -- result --
                               NOW : 1619790803151
                  NOW - BENCH_MARK : 1619704403151
                Date(NOW).format() : 2021-04-30T06:53:23.151Z
     Date(NOW-BENCH_MARK).format() : 2021-04-29T06:53:23.151Z
import java.time.LocalDateTime
import java.time.LocalDate
import java.util.Calendar

final String JOB_PATTERN      = '<group>/<name>'
final LocalDateTime DATE_TIME = LocalDateTime.now()
final LocalDate DATE_TAG      = java.time.LocalDate.now()
final long CURRENT_TIME       = System.currentTimeMillis()
final long RIGHT_NOW          = Calendar.getInstance().getTimeInMillis()

println """
  >> current time :
        DATE_TIME : ${DATE_TIME}
         DATE_TAG : ${DATE_TAG}
     CURRENT_TIME : ${CURRENT_TIME}
        RIGHT_NOW : ${RIGHT_NOW}
"""

jenkins.model.Jenkins.instance.getAllItems( Job.class ).each { job ->
  if ( job.fullName.contains( JOB_PATTERN ) ) {
    def build = job.getLastBuild()
    println """
      >> ${job.getFullName()} : ${build.getId()} :
              build.getTimestampString() : ${build.getTimestampString()}
             build.getTimestampString2() : ${build.getTimestampString2()}
                         build.getTime() : ${build.getTime()}
                 build.startTimeInMillis : ${build.startTimeInMillis}
      build.startTimeInMillis.getClass() : ${build.startTimeInMillis.getClass()}
                                duration : ${groovy.time.TimeCategory.minus( new Date(), build.getTime() )}
  """
  }
}
"DONE"
get build start time
1.5.4.2.1 -- get build start time
  • or:

    final String JOB_PATTERN = '<group>/<name>'                  // keywords
    
    jenkins.model.Jenkins.instance.getAllItems( Job.class ).findAll { Job job ->
      job.fullName.contains( JOB_PATTERN )
    }.each { Job job ->
      def build = job.getLastBuild()
      println """
                     build.getTime() : ${build.getTime()}
             build.getTimeInMillis() : ${build.getTimeInMillis()}
                build.getTimestamp() : ${build.getTimestamp()}
        build.getStartTimeInMillis() : ${build.getStartTimeInMillis()}
          build.getTimestampString() : ${build.getTimestampString()}
         build.getTimestampString2() : ${build.getTimestampString2()}
      """
    }
    
  • result:

                   build.getTime() : Thu Apr 29 04:08:08 PDT 2021
           build.getTimeInMillis() : 1619694488799
              build.getTimestamp() : java.util.GregorianCalendar[time=1619694488799,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2021,MONTH=3,WEEK_OF_YEAR=18,WEEK_OF_MONTH=5,DAY_OF_MONTH=29,DAY_OF_YEAR=119,DAY_OF_WEEK=5,DAY_OF_WEEK_IN_MONTH=5,AM_PM=0,HOUR=4,HOUR_OF_DAY=4,MINUTE=8,SECOND=8,MILLISECOND=799,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]
      build.getStartTimeInMillis() : 1619694488807
        build.getTimestampString() : 2 min 1 sec
       build.getTimestampString2() : 2021-04-29T11:08:08Z
    

get started build time

[!NOTE|label:references:] get lastBuild start time

final String JOB_PATTERN = '<group>/<name>'                  // keywords

jenkins.model.Jenkins.instance.getAllItems( Job.class ).findAll { Job job ->
  job.fullName.contains( JOB_PATTERN )
}.each { Job job ->
  def build = job.getLastBuild()
  println groovy.time.TimeCategory.minus( new Date(), build.getTime() )
  println ( new Date(build.startTimeInMillis).format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") )
  println build.getTime()
}

"DONE"

-- result --
14 hours, 37 minutes, 25.397 seconds
2024-02-14T04:06:30.021Z
Wed Feb 14 04:06:30 PST 2024

sort last build

[!TIP|label:references:]

  • Date.format(String format))
  • Convert milliseconds to yyyy-MM-dd date-time format in Groovy
  • java.util.Date
  • * imarslo : groovy/time
  • details:
    java.util.Date                                           : job.getLastBuild()?.getTime()
    java.lang.Long                                           : job.getLastBuild()?.getTimeInMillis()
    Date.getTime()      : java.util.Date -> java.lang.Long   : job.getLastBuild()?.getTime()          -> job.getLastBuild()?.getTime().getTime()
    Date.format(String) : java.lang.Long -> java.lang.String : job.getLastBuild()?.getTimeInMillis()  -> new Date(job.getLastBuild()?.getTimeInMillis())?.format("yyyy-MM-dd'T'HH : mm : ss.SSS'Z'")
    
  • example:

    println "${job.getLastBuild()?.getTime()} ~> ${job.getLastBuild()?.getTime()?.getClass()}"
    println "${new Date(job.getLastBuild()?.getTimeInMillis())?.format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")} ~> ${new Date(job.getLastBuild()?.getTimeInMillis())?.format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")?.getClass()}"
    println "${job.getLastBuild()?.getTimeInMillis()} ~> ${job.getLastBuild()?.getTimeInMillis()?.getClass()}"
    println "${job.getLastBuild()?.getTime()?.getTime()} ~> ${job.getLastBuild()?.getTime()?.getTime()?.getClass()}"
    
    -- result --
    Thu Aug 11 06:28:00 PDT 2022 ~> class java.util.Date
    2022-08-11T06:28:00.220Z ~> class java.lang.String
    1660224480220 ~> class java.lang.Long
    1660224480220 ~> class java.lang.Long
    
List<String> projects = [ 'project-1', 'project-2', 'project-n' ]

jenkins.model.Jenkins.instance
       .getAllItems( Job.class )
       .findAll { projects.any { p -> it.fullName.startsWith(p) } }
       .collectEntries {[ (it.fullName) : it.getLastBuild()?.getTime() ]}
       .sort() { a, b -> b.value?.getTime() <=> a.value?.getTime() }           // Date to timeToMillis
       .each { println "${it.key.padRight(30)} ~> ${it.value}" }

"DONE"
  • result:
    marslo/dangling                ~> Fri Nov 04 02:28:14 PDT 2022
    marslo/abort                   ~> Thu Aug 11 06:28:00 PDT 2022
    marslo/dump                    ~> Thu Aug 11 01:29:39 PDT 2022
    marslo/agent                   ~> Tue Aug 09 06:19:43 PDT 2022
    marslo/docker                  ~> null
    Result: DONE
    

sort all buildable jobs

jenkins.model.Jenkins.instance
       .getAllItems( org.jenkinsci.plugins.workflow.job.WorkflowJob.class )
       .findAll { it.isBuildable() }
       .collectEntries {[ (it.fullName + ' #' + it.getLastBuild()?.id) : it.getLastBuild()?.getTime() ]}
       .sort() { a, b -> b.value?.getTime() <=> a.value?.getTime() }           // Date to timeToMillis
       .each { println "${it.key.padRight(40)} ~> ${it.value}" }

"DONE"

list builds

list all running builds

[!NOTE|label:references:]

jenkins.model.Jenkins.instance.computers
             .collect { it.executors }
             .flatten()
             .findAll { it.isBusy() }
             .collect { it.currentExecutable.parentExecutable }

list all running builds via pattern

import static groovy.json.JsonOutput.*

final String JOB_PATTERN                 = '<group>[/<name>]'                         // project/job keywords
Map<String, Map<String, String>> results = [:]

jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
  job.fullName.contains( JOB_PATTERN )
}.each { Job job ->
  results.(job.fullName) = job.builds.findAll { Run run ->
    run.isBuilding()                                                                  // or `run.result.equals(null)`
  }.collectEntries { Run run ->
    [ (run.id) : run.getAbsoluteUrl() ]
  }
}

results.findAll{ !it.value.isEmpty() }
       .each { name, builds ->
         println ">> ${name} : ${builds.size()} :" +
                 '\n\t - ' + builds.collect{ "#${it.key} : ${it.value}" }.join('\n\t - ')
       }

"DONE"

list builds running more than 24 hours via executor

final long BENCH_MARK    = 1*24*60*60*1000
final Calendar RIGHT_NOW = Calendar.getInstance()

jenkins.model.Jenkins.instance.computers
       .collect { it.executors }
       .flatten()
       .findAll { it.isBusy() }
       .collect { it.currentExecutable.parentExecutable }
       .findAll { build -> ( RIGHT_NOW.getTimeInMillis() - build.startTimeInMillis ) >= BENCH_MARK }
       .each { build ->
         println "${build.fullDisplayName} started at : ${groovy.time.TimeCategory.minus( new Date(), build.getTime() )}"
       }
  • show UserIdCause.userId:

    import org.jenkinsci.plugins.workflow.job.WorkflowRun
    
    final long BENCH_MARK    = 1*24*60*60*1000
    final Calendar RIGHT_NOW = Calendar.getInstance()
    
    List<WorkflowRun> builds = jenkins.model.Jenkins.instance.computers
           .collect { it.executors }
           .flatten()
           .findAll { it.isBusy() }
           .collect { it.currentExecutable.parentExecutable }
           .findAll { ( RIGHT_NOW.getTimeInMillis() - it.startTimeInMillis ) >= BENCH_MARK }
    
    builds.each { build ->
      println ">> ${build.fullDisplayName} :" +
              "\n\t - started " + groovy.time.TimeCategory.minus( new Date(), build.getTime() ) +
              "\n\t - triggerd by " + build.getCause( Cause.UserIdCause.class ).userId
    }
    
    "DONE"
    
  • list job which running for more than 24 hours

    [!TIP] only for lastBuild.

    /**
     * We had to write this script several times. Time to have it stored, it is a very simple approach but will serve as starting point for more refined approaches.
    **/
    
    jeniins.model.jenkins.model.Jenkins.instance.getAllItems( Job )
                    .findAll{ job -> job.isBuildable() }
                    .each { job ->
                      def build = job.getLastBuild()
                      def runningSince = groovy.time.TimeCategory.minus( new Date(), build.getTime() )
                      if ( runningSince.hours >= 24 ){
                        println job.name +"---- ${runningSince.hours} hours:${runningSince.minutes} minutes"
                      }
                    }
    "DONE"
    

list builds running more than 24 hours via pattern

[!NOTE|label:references:]

import hudson.model.Job
import hudson.model.Result
import hudson.model.Run
import java.util.Calendar
import jenkins.model.Jenkins

final Calendar RIGHT_NOW = Calendar.getInstance()
final long BENCH_MARK    = 1*24*60*60*1000
final String JOB_PATTERN = '<group>/<name>'

jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
  job.fullName.startsWith( JOB_PATTERN )
}.collect { Job job ->
  job.builds.findAll { Run run ->
    run.isBuilding() &&
    ( RIGHT_NOW.getTimeInMillis() - run.getStartTimeInMillis() ) <= BENCH_MARK
  }
}.sum()

list builds via job pattern

import hudson.model.Job
import hudson.model.Result
import hudson.model.Run
import jenkins.model.Jenkins

final String JOB_PATTERN = '<group>/<name>'

jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
  job.fullName.startsWith( JOB_PATTERN )
}.collectEntries { Job job ->
  [ ( job.fullName ) : job.builds.findAll { Run run -> run.isBuilding() }.collect { Run run -> run.id } ]
}.findAll{ it.value }
 .each { println "${it.key} : ${it.value.size()} :\n\t#${it.value.join(', #')}\n" }

"DONE"

list all builds byTimestamp ( within 24 hours )

[!TIP|label:references:]

String JOB_PATTERN      = '<group>[/<name>]'                  // keywords
final long CURRENT_TIME = System.currentTimeMillis()
final int BENCH_MARK    = 1*24*60*60*1000                     // days * hours * minutes * seconds * microseconds (1000)

jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
  job.fullName.contains( JOB_PATTERN )
}.each { Job job ->
  def history = job.getBuilds().byTimestamp( CURRENT_TIME - BENCH_MARK, CURRENT_TIME )
  if ( history ) {
    println """
      >> ${job.fullName} : ${history.size()} :
         history         : ${history.join('\n\t\t\t    ')}
    """
  }
}

get last 24 hours failure builds

import hudson.model.Job
import hudson.model.Result
import hudson.model.Run
import java.util.Calendar
import jenkins.model.Jenkins

final Calendar RIGHT_NOW = Calendar.getInstance()
final long BENCH_MARK    = 1*24*60*60*1000
final String JOB_PATTERN = '<group>'

List<List<String, String>> results = jenkins.model
                                            .Jenkins
                                            .instance
                                            .getAllItems(Job.class)
                                            .findAll { Job job -> job.fullName.contains( JOB_PATTERN ) }
                                            .collect { Job job ->
                                                        job.builds.findAll { Run run ->
                                                          run.result == Result.FAILURE &&
                                                          ( RIGHT_NOW.getTimeInMillis() - run.getStartTimeInMillis() ) <= BENCH_MARK
                                                        }
                                                      }
                                            .sum()
                                            .collect {[ "${it.fullDisplayName}", "${it.absoluteUrl}" ]}
results.add( 0, results.transpose().collect { column -> column.collect{ it.size() }.max() } )

println results.withIndex().collect { raw, idx ->
  if ( idx ) {
    raw.withIndex().collect { x, y -> "${x.padRight(results[0][y])}" }.join(' | ')
  }
}.findAll().join('\n')

-- result --
marslo » sandbox » pipeline #3549 | https://jenkins.sample.com/job/marslo/job/sandbox/job/pipeline/3549/
marslo » whitebox » builder #191  | https://jenkins.sample.com/job/marslo/job/whitebox/job/builder/191/
marslo » sample #510              | https://jenkins.sample.com/job/marslo/job/sample/510/
marslo » sample #505              | https://jenkins.sample.com/job/marslo/job/sample/505/
  • without table format

    import hudson.model.Job
    import hudson.model.Result
    import hudson.model.Run
    import java.util.Calendar
    import jenkins.model.Jenkins
    
    final Calendar RIGHT_NOW = Calendar.getInstance()
    final long BENCH_MARK    = 1*24*60*60*1000
    final String JOB_PATTERN = '<group>'
    
    jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
      job.fullName.contains( JOB_PATTERN )
    }.collect { Job job ->
      job.builds.findAll { Run run ->
        run.result == Result.FAILURE &&
        ( RIGHT_NOW.getTimeInMillis() - run.getStartTimeInMillis() ) <= BENCH_MARK
      }
    }.sum()
    

get last 24 hours failure builds via Map structure

import hudson.model.Job
import hudson.model.Result
import hudson.model.Run
import java.util.Calendar
import jenkins.model.Jenkins
import static groovy.json.JsonOutput.*

final Calendar RIGHT_NOW  = Calendar.getInstance()
final long BENCH_MARK    = 1*24*60*60*1000
final String JOB_PATTERN = '<group>'

Map results = [:]

jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
  job.fullName.contains( JOB_PATTERN )
}.each { Job job ->
  results.(job.fullName) = job.builds.findAll { Run run ->
    ! run.isBuilding() &&
    run.result == Result.FAILURE &&
    ( RIGHT_NOW.getTimeInMillis() - run.getStartTimeInMillis() ) <= BENCH_MARK
  }.collectEntries { Run run ->
    [ (run.id) : run.getAbsoluteUrl() ]
  }
}

println prettyPrint( toJson(results.findAll{ !it.value.isEmpty() }) )
  • or

    import hudson.model.Job
    import hudson.model.Result
    import hudson.model.Run
    import jenkins.model.Jenkins
    import static groovy.json.JsonOutput.*
    
    final long CURRENT_TIME  = java.util.Calendar.getInstance().getTimeInMillis()
    final long BENCH_MARK    = 1*24*60*60*1000
    final String JOB_PATTERN = '<group>'
    Map results = [:]
    
    jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
      job.fullName.contains( JOB_PATTERN )
    }.each { Job job ->
      results.(job.fullName) = job.getBuilds().byTimestamp( CURRENT_TIME - BENCH_MARK, CURRENT_TIME ).findAll { Run run ->
        ! run.isBuilding() &&
        run.result == Result.FAILURE
      }.collectEntries { Run run ->
        [ run.id, run.getAbsoluteUrl() ]
      }
    }
    
    println prettyPrint( toJson(results.findAll{ !it.value.isEmpty() }) )
    
  • or

    import hudson.model.Job
    import hudson.model.Result
    import hudson.model.Run
    import jenkins.model.Jenkins
    import static groovy.json.JsonOutput.*
    
    final long CURRENT_TIME  = java.util.Calendar.getInstance().getTimeInMillis()
    final long BENCH_MARK    = 1*24*60*60*1000
    final String JOB_PATTERN = '<group>'
    Map results = [:]
    
    jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
      job.fullName.contains(JOB_PATTERN)
    }.each { Job job ->
      def history = job.getBuilds().byTimestamp( CURRENT_TIME - BENCH_MARK, CURRENT_TIME )
      if( history ) {
        results.(job.fullName) = history.findAll { Run run ->
          !run.isBuilding() &&
          run.result == Result.FAILURE
        }.collectEntries { Run run ->
          [ (run.id) : run.getAbsoluteUrl() ]
        }
      }
    }
    
    println prettyPrint( toJson(results.findAll{ !it.value.isEmpty() }) )
    

list build with results

get all builds result percentage

final String JOB_PATTERN = '<group>/<name>'
Map<String, Map<String, String>> results = [:]
int sum = 0

jenkins.model.Jenkins.instance.getAllItems( Job.class ).findAll{ project ->
  project.fullName.contains( JOB_PATTERN )
  // or
  // project.fullName.startsWith( JOB_PATTERN )
}.each { project ->
  results."${project.fullName}" = [ SUCCESS:0, UNSTABLE:0, FAILURE:0, ABORTED:0, INPROGRESS:0, NOT_BUILT:0 ]
  def build = project.getLastBuild()
  while ( build ) {
    // if job is building, then results."${project.fullName}"."${build.result}" will be null
    if ( build.isBuilding() ) {
      results."${project.fullName}".INPROGRESS = results."${project.fullName}".INPROGRESS + 1
    } else {
      // println "$project.name;$build.id;$build.result"
      results."${project.fullName}"."${build.result}" = results."${project.fullName}"."${build.result}" + 1
    }
    build = build.getPreviousBuild()
  }
}
results.each{ name, status ->
  sum = status.values().sum()
  println "${name}: ${sum} : "
  status.each{ r, c ->
    if ( c ) println "\t${r.padRight(11)}: ${c.toString().padRight(10)}: percentage: " + (sum ? "${c * 100 / sum}%" : '0%')
  }
}
"DONE"
build status
1.5.4.2.2 -- build status

get builds result percentage ( within 24 hours )

final String JOB_PATTERN = '<group>'
final long CURRENT_TIME  = System.currentTimeMillis()
final int BENCH_MARK     = 1*24*60*60*1000

Map<String, Map<String, List<String>>> results = [:]
int sum = 0

jenkins.model.Jenkins.instance.getAllItems( Job.class ).findAll { project ->
  project.fullName.startsWith( JOB_PATTERN )
}.each { project ->
  if ( project.getBuilds().byTimestamp(CURRENT_TIME - BENCH_MARK, CURRENT_TIME).size() > 0 ) {
    results."${project.fullName}" = [:]
    def build = project.getLastBuild()
    while ( build && (CURRENT_TIME - build.startTimeInMillis) <= BENCH_MARK ) {
      String key = build.isBuilding() ? 'INPROGRESS' : "${build.result}"
      results."${project.fullName}"."${key}" = ( results."${project.fullName}".getOrDefault( key, [] ) << build )
      build = build.getPreviousBuild()
    }
  }
}

results.each { name, status ->
  sum = status.values().flatten().size()
  println "\n>> ${name}: ${sum} : "
  println status.collect { r, b ->
    "\t${r.padRight(10)}: ${b.size()}: ( " + ( sum ? "${b.size()*100/sum}%" : '0%' ) + ' )' +
    '\n\t  -' + b.collect {"\t${it.id} : " + new Date(it.startTimeInMillis).format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")}.join('\n\t  -')
  }.join('\n')
}

"DONE"

-- result --
>> marslo/whitebox/sample: 5 :
  SUCCESS   : 3: ( 60% )
    - 193 : 2024-02-20T01:15:48.536Z
    - 192 : 2024-02-19T22:19:12.759Z
    - 190 : 2024-02-19T20:37:42.204Z
  FAILURE   : 1: ( 20% )
    - 191 : 2024-02-19T21:53:17.644Z
  NOT_BUILT : 1: ( 20% )
    - 189 : 2024-02-19T06:01:43.875Z

>> marslo/sandbox/script: 3 :
  FAILURE   : 3: ( 100% )
    - 84 : 2024-02-20T00:38:18.385Z
    - 83 : 2024-02-19T21:01:42.314Z
    - 82 : 2024-02-19T03:00:13.123Z

>> marslo/sandbox/pipeline: 8 :
  NOT_BUILT : 5: ( 62.5% )
    - 3553 : 2024-02-20T21:51:00.713Z
    - 3552 : 2024-02-20T15:51:00.727Z
    - 3551 : 2024-02-20T09:51:00.729Z
    - 3548 : 2024-02-19T15:51:00.721Z
    - 3547 : 2024-02-19T09:51:00.726Z
  SUCCESS   : 2: ( 25% )
    - 3550 : 2024-02-20T03:51:00.713Z
    - 3546 : 2024-02-19T03:51:00.707Z
  FAILURE   : 1: ( 12.5% )
    - 3549 : 2024-02-19T21:51:00.722Z
  • with percentage only

    final String JOB_PATTERN = '<group>'
    final long CURRENT_TIME  = System.currentTimeMillis()
    final int BENCH_MARK     = 1*24*60*60*1000
    
    Map<String, Map<String, String>> results = [:]
    int sum = 0
    
    jenkins.model.Jenkins.instance.getAllItems( Job.class ).findAll{ project ->
      project.fullName.startsWith( JOB_PATTERN )
      // or
      // project.fullName.contains( JOB_PATTERN )
    }.each { project ->
      if ( project.getBuilds().byTimestamp(CURRENT_TIME - BENCH_MARK, CURRENT_TIME).size() > 0 ) {
        results."${project.fullName}" = [ SUCCESS:0, UNSTABLE:0, FAILURE:0, ABORTED:0, INPROGRESS:0, NOT_BUILT:0 ]
        def build = project.getLastBuild()
    
        while ( build && (CURRENT_TIME - build.startTimeInMillis) <= BENCH_MARK ) {
          if ( build.isBuilding() ) {
            results."${project.fullName}".INPROGRESS = results."${project.fullName}".INPROGRESS + 1
          } else {
            results."${project.fullName}"."${build.result}" = results."${project.fullName}"."${build.result}" + 1
          } // if job is building, then results."${project.fullName}"."${build.result}" will be null
          build = build.getPreviousBuild()
        } // traverse in the whole traverse builds
    
      } // if there's builds within 24 hours
    }
    
    results.each{ name, status ->
      sum = status.values().sum()
      println "\n>> ${name}: ${sum} : "
      status.each{ r, c ->
        if ( c ) println "\t${r.padRight(11)}: ${c.toString().padRight(5)}: percentage: " +
                         ( sum ? "${c * 100 / sum}%" : '0%' )
      }
    }
    
    "DONE"
    
build status for jobs within 24 hours
1.5.4.2.3 -- build status for jobs within 24 hours

get builds result data range

[!TIP]

  • find only String type parameters:
    Map params = build?.getAction( ParametersAction.class )?.parameters?.findAll{ it instanceof StringParameterValue }?.dump()
    
  • scripts:
import java.text.SimpleDateFormat
import java.util.Date
import static groovy.json.JsonOutput.*

final String JOB_PATTERN = '<group>[/<name>]'                        // keywords
final Map<String, String> PARAM = [ 'param_name' : 'param_value' ]
Map<String, Map<String, String>> results = [:]

SimpleDateFormat simpleDateFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" )
final String START_DATE = '2021-04-26 00:00:00'
final String END_DATE   = '2021-04-27 00:00:00'

long start = simpleDateFormat.parse( START_DATE ).getTime()
long end   = simpleDateFormat.parse( END_DATE ).getTime()

jenkins.model.Jenkins.instance.getAllItems( Job.class ).findAll { Job job ->
  job.fullName.contains( JOB_PATTERN )
}.each { Job job ->
  results."${job.fullName}" = [:]
  job.getBuilds().byTimestamp( start, end ).each { Run build ->
    Map params = build?.getAction( ParametersAction.class )?.parameters?.findAll{ it instanceof StringParameterValue }?.collectEntries {
                   [ it.name, it.value ]
                 }
    results."${job.fullName}"."${build.getId()}" = [
            'time' : build.getTime().toString() ,
          'params' : params?.collect { k, v -> "${k} : ${v}"},
      'paramsExist': params?.entrySet()?.containsAll( PARAM.entrySet() )
    ]
    if ( build.isBuilding() ) {
      results."${job.fullName}"."${build.getId()}" << [ 'status' : 'INPROGRESS' ]
    } else {
      results."${job.fullName}"."${build.getId()}" << [ 'status' : build.getResult().toString() ]
    }
  }

  println prettyPrint( toJson(results) )
}

int count = 0
results.get( JOB_PATTERN ).each { k, v ->
  if ( v.paramsExist ) {
    count += 1
    println """
      #${k} : ${v.status} : ${v.time}
              ${v.params}
    """
  }
}

println "total number: ${count}"
"DONE"
filter build history via params
1.5.4.2.4 -- filter build history via params
filter build history via params details
1.5.4.2.5 -- filter build history via params details

get builds result and percentage in data range

import java.util.Date
import java.text.DecimalFormat
import java.text.SimpleDateFormat
import static groovy.json.JsonOutput.*

DecimalFormat df = new DecimalFormat("0.00")                          // keep two decimal places
SimpleDateFormat simpleDateFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" )

final String JOB_PATTERN = '<group>[/<name>]'                         // project/job keywords
final Map<String, String> PARAM = [ 'param_name' : 'param_value' ]    // setup PARAM as non-empty to list all wanted results
// final Map<String, String> PARAM = [ : ]                            // setup PARAM as empty to list all results without params validation

final long START
final long END
Map<String, Map<String, String>> results = [:]
Map<String, Map<String, Integer>> status = [:]

// start-end time format for x days/hours/minutes ago
final long NOW_TIME     = System.currentTimeMillis()
final int BENCH_MARK    = 1*24*60*60*1000
//                        | |  |  |   + milliseconds
//                        | |  |  + seconds
//                        | |  + minutes
//                        | + housrs
//                        + days

// start-end time format for time-x to time-y
// final String START_TIME = '2021-04-26 00:00:00'
// final String END_TIME   = '2021-04-29 00:00:00'

if ( NOW_TIME && BENCH_MARK ) {
  START = NOW_TIME - BENCH_MARK
  END   = NOW_TIME
} else if ( START_TIME && END_TIME ) {
  START = simpleDateFormat.parse( START_TIME ).getTime()
  END   = simpleDateFormat.parse( END_TIME ).getTime()
} else {
  return
}

jenkins.model.Jenkins.instance.getAllItems( Job.class ).findAll { Job job ->
  job.fullName.contains( JOB_PATTERN )
}.each { Job job ->
  results."${job.fullName}" = [:]
  job.getBuilds().byTimestamp( START, END ).each { Run build ->
    Map params = PARAM
                  ? build?.getAction( ParametersAction.class )?.parameters?.findAll{ it instanceof StringParameterValue }?.collectEntries {
                             [ it.name, it.value ]
                           }
                  : [:]
    results."${job.fullName}"."${build.getId()}" = [
            'time' : build.getTime().toString() ,
          'params' : params ,
      'paramsExist': params?.entrySet()?.containsAll( PARAM.entrySet() )
    ]
    if ( build.isBuilding() ) {
      results."${job.fullName}"."${build.getId()}" << [ 'status' : 'INPROGRESS' ]
    } else {
      results."${job.fullName}"."${build.getId()}" << [ 'status' : build.getResult().toString() ]
    }
  }
}

println simpleDateFormat.format(START) +
        ' ~ ' + simpleDateFormat.format(END) +
        ' : ' + ( PARAM ? "for params : ${PARAM.collect{k ,v -> "${k} ~> ${v}"}.join(' ')}" : 'for all builds' ) +
        ' :'

results.each { name, values ->
  status."${name}" = [ SUCCESS:0, UNSTABLE:0, FAILURE:0, ABORTED:0, INPROGRESS:0, NOT_BUILT:0 ]
  Map wanted = values.findAll { k, v -> v.get('paramsExist') == true }
  wanted.each { k, v -> status."${name}"."${v.status}" += 1 }

  println "\n>> ${name} : ${wanted.size()} : "
  status."${name}".each { r, c ->
    if (c) {
      println '\t\t' + r +
              ' :\ttotal : ' + c +
              '\tpercentage : ' + (wanted.size() ? "${df.format(c * 100 / wanted.size())}%" : '0%') + '\n' +
              '\t\t\t\tbuilds :\t' +  wanted.findAll { k, v -> v.get('status') == r }?.keySet()?.collect{ "#${it}" }.join(', ')
    }  // print only exists status
}

"DONE"
build-history-with-status-and-percentage-for-params
1.5.4.2.6 -- build-history-with-status-and-percentage-for-params
build-history-with-status-and-percentage-for-all-builds
1.5.4.2.7 -- build-history-with-status-and-percentage-for-all-builds

stop builds

[!TIP]

abort single build

final String JOB_NAME      = env.JOB_NAME
final Integer BUILD_NUMBER = env.BUILD_NUMBER

jenkins.model.Jenkins.instance
       .getItemByFullName( JOB_NAME )
       .getBuildByNumber( BUILD_NUMBER )
       .finish(
         hudson.model.Result.ABORTED,
         new java.io.IOException( "Aborting build" )
       )

abort previous running builds by params condition

[!NOTE|label:reference:]

String JOB_NAME               = env.JOB_NAME
String EXCLUDE_JOB_NUMBER     = env.BUILD_NUMBER
Map<String, String> condition = [ 'str1': params.str1 ]

currentBuild.description = params.collect{ "${it.key}: ${it.value}" }.join('<br>')

jenkins.model.Jenkins.instance
       .getItemByFullName( JOB_NAME )
       .getBuilds()
       .findAll { Run run ->
         run.id < EXCLUDE_JOB_NUMBER &&
         run.isInProgress() &&
         run?.getAction(ParametersAction.class)?.parameters?.find {
           condition.keySet().contains( it?.name ) &&
           condition.getOrDefault( it?.name, '' ) == it?.value
         }
       }.each { Run run ->
         run.setDescription( "<b>aborted by ${currentBuild.fullDisplayName} due to new pathcset triggered !</b><br>" + (run.getDescription() ?: '') )
         run.finish(
                    hudson.model.Result.ABORTED,
                    new java.io.IOException( "Aborting build due to new patchset has been triggered" )
                  )
       }
  • abort running builds if new one is running

    import hudson.model.Result
    import jenkins.model.CauseOfInterruption
    
    // iterate through current project runs
    build.getProject()._getRuns().iterator().each { run ->
      def exec = run.getExecutor()
      // if the run is not a current build and it has executor (running) then stop it
      if( run != build && exec != null ) {
        // prepare the cause of interruption
        def cause = { "interrupted by build #${build.getId()}" as String } as CauseOfInterruption
        exec.interrupt( Result.ABORTED, cause )
      }
    }
    
    • or

      import hudson.model.Result
      import hudson.model.Run
      import jenkins.model.CauseOfInterruption.UserInterruption
      
      def abortPreviousBuilds() {
        Run previousBuild = currentBuild.rawBuild.getPreviousBuildInProgress()
      
        while ( previousBuild != null ) {
          if ( previousBuild.isInProgress() ) {
            def executor = previousBuild.getExecutor()
              if ( executor != null ) {
                echo ">> Aborting older build #${previousBuild.number}"
                  executor.interrupt( Result.ABORTED,
                                      new UserInterruption(
                                        "Aborted by newer build #${currentBuild.number}"
                                    ))
              }
          }
          previousBuild = previousBuild.getPreviousBuildInProgress()
        }
      
      } // abortPreviousBuilds
      
    • or: cancel builds same job

      /**
       * Author: Isaac S Cohen
       * This script works with workflow to cancel other running builds for the same job
       * Use case: many build may go to QA, but only the build that is accepted is needed,
       * the other builds in the workflow should be aborted
      **/
      
      def JOB_NAME     = env.JOB_NAME
      int BUILD_NUMBER = env.BUILD_NUMBER.toInteger()
      
      def job = jenkins.model.Jenkins.instance.getItemByFullName( JOB_NAME )
      for ( build in job.builds ) {
        if ( !build.isBuilding() ) { continue; }
        if ( BUILD_NUMBER == build.getNumber().toInteger() ) { continue; println "equals" }
        build.doStop()
      }
      
    • or: properly stop only running pipelines

stop all queue and running jobs

[!TIP|label:reference:]

import java.util.ArrayList
import hudson.model.*
import jenkins.model.Jenkins

// Remove everything which is currently queued
def q = jenkins.model.Jenkins.instance.queue
for ( queued in jenkins.model.Jenkins.instance.queue.items ) {
  q.cancel( queued.task )
}

// stop all the currently running jobs
for ( job in jenkins.model.Jenkins.instance.items ) {
  stopJobs(job)
}

def stopJobs( job ) {
  if ( job in com.cloudbees.hudson.plugins.folder.Folder ) {
    for ( child in job.items ) {
      stopJobs( child )
    }
  } else if ( job in org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject ) {
    for ( child in job.items ) {
      stopJobs( child )
    }
  } else if ( job in org.jenkinsci.plugins.workflow.job.WorkflowJob && job.isBuilding() ) {
    for ( build in job.builds ) {
      build.doKill()
    }
  }
}

stop all running builds via pattern

[!NOTE|label:references:]

import hudson.model.Job
import hudson.model.Result
import hudson.model.Run
import java.util.Calendar
import jenkins.model.Jenkins

final Calendar RIGHT_NOW = Calendar.getInstance()
final String JOB_PATTERN = '<group>/<name>'

jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
  job.fullName.startsWith( JOB_PATTERN )
}.collect { Job job ->
  job.builds.findAll { Run run -> run.isBuilding() }
            .collect { Run run ->
                           run.setDescription( '<b>aborted by Jenkins restart</b><br>' + run.getDescription() )
                           // or run.doStop(), run.doTerm(), run.doKill()
                           run.finish(
                                       hudson.model.Result.ABORTED,
                                       new java.io.IOException( "aborted by Jenkins restart" )
                                     )
            }
}
  • or cancel builds in same job

    /*
     Author: Isaac S Cohen
     This script works with workflow to cancel other running builds for the same job
     Use case: many build may go to QA, but only the build that is accepted is needed,
     the other builds in the workflow should be aborted
    */
    
    final String JOB_NAME  = env.JOB_NAME
    final int BUILD_NUMBER = env.BUILD_NUMBER.toInteger()
    
    def job = jenkins.model.Jenkins.instance.getItemByFullName( JOB_NAME )
    for ( build in job.builds ) {
      if ( !build.isBuilding() ) { continue }
      if ( BUILD_NUMBER == build.getNumber().toInteger() ) { continue; println "equals" }
      build.doStop()
    }
    

stop all running builds started in 24 hours

[!NOTE|label:references:]

import hudson.model.Job
import hudson.model.Result
import hudson.model.Run
import java.util.Calendar
import jenkins.model.Jenkins

final Calendar RIGHT_NOW = Calendar.getInstance()
final long BENCH_MARK    = 1*24*60*60*1000
final String JOB_PATTERN = '<group>/<name>'

jenkins.model.Jenkins.instance.getAllItems(Job.class).findAll { Job job ->
  job.fullName.startsWith( JOB_PATTERN )
}.collect { Job job ->
  job.builds.findAll { Run run ->
                           run.isBuilding() &&
                           ( RIGHT_NOW.getTimeInMillis() - run.getStartTimeInMillis() ) <= BENCH_MARK
            }
            .collect { Run run ->
                           run.setDescription( '<b>aborted by Jenkins restart</b><br>' + run.getDescription() )
                           run.finish(
                                       hudson.model.Result.ABORTED,
                                       new java.io.IOException( "aborted by Jenkins restart" )
                                     )
            }
}

list all queue tasks and blocked reason

jenkins.model.Jenkins.instance.queue.items.each {
  println """
                   getId : ${it.getId()}
             isBuildable : ${it.isBuildable()}
      getFullDisplayName : ${it.task.getFullDisplayName()}
          getDisplayName : ${it.task.getDisplayName()}
       isConcurrentBuild : ${it.task.isConcurrentBuild()}
          getAffinityKey : ${it.task.getAffinityKey()}
                  getUrl : ${it.task.getUrl()}
           getWhyBlocked : ${it.task.getWhyBlocked()}
    getCauseOfBlockage() : ${it.task.getCauseOfBlockage()}
  """
  // println it.task.metaClass.methods*.name.sort().unique()
}
  • result:
                     getId : 80210
               isBuildable : false
        getFullDisplayName : marslo » sandbox » test
            getDisplayName : test
         isConcurrentBuild : false
            getAffinityKey : marslo » sandbox » test
                    getUrl : job/marslo/job/sandbox/job/test/
             getWhyBlocked : Build #27 is already in progress (ETA: 3 min 28 sec)
      getCauseOfBlockage() : Build #27 is already in progress (ETA: 3 min 28 sec)
    

remove builds

[!NOTE|label:references:]

delete multiple builds

import org.jenkinsci.plugins.workflow.job.WorkflowJob

final String JOB_PATTERN = '<group>/<job>'
WorkflowJob project      = jenkins.model.Jenkins.instance.getItemByFullName( JOB_PATTERN )
final int START_BUILD    = <build-id>
final int END_BUILD      = project.getLastCompletedBuild().getId().toInteger()

( START_BUILD..END_BUILD ).each { project.getBuildByNumber( it ).delete() }
  • setup next build number if necessary
    jenkins.model.Jenkins.instance.getItemByFullName( JOB_PATTERN ).updateNextBuildNumber( START_BUILD+1 )
    
Copyright © marslo 2020-2023 all right reserved,powered by GitbookLast Modified: 2024-03-12 15:01:28

results matching ""

    No results matching ""