TrademarkTrademark
Features
Documentation

Terraform Modules - Define, Enforce, Report

Creating and using the module is straightforward, but are you doing so in a scalable way
Ryan FeeApril 10, 2023
Terraform Modules - Define, Enforce, Report

Terraform modules are reusable packages of Terraform code that group a set of resources meant to be deployed together. Writing one and calling it is straightforward. Doing it in a way that holds up across a whole organization is the harder part. There are three phases to module usage:

  • Defining
  • Enforcing
  • Reporting

First, you need to define which modules your organization can use. Defining a module means writing the Terraform configuration files that spell out the resources to create and their parameters. You might copy code straight from the public Terraform registry, pull pieces of the public modules, or start one from scratch. There's no right or wrong way to do that, but you do want to define the modules in a module registry. A module registry will help your end users know which modules have been approved and how to use them, which reduces the amount of snowflake modules you have within your organization. All TACOs support module registries.

Next, you'll want to enforce the modules. Having a registry doesn't mean people will actually use what's in it. You can enforce that through Open Policy Agent and Scalr. The first policy I'd reach for is one that enforces the module source. In the example below, if an AWS DB or S3 resource gets created, it has to come from the module named in the policy:

#This policy will forbid resources from getting created unless done so through a module and a specific source.
package terraform
 
import input.tfplan as tfplan
 
# Map of resource types which must be created only using module with corresponding module source
resource_modules = {
    "aws_db_instance": "terraform-aws-modules/rds/aws",
    "aws_s3_bucket": "scalr-demo.scalr.io/acc-sscctbisjkl35b8/s3-bucket/aws"
}
 
contains(arr, elem) {
  arr[_] = elem
}
 
deny[reason] {
    resource := tfplan.resource_changes[_]
    action := resource.change.actions[count(resource.change.actions) - 1]
    contains(["create", "update"], action)
    module_source = resource_modules[resource.type]
    not resource.module_address
    reason := sprintf(
        "%s cannot be created directly. Module '%s' must be used instead",
        [resource.address, module_source]
    )
}
 
deny[reason] {
    resource := tfplan.resource_changes[_]
    action := resource.change.actions[count(resource.change.actions) - 1]
    contains(["create", "update"], action)
    module_source = resource_modules[resource.type]
    parts = split(resource.module_address, ".")
    module_name := parts[1]
    actual_source := tfplan.configuration.root_module.module_calls[module_name].source
    not actual_source == module_source
    reason := sprintf(
        "%s must be created with '%s' module, but '%s' is used",
        [resource.address, module_source, actual_source]
    )
}

Lastly, you'll want to report on module usage. Reporting will catch anything that the defining and enforcing phases were not able to catch:

  • Understanding which modules are used the most and which ones can be deprecated.
  • Ensure compliance by knowing if users are working around your controls in some way.
  • Knowing if workspaces are on older versions and getting them upgraded.

Scalr module usage report showing version and workspace consumption

Reporting ties the other two phases together and tells you how your module management is actually doing.

Together, these three phases let you scale module usage as your Terraform footprint grows. You probably won't need all of them on day one. Picking a tool that can grow into them pays off later, once the number of workspaces and modules gets past what you can track by hand. Plans and a run-cost calculator are on the pricing page.

Get started using modules to define, enforce and report on Terraform using Scalr today.

Frequently asked questions

What are the phases of managing Terraform modules at scale?

There are three phases: defining, enforcing, and reporting. You define which modules your organization can use and publish them in a module registry, enforce that people actually use them with policy as code, and report on module usage to catch anything the first two phases missed.

Why should Terraform modules go in a module registry?

A module registry tells your end users which modules have been approved and how to use them, which reduces the number of snowflake modules in your organization. It doesn't matter whether the module code came from the public registry or was written from scratch, but approved modules should live in a registry. All TACOs support module registries.

How do you enforce that teams use approved Terraform modules?

Use Open Policy Agent policies evaluated against the Terraform plan. A common first policy enforces module source: for example, requiring that AWS DB or S3 resources can only be created through a specific approved module, and denying the run if the resource is created directly or from the wrong source.

Why report on Terraform module usage?

Reporting catches what defining and enforcing miss. It shows which modules are used most and which can be deprecated, whether users are working around your controls, and which workspaces sit on older module versions that need upgrading. It ties the other two phases together and tells you how your module management is actually doing.
About the author
Ryan Feedirector of platform engineering at Scalr
Ryan Fee is the director of platform engineering at Scalr, with over 15 years of experience improving infrastructure experiences at companies large and small.