How to create a Terraform Module

Terraform is becoming more popular as Infrastructure-as-Code (IaC) is being adopted by almost all companies. Once you start making your hands dirty with Terraform, you will soon realize that you need to divide your code into reusable modules instead of having a giant main.tf file. Having a standard directory structure for your modules is important for your code readability and also if you would like to publish your modules in Terraform Registry. Terraform has good documentation for the standard module directory published on their web page and let's create a simple Terraform module by following their standard documentation.

Let's list the requirements from their page:

  • GitHub. The module must be on GitHub and must be a public repo. This is only a requirement for the public registry. If you're using a private registry, you may ignore this requirement.

  • Named terraform-<PROVIDER>-<NAME>. Module repositories must use this three-part name format, where <NAME> reflects the type of infrastructure the module manages and <PROVIDER> is the main provider where it creates that infrastructure. The <NAME> segment can contain additional hyphens. Examples: terraform-google-vault or terraform-aws-ec2-instance.

  • Repository description. The GitHub repository description is used to populate the short description of the module. This should be a simple one-sentence description of the module.

  • Standard module structure. The module must adhere to the standard module structure. This allows the registry to inspect your module and generate documentation, track resource usage, parse submodules and examples, and more.

  • x.y.z tags for releases. The registry uses tags to identify module versions. Release tag names must be a semantic version, which can optionally be prefixed with a v. For example, v1.0.4 and 0.9.2. To publish a module initially, at least one release tag must be present. Tags that don't look like version numbers are ignored.

Repository Requirements

You can create a repository with the above requirements or you can call a wrapper script I created to prepare the directory structure for you.

git clone https://github.com/yasarlaro/Terraform-AzureRM-Module-Skeleton.git
cd Terraform-AzureRM-Module-Skeleton
chmod +x generate-module-skeleton.sh
./generate-module-skeleton.sh

Creating Sample Module

Terraform module codes can be found here.

Code will mean more than diagrams and words, so let's make our hands dirty and create a simple module to create a resource group in azure together.

Let's run the generator script and create our basic module structure as below:

$ ./generate-module-skeleton.sh
[INFO]: Enter module directory: /home/onur/gitrepos/terraform-modules
[INFO]: Enter module name: terraform-azurerm-resource-group
[INFO]: Module will be created under /home/onur/gitrepos/terraform-modules/terraform-azurerm-resource-group
[INFO]: Do you agree? (Y|N)y
[INFO]: Creating module directory
[INFO]: Copying module files
[INFO]: Module directory created as below:
/home/onur/gitrepos/terraform-modules/terraform-azurerm-resource-group
├── examples
│   └── basic-example
│       ├── main.tf
│       ├── outputs.tf
├── LICENSE
├── main.tf
├── outputs.tf
├── providers.tf
├── README.md
├── variables.tf
└── versions.tf

Now we have the basic structure to start with. Let's open the code in your favorite code editor and start writing our module. We will refer to the official documentation for the resource group.

Let's create the providers.tf file as:

providers.tf
provider "azurerm" {
  features {}

set upThen let's create version.tf file to setup the minimum version for the module:

version.tf
terraform {
  required_version = ">= 1.2"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 3.27"
    }
  }
}t

Now it is time to create our main.tf file:

main.tf
resource "azurerm_resource_group" "rg" {
  name     = var.name
  location = var.location
  tags     = var.tags
}

Once the main.tf file is created, we need to define the variables.tf file

variables.tf
variable "name" {
  type        = string
  default     = "onur-test-rg-001"
  description = "(Required) The Name which should be used for this Resource Group. Changing this forces a new Resource Group to be created"
}

variable "location" {
  type        = string
  description = "(Required) The Azure Region where the Resource Group should exist. Changing this forces a new Resource Group to be created."
  default     = "North Europe"
}

variable "tags" {
  type        = map(string)
  description = "(Optional) A mapping of tags which should be assigned to the Resource Group."
  default = {}
}

We can also provide output for our module in outputs.tf file as below:

output.tf
output "name" {
  value       = azurerm_resource_group.rg.name
  description = "Name of the resource"
}

output "location" {
  value       = azurerm_resource_group.rg.location
  description = "Location of the resource"
}

output "tags" {
  value       = azurerm_resource_group.rg.tags[*]
  description = "Tag(s) of the resource"
}

Then we need to provide some examples on how to use the module. Since it is a sample module on a basic resource, let's have only a single simple example under ./examples/basic-example directory:

./examples/basic-example/main.tf
provider "azurerm" {
  features {}
}

module "resource-group-test" {
  # To use locally
  source = "../../"

  # To use from registry
  #source  = "yasarlaro/azurerm/resource-group"
  #version = "x.x.x"

  name     = "tf-module-test-rg-001"
  location = "northeurope"
  tags = {
    environment = "terraform-test"
    source      = "terraform"
  }
}

Let's define the outputs for the test:

./examples/basic-example/outputs.tf
output "rg_name" {
  value = module.resource-group-test.name
  description = "Returns the name of the resource group"
}

output "rg_location" {
  value = module.resource-group-test.location
  description = "Returns the location of the resource group"
}

Then we can create README file for the module and commit the changes to a remote repository. To comply with the requirements, we need to follow semantic versioning and provide a tag and create a release. Let's create a tag with "v1.0.0" version and release it in GitHub.

With the requirements met, you can publish a public module by going to the Terraform Registry and clicking the "Upload" link in the top navigation.

If you're not signed in, this will ask you to connect with GitHub. We only ask for access to public repositories, since the public registry may only publish public modules. We require access to hooks so we can register a webhook with your repository. We require access to your email address so that we can email you alerts about your module. We will not spam you.

The upload page will list your available repositories, filtered to those that match the naming convention described above. This is shown in the screenshot below. Select the repository of the module you want to add and click "Publish Module."

Last updated