How to read contents from properties file from Jenkins pipeline - jenkins

I have a Jenkins pipeline script and it has to read the contents from a properties one by one which is having as a key-value pair. I need to split key and value separately. Below are the properties and groovy file which I am using.
To be read file (mypropsfile.properties) :
product1=workspacename1:path1/path2/path3
product2=workspacename2:path1/path2/path3
product3=workspacename3:path1/path2/path3
My groovy file:
stage('readfromfile') {
steps {
script{
def readpropscontent = readProperties file: 'mypropsfile.properties'
echo 'readpropscontent ::: '+readpropscontent
for (String item : readpropscontent) {
echo "item ::: "+item
def readpropscontentfile2 = item.split("=")[0];
echo 'readpropscontentfile2 ::: '+readpropscontentfile2
}
}
}
}
Updated groovy file to split the value:
def readpropscontent = readFile file: 'mypropsfile.properties'
echo 'readpropscontent ::: '+readpropscontent
for (String item : readpropscontent.split('\n')) {
echo "item ::: "+item
def PropsFileValue = item.split("=")[1];
echo 'PropsFileValue ::: '+PropsFileValue
for (String splittingparams : PropsFileValue) {
def path1= splittingparams.split(":")[0];
echo 'path1::: '+path1
//def path2= splittingparams.split(":")[1];
//def path3= splittingparams.split("/")[1];
}
when I try to run it in Jenkins pipeline, I face the below issue:
hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: java.util.AbstractMap$SimpleImmutableEntry.split() is applicable for argument types: (java.lang.String) values: [=]
Possible solutions: split(groovy.lang.Closure), wait(), wait(long), sprintf(java.lang.String, [Ljava.lang.Object;), getAt(java.lang.String), print(java.io.PrintWriter)
I don't want to hardcode the key to fetch its value, by reading the file line by line I need to get the key and value. Can someone provide the inputs to resolve this? Thank you !

The readProperties method returns a Map of properties. So you don't have to iterate to read them, simply use the key(name of the property) to extract the value for a specific property.
stage('readfromfile') {
steps {
script{
def readpropscontent = readProperties file: 'mypropsfile.properties'
echo 'readpropscontent ::: '+readpropscontent
echo "PROP1 :::: ${readpropscontent['product1']}"
echo "PROP2 :::: ${readpropscontent['product2']}"
echo "PROP3 :::: ${readpropscontent['product3']}"
}
}
}
Update 1
Printing all keys and values from properties.
stage('readfromfile') {
steps {
script{
def readpropscontent = readProperties file: 'mypropsfile.properties'
echo 'readpropscontent ::: '+readpropscontent
readpropscontent.each{ k,v ->
echo "KEY = $k :::: VAL = $v "
}
}
}
}
Update 2
Using readFile instead of readProperties
steps {
script{
def readpropscontent = readFile file: 'mypropsfile.properties'
echo 'readpropscontent ::: '+readpropscontent
for (String item : readpropscontent.split('\n')) {
echo "item ::: "+item
def readpropscontentfile2 = item.split("=")[0];
echo 'readpropscontentfile2 ::: '+readpropscontentfile2
}
}
}

Related

Loop variable is not passed to shell script in Jenkins

I am trying to pass a loop variable to a shell script, but my shell script is getting an empty variable value
pipeline {
agent any
stages {
stage('Test') {
steps {
script {
elements = ['1', '2', '3', '4']
for (String val in elements){
echo val
check = sh (script: """bash ./check_access/check.sh ${val}""", returnStdout: true).trim()
echo "check: ${check}"
}
}
}
}
}
}
./check_access/check.sh
#!/bin/sh
echo "val: $val"
the result i get
18:13:30 1
18:13:31 + bash ./check_access/check.sh 1
18:13:31 check: val:
.
.
.
18:13:31 4
18:13:32 + bash ./check_access/check.sh 4
18:13:32 check: val:
desired result
bash ./check_access/check.sh 1
check: val: 1
.
.
.
bash ./check_access/check.sh 4
check: val: 4
Could you please help
Solution:
I pass val as the first parameter to my script. So I should use $1 inside the script, not $val

Jenkinsfile/Groovy: why is result of dictionary AND integer an integer?

In Groovy/Jenkinsfile declarative syntax, why is the result of the boolean AND operation on dictionary, dictionary, and integer objects an integer instead of boolean true/false?
pipeline {
agent any
stages {
stage( "1" ) {
steps {
script {
a = [:]
a.a = [:]
a.a["a"] = "1"
a.a["b"] = "2"
echo "${a}"
echo "${a.a}"
echo "${a.a.size()}"
def my_bool = (a && a.a && a.a.size())
echo "my_bool ${my_bool}"
}
}
}
stage( "2" ) {
when {
expression { true == (a && a.a && a.a.size()) } // Fails because result is integer "2", not boolean "true"
}
steps {
script {
echo "hello, world!"
}
}
}
}
}
My biases from other programming languages led me to think that a && a.a && a.a.size() should implicitly be converted to a boolean value. The echo reveals that the value is integer 2.
What is the Jenkins/Groovy idiomatic way to deal with this? I.e. if a stage is conditional on "dictionaries being non-null and having nonzero size", what is the idiomatically correct/preferred way to write that conditional?
Update:
Note: the echo "my_bool ${my_bool}" statement prints "my_bool 2". This is with Jenkins version 2.222.3.
expression { a?.a?.size() }
or even
expression { a?.a }

In Jenkins pipeline Groovy, how can I set a value as a quoted string in a YAML file?

I have the following code that updates values in a Yaml file
import org.yaml.snakeyaml.Yaml
String version = "1.0.0"
def data = readYaml file: "Chart.yaml"
data.name = "myApp"
// The version needs to be in double-quotes
data.appVersion = "\"${version}\""
sh "rm Chart.yaml"
writeYaml file: "Chart.yaml", data: data
sh "cat Chart.yaml"
So I need the Chart.yaml file to have
appVersion = "1.0.0"
However, the above code puts this, with the extra single-quotes.
appVersion = '"1.0.0"'
Same result for
data.appVersion = '"'+version+'"'
If I just have this
data.appVersion = "${version}" // just string interpolation in this case
then of course I get this, with no quotes
appVersion = 1.0.0
Here's what I ended up doing based on Unforgetable631's suggestion.
import org.yaml.snakeyaml.Yaml
String version = "1.0.0"
def data = readYaml file: "Chart.yaml"
data.name = "myApp"
// The version needs to be in double-quotes
data.appVersion = version
sh "rm Chart.yaml"
writeYaml file: "Chart.yaml", data: data
sh """
cat Chart.yaml
sed -i \"s/$version/\\"$version\\"/g\" Chart.yaml
cat Chart.yaml
"""

Groovy multiline string interpolation whitespace

I am trying to generate some generic Groovy code for Jenkins but I seem to have trouble with multi line strings and extra white space. I've tried everything I could find by Googling but I can't seem to get it working.
My issue isn't related to simple multi line strings. I managed to trim white space by using the stripIndent() and stripMargin() methods for simple cases. My issue is caused by having interpolated methods inside my strings.
Groovy info: Groovy Version: 3.0.2 JVM: 13.0.2 Vendor: Oracle Corporation OS: Mac OS X
String method2(String tier, String jobName) {
return """
Map downstreamJobs = [:]
stage ("${jobName}-${tier}-\${region}_${jobName}") {
test
}
""".stripIndent().stripMargin()
}
static String simpleLog() {
return """
script {
def user = env.BUILD_USER_ID
}
""".stripIndent().stripMargin()
}
static String method1() {
return """\
import jenkins.model.Jenkins
currentBuild.displayName = "name"
${simpleLog()}
""".stripIndent().stripMargin()
}
String generateFullDeploymentPipelineCode() {
return """Text here
${method1()}
${method2("test1", "test2")}
""".stripIndent().stripMargin()
}
println(generateFullDeploymentPipelineCode())
This is what it prints(or writes to disk):
Text here
import jenkins.model.Jenkins
currentBuild.displayName = "name"
script {
def user = env.BUILD_USER_ID
}
Map downstreamJobs = [:]
stage ("test2-test1-${region}_test2") {
test
}
Why the extra space around the import lines? I know the indentation method is supposed to trim all white space according to the least number of leading spaces, so that's why we use backslash (example here https://stackoverflow.com/a/19882917/7569335).
That works for simple strings, but it breaks down once use start using interpolation. Not with regular variables, just when you interpolate an entire method.
as variant - use just stripMargin() and only once on a final string
String method2(String tier, String jobName) {
return """\
|Map downstreamJobs = [:]
|stage ("${jobName}-${tier}-\${region}_${jobName}") {
| test
|}
"""
}
static String simpleLog() {
return """\
|script {
| def user = env.BUILD_USER_ID
|}
"""
}
static String method1() {
return """\
|import jenkins.model.Jenkins
|currentBuild.displayName = "name"
${simpleLog()}
"""
}
String generateFullDeploymentPipelineCode() {
return """\
|Text here
${method1()}
${method2("test1", "test2")}
""".stripIndent().stripMargin()
}
println(generateFullDeploymentPipelineCode())
result:
Text here
import jenkins.model.Jenkins
currentBuild.displayName = "name"
script {
def user = env.BUILD_USER_ID
}
Map downstreamJobs = [:]
stage ("test2-test1-${region}_test2") {
test
}
another variant with trim() and stripIndent()
def method2(String tier, String jobName) {
return """
Map downstreamJobs = [:]
stage ("${jobName}-${tier}-\${region}_${jobName}") {
test
}
""".trim()
}
def simpleLog() {
return """
script {
def user = env.BUILD_USER_ID
}
""".trim()
}
def method1() {
return """
import jenkins.model.Jenkins
currentBuild.displayName = "name"
${simpleLog()}
""".trim()
}
def generateFullDeploymentPipelineCode() {
return """\
Text here
${method1()}
${method2("test1", "test2")}
""".stripIndent()
}
println(generateFullDeploymentPipelineCode())
When you insert a string through interpolation you only indent the first line of it. The following lines of the inserted string will be indented differently, which messes everything up.
Using some lesser-known members of GString (namely .strings[] and .values[]), we can align the indentation of all lines of each interpolated value.
String method2(String tier, String jobName) {
indented """
Map downstreamJobs = [:]
stage ("${jobName}-${tier}-\${region}_${jobName}") {
test
}
"""
}
String simpleLog() {
indented """\
script {
def user = env.BUILD_USER_ID
}
"""
}
String method1() {
indented """\
import jenkins.model.Jenkins
currentBuild.displayName = "name"
${simpleLog()}
"""
}
String generateFullDeploymentPipelineCode() {
indented """\
Text here
${method1()}
${method2("test1", "test2")}
"""
}
println generateFullDeploymentPipelineCode()
//---------- Move the following code into its own script ----------
// Function to adjust the indentation of interpolated values so that all lines
// of a value match the indentation of the first line.
// Finally stripIndent() will be called before returning the string.
String indented( GString templ ) {
// Iterate over the interpolated values of the GString template.
templ.values.eachWithIndex{ value, i ->
// Get the string preceding the current value. Always defined, even
// when the value is at the beginning of the template.
def beforeValue = templ.strings[ i ]
// RegEx to match any indent substring before the value.
// Special case for the first string, which doesn't necessarily contain '\n'.
def regexIndent = i == 0
? /(?:^|\n)([ \t]+)$/
: /\n([ \t]+)$/
def matchIndent = ( beforeValue =~ regexIndent )
if( matchIndent ) {
def indent = matchIndent[ 0 ][ 1 ]
def lines = value.readLines()
def linesNew = [ lines.head() ] // The 1st line is already indented.
// Insert the indentation from the 1st line into all subsequent lines.
linesNew += lines.tail().collect{ indent + it }
// Finally replace the value with the reformatted lines.
templ.values[ i ] = linesNew.join('\n')
}
}
return templ.stripIndent()
}
// Fallback in case the input string is not a GString (when it doesn't contain expressions)
String indented( String templ ) {
return templ.stripIndent()
}
Live Demo at codingground
Output:
Text here
import jenkins.model.Jenkins
currentBuild.displayName = "name"
script {
def user = env.BUILD_USER_ID
}
Map downstreamJobs = [:]
stage ("test2-test1-${region}_test2") {
test
}
Conclusion:
Using the indented function, a clean Groovy syntax for generating code from GString templates has been achieved.
This was quite a learning experience. I first tried to do it completely different using the evaluate function, which turned out to be too complicated and not so flexible. Then I randomly browsed through some posts from mrhaki blog (always a good read!) until I discovered "Groovy Goodness: Get to Know More About a GString". This was the key to implementing this solution.

Ignore lines starting with # while reading a file in groovy

I'm writing a code in groovy to read a file line by line and perform an action (for ex: build a code for a specific configuration) based on the information available in the line.
The following groovy code runs fine for me, except that i want to ignore or skip the first line or rather any line which starts with # or comment (//).
__SAMPLE_GROOVY_CODE__
input_file = '/home/user/inputFile.txt'
// read all the lines into a list, each line is an element in the list
File FH1 = new File(input_file)
def lines = FH1.readLines()
for (line in lines)
{
env.c_num = sh(returnStdout: true, script: "echo '${line}' | cut -d ':' -f 1").trim();
env.p_num = sh(returnStdout: true, script: "echo '${line}' | cut -d ':' -f 2").trim();
env.p_len = p_num.length();
println("INFO: Length is ${p_len} \n");
if(env.p_len != '0')
{
println ("INFO: Build is required !! \n");
println ("INFO: c_num is: ${c_num} \n");
println ("INFO: p_num is: ${p_num} \n");
// Code for build will come here..!!
}
else
{
println("INFO: Build is NOT required !! \n");
}
}
__INPUT_FILE__
$> cat /home/user/inputFile.txt
# Details of inputFile.txt
1234-A0: aa ab ac ad ae al
5678-B0:
2345-B0: ba
3456-B0:
4567-B0: ca

Resources