9

I have set up some folders (Using Cloudbees Folder Plugin).

It sounds like the simplest possible command to be able to tell Jenkins: Build every job in Folder X.

I do not want to have to manually create a comma-separated list of every job in the folder. I do not want to add to this list whenever I want to add a job to this folder. I simply want it to find all the jobs in the folder at run time, and try to build them.

I'm not finding a plugin that lets me do that.

I've tried using the Build Pipeline Plugin, the Bulk Builder Plugin, the MultiJob plugin, and a few others. None seem to support the use case I'm after. I simply want any Job in the folder to be built. In other words, adding a job to this build is as simple as creating a job in this folder.

How can I achieve this?

JRG
  • 4,037
  • 3
  • 23
  • 34
Steve Cohen
  • 4,679
  • 9
  • 51
  • 89

7 Answers7

5

I've been using Jenkins for some years and I've not found a way of doing what you're after.

The best I've managed is: I have a "run every job" job (which contains a comma-separated list of all the jobs you want). Then I have a separate job that runs periodically and updates the "run every job" job as new projects come and go.

Wayne Booth
  • 424
  • 2
  • 8
  • Thanks. I was hoping not to have to go that route.But if I have to, I have to. – Steve Cohen May 25 '16 at 10:52
  • If you found an answer provided here helpful, please upvote and/or mark as accepted, so that other users with a similar question can benefit. – Wayne Booth May 29 '16 at 08:54
  • @Wayne_Booth Your answer was useful and I voted it up, but can you be a bit more specific about what form this would take? Does it actually modify the "run every job" jobs' config.xml? – Steve Cohen Jun 02 '16 at 17:53
  • @Wayne_Booth I have gotten this to work by creating a build that iterates the list of all projects in the folder and adds each to the configuration of the job that launches all jobs. It then launches the all-job-launcher. But this doesn't work because I am not forcing the configuration of that job to reload before launching it. Not sure if it's possible to do that or how. – Steve Cohen Jun 13 '16 at 15:26
4

One way to do this is to create a Pipeline job that runs Groovy script to enumerate all jobs in the current folder and then launch them.

The version below requires the sandbox to be disabled (so it can access Jenkins.instance).

def names = jobNames()
for (i = 0; i < names.size(); i++) {
    build job: names[i], wait: false
}

@NonCPS
def jobNames() {
  def project = Jenkins.instance.getItemByFullName(currentBuild.fullProjectName)
  def childItems = project.parent.items

  def targets = []
  for (i = 0; i < childItems.size(); i++) {
      def childItem = childItems[i]
      if (!childItem instanceof AbstractProject) continue;
      if (childItem.fullName == project.fullName) continue;

      targets.add(childItem.fullName)
  }

  return targets
}

