|
| 1 | +--- |
| 2 | +name: developing-components |
| 3 | +description: |
| 4 | + Use when creating new Terraform/OpenTofu components or modifying existing ones. Covers required files, catalog |
| 5 | + defaults, stack configuration, and naming conventions. |
| 6 | +--- |
| 7 | + |
| 8 | +# Developing Components |
| 9 | + |
| 10 | +Components are opinionated Terraform root modules that implement a specific AWS resource with predefined conventions. |
| 11 | + |
| 12 | +## Before Creating a New Component |
| 13 | + |
| 14 | +**Always check for existing components first.** Creating new components adds maintenance burden. |
| 15 | + |
| 16 | +### 1. Check Local Generic Components |
| 17 | + |
| 18 | +Many use cases can be solved by configuring existing generic components via catalog: |
| 19 | + |
| 20 | +- **`iam-role`** - Any IAM role. Create `stacks/catalog/iam-role/my-role.yaml` instead of a new component. |
| 21 | +- **`s3-bucket`** - Any S3 bucket with standard configurations. |
| 22 | +- **`lambda`** - Lambda functions with common patterns. |
| 23 | + |
| 24 | +### 2. Check Cloud Posse Component Library |
| 25 | + |
| 26 | +Browse https://docs.cloudposse.com/components/library/ for pre-built components. Common ones: |
| 27 | + |
| 28 | +- `eks/cluster`, `eks/alb-controller` - Kubernetes |
| 29 | +- `aurora-postgres`, `rds` - Databases |
| 30 | +- `elasticache-redis` - Caching |
| 31 | +- `ecs`, `ecs-service` - Container workloads |
| 32 | +- `cloudwatch-logs`, `sns-topic`, `sqs-queue` - AWS services |
| 33 | + |
| 34 | +To vendor a Cloud Posse component: |
| 35 | + |
| 36 | +```bash |
| 37 | +# Add to component.yaml in the component directory, then: |
| 38 | +atmos vendor pull -c <component-name> |
| 39 | +``` |
| 40 | + |
| 41 | +### 3. When to Create a Custom Component |
| 42 | + |
| 43 | +Create a new component when: |
| 44 | + |
| 45 | +- **Tightly coupled resources** - The resources share the same lifecycle and must be created/destroyed together. For |
| 46 | + example, an application-specific ECS service with its ALB target group, CloudWatch alarms, and autoscaling policies. |
| 47 | +- **Unique to your infrastructure** - The configuration is specific to your organization and wouldn't benefit from Cloud |
| 48 | + Posse's generic abstractions. |
| 49 | +- **Single account/region deployment** - All resources deploy to one AWS account and region. If resources span accounts |
| 50 | + or regions, split into separate components. |
| 51 | + |
| 52 | +**Teralithic components** (all-in-one) are appropriate when: |
| 53 | + |
| 54 | +- Resources are always deployed together and never independently |
| 55 | +- Splitting would create artificial boundaries requiring complex cross-component wiring |
| 56 | +- The blast radius is acceptable (all resources affected by any change) |
| 57 | + |
| 58 | +**Avoid** custom components when: |
| 59 | + |
| 60 | +- You're wrapping a single AWS resource (use generic components instead) |
| 61 | +- The resources have different lifecycles (e.g., database vs. application) |
| 62 | +- You might reuse this pattern elsewhere (vendor or create a generic component) |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## New Component Structure |
| 67 | + |
| 68 | +Every new component requires three parts: |
| 69 | + |
| 70 | +## 1. Root Module (`components/terraform/<component-name>/`) |
| 71 | + |
| 72 | +Required files (copy from an existing component like `ecs`): |
| 73 | + |
| 74 | +- **`context.tf`** - Always identical across all components. Copy from any existing component. Provides Cloud Posse's |
| 75 | + null-label context for consistent naming. |
| 76 | +- **`providers.tf`** - Standard provider configuration. Copy from an existing component. Includes the |
| 77 | + `account_map_enabled` variable and dummy `iam_roles` module for compatibility. |
| 78 | +- **`versions.tf`** - OpenTofu/Terraform and provider version constraints |
| 79 | +- **`variables.tf`** - Component-specific input variables |
| 80 | +- **`main.tf`** - Main resource definitions |
| 81 | +- **`outputs.tf`** - Output values |
| 82 | + |
| 83 | +**Note on remote-state.tf**: Avoid creating `remote-state.tf` files for new components. Instead, use Atmos functions |
| 84 | +(`!terraform.state`) in stack YAML to pass values from other components. This keeps components simpler and moves |
| 85 | +cross-component wiring to the stack configuration layer. |
| 86 | + |
| 87 | +## 2. Catalog Defaults (`stacks/catalog/<component-name>/defaults.yaml`) |
| 88 | + |
| 89 | +Define organization-wide default values for the component, including dependency lookups: |
| 90 | + |
| 91 | +```yaml |
| 92 | +components: |
| 93 | + terraform: |
| 94 | + <component-name>/defaults: |
| 95 | + metadata: |
| 96 | + component: <component-name> |
| 97 | + type: abstract # Makes this a base configuration, not directly deployable |
| 98 | + vars: |
| 99 | + enabled: true |
| 100 | + name: <component-name> |
| 101 | + # Static defaults |
| 102 | + some_setting: "default-value" |
| 103 | + # Dynamic lookups - resolved at plan time based on current stack |
| 104 | + vpc_id: !terraform.state vpc vpc_id |
| 105 | + subnet_ids: !terraform.state vpc private_subnet_ids |
| 106 | +``` |
| 107 | +
|
| 108 | +## 3. Stack Configuration (`stacks/orgs/acme/<tenant>/<stage>/<region>/<layer>.yaml`) |
| 109 | + |
| 110 | +Import the catalog - often no component block is needed if catalog defaults are complete: |
| 111 | + |
| 112 | +```yaml |
| 113 | +import: |
| 114 | + - orgs/acme/plat/dev/_defaults |
| 115 | + - mixins/region/us-east-1 |
| 116 | + - catalog/<component-name>/defaults |
| 117 | +``` |
| 118 | + |
| 119 | +If overrides are needed: |
| 120 | + |
| 121 | +```yaml |
| 122 | +import: |
| 123 | + - catalog/<component-name>/defaults |
| 124 | +
|
| 125 | +components: |
| 126 | + terraform: |
| 127 | + <component-name>: |
| 128 | + vars: |
| 129 | + # Account/region-specific overrides only |
| 130 | + some_setting: "override-value" |
| 131 | +``` |
| 132 | + |
| 133 | +## Naming Conventions |
| 134 | + |
| 135 | +- Use descriptive, specific names that indicate the component's purpose |
| 136 | +- Prefix with the service/platform when specific (e.g., `ecs-adot-collector` not just `adot-collector`) |
| 137 | +- Use slashes for component hierarchies in catalogs (e.g., `iam-role/grafana-cloudwatch-access`, |
| 138 | + `grafana/datasource/cloudwatch`) |
| 139 | + |
| 140 | +## Stack File Organization |
| 141 | + |
| 142 | +Stack files in `stacks/orgs/acme/` mirror the AWS account structure: |
| 143 | + |
| 144 | +- `orgs/acme/core/` - Core accounts (root, audit, security, identity, network, dns, auto, artifacts) |
| 145 | +- `orgs/acme/plat/` - Platform accounts (sandbox, dev, staging, prod) |
| 146 | + |
| 147 | +Within each stage, organized by region: |
| 148 | + |
| 149 | +- `global-region/` - Global (us-east-1) resources like IAM |
| 150 | +- `us-east-2/` - Regional resources |
| 151 | + |
| 152 | +Regional stack files are typically split by layer: |
| 153 | + |
| 154 | +- `foundation.yaml` - VPC, networking, base infrastructure |
| 155 | +- `platform.yaml` - Shared platform services (ECS clusters, databases) |
| 156 | +- `app.yaml` - Application-specific resources |
0 commit comments