Terraform unquoted resource for Terraform 0.11 - terraform

I am using terraform 0.11 for a custom module implementation of terraform-aws-provider. Inside the module there are a lot of unquoted resource value assignments that make me confused since they are not working.
This is the sample module code that I am using
resource "aws_iam_policy" "example" {
name = example_policy
policy = data.aws_iam_policy_document.example.json
}
In the official terraform documentation, it was given with interpolation around the value to become
resource "aws_iam_policy" "example" {
name = "example_policy"
policy = "${data.aws_iam_policy_document.example.json}"
}
from: https://www.terraform.io/docs/providers/aws/d/iam_policy_document.html
When I tried to do terraform get the following error message came up: Unknown token: 39:24 IDENT data.aws_iam_policy_document.example.json, and when I tried to use terraform 0.12, it managed to fetch it correctly.
Is this unquoted resource value exclusive to terraform > v0.12?

Yes. Terraform 0.11 requires that all references look like string interpolations (like your second example). Terraform 0.12 added support for first-class expressions which let you reference variables outside of strings (like your first example).
The docs also include an example of the newer, cleaner syntax:
# Old 0.11 example
tags = "${merge(map("Name", "example"), var.common_tags)}"
# Updated 0.12 example
tags = merge({ Name = "example" }, var.common_tags)

Related

How to use depends_on in terraform if resources are created using count or for_each

Hello How to use depends_on in the cases of count or for_each.
The basic syntax of depends_on assumes
resource "azurerm_storage_container" "cont1"{
.....
.....
depends_on = [
azurerm_storage_account.storageAccount
]
}
resource "azurerm_storage_account" "storageAccount" {
count = 3
name = "storageAccountName-${count.index}"
resource_group_name = "rg-demo"
location = "centralus"
account_tier = "Standard"
account_replication_type= "LRS"
# all other relevant attributes
}
So here its azurerm_storage_account.storageAccount if only one such resource were to be created but if we have many azurerm_storage_account created as for_each or count was used in them then what to write in place of storageAccount? Like suppose we have to have a dependency of storageAccountName-2 on the container then how will we write the depends on?
As was mentioned in a comment above, you can add an index to the storageAccount resource reference:
resource "azurerm_storage_account" "test_storage_account" {
count = 3
name = "teststorageaccount${count.index}"
...
}
resource "azurerm_storage_container" "container" {
depends_on = [
azurerm_storage_account.test_storage_account[0]
]
name = "testcontainer"
...
}
You can also omit the index and have the container(s) depend on the entire storage account resource. Both worked in my testing.
Additionally, while implicit dependency would normally allow you to not have a "depends_on" block in the container resource (if you had a single storage account), the implicit dependency does not appear to work when you have multiple storage accounts created with a "for_each" that are later referenced in a storage container resource.
Dependencies in Terraform are only between static blocks, not between individual instances declared by those blocks. This is because the count and for_each expressions themselves can have dependencies, and so Terraform must build the dependency graph before calculating which instances will exist for each resource or module.
Therefore using count and for_each for a resource changes nothing about how you would create dependencies for the resource.
As usual, the most common way to create a dependency is to just refer to the resource in at least one of the arguments of another resource. That creates a dependency automatically, so you don't need depends_on at all:
resource "azurerm_storage_account" "example" {
count = 3
# ...
}
resource "azurerm_storage_container" "example" {
# the azurerm_storage_account.example part of the following
# expression creates a dependency, regardless of what
# else this expression does.
count = length(azurerm_storage_account.example)
# ...
}
The depends_on argument is needed only for situations where a remote API design causes there to be "hidden dependencies" that Terraform can't discover automatically by noticing your references.
One way that could be true with the resource types you showed here is if you are writing a shared module that declares both of these resources and then exports the storage account IDs as an output value. In that case you may wish to tell Terraform that the output value also depends on the storage container, which would represent that the storage account isn't actually ready to use until the storage container has been created:
output "storage_account_ids" {
value = toset(azurerm_storage_account.example[*].id)
# The storage accounts are not ready to use
# until their containers are also ready.
depends_on = [azurerm_storage_container.example]
}
Notice that depends_on refers to just azurerm_storage_container.example, because Terraform only tracks dependencies on whole resources.
Although it is syntactically valid to write an instance ID here -- for example, you could declare a dependency on azurerm_storage_container.example[0] and Terraform would accept it -- Terraform will just ignore the [0] part and create a dependency on the whole resource anyway. The [0] suffix makes absolutely no difference to Terraform's behavior.

