terraform error message line numbers - terraform

Is there any way to get the line number causing terraform errors? For example:
$ terraform plan
module root: module foo: bar is not a valid parameter
$
Ideally the error message would give me file paths and line numbers corresponding to the error, e.g.
$ terraform plan
File "maint.tf", line 120:
bar = "123"
InvalidParameterError: "bar" is not a valid parameter of module foo
$
I understand not being a procedural language may make this more difficult but not containing a single file path nor line number seems excessive.

Unfortunately, no, there isn't currently a way to make terraform output the error file or line location
This is a known usability issue with terraform, and the maintainers are updating error messages on a case-by-case basis. (see https://github.com/hashicorp/terraform/issues/1758).
Per mitchellh, "error messages are improving," but for now it seems that humans will have to find the errors.

Due to how terraform state is managed, there aren't always line numbers for an error to map to. Syntactical errors should result in a line number, but there are some scenarios where you'll error because of the terraform state (on disk, on in s3, etc).
For example, the following is a valid main.tf file:
terraform { }
So running terraform apply on the above should work right? Yes, unless the terraform state is tracking a resource which still requires a provider.
Let's say your terraform state matches the following main.tf file.
terraform {
required_providers {
foo_provider { ..source and version }
}
}
provider "foo_provider" {
domain = "this is be a required field"
}
resource "foo_resource" {
name = "bar"
}
If you remove everything foo*, the terraform state is still tracking the the foo_resoruce, so you can't just run terraform apply against an empty main.tf file.
Let's say you do anyway. Run terraform apply against the empty main.tf
terraform { }
You will probably get an error like the following The argument "domain" is required, but was not set. ..and there will be no line number! The error can be super generic and have no mention of the resource or provider causing it. It comes from your terraform tracked state, not syntax. You have to remove the resource from terraform's tracked state before removing the provider (and it's required arguments).

Related

Terraform execution order with locals block

We have a requirement with one of the terraform scripts to execute a python script, generate the output, and read the output file. We are trying to achieve this through the below method,
resource "null_resource" "get_data_plane_ip" {
provisioner "local-exec" {
command = "python myscript.py > output.json"
}
triggers = {
always_run = "${timestamp()}"
}
}
locals {
var1 = jsondecode(file("output.json"))
}
The problem with the above method is, we have seen locals block gets executed before the python script gets executed through local-exec resource. So the terraform apply fails. we can't use depends_on in locals block to specify the order as well.
Any suggestion on how can we make sure locals gets executed only after local-exec resource?
You could potentially use another null_resource resource in this situation.
For example, take the following configuration:
resource "null_resource" "get_data_plane_ip" {
provisioner "local-exec" {
command = "python myscript.py > output.json"
}
triggers = {
always_run = timestamp()
}
}
resource "null_resource" "dependent" {
triggers = {
contents = file("output.json")
}
depends_on = [null_resource.get_data_plane_ip]
}
locals {
var1 = jsondecode(null_resource.b.triggers.contents)
}
output "var1" {
value = local.var1
}
The null_resource.dependent resource has an explicit dependency on the null_resource.get_data_plane_ip resource. Therefore, it will wait for the null_resource.get_data_plane_ip resource to be "created".
Since the triggers argument is of type map(string), you can use the file function to read the contents of the output.json file, which returns a string.
You can then create a local variable to invoke jsondecode on the triggers attribute of the null_resource.dependent resource.
The documentation for the file function says the following:
This function can be used only with files that already exist on disk at the beginning of a Terraform run. Functions do not participate in the dependency graph, so this function cannot be used with files that are generated dynamically during a Terraform operation. We do not recommend using dynamic local files in Terraform configurations, but in rare situations where this is necessary you can use the local_file data source to read files while respecting resource dependencies.
There a few different things to note in this paragraph. The first is that the documentation recommends against doing what you are doing except as a last resort; I don't know if there's another way to get the result you were hoping for, so I'll leave you to think about that part, and focus on the other part...
The local_file data source is a data resource type that just reads a file from local disk. Because it appears as a resource rather than as a language operator/function, it'll be a node in Terraform's dependency graph just like your null_resource.get_data_plane_ip, and so it can depend on that resource.
The following shows one way to write that:
resource "null_resource" "get_data_plane_ip" {
triggers = {
temp_filename = "${path.module}/output.json"
}
provisioner "local-exec" {
command = "python myscript.py >'${self.triggers.temp_filename}'"
}
}
data "local_file" "example" {
filename = null_resource.get_data_plane_ip.triggers.temp_filename
}
locals {
var1 = jsondecode(data.local_file.example.content)
}
Note that this sort of design will make your Terraform configuration non-converging, which is to say that you can never reach a point where terraform apply will report that there are no changes to apply. That's often undesirable, because a key advantage of the declarative approach is that you can know you've reached a desired state and thus your remote system matches your configuration. If possible, I'd suggest to try to find an alternative design that can converge after selecting a particular IP address, though that'd typically mean representing that "data plane IP" as a resource itself, which may require writing a custom provider if you're interacting with a bespoke system.
I've not used it myself so I can't recommend it, but I notice that there's a community provider in the registry which offers a shell_script_resource resource type, which might be useful as a compromise between running commands in provisioners and writing a whole new provider. It seems like it allows you to write a script for create where the result would be retained as part of the resource state, and thus you could refer to it from other resources.

