62

Due to a misconfiguration, I have over 1700 failed builds in a Jenkins job.

How can I efficiently clean them?

2

7 Answers 7

92

You have several options:

  • Temporarily set the number of builds to keep in the job configuration (Discard Old Builds) so that those builds will be deleted when the next build finishes. If the next build is 1800, set it to keep the most recent 85 or so. Mark all older builds (i.e. 1 to 10) as Keep This Build Forever before starting the next build. This option will fail to delete some builds if you have a downstream job that prevents upstream builds from being deleted (not a problem in your situation if all of them failed though).

  • Use the Script Console in Manage Jenkins. If it's a top level job (not in a folder), the following will do the trick:

    Jenkins.instance.getItemByFullName('JobName').builds.findAll { it.number > 10 && it.number < 1717 }.each { it.delete() }

    Of course, this approach generally requires good backups. There's a lot you can break using the script console.

  • Delete the builds' folders from disk (by default in $JENKINS_HOME/jobs/JobName/builds/, using the start time stamp as folder name) and restart Jenkins, or Reload Configuration From Disk. This option will not allow plugins that e.g. keep the SVN history in order by moving any changes to the subsequent build to do their job.

4
  • 10
    I've found another solution: Using jenkins-cli.jar -s ... delete-builds envjs 11-1717 Apr 30, 2013 at 9:04
  • Made a Gist to list all the jobs with a build count via the terminal. Makes it easier to find jobs that need cleaning up. Could be used via cron as an email report too.
    – Nick
    Feb 3, 2014 at 12:17
  • @Nick Check out the Groovy plugin, specifically the system Groovy build step type. No need to go outside Jenkins for this, just create a job that does this. jenkins.model.Jenkins.instance.getAllItems(hudson.model.Job.class).each { println it.fullDisplayName + " has " + it.builds.size() + " builds" } ; return Sorting e.g. by builds is similarly trivial.
    – Daniel Beck
    Feb 3, 2014 at 17:59
  • 1
    For multi-branch pipelines you can use the syntax JobName/branch Apr 8, 2019 at 8:25
25

Simply make an API call:

curl -X POST http://jenkinUser:jenkinAPIToken@yourJenkinsURl.com/job/theJob/[11-1717]/doDelete

To get the APIToken: login to Jenkins > Configuration > Show API Token.

