Expected string literal in condition express in Jenkins pipeline - groovy

I am using ?: to determine the build agent of Jenkins shared library groovy script like this:
def call(String type, Map map) {
if (type == "gradle") {
pipeline {
agent "${map.agent == null}" ? "any" : "${map.agent}"
}
}
}
but it gives me the following error:
org.jenkinsci.plugins.workflow.cps.CpsCompilationErrorsException: startup failed:
/Users/dabaidabai/.jenkins/jobs/soa-robot/builds/154/libs/pipeline-shared-library/vars/ci.groovy: 6: Expected string literal # line 6, column 42.
agent "${map.agent == null}" ? "any" :
^
/Users/dabaidabai/.jenkins/jobs/soa-robot/builds/154/libs/pipeline-shared-library/vars/ci.groovy: 6: Only "agent none", "agent any" or "agent {...}" are allowed. # line 6, column 13.
agent "${map.agent == null}" ? "any" : "${map.agent}"
^
/Users/dabaidabai/.jenkins/jobs/soa-robot/builds/154/libs/pipeline-shared-library/vars/ci.groovy: 6: No agent type specified. Must be one of [any, docker, dockerfile, label, none] # line 6, column 13.
agent "${map.agent == null}" ? "any" : "${map.agent}"
What am I doing wrong?

This error is thrown by the pipeline syntax validator that runs before your pipeline code gets executed. The reason you see this error is the following:
Only "agent none", "agent any" or "agent {...}" are allowed. # line 6, column 13.
This is the constraint for the label section. It means that the following values are valid:
agent any
agent none
agent "constant string value here"
agent { ... }
When you pass something like:
agent "${map.agent ?: 'any'}
agent(map.agent ?: 'any')
you are getting Expected string literal because any form of an expression is not allowed in this place, including interpolated GStrings of any form.
Solution
There is a way to define pipeline agent dynamically however. All you have to do is to use a closure block with the label set to either expression or empty string (an equivalent of agent any in this case.)
pipeline {
agent {
label map.agent ?: ''
}
stages {
...
}
}
The label section allows you to use any expression, so map.agent is a valid construction here. Just remember to use an empty string instead of "any" - otherwise Jenkins will search for a node labeled as "any".

Don't use string replacments everyhwhere:
agent(map.agent==null ? "any" : map.agent)
Or get groovy:
agent(map.agent?:"any")
The actual problem is most likely the "ternary operator" battling against the "parens are maybe optional"-rule.

It seems to me that agent is a method taking a string argument and the way your code is written is ambiguous. Try surrounding the argument expression with parens:
agent(map.agent == null ? "any" : "${map.agent}")
I removed the quotes around map.agent == null as they seemed extraneous.
Also this could probably be rewritten using the groovy "elvis operator" (:?) as:
agent(map.agent ?: "any")
Which essentially means "use map.agent if it has a value, otherwise use 'any'". "If it has a value" is in this context being defined using groovy truth where both empty string and null represent "no value".

Related

Groovy string comparison in Jenkins pipeline

I'm trying to compare two strings in Jenkins pipeline. The code more or less look like this:
script {
def str1 = 'test1.domainname-test.com'
def str2 = 'test1.domainname-test.com'
if ( str1 == str2 ) {
currentBuild.result = 'ABORT'
error("TENANT_NAME $TENANT_NAME.domainname-test.com is already defined in domainname-test.com record set. Please specify unique name. Exiting...")
}
}
str1 is fed by a preceeding command I skipped here due to simplicity. I am getting this error:
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: No such field found: field java.lang.String domainname
What am I doing wrong? I tried equals method too, same result. As if it stucked on those dots, thinking it's some kind of property. Thanks in advance
You're missing curly brackets surrounding the TENANT_NAME variable name. In your example:
error("TENANT_NAME $TENANT_NAME.domainname-test.com is already defined in domainname-test.com record set. Please specify unique name. Exiting...")
the $ sign gets applied to TENANT_NAME.domainname. And because TENANT_NAME is a string, Groovy interprets the following part as you were trying to access domainname property from a String class, and you get No such field found: field java.lang.String domainname exception.
To avoid such problems, wrap your variable name with {} and you will be fine.
error("TENANT_NAME ${TENANT_NAME}.domainname-test.com is already defined in domainname-test.com record set. Please specify unique name. Exiting...")

groovy wrongly infers "off" as boolean instead of string

