48

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

How can I efficiently clean them?

72

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.

  • 10
    I've found another solution: Using jenkins-cli.jar -s ... delete-builds envjs 11-1717 – Aaron Digulla Apr 30 '13 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 '14 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 '14 at 17:59
  • For multi-branch pipelines you can use the syntax JobName/branch – waitinforatrain Apr 8 at 8:25
17

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.

  • I thinks this is the best approach to the problem. it can be executed remotely a provides a broad range of actions via REST – alfredocambera Aug 14 '15 at 20:48
  • Best answer since you don't have to go searching for the jar on an installed system. – Keith Tyler Jul 27 '16 at 23:32
  • I meant "you don't have to download anything" – Keith Tyler Jul 27 '16 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 '16 at 12:55
  • for delete keep forever builds, run toggleLogKeep before doDelete. – Fancyoung Apr 28 '17 at 7:21
16

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
4

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 = Jenkins.instance.getItemByFullName(jobName).getBuilds(rs);
println("Found ${builds.size()} builds");
builds.each{ b-> 
  if (reallyDelete) {
    println("Deleting ${b}");
    b.delete();
  } else {
    println("Found match ${b}");
  }
}
1

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

  • Why didn't you use a script for the Jenkins console? – Aaron Digulla Mar 27 at 9:29
  • Because I'm more familiar with bash. That's the only reason. – Anyesto Mar 27 at 11:41
  • Understood. Please edit your question and add that Jenkins must be restarted afterwards to avoid problems. – Aaron Digulla Mar 27 at 13:12
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

  • Won't you have to restart Jenkins afterwards? What if Jenkins keeps information about those folders in a database? – Aaron Digulla May 30 '17 at 13:32
  • Yes @AaronDigulla, a restart of Jenkins server would be required to reflect the changes on the Jenkins portal. – Shubham Soin Jun 14 '17 at 6:03

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.