Terraform output from 2 strings to list - amazon-web-services

In terraform I have 2 data outputs:
data "aws_instances" "daas_resolver_ip_1" {
instance_tags = {
Name = "${var.env_type}.${var.environment}.ns1.${var.aws_region}.a."
}
}
data "aws_instances" "daas_resolver_ip_2" {
instance_tags = {
Name = "${var.env_type}.${var.environment}.ns2.${var.aws_region}.b."
}
}
I want to get the private_ip from each of those combine those into a list and be used as follows:
dhcp_options_domain_name_servers = ["${data.aws_instances.daas_resolver_ip_1.private_ip}", "${data.aws_instances.daas_resolver_ip_1.private_ip}"]
How can I achieve this? At the moment this is the error I get:
Error: module.pmc_environment.module.pmc_vpc.aws_vpc_dhcp_options.vpc: domain_name_servers: should be a list

I believe what you've encountered here is a common limitation of Terraform 0.11. If this is a new configuration then starting with Terraform 0.12 should avoid the problem entirely, as this limitation was addressed in the Terraform 0.12 major release.
The underlying problem here is that the private_ip values of at least one of these resources is unknown during planning (it will be selected by the remote system during apply) but then Terraform 0.11's type checker is failing because it cannot prove that these unknown values will eventually produce a list of strings as the dhcp_options_domain_name_servers requires.
Terraform 0.12 addresses this by tracking type information for unknown values and propagating types through expressions so that e.g. in this case it could know that the result is a list of two strings but the strings themselves are not known yet. From Terraform 0.11's perspective, this is just an unknown value with no type information at all, and is therefore not considered to be a list, causing this error message.
A workaround for Terraform 0.11 is to use the -target argument to ask Terraform to deal with the operations it needs to learn the private_ip values first, and then run Terraform again as normal once those values are known:
terraform apply -target=module.pmc_environment.module.pmc_vpc.data.aws_instances.daas_resolver_ip_1 -target=module.pmc_environment.module.pmc_vpc.data.aws_instances.daas_resolver_ip_2
terraform apply
The first terraform apply with -target set should deal with the two data resources, and then the subsequent terraform apply with no arguments should then be able to see what the two IP addresses are.
This will work only if all of the values contributing to the data resource configurations remain stable after the initial creation step. You'd need to repeat this two-step process on subsequent changes if any of var.env_type, var.environment, or var.aws_region become unknown as a result of other planned actions.

Related

Upgrading from Terraform 0.13 to latest breaks my code as there are quotes now put around outputs

I'm having an issue when upgrading from Terraform 0.13 to the latest version, as in Terraform 0.13, there were no quotes around variable outputs.
In 0.13, the following code:
output "bucket-name-output" {
value = "${aws_s3_bucket.logos-bucket.id}"
}
produced this value:
logo-horizon-bucket
In the latest version of Terraform (v 1.3.7), the same code gives this value:
"logo-horizon-bucket"
So you can see that quotes are now being added around the string.
This is then causing my Golang application code, which is trying to upload an object to an S3 bucket, to fail as it's setting the name of the bucket to the output value (which is surrounded by the quotes):
bucketName := terraform.Output(t, terraformOptions, "bucket-name-output")
params := &s3manager.UploadInput{
Bucket: aws.String(bucketName),
Key: aws.String("imageFileName"),
Body: getImage(),
}
I get the following error as S3 bucket names cannot contain quotes.
InvalidBucketName: The specified bucket is not valid.
status code: 400, request id:
So my question is how to do I remove those quotes from inside the Terraform file? I could add some logic to remove them in the Golang application code, but this is pretty messy so I would like to avoid this if possible...
Thanks!
I assume that something in your system is running terraform output bucket-name-output to get the value of this output value.
The terraform output command was always intended primarily for human consumption and at some point since v0.13 the human-oriented output changed to show values using a syntax similar to how they would appear inside the Terraform language itself, because it helps the reader to understand what type of value they have exported.
There are two different ways to tell terraform output that it should produce data in a form suitable for consumption by external software rather than by humans:
terraform output -json bucket-name-output produces a JSON description of the output value. This option will work for values of any type, by following the same encoding decisions as Terraform's jsonencode function.
terraform output -raw bucket-name-output produces just a raw string version of the output value. Technically what it is doing is converting the output value to a string with the same meaning as the tostring function and then printing the resulting string. This means it only works with output values of types that tostring can convert: strings, numbers, and boolean values.
Since your program was originally trying to use the human-readable output as if it was a raw string value, I think the second of these options would be the easiest for you to retrofit into your system. Your program already seems to be expecting a raw string version of the output value and the -raw option designed for exactly that purpose.

Terraform - output variables as quoted values

I'm using Terraform to build our AWS infrastructure projects. I need to be able to output multiple variables to a file and then to load that file back into another Terraform script.
Right now, I'm able to output the variables but they come out with the values not quoted:
variable = value
However, when loading a variable file into Terraform, it expects all values to be quoted, like this:
variable = "value"
So I can't understand why the hell Terraform doesn't just export the variables that way in the first place.
Is there any way to have it do this without requiring additional work on my part?
EDIT: I'm using Terraform v0.11.13 and cannot upgrade due to security restrictions
Output in JSON and use JQ to transform into what you like to have.
terraform output -json
main.tf
output "hogehoge" {
value = "hogehoge"
}
Execution
$ terraform apply
Outputs:
hogehoge = hogehoge
$ terraform output -json
{
"hogehoge": {
"sensitive": false,
"type": "string",
"value": "hogehoge"
}
}
However, as #ydaetskcoR commented, why not use data.terraform_remote_state?

