Here We Rule

Creating a Reusable Module


For this exercise, we’ll be creating a small module that creates all of the resources outline here. This post can accompany you while reading the Publishing Modules section of the terraform documentation.

There are other modules just like that already. This one however, comes with this niffty blog post. Also, I want to keep this relatively simple as a learning resources.

After reading this, I hope you’re empowered to create your own.

Let’s get started!

#1 Get your Github repo ready

Terraform documentation states that the “module must be on GitHub and must be a public repo”. This is only a requirement for public registry, and since this module needs to be easy accessible from anywhere. It makes sense.

Keeping in mind, the repository must be named in this format: terraform-<PROVIDER>-<NAME> We’re settling on the

#2 Write the module code

Keeping in mind, terraform modules must adhere to the standard module structure. We begin with creating these main files.

I’ve also added an example file the directory for the registry to pick up and display.

.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf

main.tf

The following code is really just two resources. An S3 bucket and a Dynamodb table. The bucket will be used to store our state and the table for locking.

resource "aws_s3_bucket" "terraform_state" {
  bucket = var.bucket_name

  lifecycle {
    prevent_destroy = true
  }
}

resource "aws_s3_bucket_versioning" "versioning" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "encryption" {
  bucket = aws_s3_bucket.terraform_state.bucket

  rule {
    apply_server_side_encryption_by_default {
       sse_algorithm = "AES256"
    }
  }
}

resource "aws_dynamodb_table" "terraform_locks" {
  name = var.dynamodb_table_name
  billing_mode = "PAY_PER_REQUEST"
  hash_key = "LockID"
  attribute {
    name = "LockID"
    type = "S"
  }
}

variables.tf

We will be giving the user the ability to select what bucket name and table name to use. So we define those varialbes here. It’s important to add proper descriptions to your variables for when users run your code.

variable "bucket_name" {
 type = string
 description = "The name of your s3 bucket used for storing your state"
}

variable "dynamodb_table_name" {
 type = string
 description = "The name of your dynamo db table used for state locking"
}

outputs.tf

output "dynamodb_table" {
  description = "The DynamoDB table to manage lock states."
  value       = aws_dynamodb_table.lock
}

output "state_bucket" {
  description = "The S3 bucket to store the remote state file."
  value       = aws_s3_bucket.state
}

#3 Test our code!

Feeling pretty good about the code that was created, I’ll push it up to GitHub and use the repos as the source like so.

Keep in mind, bucket names are globally unique so make sure you use something specific enough.

module "remote-state" {
  source = "github.com/fiveanddone/terraform-aws-s3-state-backend"

  bucket_name = "my-test-bucket-name"
  dynamodb_table_name = "my-test-dynamo-table"
}

After some quick tests, the code applied correctly soo…

#4 Time to Publish!

With the requirements met, I should be able to publish by going to the terraform registry website and clicking on publish -> module.

It’s at this point a did run into an issue. My repository was placed in an orgainzation and I wasn’t able to see that repository in the list. I had to fork the repository into my personal account and then I was able to see it.

Change the source above to this:

source = "github.com/meekgeek/terraform-aws-s3-state-backend"

You can see the final repository here

What comes next? My readme file needs some love. The current implementation doesn’t really bestow confidence. It’s kinda plain 😣 but, we did do what we setout to do and that should count for something 😄