6
  • 1
    I thinks this is the best approach to the problem. it can be executed remotely a provides a broad range of actions via REST Aug 14, 2015 at 20:48
  • 1
    Best answer since you don't have to go searching for the jar on an installed system. Jul 27, 2016 at 23:32
  • I meant "you don't have to download anything" Jul 27, 2016 at 23:40
  • "Best answer since you don't have to" - if your criteria is not to download a <1m file than this is better thank CLI. If the criteria is not having to type in messy urls with auth tokens remember curl syntax (assuming it's installed in the first place), and you prefer clear commands and helps and maybe you already have the jar the CLI is better. If you only have the we interface there with a scripting console installed, then that one is the better. As usual it depends.
    – user17265
    Dec 1, 2016 at 12:55
  • for delete keep forever builds, run toggleLogKeep before doDelete.
    – Fancyoung
    Apr 28, 2017 at 7:21
17

As Aaron stated correctly you can also use the Jenkins CLI for this purpose:

java -jar jenkins-cli.jar -s http://yourserver.com delete-builds <JobName> 11-1717
7

Template for a script to run in the Jenkins Script-Console. Use the flag reallyDelete to test it before actually deleting:

// Jenkins job
def jobName = 'foo'
// Range of builds to delete
def rs = Fingerprint.RangeSet.fromString("11-1717", false);
// Set to true to actually delete. Use false to test the script.
def reallyDelete = false;

// ----------------------------------
def job = Jenkins.instance.getItemByFullName(jobName);
println("Job: ${job.fullName}");

def builds = job.getBuilds(rs);
println("Found ${builds.size()} builds");
builds.each{ b-> 
  if (reallyDelete) {
    println("Deleting ${b}");
    b.delete();
  } else {
    println("Found match ${b}");
  }
}
1
  • Just lovely, this script. Thanks!
    – Alfabravo
    May 16, 2020 at 22:35
3

I was faced with the same task when I took over a Jenkins server, where there are just over 150 jobs with up to 3000 old builds, so I wrote a small bash script that only keeps the last 10 builds:

#! /bin/bash

initialPath=$(pwd);

find /var/lib/jenkins/ -type d -name builds | while read jobs
 do

    #############################################################
    ## Enter build-directory of current job and get some numbers
    #############################################################
    cd "$jobs" ;
    ls -d [[:digit:]]* &>/dev/null ;
    rc=$? ;
    if [[ $rc -eq 0 ]] ;
     then
        buildName=$(ls -d [[:digit:]]*)  ;
        latestBuild=$(echo $buildName | awk '{print $NF}') ; # highest number
        oldestBuild=$(echo $buildName | awk '{print $1}') ; # lowest number
        amountOfBuilds=$(echo $buildName | wc -w ) ;
        lastBuildToKeep=$(echo "${latestBuild} 9" | awk '{print $1 - $2}') ;

        ############################################################
        ## Skip Folder if it contains less than 10 builds
        ############################################################
        if [ ${amountOfBuilds} -le 10 ] ;
         then
            echo "Skipping $(pwd) --> less than 10 builds";
         else
            ############################################################
            ## Delete all build-directories except the last 10
            ############################################################
            for (( i=$oldestBuild; i<$lastBuildToKeep; i++))
             do
                echo "Deleting $(pwd)/${i} ..."
                rm -r "$i" ;
            done ;
        fi ;
     else
        echo "Skipping $(pwd) --> Zero builds";
    fi ;
done ;

############################################################
## go back to $initialPath
############################################################
cd "$initialPath" ;

Restarting Jenkins afterwards is highly recommended to avoid problems. Thx @Aaron Digulla

5
  • Why didn't you use a script for the Jenkins console? Mar 27, 2019 at 9:29
  • Because I'm more familiar with bash. That's the only reason.
    – Anyesto
    Mar 27, 2019 at 11:41
  • Understood. Please edit your question and add that Jenkins must be restarted afterwards to avoid problems. Mar 27, 2019 at 13:12
  • Can someone explain how to use this script to folks like me who aren't very familiar with bash? Where to call it, what to name it, etc? Aug 19, 2020 at 13:44
  • Actually, restart is not necessary, UI Manage opition, "Reload config from Disk "option will.work too, but either way you risk losing or corrupting the cached atate. Going under the covers (bash) is not a good idea. Recommend the UI, API or groovy options instead per @dbeck.
    – Ian W
    Nov 21, 2020 at 4:56
1

Go to Manage Jenkins > Script Console

Run below command

def jobName = "build name"  
def job = Jenkins.instance.getItem(jobName)  
job.getBuilds().each { if(it.number >= 11 && it.number <= 1717) it.delete() }  
job.save()
2
  • Why is it necessary to run job.save() at the end? Dec 16, 2020 at 15:26
  • To save the settings to a file link, In this case we are deleting builds so I guess save is not that much required here. Dec 21, 2020 at 6:11
0

I have created a small python script which would serve this purpose. Following is the script:

delete_jenkins_builds.py

from os import listdir, path
import shutil


job_build_fullpath = '/var/lib/jenkins/jobs/My-Jenkins-Project/builds'
print listdir(job_build_fullpath)

for build_dir in listdir(job_build_fullpath):
        if build_dir.isdigit() and int(build_dir) in range(11, 1718):
                build_dir_fullpath = path.join(job_build_fullpath, build_dir)
                print "Deleting: " + build_dir_fullpath
                shutil.rmtree(build_dir_fullpath)
  • job_build_fullpath - Path of the Job's build directory

  • range(start_build_no, end_build_no) - range(11, 1718) looks for all the builds ranging from build no. 11 to build no. 1717. Please set it accordingly.

  • shutil.rmtree(build_dir_fullpath) - deletes each build directory that are in the range.

Python Version: 2.7

3
  • Won't you have to restart Jenkins afterwards? What if Jenkins keeps information about those folders in a database? May 30, 2017 at 13:32
  • Yes @AaronDigulla, a restart of Jenkins server would be required to reflect the changes on the Jenkins portal. Jun 14, 2017 at 6:03
  • Going under the covers (shutil/rmtree) is not a good idea; you risk losing or corrupting the cached state. Recommend the UI, API or groovy options instead per @dbeck. Also py 2.7 is obsolete. Would also recommend deleting answer.
    – Ian W
    Nov 21, 2020 at 5:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.