terraform; purpose of redundant "name" key? - terraform

Looking at the definition of a Resource here: https://www.terraform.io/docs/configuration/resources.html, you see examples like this:
resource "aws_iam_role" "example" {
name = "example"
# assume_role_policy is omitted for brevity in this example. See the
# documentation for aws_iam_role for a complete example.
assume_role_policy = "..."
}
The first example in the top line is called the "name" and can be used to reference, eg aws_iam_role.example. However, there is no discussion of 1) why there is also a key called "name" and why it is redundantly set to the same string "example". What is the purpose of this inner key name = example, and what is the best practices here?
EDIT: this seems related: https://github.com/hashicorp/terraform/issues/16394

"aws_iam_role" "example" - you reference this resource in your terraform config (e.g. aws_iam_role.example, must be unique at least within one module)
name = "example" - is the name of your resource on the cloud side (must be unique on the cloud side, e.g. within one project or region, depends on the provider)
At the same time they could be different in your terraform config, for example:
resource "aws_iam_role" "my_role" {
name = "${terraform.workspace}-example-role"
# assume_role_policy is omitted for brevity in this example. See the
# documentation for aws_iam_role for a complete example.
assume_role_policy = "..."
}
PS: in this case (with aws_iam_role resource) the name argument is optional. If you skip it, you will see a randomly generated resource name in your AWS console.

Related

Terraform - Variable name for CosmosDB

I'm using Terraform to create the resources in Azure and have split the files as main.tf, variables.tf and terraform.tfvars
In order to have a standard naming convention, I'm following the process below when naming the resources.
prefix-environment-resourcename
For example, in main.tf I'm creating it as below:
resource "azurerm_resource_group" "rg" {
name = "${var.prefix}-${var.environment}-${var.resource_group_name}"
location = "westus"
}
The variables will be declared in variables.tf and the terraform.tfvars will contain
prefix = "sample"
environment = "dev"
resource_group_name = "rg"
and when the Terraform is executed, I'll get the resource name created as "sample-dev-rg"
This will come in handy when I'm creating other resources or deploy the code to other environments. Since I just need to modify the tfvars alone.
Another example:
resource "azurerm_app_service" "example" {
name = "${var.prefix}-${var.environment_name}-${var.appservice_name}"
}
My issue is:
How do I use the logic above for CosmosDb? I need the name in the main.tf to be
created without special characters.
How do I create something like
this: sampledevcosmosdbname
If you're using Terraform 0.13 and above, you can make use of regex validation for each of the variables that make up your resource names, and ensure that none of them use special/unusual characters. Here's an example prefix variable that can only use A-Z, a-z, 0-9, and - characters:
variable "prefix" {
type = string
description = "Prefix ID"
validation {
condition = can(regex("^[A-Za-z0-9-]*$", var.prefix))
error_message = "The prefix cannot use special characters."
}
}
To create something like sampledevcosmosdbname (prefix, environment, dbname), you can just place several interpolations next to one another like so - no separation is needed:
resource "azurerm_cosmosdb_sql_database" "example" {
...
name = "${var.prefix}${var.environment}${var.dbname}"
}

proper way to use nested variables in terraform

In my terraform script, I have
resource "azuread_application" "main" {
count = "${length(var.sp_names)}"
name = "${sp_prefix}-${var.sp_names[count.index]}"
available_to_other_tenants = false
}
resource "azuread_service_principal" "main" {
count = "${length(var.sp_names)}"
application_id = "${azuread_application.main.["${sp_prefix}"-"${var.sp_names[count.index]}"].application_id}"
}
when I ran terraform init I get the following error:
An attribute name is required after a dot.
what is the right way to use nested variables and a list object?
In order for a resource to be represented as a map of instances rather than a list of instances, you need to use for_each instead of count:
resource "azuread_application" "main" {
for_each = { for n in var.sp_names : n => "${var.sp_prefix}-${n}" }
name = each.value
available_to_other_tenants = false
}
The for_each expression above is a for expression that transforms your list or set of names into a mapping from the given names to the prefixed names. In the other expressions in that block, each.key would therefore produce the original given name and each.value the prefixed name.
You can then similarly use for_each to declare the intent "create one service principal per application" by using the application resource's map itself as the for_each expression for the service principal resource:
resource "azuread_service_principal" "main" {
for_each = azuread_application.main
application_id = each.value.application_id
}
In this case, the azuread_application.main value is a map from unprefixed names to objects representing each of the declared applications. Therefore each.key in this block is the unprefixed name again, but each.value is the corresponding application object from which we can access the application_id value.
If your var.sp_names had a string "example" in it, then Terraform would interpret the above as a request to create two objects named azuread_application.main["example"] and azuread_service_principal.main["example"], identifying these instances by the var.sp_names values. This is different to count where the instances would have addresses like azuread_application.main[0] and azuread_service_principal.main[0]. By using for_each, we ensure that adding and removing items from var.sp_names will add and remove corresponding instances from those resources, rather than updating existing ones that happen to share the same numeric indices.
I am assuming you are using a version older that 0.12.x. If not the answer from Martin is the best one.
You need to leverage the splatting.
resource "azuread_service_principal" "main" {
count = "${length(var.sp_names)}"
application_id = "${azuread_application.main.*.application_id}"
}

