Jenkins Specific Build Discarders per branch

The aim of this article is to show you a way of having different build discarders depending on the branch name as a job configuration level instead of defining it in each pipeline. Having a default behavior for all the branches but with some exceptions.

If you are using Jenkins for your CI/CD workflows with several development teams, you would probably run into the lack of disk space on the instance where Jenkins is running due to a lot of builds are being stored. That is why we need a mechanism for automatically deleting old builds in order to get some free space for the new ones.

The problem is that for some specific branches we want to keep more elements than for the others so we cannot use a default build discarder for all of them. In addition, we want to have this behavior as a job level without the need to specify it in the pipeline.

These are the requirements for the builds that we want to accomplish for all the jobs:

  • For the master branch, keep the builds of the last year.
  • For the tags, keep the builds of the last year.
  • For integration branches: devel, development, staging, dev (they are named differently in each repository), keep the builds of the last six months.
  • For the orphaned branches (branches that have been deleted from the repository. They are usually the development branches that have been already merged to master), delete the branches older than two months.
  • For the rest of the branches (usually the current development branches), keep the builds of the last 6 months.
My Jenkins deployment/configuration is defined as code with Ansible, therefore, job configuration won't be an exception.

The Job DSL plugin attempts to solve this problem by allowing jobs to be defined in a programmatic form in a human-readable file.

Explaining how to use this plugin is out of the scope of this article, you can read its documentation for more details. We will just focus on the way of having custom builds discarders per branch name using this plugin as a job level.

This is the .yaml file that we have used for fulfilling the previous requirements:

          multibranchPipelineJob('my-repository') {

            displayName('My Repository')
            branchSources {
              branchSource {
                source {
                  bitbucket {
                      id('my-repository')
                      credentialsId('bitbucket')
                      repoOwner('the-repo-owner')
                      repository('my-repository')
                      traits {
                        cleanBeforeCheckoutTrait {
                          extension {
                            deleteUntrackedNestedRepositories(true)
                          }
                        }
                      }
                  }
                }
                strategy {
                  namedBranchesDifferent {
                    defaultProperties {
                      buildRetention {
                        buildDiscarder {
                          logRotator {
                            daysToKeepStr('182')
                            numToKeepStr('')
                            artifactDaysToKeepStr('182')
                            artifactNumToKeepStr('')
                          }
                        }
                      }
                      durabilityHint {
                        hint('PERFORMANCE_OPTIMIZED')
                      }
                    }
                    namedExceptions {
                      named {
                        name('master,demo/**')
                        props {
                          buildRetention {
                            buildDiscarder {
                              logRotator {
                                daysToKeepStr('365')
                                numToKeepStr('')
                                artifactDaysToKeepStr('365')
                                artifactNumToKeepStr('')
                              }
                            }
                          }
                          durabilityHint {
                            hint('PERFORMANCE_OPTIMIZED')
                          }
                        }
                      }
                      named {
                        name('dev,devel,develop,development,staging')
                        props {
                          buildRetention {
                            buildDiscarder {
                              logRotator {
                                daysToKeepStr('182')
                                numToKeepStr('')
                                artifactDaysToKeepStr('182')
                                artifactNumToKeepStr('')
                              }
                            }
                          }
                          durabilityHint {
                            hint('PERFORMANCE_OPTIMIZED')
                          }
                        }
                      }
                      named {
                        name('?*.?*.?*')
                        props {
                          buildRetention {
                            buildDiscarder {
                              logRotator {
                                daysToKeepStr('365')
                                numToKeepStr('')
                                artifactDaysToKeepStr('365')
                                artifactNumToKeepStr('')
                              }
                            }
                          }
                          durabilityHint {
                            hint('PERFORMANCE_OPTIMIZED')
                          }
                        }
                      }
                    }
                  }
                }
                buildStrategies {
                  buildTags {
                    atLeastDays('0')
                    atMostDays('14')
                  }
                  buildNamedBranches {
                    filters {
                      regex {
                        caseSensitive(true)
                        regex('master')
                      }
                    }
                  }
                  buildChangeRequests {
                    ignoreTargetOnlyChanges(false)
                    ignoreUntrustedChanges(false)
                  }
                }
              }
              orphanedItemStrategy {
                defaultOrphanedItemStrategy {
                  pruneDeadBranches(true)
                  daysToKeepStr('60')
                  numToKeepStr('100')
                }
              }
            }
            configure {
              def traits = it / sources / data / 'jenkins.branch.BranchSource' / source / traits
              traits << 'com.cloudbees.jenkins.plugins.bitbucket.BranchDiscoveryTrait' {
                strategyId(3) // detect all branches
              }
              traits << 'com.cloudbees.jenkins.plugins.bitbucket.OriginPullRequestDiscoveryTrait' {
                strategyId(1) // Merge pull requests with target
              }
              traits << 'com.cloudbees.jenkins.plugins.bitbucket.TagDiscoveryTrait' {
              }
              traits << 'jenkins.scm.impl.trait.RegexSCMHeadFilterTrait' {
                // Discard floating tags
                regex('\\D.*|\\d*\\.\\d*\\.\\d*')
              }

              it / factory(class: "org.jenkinsci.plugins.workflow.multibranch.WorkflowBranchProjectFactory") << {

                  scriptPath('Jenkinsfile')
              }
            }
          }

We will explain the sections used for the build discarders:
  • The default build discarder for all branches if not specified:
                  namedBranchesDifferent {
                    defaultProperties {
                      buildRetention {
                        buildDiscarder {
                          logRotator {
                            daysToKeepStr('182')
                            numToKeepStr('')
                            artifactDaysToKeepStr('182')
                            artifactNumToKeepStr('')
                          }
                        }
                      }
                      durabilityHint {
                        hint('PERFORMANCE_OPTIMIZED')
                      }
                    }

It will delete the builds older than 182 days by default. In my case, it will be applied for the development branches (it excludes: master, devel, tags, etc)

We can set the number of builds of each branch to be kept instead of their age.

  • Specific behavior for named branches like the master, demo/**, devel, etc.

                    namedExceptions {
                      named {
                        name('master,demo/**')
                        props {
                          buildRetention {
                            buildDiscarder {
                              logRotator {
                                daysToKeepStr('365')
                                numToKeepStr('')
                                artifactDaysToKeepStr('365')
                                artifactNumToKeepStr('')
                              }
                            }
                          }
                          durabilityHint {
                            hint('PERFORMANCE_OPTIMIZED')
                          }
                        }
                      }

It will delete the builds older than 365 days. We can specify just one branch name or a list of names (like dev,devel,develop,development,staging

                      named {
                        name('?*.?*.?*')
                        props {
                          buildRetention {
                            buildDiscarder {
                              logRotator {
                                daysToKeepStr('365')
                                numToKeepStr('')
                                artifactDaysToKeepStr('365')
                                artifactNumToKeepStr('')
                              }
                            }
                          }
                          durabilityHint {
                            hint('PERFORMANCE_OPTIMIZED')
                          }
                        }

                      } 

It will keep the build of the tags with semantic versioning (X.Y.Z) for 365 days.

  • For orphaned branches:

              orphanedItemStrategy {
                defaultOrphanedItemStrategy {
                  pruneDeadBranches(true)
                  daysToKeepStr('60')
                  numToKeepStr('')
                }
              }

It will delete the builds older than 60 days.

With this configuration, you can create a job with specific build discarders for each kind of branch. You just need to load this file in Jenkins with the Job-DSL plugin to be applied.

Let me know if you use another approach.

I hope this helps!

=)~







Comments