{% 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).
-
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
this
if 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_gateway
and multiple resources of typeaws_route_table
, soaws_nat_gateway
should be namedthis
andaws_route_table
should 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_each
inside 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_on
andlifecycle
, if necessary. All of these should be separated by a single empty line. -
When using conditions in an argument
count
/for_each
prefer boolean values instead of usinglength
or other expressions.
{% 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 %}
{% 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 %}
{% 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 %}
- Don't reinvent the wheel in resource modules: use
name
,description
, anddefault
value 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
description
on 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.number
type can be converted tostring
). - Use type
any
to 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.
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{name}
fordata "aws_subnet" "private"
isprivate
{name}
forresource "aws_vpc_endpoint_policy" "test"
istest
{type}
is a resource or data source type without a provider prefix{type}
fordata "aws_subnet" "private"
issubnet
{type}
forresource "aws_vpc_endpoint_policy" "test"
isvpc_endpoint_policy
{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 (this
as prefix should be omitted). See example. - If the returned value is a list it should have a plural name. See example.
- Always include
description
for all outputs even if you think it is obvious. - Avoid setting
sensitive
argument 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)
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 %}
{% 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 %}