Terraform syntax for putting json as value in a map - terraform

I'm new to terraform. I have a json object that I need to set as the value in a terraform map so that the resource gets created with the json as the value.
The .tf file looks like this in that section:
...
config_overrides = {
override_1 = "True"
override_2 = '{"key1":"val1","key2":"val2"}' #this is the json object
}
...
However, the terraform lint command terraform lint -check is failing on the json object.
$ terraform fmt -check
Error: Invalid character
on myterraform.tf line 28, in resource <<resource name>> :
28: override_2 = '{"key1":"val1","key2":"val2"}'
Single quotes are not valid. Use double quotes (") to enclose strings.
Error: Invalid expression
on myterraform.tf line 28, in resource <<resource name>>:
28: override_2 = '{"key1":"val1","key2":"val2"}'
Expected the start of an expression, but found an invalid expression token.
I have tried many different variations and cant get the linter to accept it. Please advise.

You can use Terraform's jsonencode function so that Terraform itself is responsible for generating the JSON and you only need to worry about the data structure:
override_2 = jsonencode({
"key1": "val1",
"key2": "val2",
})
Terraform's object expression syntax happens to be similar to JSON's and so the argument to jsonencode here looks a lot like the JSON string it'll convert to, but that is really just a normal Terraform expression and so you can include any Terraform expression constructs in there. For example:
override_2 = jsonencode({
"key1": "val1",
"key2": var.any_variable,
})

You will need to use the \ in the value ' isn't going to work.
config_overrides = {
override_1 = "True"
override_2 = "{\"key1\":\"val1\",\"key2\":\"val2\"}"
}

Related

Load variable values from the given .tfvars file got an error to many arguments

I run terraform plan commnad with apllo.tfvars file
terraform plan -var-file=apllo.tfvars
│ Error: Too many command line arguments
│ To specify a working directory for the plan, use the global -chdir flag.
my variable.tf
variable "user" {
type = string
}
# number variable
variable "age" {
type = number
}
apllo.tfvars
user = "AWSUSER"
age = 222
output.tf
output "name" {
value = "hello ${var.user}"
}
output "age" {
value = "age ${var.age}"
}
If you are using Powershell for running Terraform, try specifying the .tfvar file using single or double quotes, such as:
terraform plan -var-file="apollo.tfvar"

Cloudwatch alarm creation fails due to heredoc

I am trying to create a composite cloudwatch alarm using terraform. But unfortunately my terraform code breaks with the following error:
Error: error creating CloudWatch Composite Alarm
(node-count-office-time-composite-alarm-DP-1474-desert):
ValidationError: AlarmRule must not contain leading or trailing
whitespace or be null
status code: 400, request id: 272b14ae-e6bd-4e65-8bb8-25372d9a5f7c
Following is my terraform code:
resource "aws_cloudwatch_composite_alarm" "node_count_office_time_alarm" {
depends_on = [aws_cloudwatch_metric_alarm.node_count, aws_cloudwatch_metric_alarm.office_time]
alarm_description = "Composite alarm for node count & office time"
alarm_name = "node-count-office-time-composite-alarm-${local.postfix}"
alarm_actions = [var.sns_topic_arn]
ok_actions = [var.sns_topic_arn]
alarm_rule =<<-EOF
ALARM(${aws_cloudwatch_metric_alarm.node_count.alarm_name}) AND
ALARM(${aws_cloudwatch_metric_alarm.office_time.alarm_name})
EOF
}
I checked many times and there are no leading or trailing spaces in my alarm_rule. Only new line after AND operator. I am using terraform 0.15.3 version. Anyone faces similar issues and how can I resolve this issue? thanks
I did not find the solution to how to make the heredoc working. But I fixed it for the time being using direct string expression instead of heredoc block. Following is the string expression:
alarm_rule = "ALARM(${aws_cloudwatch_metric_alarm.node_count.alarm_name}) AND ALARM(${aws_cloudwatch_metric_alarm.office_time.alarm_name})"
I hope it is useful for others if they face the same issue. thanks
Terraform instructions https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_composite_alarm are not accurate as of this writing in 2021.
alarm_rule accepts a single string argument, EOF/heredoc has to be processed to create a literal string:
locals {
alarm_rule_with_newlines = <<-EOF
ALARM(${aws_cloudwatch_metric_alarm.alpha.alarm_name}) OR
ALARM(${aws_cloudwatch_metric_alarm.bravo.alarm_name})
EOF
}
[...]
alarm_rule = trimspace(replace(local.alarm_rule_with_newlines, "/\n+/", " "))
I was not satisfied with neither of proposed answers so I have another solution.
Move your composite alert rules to separate file and just read it:
alarm_rule = file("./composite-alert-rule")
or
alarm_rule = templatefile("./composite-alert-rule", { arg = ... })
if you need to pass some dynamic args.
Check terraform docs for reference:
https://www.terraform.io/language/functions/templatefile
https://www.terraform.io/language/functions/file

Terraform External Provider resource does not have attribute for variable during plan phase

When running terraform plan with the below scripts I Gert the following error message:
Error: Error running plan: 1 error(s) occurred:
* output.foobaz: Resource 'data.external.example' does not have attribute 'result.foobaz' for variable 'data.external.example.result.foobaz'
It doesn't appear from testing that the external script is actually executed during the plan phase, however, it does appear that the plan phase is trying to interpolate the expected response, which seem s incorrect to me. Is there something I'm missing?
provider "scaleway" {
region = "ams1"
}
resource "scaleway_ip" "swarm_manager_ip" {
count = 1
}
data "external" "example" {
program = ["./scripts/test.sh"]
query = {
# arbitrary map from strings to strings, passed
# to the external program as the data query.
foo = "${scaleway_ip.swarm_manager_ip.0.ip}"
baz = "i-am-baz"
}
}
output "foobaz" {
value = "${data.external.example.result.foobaz}"
}
output "scaleway_ip_address" {
value = "${scaleway_ip.swarm_manager_ip.0.ip}"
}
Here is the external script:
#!/bin/bash
# Exit if any of the intermediate steps fail
set -e
# Extract "foo" and "baz" arguments from the input into
# FOO and BAZ shell variables.
# jq will ensure that the values are properly quoted
# and escaped for consumption by the shell.
eval "$(jq -r '#sh "FOO=\(.foo) BAZ=\(.baz)"')"
# Placeholder for whatever data-fetching logic your script implements
FOOBAZ="$FOO BAZ"
# Safely produce a JSON object containing the result value.
# jq will ensure that the value is properly quoted
# and escaped to produce a valid JSON string.
jq -n --arg foobaz "$FOOBAZ" '{"foobaz":$foobaz}'
Your Terraform syntax is incorrect. data.external.example.result is a map. To access its entry foobaz you need to code
"${data.external.example.result["foobaz"]}"
See https://www.terraform.io/docs/configuration/interpolation.html

How do I pass a list as an argument?

This solution here did not work for me.
// my tf file:
variable "myvar" {type = "list"}
module "my-module" {
blah = "${var.myvar}"
source = "path/to/module"
}
Various command line attempts:
terraform plan -var myvar="zzzz"
should be type list, got string
terraform plan -var myvar=["zzzz"]
invalid value "myvar=[zzzz]" for flag -var: Cannot parse value for variable ("[zzzz]") as valid HCL: At 1:6: unexpected token while parsing list: IDENT
terraform plan -var 'myvar=["zzzz"]'
invalid value "myvar=[zzzz]" for flag -var: Cannot parse value for variable ("[zzzz]") as valid HCL: At 1:6: unexpected token while parsing list: IDENT
Strange.
I used the following main.tf
variable "test" {
type = "list"
}
and running:
$ terraform version
Terraform v0.11.1
$ terraform plan -var 'test=["vvv"]'
completed successfully.
It could be that there is an incorrect IDENT somewhere else in your file/module. Please try running the following command to validate everything except unset variables:
$ terraform validate -check-variables=false

What is the terraform syntax to create an AWS Route53 TXT record that has a map as JSON as payload?

My intention is to create an AWS Route53 TXT record, that contains a JSON representation of a terraform map as payload.
I would expect the following to do the trick:
variable "payload" {
type = "map"
default = {
foo = "bar"
baz = "qux"
}
}
resource "aws_route53_record" "TXT-json" {
zone_id = "${module.domain.I-zone_id}"
name = "test.${module.domain.I-fqdn}"
type = "TXT"
ttl = "${var.ttl}"
records = "${list(jsonencode(var.payload))}"
}
terraform validate and terraform plan are ok with that. terraform apply starts happily, but AWS reports an error:
* aws_route53_record.TXT-json: [ERR]: Error building changeset: InvalidChangeBatch: Invalid Resource Record: FATAL problem: InvalidCharacterString (Value should be enclosed in quotation marks) encountered with '"{"baz":"qux","foo":"bar"}"'
status code: 400, request id: 062d4536-3ad3-11e7-af24-0fbcd067fb9e
Terraform version is
Terraform v0.9.4
String handling is very difficult in HCL. I found many references surrounding this issue on the 'net, but I can't seem to find the actual solution. A solution based on the workaround noted in terraform#10048 doesn't work. "${list(substr(jsonencode(var.payload), 1, -1))}" removes the starting curly brace {, not the first quote. That seems to be added later.
Adding quotes (as the error message from AWS suggests) doesn't help; it just adds more quotes, and there already are (the AWS error message is misleading).
The message you're getting is not generated by Terraform. It is a validation error raised by Route53. You'd get the same error if you added eg. {"a":2,"foo":"bar"} as value via the AWS console.
On the other hand, escaping the JSON works ie. I was able to add "{\"a\":2,\"foo\":\"bar\"}" as a TXT value through the AWS console.
If you're OK with that, you can perform a double jsonencode, meaning that you can jsonencode the JSON string generated by jsonencode such as:
variable "payload" {
type = "map"
default = {
foo = "bar"
baz = "qux"
}
}
output "test" {
value = "${jsonencode(jsonencode(var.payload))}"
}
which resolves to:
➜ ~ terraform apply
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
test = "{\"baz\":\"qux\",\"foo\":\"bar\"}"
(you would of course have to use the aws_route53_record resource instead of output)
so basically this works:
resource "aws_route53_record" "record_txt" {
zone_id = "${data.aws_route53_zone.primary.zone_id}"
name = "${var.my_domain}"
type = "TXT"
ttl = "300"
records = ["{\\\"my_value\\\", \\\"${var.my_value}\\\"}"]
}
U're welcome.

Resources