If you use Pipeline libraries, then the following is much nicer (and does not require you to allow a Groovy sandbox escape:

Add the following to your library:

package myorg;

public String runAllSiblings(jobName) {
  def names = siblingProjects(jobName)
  for (def i = 0; i < names.size(); i++) {
    build job: names[i], wait: false
  }
}

@NonCPS
private List siblingProjects(jobName) {
  def project = Jenkins.instance.getItemByFullName(jobName)
  def childItems = project.parent.items

  def targets = []
  for (def i = 0; i < childItems.size(); i++) {
    def childItem = childItems[i]
    if (!childItem instanceof AbstractProject) continue;
    if (childItem.fullName == jobName) continue;

    targets.add(childItem.fullName)
  }
  return targets
}

And then create a pipeline with the following code:

(new myorg.JobUtil()).runAllSiblings(currentBuild.fullProjectName)

Yes, there are ways to simplify this further, but it should give you some ideas.

Ben Walding
  • 4,006
  • 2
  • 30
  • 28
  • This looks good. I haven't used Pipeline before, but the ideas here are similar to mine. – Steve Cohen Feb 16 '18 at 23:34
  • 1
    Perfect for my use case. I have many jobs in a single folder that run various tests. I set `wait: false`, and `propagate: false` so all jobs are processed sequentially, and the parent job doesn't fail if one of the child jobs does. – blindsnowmobile Jun 18 '19 at 20:10
0

I developed a Groovy script that does this. It works very nicely. There are two Jobs, initBuildAll, which runs the groovy script and then launches the 'buildAllJobs' jobs. In my setup, I launch the InitBuildAll script daily. You could trigger it another way that works for you. We aren't full up CI, so daily is good enough for us.

One caveat: these jobs are all independent of one another. If that's not your situation, this may need some tweaking.

These jobs are in a separate Folder called MultiBuild. The jobs to be built are in a folder called Projects.

import com.cloudbees.hudson.plugins.folder.Folder
import javax.xml.transform.stream.StreamSource
import hudson.model.AbstractItem
import hudson.XmlFile
import jenkins.model.Jenkins


    Folder findFolder(String folderName) {
      for (folder in Jenkins.instance.items) {
        if (folder.name == folderName) {
          return folder
        }
      }
      return null
    }

    AbstractItem findItem(Folder folder, String itemName) {
      for (item in folder.items) {
        if (item.name == itemName) {
          return item
        }
      }
      null
    }


    AbstractItem findItem(String folderName, String itemName) {
      Folder folder = findFolder(folderName)
      folder ? findItem(folder, itemName) : null
    }

    String listProjectItems() {
      Folder projectFolder = findFolder('Projects')
      StringBuilder b = new StringBuilder()
      if (projectFolder) {

        for (job in projectFolder.items.sort{it.name.toUpperCase()}) {
          b.append(',').append(job.fullName)
        }
        return b.substring(1) // dump the initial comma
      }
      return b.toString()
    }

    File backupConfig(XmlFile config) {
      File backup = new File("${config.file.absolutePath}.bak")
      FileWriter fw = new FileWriter(backup)
      config.writeRawTo(fw)
      fw.close()
      backup
    }


    boolean updateMultiBuildXmlConfigFile() {
      AbstractItem buildItemsJob = findItem('MultiBuild', 'buildAllProjects')
      XmlFile oldConfig = buildItemsJob.getConfigFile()

      String latestProjectItems = listProjectItems()
      String oldXml = oldConfig.asString()
      String newXml = oldXml;
      println latestProjectItems
      println oldXml

      def mat = newXml =~ '\\<projects\\>(.*)\\<\\/projects\\>'
      if (mat){
        println mat.group(1)
        if (mat.group(1) == latestProjectItems) {
           println 'no Change'
           return false;
        } else {
          // there's a change
             File backup = backupConfig(oldConfig)
           def newProjects = "<projects>${latestProjectItems}</projects>"
           newXml = mat.replaceFirst(newProjects)
           XmlFile newConfig = new XmlFile(oldConfig.file)
           FileWriter nw = new FileWriter(newConfig.file)
           nw.write(newXml)
           nw.close()
           println newXml
           println 'file updated'
           return true
        }
      }
      false
    }

    void reloadMultiBuildConfig() {
      AbstractItem job = findItem('MultiBuild', 'buildAllProjects')

      def configXMLFile = job.getConfigFile();
      def file = configXMLFile.getFile();

      InputStream is = new FileInputStream(file);

      job.updateByXml(new StreamSource(is));
      job.save();

      println "MultiBuild Job updated"

    }

    if (updateMultiBuildXmlConfigFile()) {
      reloadMultiBuildConfig()
    }
Steve Cohen
  • 4,679
  • 9
  • 51
  • 89
0

A slight variant on Wayne Booth's "run every job" approach. After a little head scratching I was able to define a "run every job" in Job DSL format.

The advantage being I can maintain my job configuration in version control. e.g.

job('myfolder/build-all'){
   publishers {
         downstream('myfolder/job1')
         downstream('myfolder/job2')
         downstream('myfolder/job2')
   }
}
Mark McLaren
  • 11,470
  • 2
  • 48
  • 79
0

Pipeline Job

When running as a Pipeline job you may use something like:

echo jobNames.join('\n')
jobNames.each {
    build job: it, wait: false
}

@NonCPS
def getJobNames() {
  def project = Jenkins.instance.getItemByFullName(currentBuild.fullProjectName)
  project.parent.items.findAll {
      it.fullName != project.fullName && it instanceof hudson.model.Job
  }.collect { it.fullName }
}


Script Console

Following code snippet can be used from the script console to schedule all jobs in some folder:

import hudson.model.AbstractProject

Jenkins.instance.getAllItems(AbstractProject.class).each {
  if(it.fullName =~ 'path/to/folder') {
    (it as AbstractProject).scheduleBuild2(0)
  }
}

With some modification you'd be able to create a jenkins shared library method (requires to run outside the sandbox and needs @NonCPS), like:

import hudson.model.AbstractProject

@NonCPS
def triggerItemsInFolder(String folderPath) {
  Jenkins.instance.getAllItems(AbstractProject.class).each {
    if(it.fullName =~ folderPath) {
      (it as AbstractProject).scheduleBuild2(0)
    }
  }
}
Joerg S
  • 4,730
  • 3
  • 24
  • 43
0

Reference pipeline script to run a parent job that would trigger other jobs as suggested by @WayneBooth

pipeline {
    agent any
    stages {
 
        stage('Parallel Stage') {
            parallel {
                stage('Parallel 1') {
                    steps {
                        build(job: "jenkins_job_1")
                    }
                }
                stage('Parallel 2') {
                    steps {
                        build(job: "jenkins_job_2")
                    }
                }
            }
        }
}
JKC
  • 47
  • 8
0

The best way to run an ad-hoc command like that would be using the Script Console (can be found under Manage Jenkins).

The console allows running Groovy Script - the script controls Jenkins functionality. The documentation can be found under Jenkins JavaDoc.

A simple script triggering immediately all Multi-Branch Pipeline projects under the given folder structure (in this example folder/subfolder/projectName):

import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject
import hudson.model.Cause.UserIdCause

Jenkins.instance.getAllItems(WorkflowMultiBranchProject.class).findAll {
    return it.fullName =~ '^folder/subfolder/'
}.each {
    it.scheduleBuild(0, new UserIdCause())
}

The script was tested against Jenkins 2.324.

Yuri
  • 4,254
  • 1
  • 29
  • 46