docs(milestone): complete v0.2 — infrastructure-as-code + kubernetes
---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---
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
# Modules — Derived Rules
|
||||
|
||||
> Derives from `domains/infrastructure-as-code/first-principles.md`. P6 (Modules Compose) lives here. Referenced by `terraform.md` and `opentofu.md`.
|
||||
|
||||
## Why Modules (P6 Modules Compose)
|
||||
|
||||
- A module is the unit of reuse, review, and versioning in IaC. It encapsulates a repeatable pattern behind a typed interface.
|
||||
- Composition — building large from small — is the IaC expression of core C6 Composability. Without modules, every stack is a one-off; with modules, a stack is an assembly of reviewed parts.
|
||||
- A good module has one job (a VPC, a database, a load balancer), a small typed surface, and no hidden side effects.
|
||||
- A versioned module is the IaC expression of `domains/devops/first-principles.md` P1 (Reproducibility) and P6 (Configuration as Code): a module pins a reusable, rebuildable pattern that any environment can call.
|
||||
|
||||
## Module Structure (P1 Declarative Intent, C2 Clarity)
|
||||
|
||||
- The conventional layout: `main.tf` (resources), `variables.tf` (inputs), `outputs.tf` (outputs), `versions.tf` (provider/version pins). A `README.md` is required for any published module.
|
||||
- Inputs are typed and validated: `variable "name" { type = string, description = "...", validation { ... } }`. The description is the contract.
|
||||
- Outputs are the module's interface to consumers. Mark sensitive outputs `sensitive = true`. Document non-obvious outputs in the description.
|
||||
- A module does not declare a provider configuration unless it owns the provider. Most modules declare only `required_providers` (the constraint) and let the consumer configure the provider.
|
||||
|
||||
## Versioning (P5 Version Everything)
|
||||
|
||||
- Modules are versioned. The registry expects SemVer tags (`v1.0.0`). A consumer pins to a version or a range (`~> 1.0`).
|
||||
- A breaking change bumps the major. An additive change bumps the minor. A fix bumps the patch. No silent breaking changes within a minor.
|
||||
- Tag the module repo; the tag IS the version. Never `source = "git::...?ref=main"` in prod — unversioned modules drift.
|
||||
|
||||
## Source Patterns (P5 Version Everything)
|
||||
|
||||
| Source | When | Risk |
|
||||
|--------|------|------|
|
||||
| Registry (`<ns>/<name>/<provider>`) | Public, versioned, signed | Verify the publisher; pin the version |
|
||||
| Git (`git::https://...?ref=v1.0.0`) | Private modules across repos | Pin to a tag, not a branch |
|
||||
| Local (`./modules/networking`) | Monorepo, single repo | Re-reviewed on every change; no independent version |
|
||||
| Inline (no module) | Trivial one-off | Becomes a copy-paste anti-pattern at scale |
|
||||
|
||||
- Local modules in a monorepo are fine — they trade independent versioning for co-evolution. The boundary is the review unit: if the module and the consumer always change together, local is correct.
|
||||
- Cross-repo modules must be versioned via git tags; unversioned cross-repo modules are the worst case (drift without a version to pin).
|
||||
|
||||
## The Module-vs-Copy Boundary (P6 Modules Compose)
|
||||
|
||||
- If a block is used more than once, it is a module. If it is used once and will never be reused, inline is acceptable.
|
||||
- If two copies differ in one attribute, that is a module with a variable, not two copies. The variable is the difference; the shared body is the module.
|
||||
- If you find yourself copy-pasting a block and editing it, stop. The edit is a variable. The copy is a module call.
|
||||
- A module that has grown to do many jobs should be split. A module with 20 variables is two modules.
|
||||
|
||||
## Composition (P6 Modules Compose, C6 Composability)
|
||||
|
||||
- Compose by calling modules from a root configuration: `module "vpc" { source = "...", version = "..." }`. The root is the assembly; the modules are the parts.
|
||||
- Outputs of one module feed inputs of another: `module "app" { vpc_id = module.vpc.vpc_id }`. This is the composition edge.
|
||||
- Avoid hidden coupling: a module should not reach into another module's state. If two modules must share state, promote the shared concern to the root or a parent module.
|
||||
|
||||
## Reviewing Modules (P4 Plan Before Apply)
|
||||
|
||||
- A module is reviewed once, at its version. Consumers trust the version pin. A module change requires a new version and a review of the diff.
|
||||
- When a module changes, every consumer that bumps the version gets the change. Treat a module version bump as a real change: review the module diff, run the consumer's `plan`.
|
||||
- A module with a breaking change must not auto-bump in consumers. Pin consumers to the old major until they explicitly migrate.
|
||||
|
||||
## What Violates Module Discipline
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| Copy-pasted block with a one-line difference | P6 Modules Compose |
|
||||
| `source = "git::...?ref=main"` in prod | P5 Version Everything |
|
||||
| Module with 20 variables | P6 Modules Compose (split it) |
|
||||
| Silent breaking change within a minor | P5 Version Everything |
|
||||
| Module reaching into another module's state | C6 Composability, P1 Declarative Intent |
|
||||
| Unpublished module with no README | C2 Clarity |
|
||||
Reference in New Issue
Block a user