How to trigger manual clean of Hudson workspaces

10,038

Solution 1

I have tried following script and it works for Single node,

  def hi = hudson.model.Hudson.instance
             hi.getItems(hudson.model.Job).each {
                job ->
                if(job.isDisabled())
                {
                   println(job.displayName)
                   job.doDoWipeOutWorkspace()
                 }
           }

Solution 2

You do not need to write a plugin. You can write a job that utilizes Groovy plugin to write a Groovy system script. The job would run, say, nightly. It would identify disabled projects and erase their workspaces. Here is a link to Hudson Model API that your script will tap into. There is a Groovy script console at http://<hudson-server>/script that is very useful for debugging.

Here is a code snippet that should be of direct benefit to you. Run it in the script console and examine the output:

def hi = hudson.model.Hudson.instance
hi.getItems(hudson.model.Job).each {
  job ->
    println(job.displayName)
    println(job.isDisabled())
    println(job.workspace)
}

You may also find code snippets in this answer useful. They refer to Jenkins API, but on this level I do not think there is a difference between Jenkins and Hudson.

Update:

Here's how you can do it on multiple slaves: create a multi-configuration job (also called "matrix job") that runs on all the slaves. On each slave the following system Groovy script will give you for every job its workspace on that slave (as well as enabled/disabled flag):

def hi = hudson.model.Hudson.instance

def thr = Thread.currentThread()
def build = thr?.executable
def node = build.executor.owner.node

hi.getItems(hudson.model.Job).each {
  job ->
    println("---------")
    println(job.displayName)
    println(job.isDisabled())
    println(node.getWorkspaceFor(job))
  }

As the script runs on the slave itself you can wipe out the workspace directly from it. Of course, the worskspace may not exist, but it's not a problem. Note that you write the script only once - Jenkins will run it on all the slaves you specify in the matrix job automatically.

Solution 3

The following Groovy script wipes workspaces of certain jobs on all nodes. Execute it from "Jenkins host"/computer/(master)/script

In the TODO part, change the job name to the one that you need.

import hudson.model.*
// For each job
for (item in Hudson.instance.items)
{
  jobName = item.getFullDisplayName()
  // check that job is not building
  if (!item.isBuilding())
  {
    // TODO: Modify the following condition to select which jobs to affect
    if (jobName == "MyJob")
    {
      println("Wiping out workspaces of job " + jobName)
      customWorkspace = item.getCustomWorkspace()
      println("Custom workspace = " + customWorkspace)

      for (node in Hudson.getInstance().getNodes())
      {
        println("  Node: " + node.getDisplayName())
        workspacePath = node.getWorkspaceFor(item)
        if (workspacePath == null)
        {
          println("    Could not get workspace path")
        }
        else
        {
          if (customWorkspace != null)
          {
            workspacePath = node.getRootPath().child(customWorkspace)
          }

          pathAsString = workspacePath.getRemote()
          if (workspacePath.exists())
          {
            workspacePath.deleteRecursive()
            println("    Deleted from location " + pathAsString)
          }
          else
          {
            println("    Nothing to delete at " + pathAsString)
          }
        }
      }
    }
  }
  else
  {
    println("Skipping job " + jobName + ", currently building")
  }
}

Solution 4

its a bit late, but i ran into the same problem. my script will check if atleast 2 GB space is available. if this is not the case, all workspaces on the node are cleared to free space.

import hudson.FilePath.FileCallable
import hudson.slaves.OfflineCause

