Among all the concepts that you will need to know about Terraform (providers, states, workspaces, etc.), there are also modules.
Terraform modules are self-contained and reusable collections of infrastructure code, allowing users to encapsulate and share configurations for specific components, promoting ease of maintenance in infrastructure-as-code projects.
They can be used within your company, or shared publicly with the community.
In this article, we will create a module in the simplest way possible by describing the structure and implementing some writing best practices and code validation mechanisms.
Simple example
To illustrate the creation of a module, we will take the common case of S3 buckets to be created on AWS.
The division of the module will depend on your needs. Hashicorp does not recommend writing modules for public resource wrappers, because all these modules are already community contributed.
Here we take a simple case for the demonstration, also if you want to provide a private module according to your company's standards, it may still make sense to make a wrapper.
We will implement the structure explained by Hashicorp, creating only the basic elements that interest us. We also make sure to follow the recommended naming conventions.
Here is the structure that we are going to put in place, I will describe all these elements.
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── examples
│ └── s3_bucket_simple.tf
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf
Main resources
main.tf defines the main resource we create, in our case, the s3 bucket :
resource "aws_s3_bucket" "this" {
bucket = var.name
tags = var.tags
}
resource "aws_s3_bucket_ownership_controls" "this" {
bucket = aws_s3_bucket.this.id
rule {
object_ownership = "BucketOwnerPreferred"
}
}
resource "aws_s3_bucket_acl" "this" {
depends_on = [aws_s3_bucket_ownership_controls.this]
bucket = aws_s3_bucket.this.id
acl = "private"
}
variables.tf defines the variables you can pass to the module :
variable "name" {
type = string
default = null
description = "Name for the bucket. If omitted, Terraform will assign a random, unique name."
}
variable "tags" {
type = map(any)
default = null
description = "Details the tags to apply to the bucket"
}
outputs.tf defines the attributes you can export from the module :
output "bucket_id" {
value = aws_s3_bucket.this.id
description = "Bucket Name (aka ID)"
}
output "bucket_arn" {
value = aws_s3_bucket.this.arn
description = "Bucket ARN"
}
output "bucket_name" {
value = aws_s3_bucket.this.bucket
description = "Bucket Name"
}
and versions.tf defines the authorized versions of Terraform and providers to use the module :
terraform {
required_version = ">=1.2"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.0, < 5.16.0"
}
}
}
Usage example
An example is described in the examples/s3_bucket_simple.tf file, you can use it to inspire yourself in using the module :
module "test_s3" {
source = "git::https://github.com/jdxlabs/terraform-s3-module.git?ref=0.0.4"
name = "${var.workspace}-${var.env}-test-bucket"
tags = {
Name = "${var.workspace}-${var.env}-test-bucket"
Env = var.env
}
}
License
For public repositories, I recommend the GPL (General Public License) v3, it provides strong protections for open-source software, ensuring that any modifications or distributions of the code must also be open source and freely accessible to the community.
If you aren't sure of the license you want to attribute to the project, you can go to choosealicense.com.
Documentation
There are two levels of documentation :
- README.md gives instructions for using the module
- CONTRIBUTING.md provides instructions for improving the module itself
Quality validation for contributions
When people contribute to the project, we use a tool called pre-commit, which allows us to improve the code before the changes are shared to the repository.
We use validation scripts, called hooks, notably those from pre-commit-terraform.
.pre-commit-config.yaml defines the hooks we want to execute with pre-commit
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.85.0
hooks:
- id: terraform_fmt
- id: terraform_tflint
- id: terraform_checkov
- id: terraform_docs
Terraform-fmt is a command built into Terraform to ensure that code is formatted correctly.
tflint ensures that Terraform code has no typing or forgetting issues.
checkov avoids committing AWS resources with security vulnerabilities, it analyzes the deployed resources and indicates what does not comply with the rules of security best practices.
terraform-docs generates the well-formatted documentation in the README.md file.
Module exposition
The module can come from a registry or through a local / remote reference.
For local reference, you can call it like this :
module "s3" {
source = "./path/to/your/module"
…
For remote reference, you have the choice between https and ssh :
# https
module "s3" {
source = "git::https://github.com/jdxlabs/terraform-s3-module.git?ref=0.0.4"
…
# ssh
module "s3" {
source = "git@github.com:jdxlabs/terraform-s3-module.git?ref=0.0.4"
…
You can put the module on the Terraform Registry, but you can very well use it directly without referencing it on this platform.
If you want to reference it to share with the public community, it's quite simple to publish your git repository there. Simply sign up with your Github account, select your repository and click “publish”.
A good practice is to create git tagged releases, in order to point to them during module calls (you can also enter the changelog to follow developments).
Conclusion
The complete code of the demonstration is available on github and on the registry.
You now have all the keys to create and publish a complete and well-configured module, to share within your company or publicly with the community.
Top comments (0)