Error: Invalid multi-line string terraform - terraform

I am trying to define a resource but Terraform complains
resource "aws_cloudwatch_event_target" "my-functions-order-machine-completion-target" {
target_id = "some target id"
rule = aws_cloudwatch_event_rule.my-functions-order-machine-completion-rule.name
arn = module.lambda["myLambda"].function_arn
}
error:
Quoted strings may not be split over multiple lines. To produce a multi-line
string, either use the \n escape to represent a newline character or use the
"heredoc" multi-line template syntax.
I saw Heredoc Strings but not sure how to use this after "resource"

Although not a great answer, shortening the name of the resource sorted out the issue for me.
I also tried interpolation syntax like so which did not work:
resource "resource" "${var.name}"

Related

Are there line continuation character for string in Terraform

I am writing some long SQL queries in a Terraform file, the query would be like:
"CREATE TABLE example_${var.name} (id string, name string..........................................)"
To make the query readable, I hope the query would be the format like the following, and cross multiple lines
CREATE TABLE example_$(var.name) (
id string,
name string,
................................
)
Is there a line continuation character for a long single line string to be written as multiple lines. Just like we could use backslash \ in Python for long string?
I have tried use heredoc, but it does not work when running the query. Thanks
It sounds like your goal is to have a long SQL query defined in Terraform, but across multiple lines so you don't need to horizontal scroll to infinity and beyond.
In my team we use heredoc to achieve this although you said it's not possible in your case.
Another idea my team use when heredoc isn't possible is to join an array of strings.
E.g.
locals {
sql = join(",", [
"id string",
"name string",
"address string",
"renter string",
"profession string"
])
}
Results in
> local.sql
id string,name string,address string,renter string,profession string
I hope I've understood your question correctly but if not please let me know.
PS: There's an open issue for multiline strings in Terraform
To make a multi-line strings in Terraform using the heredoc string syntax.
locals {
sql = <<EOT
CREATE TABLE example_$(var.name) (
id string,
name string,
................................
)
EOT
}

How can I convert a list to a string in Terraform?

join works BUT i want to keep the double quotes join gives me this
[ben,linda,john]
BUT i want this
["ben", "linda", "john"]
this is getting crazy, spent over 2 hours trying to fix this
i want to pass in a list as a string variable
why can't terraform just take in my list as a string? why is this so difficult?
so i have
name = ["ben", "linda", "john"]
and i want to pass this to variable used in terrform
var.name
why can't terrform take this as is?
i get the error saying epxtected a string and i can not find a solution online after sarching everywhere
i have been able to get
[ ben,linda,john ] using join(",", var.name) but i want ["ben", "linda", "john"]
$ terraform --version
Terraform v0.12.18
+ provider.aws v2.42.0
+ provider.template v2.1.2
Conversion from list to string always requires an explicit decision about how the result will be formatted: which character (if any) will delimit the individual items, which delimiters (if any) will mark each item, which markers will be included at the start and end (if any) to explicitly mark the result as a list.
The syntax example you showed looks like JSON. If that is your goal then the easiest answer is to use jsonencode to convert the list directly to JSON syntax:
jsonencode(var.names)
This function produces compact JSON, so the result would be the following:
["ben","linda","john"]
Terraform provides a ready-to-use function for JSON because its a common need. If you need more control over the above decisions then you'd need to use more complex techniques to describe to Terraform what you need. For example, to produce a string where each input string is in quotes, the items are separated by commas, and the entire result is delimited by [ and ] markers, there are three steps:
Transform the list to add the quotes: [for s in var.names : format("%q", s)]
Join that result using , as the delimiter: join(", ", [for s in var.names : format("%q", s)])
Add the leading and trailing markers: "[ ${join(",", [for s in var.names : format("%q", s)])} ]"
The above makes the same decisions as the JSON encoding of a list, so there's no real reason to do exactly what I've shown above, but I'm showing the individual steps here as an example so that those who want to produce a different list serialization have a starting point to work from.
For example, if the spaces after the commas were important then you could adjust the first argument to join in the above to include a space:
"[ ${join(", ", [for s in var.names : format("%q", s)])} ]"

Question about referencing specific indexes in Terraform Interpolation

