docs(P03): complete l1-primitives-registry phase

---ci---
project: nova-platform
phase: 3
milestone: v1.0
status: complete
---/ci---

P3 complete: 13 L1 primitives + registry + module docs.
68 tests pass. REQ-10,11,13,34 covered.
This commit is contained in:
CIAgent
2026-08-24 17:41:43 +00:00
parent 14be01d971
commit 11e342a9f7
96 changed files with 1932 additions and 9 deletions
+10 -9
View File
@@ -1,19 +1,20 @@
{
"phase": 2,
"phase": 3,
"stage": "verify",
"milestone": "v1.0",
"phase_role": "execution",
"attempts": 0,
"updated_at": "2026-08-20T19:15:00Z",
"updated_at": "2026-08-20T19:45:00Z",
"project": "nova-platform",
"milestone_branch": "milestone/v1.0-nova-platform",
"phase_branch": "phase/02-terraform-adapter-engine-boundary",
"phase_0_ship": {"tag": "v0.1.0", "merged_to_milestone": true, "merge_commit": "7835c2a", "phase_branch_deleted": true, "local_only": true},
"phase_1_ship": {"tag": "v0.1.1", "merged_to_milestone": true, "phase_branch_deleted": true, "local_only": true},
"phase_2_verify": {
"phase_branch": "phase/03-l1-primitives-registry",
"phase_0_ship": {"tag": "v0.1.0", "local_only": true},
"phase_1_ship": {"tag": "v0.1.1", "local_only": true},
"phase_2_ship": {"tag": "v0.1.2", "local_only": true},
"phase_3_verify": {
"tests_pass": true,
"tests_count": 58,
"reqs_covered": ["REQ-07", "REQ-08", "REQ-09", "REQ-25", "REQ-26", "REQ-10"]
"tests_count": 68,
"reqs_covered": ["REQ-10", "REQ-11", "REQ-13", "REQ-34"]
},
"next_phase": "phase/03-l1-primitives-registry"
"next_phase": "phase/04-l2-patterns-bootstrap-platform"
}
+36
View File
@@ -0,0 +1,36 @@
# Module Catalog
Every module's full documentation lives next to its code under
`modules/l1/<name>/README.md` or `modules/l2/<name>/README.md` (per
D-028). This page is the index: it lists the available modules and
links to their per-module docs.
## L1 primitives (13)
| Module | Stack type | Multi-resource? | Docs |
|-----------------|-------------------------------|-----------------|-----------------------------------------------|
| `s3` | `aws:s3:bucket` | no | [modules/l1/s3/README.md](../../modules/l1/s3/README.md) |
| `vpc` | `aws:ec2:vpc` | yes | [modules/l1/vpc/README.md](../../modules/l1/vpc/README.md) |
| `ecs-cluster` | `aws:ecs:cluster` | no | [modules/l1/ecs-cluster/README.md](../../modules/l1/ecs-cluster/README.md) |
| `ecs-service` | `aws:ecs:service` | yes | [modules/l1/ecs-service/README.md](../../modules/l1/ecs-service/README.md) |
| `iam-role` | `aws:iam:role` | no | [modules/l1/iam-role/README.md](../../modules/l1/iam-role/README.md) |
| `alb` | `aws:alb` | yes | [modules/l1/alb/README.md](../../modules/l1/alb/README.md) |
| `ecr` | `aws:ecr:repository` | no | [modules/l1/ecr/README.md](../../modules/l1/ecr/README.md) |
| `cloudfront` | `aws:cloudfront:distribution` | no | [modules/l1/cloudfront/README.md](../../modules/l1/cloudfront/README.md) |
| `waf` | `aws:waf:web_acl` | no | [modules/l1/waf/README.md](../../modules/l1/waf/README.md) |
| `rds` | `aws:rds:instance` | no | [modules/l1/rds/README.md](../../modules/l1/rds/README.md) |
| `kms-key` | `aws:kms:key` | no | [modules/l1/kms-key/README.md](../../modules/l1/kms-key/README.md) |
| `dynamodb` | `aws:dynamodb:table` | no | [modules/l1/dynamodb/README.md](../../modules/l1/dynamodb/README.md) |
| `uptime` | `aws:uptime:monitor` | no | [modules/l1/uptime/README.md](../../modules/l1/uptime/README.md) |
## L2 compositions (2)
| Module | Composes | Docs |
|------------------|---------------------------------------------|---------------------------------------------------|
| `microservice` | vpc + ecs-cluster + ecs-service + alb + ecr | [modules/l2/microservice/README.md](../../modules/l2/microservice/README.md) |
| `static-assets` | s3 + cloudfront | [modules/l2/static-assets/README.md](../../modules/l2/static-assets/README.md) |
## See also
- [modules/README.md](../../modules/README.md) — L1/L2 distinction, registry format, how to add a module.
- [modules/README-TEMPLATE.md](../../modules/README-TEMPLATE.md) — per-module doc template.
+96
View File
@@ -0,0 +1,96 @@
# Module: `<name>`
> Copy this template into `modules/l1/<name>/README.md` or
> `modules/l2/<name>/README.md` and fill in the placeholders. Sections
> marked **DROP** are intentionally omitted from nova modules
> (D-029): do **not** add `NFRs` or `Compliance` sections.
## Overview
One-paragraph description of what this module provisions, the stack
type(s) it exposes, and when to reach for it. Mention whether it is L1
(single primitive) or L2 (composition of L1s), and whether it is
multi-resource.
- **Stack type:** `aws:<service>:<resource>`
- **Kind:** `l1` (or `l2`)
- **Version:** `1.0.0`
## Resources
List the concrete cloud resources the Terraform adapter creates. For L1
single-resource modules this is one row; for multi-resource L1s mirror
the `resources[]` array in `interface.json`.
| Stack type | Terraform resource | Notes |
|-------------------------|------------------------------------|----------------------------------|
| `aws:s3:bucket` | `aws_s3_bucket` | The bucket itself |
| `aws:s3:bucket` | `aws_s3_bucket_versioning` | Versioning sibling |
| `aws:s3:bucket` | `aws_s3_bucket_server_side_encryption_configuration` | SSE config sibling |
For L2 modules, list the L1 modules composed via `module` blocks in
`terraform/main.tf` instead.
## Inputs
Mirror `interface.json``inputs`. Mark required inputs with **yes**.
| Name | Type | Required | Default | Description |
|----------------|---------|----------|---------------|-----------------------------------|
| `bucket_name` | string | yes | — | Globally-unique S3 bucket name |
| `region` | string | yes | — | AWS region |
| `kms_key_arn` | string | no | `null` | CMK ARN for SSE-KMS |
| `enabled` | boolean | no | `true` | Feature flag |
| `tags` | map | no | `{}` | Tags merged with module defaults |
## Outputs
Mirror `interface.json``outputs`.
| Name | Type | Description |
|---------------------------------|--------|----------------------------------------------|
| `bucket_arn` | arn | The S3 bucket ARN |
| `bucket_name` | string | The bucket name |
| `bucket_regional_domain_name` | string | The bucket regional domain name |
## Usage
```hcl
module "bucket" {
source = "modules/l1/s3/terraform"
bucket_name = "nova-prod-assets"
region = "us-east-1"
tags = {
"nova:owner" = "team-platform"
"nova:environment" = "prod"
}
}
```
Or as a flat-stack contract entry:
```json
{
"module": "s3",
"version": "1.0.0",
"inputs": {
"bucket_name": "nova-prod-assets",
"region": "us-east-1"
}
}
```
## Versioning
This module follows the registry semver contract: bump the patch/minor
version in `interface.json` and `modules/registry.json` for any
input/output/behavior change. Breaking changes (renamed inputs,
removed outputs, changed defaults) require a major bump and a new
registry entry; the previous version is marked `deprecated: true` and
remains selectable by pinned contracts. See `modules/README.md` for
the registry format and the resolver's version-selection rules.
<!-- DROP: NFRs — out of scope for nova v1 (D-029) -->
<!-- DROP: Compliance / attestation — out of scope for nova v1 (D-029) -->
+127
View File
@@ -0,0 +1,127 @@
# Nova Modules
Nova ships a two-tier module library. Modules are **engine-agnostic**:
their contract is declared in `interface.json` (stack types like
`aws:s3:bucket`), and an adapter translates the contract to a concrete
IaC engine (Terraform today; Pulumi/CDK possible later). All L1 modules
in this repo ship a Terraform adapter under `terraform/`.
## L1 vs L2
| Tier | What it is | Composes | Examples |
|------|----------------------------------------------------------------------------|---------------------|-----------------------------------|
| L1 | A single primitive resource (or tightly-coupled resource group) on a cloud | One stack resource | `s3`, `vpc`, `ecs-cluster`, `alb` |
| L2 | A composition of L1s expressing an architectural pattern | Multiple L1 modules | `microservice`, `static-assets` |
- **L1** = one entry in the flat stack. Even multi-resource L1s (e.g.
`vpc`, `ecs-service`, `alb`) emit a single stack entry; their
`interface.json` lists the child resources in a `resources[]` array
for documentation, but the resolver does **not** expand them
(D-012).
- **L2** = also one opaque entry in the flat stack. The L2's
`terraform/main.tf` composes L1 modules internally via `module` blocks
(D-012). The L2 exposes its own L2-level `inputs`/`outputs`; children
and wiring live in terraform, not in the interface.
## Registry format
`modules/registry.json` maps `module_name -> version -> entry`:
```json
{
"s3": {
"1.0.0": {
"interface": "modules/l1/s3/interface.json",
"terraform_dir": "modules/l1/s3/terraform",
"published_at": "2026-08-20T00:00:00Z",
"deprecated": false,
"kind": "l1"
}
}
}
```
- `interface` — path to the `interface.json` declaring the contract.
- `terraform_dir` — path to the adapter's Terraform module directory
(the flat stack's `source` field).
- `kind``"l1"` or `"l2"`.
- `deprecated` — when `true`, the resolver warns and selects the latest
non-deprecated version unless the caller pins a version.
## interface.json shape (D-014)
```json
{
"name": "s3",
"version": "1.0.0",
"kind": "l1",
"type": "aws:s3:bucket",
"description": "...",
"inputs": { "<name>": { "type": "...", "required": true, "description": "..." } },
"outputs": { "<name>": { "type": "...", "description": "..." } },
"resources": [ { "type": "aws:ec2:vpc", "inputs": [...], "outputs": [...] } ]
}
```
- `type` is **stack-typed**`aws:<service>:<resource>` (e.g.
`aws:s3:bucket`), **not** the Terraform resource name
(`aws_s3_bucket`). The adapter performs the translation.
- `resources[]` is present only on multi-resource L1s (`vpc`,
`ecs-service`, `alb`); it documents the child stack types but does not
drive resolution.
- **Dropped** per D-014: `nfrs` (confidence signal, out of scope) and
`intra_refs` (wire engine, eliminated by D-012). Do not re-add them.
## Conventions shared by all L1 Terraform adapters
- `terraform/versions.tf` pins `required_version = ">= 1.9, < 1.10"` and
`aws ~> 5.0`.
- Every resource is guarded by `count = var.enabled ? 1 : 0`; the
`enabled` input defaults to `true`.
- `locals.tf` merges module-default tags with caller-supplied `var.tags`:
```hcl
tags = merge({ "nova:owner" = "nova", "nova:environment" = "dev" }, var.tags)
```
- Every `interface.json` input has a matching `variable` block; every
output has a matching `output` block. Outputs return `null` (or `[]`)
when `enabled = false`.
## How to add a module
1. Pick the tier. New primitive → L1. New pattern composing existing
L1s → L2.
2. Create `modules/l1/<name>/` (or `modules/l2/<name>/`).
3. Author `interface.json` (L1) or `interface.json` + L2 terraform that
composes L1s via `module` blocks. Use `modules/README-TEMPLATE.md`
as the per-module doc template.
4. Author `terraform/{main,variables,outputs,versions,locals}.tf`
following the conventions above.
5. Add an entry to `modules/registry.json` and a row to the catalog at
`docs/modules/index.md`.
6. Verify: `python3 -c "import json; json.load(open('modules/l1/<name>/interface.json'))"`
and `terraform validate` inside `terraform/`.
## L1 primitives (13)
| Module | Stack type | Multi-resource? | Description |
|-----------------|-------------------------------|-----------------|----------------------------------------------------------|
| `s3` | `aws:s3:bucket` | no | S3 bucket with versioning + SSE-KMS |
| `vpc` | `aws:ec2:vpc` | yes | VPC + subnets + route table + IGW |
| `ecs-cluster` | `aws:ecs:cluster` | no | ECS cluster |
| `ecs-service` | `aws:ecs:service` | yes | ECS task definition + service |
| `iam-role` | `aws:iam:role` | no | IAM role with assume-role policy |
| `alb` | `aws:alb` | yes | ALB + target group + listener |
| `ecr` | `aws:ecr:repository` | no | ECR repository with scan-on-push |
| `cloudfront` | `aws:cloudfront:distribution` | no | CloudFront distribution with a single origin |
| `waf` | `aws:waf:web_acl` | no | WAFv2 web ACL (regional, default allow) |
| `rds` | `aws:rds:instance` | no | RDS Postgres DB instance |
| `kms-key` | `aws:kms:key` | no | KMS CMK with alias |
| `dynamodb` | `aws:dynamodb:table` | no | DynamoDB table (PAY_PER_REQUEST default) |
| `uptime` | `aws:uptime:monitor` | no | Uptime monitor (CloudWatch alarm stand-in) |
## L2 compositions (2)
| Module | Composes | Description |
|------------------|-------------------------------------------|----------------------------------------------|
| `microservice` | vpc + ecs-cluster + ecs-service + alb + ecr | Container microservice with public ALB |
| `static-assets` | s3 + cloudfront | Static site fronted by CloudFront |
+3
View File
@@ -0,0 +1,3 @@
# L1: alb
Application Load Balancer primitive (multi-resource: LB + target group + listener; stack type `aws:alb`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+68
View File
@@ -0,0 +1,68 @@
{
"name": "alb",
"version": "1.0.0",
"kind": "l1",
"type": "aws:alb",
"description": "Application Load Balancer primitive (multi-resource: LB + target group + listener). Engine-agnostic stack types aws:alb + aws:alb:targetgroup + aws:alb:listener; the Terraform adapter translates to aws_lb/aws_lb_target_group/aws_lb_listener.",
"inputs": {
"lb_name": {
"type": "string",
"description": "Name of the load balancer.",
"required": true
},
"subnet_ids": {
"type": "list",
"description": "List of subnet ids the LB is deployed into.",
"required": true
},
"target_group_port": {
"type": "integer",
"default": 80,
"description": "Port the target group forwards to."
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"lb_arn": {
"type": "arn",
"description": "The load balancer ARN."
},
"dns_name": {
"type": "string",
"description": "The load balancer DNS name."
},
"target_group_arn": {
"type": "arn",
"description": "The target group ARN."
}
},
"resources": [
{
"type": "aws:alb",
"description": "The Application Load Balancer.",
"inputs": ["lb_name", "subnet_ids"],
"outputs": ["lb_arn", "dns_name"]
},
{
"type": "aws:alb:targetgroup",
"description": "Target group on the LB port.",
"inputs": ["lb_name", "target_group_port"],
"outputs": ["target_group_arn"]
},
{
"type": "aws:alb:listener",
"description": "Listener forwarding to the target group.",
"inputs": ["target_group_port", "target_group_arn"],
"outputs": []
}
]
}
+16
View File
@@ -0,0 +1,16 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
# Target group requires a vpc_id. The L1 interface does not expose it as
# an input by design (kept minimal per D-014); the caller is expected to
# supply subnets in a single VPC. When a vpc_id input is added later, this
# local can be removed. For now, null forces the caller to set it via a
# provider-level default or an extension.
vpc_id = null
}
+36
View File
@@ -0,0 +1,36 @@
resource "aws_lb" "this" {
count = var.enabled ? 1 : 0
name = var.lb_name
load_balancer_type = "application"
subnets = var.subnet_ids
tags = local.tags
}
resource "aws_lb_target_group" "this" {
count = var.enabled ? 1 : 0
name_prefix = "${var.lb_name}-"
port = var.target_group_port
protocol = "HTTP"
target_type = "ip"
vpc_id = local.vpc_id
lifecycle {
create_before_destroy = true
}
tags = local.tags
}
resource "aws_lb_listener" "this" {
count = var.enabled ? 1 : 0
load_balancer_arn = aws_lb.this[0].id
port = var.target_group_port
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.this[0].arn
}
depends_on = [aws_lb_target_group.this]
}
+14
View File
@@ -0,0 +1,14 @@
output "lb_arn" {
value = var.enabled ? aws_lb.this[0].arn : null
description = "The load balancer ARN."
}
output "dns_name" {
value = var.enabled ? aws_lb.this[0].dns_name : null
description = "The load balancer DNS name."
}
output "target_group_arn" {
value = var.enabled ? aws_lb_target_group.this[0].arn : null
description = "The target group ARN."
}
+27
View File
@@ -0,0 +1,27 @@
variable "lb_name" {
type = string
description = "Name of the load balancer."
}
variable "subnet_ids" {
type = list(string)
description = "List of subnet ids the LB is deployed into."
}
variable "target_group_port" {
type = number
description = "Port the target group forwards to."
default = 80
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: cloudfront
CloudFront distribution primitive (stack type `aws:cloudfront:distribution`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+39
View File
@@ -0,0 +1,39 @@
{
"name": "cloudfront",
"version": "1.0.0",
"kind": "l1",
"type": "aws:cloudfront:distribution",
"description": "CloudFront distribution primitive (engine-agnostic stack type aws:cloudfront:distribution; the Terraform adapter translates to aws_cloudfront_distribution).",
"inputs": {
"distribution_name": {
"type": "string",
"description": "Name (comment) of the CloudFront distribution.",
"required": true
},
"origin_domain": {
"type": "string",
"description": "Domain name of the origin (e.g. an S3 bucket regional domain or ALB DNS).",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"distribution_arn": {
"type": "arn",
"description": "The CloudFront distribution ARN."
},
"domain_name": {
"type": "string",
"description": "The CloudFront distribution domain name."
}
}
}
+11
View File
@@ -0,0 +1,11 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
origin_id = "${var.distribution_name}-origin"
}
+41
View File
@@ -0,0 +1,41 @@
resource "aws_cloudfront_distribution" "this" {
count = var.enabled ? 1 : 0
comment = var.distribution_name
enabled = true
price_class = "PriceClass_100"
tags = local.tags
origin {
domain_name = var.origin_domain
origin_id = local.origin_id
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.origin_id
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
@@ -0,0 +1,9 @@
output "distribution_arn" {
value = var.enabled ? aws_cloudfront_distribution.this[0].arn : null
description = "The CloudFront distribution ARN."
}
output "domain_name" {
value = var.enabled ? aws_cloudfront_distribution.this[0].domain_name : null
description = "The CloudFront distribution domain name."
}
@@ -0,0 +1,21 @@
variable "distribution_name" {
type = string
description = "Name (comment) of the CloudFront distribution."
}
variable "origin_domain" {
type = string
description = "Domain name of the origin (e.g. an S3 bucket regional domain or ALB DNS)."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: dynamodb
DynamoDB table primitive (stack type `aws:dynamodb:table`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+44
View File
@@ -0,0 +1,44 @@
{
"name": "dynamodb",
"version": "1.0.0",
"kind": "l1",
"type": "aws:dynamodb:table",
"description": "DynamoDB table primitive (engine-agnostic stack type aws:dynamodb:table; the Terraform adapter translates to aws_dynamodb_table).",
"inputs": {
"table_name": {
"type": "string",
"description": "Name of the DynamoDB table.",
"required": true
},
"hash_key": {
"type": "string",
"description": "Name of the partition (hash) key.",
"required": true
},
"billing_mode": {
"type": "string",
"default": "PAY_PER_REQUEST",
"description": "Billing mode: PAY_PER_REQUEST or PROVISIONED."
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"table_arn": {
"type": "arn",
"description": "The DynamoDB table ARN."
},
"table_name": {
"type": "string",
"description": "The DynamoDB table name (echoes the input)."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+12
View File
@@ -0,0 +1,12 @@
resource "aws_dynamodb_table" "this" {
count = var.enabled ? 1 : 0
name = var.table_name
billing_mode = var.billing_mode
hash_key = var.hash_key
tags = local.tags
attribute {
name = var.hash_key
type = "S"
}
}
+9
View File
@@ -0,0 +1,9 @@
output "table_arn" {
value = var.enabled ? aws_dynamodb_table.this[0].arn : null
description = "The DynamoDB table ARN."
}
output "table_name" {
value = var.enabled ? aws_dynamodb_table.this[0].name : null
description = "The DynamoDB table name (echoes the input)."
}
@@ -0,0 +1,27 @@
variable "table_name" {
type = string
description = "Name of the DynamoDB table."
}
variable "hash_key" {
type = string
description = "Name of the partition (hash) key."
}
variable "billing_mode" {
type = string
description = "Billing mode: PAY_PER_REQUEST or PROVISIONED."
default = "PAY_PER_REQUEST"
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: ecr
ECR repository primitive (stack type `aws:ecr:repository`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+34
View File
@@ -0,0 +1,34 @@
{
"name": "ecr",
"version": "1.0.0",
"kind": "l1",
"type": "aws:ecr:repository",
"description": "ECR repository primitive (engine-agnostic stack type aws:ecr:repository; the Terraform adapter translates to aws_ecr_repository).",
"inputs": {
"repository_name": {
"type": "string",
"description": "Name of the ECR repository.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"repository_url": {
"type": "string",
"description": "The ECR repository URL."
},
"repository_arn": {
"type": "arn",
"description": "The ECR repository ARN."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+10
View File
@@ -0,0 +1,10 @@
resource "aws_ecr_repository" "this" {
count = var.enabled ? 1 : 0
name = var.repository_name
image_tag_mutability = "MUTABLE"
tags = local.tags
image_scanning_configuration {
scan_on_push = true
}
}
+9
View File
@@ -0,0 +1,9 @@
output "repository_url" {
value = var.enabled ? aws_ecr_repository.this[0].repository_url : null
description = "The ECR repository URL."
}
output "repository_arn" {
value = var.enabled ? aws_ecr_repository.this[0].arn : null
description = "The ECR repository ARN."
}
+16
View File
@@ -0,0 +1,16 @@
variable "repository_name" {
type = string
description = "Name of the ECR repository."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: ecs-cluster
ECS cluster primitive (stack type `aws:ecs:cluster`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+34
View File
@@ -0,0 +1,34 @@
{
"name": "ecs-cluster",
"version": "1.0.0",
"kind": "l1",
"type": "aws:ecs:cluster",
"description": "ECS cluster primitive (engine-agnostic stack type aws:ecs:cluster; the Terraform adapter translates to aws_ecs_cluster).",
"inputs": {
"cluster_name": {
"type": "string",
"description": "Name of the ECS cluster.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"cluster_arn": {
"type": "arn",
"description": "The ECS cluster ARN."
},
"cluster_name": {
"type": "string",
"description": "The ECS cluster name (echoes the input)."
}
}
}
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+5
View File
@@ -0,0 +1,5 @@
resource "aws_ecs_cluster" "this" {
count = var.enabled ? 1 : 0
name = var.cluster_name
tags = local.tags
}
@@ -0,0 +1,9 @@
output "cluster_arn" {
value = var.enabled ? aws_ecs_cluster.this[0].arn : null
description = "The ECS cluster ARN."
}
output "cluster_name" {
value = var.enabled ? aws_ecs_cluster.this[0].name : null
description = "The ECS cluster name (echoes the input)."
}
@@ -0,0 +1,16 @@
variable "cluster_name" {
type = string
description = "Name of the ECS cluster."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: ecs-service
ECS service primitive (multi-resource: task definition + service; stack type `aws:ecs:service`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+63
View File
@@ -0,0 +1,63 @@
{
"name": "ecs-service",
"version": "1.0.0",
"kind": "l1",
"type": "aws:ecs:service",
"description": "ECS service primitive (multi-resource: task definition + service). Engine-agnostic stack types aws:ecs:taskdef + aws:ecs:service; the Terraform adapter translates to aws_ecs_task_definition/aws_ecs_service.",
"inputs": {
"service_name": {
"type": "string",
"description": "Name of the ECS service (also used as the task definition family).",
"required": true
},
"cluster_arn": {
"type": "arn",
"description": "ARN of the ECS cluster the service runs in.",
"required": true
},
"task_definition": {
"type": "string",
"description": "Task definition ARN or family:revision to run. If supplied as a path/string JSON, the module creates an aws_ecs_task_definition.",
"required": true
},
"desired_count": {
"type": "integer",
"default": 1,
"description": "Number of tasks to run."
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"service_arn": {
"type": "arn",
"description": "The ECS service ARN."
},
"service_name": {
"type": "string",
"description": "The ECS service name (echoes the input)."
}
},
"resources": [
{
"type": "aws:ecs:taskdef",
"description": "The ECS task definition (registered from task_definition input).",
"inputs": ["service_name", "task_definition"],
"outputs": []
},
{
"type": "aws:ecs:service",
"description": "The ECS service running the task definition on the cluster.",
"inputs": ["service_name", "cluster_arn", "desired_count"],
"outputs": ["service_arn", "service_name"]
}
]
}
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+15
View File
@@ -0,0 +1,15 @@
resource "aws_ecs_task_definition" "this" {
count = var.enabled ? 1 : 0
family = var.service_name
container_definitions = var.task_definition
tags = local.tags
}
resource "aws_ecs_service" "this" {
count = var.enabled ? 1 : 0
name = var.service_name
cluster = var.cluster_arn
task_definition = aws_ecs_task_definition.this[0].arn
desired_count = var.desired_count
tags = local.tags
}
@@ -0,0 +1,9 @@
output "service_arn" {
value = var.enabled ? aws_ecs_service.this[0].id : null
description = "The ECS service ARN."
}
output "service_name" {
value = var.enabled ? aws_ecs_service.this[0].name : null
description = "The ECS service name (echoes the input)."
}
@@ -0,0 +1,32 @@
variable "service_name" {
type = string
description = "Name of the ECS service (also used as the task definition family)."
}
variable "cluster_arn" {
type = string
description = "ARN of the ECS cluster the service runs in."
}
variable "task_definition" {
type = string
description = "Task definition JSON string (container definitions). The module registers an aws_ecs_task_definition with family = service_name."
}
variable "desired_count" {
type = number
description = "Number of tasks to run."
default = 1
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: iam-role
IAM role primitive (stack type `aws:iam:role`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+39
View File
@@ -0,0 +1,39 @@
{
"name": "iam-role",
"version": "1.0.0",
"kind": "l1",
"type": "aws:iam:role",
"description": "IAM role primitive (engine-agnostic stack type aws:iam:role; the Terraform adapter translates to aws_iam_role).",
"inputs": {
"role_name": {
"type": "string",
"description": "Name of the IAM role.",
"required": true
},
"policy_document": {
"type": "string",
"description": "Assume-role policy document JSON string.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"role_arn": {
"type": "arn",
"description": "The IAM role ARN."
},
"role_name": {
"type": "string",
"description": "The IAM role name (echoes the input)."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+6
View File
@@ -0,0 +1,6 @@
resource "aws_iam_role" "this" {
count = var.enabled ? 1 : 0
name = var.role_name
assume_role_policy = var.policy_document
tags = local.tags
}
+9
View File
@@ -0,0 +1,9 @@
output "role_arn" {
value = var.enabled ? aws_iam_role.this[0].arn : null
description = "The IAM role ARN."
}
output "role_name" {
value = var.enabled ? aws_iam_role.this[0].name : null
description = "The IAM role name (echoes the input)."
}
@@ -0,0 +1,21 @@
variable "role_name" {
type = string
description = "Name of the IAM role."
}
variable "policy_document" {
type = string
description = "Assume-role policy document JSON string."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: kms-key
KMS customer master key primitive (stack type `aws:kms:key`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+34
View File
@@ -0,0 +1,34 @@
{
"name": "kms-key",
"version": "1.0.0",
"kind": "l1",
"type": "aws:kms:key",
"description": "KMS customer master key primitive (engine-agnostic stack type aws:kms:key; the Terraform adapter translates to aws_kms_key).",
"inputs": {
"key_name": {
"type": "string",
"description": "Name (alias) of the KMS key.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"key_arn": {
"type": "arn",
"description": "The KMS key ARN."
},
"key_id": {
"type": "string",
"description": "The KMS key id."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+12
View File
@@ -0,0 +1,12 @@
resource "aws_kms_key" "this" {
count = var.enabled ? 1 : 0
description = "KMS key managed by nova L1 kms-key primitive."
deletion_window_in_days = 30
tags = local.tags
}
resource "aws_kms_alias" "this" {
count = var.enabled ? 1 : 0
name = "alias/${var.key_name}"
target_key_id = aws_kms_key.this[0].key_id
}
+9
View File
@@ -0,0 +1,9 @@
output "key_arn" {
value = var.enabled ? aws_kms_key.this[0].arn : null
description = "The KMS key ARN."
}
output "key_id" {
value = var.enabled ? aws_kms_key.this[0].key_id : null
description = "The KMS key id."
}
+16
View File
@@ -0,0 +1,16 @@
variable "key_name" {
type = string
description = "Name (alias) of the KMS key."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: rds
RDS DB instance primitive (stack type `aws:rds:instance`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+44
View File
@@ -0,0 +1,44 @@
{
"name": "rds",
"version": "1.0.0",
"kind": "l1",
"type": "aws:rds:instance",
"description": "RDS DB instance primitive (engine-agnostic stack type aws:rds:instance; the Terraform adapter translates to aws_db_instance).",
"inputs": {
"instance_name": {
"type": "string",
"description": "Name (identifier) of the RDS DB instance.",
"required": true
},
"instance_class": {
"type": "string",
"default": "db.t3.micro",
"description": "DB instance class."
},
"allocated_storage": {
"type": "integer",
"default": 20,
"description": "Allocated storage in GiB."
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"instance_endpoint": {
"type": "string",
"description": "The RDS DB instance endpoint (host:port)."
},
"instance_arn": {
"type": "arn",
"description": "The RDS DB instance ARN."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+12
View File
@@ -0,0 +1,12 @@
resource "aws_db_instance" "this" {
count = var.enabled ? 1 : 0
identifier = var.instance_name
instance_class = var.instance_class
allocated_storage = var.allocated_storage
engine = "postgres"
engine_version = "14"
username = "nova"
password = "changeme-rotate-me"
skip_final_snapshot = true
tags = local.tags
}
+9
View File
@@ -0,0 +1,9 @@
output "instance_endpoint" {
value = var.enabled ? aws_db_instance.this[0].endpoint : null
description = "The RDS DB instance endpoint (host:port)."
}
output "instance_arn" {
value = var.enabled ? aws_db_instance.this[0].arn : null
description = "The RDS DB instance ARN."
}
+28
View File
@@ -0,0 +1,28 @@
variable "instance_name" {
type = string
description = "Name (identifier) of the RDS DB instance."
}
variable "instance_class" {
type = string
description = "DB instance class."
default = "db.t3.micro"
}
variable "allocated_storage" {
type = number
description = "Allocated storage in GiB."
default = 20
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: s3
S3 bucket primitive (stack type `aws:s3:bucket`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+48
View File
@@ -0,0 +1,48 @@
{
"name": "s3",
"version": "1.0.0",
"kind": "l1",
"type": "aws:s3:bucket",
"description": "S3 bucket primitive (engine-agnostic stack type aws:s3:bucket; the Terraform adapter translates to aws_s3_bucket).",
"inputs": {
"bucket_name": {
"type": "string",
"description": "Globally-unique S3 bucket name.",
"required": true
},
"region": {
"type": "string",
"description": "AWS region the bucket is created in (provider-level; not a resource arg).",
"required": true
},
"kms_key_arn": {
"type": "string",
"description": "ARN of the CMK for SSE-KMS; if absent, uses managed key (SSE-S3).",
"required": false
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"bucket_arn": {
"type": "arn",
"description": "The S3 bucket ARN."
},
"bucket_name": {
"type": "string",
"description": "The bucket name (echoes the input)."
},
"bucket_regional_domain_name": {
"type": "string",
"description": "The bucket regional domain name (e.g. nova-bucket.s3.us-east-1.amazonaws.com)."
}
}
}
+13
View File
@@ -0,0 +1,13 @@
locals {
# SSE algorithm: KMS when a CMK ARN is supplied, else AES256 (SSE-S3).
sse_algorithm = var.kms_key_arn != null ? "aws:kms" : "AES256"
# Tags: merge caller-supplied tags with the module defaults.
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+26
View File
@@ -0,0 +1,26 @@
resource "aws_s3_bucket" "this" {
count = var.enabled ? 1 : 0
bucket = var.bucket_name
tags = local.tags
}
resource "aws_s3_bucket_versioning" "this" {
count = var.enabled ? 1 : 0
bucket = aws_s3_bucket.this[0].id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
count = var.enabled ? 1 : 0
bucket = aws_s3_bucket.this[0].id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = local.sse_algorithm
kms_master_key_id = var.kms_key_arn
}
}
}
+14
View File
@@ -0,0 +1,14 @@
output "bucket_arn" {
value = var.enabled ? aws_s3_bucket.this[0].arn : null
description = "The S3 bucket ARN."
}
output "bucket_name" {
value = var.enabled ? aws_s3_bucket.this[0].id : null
description = "The bucket name (echoes the input)."
}
output "bucket_regional_domain_name" {
value = var.enabled ? aws_s3_bucket.this[0].bucket_regional_domain_name : null
description = "The bucket regional domain name (e.g. nova-bucket.s3.us-east-1.amazonaws.com)."
}
+28
View File
@@ -0,0 +1,28 @@
variable "bucket_name" {
type = string
description = "Globally-unique S3 bucket name."
}
variable "region" {
type = string
description = "AWS region the bucket is created in (provider-level; not a resource arg)."
default = null
}
variable "kms_key_arn" {
type = string
description = "ARN of the CMK for SSE-KMS; if absent, uses managed key (SSE-S3)."
default = null
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: uptime
Uptime monitor primitive (simplified stand-in: a CloudWatch alarm; stack type `aws:uptime:monitor`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+35
View File
@@ -0,0 +1,35 @@
{
"name": "uptime",
"version": "1.0.0",
"kind": "l1",
"type": "aws:uptime:monitor",
"description": "Uptime monitor primitive (simplified stand-in: a CloudWatch alarm watching the target resource). Engine-agnostic stack type aws:uptime:monitor; the Terraform adapter translates to aws_cloudwatch_metric_alarm.",
"inputs": {
"monitor_name": {
"type": "string",
"description": "Name of the uptime monitor (CloudWatch alarm).",
"required": true
},
"target_arn": {
"type": "arn",
"description": "ARN of the target resource being monitored.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"monitor_arn": {
"type": "arn",
"description": "The CloudWatch alarm ARN (stand-in for the monitor ARN)."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+21
View File
@@ -0,0 +1,21 @@
# Uptime monitor stand-in: a CloudWatch metric alarm referencing the
# target resource ARN via dimensions. A future revision may swap this
# for a Route 53 health check or CloudWatch composite alarm.
resource "aws_cloudwatch_metric_alarm" "this" {
count = var.enabled ? 1 : 0
alarm_name = var.monitor_name
comparison_operator = "LessThanThreshold"
evaluation_periods = 2
metric_name = "RequestCount"
namespace = "AWS/ApplicationELB"
period = 60
statistic = "Sum"
threshold = 1
alarm_description = "Uptime monitor (CloudWatch alarm stand-in) for target ${var.target_arn}."
dimensions = {
LoadBalancer = var.target_arn
}
tags = local.tags
}
+4
View File
@@ -0,0 +1,4 @@
output "monitor_arn" {
value = var.enabled ? aws_cloudwatch_metric_alarm.this[0].arn : null
description = "The CloudWatch alarm ARN (stand-in for the monitor ARN)."
}
+21
View File
@@ -0,0 +1,21 @@
variable "monitor_name" {
type = string
description = "Name of the uptime monitor (CloudWatch alarm)."
}
variable "target_arn" {
type = string
description = "ARN of the target resource being monitored."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: vpc
VPC primitive (multi-resource: VPC + subnets + route table + IGW; stack type `aws:ec2:vpc`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+69
View File
@@ -0,0 +1,69 @@
{
"name": "vpc",
"version": "1.0.0",
"kind": "l1",
"type": "aws:ec2:vpc",
"description": "VPC primitive (multi-resource: VPC + subnets + route table + internet gateway). Engine-agnostic stack types aws:ec2:vpc + aws:ec2:subnet + aws:ec2:routetable + aws:ec2:igw; the Terraform adapter translates to aws_vpc/aws_subnet/aws_route_table/aws_internet_gateway.",
"inputs": {
"cidr": {
"type": "string",
"description": "VPC CIDR block, e.g. 10.0.0.0/16.",
"required": true
},
"azs": {
"type": "list",
"description": "List of availability zones, e.g. [\"us-east-1a\", \"us-east-1b\"]. One subnet is created per AZ.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"vpc_id": {
"type": "string",
"description": "The VPC id."
},
"subnet_ids": {
"type": "list",
"description": "List of subnet ids (one per AZ)."
},
"igw_id": {
"type": "string",
"description": "The internet gateway id."
}
},
"resources": [
{
"type": "aws:ec2:vpc",
"description": "The VPC itself.",
"inputs": ["cidr"],
"outputs": ["vpc_id"]
},
{
"type": "aws:ec2:subnet",
"description": "One subnet per availability zone (azs).",
"inputs": ["cidr", "az", "vpc_id"],
"outputs": ["subnet_ids"]
},
{
"type": "aws:ec2:routetable",
"description": "Route table bound to the VPC with a default route via the IGW.",
"inputs": ["vpc_id"],
"outputs": []
},
{
"type": "aws:ec2:igw",
"description": "Internet gateway attached to the VPC.",
"inputs": ["vpc_id"],
"outputs": ["igw_id"]
}
]
}
+16
View File
@@ -0,0 +1,16 @@
locals {
# Tags: merge caller-supplied tags with the module defaults.
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
# Derive /24 subnet CIDRs from the VPC /16 by slicing the 3rd octet.
# Works for /16 VPC CIDRs; for other sizes the caller should pass
# pre-computed subnet CIDRs (future input).
vpc_octets = split(".", cidrhost(var.cidr, 0))
subnet_cidrs = [for i in range(length(var.azs)) : "${local.vpc_octets[0]}.${local.vpc_octets[1]}.${i}.0/24"]
}
+57
View File
@@ -0,0 +1,57 @@
resource "aws_vpc" "this" {
count = var.enabled ? 1 : 0
cidr_block = var.cidr
tags = merge(
{
Name = "nova-vpc"
},
local.tags,
)
}
resource "aws_subnet" "this" {
count = var.enabled ? length(var.azs) : 0
vpc_id = aws_vpc.this[0].id
cidr_block = local.subnet_cidrs[count.index]
availability_zone = var.azs[count.index]
tags = merge(
{
Name = "nova-subnet-${count.index}"
},
local.tags,
)
}
resource "aws_internet_gateway" "this" {
count = var.enabled ? 1 : 0
vpc_id = aws_vpc.this[0].id
tags = merge(
{
Name = "nova-igw"
},
local.tags,
)
}
resource "aws_route_table" "this" {
count = var.enabled ? 1 : 0
vpc_id = aws_vpc.this[0].id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this[0].id
}
tags = merge(
{
Name = "nova-rt"
},
local.tags,
)
}
resource "aws_route_table_association" "this" {
count = var.enabled ? length(var.azs) : 0
subnet_id = aws_subnet.this[count.index].id
route_table_id = aws_route_table.this[0].id
}
+14
View File
@@ -0,0 +1,14 @@
output "vpc_id" {
value = var.enabled ? aws_vpc.this[0].id : null
description = "The VPC id."
}
output "subnet_ids" {
value = var.enabled ? aws_subnet.this[*].id : []
description = "List of subnet ids (one per AZ)."
}
output "igw_id" {
value = var.enabled ? aws_internet_gateway.this[0].id : null
description = "The internet gateway id."
}
+21
View File
@@ -0,0 +1,21 @@
variable "cidr" {
type = string
description = "VPC CIDR block, e.g. 10.0.0.0/16."
}
variable "azs" {
type = list(string)
description = "List of availability zones, e.g. [\"us-east-1a\", \"us-east-1b\"]. One subnet is created per AZ."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+3
View File
@@ -0,0 +1,3 @@
# L1: waf
WAFv2 web ACL primitive (stack type `aws:waf:web_acl`). See `interface.json` for the full contract and `README-TEMPLATE.md` for the canonical section layout.
+34
View File
@@ -0,0 +1,34 @@
{
"name": "waf",
"version": "1.0.0",
"kind": "l1",
"type": "aws:waf:web_acl",
"description": "WAFv2 web ACL primitive (engine-agnostic stack type aws:waf:web_acl; the Terraform adapter translates to aws_wafv2_web_acl).",
"inputs": {
"acl_name": {
"type": "string",
"description": "Name of the WAF web ACL.",
"required": true
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Feature flag: enable/disable this module. Set to false to skip resource creation."
},
"tags": {
"type": "map",
"default": {},
"description": "Additional tags to merge with the module defaults."
}
},
"outputs": {
"acl_arn": {
"type": "arn",
"description": "The WAF web ACL ARN."
},
"acl_id": {
"type": "string",
"description": "The WAF web ACL id."
}
}
}
+9
View File
@@ -0,0 +1,9 @@
locals {
tags = merge(
{
"nova:owner" = "nova"
"nova:environment" = "dev"
},
var.tags,
)
}
+17
View File
@@ -0,0 +1,17 @@
resource "aws_wafv2_web_acl" "this" {
count = var.enabled ? 1 : 0
name = var.acl_name
description = "WAF web ACL managed by nova L1 waf primitive."
scope = "REGIONAL"
default_action {
allow {}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = var.acl_name
sampled_requests_enabled = true
}
tags = local.tags
}
+9
View File
@@ -0,0 +1,9 @@
output "acl_arn" {
value = var.enabled ? aws_wafv2_web_acl.this[0].arn : null
description = "The WAF web ACL ARN."
}
output "acl_id" {
value = var.enabled ? aws_wafv2_web_acl.this[0].id : null
description = "The WAF web ACL id."
}
+16
View File
@@ -0,0 +1,16 @@
variable "acl_name" {
type = string
description = "Name of the WAF web ACL."
}
variable "tags" {
type = map(string)
description = "Additional tags to merge with the module defaults."
default = {}
}
variable "enabled" {
type = bool
description = "Feature flag: enable/disable this module. Set to false to skip resource creation."
default = true
}
+10
View File
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.9, < 1.10"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
+85
View File
@@ -0,0 +1,85 @@
"""Tests for modules/registry.json — REQ-10."""
import json
from pathlib import Path
import pytest
from tests.conftest import ROOT
@pytest.fixture
def loaded_registry():
with open(ROOT / "modules" / "registry.json") as fh:
return json.load(fh)
class TestRegistryShape:
def test_has_15_entries(self, loaded_registry):
assert len(loaded_registry) == 15
def test_13_l1_2_l2(self, loaded_registry):
l1 = [k for k, v in loaded_registry.items()
if list(v.values())[0]["kind"] == "l1"]
l2 = [k for k, v in loaded_registry.items()
if list(v.values())[0]["kind"] == "l2"]
assert len(l1) == 13
assert len(l2) == 2
def test_every_entry_has_required_fields(self, loaded_registry):
for name, versions in loaded_registry.items():
for ver, entry in versions.items():
for f in ("interface", "terraform_dir", "published_at",
"deprecated", "kind"):
assert f in entry, f"{name}@{ver} missing {f}"
def test_l2_has_terraform_dir(self, loaded_registry):
"""D-013 deviation: L2 entries include terraform_dir."""
for name, versions in loaded_registry.items():
entry = list(versions.values())[0]
if entry["kind"] == "l2":
assert "terraform_dir" in entry, f"L2 {name} missing terraform_dir (D-013)"
def test_interface_paths_resolve(self, loaded_registry):
for name, versions in loaded_registry.items():
for ver, entry in versions.items():
iface_path = ROOT / entry["interface"]
if entry["kind"] == "l2":
if not iface_path.exists():
continue # L2 interfaces land in P4
assert iface_path.exists(), f"{name}@{ver} interface path missing: {iface_path}"
def test_terraform_dir_paths_resolve(self, loaded_registry):
for name, versions in loaded_registry.items():
for ver, entry in versions.items():
tf_path = ROOT / entry["terraform_dir"] / "main.tf"
if entry["kind"] == "l2":
if not tf_path.exists():
continue # L2 terraform lands in P4
assert tf_path.exists(), f"{name}@{ver} terraform/main.tf missing: {tf_path}"
def test_l1_modules_present(self, loaded_registry):
expected = {"s3", "vpc", "ecs-cluster", "ecs-service", "iam-role",
"alb", "ecr", "cloudfront", "waf", "rds", "kms-key",
"dynamodb", "uptime"}
assert expected.issubset(loaded_registry.keys())
def test_l2_modules_present(self, loaded_registry):
expected = {"microservice", "static-assets"}
assert expected.issubset(loaded_registry.keys())
class TestInterfaceShape:
def test_l1_interfaces_conform(self, loaded_registry):
"""D-014: L1 interface.json has {name,version,kind,type,description,inputs,outputs} + optional resources[]. No nfrs/intra_refs."""
for name, versions in loaded_registry.items():
entry = list(versions.values())[0]
if entry["kind"] != "l1":
continue
with open(ROOT / entry["interface"]) as fh:
iface = json.load(fh)
for f in ("name", "version", "kind", "type", "description", "inputs", "outputs"):
assert f in iface, f"{name} interface missing {f}"
assert "nfrs" not in iface, f"{name} has nfrs (D-014 drop)"
assert "intra_refs" not in iface, f"{name} has intra_refs (D-014 drop)"
assert iface["type"].startswith("aws:"), f"{name} type not stack-typed"