Terraform ignore_changes for resource output

Is there anyway to ignore changes to resource output? Or tell terraform to not refresh it?
A terraform resource I'm using returns a state_info output (map of string) that can be modified by processes outside of Terraform. I want to ignore these changes. Is this possible.
resource "aiven_vpc_peering_connection" "this" {
lifecycle {
ignore_changes = [
state_info
]
}
}
state_info is getting set to null outside of Terraform. I'm using state_info in other terraform resources. It's failing with aiven_vpc_peering_connection.this.state_info is empty map of string on subsequent terraform plans I run
The ignore_changes mechanism instructs Terraform to disregard a particular argument when it's comparing values in the configuration with values in the prior state snapshot, so it doesn't have any effect for attributes that are only saved in the prior state due to them not being explicitly configurable.
It sounds like what you want is instead to have Terraform disregard a particular argument when it's updating the prior state to match remote objects (the "refresh" step), so that the result would end up being a mixture of new content from the remote API and content previously saved in the state. Terraform has no mechanism to achieve that: the values stored in the state after refreshing are exactly what the provider returned. This guarantee can be important for some resource types because retaining an old value for one argument while allowing others to change could make the result inconsistent, if e.g. the same information is presented in multiple different ways.
The closest you can get to what you described is to use the value as exported by the upstream resource and then specify ignore_changes on the resource where you ultimately use that value, telling Terraform to ignore the changes in the upstream object when comparing the downstream object with its configuration.

Unknown token IDENT aws_region

I have just run Terraform upgrade. My code was updated but now it shows some errors. The first was:
variable "s3_bucket_name" {
type = list(string)
default = [
"some_bucket_name",
"other_bucket_name",
...
]
}
It doesn't like list(string). I went back to square one and redid the entire Getting Started tutorial. It said that I could either explicitly state type = list or I could implicitly state it by leaving out type and just using the [square brackets].
I saw here: unknown token IDENT list error for IP address variable that I could use "list" (quotes) but I can't find any information on list(string).
So I commented out my list(string) which moved the error along to the next part.
provider "aws" {
region = var.aws_region
}
The tutorial indicates that this is the correct way to create a region tag (there's actually part of the tutorial with that exact code).
Can anyone help me to understand what Unknown token IDENT means as it's throughout my code but it's not helping me to understand what I should do to fix it.
This error appears when you execute terraform 0.12upgrade and your code syntax is already in Terraform 0.12x or obviously a mix of syntax versions <= 0.11x and 0.12x. Also the Unknown token IDENT error can happen when your installed version on your local machine (or in the remote CI/CD server) is 0.11x and your code syntax is on 0.12x and you run a terraform command such as terraform init
variable "var1" {
type = "list"
...
}
This a Terraform 0.11x syntax the alternative 12x is type = list(string)
To reproduce your error, I have a Terraform code 0.12x, I executed terraform 0.12upgrade then the unknown token: IDENT showed up!
In sum, I thought that your first code iteration is already in the correct syntax so there’s no need to upgrade.
To avoid this kind of errors you can add a new version.tf file in your code with this content:
terraform {
required_version = ">= 0.12"
}
Upgrading tips:
Don’t mix the syntaxes in the same Terraform code, if so, downgrade manually your code to 0.11x
Put all your Terraform code syntax in 0.11x
Then run: terraform 0.12upgrade

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.

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