How do I pass a list as an argument? - terraform

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

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"

Terraform syntax for putting json as value in a map

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\"}"
}

Why is my Terraform output not working in module?

I have the following simple setup:
~$ tree
.
├── main.tf
└── modules
└── world
└── main.tf
~$ cat main.tf
output "root_module_says" {
value = "hello from root module"
}
module "world" {
source = "modules/world"
}
~$ cat modules/world/main.tf
output "world_module_says" {
value = "hello from world module"
}
I then run:
~$ terraform get
~$ terraform apply
I expect to see world_module_says in the outputs, but I do not, I only see root_module_says.
This is really confusing as to why?
If it helps:
~$ terraform --version
v0.10.8
Terraform only shows the output from root (by default pre v0.12)
https://www.terraform.io/docs/commands/output.html
Prior to Terraform 0.12 you can get the output from the world module with:
terraform output -module=world
I think the logic here is that the output from the module would be consumed by root and if you actually needed the output then you'd output it in root too so main.tf might contain this:
output "root_module_says" {
value = "hello from root module"
}
output "world_module_says" {
value = "${module.world.world_module_says}"
}
module "world" {
source = "modules/world"
}
Beginning with Terraform 0.12 this is the only way to get the output from within a module.
Before Terraform 0.12
The below command shows the output for a specific module
terraform output -module=example_module
After Terraform 0.12
The below command fails in Terraform 0.12 and above with an error:
terraform output -module=example_module
Error: Unsupported option
The -module option is no longer supported since Terraform 0.12, because now
only root outputs are persisted in the state.
To get outputs from a module in Terraform 0.12 and above, you must export them from the root (e.g example_module) module using an output block in the caller module. This can be now done simply by adding one line in the caller module like below:
output "example_module_outputs" {
value = module.example_module
}
Now you can see the output running the following command:
terraform output
Expansion to the answers above when using multiple output varialbes:
When accessing a module output group (using the 'terraform output' command from a console or a script) then Terraform will printout all output variables within this group in a formatted way, or in json format using '-json' flag.
When extracting output values, we can use the '-raw' flag only if there is a single variable which has a string format.
If we have more than 1 outout in the output group within the module:
Lets say we declared in the 'root' module the following outout group in a module:
output "world_world_module_says" {
value = module.world.world_module_says
}
and in the module main.tf we have 2 vars:
output "world_module_says" {
value = "hello from world module"
}
output "world_module_says_again" {
value = "hi again from world module"
}
Two ways to extract a single output variable from the group:
using -json flag and manipulating the output using the jq command:
terraform output -json world_world_module_says |jq '.'"world_module_says"
The other way is to declare each output individually in the 'root' module to allow easy access using '-raw':
output "world_world_module_says" {
value = module.world.world_module_says
}
output "world_world_module_says_again" {
value = module.world.world_module_says_again
}

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

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