How to add a resource using the same module?

Terraform newbie here. I've a module which creates an instance in GCP. I'm using variables and terraform.tfvars to initialize them. I created one instance successfully - say instance-1. But when I modify the .tfvars file to create a second instance (in addition to the first), it says it has to destroy the first instance. How can I run the module to 'add' an instance, instead of 'replacing the instance'? I know the first instance which was created is in terraform.tfstate. But that doesn't explain the inability to 'add' an instance.
Maybe I'm wrong, but I'm looking at 'modules' (and its config files) as functions- such that I can call them anytime with different parameters. That does not appear to be the case.
Terraform will try to maintain the deployed resources matching your resources definition.
If you want two instances at the same time, then you should describe them both in your .tf file.
Ex. same instances, add a count to your definition
resource "some_resource" "example" {
count = 2
name = "example-${count.index}"
}
Ex. two different resources with specific values
resource "some_resource" "example-1" {
name = "example-1"
size = "small"
}
resource "some_resource" "example-2" {
name = "example-2"
size = "big"
}
Better you can set the specific values in tfvars for each resource
resource "some_resource" "example" {
count = 2
name = "example-${count.index}"
size = ${vars.mysize[count.index]}
}
variable mysize {}
with tfvars file:
mysize = ["small", "big"]

Terraform variable files

I am trying to use a variables file to deploy resource groups in Azure using Terraform but it works if I only have one variable. If I use two I get an error:
"invalid value "variables.tf" for flag -var-file: multiple map
declarations not supported for variables"
The variables file is as below :
variable "resource_group_name" {
description = "The name of the resource group in which the resources will be created"
default = "im-from-the-variables-file"
}
variable "location" {
description = "The location/region where the virtual network is created. Changing this forces a new resource to be created."
default = "west europe"
}
The main file used to deploy is as below:
resource "azurerm_resource_group" "vm" {
name = "${var.resource_group_name}"
location = "${var.location}"
}
You've confused the variable definition syntax to the variable setting syntax.
Terraform will concatenate all the .tf files in a directory so your variables.tf file (assuming it's in the same directory as your main.tf (or whatever contains your azurerm_resource_group resources etc) is already included.
You need to define every variable before it can be used so things like:
resource "azurerm_resource_group" "vm" {
name = "${var.resource_group_name}"
location = "${var.location}"
}
by themselves would not be valid as the variables resource_group_name and location are not defined.
You define variables with the syntax you've used in your variables.tf file:
variable "location" {
description = "The location/region where the virtual network is created. Changing this forces a new resource to be created."
default = "west europe"
}
To override the defaults (if wanted or if a default isn't provided) then you need to either pass the variable in at run time (using TF_VAR_location environment variables or by using -var location="west us") or you can define vars files that take the form:
location = "west us"
resource_group_name = "im-from-the-variables-file"
Terraform will automatically load any files in the directory called terraform.tfvars or *.auto.tfvars and you can also define extra vars files at any point by using -var-file=myvars.tfvars as you have attempted to do (but with a .tf file containing HCL instead of key-pairs.
Adding to what ydaetskcoR has mentioned in above answer. If you have already specified default values in variable file for all variables defined and you need just that default values you don't even need to pass -var-file since default values will be used if you don't pass values

Need to understand terraform resource structure

I am reading terraform and found below code:
resource "aws_vpc" "vpc_main" {
cidr_block = "10.0.0.0/16"
tags {
Name = "Main VPC"
}
}
Here I could not understand what vpc_main stands for in the resource definition. Could somebody explain?
It's a user-defined name for the resource - without this you wouldn't be able to distinguish multiple instances of the same resource type.
See the docs for more details.
Variable types and names in other programming languages are a good analogy. For example, in Java, you can declare a variable as follows:
String foo
The type of the variable is String and the name of the variable is foo. You must specify a name so you can (a) distinguish it from other variables of the same type and (b) refer to it later.
In Terraform, it's essentially the same idea:
resource "aws_instance" "foo" { ... }
Here, the type of the resource is aws_instance and the name is foo. You must specify a name so you can (a) distinguish it from other resources of the same type and (b) refer to it later. For example, to output the public IP address of that Instance, you could do the following:
output "public_ip" {
value = "${aws_instance.foo.public_ip}"
}

Resources