AWS Terraform Provider Resources vs Registry Modules to Provision Infra to AWS
There are two main ways for provisioning Infrastructure Resources to AWS, so… Which one should I use?
When I have to provision Infrastructure resources to AWS, the first thing I do is to look for the official resource documentation page. But if we do that, For most of the AWS Resources we will get 2 Results.
Provider Resources and Registry Modules
Terraform Provider Resource
A Resource in Terraform represents an individual component within a provider. It could be a compute instance, a database, a networking rule, etc. Resources are the basic building blocks in your Terraform configuration files. You define what you want the end state of your infrastructure to look like using these resources. A single provider typically has multiple resources.
Documentation AWS Provider: https://registry.terraform.io/providers/hashicorp/aws/latest/docs
Terraform Registry Module
A Module is a reusable, standardized package that encapsulates one or many resources. Modules can be shared and reused across different projects and teams. While resources are low-level definitions, modules can represent higher-level abstractions that include multiple resources and even other modules.
Modules can be sourced from various locations, including the Terraform Registry. They are used for organizing your code and making it reusable. A Terraform Registry Module usually comes with versioning and documentation, helping you understand its functionality and how to use it.
So using modules to group Resources is a well known best-practice of Terraform, but now we don’t have to re-invent the wheel when a simplee Resource is required, you just instantiate the Terraform Registry Official Modules. So you final Module can be composed by N Resources all also created by Modules.
Here is a list of Modules that we can implement:
gh repo list terraform-aws-modules
+-------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| Module | Description |
+-------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
| terraform-aws-eks | Terraform module to create an Elastic Kubernetes (EKS) cluster and associated resources |
| terraform-aws-iam | Terraform module which creates IAM resources on AWS |
| terraform-aws-acm | Terraform module which creates and validates ACM certificat |
| terraform-aws-eventbridge | Terraform module which creates EventBridge resources on AWS |
| terraform-aws-step-functions | Terraform module which creates Step Functions on AW |
| terraform-aws-cloudfront | Terraform module which creates CloudFront resources on AWS |
| terraform-aws-vpc | Terraform module which creates VPC resources on AWS |
| terraform-aws-rds-aurora | Terraform module which creates RDS Aurora resources on AWS |
| terraform-aws-ecs | Terraform module which creates AWS ECS resources |
| terraform-aws-dynamodb-table | Terraform module which creates DynamoDB table on AWS |
| terraform-aws-s3-bucket | Terraform module which creates S3 bucket resources on AWS |
| terraform-aws-lambda | Terraform module, which takes care of a lot of AWS Lambda/serverless tasks (build dependencies, packages, updates, deployments) in countless combinations |
| terraform-aws-kms | Terraform module which creates AWS KMS resources |
| terraform-aws-ec2-instance | Terraform module which creates EC2 instance(s) on AWS |
| terraform-aws-rds | Terraform module which creates RDS resources on AWS |
| terraform-aws-alb | Terraform module to create an AWS Application/Network Load Balancer (ALB/NLB) and associated resources |
| terraform-aws-efs | Terraform module which creates AWS EFS resources |
| terraform-aws-cloudwatch | Terraform module which creates Cloudwatch resources on AWS |
| terraform-aws-autoscaling | Terraform module which creates Auto Scaling resources on AWS |
| terraform-aws-security-group | Terraform module which creates EC2-VPC security groups on AWS |
| terraform-aws-sns | Terraform module which creates SNS resources on AWS |
| terraform-aws-transit-gateway | Terraform module which creates Transit Gateway resources on AWS |
| terraform-aws-apigateway-v2 | Terraform module to create an AWS API Gateway v2 (HTTP/WebSocket) |
+-------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+
Full list of AWS Modules https://github.com/terraform-aws-modules
These modules can help you implement common infrastructure patterns without having to write code from scratch.
The registry includes modules for various cloud providers like AWS, Azure, Google Cloud, and many more.
Example #1: DynamoDB Table
Terraform Provider Resource
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/dynamodb_table
# Terraform Provider Resource
resource "aws_dynamodb_table" "example_table" {
name = "${random_pet.prefix.id}-example-table"
hash_key = "id"
billing_mode = "PROVISIONED"
read_capacity = 20
write_capacity = 20
attribute {
name = "id"
type = "N"
}
tags = {
Terraform = "true"
Environment = "dev"
}
}
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_dynamodb_table.example_table will be created
+ resource "aws_dynamodb_table" "example_table" {
+ arn = (known after apply)
+ billing_mode = "PROVISIONED"
+ hash_key = "id"
+ id = (known after apply)
+ name = (known after apply)
+ read_capacity = 20
+ stream_arn = (known after apply)
+ stream_label = (known after apply)
+ stream_view_type = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Terraform" = "true"
}
+ write_capacity = 20
+ attribute {
+ name = "id"
+ type = "N"
}
}
# random_pet.prefix will be created
+ resource "random_pet" "prefix" {
+ id = (known after apply)
+ length = 2
+ separator = "-"
}
Plan: 2 to add, 0 to change, 0 to destroy.
Terraform Registry Module
https://registry.terraform.io/modules/terraform-aws-modules/dynamodb-table/aws/latest
# Terraform Registry Module
module "example_table" {
source = "git::https://github.com/terraform-aws-modules/terraform-aws-dynamodb-table.git//"
name = "${random_pet.prefix.id}-example-table"
hash_key = "id"
billing_mode = "PROVISIONED"
read_capacity = 20
write_capacity = 20
attributes = [
{
name = "id"
type = "N"
}
]
tags = {
Terraform = "true"
Environment = "dev"
}
}
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# random_pet.prefix will be created
+ resource "random_pet" "prefix" {
+ id = (known after apply)
+ length = 2
+ separator = "-"
}
# module.example_table.aws_dynamodb_table.this[0] will be created
+ resource "aws_dynamodb_table" "this" {
+ arn = (known after apply)
+ billing_mode = "PROVISIONED"
+ deletion_protection_enabled = false
+ hash_key = "id"
+ id = (known after apply)
+ name = (known after apply)
+ read_capacity = 20
+ stream_arn = (known after apply)
+ stream_enabled = false
+ stream_label = (known after apply)
+ stream_view_type = (known after apply)
+ tags = (known after apply)
+ tags_all = (known after apply)
+ write_capacity = 20
+ attribute {
+ name = "id"
+ type = "N"
}
+ point_in_time_recovery {
+ enabled = false
}
+ server_side_encryption {
+ enabled = true
+ kms_key_arn = (known after apply)
}
+ timeouts {
+ create = "10m"
+ delete = "10m"
+ update = "60m"
}
+ ttl {
+ enabled = false
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
So what is going on here?… On the Terraform Registry Module Plan it adds a couple of extra parameters that are NOT included on the Terraform Provider Resource for point_in_time_recovery, server_side_encryption, timeouts and ttl.
So looks like out of the Box we are applying some best practices to our infrastructure without even noticing it.
Example #2: S3 Bucket
Terraform Provider Resource
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket
# Terraform Provider Resource
resource "aws_s3_bucket" "example_bucket" {
bucket = "${random_pet.prefix1.id}-example-bucket"
acl = "private"
}
Terraform will perform the following actions:
# aws_s3_bucket.example_bucket will be created
+ resource "aws_s3_bucket" "example_bucket" {
+ acceleration_status = (known after apply)
+ acl = "private"
+ arn = (known after apply)
+ bucket = (known after apply)
+ bucket_domain_name = (known after apply)
+ bucket_prefix = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = (known after apply)
+ policy = (known after apply)
+ region = (known after apply)
+ request_payer = (known after apply)
+ tags_all = (known after apply)
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
}
# random_pet.prefix1 will be created
+ resource "random_pet" "prefix1" {
+ id = (known after apply)
+ length = 5
+ separator = "-"
}
Plan: 2 to add, 0 to change, 0 to destroy.
Terraform Registry Module
https://registry.terraform.io/modules/terraform-aws-modules/s3-bucket/aws/latest
# Terraform Registry Module
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "1.9.0"
bucket = "${random_pet.prefix2.id}-example-bucket"
acl = "private"
}
Terraform will perform the following actions:
# random_pet.prefix1 will be created
+ resource "random_pet" "prefix1" {
+ id = (known after apply)
+ length = 5
+ separator = "-"
}
# module.s3_bucket.aws_s3_bucket.this[0] will be created
+ resource "aws_s3_bucket" "this" {
+ acceleration_status = (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = (known after apply)
+ bucket_domain_name = (known after apply)
+ bucket_prefix = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = false
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = false
+ policy = (known after apply)
+ region = (known after apply)
+ request_payer = (known after apply)
+ tags_all = (known after apply)
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
}
# module.s3_bucket.aws_s3_bucket_acl.this[0] will be created
+ resource "aws_s3_bucket_acl" "this" {
+ acl = "private"
+ bucket = (known after apply)
+ id = (known after apply)
}
# module.s3_bucket.aws_s3_bucket_public_access_block.this[0] will be created
+ resource "aws_s3_bucket_public_access_block" "this" {
+ block_public_acls = true
+ block_public_policy = true
+ bucket = (known after apply)
+ id = (known after apply)
+ ignore_public_acls = true
+ restrict_public_buckets = true
}
Plan: 4 to add, 0 to change, 0 to destroy.
So what is going on here?… On the Terraform Registry Module Plan it adds a couple of extra resources for aws_s3_bucket_acl, aws_s3_bucket_public_access_block that are NOT included on the Terraform Provider Resource.
So which method should I use?
Using the Terraform Registry Module terraform-aws-modules module from the Terraform Registry as opposed to directly using the provider resource for DynamoDB has several benefits:
Simplification: The module encapsulates multiple resources and best practices into a single, high-level abstraction. Instead of defining each individual component yourself, the module does it for you, saving time and reducing complexity.
Standardization: Modules are reusable across different projects and teams. Using a well-designed module can help ensure that resources are being created in a consistent, standardized manner, adhering to best practices.
Versioning: Modules in the Terraform Registry are versioned. This allows you to easily roll back to previous configurations or adopt new module features incrementally.
Community-Tested: Modules from the Terraform Registry are often well-tested and vetted by the community. This can increase the reliability and stability of your infrastructure components.
Easier to Maintain and Update: Since the module abstracts away low-level details, it can be easier to maintain and update. When a change needs to be made, you may only need to update the module version or modify a high-level variable, rather than making several changes to low-level resources.
Documentation and Examples: Modules in the Terraform Registry often come with comprehensive documentation and examples, making it easier to understand how to implement and customize the module to fit your needs.
The choice between using a module or directly using the provider’s resources often depends on the level of customization you need and your preference for managing the infrastructure’s complexity.
Examples of Composite Module
When to Use Provider Resources Directly?
There are instances when using the raw provider resources offers advantages:
- High Customisation: You have complete control over every parameter.
- Specific Requirements: Not every use-case might be covered by available modules.
Examples of Custom Implementations
- Custom CloudFront Configuration: My CloudFront Resource for Web UI Module
- Custom Transit Gateway Setup: My Transit Gateway Module