Terraform: count == true - terraform

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.

Related

Terraform contains(...) - how to check for wildcard or prefix

Is there a way I can check if a variable in Terraform contains a specific substring, i.e. en environment prefix such as eu- or us-? Please, see the following to better understand what I want to do.
contains("eu-<...>", var.environment) ? do-something : do-something-else
<...> could be anything.
You can achieve this with regexall:
length(regexall("eu-", var.environment)) > 0
For example:
variable environment {
default = "eu-dev-environment"
}
locals {
contains = length(regexall("eu-", var.environment)) > 0
}
The value for contains will be true in this case.

Parsing Terraform files with inner conditions

I'm trying to parse and configure a Terraform HCL configuration, using a script.
So far I've been using a tool named "hclq" (Link to github page).
Unfortunately, while this tool is great. When I have a conditional statement, such as:
resource "vault_identity_group" "group_a" {
count = terraform.workspace != "prod" ? 1 : 0
...
}
As documented by HashiCorp: Conditional Expressions
I tried encasing the condition as a literal:
count = ${terraform.workspace != "prod" ? 1 : 0}
But it seems that is no longer supported by Terraform.
Does anyone have any idea how can I get over this issue?
Thank you!
UPDATE: I've found an error in my literal, it should be encased in quotation marks:
count = "${terraform.workspace != var.prod ? 1 : 0}"
This way, the hclq tool can parse it as a string, I also had to switch the "prod" string with a variable, as the TF configuration does not support character escaping.

Expected string literal in condition express in Jenkins pipeline

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".

Terraform enclosing issue in variable assignment

I Got a syntax problem with terraform:
Let me write some pseudo code to descript the problem as the line is a bit complicated:
I would like to have display_name equal to force_name when defined.
And if not defined I would like to have name_prefix**-01**
Now the -XX suffix is always added in both case, and I can't enclose it correctly to add it in the else clause.
What I tried:
I've tried many enclosing {} "" () in differents places.
resource "exoscale_compute" "generic" {
count = "${var.replicas}"
affinity_groups = "${var.affinity_group}"
disk_size = "${var.disk_size}"
display_name = "${var.force_name != "" ? var.force_name : var.name_prefix}-${format("%02d", count.index + var.replicas_index_start) }
The issue:
The output is always forcedname**-01** or nameprefix**-01**
What I'd like would be:
forcedname or nameprefix-01
Could you help ?
Thanks
You can nest the interpolation, so the 2nd option for the ?: operator becomes another string with more interpolation:
display_name = "${var.force_name != "" ? var.force_name : "${var.name_prefix}-${format("%02d", count.index + var.replicas_index_start)}" }

How to check if string contains a substring in terraform interpolation?

How do you check if a terraform string contains another string?
For example, I want to treat terraform workspaces with "tmp" in the name specially (e.g. allowing rds instances to be deleted without a snapshot), so something like this:
locals
{
is_tmp = "${"tmp" in terraform.workspace}"
}
As far as I can tell, the substr interpolation function doesn't accomplish this.
For terraform 0.12.xx apparently you are suppose to use regexall to do this.
From the manual for terraform 0.12.XX:
regexall() documentation
regexall can also be used to test whether a particular string matches a given pattern, by testing whether the length of the resulting list of matches is greater than zero.
Example from the manual:
> length(regexall("[a-z]+", "1234abcd5678efgh9"))
2
> length(regexall("[a-z]+", "123456789")) > 0
false
Example applied to your case in terraform 0.12.xx syntax should be something like:
locals
{
is_tmp = length(regexall(".*tmp.*", terraform.workspace)) > 0
}
It also specifically says in the manual not to use "regex" but instead use regexall.
If the given pattern does not match at all, the regex raises an error. To test whether a given pattern matches a string, use regexall and test that the result has length greater than zero.
As stated above this is because you will actually get an exception error when you try to use it in the later versions of 0.12.xx that are out now when you run plan. This is how I found this out and why I posted the new answer back here.
You can indirectly check for substrings using replace, e.g.
locals
{
is_tmp = "${replace(terraform.workspace, "tmp", "") != terraform.workspace}"
}
Like #MechaStorm, with Terrafor 0.12.7+ you can use regex to return a Boolean value if your string contains a particular substring
locals {
is_tmp = contains(regex("^(?:.*(tmp))?.*$",terraform.workspace),"tmp")
}
The regex query returns a list of capture groups for any characters before tmp, tmp if found, any characters after tmp. Then contains looks for "tmp" in the list and returns true or false. I am using this type of logic in my own terraform.
Length of the list produced by split function is greater than one when separtor is a substring.
locals {
is_tmp = length(split("tmp", terraform.workspace)) > 1
}
Use replace( string, search, replace ) as in the snippet:
// string contains ABBA = result is ABBA
output "match" {
value = "${ replace("xxxABBAyyy", "/(?:.*)(ABBA)(?:.*)/", "$1") }"
}
// string doesn't contain ABBA = result is original string
output "no_match" {
value = "${ replace("xxxBABAyyy", "/(?:.*)(ABBA)(?:.*)/", "$1")}"
}
// string contains ABBA (ingorecase) = result is AbBA
output "equals_ignorecase" {
value = "${ replace("xxxAbBAyyy", "/(?:.*)((?i)ABBA)(?:.*)/", "$1")}"
}
An output of terraform apply is:
Outputs:
equals_ignorecase = AbBA
match = ABBA
no_match = xxxBABAyyy
In terraform 0.12.7, we now have regex . This may help simplify some code and make it readable to some (perhaps?)
> regex("[a-z]+", "53453453.345345aaabbbccc23454")
aaabbbccc
I use this way to check if bucket name start with test-tmp
eg. test-tmp, test-tmp-app1, test-tmp-db1 etc..
is_test_bucket = can(regex("^(test-tmp){1}($|-{1}.*$)", var.bucket_name))
Something that makes sense reading, IMHO:
locals {
is_tmp = can(regex("tmp", terraform.workspace))
}
This works because the regex function will raise an error if no matches are found.
Bonus: since Terraform 1.3.x, there are the new startswith and endswith functions that can be handy in a good amount of cases.

Resources