terraform module - ghdrako/doc_snipets GitHub Wiki

A Terraform module is a set of Terraform configuration files in a single directory. Even a simple configuration consisting of a single directory with one or more .tf files is a module. When you run Terraform commands directly from such a directory, it is considered the root module.

A module that is called by another configuration is sometimes referred to as a "child module" of that configuration.

Terraform Registry - official and community modules

While the root module is the only required element, recommend the structure below as the minimum

$ tree minimal-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── LICENCE

Be aware of these files and ensure that you don't distribute them as part of your module:

  • terraform.tfstate and terraform.tfstate.backup files contain your Terraform state and are how Terraform keeps track of the relationship between your configuration and the infrastructure provisioned by it.
  • The .terraform directory contains the modules and plugins used to provision your infrastructure. These files are specific to an individual instance of Terraform when provisioning infrastructure, not the configuration of the infrastructure defined in .tf files.
  • .tfvarsfiles don't need to be distributed with your module unless you are also using it as a standalone Terraform configuration because module input variables are set via arguments to the module block in your configuration.

If you are tracking changes to your module in a version control system such as Git, you will want to configure your version control system to ignore these files. For an example, see this .gitignore file from GitHub.

$ tree complete-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── ...
├── modules/
│   ├── nestedA/
│   │   ├── README.md
│   │   ├── variables.tf
│   │   ├── main.tf
│   │   ├── outputs.tf
│   ├── nestedB/
│   ├── .../
├── examples/
│   ├── exampleA/
│   │   ├── main.tf
│   ├── exampleB/
│   ├── .../

There is a main root module that has the main configuration file you would be working with, which can consume multiple other modules. We can define the hierarchical structure of modules like this: the root module can ingest a child module, and that child module can invoke multiple other child modules. We can reference or read submodules in this way:

module.<rootmodulename>.module.<childmodulename>

However, it is recommended to keep a module tree flat and have only one level of child module.

# referenced one module in another module
module "aws_network" {
  source     = "./modules/network"
  cidr_block = "10.0.0.0/8"
}

module "aws_instance" {
  source     = "./modules/instance"
  vpc_id     = module.aws_network.vpc_id
  subnet_ids = module.aws_network.subnet_ids
}

We can define a module as a container with multiple resources that can be consumed together.

module "name" {}

Terraform modules support some key arguments such as source, version, and input variable, and loops such as for_each and count.

module "terraform-module" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "2.55.0"
}

Using modules

When using a new module for the first time, you must run either terraform init or terraform get to install the module in the .terraform/modules directory within your configuration's working directory. For local modules, Terraform will create a symlink to the module's directory.


Output values

Modules also have output values, which are defined within the module with the output keyword. You can access them by referring to module.

<MODULE NAME>.<OUTPUT NAME>

Module outputs are usually either passed to other parts of your configuration, or defined as outputs in your root module. output.tf:

# Output variable definitions
output "vpc_public_subnets" {  
  description = "IDs of the VPC's public subnets"  
  value       = module.vpc.public_subnets
}

Providers

A module intended to be called by one or more other modules must not contain any provider blocks. A module containing its own provider configurations is not compatible with the for_each, count, and depends_on arguments that were introduced in Terraform v0.13.

Although provider configurations are shared between modules, each module must declare its own provider requirements, so that Terraform can ensure that there is a single version of the provider that is compatible with all modules in the configuration.

If you are writing a shared Terraform module, constrain only the minimum required provider version using a >= constraint.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 2.7.0"
    }
  }
}

source

mandatory, referring to either the local path where the module configuration file is located or the remote reference Uniform Resource Locator (URL) from where it should be downloaded. The same source path can be defined in multiple modules or in the same file and can be called multiple times, making modules more efficient for reuse.

When using a new module for the first time, you must run either terraform init or terraform get to install the module. When either of these commands is run, Terraform will install any new modules in the .terraform/modules directory within your configuration's working directory. For local modules, Terraform will create a symlink to the module's directory. Because of this, any changes to local modules will be effective immediately, without your having to re-run terraform get.

local referencing

We have two options for referencing a local path: ./ and ../. We have used ../, which takes the terraform-module directory (that is, the parent directory).

# For local referencing, you can define a module in the following way:
module "terraform-module" {
    source = "../terraform-module"
}

remote ways of referencing

https://www.terraform.io/docs/language/modules/sources.html few ways in which you can reference remotely:

  • Terraform Cloud
  • Terraform Enterprise private module registries
  • Terraform Registry
  • GitHub
  • Bitbucket
  • Generic Git; Mercurial repositories
  • HyperText Transfer Protocol (HTTP) URLs
  • Simple Storage Service (S3) buckets
  • Google Cloud Storage (GCS) buckets

Terraform Registry

It can be referenced using the specified syntax //

to use modules that are hosted in a private registry similar to Terraform Cloud, then you can reference them in code by providing a source path with the syntax ///.

GitHub

module "terraform-module" {
  source = "github.com/hashicorp/terraform-module"
}

To clone over Secure Shell (SSH), you can use the following code:

module "terraform-module" {
 source = "[email protected]:hashicorp/terraform-module.git"

}

GitHub supports a ref argument that helps select a specific version of a module. You would be required to pass credentials to get authenticated to a private repository.

Generic Git repository

You can define any valid Git repository by prefixing the address with git::. Both SSH and HTTPS can be defined in the following ways:

module "terraform-module" {
  source = "git::https://example.com/terraform-module.git"
}

module "terraform-module" {
  source = "git::ssh://[email protected]/terraform-module.git"
}

Terraform downloads modules from the Git repository by executing git clone.

Git lab

Where gitlab.com can be replaced with the hostname of your self-managed GitLab instance.

You can then reference your Terraform Module from a downstream Terraform project:

module "<module>" {
  source = "gitlab.com/<namespace>/<module_name>/<module_system>"
}

Using public avaliable module

data "aws_availability_zones" "available" {
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.7.0"
name = var.project_name
cidr = var.cidr
azs =
slice(data.aws_availability_zones.available.names, 0, 3)
private_subnets = local.private_subnets
public_subnets = local.public_subnets
enable_nat_gateway = true
enable_dns_hostnames = true
tags = {
Operator = "Terraform"
}
}
locals {
private_subnets = [
cidrsubnet(var.cidr, 8, 1),
cidrsubnet(var.cidr, 8, 2),
cidrsubnet(var.cidr, 8, 3)
]
public_subnets = [
cidrsubnet(var.cidr, 8, 4),
cidrsubnet(var.cidr, 8, 5),
cidrsubnet(var.cidr, 8, 6)
]
}
$ terraform init
Initializing modules...
Downloading terraform-aws-modules/vpc/aws 3.7.0 for vpc...
- vpc in .terraform/modules/vpc
...

Module best practices

Just like almost any non-trivial computer program, real-world Terraform configurations should almost always use modules.

  • Start writing your configuration with a plan for modules. Even for slightly complex Terraform configurations managed by a single person, the benefits of using modules outweigh the time it takes to use them properly.

  • Use local modules to organize and encapsulate your code. Even if you aren't using or publishing remote modules, organizing your configuration in terms of modules from the beginning will significantly reduce the burden of maintaining and updating your configuration as your infrastructure grows in complexity.

  • Use the public Terraform Registry to find useful modules. This way you can quickly and confidently implement your configuration by relying on the work of others.

  • Publish and share modules with your team. Most infrastructure is managed by a team of people, and modules are an important tool that teams can use to create and maintain infrastructure. As mentioned earlier, you can publish modules either publicly or privately. You will see how to do this in a later lab in this series.

⚠️ **GitHub.com Fallback** ⚠️