4

Why again this type of question?

This question seems to have been asked multiple times, but all the answers are irelevant for Jenkins Pipeline jobs (plugin: workflow-job).

Situation

I am migrating a bunch of old freestyle jobs from old Jenkins standalone server to a distributed Jenkins env and I've decided to convert them to Jenkins Pipeline jobs (can't use Blue ocean for it as the SCM is SVN.

Anyway, for some of the jobs is not desired to clean up their workspaces as they are sort of sanity/verification jobs and because the size of SVN checkout and built stuff is large (2GB in 20K files, just deleting it is so slow).

However I do occasionally (ad-hoc) need to delete a workspace of such jobs. And I don't want to do it by:

And I don't have r/w access to a FS on that slave node (which would be the easiest thing to do).

Googling

Quick search on the internet avalanched me with dozens of results [1,2, 3, 4, ...] how to cleanWS() from within a Groovy script ran from Jenkins script console.

Unfortunately, none of them tries to delete workspace of true org.jenkinsci.plugins.workflow.job.WorkflowJob item instance of a job.

My Groovy attempt to cleanWS

Based on the answers gathered from the internet I started my Groovy clean up script which can be executed from the Jenkins script console <Jenkins:port/script>

import hudson.model.*
import com.cloudbees.hudson.*
import com.cloudbees.hudson.plugins.*
import com.cloudbees.hudson.plugins.folder.*
import org.jenkinsci.plugins.workflow.job.*

//jobsToRetrieve = ["aFolder/aJobInFolder","topLevelJob"]
jobsToRetrieve = ["Sandbox/PipelineTests/SamplePipeline"]

enumerateItems(Hudson.instance.items)

def enumerateItems(items) {
  items.each { item ->
    println("===============::: GENERAL INFO::: =======================")
    println(" item: " + item)
    println(" item FN:  " + item.fullName)
    println(" item.getClass " + item.getClass())
    println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")

    if ( !(item instanceof Folder)) {
      jobName = item.getFullDisplayName()
      println(" :::jobname::: " + jobName)
      if (jobsToRetrieve.contains(item.getFullName())) {
        if (item instanceof WorkflowJob) {
          println("XXXXXXXXXXXXX--- THIS IS THE JOB --- XXXXXXXXXXXXXXXXXXXXX")
          println(" item.workspace: " + item.WORKSPACE)
          println("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
          println(" following methods ain't implemented for WorkflowJob type of Item\nand it will blow out.")
          //see https://javadoc.jenkins.io/hudson/model/FreeStyleProject.html
          println(" customWS: " + item.getCustomWorkspace())
          println(" WS:" + item.getWorkspace())
          item.doDoWipeOutWorkspace()
        }
      }
    } else {
        println(" :::foldername::: " + item.displayName)
        enumerateItems(((Folder) item).getItems())
    }
    println("==========================================================")
  }
}

Results (kinda expected, but disaponting)

as you can see, my script is going to explode on calls of:

item.getCustomWorkspace()
item.getWorkspace()
item.doDoWipeOutWorkspace()

with MissingMethodException

groovy.lang.MissingMethodException: No signature of method: org.jenkinsci.plugins.workflow.job.WorkflowJob.doDoWipeOutWorkspace() is applicable for argument types: () values: []
    at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:58)
    at org.codehaus.groovy.runtime.callsite.PojoMetaClassSite.call(PojoMetaClassSite.java:49)
    at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117)
    at Script1$_enumerateItems_closure1.doCall(Script1.groovy:33)

Simply because those methods ain't available for this type of item, but only for hudson.model.FreeStyleProject

Question: How then I can delete the Pipeline job's workspace?

There is another Jenkins plugin: Workspace Cleanup which is probably used within Jenkinsfile by calling cleanWs() inside a stage() {}, but I didn't figure out how to utilise it from the outside of Jenkinsfile (like from my groovy script ran Jenkins script console).

Is it a bug/request for enhancement of Jenkins Pipeline jobs plugin? Or is there any other way how to cast the item to something from where I would have access to a desired functionality?

