How to determine the current operating system in a Jenkins pipeline
Asked Answered
R

5

18

What would be the way to determine the current OS a Jenkins pipeline is running?

Context: I'm building a shared Jenkins pipeline script that should run on all platforms (windows, OSX, linux) and execute something different in each platform.

I tried something like:

import org.apache.commons.lang.SystemUtils

if (SystemUtils.IS_OS_WINDOWS){
   bat("Command")
}
if (SystemUtils.IS_OS_MAC){
   sh("Command")
}
if (SystemUtils.IS_OS_LINUX){
   sh("Command")
}

But even it is running on windows or mac node it always goes into the SystemUtils.IS_OS_LINUX branch

I tried a quick pipeline like this.

node('windows ') {
     println ('## OS ' + System.properties['os.name'])
}
node('osx ') {
     println ('## OS ' + System.properties['os.name'])
}
node('linux') {
     println ('## OS ' + System.properties['os.name'])
}

Each node get correctly run in a machine with the correct OS but all of them print ## OS Linux

any ideas?

Thanks Fede

Reiter answered 22/5, 2017 at 6:16 Comment(1)
How do you test your pipeline?Hydrargyrum
B
17

As far as I know Jenkins only differentiates between windows and unix, i.e. if on windows, use bat, on unix/mac/linux, use sh. So you could use isUnix(), more info here, to determine if you're on unix or windows, and in the case of unix use sh and @Spencer Malone's answer to prope more information about that system (if needed).

Benevento answered 29/5, 2017 at 12:35 Comment(1)
Thanks @Jon S, that's the best answer and works perfectly.Reiter
F
22

Assuming you have Windows as your only non-unix platform, you can use the pipeline function isUnix() and uname to check on which Unix OS you're on:

def checkOs(){
    if (isUnix()) {
        def uname = sh script: 'uname', returnStdout: true
        if (uname.startsWith("Darwin")) {
            return "Macos"
        }
        // Optionally add 'else if' for other Unix OS  
        else {
            return "Linux"
        }
    }
    else {
        return "Windows"
    }
}
Faires answered 27/6, 2018 at 9:55 Comment(1)
I initially appreciated this solution but after I found some problems calling it outside of a pipeline. See my anserTycho
B
17

As far as I know Jenkins only differentiates between windows and unix, i.e. if on windows, use bat, on unix/mac/linux, use sh. So you could use isUnix(), more info here, to determine if you're on unix or windows, and in the case of unix use sh and @Spencer Malone's answer to prope more information about that system (if needed).

Benevento answered 29/5, 2017 at 12:35 Comment(1)
Thanks @Jon S, that's the best answer and works perfectly.Reiter
R
3

The workaround I found for this is

try{
   sh(script: myScript, returnStdout: true)
 }catch(Exception ex) {
    //assume we are on windows
   bat(script: myScript, returnStdout: true)
 }

Or a little bit more elegant solution without using the try/catch is to use the env.NODE_LABELS. Assuming you have all the nodes correctly labelled you can write a function like this

def isOnWindows(){
    def os = "windows"
    def List nodeLabels  = NODE_LABELS.split()
    for (i = 0; i <nodeLabels.size(); i++) 
    {
        if (nodeLabels[i]==os){
        return true
        }
   }
    return false
 }

and then

if (isOnWindows()) {
    def osName = bat(script: command, returnStdout: true)   
} else {
    def osName = sh(script: command, returnStdout: true)
}
Reiter answered 29/5, 2017 at 6:14 Comment(0)
T
3

EDIT: Added support for returning the right answer also when running in agents

I initially used @fedterzi answer but I found it problematic because it caused the following crash:

org.jenkinsci.plugins.workflow.steps.MissingContextVariableException: Required context class hudson.Launcher is missing

when attempting to call isUnix() outside of a pipeline (for example assigning a variable). I solved by combining a check on Java system properties together testing the output of uname in the actual executing node to determine the OS:

def getOs()
{
    // Determine if I'm running in the Built-In node
    def nodeName = env.NODE_NAME;
    if (nodeName == null || nodeName == 'master' || nodeName == 'built-in')
    {
        // If so determine the OS using JDK system properties 
        String osname = System.getProperty('os.name');
        if (osname.startsWith('Windows'))
            return 'windows';
        else if (osname.startsWith('Mac'))
            return 'macos';
        else if (osname.contains("nux"))
            return 'linux';
        else
            throw new Exception("Unsupported os: ${osname}");
    }
    else
    {
        if (isUnix())
        {
            // See https://mcmap.net/q/652247/-how-to-determine-the-current-operating-system-in-a-jenkins-pipeline
            // See https://mcmap.net/q/351270/-how-to-disable-command-output-in-jenkins-pipeline-build-logs
            def uname = sh(script:"#!/bin/sh -e\nuname", returnStdout: true).trim();
            if (uname.startsWith('Darwin'))
                return 'macos';
            else if (uname.contains("Linux"))
                return 'linux';
            else
                throw new Exception("Unsupported os: ${uname}");
        }
        else
        {
            return 'windows';
        }
    }
}

This allowed the function to be called in any pipeline context and returning the expected result also when running in agents. Be warned that when called out of the pipeline block it will return the OS of the controller node. Note: the function must be declared in a Jenkins shared library, to bypass security restrictions. That is, save the above code in the library repository as vars/utils.groovy and then you can use it in a pipeline as it follows:

@Library('MySharedLibrary@master') _

// The following returns the OS of the controller,
// in my case 'linux'
echo("${utils.getOs()}")

pipeline {
    agent none

    stages {
        stage('Arch') {
            parallel {
                stage('MacOs') {
                    agent {
                        label 'macos'
                    }
                    steps {
                        script {
                            echo "${utils.getOs()}"  // Prints 'macos'
                        }
                    }
                }
        
                stage('Windows') {
                    agent {
                        label 'Windows'
                    }
                    steps {
                        script {
                            echo "${utils.getOs()}"  // Prints 'windows'
                        }
                    }
                }
            }
        }
    }
}
Tycho answered 3/4, 2020 at 10:3 Comment(8)
Can you show a complete example of a working pipeline using this method? I cannot makes this workGiraffe
Just declare it outside of the pipeline and call it. Here is an example of calling a function with a return in a pipeline.Tycho
Thanks, now I just need to figure out how to handle Scripts not permitted to use staticMethod java.lang.SystemGiraffe
Good point: depending on the permissions of the users that run the pipeline you may have some restrictions on the ability to run arbitrary scripts. I may actually have similar restrictions to yours in my setup but I solve this problem by using jenkins shared libraries, which is not exactly trivial to add and requires administrator permissions. Best would be find a supported way to retrieve system properties in .jenkins, which I hope it exists.Tycho
This is wrong. The System.getProperty('os.name') will be executed on the Jenkins controller, not the node. We need to use a function like sh that will be executed on the node.Altercate
@ChrisSuszyński good point. This answer was good for me since I didn't use controller/node mode (I still don't). Be sure to post your answer if you manage to create a functionality that works for nodes too.Tycho
@ChrisSuszyński I added the support for determining the OS of the actual executing agent. I kindly ask for a review of the negative votes, since this fully address the issue on agents and still allow the function to be called in any context (which it was the main point)Tycho
@Giraffe I added an usage sample that relies on a Jenkins shared library,Tycho
U
0

Using Java classes is probably not the best approach. I'm pretty sure that unless it's a jenkins / groovy plugin, those run on the master Jenkins JVM thread. I would look into a shell approach, such as the one outlined here: https://mcmap.net/q/20158/-how-to-detect-the-os-from-a-bash-script

You could wrap that script in a shell step to get the stdout like so:

def osName = sh(script: './detectOS', returnStdout: true)

to call a copy of the script being outlined above. Then just have that script return the OS names you want, and branch logic based on the osName var.

Underlaid answered 22/5, 2017 at 12:16 Comment(2)
Hi @spencer-malone, Thanks for your response, but that does not work. sh() only works on linux and mac, if that is called in a Windows host it fails with: ` : java.io.IOException: Cannot run program "nohup" (in directory "C:\hudson\workspace\UT_jenkins_pipeline-5OP3ADAIV7CDIC63NI2TISW764YRHIQ2D5YCXCVKLV2UJ32W5XNA"): CreateProces` .Reiter
Sorry, should have specified that msys is probably required.Underlaid

© 2022 - 2024 — McMap. All rights reserved.