Let me first start out by saying that I currently have a working terraform configuration, however my IDE (VSCode; using the mauve.terraform extension, v 1.3.12) complains about my syntax when I do something like:
virtual_machine_name = "${azurerm_virtual_machine.sql["${count.index}"].name}"
It complains that it's expecting a '}' but found a '.'.
Should I be writing this out differently, or in a more "correct" manner? I'm fairly new to working with terraform so I'm sure my syntax could use some help.
Thanks in advance!
For reference, here's my full resource block:
resource "azurerm_virtual_machine_extension" "sql" {
name = "OMSExtension"
location = "${data.azurerm_resource_group.generics_sql_dev.location}"
resource_group_name = "${data.azurerm_resource_group.generics_sql_dev.name}"
virtual_machine_name = "${azurerm_virtual_machine.sql["${count.index}"].name}"
publisher = "Microsoft.EnterpriseCloud.Monitoring"
type = "MicrosoftMonitoringAgent"
type_handler_version = "1.0"
auto_upgrade_minor_version = true
count = "${var.sql_node_count}"
settings = <<-BASE_SETTINGS
{
"workspaceId" : "${data.azurerm_log_analytics_workspace.oms.workspace_id}"
}
BASE_SETTINGS
protected_settings = <<-PROTECTED_SETTINGS
{
"workspaceKey" : "${data.azurerm_log_analytics_workspace.oms.primary_shared_key}"
}
PROTECTED_SETTINGS
}
From Terraform 0.12 and later, the "standard" way to write that is:
virtual_machine_name = azurerm_virtual_machine.sql[count.index].name
What you tried would also work in Terraform 0.12, but the string interpolations are redundant in that version. It works in Terraform 0.12 because of a special backward-compatibility rule that if a quoted string sequence consists only of a single interpolation (like "${ ...anything ... }") then Terraform will ignore the quotes and just return the inner expression value directly.
That's only supported for backward-compatibility with configurations written for Terraform 0.11 and so I'd recommend avoiding it if you are using Terraform 0.12 or later; it tends to hurt readability by leaving a reader wondering if it implies conversion to a string.
For Terraform 0.11 and earlier, one level of string interpolation is required and indexing must be against a "splat operator":
virtual_machine_name = "${azurerm_virtual_machine.sql.*.name[count.index]}"
The azurerm_virtual_machine.sql.*.name part here produces a list of name values, and then [count.index] selects one of them. This approach is required in Terraform 0.11 and earlier because in those versions the index operator [...] must always come at the end of a sequence of traversal steps.

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.

Variable Interpolation in Terraform

I am having trouble in variable interpolation in terraform. Here is what my terraform configuration looks like. i.e variable inside builtin function
variable "key" {}
ssh_keys {
path = "/home/${var.provider["user"]}/.ssh/authorized_keys"
key_data = "${file(${var.key})}"
}
Command: terraform apply -var 'key=~/.ssh/id_rsa.pub'
It's not reading the value of "key" from command line argument or from env variable. However when i hardcore the value in .tf file, it works. Like below.
key_data = "${file("~/.ssh/id_rsa.pub")}"
The ${ ... } syntax is only used when embedding an expression into a quoted string. In this case, where your var.key variable is just being passed as an argument to a function already within a ${ ... } sequence, you can just reference the variable name directly like this:
key_data = "${file(var.key)}"
Nested ${ ... } sequences are sometimes used to pass an interpolated string to a function. In that case there would first be a nested set of quotes to return to string context. For example:
key_data = "${file("${path.module}/${var.key_filename}")}"
In this more complicated case, the innermost string expression is first evaluated to join together the two variables with a /, then that whole string is passed to the file function, with the result finally returned as the value of key_data.
It doesn't work because you were using the wrong flag for the scenario you described above.
If you want to specify a path to a file use the "-var-file" flag as follow:
terraform apply -var-file=~/.ssh/id_rsa.pub
If you must use the "-var" flag then you must specify the content of the file as follow:
terraform apply -var 'key=contenctOFPublicKey'
ssh_keys - (Optional) Specifies a collection of path and key_data to be placed on the virtual machine.
Note: Please note that the only allowed path is /home/<username>/.ssh/authorized_keys due to a limitation of Azure.
refer: AZURERM_VIRTUAL_MACHINE

Resources