DelphyM
  • 322
  • 2
  • 14
  • During my own migration from old style Jenkins projects to declarative pipelines I alo migrated from subversion to git. So I would suggest you to waste a thought on also migrating your version control system. – PowerStat Apr 03 '19 at 12:35
  • 1
    i would say it's not possible because `console script` runs on server and `workflow` runs on nodes... maybe you have to go through nodes and somehow run pipeline there... – daggett Apr 03 '19 at 13:01
  • @PowerStat, yes, that was one of my first initiatives and proposals, but wasn't approved. Those are kind of legacy projects, mainly maintenance and support is happening on them and Jenkins was running on sort of developer's machine. Anyway, I still would like to know (regardless of the background) how to delete Pipeline job's workspace through the means Jenkins provide without directly using Jenkinsfile script (modifying it either in SCM or "Replay" or by modifying in an way the Jenkins job). – DelphyM Apr 03 '19 at 20:01

2 Answers2

3

Alright, investigating this more and googling even more and listening to your ideas (this one particularly inspired me by Daniel Spilker) I have achieved what I wanted, which is:

Independently to CLEAN-UP Pipeline Job's WORKSPACE via Jenkins script console

(only using Jenkins available means and no messing up with Job configuration, nor updating Jenkinsfile, nor replaying)

The code is not surprisingly difficult and for manual demonstration it looks like this:

Jenkins jenkins = Jenkins.instance
Job item = jenkins.getItemByFullName('Sandbox/PipelineTests/SamplePipeline')
println("RootDir: " + item.getRootDir())

for (Node node in jenkins.nodes) {
  // Make sure slave is online
  if (!node.toComputer().online) {
    println "Node '$node.nodeName' is currently offline - skipping workspace cleanup"
    continue
  }

  println "Node '$node.nodeName' is online - performing cleanup:"

  // Do some cleanup
  FilePath wrksp = node.getWorkspaceFor(item)
  println("WRKSP "  + wrksp)
  println("ls " + wrksp.list())
  println("Free space " + wrksp.getFreeDiskSpace())
  println("===== PERFORMING CLEAN UP!!! =====")
  wrksp.deleteContents()
  println("ls now " + wrksp.list())
  println("Free space now " + wrksp.getFreeDiskSpace())
}

Its output, if your job is found, looks like:

Result

RootDir: /var/lib/jenkins/jobs/Sandbox/jobs/PipelineTests/jobs/SamplePipeline
....
.... other node's output noise
....
Node 'mcs-ubuntu-chch' is online - performing cleanup:
WRKSP /var/lib/jenkins/workspace/Sandbox/PipelineTests/SamplePipeline
ls [/var/lib/jenkins/workspace/Sandbox/PipelineTests/SamplePipeline/README.md, /var/lib/jenkins/workspace/Sandbox/PipelineTests/SamplePipeline/.git]
Free space 3494574714880
===== PERFORMING CLEAN UP!!! =====
ls now []
Free space now 3494574919680

Mission completed:)

References

Mainly Jenkins javadoc

Jazzschmidt
  • 989
  • 12
  • 27
DelphyM
  • 322
  • 2
  • 14
2

This is not the most beautiful way, but you could just execute the OS command:

def isWin = Jenkins.instance.windows
def cmd = isWin ? "rmdir /s /q $workspace" : "rm -rf $workspace"

cmd.execute()

If you are using your code just once or are not dealing with multiple OS, you can instead shorten the code to the respective command:

"rm -rf $workspace".execute()
Jazzschmidt
  • 989
  • 12
  • 27
  • Interesting idea and I've learned [a new trick](https://stackoverflow.com/questions/159148/groovy-executing-shell-commands). However I'm struggling to use it even with the minimal implementation from console. Having this: `def cmdsh = isUnix() ? "echo LN " : "echo WIN"` `def cmdbat = isWin ? "echo WIN" : "echo LN"` Just results into either: * `groovy.lang.MissingPropertyException: No such property: isWin for class: Script1` * `groovy.lang.MissingMethodException: No signature of method: Script1.isUnix() is applicable for argument types: () ...` Could you elaborate this a bit. – DelphyM Apr 03 '19 at 20:35
  • 1
    Seems like you forgot to define the `isUnix()` method. In my example `isWin` is just a variable that references the boolean `Jenkins.instance.windows` in order to make the code more readable. – Jazzschmidt Apr 04 '19 at 07:15
  • My bad, you're right @Jazzschmid. Again learned something new (y) – DelphyM Apr 04 '19 at 21:28