Naming conventions
Direct copy: https://www.terraform-best-practices.com/naming
General conventions
{% hint style="info" %} There should be no reason to not follow at least these conventions :) {% endhint %}
{% hint style="info" %} Beware that actual cloud resources often have restrictions in allowed names. Some resources, for example, can't contain dashes, some must be camel-cased. The conventions in this book refer to Terraform names themselves. {% endhint %}
- Use
_(underscore) instead of-(dash) everywhere (in resource names, data source names, variable names, outputs, etc). - Prefer to use lowercase letters and numbers (even though UTF-8 is supported).
Resource and data source arguments
-
Do not repeat resource type in resource name (not partially, nor completely):
{% hint style="success" %}
resource "aws_route_table" "public" {}{% endhint %}{% hint style="danger" %}
resource "aws_route_table" "public_route_table" {}{% endhint %}{% hint style="danger" %}
resource "aws_route_table" "public_aws_route_table" {}{% endhint %} -
Resource name should be named
thisif there is no more descriptive and general name available, or if the resource module creates a single resource of this type (eg, in AWS VPC module there is a single resource of typeaws_nat_gatewayand multiple resources of typeaws_route_table, soaws_nat_gatewayshould be namedthisandaws_route_tableshould have more descriptive names - likeprivate,public,database). - Always use singular nouns for names.
- Use
-inside arguments values and in places where value will be exposed to a human (eg, inside DNS name of RDS instance). - Include argument
count/for_eachinside resource or data source block as the first argument at the top and separate by newline after it. - Include argument
tags,if supported by resource, as the last real argument, following bydepends_onandlifecycle, if necessary. All of these should be separated by a single empty line. - When using conditions in an argument
count/for_eachprefer boolean values instead of usinglengthor other expressions.
Code examples of resource
Usage of count / for_each
{% hint style="success" %} {% code title="main.tf" %}
resource "aws_route_table" "public" {
count = 2
vpc_id = "vpc-12345678"
# ... remaining arguments omitted
}
resource "aws_route_table" "private" {
for_each = toset(["one", "two"])
vpc_id = "vpc-12345678"
# ... remaining arguments omitted
}
{% endcode %} {% endhint %}
{% hint style="danger" %} {% code title="main.tf" %}
resource "aws_route_table" "public" {
vpc_id = "vpc-12345678"
count = 2
# ... remaining arguments omitted
}
{% endcode %} {% endhint %}
Placement of tags
{% hint style="success" %} {% code title="main.tf" %}
resource "aws_nat_gateway" "this" {
count = 2
allocation_id = "..."
subnet_id = "..."
tags = {
Name = "..."
}
depends_on = [aws_internet_gateway.this]
lifecycle {
create_before_destroy = true
}
}
{% endcode %} {% endhint %}
{% hint style="danger" %} {% code title="main.tf" %}
resource "aws_nat_gateway" "this" {
count = 2
tags = "..."
depends_on = [aws_internet_gateway.this]
lifecycle {
create_before_destroy = true
}
allocation_id = "..."
subnet_id = "..."
}
{% endcode %} {% endhint %}
Conditions in count
{% hint style="success" %} {% code title="outputs.tf" %}
resource "aws_nat_gateway" "that" { # Best
count = var.create_public_subnets ? 1 : 0
}
resource "aws_nat_gateway" "this" { # Good
count = length(var.public_subnets) > 0 ? 1 : 0
}
{% endcode %} {% endhint %}
Variables
- Don't reinvent the wheel in resource modules: use
name,description, anddefaultvalue for variables as defined in the "Argument Reference" section for the resource you are working with. - Support for validation in variables is rather limited (e.g. can't access other variables or do lookups). Plan accordingly because in many cases this feature is useless.
- Use the plural form in a variable name when type is
list(...)ormap(...). - Order keys in a variable block like this:
description,type,default,validation. - Always include
descriptionon all variables even if you think it is obvious (you will need it in the future). - Prefer using simple types (
number,string,list(...),map(...),any) over specific type likeobject()unless you need to have strict constraints on each key. - Use specific types like
map(map(string))if all elements of the map have the same type (e.g.string) or can be converted to it (e.g.numbertype can be converted tostring). - Use type
anyto disable type validation starting from a certain depth or when multiple types should be supported. - Value
{}is sometimes a map but sometimes an object. Usetomap(...)to make a map because there is no way to make an object.
Outputs
Make outputs consistent and understandable outside of its scope (when a user is using a module it should be obvious what type and attribute of the value it returns).
- The name of output should describe the property it contains and be less free-form than you would normally want.
- Good structure for the name of output looks like
{name}_{type}_{attribute}, where: {name}is a resource or data source name without a provider prefix.{name}foraws_subnetissubnet, foraws_vpcit isvpc.{type}is a type of a resource sources{attribute}is an attribute returned by the output- See examples.
- If the output is returning a value with interpolation functions and multiple resources,
{name}and{type}there should be as generic as possible (thisas prefix should be omitted). See example. - If the returned value is a list it should have a plural name. See example.
- Always include
descriptionfor all outputs even if you think it is obvious. - Avoid setting
sensitiveargument unless you fully control usage of this output in all places in all modules. - Prefer
try()(available since Terraform 0.13) overelement(concat(...))(legacy approach for the version before 0.13)
Code examples of output
Return at most one ID of security group:
{% hint style="success" %} {% code title="outputs.tf" %}
output "security_group_id" {
description = "The ID of the security group"
value = try(aws_security_group.this[0].id, aws_security_group.name_prefix[0].id, "")
}
{% endcode %} {% endhint %}
When having multiple resources of the same type, this should be omitted in the name of output:
{% hint style="danger" %} {% code title="outputs.tf" %}
output "this_security_group_id" {
description = "The ID of the security group"
value = element(concat(coalescelist(aws_security_group.this.*.id, aws_security_group.web.*.id), [""]), 0)
}
{% endcode %} {% endhint %}
Use plural name if the returning value is a list
{% hint style="success" %} {% code title="outputs.tf" %}
output "rds_cluster_instance_endpoints" {
description = "A list of all cluster instance endpoints"
value = aws_rds_cluster_instance.this.*.endpoint
}
{% endcode %} {% endhint %}