for (node in Jenkins.instance.nodes) {
    computer = node.toComputer()
    if (computer.getChannel() == null) continue

    rootPath = node.getRootPath()
    size = rootPath.asCallableWith({f, c -> f.getUsableSpace()} as FileCallable).call()
    roundedSize = size / (1024 * 1024 * 1024) as int

    println("node: " + node.getDisplayName() + ", free space: " + roundedSize + "GB")
    if (roundedSize < 2) {
        computer.setTemporarilyOffline(true, [toString: {"disk cleanup"}] as OfflineCause)

        for (item in Jenkins.instance.items) {
            jobName = item.getFullDisplayName()

            if (item.isBuilding()) {
                println(".. job " + jobName + " is currently running, skipped")
                continue
            }

            println(".. wiping out workspaces of job " + jobName)

            workspacePath = node.getWorkspaceFor(item)
            if (workspacePath == null) {
                println(".... could not get workspace path")
                continue
            }

            println(".... workspace = " + workspacePath)

            customWorkspace = item.getCustomWorkspace()
            if (customWorkspace != null) {
                workspacePath = node.getRootPath().child(customWorkspace)
                println(".... custom workspace = " + workspacePath)
            }

            pathAsString = workspacePath.getRemote()
            if (workspacePath.exists()) {
                workspacePath.deleteRecursive()
                println(".... deleted from location " + pathAsString)
            } else {
                println(".... nothing to delete at " + pathAsString)
            }
        }

        computer.setTemporarilyOffline(false, null)
    }
}

Solution 5

I was recently also looking to clean up my jenkins workspaces, but with a little twist: I wanted to only remove workspaces from jobs that no longer exist. This is because jenkins does not get rid of workspaces when deleting a job, which is pretty annoying. And we only use a master at the moment, no separate nodes.

I found a script somewhere (can't find the link anymore) but tweaked it a bit for our usage, putting it in a jenkins job with an 'Execute system Groovy script' build step, running daily:

import hudson.FilePath
import jenkins.model.Jenkins
import hudson.model.Job

def deleteUnusedWorkspace(FilePath root, String path) {
  root.list().sort{child->child.name}.each { child ->
    String fullName = path + child.name

    def item = Jenkins.instance.getItemByFullName(fullName);
    println "Checking '$fullName'"

    try{
      if (item.class.canonicalName == 'com.cloudbees.hudson.plugins.folder.Folder') {
        println "-> going deeper into the folder"
        deleteUnusedWorkspace(root.child(child.name), "$fullName/")
      } else if (item == null) {
        // this code is never reached, non-existing projects generate an exception
        println "Deleting (no such job): '$fullName'"
        child.deleteRecursive()
      } else if (item instanceof Job && !item.isBuildable()) {
        // don't remove the workspace for disabled jobs!
        //println "Deleting (job disabled): '$fullName'"
        //child.deleteRecursive()
      }
    } catch (Exception exc) {
      println "   Exception happened: " + exc.message
      println "   So we delete '" + child + "'!"
      child.deleteRecursive()
    }
  }
}

println "Beginning of cleanup script."

// loop over possible slaves
for (node in Jenkins.instance.nodes) {
  println "Processing $node.displayName"
  def workspaceRoot = node.rootPath.child("workspace");
  deleteUnusedWorkspace(workspaceRoot, "")
}

// do the master itself
deleteUnusedWorkspace(Jenkins.instance.rootPath.child("workspace"), "")

println "Script has completed."

Might need some individual tweaking though. Obviously you should run this script with all delete statements commented out first, and make sure you have a backup before doing an actual run.

Share:
10,038
Konstantin Komissarchik
Author by

Konstantin Komissarchik

I have been doing professional Java development since 1999. For the latter half of that time, I have been working on building Eclipse tooling for BEA and later Oracle. I currently lead a small team of Eclipse developers working on Oracle Enterprise Pack for Eclipse product and related open source technologies. My current open source focus is Sapphire Project, which aims to simplify how Java UI is written.

Updated on June 09, 2022

Comments

  • Konstantin Komissarchik
    Konstantin Komissarchik about 2 years

    We have a Hudson cluster with eight nodes. When a particular code branch is no longer in active use, we disable the build job, but the workspaces for that job still hang around taking up space on all the nodes.

    I am looking for a way to trigger workspace cleanup across all nodes. Note that I am not looking for a "clean workspace before build" solution.