Using "$" in Groovy - grails

I see { } are used for closures, and then I believe when a $ is put in front of braces, it is simply doing a variable substitution within a string. I can't find the documentation on how the $ works in the reference ... hard to search on it unfortunately, and the Groovy String documentation is lacking in introducing this. Can you please point me to the documentation and/or explain the "$" operator in Groovy -- how all it can be used? Does Grails extend it at all beyond Groovy?

In a GString (groovy string), any valid Groovy expression can be enclosed in the ${...} including method calls etc.
This is detailed in the following page.

Grails does not extend the usage of $ beyond Groovy. Here are two practical usages of $
String Interpolation
Within a GString you can use $ without {} to evaluate a property path, e.g.
def date = new Date()
println "The time is $date.time"
If you want to evaluate an expression which is more complex than a property path, you must use ${}, e.g.
println "The time is ${new Date().getTime()}"
Dynamic Code Execution
Dynamically accessing a property
def prop = "time"
new Date()."$prop"
Dynamically invoking a method
def prop = "toString"
new Date()."$prop"()
As pointed out in the comments this is really just a special case of string interpolation, because the following is also valid
new Date().'toString'()

$ is not an operator in Groovy. In string substitution it identifies variables within the string - there's no magic there. It's a common format used for inline variables in many template and programming languages.
All special Groovy operators are listed here: http://groovy-lang.org/operators.html

Work in side Jenkins File in pipeline
#!/usr/bin/env groovy
node{
stage ('print'){
def DestPath="D\$\\"
println("DestPath:${DestPath}")
}
}

Related

JenkinsFile parameters settings for key:value pair not allowing strings

I'm trying to set a variable for a key value pairing in a jenkinsfile and can't get it to recognize the variable as a string.
zip = "name_of_zip_file_to_use"
createZipFile = [src:"./test", destination:"./"+zip+".zip"]
I have tried to use the variable zip as the whole string also but nothing seems to work. I'm not sure why it isn't recognizing the destination value variable as a string. Any ideas why I can't get this to work in the jenkinsfile?
Referencing and interpolating variable is possible this way - using ${VARIABLE_NAME} syntax. So in your case it would look:
zip = "name_of_zip_file_to_use"
createZipFile = [src:"./test", destination:"./${zip}.zip"]
If interested more about referencing variables and/or a string concatenation, see docs or similar StackOverflow topics:
variable interpolation in groovy: https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#string-interpolation and http://docs.groovy-lang.org/latest/html/documentation/#_string_interpolation
how to concatenate strings in a Jenkinsfile?

Using an environment variable as a map for common tags in a locals block