Is there a way to perform resource renaming (terraform state mv) as a part of schema migration?

This question is very similar to Terraform: automate state mv (migration / evolution)
Is it possible to do a resource rename foo -> fooo by implementing StateUpgraders attribute, i.e. programmatically change TF state instead of manually running terraform mv foo.example fooo.example.
Yes, in newer versions of Terraform you should be able to use the moved keyword [1]. An example from the docs:
resource "aws_instance" "a" {
count = 2
# (resource-type-specific configuration)
}
resource "aws_instance" "b" {
count = 2
# (resource-type-specific configuration)
}
moved {
from = aws_instance.a
to = aws_instance.b
}
Since you did not give any more details (e.g., which provider and/or resource type) the example above will work only for the aws provider and for the aws_instance resource, but you can do the same for any other provider and resource type.
[1] https://www.terraform.io/language/modules/develop/refactoring#moved-block-syntax

Terraform outputs 'Error: Variables not allowed' when doing a plan

I've got a variable declared in my variables.tf like this:
variable "MyAmi" {
type = map(string)
}
but when I do:
terraform plan -var 'MyAmi=xxxx'
I get:
Error: Variables not allowed
on <value for var.MyAmi> line 1:
(source code not available)
Variables may not be used here.
Minimal code example:
test.tf
provider "aws" {
}
# S3
module "my-s3" {
source = "terraform-aws-modules/s3-bucket/aws"
bucket = "${var.MyAmi}-bucket"
}
variables.tf
variable "MyAmi" {
type = map(string)
}
terraform plan -var 'MyAmi=test'
Error: Variables not allowed
on <value for var.MyAmi> line 1:
(source code not available)
Variables may not be used here.
Any suggestions?
This error can also occurs when trying to setup a variable's value from a dynamic resource (e.g: an output from a child module):
variable "some_arn" {
description = "Some description"
default = module.some_module.some_output # <--- Error: Variables not allowed
}
Using locals block instead of the variable will solve this issue:
locals {
some_arn = module.some_module.some_output
}
I had the same error, but in my case I forgot to enclose variable values inside quotes (" ") in my terraform.tfvars file.
This is logged as an issue on the official terraform repository here:
https://github.com/hashicorp/terraform/issues/24391
I see two things that could be causing the error you are seeing. Link to terraform plan documentation.
When running terraform plan, it will automatically load any .tfvars files in the current directory. If your .tfvars file is in another directory you must provide it as a -var-file parameter. You say in your question that your variables are in a file variables.tf which means the terraform plan command will not automatically load that file. FIX: rename variables.tf to variables.tfvars
When using the -var parameter, you should ensure that what you are passing into it will be properly interpreted by HCL. If the variable you are trying to pass in is a map, then it needs to be parse-able as a map.
Instead of terraform plan -var 'MyAmi=xxxx' I would expect something more like terraform plan -var 'MyAmi={"us-east-1":"ami-123", "us-east-2":"ami-456"}'.
See this documentation for more on declaring variables and specifically passing them in via the command line.
I had the same issue, but my problem was the missing quotes around default value of the variable
variable "environment_name" {
description = "Enter Environment name"
default= test
}
This is how I resolved this issues,
variable "environment_name" {
description = "Enter Environment name"
default= "test"
}
Check the terraform version.
I had something similar , the module was written on version 1.0 and I was using terraform version 0.12.
I had this error on Terraform when trying to pass a list into the module including my Data source:
The given value is not suitable for module. ...
In my case I was passing the wrong thing to the module:
security_groups_allow_to_msk_on_port_2181 = concat(var.security_groups_allow_to_msk_2181, [data.aws_security_group.client-vpn-sg])
It expected the id only and not the whole object. So instead this worked for me:
security_groups_allow_to_msk_on_port_2181 = concat(var.security_groups_allow_to_msk_2181, [data.aws_security_group.client-vpn-sg.id])
Also be sure what type of object you are receiving: is it a list? watch out for the types. I had the same error message when the first argument was also enclosed in [] (brackets), since it already was a list.

