This document provides guidelines and recommendations to consider when using reusable Terraform modules.
This guide is not an introduction to Terraform. For an introduction to using Terraform with Google Cloud, see Get started with Terraform.
Activate required APIs in modules
Terraform modules can activate any required services by using the
google_project_service
resource or the
project_services
module.
Including API activation makes demonstrations easier.
- If API activation is included in a module, then the API activation must
be disableable by exposing an
enable_apis
variable that defaults totrue
. If API activation is included in a module, then the API activation must set
disable_services_on_destroy
tofalse
, because this attribute can cause issues when working with multiple instances of the module.For example:
module "project-services" { source = "terraform-google-modules/project-factory/google//modules/project_services" version = "~> 12.0" project_id = var.project_id enable_apis = var.enable_apis activate_apis = [ "compute.googleapis.com", "pubsub.googleapis.com", ] disable_services_on_destroy = false }
Include an owners file
For all shared modules, include an
OWNERS
file (or
CODEOWNERS
on GitHub), documenting who is responsible for the module. Before any pull
request is merged, an owner should approve it.
Release tagged versions
Sometimes modules require breaking changes and you need to communicate the effects to users so that they can pin their configurations to a specific version.
Make sure that shared modules follow SemVer v2.0.0 when new versions are tagged or released.
When referencing a module, use a version constraint to pin to the major version. For example:
module "gke" {
source = "terraform-google-modules/kubernetes-engine/google"
version = "~> 20.0"
}
Don't configure providers or backends
Shared modules must not configure providers or backends. Instead, configure providers and backends in root modules.
For shared modules, define the minimum required provider versions in a
required_providers
block, as follows:
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = ">= 4.0.0"
}
}
}
Unless proven otherwise, assume that new provider versions will work.
Expose labels as a variable
Allow flexibility in the labeling of resources through the module's interface.
Consider providing a labels
variable with a default value of an empty map, as
follows:
variable "labels" {
description = "A map of labels to apply to contained resources."
default = {}
type = "map"
}
Expose outputs for all resources
Variables and outputs let you infer dependencies between modules and resources. Without any outputs, users cannot properly order your module in relation to their Terraform configurations.
For every resource defined in a shared module, include at least one output that references the resource.
Use inline submodules for complex logic
- Inline modules let you organize complex Terraform modules into smaller units and de-duplicate common resources.
- Place inline modules in
modules/$modulename
. - Treat inline modules as private, not to be used by outside modules, unless the shared module's documentation specifically states otherwise.
- Terraform doesn't track refactored resources. If you start with several
resources in the top-level module and then push them into submodules,
Terraform tries to recreate all refactored resources. To mitigate this
behavior, use
moved
blocks when refactoring. - Outputs defined by internal modules aren't automatically exposed. To share outputs from internal modules, re-export them.
What's next
- Learn about general style and structure best practices for Terraform on Google Cloud.
- Learn about best practices when using Terraform root modules.