Jenkins Pipeline - If/Else Statement with Matrix Name vars - jenkins-pipeline

I dont know why i cant get this to work but could use some help.
I have a Jenkins pipeline with a matrix defined, one of the axis is defined as OS for windows or linux.
In one of my stage's i want to call different things if the OS is linux or windows, but i keep getting an error
java.lang.NoSuchMethodError: No such DSL method '$' found among steps [ArtifactoryGradleBuild, MavenDescriptorStep, acceptGitLabMR, addEmbeddableBadgeConfiguration, addGitLabMRComment,
My Matrix is defined like this
matrix {
agent {
label ("${DUT} && ${OS}")
}
axes {
axis {
name 'BUILD_TYPE'
values 'Internal'
}
axis {
name 'DUT'
values 'DUT'
}
axis {
name 'OS'
values 'windows', 'linux'
}
}
And my stage is defined like this
stage ("Cleaning System") {
steps {
script {
if (${OS} == 'windows') {
stage ('Cleaning Up Windows System') {
echo "WINDOWS CLEAN"
}
}
if (${OS} == 'linux') {
stage ('Cleaning Up Linux System') {
echo "LINUX CLEAN"
}
}
}
}
}
It seems like it doesnt know the ${var} syntax inside a script block? Or what am i doing wrong?
Thanks!

You dont' require a $ in the comparison:
It should be :
if (OS == 'windows') {
...
}
if (OS == 'linux') {
...
}

Related

Problems with creating a pipelineJob environment variables via groovy script

does this https://jenkinsci.github.io/job-dsl-plugin/#path/pipelineJob-environmentVariables-groovy actually work?
Probably more of an plugin issue.
groovy scripts to set env vars seem to work for freestyle job. But I can't get them to work with pipeline jobs.
They don't cause any errors but the environment variable won't get set either.
Anyone else played around with it?
pipelineJob('example') {
environmentVariables {
env('ONE', '1')
env('TWO', '2')
groovy('''
def a = 1
return [THREE: 3 * a]
''')
}
definition {
cps {
script('''
pipeline {
agent any
stages {
stage('Env') {
steps {
sh 'env'
}
}
}
}
''')
}
}
}
thx

exit declarative pipeline prematurely based on script output

While I am aware of this question clean way to exit declarative Jenkins pipeline as success I am to green to understand how to put it to use (from where does the skipBuild variable come?).
I have a script that determines whether the pipeline should continue or not but I am unsure how to piece it together (I am free to construct the script as needed).
pipeline {
agent {
docker { image 'python:3-alpine' }
}
stage('Should I continue') {
steps {
python should_i_continue.py
}
when { ? == true }
stages {
...
}
}
}
I am aware that the capabilities increase tenfold if I use a scripted pipeline but I wonder if it is possible to do what I want with a declarative one?
You can use any custom variable, which you will set as true|false based on some condition in the steps of your pipeline and all stages that need to be executed based on that condition have to have following format:
stage('Should Continue?') {
setBuildStatus("Build complete", "SUCCESS");
when {
expression {skipBuild == true }
}
}
In other words to provide you a bit clean picture, check this abstract example:
node {
skipBuild = false
stage('Checkout') {
...
your checkout code here
...
}
stage('Build something') {
...
some code goes here
skipBuild = true
...
}
stage('Should Continue?') {
setBuildStatus("Build complete", "SUCCESS");
when {
expression {skipBuild == true }
}
}
}

Fetch credentials depending on environment

I can take credentials like explained in the example taken from here - https://jenkins.io/doc/book/pipeline/syntax/#environment
stage('Example') {
environment {
CREDS = credentials('MY_CREDS_DEV')
}
steps {
sh 'echo hello'
}
}
But what I want to do is to get credentials based on some condition.
For example I have MY_CREDS_DEV and MY_CREDS_QA defined in Jenkins credentials. And I have a property ENV=dev defined in Jenkins 'Prepare an environment for the run' section.
I'd like to access credentials based on my environment, i.e. ENV property.
I tried to use CREDS = credentials('MY_CREDS_' + ${ENV}) and tried to extract strings concatenation to a separate function and call it like CREDS = credentials(concatenate(${ENV})) but I got Internal function call parameters must be strings.
So seems I can put only a string to credentials() function which basically means to hardcode it. But how can I choose which credentials to use - dev or qa?
Use CREDS = credentials('MY_CREDS_' + ENV) or CREDS = credentials("MY_CREDS_${ENV}"). ${ENV} will not become 'dev'but ${'dev'} and therefore is no string.
For completeness:
In fact - after playing aroung with the groovy console - it looks like ${ENV} will try to call a function called $ with the closure parameter {ENV} which in turn would return 'dev'. It would give the same result as ENV if you would have defined a function like:
def $(Closure closure) {
closure()
}
But most probably that's not what you wanted to do.
Got this working in Jenkins:2.190.2 with a little groovy. Haven't tested on earlier versions. Just happens to be the one I'm on now. Works fine with multiple stages.
pipeline {
agent {
label "xxxxx"
}
environment {
ROLE = getRole()
}
stages{
stage("write to s3 etc") {
environment {
AWS = credentials("${ROLE}")
}
steps {
script {
sh"""
aws s3 sync build/ "s3://xxxxxxxxxxxx"
"""
}
}
}
}
}
def getRole() {
def branchName = "${env.BRANCH_NAME}"
if (branchName == "xxxxxx") {
return 'some_credential_string'
}
else {
return 'some_other_credential_string'
}
}
If you would like to use different credentials based on the condition, this could be done with the following example:
stage ("Example") {
steps {
script {
if ( params.TEST_PARAMETER == "test_value1" ) {
withCredentials([string(credentialsId: env.CREDENTIALS_1, variable: 'SOME_VARIABLE')]) {
yourFunction()
}
}
else {
withCredentials([string(credentialsId: env.CREDENTIALS_2, variable: 'SOME_VARIABLE')]) {
yourFunction()
}
}
}
}
}
You would need to define yourFunction in the end of your jenkinsfile. In this case, when TEST_PARAMETER is test_value1 in the job, CREDENTIALS_1 will be used from Jenkins credentials list. When TEST_PARAMETER is different, CREDENTIALS_2 credentials will be used. You could have more options by modifying this to the case loop.
Hope this helps.

Gradle Environment variables. Load from file

I am new to Gradle.
Currently I have this task:
task fooTask {
doLast {
exec {
environment 'FOO_KEY', '1234567' // Load from file here!
commandLine 'fooScript.sh'
}
}
}
fooScript.sh
#!/bin/bash
echo $FOO_KEY
Everything works great. But I have env.file with all needed environment variables. This file is used in Docker builder.
env.file
FOO_KEY=1234567
Question: how can I use env.file together with Gradle environment to load all needed env. params?
What about this :
task fooTask {
doLast {
exec {
file('env.file').readLines().each() {
def (key, value) = it.tokenize('=')
environment key, value
}
commandLine 'fooScript.sh'
}
}
}
I give also my version (check if line is not empty and not a comment, also donot override env var):
file('.env').readLines().each() {
if (!it.isEmpty() && !it.startsWith("#")) {
def pos = it.indexOf("=")
def key = it.substring(0, pos)
def value = it.substring(pos + 1)
if (System.getenv(key) == null) {
environment key, value
}
}
}
But actually, I think they should add this feature as a exec plugin property! It's quite common now to use .env file.
The following code is the only one i've been able to produce and which satisfies two of the most importants requirements to provide an efficient "UNIX standard environment file import" in Android studio :
Loads a file which depends of the Build Type (at least : debug and release)
Exposes specified environment variables in the Android code, actually not as environment variables but as buildConfigFields content.
ext {
node_env = ""
}
android.applicationVariants.all { variant ->
if (variant.name == "debug") {
project.ext.set("node_env", "development")
} else if (variant.name == "release") {
project.ext.set("node_env", "production")
}
file("." + node_env + '.env').readLines().each() {
if (!it.isEmpty() && !it.startsWith("#")) {
def pos = it.indexOf("=")
def key = it.substring(0, pos)
def value = it.substring(pos + 1)
if (System.getProperty(key) == null) {
System.setProperty("env.$key", value)
}
}
}
if (variant.name == "release") {
android.signingConfigs.release.storeFile file(System.getProperty("env.ANDROID_APP_SIGNING_STOREFILE"))
android.signingConfigs.release.keyAlias System.getProperty("env.ANDROID_APP_SIGNING_KEYALIAS")
android.signingConfigs.release.storePassword System.getProperty("env.ANDROID_APP_SIGNING_STOREPASSWORD")
android.signingConfigs.release.keyPassword System.getProperty("env.ANDROID_APP_SIGNING_KEYPASSWORD")
}
android.defaultConfig.buildConfigField "String", "ANDROID_APP_URL", "\"${System.getProperty("env.ANDROID_APP_URL")}\""
}
Kotlin :
Log.i(TAG, BuildConfig.ANDROID_APP_URL)
Please let me know what you think of it as i'm not completly sure how it works, especially to select the good file to load.
There are plugins to load env vars from a .env file (e.g. this one)
So a sample build file will look something like this (Kotlin DSL)
plugins {
id("co.uzzu.dotenv.gradle") version "1.1.0"
}
tasks.withType<Test> {
useJUnitPlatform()
//will pass the env vars loaded by the plugin to the environment of the tests
environment = env.allVariables
}
I have ended up doing it in my gradlew file. A possible drawback is that the change tends to be overwritten on upgrades to gradle.
# Hack: export all variables in the .env file
#
ENV_FILE=../../.env
if [ ! -f $ENV_FILE ];then
echo "WARNING/DEV ENV Missing a ${ENV_FILE} file with environment variables (secrets)";
fi
for secret in `cat $ENV_FILE`;do export $secret;done
If you're using Spring Boot bootRun task or anything that has a runner
tasks.named('bootRun') {
doFirst {
file('.env').readLines().each() {
def (key, value) = it.tokenize('=')
environment key, value
}
}
}

How to detect the current OS from Gradle

I found this answer about how to do it with Groovy:
Detecting the platform (Window or Linux) by Groovy/Grails:
if (System.properties['os.name'].toLowerCase().contains('windows')) {
println "it's Windows"
} else {
println "it's not Windows"
}
Is there a better way?
Actually, I looked at the Gradle project, and this looks a little cleaner as it uses Ant's existing structure:
import org.apache.tools.ant.taskdefs.condition.Os
task checkWin() << {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
println "*** Windows "
}
}
I found this in the following Gradle branch, and it seems to work nicely. gradle/gradle-core/branches/RB-0.3/build.gradle
Mid 2020 Update:
Still incubating:
OperatingSystem os = org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem;
Early 2019 Update: current() removed.
org.gradle.nativeplatform.platform.OperatingSystem.getDisplayName()
org.gradle.nativeplatform.platform.OperatingSystem.isLinux()
Keep in mind that it's still incubating though.
Mid 2018 Update: just like it was mentioned in comments, now this class moved to a different package, so one should use org.gradle.nativeplatform.platform.OperatingSystem.current()
As of mid 2015, Peter Kahn's answer is still valid. Environment-based profile activation is still something done relatively easier in Maven. But keep in mind that org.apache.tools.ant.taskdefs.condition.Os.isFamily is not exclusive in the sense that if it returns true with one particular parameter it is not necessarily means that it returns false for any other parameter. For instance:
import org.apache.tools.ant.taskdefs.condition.Os
task detect {
doLast {
println(Os.isFamily(Os.FAMILY_WINDOWS))
println(Os.isFamily(Os.FAMILY_MAC))
println(Os.isFamily(Os.FAMILY_UNIX))
}
}
It will return true both for Os.FAMILY_MAC and Os.FAMILY_UNIX on MacOS. Usually it is not something you need in build scripts.
There is though another way to achieve this using Gradle 2+ API, namely:
import org.gradle.internal.os.OperatingSystem;
task detect {
doLast {
println(OperatingSystem.current().isMacOsX())
println(OperatingSystem.current().isLinux())
}
}
Check out the documentation for the org.gradle.nativeplatform.platform.OperatingSystem interface. It is worth to mention that this interface is marked with incubating annotation, that is, "the feature is currently a work-in-progress and may change at any time". The "internal" namespace in the implementation also gives us a hint that we should use this knowing that this can change.
But personally I'd go with this solution. It's just that it's better to write a wrapper class so as not to mess up in case something will change in the future.
One can differentiate the build environment in between Linux, Unix, Windows and OS X - while the Gradle nativeplatform.platform.OperatingSystem differentiates the target environment (incl. FreeBSD and Solaris).
import org.gradle.internal.os.OperatingSystem
OperatingSystem os = OperatingSystem.current();
println "*** Building on ${os.familyName} / ${os.name} / ${os.version} / ${System.getProperty("os.arch")}."
println "*** Building on ${os.toString()}."
if (os.isLinux()) {
// Consider Linux.
} else if (os.isUnix()) {
// Consider UNIX.
} else if (os.isWindows()) {
// Consider Windows.
} else if (os.isMacOsX()) {
// Consider OS X.
} else {
// Unknown OS.
}
One can also use an Ant task (source):
import org.apache.tools.ant.taskdefs.condition.Os
task checkWin() << {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
// Consider Windows.
}
}
Or you can define osName as a string...
import org.gradle.internal.os.OperatingSystem
switch (OperatingSystem.current()) {
case OperatingSystem.LINUX:
project.ext.osName = "Linux";
break;
case OperatingSystem.MAC_OS:
project.ext.osName = "macOS";
break;
case OperatingSystem.WINDOWS:
project.ext.osName = "Windows";
break;
}
... and use it later - to include a native library for example:
run {
systemProperty "java.library.path", "lib/$osName"
}
But it wouldn't change anything since OperatingSystem works exactly like your code:
public static OperatingSystem forName(String os) {
String osName = os.toLowerCase();
if (osName.contains("Windows")) {
return WINDOWS;
} else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) {
return MAC_OS;
} else if (osName.contains("sunos") || osName.contains("solaris")) {
return SOLARIS;
} else if (osName.contains("linux")) {
return LINUX;
} else if (osName.contains("freebsd")) {
return FREE_BSD;
} else {
// Not strictly true
return UNIX;
}
}
Source: https://github.com/gradle/gradle/blob/master/subprojects/base-services/src/main/java/org/gradle/internal/os/OperatingSystem.java
Edit:
You can do the same for the architecture:
project.ext.osArch = OperatingSystem.current().getArch();
if ("x86".equals(project.ext.osArch)) {
project.ext.osArch = "i386";
}
and:
run {
systemProperty "java.library.path", "lib/$osName/$osArch"
}
Just be aware that getArch() will return:
"ppc" on PowerPC
"amd64" on 64b
"i386" OR "x86" on 32b.
getArch() will return "x86" on Solaris or "i386" for any other platform.
Edit 2:
Or if you want to avoid any import, you can simply do it yourself:
def getOsName(project) {
final String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("linux")) {
return ("linux");
} else if (osName.contains("mac os x") || osName.contains("darwin") || osName.contains("osx")) {
return ("macos");
} else if (osName.contains("windows")) {
return ("windows");
} else if (osName.contains("sunos") || osName.contains("solaris")) {
return ("solaris");
} else if (osName.contains("freebsd")) {
return ("freebsd");
}
return ("unix");
}
def getOsArch(project) {
final String osArch = System.getProperty("os.arch");
if ("x86".equals(osArch)) {
return ("i386");
}
else if ("x86_64".equals(osArch)) {
return ("amd64");
}
else if ("powerpc".equals(osArch)) {
return ("ppc");
}
return (osArch);
}
Gradle doesn't provide a public API for detecting the operating system. Hence the os. system properties are your best bet.
I don't like detecting the OS in Gradle through properties or an Ant task, and the OperatingSystem class no longer contains the current() method.
So, in my opinion, the cleanest way to detect the OS would be:
Import DefaultNativePlatform:
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
Then use DefaultNativePlatform in your task:
if (DefaultNativePlatform.getCurrentOperatingSystem().isWindows()) {
println 'Windows'
}
Mind that this method is not ideal as it is using the Gradle internal API.
It was tested with Gradle 4.10.
Without any imports I got those values from System class like here:
def osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH)
def osArch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH)
def osVersion = System.getProperty("os.version").toLowerCase(Locale.ENGLISH)
This worked for me in 2022/2023 with Gradle 7.5
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
OperatingSystem os = DefaultNativePlatform.currentOperatingSystem;
os.isLinux()
os.isWindows()
os.isMacOsX()
According to #shabunc answer, some of your options are:
org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
org.apache.tools.ant.taskdefs.condition.Os
org.gradle.nativeplatform.platform.OperatingSystem
org.gradle.internal.os.OperatingSystem
Number 3 and 4 are basically the same interface, in which OperatingSystem.current() is actually OperatingSystem.isCurrent() (because of Groovy). So it did not work for me.
Number 2 is a viable option but not that elegant IMHO.
So this is how you can use option 1 (as an example was not included):
import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform
def os = DefaultNativePlatform.currentOperatingSystem
def arch = DefaultNativePlatform.currentArchitecture
def version = "1.0.0"
switch (true) {
case os.windows && arch.i386:
implementation "com.example:example-win32-x86:${version}"
break
case os.windows && arch.amd64:
implementation "com.example:example-win32-x86-amd64:${version}"
break
case os.macOsX && arch.amd64:
implementation "com.example:example-darwin-x86-amd64:${version}"
break
case os.linux && arch.i386:
implementation "com.example:example-linux-x86:${version}"
break
case os.linux && arch.amd64:
implementation "com.example:example-linux-x86-amd64:${version}"
break
default:
println "No suitable driver found for " +
"current OS (${os.displayName}) and " +
"architecture (${arch.displayName})"
}

Resources