Terraform can not refer to resource by index - terraform

I have an aws instance defied like so
resource "aws_instance" "an_instance" {
count = "${var.instance_count}"
......
}
which works just fine, BUT when I add this snippet
resource "aws_ebs_volume" "on_host_1_1" {
availability_zone = "${aws_instance.an_instance[1].availability_zone}"
snapshot_id = "snap-abcdca8ee59112345f"
tags = "${local.all_tags}"
}
I get the following error:
Error reading config for aws_ebs_volume[on_host_1_1]: parse error at 1:31: expected "}" but found "."
Any ideas what is wrong?
Terraform v0.11.14
+ provider.aws v2.25.0

You need to use the proper syntax for referencing a specific element of a list. You can see the documentation here. Specifically note the section mentioning:
To reference a particular instance of a resource you can use resource.foo.*.id[#] where # is the index number of the instance.
Therefore, your resource with the proper syntax would be:
resource "aws_ebs_volume" "on_host_1_1" {
availability_zone = "${aws_instance.an_instance.*.availability_zone[1]}"
snapshot_id = "snap-abcdca8ee59112345f"
tags = "${local.all_tags}"
}
which will give you the behavior you desire. The reason this works is because the splat operator * properly signifies to Terraform that the resource output is a list, and not a single element type.

Related

How do I get vpc_id so I can create a VPC subnet in Terraform?

I am creating an AWS VPC with a single public subnet in a brand-new Terraform project, consisting only of a main.tf file. In that file I am using two resource blocks, aws_vpc and aws_subnet. The second resource must be attached to the first using the vpc_id attribute. The value of this attribute is created only upon apply, so it cannot be hard-coded. How do I get the ID of the resource I just created, so I can use it in the subsequent block?
resource "aws_vpc" "my_vpc" {
cidr_block = "102.0.0.0/16"
tags = {
Name = "My-VPC"
}
}
resource "aws_subnet" "my_subnet" {
vpc_id = # what goes here?
cidr_block = "102.0.0.0/24"
tags = {
Name = "My-Subnet"
}
}
The docs give the example of data.aws_subnet.selected.vpc_id for vpc_id. The value of this appears to depend on two other blocks, variable and data. I am having a hard time seeing how to wire these up to my VPC. I tried copying them directly from the docs. Upon running terraform plan I get the prompt:
var.subnet_id
Enter a value:
This is no good; I want to pull the value from the VPC I just created, not enter it at the command prompt. Where do I specify that the data source is the resource that I just created in the previous code block?
I have heard that people create a separate file to hold Terraform variables. Is that what I should to do here? It seems like it should be so basic to get an ID from one resource and use it in the next. Is there a one-liner to pass along this information?
You can just call the VPC in the subnet block by referencing Terraform's pointer. Also, doing this tells Terraform that the VPC needs to be created first and destroyed second.
resource "aws_vpc" "my_vpc" {
cidr_block = "102.0.0.0/16"
tags = {
Name = "My-VPC"
}
}
resource "aws_subnet" "my_subnet" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "102.0.0.0/24"
tags = {
Name = "My-Subnet"
}
}
I want to pull the value from the VPC I just created,
You can't do this. You can't dynamically populate variables from data sources. But you could use local instead:
locals {
subnet_id = data.aws_subnet.selected.id
}
and refer to it as local.subnet_id.

Using Terraform module output as input to local-exec provisioner

I'm trying to define a module testing framework for terraform and my approach is to use Pester, called from a local-exec provisioner in order to verify build is correct.
To this end I was hoping to be able to use output from the module, e.g:
output "windows_ip_address" {
value = module.windowsservers.network_interface_private_ip
}
... as an input for a local-exec provisioner. e.g:
module "windowsservers" {
source = "../../"
vm_hostname = "host${random_id.ip_dns.hex}-windows" // line can be removed if only one VM module per resource group
resource_group_name = azurerm_resource_group.test.name
is_windows_image = true
admin_username = var.admin_username
admin_password = var.admin_password
vm_os_simple = "WindowsServer"
vnet_subnet_id = azurerm_subnet.subnet1.id
}
resource "null_resource" "run-pestertest" {
provisioner "local-exec" {
#command = "..\\test_azurerm_compute.ps1 -vmhostname test -vmip ${module.windowsservers.network_interface_private_ip}"
command = "echo ${module.windowsservers.network_interface_private_ip}"
interpreter = ["pwsh", "-Command"]
}
depends_on = [module.windowsservers]
triggers = {
always_run = "${timestamp()}"
}
}
...but i'm getting:
Error: Invalid template interpolation value: Cannot include the given value in a string template: string required.
I thought by using depends_on i'd be able to force terraform to graph it out in such a way that the "windowsserver" module would be inacted prior to null_resource - but I think maybe there is something fundamentally incorrect with what i'm doing!
Thanks
Dan
I apologize if this is a silly question, but have you verified the module output you want to use (module.windowsservers.network_interface_private_ip) is in fact typed as a string? Perhaps it's a list, or something else .. You can try "forcing" it to be a string in a locals block and see if that either fixes the error or changes it to indicate perhaps the output type isn't actually a string ..
locals = {
module_private_ip = "${tostring(module.windowsservers.network_interface_private_ip)}"
}
I only mention the locals block because it looks like you use it in multiple places, and using the locals means only one place it's used, and once place that could be spitting out the error about invalid type.
I've also used the locals block as a trick to deal with dependencies between modules as TF doesn't always seem to handle that well..
and I apologize for posting as an "answer", but I don't have the karma to post comments yet :)