I've run into a specific problem while trying to automate a tagging process in terraform. I've set an environment variable that is essentially a list of all the tags we'd be using for all resources provisioned in the apply. It looks like this...
export TF_VAR_taglist='{JiraEpic = "ETOS-56","AssignedResearcherPri" = "Isaac",AssignedResearcherSec = "Matt"}'
After setting the environment variable I added a variable called "taglist" in the variables.tf file that grabs the aforementioned environment variable. It looks like this...
variable "taglist"{}
Lastly, I have another locals.tf file where i set a common_tags variable. Like so...
locals { common_tags ="${var.taglist}" }
When i terraform apply, the build fails while trying to map the tags properly. This is the error i receive...
Error: Incorrect attribute value type
on kube_master_worker_nodes_ec2.tf line 9, in resource "aws_instance" "master":
9: tags = local.common_tags
|----------------
| local.common_tags is "{JiraEpic = \"ETOS-56\",AssignedResearcherPri = \"Isaac \",AssignedResearcherSec = \"Matt\"}"
Inappropriate value for attribute "tags": map of string required.
I then decided to define the type of the variable as map(string in the variables.tf file like this
variable "taglist"{ type = map(string) }
I had hoped that this would allow terraform to recognize this variable as a map of strings and not just a string literal, but I was wrong, and these are the errors I get when that definition is applied.
Error: Missing attribute separator
on <value for var.taglist> line 1:
(source code not available)
Expected a newline or comma to mark the beginning of the next attribute.
Error: No value for required variable
on variables.tf line 11:
11: variable "taglist"{
The root module input variable "taglist" is not set, and has no default value.
Use a -var or -var-file command line argument to provide a value for this
variable.
I'm really stuck on this, and I feel like I'm close. Can anyone provide some insight into this and how I should go about solving it?
I want to first thank Martin Atkins for giving me the idea of using colons instead of equal signs in my environment variable, because that was the ONLY issue. The variable was not properly represented as a JSON object so terraform was interpreting it as a string.
I changed
export TF_VAR_taglist='{JiraEpic = "ETOS-56","AssignedResearcherPri" = "Isaac",AssignedResearcherSec = "Matt"}'
to this
export TF_VAR_taglist='{"JiraEpic":"ETOS-56","AssignedResearcherPri":"Isaac", "AssignedResearcherSec":"Matt"}'
The build purrs like a kitten on catnip now.
Terraform uses the type constraint of a variable to decide how to interpret a string representation of its value. By default, Terraform will assume the value expects a primitive type such as a string or number, because that's the most typical case for variables set via the command line or environment variables.
Since your tag list is a list you need Terraform to interpret it as a map expression rather than as a string. You can tell Terraform to do that by telling Terraform which type of value you expect:
variable "taglist" {
type = map(string)
}
You can read more about this in the Terraform documentation section Complex-typed Values.
You then need to make sure that the value in the environment variable is a valid object expression in order to avoid a syntax error. If you're setting the environment variable from the shell command line then you need to be mindful of escaping/quoting to ensure that Terraform will see the value with all of the quotes intact, and without any extra metacharacters.
The result is often hard to read clearly, which is why the Terraform documentation recommends using a .tfvars file to set complex-typed variables, instead of the -var command line option or environment variables. However, since you are using automation you might find it easier to generate a .tfvars.json file instead, which uses standard JSON format and is therefore easier to generate using JSON libraries available in most programming languages. Here's the .tfvars.json equivalent of the value you showed in your question:
{
"taglist": {
"JiraEpic": "ETOS-56",
"AssignedResearcherPri": "Isaac",
"AssignedResearcherSec": "Matt"
}
}
Note that subjectively I'd find it pretty confusing to have a variable whose name ends in list when it actually expects a map. A more typical name for this variable would be just tags, though if it's useful to mention its type in order to distinguish it from other variables then I'd suggest tag_map instead, to make it less confusing.

DSL Jenkins pass parameter to phaseJob

On Groovy DSL how can I use jenkins parameters in a phaseJob name.
When trying to do
stringParam('jobName', 'bla', 'blabla')
...
phaseJob('$jobName')
jobName is not replaced with the value I entered in Jenkins
You are using single quotes. As you can read here
Any Groovy expression can be interpolated in all string literals, apart from single and triple single quoted strings.
You need to use double quotes in order to make "string ${interpolation}" work:
phaseJob("$jobName")

Unit testing grails ConfigSlurper behavior

I'd like to write tests that would test behavior of externalized configs and assert that what gets set is what I expect. This is for the specific case where something like this is done:
Config.groovy:
a.reused.value = 'orig'
my.variable = '${a.reused.value}'
Externalized groovy file:
a.reused.value = 'new_value'
I expect that both a.reused.value and my.variable would be 'new_value'.
Now, I think I could have my unit test read in strings representing these config files (I do similar things for other unit tests to populate Holders.grailsApplication.config, for example), utilizing perhaps merge?
But what I cannot figure out is how to get the value that Grails actually gets during application run time. Instead, I get "${a.reused.value}" in my unit tests.
Is there a way to mimic this behavior of what Grails does of actually resolving this value? I did some digging around in Grails 2.4.4 source (which is what we are using) and didn't have any luck in figuring this part out. I also did try Eval.me(), but that doesn't seem to be quite right either.
While setting my.variable, you are not using a GString object, causing the expression to be treated as a value itself. Use double quotes to resolve expression automatically.
a.reused.value = 'orig' my.variable = "${a.reused.value}"
Update 1:
What you want to do is directly not possible. You are assigning the value to a variable from an expression. During evaluation of the config object for the first time, my.variable has been assigned a value, and now it doesn't contain an expression any more. So you have two options: 1) either reassign the second variable in external config also or 2) use a closure to assign the value to second variable.
my.variable = { -> "$a.reused.value" }
and while accessing do: grailsApplication.config.my.variable.call()
But again, in your code, you would have to be sure that this variable contains a closure not a value itself.

Does Grails surround log statements with isSomethingEnabled()

As per http://docs.codehaus.org/display/GROOVY/Groovy+1.8+release+notes#Groovy18releasenotes-Log groovy does surround log statements with checks like isDebugEnabled() etc.
Does grails do that for the generated code?
For this log call in a grails service:
log.debug("competitors errors stage 1: ${failedCarrierRequests}")
In the decompiled .class file I see only this:
arrayOfCallSite[85].call(log, new GStringImpl(new Object[] { allCompetitorDepartmentsRows.get() }, new String[] { "All competitors: ", "" }));
It is unclear whether there is a check for log level behind the scenes or not.
As of 2.2.2: no.
Grails injects an apache commons Log field into the artefact classes, and the log4j plugin marries that to a log4j Logger.
However, in your example you pass a GString as the only parameter. Since they are lazy-transformed to Java Strings, the log4j logger would hit it's own internal debug enabled check and skip the toString() call.
If you do something expensive like parameter building, however, and you're concerned about the wasted cycles, you must call isDebugEnabled() yourself:
if (log.isDebugEnabled()) {
log.debug("Some string concatenation with a slow method: " + slowMethod())
}
I should point out that this contrived example could be converted to use GString to save the debug check:
log.debug "GString with lazily initialized slow method call: ${slowMethod()}"
There was some discussion awhile back on the grails-user mailing list about adding an AST transformation to add the checks, but it didn't go anywhere.
Okay response that GStrings are lazy loaded doesn't seems to hold up against my testing.
I have created a test to basically log something four different ways, and it does appear to evaluate any logging statement GString.
Here is my test file GIST: https://gist.github.com/tgsoverly/34f9a56287291297777b
The test fails for the GString and the method IS called when in the log statement.
It would also not be something the groovy people say should be the case according to their template section: http://groovy.codehaus.org/Strings+and+GString

Resources