Terraform - AWS - create multiple instances - different AZ (where instance count is greater than AZ list length)

I am facing a problem with Terraform (v0.12) to create multiple instances using count variable and subnet id's list, where the count is greater than the length of the subnet id's list.
For example;
resource "aws_instance" "main" {
count = 20
ami = var.ami_id
instance_type = var.instance_type
# ...
subnet_id = var.subnet_ids_list[count.index]
}
Where my count is '20' and length(var.subnet_ids_list) is 2. It throws the following error:
count.index is 2
var.instance_subnet_id is tuple with 2 elements
The given key does not identify an element in this collection value.
I tried to make the "subnet_ids_list" as string with comma-separated and used "split", but it too give the same error.
Later thought to append subnet elements to "subnet_ids_list" in order to make it to "20". something like;
Python 2.7
>>> subnet_ids_list = subnet_ids_list * 10
Can someone help me with how to achieve similar with Terraform or any other approaches to solve this problem.
Original like;
subnet_ids_list = ["sub-1", "sub-2"]
Converted to - satisfy the value provided to count;
subnet_ids_list = ["sub-1", "sub-2", "sub-1", "sub-2",....., "sub-1", "sub-2",] (length=20).
I don't want to use AWS autoscaling groups for this purpose.
You can use the element function if you need to loop back through a list of things as mentioned in the linked documentation:
The index is zero-based. This function produces an error if used with
an empty list.
Use the built-in index syntax list[index] in most cases. Use this
function only for the special additional "wrap-around" behavior
described below.
> element(["a", "b", "c"], 3)
a
It doesn't make sense to create a new subnet whenever you need to spin up a new EC2. I'd recommend you to take a look at the official documentation about the basics of VPC and subnets: https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Subnets.html#vpc-subnet-basics
For example, if you create a VPC with CIDR block 10.0.0.0/24, it supports 256 IP addresses. You can break this CIDR block into two subnets, each supporting 128 IP addresses. One subnet uses CIDR block 10.0.0.0/25 (for addresses 10.0.0.0 - 10.0.0.127) and the other uses CIDR block 10.0.0.128/25 (for addresses 10.0.0.128 - 10.0.0.255).
In your Terraform example, it looks like you have 2 subnets (private and public?), so your counter must be rather 0 or 1 when accessing subnet_ids_list. Even a better solution would be to tag your subnets: https://www.terraform.io/docs/providers/aws/r/subnet.html#inner
You might have another counter though to control number of instances. Hope it helps!
EDIT: Based on your comments, a Map would be a better data structure to control instance/subnet. Key could be the instance or the subnet itself, e.g. { "aws_instance" = "sub-1" }
Reference: https://www.terraform.io/docs/configuration-0-11/variables.html#maps

Terraform - Conditional operator cannot be used with list values

I'm using Terraform with AWS as a provider.
I want to use a ternary operator in my availability zones local variable.
The logic is simple:
If a variable exist - take it.
If not, use the availability zones data.
The following code:
data "aws_availability_zones" "available" {}
locals {
azs = "${length(var.azs) > 0 ? var.azs : data.aws_availability_zones.available.names}"
}
variable "azs" {
description = "A list of Availability zones in the region"
default = []
type = "list"
}
Generates the following error:
conditional operator cannot be used with list values.
Although its quiet a simple operation, It turns out like a familiar issue.
I followed the work-arounds in the mentioned thread, but they looked looked quiet complicated (Using compact split and join functions together).
Any suggestions for more simple solution?
Thank you.
you are close to the answer.
Not sure how you define the variable var.azs, I guess they are defined as string and connected with commas.
So you need adjust the code, join the list to string.
locals {
azs = "${length(var.azs) > 0 ? var.azs : join(",", data.aws_availability_zones.available.names)}"
}

Terraform Interpolation in outputs

I create an AWS RDS instance with different KMS CMKs depending on whether or not the environment is Production or Non-Production. So I have two resources that use the terraform count if:
count = "${var.bluegreen == "nonprod" ? 1 : 0}"
This spins up an RDS instance with different KMS keys with different addresses. I need to capture that endpoint (which I do with terraform show after the build finishes) so why doesn't this work in Terraform?
output "rds_endpoint" {
value = "${var.bluegreen == "nonprod" ? aws_db_instance.rds_nonprod.address : aws_db_instance.rds_prod.address}"
}
It is an error to access attributes of a resource that has count = 0, and unfortunately Terraform currently checks both "sides" of a conditional during its check step, so expressions like this can fail. Along with this, there is a current behavior that errors in outputs are not explicitly shown since outputs can get populated when the state isn't yet complete (e.g. as a result of using -target). These annoyances all sum up to a lot of confusion in this case.
Instead of using a conditional expression in this case, it works better to use "splat expressions", which evaluate to an empty list in the case where count = 0. This would looks something like the following:
output "rds_endpoint" {
value = "${element(concat(aws_db_instance.rds_nonprod.*.address, aws_db_instance.rds_prod.*.address), 0)}"
}
This takes the first element of a list created by concatenating together all of the nonprod addresses and all of the prod addresses. Due to how you've configured count on these resource blocks, the resulting list will only ever have one element and so it will just take that element.
In general, to debug issues with outputs it can be helpful to evaluate the expressions in terraform console, or somewhere else in a config, to bypass the limitation that errors are silently ignored on outputs.