26

I'm creating a Jenkins pipeline job and I need to run a job on all nodes labelled with a certain label.

Therefore I'm trying to get a list of node names assigned with a certain label. (With a node I can get the labels with getAssignedLabels())

The nodes-list in jenkins.model.Jenkins.instance.nodes seems not contain the master-node which I need to include in my search.

My current solution is to iterate over the jenkins.model.Jenkins.instance.computers and use the getNode()-method to get the node. This works, but in the javadoc of Jenkins I'm reading the this list might not be up-to-date.

In the long-run I will add (dynamically) cloud-nodes and I'm afraid that I won't be able to use computers then.

What is the right way to get the list of all current nodes?

This is what I'm doing right now:

@NonCPS
def nodeNames(label) {
    def nodes = []
    jenkins.model.Jenkins.instance.computers.each { c ->
        if (c.node.labelString.contains(label)) {
            nodes.add(c.node.selfLabel.name)
        }
    }   
    return nodes
}
simon.denel
  • 780
  • 6
  • 23
Patrick B.
  • 11,773
  • 8
  • 58
  • 101

11 Answers11

18

Updated answer: in a pipeline use nodesByLabel to get all nodes assigned to a label.

towel
  • 2,094
  • 1
  • 20
  • 30
15

This is the way I'm doing it right now. I haven't found anything else:

@NonCPS
def hostNames(label) {
  def nodes = []
  jenkins.model.Jenkins.get().computers.each { c ->
    if (c.node.labelString.contains(label)) {
      nodes.add(c.node.selfLabel.name)
    }
  }
  return nodes
}

jenkins.model.Jenkins.get.computers contains the master-node and all the slaves.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Patrick B.
  • 11,773
  • 8
  • 58
  • 101
  • 1
    @Oliver changed the code from `Jenkins.instance.computers` to `Jenkins.get.computers` - how he known what he's doing. – Patrick B. Jun 14 '20 at 15:43
  • Luke, read the source! https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/jenkins/model/Jenkins.java#L818 – Oliver Jun 15 '20 at 11:25
  • 2
    Your code results in `groovy.lang.MissingPropertyException: No such property: get for class: jenkins.model.Jenkins`. Please change `jenkins.model.Jenkins.get` to `jenkins.model.Jenkins.get()` – Boris Sep 25 '20 at 16:24
7

Update to @patrick-b answer : contains can be buggy if you have labels containing same string, I've added a split step do check every label separated with spaces.

@NonCPS
def hostNames(label) {
    def nodes = []
    jenkins.model.Jenkins.get.computers.each { c ->
        c.node.labelString.split(/\s+/).each { l ->
            if (l != null && l.equals(label)) {
                nodes.add(c.node.selfLabel.name)
             }
        }
    }

    return nodes
}
Oliver
  • 3,815
  • 8
  • 35
  • 63
k4cy
  • 322
  • 3
  • 8
  • 1
    The code above always returned an empty list for me. The `l.equals(label)` actually needed to be `(l == label)'. I also added an 'if ( c.isOnline() )` check to ensure only online slaves were returned. – Nick Holt May 20 '19 at 16:24
7

Here is a functional solution which is more readable and concise:

def nodes = jenkins.model.Jenkins.get().computers
  .findAll{ it.node.labelString.contains(label) }
  .collect{ it.node.selfLabel.name }

You can verify it in the Jenkins Script Console.

Boris
  • 22,667
  • 16
  • 50
  • 71
2

using nodesByLabel as pointed out by @towel is probably the solution in most cases. One limitation I found with nodesByLabel is that there is no way to indiscriminately select all nodes. I can't use any of the other solutions either because of script security, some of these can be pretty dangerous, so I preferred not to approve them for usage.

As an alternatively, you can add a function as a pipeline library, which will allow usage of these functions. Since pipeline libraries can be set up so they are fully under the administrator's control, it's safer to go with this route. To do so, set up a pipeline library (I don't think it matters if it's global or not, but for me it is). Then add the following contents to the file vars/parallelRunOnNodes.groovy:

def call(Closure callback) {
    parallel jenkins.model.Jenkins.get().computers.collectEntries { agent ->
        def nodeLabel = agent.node.selfLabel.name
        ["${nodeLabel}": {
            node("${nodeLabel}") {
                stage("${nodeLabel}") {
                    callback(nodeLabel)
                }
            }
        }]
    }
}

