d1aa5daf2b
---ci--- project: atelier phase: 5 milestone: v0.2 status: complete requirements: covered: [ATELIER-36, ATELIER-37, ATELIER-38, ATELIER-39, ATELIER-40, ATELIER-41, ATELIER-42, ATELIER-43, ATELIER-44, ATELIER-45, ATELIER-46, ATELIER-47, ATELIER-48, ATELIER-49, ATELIER-50, ATELIER-51, ATELIER-52, ATELIER-53, ATELIER-54, ATELIER-55, ATELIER-56, ATELIER-57, ATELIER-58, ATELIER-59] partial: [] ---/ci---
125 lines
4.0 KiB
Markdown
125 lines
4.0 KiB
Markdown
# Good Example: Terraform Module
|
|
|
|
> A reusable Terraform module that follows Atelier's Infrastructure as Code principles. Each aspect cites the principle it satisfies.
|
|
|
|
## The Module
|
|
|
|
A versioned module that provisions an S3 bucket with logging, versioning, and encryption — the canonical "secure bucket" pattern, composed rather than copy-pasted.
|
|
|
|
### Consumer Call
|
|
|
|
```hcl
|
|
module "logs_bucket" {
|
|
source = "registry.example.com/infra/secure-bucket/aws"
|
|
version = "1.2.0"
|
|
|
|
name = "app-logs"
|
|
region = "us-east-1"
|
|
force_destroy = false
|
|
retention_days = 90
|
|
}
|
|
```
|
|
|
|
### Module Structure
|
|
|
|
```
|
|
secure-bucket/
|
|
├── main.tf # the resource
|
|
├── variables.tf # typed inputs
|
|
├── outputs.tf # the interface to consumers
|
|
├── versions.tf # provider pin
|
|
└── README.md # the module contract
|
|
```
|
|
|
|
### `versions.tf` (P5 Version Everything)
|
|
|
|
```hcl
|
|
terraform {
|
|
required_version = ">= 1.5.0"
|
|
required_providers {
|
|
aws = {
|
|
source = "hashicorp/aws"
|
|
version = "~> 5.0"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### `variables.tf` (P1 Declarative Intent, C2 Clarity)
|
|
|
|
```hcl
|
|
variable "name" {
|
|
type = string
|
|
description = "Globally unique bucket name."
|
|
validation {
|
|
condition = can(regex("^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$", var.name))
|
|
error_message = "Bucket name must be lowercase, 3-63 chars, DNS-compatible."
|
|
}
|
|
}
|
|
|
|
variable "retention_days" {
|
|
type = number
|
|
default = 30
|
|
description = "S3 lifecycle transition age in days."
|
|
}
|
|
```
|
|
|
|
### `main.tf` (P1, P3 State is Truth, P10 Secrets Never in Code)
|
|
|
|
```hcl
|
|
resource "aws_s3_bucket" "this" {
|
|
bucket = var.name
|
|
}
|
|
|
|
resource "aws_s3_bucket_versioning" "this" {
|
|
bucket = aws_s3_bucket.this.id
|
|
versioning_configuration {
|
|
status = "Enabled"
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
|
|
bucket = aws_s3_bucket.this.id
|
|
rule {
|
|
apply_server_side_encryption_by_default {
|
|
sse_algorithm = "AES256"
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_lifecycle_configuration" "this" {
|
|
bucket = aws_s3_bucket.this.id
|
|
rule {
|
|
id = "retention"
|
|
status = "Enabled"
|
|
filter { prefix = "" }
|
|
expiration { days = var.retention_days }
|
|
}
|
|
}
|
|
```
|
|
|
|
## What Makes It Good
|
|
|
|
### Composition (IaC P6 Modules Compose, C6 Composability)
|
|
- The bucket pattern is one module, versioned once, consumed many times. A new consumer does not copy 40 lines of HCL — they call the module with a `name` and a `retention_days`.
|
|
- See `domains/infrastructure-as-code/modules.md` for the module-vs-copy boundary.
|
|
|
|
### Pinning (IaC P5 Version Everything)
|
|
- The consumer pins `version = "1.2.0"`. The module pins its provider (`version = "~> 5.0"`) and the required Terraform version. A commit is a complete, reproducible world.
|
|
- No `latest` anywhere. See `domains/infrastructure-as-code/terraform.md`.
|
|
|
|
### State Discipline (IaC P3 State is Truth, P8 Remote State with Locking)
|
|
- The consumer's root configuration declares a remote backend with locking (S3 + DynamoDB, GCS, etc.). The module itself does not declare a backend — the consumer owns state.
|
|
- See `domains/infrastructure-as-code/state.md` for backend selection and locking.
|
|
|
|
### Secrets Hygiene (IaC P10 Secrets Never in Code)
|
|
- The bucket is encrypted at rest (SSE-S3 AES256). No secret is hardcoded; encryption is a provider-managed default. If KMS were used, the key would come from a `data` source or a dedicated KMS module — never a literal.
|
|
- See `domains/security/secrets.md` for the general secrets principles.
|
|
|
|
### Plan Before Apply (IaC P4 Plan Before Apply)
|
|
- The consumer runs `terraform plan` before `apply`. The plan shows the new bucket, versioning, encryption, and lifecycle. Every line is reviewed. The plan is the contract review; `apply` is the signature.
|
|
|
|
### Cross-Domain Links
|
|
- `domains/devops/P1 Reproducibility` — the module makes the bucket reproducible from source.
|
|
- `domains/devops/P6 Configuration as Code` — the bucket is config, not a console click.
|
|
- `domains/security/supply-chain` — a versioned, signed module from a trusted registry is a supply-chain control. |