We’ve all been there we’ve set up multiple resources and tried to be as consistent as possible when it comes to naming conventions. This is not a simple assignment, this is where Terraform can help a litte.
The naming dilemma
Let’s take Azure naming as an example. This logic applies to multiple providers.
For Azure there are 3 topics we have to consider when choosing a name: rules, restrictions and recommendations.
Let us use a resource group as an example:
Rules
- Length should be between 1 and 90 characters
- Can include alphanumeric, underscore, parentheses, hyphen, period (except at end), and Unicode characters that match the allowed characters
- The name is case insensitive
Restrictions
- Should be unique withing a subscription
Recomendations
- Prefix with
rg-
to denote resource type - Randomize the name to avoid naming collisions. This is usually done by adding a random string as suffix.
- Add valuable information in the name. Ex. environment name, org name, …
Terraform solution
Luckily Terraform has us covered by providing dynamic naming/functions which can hold all of these rules, restrictions and recommendations. (except the validation of the unique name).
1locals {2 org = "fds"3 prj = "naming"4 env = "dev"5 random_suffix = "rtfdg"6}78resource "azurerm_resource_group" "main" {9 name = regex("^[-\\w\\._\\(\\)]+$",substr("rg-${local.org}-${local.prj}-${local.env}-main-${local.suffix}", 90))10 location = "westeurope"11}
This will result in rg-fds-naming-dev-rtfdg
Adding all of this code to each creation of a resource group does not seem to be very efficient. This is when Terraform Modules comes along.
All of this logic can be moved into a single naming module.
1terraform {2 required_providers {3 random = {4 source = "hashicorp/random"5 version = "~> 2.2"6 }7 }8}910variable "prefix" {11 type = list(string)12 default = []13 description = "It is not recommended that you use prefix by azure you should be using a suffix for your resources."14}1516variable "suffix" {17 type = list(string)18 default = []19 description = "It is recommended that you specify a suffix for consistency. please use only lowercase characters when possible"20}2122variable "unique_seed" {23 description = "Custom value for the random characters to be used"24 type = string25 default = ""26}2728variable "unique_length" {29 description = "Max length of the uniqueness suffix to be added"30 type = number31 default = 432}3334variable "unique_include_numbers" {35 description = "If you want to include numbers in the unique generation"36 type = bool37 default = true38}3940resource "random_string" "main" {41 length = 6042 special = false43 upper = false44 number = var.unique_include_numbers45}4647resource "random_string" "first_letter" {48 length = 149 special = false50 upper = false51 number = false52}5354locals {55 // adding a first letter to guarantee that you always start with a letter56 random_safe_generation = join("", [random_string.first_letter.result, random_string.main.result])57 random = substr(coalesce(var.unique_seed, local.random_safe_generation), 0, var.unique_length)58 prefix = join("-", var.prefix)59 prefix_safe = lower(join("", var.prefix))60 suffix = join("-", var.suffix)61 suffix_unique = join("-", concat(var.suffix, [local.random]))62 suffix_safe = lower(join("", var.suffix))63 suffix_unique_safe = lower(join("", concat(var.suffix, [local.random])))64 // Names based in the recomendations of65 // https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/naming-and-tagging66 az = {67 resource_group = {68 name = substr(join("-", compact([local.prefix, "rg", local.suffix])), 0, 90)69 name_unique = substr(join("-", compact([local.prefix, "rg", local.suffix_unique])), 0, 90)70 dashes = true71 slug = "rg"72 min_length = 173 max_length = 9074 scope = "subscription"75 regex = "^[a-zA-Z0-9-._\\(\\)]+[a-zA-Z0-9-_\\(\\)]$"76 }7778 }79 validation = {8081 resource_group = {82 valid_name = length(regexall(local.az.resource_group.regex, local.az.resource_group.name)) > 0 && length(local.az.resource_group.name) > local.az.resource_group.min_length83 valid_name_unique = length(regexall(local.az.resource_group.regex, local.az.resource_group.name_unique)) > 084 }85 }86}878889output "resource_group" {90 value = local.az.resource_group91}
Ps: this code is a little bit more complex for scalability reasons. At the end of this article you’ll find a reference to the repository where this snippet came from.
Usage
So we’ve created the module, now it’s time to use it in our own TF configuration. We have to create an instance of the naming module. The output of this module will be used as input for other TF resources.
1module "naming" {2 source = "Azure/naming/azurerm"3 suffix = [ "fds", "dev" ]4}5resource "azurerm_resource_group" "example" {6 name = module.naming.resource_group.name ## results in rg-fds-naming-dev7 location = "West Europe"8}
if you want this to be unique for this module and not shared with other instances of this module you can use name_unique
1module "naming" {2 source = "Azure/naming/azurerm"3 suffix = [ "fds", "dev" ]4}5resource "azurerm_resource_group" "example" {6 name = module.naming.resource_group.name_unique ## results in rg-fds-naming-dev7 location = "West Europe"8}
Here you can find the terraform naming module with most of the Azure Resouces already implemented. You can import this module in your own repository and add your own layer of requirements on top of it.