Which can then be used as follows:

pipeline {
    agent none
    stages {
        stage('Parallel on all nodes') {
            steps {
                parallelRunOnNodes { nodeLabel ->
                    println(nodeLabel)
                }
            }
        }
    }
}

Obviously adjust as you see fit, e.g. you can add additional parameters to filter, maybe you don't care about parallel etc.

GManz
  • 1,548
  • 2
  • 21
  • 42
  • Great solution! I was trying to edit it so it can only create stages for nodes with labels containing a specific word. Can you help please? I tried adding `agent.node.labelString.contains("label-")` but it fails with `No such property: Entry for class: java.util.Map` I think because *contains* returns boolean not Map. – Pamela Sarkisyan Feb 09 '22 at 18:40
  • 1
    @PamelaSarkisyan you want to filter the nodes first prior to the `collectEntries` call. Something like `jenkins.model.Jenkins.get().computers.findAll { it.node.selfLabel.name.contains("label-) }.collectEntries ...` – GManz Feb 16 '22 at 06:32
1

Try using for (aSlave in hudson.model.Hudson.instance.slaves) {} and aSlave.getLabelString()); to get all the labels for all of your nodes. You can construct a list of nodes per label this way.

kirkpatt
  • 625
  • 5
  • 21
1

I think that you can do this with:

def nodes = Jenkins.get.getLabel('my-label').getNodes()
for (int i = 0; i < nodes.size(); i++) {
    node(nodes[i].getNodeName()) {
        // on node
    }
}

I don't know for sure whether this works with cloud nodes.

Oliver
  • 3,815
  • 8
  • 35
  • 63
Russell Gallop
  • 1,631
  • 2
  • 17
  • 34
  • Yes. It does work with cloud nodes. We use something like this: `nodes = Jenkins.instance.getLabel('GO_BUILDER||BASIC_SLAVE').getNodes().collect{it.getNodeName()}` BASIC_SLAVEs are Cloud nodes in our case. You can even include a label on your Master, but beware, master node returns as an empty string! – Steven the Easily Amused Jan 20 '20 at 07:15
0

Here is my answer

String labelIWantServersOf = "XXXX"; // This is the label assosiated with nodes for which i want the server names of
List serverList = [];

for (aSlave in hudson.model.Hudson.instance.slaves) {          
  if (aSlave.getLabelString().indexOf(labelIWantServersOf ) > -1) {
     if(!aSlave.getComputer().isOffline() ){
          serverList.add(aSlave.name);        
     }
  }    
}

return serverList;
Andy
  • 434
  • 5
  • 7
0

Another wat to get Labels and Display Name of nodes

def jenkins = Jenkins.instance
def computers = jenkins.computers
computers.each {
   println "${it.displayName} ${it.hostName}"
}

def labels = jenkins.getLabels()
labels.each {
   println "${it.displayName}"
}
Yakir GIladi Edry
  • 2,511
  • 2
  • 17
  • 16
0

I combined the answers from the original question and https://stackoverflow.com/a/54145233/1817610 and saved it as a shared library method

vars/nodeNames.groovy

def call(String label) {
    def nodes = []
    jenkins.model.Jenkins.instance.computers.each { c ->
        c.node.labelString.split(/\s+/).each { l ->
            if (l != null && l.equals(label)) {
                nodes.add(c.node.selfLabel.name)
             }
        }
    }
    return nodes
}

Once the shared library is configured, see https://www.jenkins.io/doc/book/pipeline/shared-libraries/, this can be used as

@Library("my-jenkins-shared-lib") _

print ("linux pipeline can run on" + nodeNames("linux"))
carl verbiest
  • 1,113
  • 1
  • 15
  • 30
-2

This is one of the top Google hits for how to list nodes on a Jenkins server. If you're just looking for the list of nodes, it can be viewed at the following server URL:

http://JENKINS_HOSTNAME:JENKINS_PORT/computer/

The result is a table displaying the name, OS, JVM version, clock sync status, remoting version and response time of known nodes. It also displays whether the node image (well, JAR) is significantly outdated or subject to error/security alerts.

If there is no access available to API calls, the URL can always be scraped and parsed to get the list of nodes and any of the other data included in the table.

ingyhere
  • 11,818
  • 3
  • 38
  • 52