Will resources definition moving to a different module make 'terraform apply' to delete and recreate these resources?

I have created some VMs with a main.tf, and terraform generates a cluster.tfstate file.
Now because of refactoring, I move the VM resource definitions into a module, and refer to this module in main.tf. When I run terraform apply --state=./cluster.tfstate, will terraform destroy and recreate these VMs?
I would expect it will not. Is my understanding correct?
Let's try this using the example given in the aws_instance documentation:
# Create a new instance of the latest Ubuntu 14.04 on an
# t2.micro node with an AWS Tag naming it "HelloWorld"
provider "aws" {
region = "us-west-2"
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "web" {
ami = "${data.aws_ami.ubuntu.id}"
instance_type = "t2.micro"
tags {
Name = "HelloWorld"
}
}
If we terraform apply this, we get an instance that is referenced within Terraform as aws_instance.web:
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
If we move this definition to a module ubuntu_instance, the directory structure might look like this with the above code in instance.tf:
.
├── main.tf
└── ubuntu_instance
└── instance.tf
Now you intend to create the same instance as before, but internally Terraform now names this resource module.ubuntu_instance.aws_instance.web
If you attempt to apply this, you would get the following:
Plan: 1 to add, 0 to change, 1 to destroy.
The reason this happens is that Terraform has no idea that the old and new code reference the same instance. When you refactor in a module, you are removing a resource, and thus Terraform deletes that resource.
Terraform maps your code to real resources in the state file. When you create an instance, you can only know that instance maps to your aws_instance because of the state file. So the proper way (as mentioned by Jun) is to refactor your code, then tell Terraform to move the mapping to the real instance from aws_instance.web to module.ubuntu_instance.aws_instance.web Then when you apply, Terraform will leave the instance alone because it matches what your code says. The article Jun linked to is a good discussion of this.

Referring to resources named with variables in Terraform

I'm trying to create a module in Terraform that can be instantiated multiple times with different variable inputs. Within the module, how do I reference resources when their names depend on an input variable? I'm trying to do it via the bracket syntax ("${aws_ecs_task_definition[var.name].arn}") but I just guessed at that.
(Caveat: I might be going about this in completely the wrong way)
Here's my module's (simplified) main.tf file:
variable "name" {}
resource "aws_ecs_service" "${var.name}" {
name = "${var.name}_service"
cluster = ""
task_definition = "${aws_ecs_task_definition[var.name].arn}"
desired_count = 1
}
resource "aws_ecs_task_definition" "${var.name}" {
family = "ecs-family-${var.name}"
container_definitions = "${template_file[var.name].rendered}"
}
resource "template_file" "${var.name}_task" {
template = "${file("task-definition.json")}"
vars {
name = "${var.name}"
}
}
I'm getting the following error:
Error loading Terraform: Error downloading modules: module foo: Error loading .terraform/modules/af13a92c4edda294822b341862422ba5/main.tf: Error reading config for aws_ecs_service[${var.name}]: parse error: syntax error
I was fundamentally misunderstanding how modules worked.
Terraform does not support interpolation in resource names (see the relevant issues), but that doesn't matter in my case, because the resources of each instance of a module are in the instance's namespace. I was worried about resource names colliding, but the module system already handles that.
The picture below shows what is going on.
The terraform documentation does not make their use of "NAME" clear versus the "name" values that are used for the actual resources created by the infrastructure vender (like, AWS or Google Cloud).
Additionally, it isn't always "name=, but sometimes, say, "endpoint= or even "resource_group_name= or whatever.
And there are a couple of ways to generate multiple "name" values -- using count, variables, etc., or inside tfvar files and running terraform apply -var-file=foo.tfvars

Resources