I've got a Jenkinsfile script in groovy which is processing a Java application's application.properties file, which I have just added to with
spring.main.banner-mode: off
In my script I read the application.properties file into a map in memory using a Jenkins add-in library yamlRead and then I output the value again into another file but it comes out as:
spring.main.banner-mode: false
That breaks my Java program on boot with a nasty spring boot error. The spring boot variable expects either OFF, FILE or CONSOLE.
I have no way to change yamlRead but I can change the output script which looks like this:
yaml.each {
key, value -> B: {
// some processing...
sh "echo '$base$key=$value' >> $file"
}
}
}
How can I determine if the map actually has the boolean type (which would be bad since I can't change it) or whether the undesired cast to boolean happens in myy echo >> file?
Or could I somehow force groovy not to infer the booleanness when it reads the input, perhaps with quotes around "off"?
Everything is working as expected. Groovy is not your problem its YAML. The YAML reference says that 'off' is interpreted as 'false' as you can see here
https://yaml.org/refcard.html
The Jenkins yamlRead reads 'off' and transforms it to a boolean with value 'false'.
as Thomas wrote: off is a reserved word in yaml format for boolean false
however you could quote it to force it to be a string:
spring.main.banner-mode: 'off'
in this case spring.main.banner-mode key will have a string value off
to check boolean false value you could use something like:
yaml.each {
key, value -> B: {
// some processing...
sh "echo '$base$key=${ value==false? 'off' : value }' >> $file"
}
}
PS:
instead of calling sh to append to a file one key-value you could use following code:
def values = yaml.collect{k,v-> "$k=$v"}.join("\n")
writeFile( file: file, text: values )

Terraform: count == true

I have used this previously in my TF code:
count = "${var.whatever == "true" ? 1 : 0}"
Which works great for what I wanted to use. However, I'm thinking of how best to use something similar to say, if var.whatever is equal to true, then count is equal to length(var.whatever).
Would this work?
count = "${var.whatever == "true" ? ${length(var.whatever) : 0}"
It's just that I was always under the impression with TF that you can't nest interpolations.
Also, this kind of leads me to another interesting question.. what do you guys use to test syntax? I don't mean to lint the TF Code, I mean something to run the code against to test things like this? I don't want to just deploy to dev, just to test the output of my TF code. I was wondering if there was something, shell like, which I could literally test this stuff as is it were bash or something. Any thoughts?
Your original idea won't work because if var.whatever is a list, then it can't also be a string (i.e. = "true").
However, the good news is that interpolations can be used inside the general ternary operator.
So you can have something like:
count = "${var.bool == "true" ? length(var.whatever) : 0}"
A good way to test out interpolations before dropping them into your final code is by using Terraform's console feature (i.e. terraform console).
Set up your vars in a file, say console.tf in a directory without any other TF code.
variable "whatever" {
type = "list"
default = ["1", "2", "foo", "bar" ]
}
variable "bool" {
default = "true"
}
Now from the command line, run terraform console.
$ terraform console
> var.whatever
[
"1",
"2",
"foo",
"bar",
]
> length(var.whatever)
4
> "${var.bool == "true" ? length(var.whatever) : 0}"
4
> exit
$
You can see that Terraform performs the interpolations, then runs the ternary operator on those evaluated values.

Using from {x}.field in DSL in Drools

I have the following Drools DSL "sentence":
[when]The field {field} in the module {module} contains value {value}=$a : {module} ( {field} != null)
String( this.equalsIgnoreCase("{value}") ) from $a.{field}
where the field is a Set of Strings.
Now, if I have two of these rules, it obviously won't work as the variable $a occurs twice. So I wanted to improve the rule to make the variable, well, variable:
[when]The field {field} in the module {module} contains value {value} as {a}={a} : {module} ( {field} != null)
String( this.equalsIgnoreCase("{value}") ) from {a}.{field}
This doesn't work, I can't use the part {a}., that breaks.
So, my questions are: Is there either a way to rewrite the rules or a way to allow the {variable}. notation to work? Alternatively, is there a contains operator which works case insensitive?
After I subscribed to the Drools-Users mailing list, I got an answer:
http://drools.46999.n3.nabble.com/rules-users-Using-from-x-field-in-DSL-tt4017872.html
Summary: Bug in DSL parser, as a workaround add an extra letter after the variable on the RHS: ... as {a}={a}x (...) ... from {a}x.{field}

What do Groovy assertions look like with parentheses?

The example on this page only shows Groovy assertions without parentheses.
assert a != null, 'First parameter must not be null'
What would this look like if I wanted to include the parentheses? I presume that this is the closest equivalent to Perl's die() function (print error message and exit in one statement)?
The answer is:
assert(a != null), "First parameter must not be null"

Resources