Terraform migration from v0.11 to v0.12 problem with tags

I'm trying migrate my terraform plan from v0.11 to v0.12 terraform version and when I execute the validation I have some error with the same error : "Unsupported block type" and the service mark the trouble into the "TAGS" tag with that comment:
Blocks of type "tags" are not expected here. Did you mean to define argument "tags"? If so, use the equals sign to assign it a value.
One example it's this troubling resource:
resource "aws_vpc" "VPC" {
cidr_block = "10.0.0.0/24"
enable_dns_hostnames = "true"
enable_dns_support = "true"
tags {
Name = "${var.name}-VPC-Default"
Environment = var.env
Region = var.region
}
}
I read into the documentation about this resource that support tag type "TAGS" and into the v0.11 version it's working fine.
Any suggestion about what it's my problem?
The error is explaining that in Terraform 0.12 tags are no longer a block, but rather now an argument. A block in Terraform appears like:
block { ... }
which is how your tags currently appear. An argument appears like:
argument = value
Therefore, you need to convert your tags from a block to an argument. That can be done like the following:
tags = {
Name = "${var.name}-VPC-Default"
Environment = var.env
Region = var.region
}
where tags is now being assigned the map value you formerly contained within your block.

How to iterate multiple resources over the same list?

New to Terraform here. I'm trying to create multiple projects (in Google Cloud) using Terraform. The problem is I've to execute multiple resources to completely set up a project. I tried count, but how can I tie multiple resources sequentially using count? Here are the following resources I need to execute per project:
Create project using resource "google_project"
Enable API service using resource "google_project_service"
Attach the service project to a host project using resource "google_compute_shared_vpc_service_project" (I'm using shared VPC)
This works if I want to create a single project. But, if I pass a list of projects as input, how can I execute all the above resources for each project in that list sequentially?
Eg.
Input
project_list=["proj-1","proj-2"]
Execute the following sequentially:
resource "google-project" for "proj-1"
resource "google_project_service" for "proj-1"
resource "google_compute_shared_vpc_service_project" for "proj-1"
resource "google-project" for "proj-2"
resource "google_project_service" for "proj-2"
resource "google_compute_shared_vpc_service_project" for "proj-2"
I'm using Terraform version 0.11 which does not support for loops
In Terraform, you can accomplish this using count and the two interpolation functions, element() and length().
First, you'll give your module an input variable:
variable "project_list" {
type = "list"
}
Then, you'll have something like:
resource "google_project" {
count = "${length(var.project_list)}"
name = "${element(var.project_list, count.index)}"
}
resource "google_project_service" {
count = "${length(var.project_list)}"
name = "${element(var.project_list, count.index)}"
}
resource "google_compute_shared_vpc_service_project" {
count = "${length(var.project_list)}"
name = "${element(var.project_list, count.index)}"
}
And of course you'll have your other configuration in those resource declarations as well.
Note that this pattern is described in Terraform Up and Running, Chapter 5, and there are other examples of using count.index in the docs here.
A small update to this question/answer (terraform 0.13 and above). The count or length is not advisable to use anymore due to the way that terraforms works, let's imagine the next scenario:
Suppose you have an array with 3 elements: project_list=["proj-1","proj-2","proj-3"], once you apply that if you want to delete the "proj-2" item from your array once you run the plan, terraform will modify your second element to "proj-3" instead of removing It from the list (more info in this good post). The solution to get the proper behavior is to use the for_each function as follow:
variable "project_list" {
type = list(string)
}
resource "google_project" {
for_each = toset(var.project_list)
name = each.value
}
resource "google_project_service" {
for_each = toset(var.project_list)
name = each.value
}
resource "google_compute_shared_vpc_service_project" {
for_each = toset(var.project_list)
name = each.value
}
Hope this helps! 👍

concatenate two variables in terraform

i'm trying to create a kubernetes cluster from kops using terraform,following code is a part of infrastructure, i'm trying to create the name concatenate with two variables, and i'm getting illegal char error line two, error happens because im trying to define the name with concatenate variables. is it possible in terraform?
resource "aws_autoscaling_group" "master-kubernetes" {
name = "master-"${var.zone}".masters."${var.cluster_name}""
launch_configuration = "${aws_launch_configuration.master-kubernetes.id}"
max_size = 1
min_size = 1
vpc_zone_identifier = ["${aws_subnet.subnet-kubernetes.id}"]
With latest terraform 0.12.x terraform format doc , you could do better like:
resource "aws_autoscaling_group" "master-kubernetes" {
name = format("master-%s.masters.%s", var.zone, var.cluster_name)
}
Try this:
resource "aws_autoscaling_group" "master-kubernetes" {
name = "master-${var.zone}.masters.${var.cluster_name}"
# ... other params ...
}
I would say rather concatenating at resource level use the locals first define a variable in locals and then you can utilize it at resource level
Locals declaration
locals {
rds_instance_name = "${var.env}-${var.rds_name}"
}
Resource Level declaration
resource "aws_db_instance" "default_mssql" {
count = var.db_create ? 1 : 0
name = local.rds_instance_name
........
}
This is as simple as its need to be ....

Resources