diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml index c97ae62..e66984e 100644 --- a/.trunk/configs/.markdownlint.yaml +++ b/.trunk/configs/.markdownlint.yaml @@ -12,3 +12,7 @@ whitespace: false # Ignore MD041/first-line-heading/first-line-h1 # Error: First line in a file should be a top-level heading MD041: false + +# Ignore markdownlint/MD029 +# Error: Ordered list item prefix +MD029: false diff --git a/README.md b/README.md index d39a69f..97fd9f3 100644 --- a/README.md +++ b/README.md @@ -18,21 +18,163 @@ We recommend to include: - Prerequisites and Dependencies: Mention any dependencies, required providers, or external resources. - Example Configurations: If applicable, include or link to example code snippets or a separate examples/ directory. -## Module Repository/Directory Structure +## Structure -Below is a recommended structure for both TF child modules and root modules. Inside each file, you’ll find guidance and best practices that help maintain clarity and consistency across your infrastructure code. +This template includes a recommended layout for both root modules and child TF modules. Each file has a specific purpose and set of best practices. While many principles apply to both root and child modules, any differences are noted below. + +For root modules: + +```sh +. +├── README.md +├── main.tf +├── data.tf (optional) +├── outputs.tf (optional) +├── providers.tf +├── variables.tf +└── versions.tf +``` + +For child modules: ```sh . ├── README.md ├── main.tf ├── data.tf (optional) -├── outputs.tf -├── providers.tf (root module only) +├── outputs.tf (optional) ├── variables.tf └── versions.tf ``` +### File-by-File Guidance + +The principles below apply to both root and child modules, unless otherwise specified. + +1. `main.tf` + +- Purpose: Defines core resources and the module’s primary logic. In root modules, this may also include calls to child modules. +- Best Practices: + - Resource definitions: Declare here all the primary resources that this module is responsible for managing. + - Locals and expressions: Use locals blocks to simplify expressions and keep the code DRY (Don’t Repeat Yourself). + - Comments and structure: Organize resources logically and use comments to explain complex or non-obvious configurations. + - Minimal hard-coding: Use variables extensively to avoid embedding environment-specific values directly in the code. + - Child module calls: + - Use Terraform Registry Modules with Version Pinning: + ```hcl + module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "1.0.0" + } + ``` + - Use Git Sources with a Specific Tag or Commit + ```hcl + module "vpc" { + source = "git::https://github.com/org/terraform-aws-vpc.git?ref=v1.0.0" + } + ``` + +2. `data.tf` (Optional) + +- Purpose: Contains data sources that retrieve external information. +- Best Practices: + - Data source declarations: Place all data blocks here, for example, `data "aws_ami" "linux" { ... }`. + - Clear naming and purpose: Use descriptive names for data sources to indicate their role (e.g., `data "aws_ami" "ubuntu_latest"`). + - Commenting and filtering: Document why each data source is used and ensure filters or queries are well explained. + - Minimize external dependencies: Only query the minimum necessary information. Overly complicated data sources can slow down Terraform runs and confuse future maintainers. + +3. `outputs.tf` (Optional) + +- Purpose: Defines values exported from the module for use by its caller. +- Best Practices: + - Descriptive output names: Use meaningful names (e.g., `instance_id`, `db_connection_string`). + - Descriptions: Include description attributes to clarify the purpose of each output. + - Minimal outputs: Only output what consumers need. For sensitive outputs, mark as `sensitive = true`. + +4. `providers.tf` (Root Module Only) + +- Purpose: Configures providers for the root module, such as authentication or default regions. +- Best Practices: + - Provider configuration: Define providers (e.g., `aws {}`, `google {}`) and set their region, credentials, or other parameters. + - Multiple provider configurations: If you need multiple configurations for the same provider (e.g., two AWS regions), define them here with explicit aliases. + - Avoid hard-coded and static credentials: Instead of embedding static credentials directly in your code, consider: + - AWS Assume Role: For the AWS provider, configure an assume role to obtain temporary credentials dynamically. + - Encrypted Configuration Files: For providers requiring API tokens, use a tool like [SOPS](https://getsops.io/) to encrypt sensitive variables. + +5. `variables.tf` + +- Purpose: Defines input variables controlling the module’s configuration. +- Best Practices: + - Descriptive variables: Use meaningful names and description attributes. + - Default values: Provide reasonable defaults when possible. For mandatory inputs, omit defaults to enforce explicit user input. + - Type vonstraints and validation: Use type constraints and validation blocks to catch incorrect inputs early. + - Group related variables: Organize variables logically, adding comments to separate sections if many variables exist. + +6. `versions.tf` + +- Purpose: Sets Terraform and provider version requirements for consistency and compatibility. +- Best Practices: + - See the detailed version constraints explanation in [Versioning TF and Providers].(#versioning-tf-and-providers) + - Regular Review: Update constraints as Terraform and providers evolve. + +## Versioning TF and Providers + +We’re particular about how we version providers and Terraform/OpenTofu in child and root modules. We recommend the following: + +### Child Modules + +Since child-modules are intended to be used many times throughout your code, it’s important to make it so that they create as little restrictions on the consuming consuming root module as possible. + +This means you should: + +- Identify the earliest Terraform/OpenTofu and provider versions your child module supports. +- Use the `>=` operator to ensure that consumers run at least these versions. + +By setting a lower bound (e.g., `>= 1.3`) rather than pinning exact versions, you allow root modules to choose their own Terraform and provider versions. This means a root module can upgrade Terraform or providers without requiring updates to all child modules. + +Example: + +```hcl +terraform { + required_version = ">= 1.3" + + required_providers { + random = { + source = "hashicorp/random" + version = ">= 3.0" + } + } +} +``` + +In this example, the child module only demands a minimum version (Terraform 1.3, Random provider 3.0), letting the root module run newer versions as they become available. + +### Root Modules + +Root modules are intended to be planned and applied and therefore they should be more prescriptive so that they’re called consistently in each case that you instantiate a new root module instance (i.e. a state file). + +To accomplish that, you should do the following: + +- Explicitly pin the latest version of Terraform/OpenTofu that your root module supports. You’ll need to upgrade this version each time you want to use a new TF version across your code base. +- Identify the highest stable provider versions your root module supports, then use the [pessimistic operator](https://developer.hashicorp.com/terraform/language/expressions/version-constraints#operators) `~>` to allow only patch-level updates. This gives you automatic bug fixes and minor improvements without risking major breaking changes. + +Example: + +```hcl +terraform { + required_version = "1.3.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.81.0" + } + } +} +``` + +In this example Terraform is pinned exactly at 1.3.7, the AWS provider is pinned with `~> 5.81.0`, which means it can update to 5.81.1, 5.81.2, etc., but not jump to 5.82.0. + ## Additional Tips - Testing and Examples: Consider adding an examples/ directory with sample configurations and a test/ directory (if using tools like terratest or native Terraform testing) to ensure the module works as intended diff --git a/root-module/README.md b/root-module/README.md index 392e8f7..ae4c381 100644 --- a/root-module/README.md +++ b/root-module/README.md @@ -2,15 +2,15 @@ This is a template root module. + + ## Documentation Recommendations (DO NOT INCLUDE THIS INTO THE REAL README) - Module description: Briefly explain what the root module sets up (e.g., infrastructure for RDS Postgres instances). - Use [terraform-docs](https://github.com/terraform-docs/terraform-docs) to ensure that variables, outputs, child module, and resource documentation is included. - Maintain current information: Keep the README updated as the infrastructure evolves. -## Structure - -Explore the contents of each file to understand their purpose and discover recommended best practices. + diff --git a/root-module/data.tf b/root-module/data.tf deleted file mode 100644 index 6e0e36e..0000000 --- a/root-module/data.tf +++ /dev/null @@ -1,10 +0,0 @@ -# Purpose: -# The data.tf file contains Terraform data sources—these do not create resources but query existing infrastructure or configuration for reference. - -# Best Practices: -# - Data sources in a root module often fetch information about existing shared infrastructure or external systems. -# Keep these lookups minimal and well-documented, as the root module is usually the topmost layer of your configuration. -# - Data Source Declarations: Place all data blocks here, for example, data "aws_ami" "linux" { ... }. -# - Clear Naming and Purpose: Use descriptive names for data sources to indicate their role (e.g., data "aws_ami" "ubuntu_latest"). -# - Commenting and Filtering: Document why each data source is used and ensure filters or queries are well explained. -# - Minimize External Dependencies: Only query the minimum necessary information. Overly complicated data sources can slow down Terraform runs and confuse future maintainers. diff --git a/root-module/example.auto.tfvars b/root-module/example.auto.tfvars index 06c8046..be955f5 100644 --- a/root-module/example.auto.tfvars +++ b/root-module/example.auto.tfvars @@ -1,6 +1,5 @@ -# Purpose: -# The example.auto.tfvars file provides a sample set of input variable values for the root module. -# Terraform automatically loads any .auto.tfvars files, applying these values without requiring additional command-line flags. +# This example.auto.tfvars file provides a sample set of input variable values for the root module. +# Terraform automatically loads any `.auto.tfvars` files, applying these values without requiring additional command-line flags. # Rename or remove this file to fit your needs. length = 1 diff --git a/root-module/main.tf b/root-module/main.tf index f0a90c7..180c194 100644 --- a/root-module/main.tf +++ b/root-module/main.tf @@ -1,22 +1,3 @@ -# Purpose: -# The main.tf file in a root module is the entry point for defining and orchestrating your infrastructure. -# It may include resource definitions, calls to child modules, and overall configuration logic. - -# Best Practices: -# - Resource declarations: Place core resources that are unique to this layer of your infrastructure. -# - Module calls: -# - Use Terraform Registry Modules with Version Pinning: -# module "vpc" { -# source = "terraform-aws-modules/vpc/aws" -# version = "1.0.0" -# } -# - Use Git Sources with a Specific Tag or Commit: -# module "vpc" { -# source = "git::https://github.com/org/terraform-aws-vpc.git?ref=v1.0.0". -# } -# - Logical grouping: Group related resources and modules logically and use comments to explain complex logic. -# - Minimal hard-coding: Use variables defined in variables.tf instead of hard-coded values for flexibility and reusability. - locals { # Get the current timestamp and format it as YYYYMMDD prefix = formatdate("YYYYMMDD", timestamp()) diff --git a/root-module/outputs.tf b/root-module/outputs.tf deleted file mode 100644 index 85145b1..0000000 --- a/root-module/outputs.tf +++ /dev/null @@ -1,7 +0,0 @@ -# Purpose: -# The outputs.tf file defines values that the module exports for use by the caller. - -# Best Practices: -# - Descriptive output names: Use meaningful names (e.g., instance_id, db_connection_string). -# - Descriptions: Include description attributes to clarify the purpose of each output. -# - Minimal outputs: Only output what consumers need. For sensitive outputs, mark as `sensitive = true`. diff --git a/root-module/providers.tf b/root-module/providers.tf index f3cf5ac..17955e4 100644 --- a/root-module/providers.tf +++ b/root-module/providers.tf @@ -1,13 +1,3 @@ -# Purpose: -# The providers.tf file configures any providers the root module needs, including setting up authentication, default regions, or other provider-specific settings. - -# Best Practices: -# - Provider configuration: Define providers (e.g., aws {}, azurerm {}, google {}) and set their region, credentials, or other parameters. -# - Multiple provider configurations: If you need multiple configurations for the same provider (e.g., two AWS regions), define them here with explicit aliases. -# - Avoid hard-coded and static credentials: Instead of embedding static credentials directly in your code, consider: -# - AWS Assume Role: For the AWS provider, configure an assume role to obtain temporary credentials dynamically. -# - Encrypted Configuration Files: For providers requiring API tokens, use a tool like SOPS to encrypt sensitive variables. - provider "random" { # Configuration options } diff --git a/root-module/variables.tf b/root-module/variables.tf index f36b693..f684e07 100644 --- a/root-module/variables.tf +++ b/root-module/variables.tf @@ -1,12 +1,3 @@ -# Purpose: -# The variables.tf file defines input variables that control the module’s configuration. - -# Best Practices: -# - Descriptive variables: Use meaningful names and description attributes. -# - Default values: Provide reasonable defaults when possible. For mandatory inputs, omit defaults to enforce explicit user input. -# - Type vonstraints and validation: Use type constraints and validation blocks to catch incorrect inputs early. -# - Group related variables: Organize variables logically, adding comments to separate sections if many variables exist. - variable "length" { description = "The length of the random name" type = number diff --git a/root-module/versions.tf b/root-module/versions.tf index a73c535..9c733fb 100644 --- a/root-module/versions.tf +++ b/root-module/versions.tf @@ -1,17 +1,10 @@ -# Purpose: -# The versions.tf file sets explicit Terraform and provider versions, ensuring that users run a known-compatible setup. - -# Best Practices: -# - Pessimistic version constraints: Use version constraints (e.g., required_version = "~> 1.3") to allow patch updates but prevent breaking changes. -# - Stability over time: Regularly review and update version constraints as Terraform and providers evolve. - terraform { - required_version = "~> 1.0" + required_version = "1.8.7" required_providers { random = { source = "hashicorp/random" - version = "~> 3.0" + version = "~> 3.0.0" } } } diff --git a/terraform-random-pet/README.md b/terraform-random-pet/README.md index 3a65383..1f27ddb 100644 --- a/terraform-random-pet/README.md +++ b/terraform-random-pet/README.md @@ -2,6 +2,8 @@ This is a template child module. + + ## Documentation Recommendations (DO NOT INCLUDE THIS INTO THE REAL README) ### Naming @@ -9,34 +11,24 @@ This is a template child module. The repository/directory name should follow this pattern: ```sh -terraform-- +[terraform-]- ``` Here’s what this means: -1. The repository should start with `terraform-` if your module should be [published to and discovered on the Registry](https://opentofu.org/docs/language/modules/develop/publish/). Even if you don’t intend to publish the module, following this pattern is a good practice that helps differentiate your Terraform child modules from other code in your projects. -2. Include the provider name: After the prefix, specify the primary provider your module is for, such as aws, google, datadog, etc. +1. [If it's a separate repository] The repository should start with `terraform-` if your module should be [published to and discovered on the Registry](https://opentofu.org/docs/language/modules/develop/publish/). Even if you don’t intend to publish the module, following this pattern is a good practice that helps differentiate your Terraform child modules from other code in your projects. +2. Include the provider name: Specify the primary provider your module is for, such as aws, google, datadog, etc. 3. Use descriptive name: Follow the provider name with a clear and concise identifier that describes the module’s purpose. 4. Use hyphens to separate words. Also: 1. Keep name short and focused: While it should be descriptive, avoid overly long names. The goal is to convey the module’s purpose concisely: - - Good: terraform-aws-internal-lb - - Not so good: terraform-aws-internal-misc-module - - Too long: terraform-aws-internal-application-load-balancer-with-extra-rules + - Good: `terraform-aws-internal-lb`. + - Not so good: `terraform-aws-internal-misc-module`. + - Too long: `terraform-aws-internal-application-load-balancer-with-extra-rules`. 2. Module names should reflect their purpose rather than environment-specific details. -### Structure - -Explore the contents of each file to understand their purpose and discover recommended best practices. - -## Use Cases - -- Generating unique resource names (e.g., S3 buckets, compute instances). -- Attaching a common prefix to easily identify resources associated with a particular application or environment. -- Simplifying naming conventions and reducing collisions in environments with multiple resources. - ### Usage To use this module, reference it from your main configuration and provide the necessary input variables. For example: @@ -61,6 +53,8 @@ output "pet_name" { } ``` + + ## Requirements diff --git a/terraform-random-pet/data.tf b/terraform-random-pet/data.tf deleted file mode 100644 index 2510568..0000000 --- a/terraform-random-pet/data.tf +++ /dev/null @@ -1,8 +0,0 @@ -# Purpose: -# The data.tf file contains Terraform data sources—these do not create resources but query existing infrastructure or configuration for reference. - -# Best Practices: -# - Data source declarations: Place all data blocks here, for example, data "aws_ami" "linux" { ... }. -# - Clear naming and purpose: Use descriptive names for data sources to indicate their role (e.g., data "aws_ami" "ubuntu_latest"). -# - Commenting and filtering: Document why each data source is used and ensure filters or queries are well explained. -# - Minimize external dependencies: Only query the minimum necessary information. Overly complicated data sources can slow down Terraform runs and confuse future maintainers. diff --git a/terraform-random-pet/main.tf b/terraform-random-pet/main.tf index 4002dbf..c7103e1 100644 --- a/terraform-random-pet/main.tf +++ b/terraform-random-pet/main.tf @@ -1,12 +1,3 @@ -# Purpose: -# The main.tf file contains the core resource definitions and logic that compose the module’s functionality. - -# Best Practices: -# - Resource definitions: Declare here all the primary resources that this module is responsible for managing. -# - Locals and expressions: Use locals blocks to simplify expressions and keep the code DRY (Don’t Repeat Yourself). -# - Comments and structure: Organize resources logically and use comments to explain complex or non-obvious configurations. -# - Minimal hard-coding: Use variables extensively to avoid embedding environment-specific values directly in the code. - resource "random_pet" "template" { length = var.length prefix = var.prefix diff --git a/terraform-random-pet/outputs.tf b/terraform-random-pet/outputs.tf index 0d8d170..c44df14 100644 --- a/terraform-random-pet/outputs.tf +++ b/terraform-random-pet/outputs.tf @@ -1,11 +1,3 @@ -# Purpose: -# The outputs.tf file defines values that the module exports for use by the caller. - -# Best Practices: -# - Descriptive output names: Use meaningful names (e.g., instance_id, db_connection_string). -# - Descriptions: Include description attributes to clarify the purpose of each output. -# - Minimal outputs: Only output what consumers need. For sensitive outputs, mark as sensitive = true. - output "random_pet_name" { description = "The generated random pet name" value = random_pet.template.id diff --git a/terraform-random-pet/versions.tf b/terraform-random-pet/versions.tf index e565395..0cf661c 100644 --- a/terraform-random-pet/versions.tf +++ b/terraform-random-pet/versions.tf @@ -1,11 +1,3 @@ -# Purpose: -# The versions.tf file specifies the minimum allowed Terraform and provider versions using `>=`, ensuring consistency and compatibility. - -# Best Practices: -# - Terraform requirements: Use required_version to pin known-compatible Terraform versions (e.g., required_version = ">= 1.3"). -# - Provider requirements: Lock provider versions with required_providers to avoid unexpected upgrades (e.g., aws = { source = "hashicorp/aws" version = ">= 5.0" }). -# - Stability over time: Regularly review and update version constraints as Terraform and providers evolve. - terraform { required_version = ">= 1.0"