OpenTofu and Terraform Naming Conventions

OpenTofu and Terraform Naming Conventions


In the previous blog post, I wrote about the principles and best practices of IaC design. To complete the topic of standardization, it is important to consider a standardized codebase. This helps you collaborate more effectively with your teammates in maintaining Terraform modules within your organization and supports development teams in doing so as well. In this post, I’m going to share my experiences with naming conventions for writing a standard block of OpenTofu and Terraform code. Some of these conventions are useful for creating a standard Terraform file, while others are necessary due to restrictions in providers’ APIs, such as those from AWS and Azure.

General Conventions

  • Always use _ (underscore) instead of  (dash)
resource "aws_db_instance" "dev_db" {
  ...
  name = "backend_db_instance"
  ...
}
  • Only use lowercase letters and numbers
resource "aws_key_pair" "ops" {
  key_name   = "roozbeh_key_1"
  public_key = "ssh-rsa ..."
}
HCL

Resource and Data Source Conventions

  • Do not repeat resource type in resource name (not partially, nor completely)
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.example.id
  ...
}
HCL
  • Resource name should be named this if there is no more descriptive and general name available
resource "aws_nat_gateway" "this" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.example.id
  ...
}
HCL
  • Always use singular nouns for names
resource "aws_eip" "loadbalancer" {
  instance = aws_instance.web.id
  vpc      = true
}
HCL
  • Use - inside arguments values and in places where value will be exposed to a human (eg, inside DNS name of RDS instance or Route 53 record)
resource "aws_route53_record" "www" {
  zone_id = aws_route53_zone.primary.zone_id
  name    = "www.example-domain.com"
  type    = "A"
  ttl     = "300"
  records = [aws_eip.lb.public_ip]
}
HCL
  • Include count an argument inside resource blocks as the first argument at the top and separated by a newline after it
resource "aws_instance" "web" {
  count = "5"
  ...
}
HCL
  • Include tags the argument, if supported by resources as the last real argument, followed by depends_on and lifecycle, if necessary. All of these should be separated by a single empty line:
resource "aws_nat_gateway" "this" {
  count         = "1"

  ...

  tags = {
    Name = "..."
  }

  depends_on = ["aws_internet_gateway.this"]

  lifecycle {
    create_before_destroy = true
  }
}
HCL
  • When using conditions in count argument use a boolean value if it makes sense, otherwise, use length or other interpolation:
count = "${length(var.public_subnets) > 0 ? 1 : 0}"
HCL
  • To make inverted conditions don’t introduce another variable unless necessary, use 1 - boolean value instead:
count = "${1 - var.create_public_subnets}"
HCL

Variable Conventions

  • Don’t reinvent the wheel in resource modules — use the same variable names, description, and default as defined in the “Argument Reference” section for the resource you are working on.
  • Use type = "list" declaration if there is default = []:
variable "availability_zone_names" {
  type    = list(string)
  default = [
    "eu-central-1a"
    "eu-central-1b"
    "eu-central-1c"
  ]
}
HCL
  • Use type = "map" declaration if there is default = {} :
variable "images" {
  type = "map"

  default = {
    eu-central-1 = "image-1234"
    eu-west-1    = "image-4567"
  }
}
HCL
  • Use the plural form in the name of variables of type list and map :
variable "users" {
  type    = "list"
  ...
}

variable "images" {
  type = "map"
  ...
}
HCL
  • When defining variables order the keys: description , typedefault . Always include description for all variables even if you think it is obvious.
variable "key" {
  description = "description"
  type        = "string"
  default     = "value"
}
HCL

Outputs

A name for the outputs is important to make them consistent and understandable outside of its scope (when the user is using a module it should be obvious what type and attribute of the value are returned).

  • The general recommendation for output names is that they should be descriptive of the value they contain and less free-form than you would normally want.
  • Good structure for names of output looks like {name}_{type}_{attribute} , where:
    1. {name} is a resource or data source name without a provider prefix. {name} for aws_subnet is subnet, foraws_vpc it is vpc.
    2. {type} is a type of resource source.
    3. {attribute} is an attribute returned by the output
  • If the output is returning a value with interpolation functions and multiple resources, {name} and {type} there should be as generic as possible (this is often the most generic and should be preferred).
  • If the returned value is a list it should have a plural name.
  • Always include description for all outputs even if you think it is obvious.

Conclusion

This approach improves collaboration between you and your teammates, ensuring that everyone is able to work together more efficiently when maintaining and updating Terraform modules across your organization. By establishing a standardized process, it also provides better consistency and clarity, which not only streamlines the workflow within your immediate team but also enables development teams throughout the organization to follow best practices, reduce errors, and achieve better integrations during the development and deployment of infrastructure as code.

Related Posts
Leave a Reply

Your email address will not be published.Required fields are marked *