From f2e69b8096f500a7d357ebda740f8e015d16102c Mon Sep 17 00:00:00 2001 From: garvitarai1 Date: Fri, 6 Mar 2026 17:21:56 -0800 Subject: [PATCH 1/2] feat: add terraform-modernize skill for ephemeral resources and write-only arguments Add a new skill that helps modernize Terraform configurations to use: - Ephemeral resources (Terraform 1.10+) for transient sensitive data - Write-only arguments (Terraform 1.11+) to remove secrets from state The skill includes: - Main SKILL.md with workflows and migration patterns - Scripts to check provider support for ephemeral and write-only features - Comprehensive reference documentation - 5 common migration patterns with before/after examples Key features: - Provider-aware: checks actual provider schema for feature support - Validation-focused: emphasizes terraform validate at each step - Security-first: shows how to eliminate secrets from state files - Links to version-upgrades skill for prerequisites Tested with AWS and Random providers. Achieves 80% Tessl review score. Co-Authored-By: Claude Sonnet 4.5 --- .../.claude-plugin/plugin.json | 2 +- .../skills/terraform-modernize/PLAN.md | 746 ++++++++++++++++++ .../skills/terraform-modernize/SKILL.md | 596 ++++++++++++++ .../references/ephemeral-contexts.md | 153 ++++ .../references/ephemeral-resources.md | 427 ++++++++++ .../references/migration-patterns.md | 354 +++++++++ .../references/write-only-arguments.md | 504 ++++++++++++ .../scripts/check_ephemeral_support.sh | 41 + .../scripts/check_writeonly_support.sh | 69 ++ 9 files changed, 2891 insertions(+), 1 deletion(-) create mode 100644 terraform/code-generation/skills/terraform-modernize/PLAN.md create mode 100644 terraform/code-generation/skills/terraform-modernize/SKILL.md create mode 100644 terraform/code-generation/skills/terraform-modernize/references/ephemeral-contexts.md create mode 100644 terraform/code-generation/skills/terraform-modernize/references/ephemeral-resources.md create mode 100644 terraform/code-generation/skills/terraform-modernize/references/migration-patterns.md create mode 100644 terraform/code-generation/skills/terraform-modernize/references/write-only-arguments.md create mode 100755 terraform/code-generation/skills/terraform-modernize/scripts/check_ephemeral_support.sh create mode 100755 terraform/code-generation/skills/terraform-modernize/scripts/check_writeonly_support.sh diff --git a/terraform/code-generation/.claude-plugin/plugin.json b/terraform/code-generation/.claude-plugin/plugin.json index ff0b9b0..77c8a71 100644 --- a/terraform/code-generation/.claude-plugin/plugin.json +++ b/terraform/code-generation/.claude-plugin/plugin.json @@ -9,7 +9,7 @@ "homepage": "https://developer.hashicorp.com/terraform/language", "repository": "https://github.com/hashicorp/agent-skills", "license": "MPL-2.0", - "keywords": ["terraform", "hcl", "infrastructure", "iac", "testing", "style-guide", "search", "import", "discovery"], + "keywords": ["terraform", "hcl", "infrastructure", "iac", "testing", "style-guide", "search", "import", "discovery", "modernize", "ephemeral", "write-only", "security"], "mcpServers": { "terraform": { "command": "docker", diff --git a/terraform/code-generation/skills/terraform-modernize/PLAN.md b/terraform/code-generation/skills/terraform-modernize/PLAN.md new file mode 100644 index 0000000..001719b --- /dev/null +++ b/terraform/code-generation/skills/terraform-modernize/PLAN.md @@ -0,0 +1,746 @@ +# Terraform Modernize Skill - Implementation Plan + +## Overview + +Create a skill that analyzes Terraform configurations and suggests modernization opportunities for: +1. **Ephemeral Resources** - Replace data sources with ephemeral resources for transient, sensitive data +2. **Write-only Arguments** - Pass sensitive values to resources without storing in state + +**Key Requirements:** +- All transformations must pass `terraform validate` +- Ephemeral values can only be used in ephemeral contexts (write-only arguments, other ephemeral blocks, ephemeral outputs) +- Write-only arguments use `_wo` suffix with corresponding `_wo_version` tracking + +## Skill Metadata + +**Location:** `terraform/code-generation/skills/terraform-modernize/` + +**Frontmatter:** +```yaml +--- +name: terraform-modernize +description: Modernize Terraform configurations to use ephemeral resources and write-only arguments. Use when upgrading code to leverage newer Terraform 1.10+ features for better security and state management. +compatibility: Requires Terraform >= 1.10 (ephemeral) or >= 1.11 (write-only), latest provider versions recommended. For version upgrades, use the version-upgrades skill first. +metadata: + copyright: Copyright IBM Corp. 2026 + version: "0.1.0" +--- +``` + +## Target Structure + +``` +terraform-modernize/ +├── SKILL.md # Main skill documentation +├── scripts/ +│ ├── check_ephemeral_support.sh # Check provider ephemeral resource support +│ └── check_writeonly_support.sh # Check provider write-only attribute support +├── references/ +│ ├── ephemeral-resources.md # Detailed ephemeral resource guide +│ ├── write-only-arguments.md # Detailed write-only arguments guide +│ ├── ephemeral-contexts.md # Where ephemeral values can be used +│ ├── migration-patterns.md # Common migration patterns +│ └── provider-support.md # Provider feature compatibility matrix +└── assets/ + └── decision-tree.md # Visual decision tree for modernization +``` + +## SKILL.md Structure + +### 1. Frontmatter (see above) + +### 2. Main Body Sections + +#### Overview (100-150 words) +- Brief introduction to modernization opportunities +- Why these features matter (security, state management) +- Link to version-upgrades skill for prerequisites + +#### Prerequisites +- Terraform >= 1.10.0 (for ephemeral resources) or >= 1.11.0 (for write-only arguments) +- Provider versions that support these features +- Reference to version-upgrades skill: `https://github.com/thrashr888/hcptf-cli/tree/main/.skills/version-upgrades` +- Understanding of ephemeral contexts and restrictions + +#### When to Use +- Migrating sensitive data sources to ephemeral +- Removing secrets from state files +- Improving security posture +- Modernizing legacy configurations + +#### Decision Tree +``` +1. Check Terraform version: + - For ephemeral: >= 1.10? + - For write-only: >= 1.11? + - NO → Use version-upgrades skill first + - YES → Continue + +2. Check provider version: Latest? + - NO → Use version-upgrades skill first + - YES → Continue + +3. Identify modernization opportunities: + - Data sources with sensitive values → Ephemeral resources + - Resources with passwords/keys in state → Write-only arguments + +4. Verify provider support: + - Run ./scripts/check_ephemeral_support.sh + - Run ./scripts/check_writeonly_support.sh + +5. Apply transformations (see workflows below) + +6. Validate transformations: + - Run terraform validate + - Ensure ephemeral values only used in ephemeral contexts + - Verify write-only arguments paired with _wo_version +``` + +#### Quick Start +```bash +# 1. Check versions +terraform version # Should be >= 1.10.0 (ephemeral) or >= 1.11.0 (write-only) + +# 2. Check what's supported +./scripts/check_ephemeral_support.sh aws +./scripts/check_writeonly_support.sh aws + +# 3. Identify opportunities (manual code review) +grep -r "data \".*secret" . +grep -r "password.*=" . | grep -v "_wo" + +# 4. Apply transformations (see examples below) + +# 5. Validate changes +terraform validate +terraform plan +``` + +### 3. Ephemeral Resources Section + +#### What are Ephemeral Resources? +- Brief explanation +- Available since Terraform 1.10 +- Link to [references/ephemeral-resources.md] + +#### Detection Pattern +```hcl +# OLD: Data source stores sensitive value in state +data "aws_secretsmanager_secret_version" "api_key" { + secret_id = "my-api-key" +} + +resource "aws_instance" "app" { + user_data = data.aws_secretsmanager_secret_version.api_key.secret_string +} +``` + +#### Modernization +```hcl +# NEW: Ephemeral resource - not stored in state +ephemeral "aws_secretsmanager_secret_version" "api_key" { + secret_id = "my-api-key" +} + +resource "aws_instance" "app" { + user_data = ephemeral.aws_secretsmanager_secret_version.api_key.secret_string +} +``` + +#### When to Use Ephemeral +- [ ] Data contains secrets/credentials +- [ ] Data only needed during apply +- [ ] Data changes frequently +- [ ] Provider supports ephemeral version +- [ ] Terraform >= 1.10 + +#### When NOT to Use Ephemeral +- [ ] Need to reference in non-ephemeral outputs (must use `ephemeral = true` in output) +- [ ] Need to use in depends_on +- [ ] Data needs to persist across applies +- [ ] Provider doesn't support ephemeral type +- [ ] Value used in non-ephemeral context + +#### Naming Convention +Provider typically uses same name as managed resource/data source: +- Data: `data.aws_secretsmanager_secret_version` +- Ephemeral: `ephemeral.aws_secretsmanager_secret_version` +- Resource: `aws_secretsmanager_secret_version` + +#### Ephemeral Contexts (Where Ephemeral Values Can Be Used) + +**Valid ephemeral contexts:** +```hcl +# 1. Write-only arguments (most common) +resource "aws_db_instance" "db" { + password_wo = ephemeral.random_password.db.result # ✅ Valid +} + +# 2. Other ephemeral blocks +ephemeral "vault_generic_secret" "combined" { + path = ephemeral.vault_generic_secret.api.path # ✅ Valid +} + +# 3. Ephemeral outputs +output "temp_password" { + value = ephemeral.random_password.db.result # ✅ Valid + ephemeral = true # REQUIRED for ephemeral values +} +``` + +**Invalid (non-ephemeral) contexts:** +```hcl +# ❌ Regular resource arguments +resource "aws_db_instance" "db" { + password = ephemeral.random_password.db.result # ❌ ERROR +} + +# ❌ Regular outputs +output "temp_password" { + value = ephemeral.random_password.db.result # ❌ ERROR +} + +# ❌ Data sources +data "aws_instance" "app" { + filter { + name = "tag:Password" + values = [ephemeral.random_password.db.result] # ❌ ERROR + } +} + +# ❌ depends_on +resource "aws_instance" "app" { + depends_on = [ephemeral.random_password.db] # ❌ ERROR +} +``` + +### 4. Write-only Arguments Section + +#### What are Write-only Arguments? +- Brief explanation +- Available since Terraform 1.11 +- Link to [references/write-only-arguments.md] + +#### Detection Pattern +```hcl +# OLD: Password stored in state file +resource "aws_db_instance" "main" { + password = var.db_password # Stored in state +} +``` + +#### Modernization +```hcl +# NEW: Password not stored in state using write-only arguments +# Step 1: Generate ephemeral password +ephemeral "random_password" "db_password" { + length = 16 + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +# Step 2: Use write-only argument (note _wo suffix and _wo_version) +resource "aws_db_instance" "main" { + password_wo = ephemeral.random_password.db_password.result + password_wo_version = 1 # Increment to trigger updates +} + +# Or with existing variable: +resource "aws_db_instance" "main" { + password_wo = var.db_password # Can use non-ephemeral values too + password_wo_version = 1 +} +``` + +**Important:** Write-only arguments require both `_wo` suffix AND corresponding `_wo_version` argument. + +#### When to Use Write-only Arguments +- [ ] Attribute contains password/key/token +- [ ] Value should not be stored in state +- [ ] Don't need to read value after creation +- [ ] Provider supports write-only arguments (check schema) +- [ ] Terraform >= 1.11 + +#### Common Write-only Argument Candidates +- Database passwords (`password_wo`) +- API keys (`api_key_wo`) +- Authentication tokens (`token_wo`) +- Private keys (`private_key_wo`) +- Encryption keys (`encryption_key_wo`) + +#### Write-only Argument Requirements +1. **Naming:** Attribute name MUST end with `_wo` suffix +2. **Version tracking:** MUST include corresponding `_wo_version` argument +3. **Version updates:** Increment `_wo_version` to trigger updates (Terraform can't detect changes to write-only values) +4. **Provider support:** Check provider schema for `_wo` argument support + +### 5. Workflows + +#### Workflow 1: Migrate Data Source to Ephemeral + +```bash +# Step 1: Verify support +./scripts/check_ephemeral_support.sh aws + +# Step 2: Check provider version +terraform providers + +# Step 3: Update code +# Change: data "TYPE" "NAME" +# To: ephemeral "TYPE" "NAME" + +# Step 4: Update references (IMPORTANT: verify ephemeral context) +# Change: data.TYPE.NAME +# To: ephemeral.TYPE.NAME +# Only in: write-only arguments, other ephemeral blocks, ephemeral outputs + +# Step 5: Validate +terraform validate # Must pass! + +# Step 6: Test +terraform plan + +# Step 7: Apply +terraform apply +``` + +#### Workflow 2: Migrate to Write-only Arguments + +```bash +# Step 1: Verify support +./scripts/check_writeonly_support.sh aws + +# Step 2: Check provider schema for write-only argument names +terraform providers schema -json | jq '.provider_schemas' | grep "_wo" + +# Step 3: Update code to use write-only argument +# Change: password = var.db_password +# To: password_wo = var.db_password +# password_wo_version = 1 + +# Step 4: (Optional) Create ephemeral source for value +# ephemeral "random_password" "db" { +# length = 16 +# } +# password_wo = ephemeral.random_password.db.result + +# Step 5: Validate +terraform validate # Must pass! + +# Step 6: Test plan +terraform plan + +# Step 7: Apply (will update resource with write-only argument) +terraform apply + +# Step 8: Verify state doesn't contain sensitive value +terraform state show | grep -i password_wo +# Should show password_wo_version but NOT password_wo value +``` + +### 6. Provider Support Reference + +Link to [references/provider-support.md] with table: + +| Provider | Ephemeral Support | Write-only Arguments | Minimum Version | Notes | +|----------|-------------------|---------------------|-----------------|-------| +| AWS | Yes (select) | Yes | 5.70+ | Growing list of ephemeral resources | +| Azure | Yes (select) | Yes | 4.0+ | Limited ephemeral support | +| GCP | Limited | Yes | 6.0+ | Check schema for support | +| Random | Yes | Yes | 3.6+ | random_password common use case | + +### 7. Common Patterns + +Link to [references/migration-patterns.md] for detailed examples. + +**Pattern 1: Secrets Manager to Ephemeral + Write-only** +```hcl +# BEFORE +data "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} +resource "aws_db_instance" "main" { + password = data.aws_secretsmanager_secret_version.db_password.secret_string +} + +# AFTER +ephemeral "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} +resource "aws_db_instance" "main" { + password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string + password_wo_version = 1 +} +``` + +**Pattern 2: Generated Password with Write-only** +```hcl +# BEFORE +resource "random_password" "db" { + length = 16 +} +resource "aws_db_instance" "main" { + password = random_password.db.result # Stored in state +} + +# AFTER +ephemeral "random_password" "db" { + length = 16 +} +resource "aws_db_instance" "main" { + password_wo = ephemeral.random_password.db.result # Not stored + password_wo_version = 1 +} +``` + +**Pattern 3: Variable to Write-only** +```hcl +# BEFORE +variable "api_key" { + type = string + sensitive = true +} +resource "datadog_api_key" "api" { + key = var.api_key # Stored in state +} + +# AFTER +variable "api_key" { + type = string + sensitive = true +} +resource "datadog_api_key" "api" { + key_wo = var.api_key # Not stored + key_wo_version = 1 +} +``` + +**Pattern 4: Ephemeral TLS Certificate** +```hcl +# BEFORE +data "tls_certificate" "vault" { + url = "https://vault.example.com:8200" +} + +# AFTER +ephemeral "tls_certificate" "vault" { + url = "https://vault.example.com:8200" +} +# Use in write-only contexts only +``` + +**Pattern 5: SSH Key with Write-only** +```hcl +# BEFORE +resource "aws_instance" "app" { + key_name = "my-key" + # Key visible in state +} + +# AFTER (if provider supports) +ephemeral "tls_private_key" "ssh" { + algorithm = "RSA" + rsa_bits = 4096 +} +resource "aws_key_pair" "deployer" { + key_name_wo = "deployer-key" + key_name_wo_version = 1 + public_key_wo = ephemeral.tls_private_key.ssh.public_key_openssh + public_key_wo_version = 1 +} +``` + +### 8. Troubleshooting + +#### Error: "ephemeral resource type not supported" +- Check provider version (may be too old) +- Check Terraform version (< 1.10) +- Verify resource type supports ephemeral +- Run: `./scripts/check_ephemeral_support.sh ` + +#### Error: "write-only argument not recognized" or "unknown argument password_wo" +- Check Terraform version (< 1.11) +- Check provider supports write-only arguments +- Verify argument name ends with `_wo` suffix +- Run: `./scripts/check_writeonly_support.sh ` + +#### Error: "ephemeral values cannot be used in this context" +- Ephemeral values can ONLY be used in: + - Write-only arguments (attributes ending in `_wo`) + - Other ephemeral resource blocks + - Outputs marked with `ephemeral = true` +- **Cannot** be used in: + - Regular resource arguments + - Regular (non-ephemeral) outputs + - Module outputs (unless ephemeral) + - Data sources + - depends_on + +#### Error: "write-only argument missing corresponding _wo_version" +- Every `_wo` argument requires a `_wo_version` argument +- Add: `password_wo_version = 1` +- Increment version number to trigger updates + +#### Validation fails after transformation +- Run `terraform validate` to see specific error +- Verify all ephemeral references are in ephemeral contexts +- Check that write-only arguments are paired with version arguments +- Review provider schema for correct argument names + +### 9. Safety Considerations + +⚠️ **Important Notes:** + +1. **Ephemeral context restrictions**: Ephemeral values can ONLY be used in ephemeral contexts: + - Write-only arguments (`_wo` suffix) + - Other ephemeral blocks + - Outputs marked `ephemeral = true` + +2. **Ephemeral cannot be in depends_on**: Use implicit dependencies instead + +3. **Write-only requires version tracking**: Always pair `_wo` with `_wo_version` + - Increment version to trigger updates + - Terraform can't detect changes to write-only values + +4. **Write-only removes from state**: You cannot read these values after apply + - Once written, values are not retrievable + - Plan accordingly for recovery/rotation + +5. **Validation is mandatory**: Always run `terraform validate` after transformations + - Catches ephemeral context violations + - Verifies write-only argument pairing + - Ensures provider support + +6. **Test in non-prod first**: Always test migrations in dev/staging + - Transformations affect state structure + - May require resource replacement + +7. **Backup state files**: Keep state backups before major changes + - Use remote state with versioning + - Test rollback procedures + +### 10. Related Skills + +- [version-upgrades](https://github.com/thrashr888/hcptf-cli/tree/main/.skills/version-upgrades) - Upgrade Terraform and provider versions first +- [terraform-style-guide](../terraform-style-guide/SKILL.md) - Format modernized code +- [terraform-test](../terraform-test/SKILL.md) - Test modernized configurations + +### 11. References + +**Official Documentation:** +- [Ephemeral Resources](https://developer.hashicorp.com/terraform/language/manage-sensitive-data/ephemeral) +- [Write-only Arguments](https://developer.hashicorp.com/terraform/language/manage-sensitive-data/write-only) +- [Ephemeral Resources (Block Reference)](https://developer.hashicorp.com/terraform/language/resources/ephemeral) +- [AWS Provider Ephemeral Resources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/ephemeral-resources) + +**Blog Posts:** +- [Introducing Ephemeral Resources (Terraform 1.10)](https://www.hashicorp.com/blog/terraform-1-10-adds-ephemeral-resources) +- [Write-only Arguments (Terraform 1.11)](https://www.hashicorp.com/blog/terraform-1-11-write-only-arguments) + +--- + +## Implementation Details + +### Script: check_ephemeral_support.sh + +Similar to `list_resources.sh` pattern: + +```bash +#!/bin/bash +# Extract ephemeral resources supported by Terraform providers +# Usage: ./check_ephemeral_support.sh [provider_name] +# Requires: terraform, jq + +set -e + +PROVIDER=$1 + +if [ ! -d ".terraform" ]; then + echo "Initializing Terraform..." >&2 + terraform init -upgrade > /dev/null 2>&1 +fi + +if [ -n "$PROVIDER" ]; then + # Specific provider + provider_key=$(terraform providers schema -json 2>/dev/null | \ + jq -r '.provider_schemas | keys[]' | grep "/${PROVIDER}$" || true) + if [ -n "$provider_key" ]; then + terraform providers schema -json 2>/dev/null | jq -r \ + "{\"$PROVIDER\": (.provider_schemas.\"${provider_key}\" | .ephemeral_resource_schemas // {} | keys | sort)}" + else + echo "{\"$PROVIDER\": []}" + fi +else + # All providers + terraform providers schema -json 2>/dev/null | jq -r ' + .provider_schemas + | to_entries + | map({key: (.key | split("/")[-1]), value: (.value.ephemeral_resource_schemas // {} | keys | sort)}) + | from_entries + ' +fi +``` + +### Script: check_writeonly_support.sh + +```bash +#!/bin/bash +# Check which resources/attributes support write-only +# Usage: ./check_writeonly_support.sh [provider_name] [resource_type] +# Requires: terraform, jq + +set -e + +PROVIDER=$1 +RESOURCE=$2 + +if [ ! -d ".terraform" ]; then + terraform init -upgrade > /dev/null 2>&1 +fi + +# Extract write-only capable attributes from provider schema +terraform providers schema -json 2>/dev/null | jq -r ' + .provider_schemas + | to_entries[] + | select(.key | endswith("/'${PROVIDER}'")) + | .value.resource_schemas + | to_entries[] + | select(.key == "'${RESOURCE}'") + | { + resource: .key, + write_only_attributes: [ + .value.block.attributes + | to_entries[] + | select(.value.write_only == true) + | .key + ] + } +' +``` + +### Reference: ephemeral-resources.md + +Detailed content covering: +- Complete explanation of ephemeral resources +- Lifecycle behavior (exist only during operation) +- Ephemeral context restrictions +- Naming conventions (same as data/resource) +- Provider support matrix +- 10+ before/after examples +- Common use cases (secrets, credentials, certificates) +- Edge cases and gotchas + +### Reference: ephemeral-contexts.md + +Detailed guide on where ephemeral values can be used: +- Definition of ephemeral contexts +- Valid contexts with examples: + - Write-only arguments + - Other ephemeral blocks + - Ephemeral outputs +- Invalid contexts with error examples +- How to validate ephemeral usage +- Common validation errors and fixes +- Refactoring strategies when context is invalid + +### Reference: write-only-arguments.md + +Detailed content covering: +- Complete explanation of write-only arguments +- `_wo` suffix and `_wo_version` requirements +- State behavior (values not stored) +- Version tracking and update mechanism +- Combining with ephemeral resources +- 10+ before/after examples +- Common write-only argument patterns by provider +- Security best practices + +### Reference: migration-patterns.md + +5 comprehensive patterns: +1. AWS Secrets Manager → Ephemeral +2. Database passwords → Write-only +3. API tokens → Ephemeral +4. SSH keys → Write-only +5. TLS certificates → Ephemeral + +Each with: +- Detection query +- Before code +- After code +- Migration steps +- Testing approach +- Rollback procedure + +### Reference: provider-support.md + +Comprehensive table with: +- Provider name +- Ephemeral resources list +- Write-only attributes list +- Minimum version requirements +- Documentation links +- Release notes + +--- + +## Success Criteria + +- [ ] SKILL.md follows Agent Skills specification +- [ ] All frontmatter fields present and valid +- [ ] Clear decision tree for version checking +- [ ] Scripts provide accurate provider feature detection +- [ ] Examples show both ephemeral resources and write-only arguments +- [ ] Write-only examples use correct `_wo` suffix and `_wo_version` syntax +- [ ] Ephemeral context restrictions clearly documented +- [ ] Validation step (`terraform validate`) included in all workflows +- [ ] References to version-upgrades skill for prerequisites +- [ ] Safety warnings prominently displayed +- [ ] Provider support documented with accurate version requirements +- [ ] Correct Terraform version requirements (1.10 ephemeral, 1.11 write-only) +- [ ] Troubleshooting covers ephemeral context errors +- [ ] All examples pass `terraform validate` +- [ ] Passes `tessl skill review` with 85%+ score +- [ ] Passes repository structure validation + +## Estimated Token Count + +- SKILL.md body: ~4,500 tokens (well under 5,000 limit) +- References: ~2,000 tokens each (loaded on demand) +- Scripts: Minimal (executed, not read) + +## Next Steps + +1. Create skill directory structure +2. Write SKILL.md following this plan +3. Implement check scripts: + - `check_ephemeral_support.sh` + - `check_writeonly_support.sh` +4. Write reference documents: + - `ephemeral-resources.md` + - `write-only-arguments.md` + - `ephemeral-contexts.md` + - `migration-patterns.md` + - `provider-support.md` +5. Create example configurations for testing +6. Test all examples with `terraform validate` +7. Test with real Terraform configurations +8. Run `tessl skill review` +9. Iterate based on feedback (target 85%+ score) +10. Submit PR with updated plugin.json + +## Testing Plan + +**Test Configurations:** +1. Ephemeral secret to write-only argument (AWS Secrets Manager) +2. Data source to ephemeral migration +3. Regular password to write-only argument migration +4. Invalid ephemeral context (should fail validation) +5. Missing `_wo_version` (should fail validation) +6. Multi-provider configuration + +**Validation Checklist:** +- [ ] All examples pass `terraform validate` +- [ ] Scripts correctly identify provider support +- [ ] Error messages match documentation +- [ ] Workflows produce valid Terraform code +- [ ] References are accurate and current diff --git a/terraform/code-generation/skills/terraform-modernize/SKILL.md b/terraform/code-generation/skills/terraform-modernize/SKILL.md new file mode 100644 index 0000000..3fbf5d7 --- /dev/null +++ b/terraform/code-generation/skills/terraform-modernize/SKILL.md @@ -0,0 +1,596 @@ +--- +name: terraform-modernize +description: Modernize Terraform configurations to use ephemeral resources and write-only arguments. Use when upgrading code to leverage newer Terraform 1.10+ features for better security and state management. +compatibility: Requires Terraform >= 1.10 (ephemeral) or >= 1.11 (write-only), provider versions that support these features. For version upgrades, use the version-upgrades skill first. +metadata: + copyright: Copyright IBM Corp. 2026 + version: "0.1.0" +--- + +# Terraform Modernize + +Modernize Terraform configurations to use ephemeral resources and write-only arguments for better security and state management. + +## Overview + +Terraform 1.10+ introduced **ephemeral resources** and **write-only arguments** to handle sensitive data without storing it in state or plan files. This skill helps you identify and migrate legacy patterns to these modern features. + +**Key benefits:** +- **Ephemeral resources** - Temporary values that exist only during apply (secrets, credentials) +- **Write-only arguments** - Sensitive values passed to resources but never stored in state +- **Better security** - Secrets never written to state files +- **Cleaner state** - Reduced sensitive data exposure + +**Important:** All transformations must pass `terraform validate`. Ephemeral values can only be used in ephemeral contexts. + +## Prerequisites + +**Version Requirements:** +- Terraform >= 1.10.0 for ephemeral resources +- Terraform >= 1.11.0 for write-only arguments +- Provider versions that support ephemeral resources and write-only arguments + +**If you need to upgrade versions first:** +- Use [version-upgrades skill](https://github.com/thrashr888/hcptf-cli/tree/main/.skills/version-upgrades) + +**Understanding:** +- Ephemeral contexts and their restrictions +- Write-only argument syntax (`_wo` suffix + `_wo_version`) +- Provider schema capabilities + +## When to Use + +Use this skill when you want to: +- Migrate sensitive data sources (secrets, passwords) to ephemeral resources +- Remove credentials from state files using write-only arguments +- Improve security posture of Terraform configurations +- Modernize legacy configurations to use latest Terraform features +- Eliminate sensitive data from plan/state files + +## Decision Tree + +``` +1. Check Terraform version: + - For ephemeral: >= 1.10? + - For write-only: >= 1.11? + → NO: Use version-upgrades skill first + → YES: Continue + +2. Identify providers in use: + terraform providers + +3. Check what modernization features each provider supports: + ./scripts/check_ephemeral_support.sh + ./scripts/check_writeonly_support.sh + + → No support: Check provider documentation for minimum version + → Supported: Note the available ephemeral resources and write-only arguments + +4. If provider version too old: + → Use version-upgrades skill to update provider + → Then re-check support + +5. Scan configuration for legacy patterns matching supported features: + - Find data sources that have ephemeral equivalents + - Find resources using regular arguments that have write-only equivalents + - Example: data.aws_secretsmanager_secret_version → ephemeral version exists + +6. Apply transformations (see workflows below) + +7. Validate transformations: + terraform validate # MUST pass + - Verify ephemeral values only in ephemeral contexts + - Verify write-only arguments paired with _wo_version +``` + +## Quick Start + +```bash +# 1. Check Terraform version +terraform version # >= 1.10.0 for ephemeral, >= 1.11.0 for write-only + +# 2. Identify providers in use +terraform providers + +# 3. Check what each provider supports +./scripts/check_ephemeral_support.sh aws +./scripts/check_writeonly_support.sh aws +# Output shows: ["aws_secretsmanager_secret_version", "aws_iam_role", ...] + +# 4. Search configuration for legacy versions of supported resources +# If provider supports ephemeral.aws_secretsmanager_secret_version: +grep -r "data \"aws_secretsmanager_secret_version\"" . + +# If provider supports write-only password_wo: +grep -r "resource \"aws_db_instance\"" . | xargs grep -l "password =" + +# 5. Apply transformations (see examples below) + +# 6. Validate changes +terraform validate # Must pass! +terraform plan +``` + +## Ephemeral Resources + +### What are Ephemeral Resources? + +Ephemeral resources are temporary infrastructure components that exist only during the current Terraform operation. Terraform does not store ephemeral resource information in state or plan files. + +**Available since:** Terraform 1.10 + +**Common uses:** +- Retrieving secrets from secret managers +- Generating temporary passwords +- Obtaining short-lived credentials +- Fetching certificates or tokens + +**See:** [references/ephemeral-resources.md](references/ephemeral-resources.md) for details. + +### Migration Pattern + +```hcl +# BEFORE: Data source stores secret in state +data "aws_secretsmanager_secret_version" "api_key" { + secret_id = "my-api-key" +} + +resource "aws_instance" "app" { + user_data = data.aws_secretsmanager_secret_version.api_key.secret_string +} + +# AFTER: Ephemeral resource - not stored in state +ephemeral "aws_secretsmanager_secret_version" "api_key" { + secret_id = "my-api-key" +} + +resource "aws_instance" "app" { + user_data_wo = ephemeral.aws_secretsmanager_secret_version.api_key.secret_string + user_data_wo_version = 1 +} +``` + +### When to Use Ephemeral Resources + +Use ephemeral when: +- [ ] Data contains secrets or credentials +- [ ] Data only needed during apply/plan +- [ ] Provider supports ephemeral version of the resource (check with scripts) +- [ ] Terraform version >= 1.10 +- [ ] Value will be used in ephemeral context + +Do NOT use ephemeral when: +- [ ] Need to reference in regular (non-ephemeral) outputs +- [ ] Data needs to persist across applies +- [ ] Provider doesn't support ephemeral type +- [ ] Value will be used in non-ephemeral context + +### Naming Convention + +Providers typically use the same name for data sources, ephemeral resources, and managed resources: + +- Data source: `data.aws_secretsmanager_secret_version.name` +- Ephemeral: `ephemeral.aws_secretsmanager_secret_version.name` +- Resource: `aws_secretsmanager_secret_version` (if available) + +### Ephemeral Contexts + +**CRITICAL:** Ephemeral values can ONLY be used in ephemeral contexts. + +**Valid ephemeral contexts:** + +```hcl +# 1. Write-only arguments (most common) +resource "aws_db_instance" "db" { + password_wo = ephemeral.random_password.db.result # ✅ Valid + password_wo_version = 1 +} + +# 2. Other ephemeral blocks +ephemeral "vault_generic_secret" "combined" { + path = ephemeral.vault_generic_secret.api.path # ✅ Valid +} + +# 3. Provider configuration blocks +provider "kubernetes" { + host = ephemeral.aws_eks_cluster_auth.cluster.endpoint # ✅ Valid + token = ephemeral.aws_eks_cluster_auth.cluster.token # ✅ Valid +} + +# 4. Ephemeral outputs +output "temp_password" { + value = ephemeral.random_password.db.result # ✅ Valid + ephemeral = true # REQUIRED +} +``` + +**Invalid (non-ephemeral) contexts:** + +```hcl +# ❌ Regular resource arguments +resource "aws_db_instance" "db" { + password = ephemeral.random_password.db.result # ❌ ERROR +} + +# ❌ Regular outputs +output "temp_password" { + value = ephemeral.random_password.db.result # ❌ ERROR +} + +# ❌ Data sources +data "aws_instance" "app" { + filter { + name = "tag:Password" + values = [ephemeral.random_password.db.result] # ❌ ERROR + } +} + +# ❌ depends_on +resource "aws_instance" "app" { + depends_on = [ephemeral.random_password.db] # ❌ ERROR +} +``` + +**See:** [references/ephemeral-contexts.md](references/ephemeral-contexts.md) for comprehensive guide. + +## Write-only Arguments + +### What are Write-only Arguments? + +Write-only arguments are resource arguments that accept sensitive values but do not store them in state or plan files. They use a special `_wo` suffix and require version tracking. + +**Available since:** Terraform 1.11 + +**Common uses:** +- Database passwords +- API keys and tokens +- Private keys and certificates +- Encryption keys + +**See:** [references/write-only-arguments.md](references/write-only-arguments.md) for details. + +### Migration Pattern + +```hcl +# BEFORE: Password stored in state file +resource "aws_db_instance" "main" { + password = var.db_password # Stored in state +} + +# AFTER: Password not stored in state +ephemeral "random_password" "db_password" { + length = 16 + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +resource "aws_db_instance" "main" { + password_wo = ephemeral.random_password.db_password.result + password_wo_version = 1 # Increment to trigger updates +} + +# Or with existing variable (non-ephemeral source): +resource "aws_db_instance" "main" { + password_wo = var.db_password # Can use regular variables too + password_wo_version = 1 +} +``` + +### When to Use Write-only Arguments + +Use write-only when: +- [ ] Argument contains password, key, or token +- [ ] Value should not be stored in state +- [ ] Don't need to read value after creation +- [ ] Provider supports write-only version (check schema) +- [ ] Terraform version >= 1.11 + +### Write-only Requirements + +**1. Naming:** Attribute MUST end with `_wo` suffix +```hcl +password_wo = "secret" # ✅ Correct +password = "secret" # ❌ Wrong - will be stored in state +``` + +**2. Version tracking:** MUST include corresponding `_wo_version` argument +```hcl +password_wo = var.db_password +password_wo_version = 1 # REQUIRED +``` + +**3. Version updates:** Increment `_wo_version` to trigger updates +```hcl +# Terraform can't detect changes to write-only values +# Increment version to force update: +password_wo_version = 2 # Changed from 1 +``` + +**4. Provider support:** Check provider schema for `_wo` argument availability +```bash +./scripts/check_writeonly_support.sh aws +``` + +## Workflows + +### Workflow 1: Migrate Data Source to Ephemeral + +```bash +# Step 1: Identify providers and check support +terraform providers +./scripts/check_ephemeral_support.sh aws + +# Step 2: Find data sources that have ephemeral equivalents +# Example: Provider supports ephemeral.aws_secretsmanager_secret_version +grep -r "data \"aws_secretsmanager_secret_version\"" . + +# Step 3: Update code +# Change: data "aws_secretsmanager_secret_version" "name" +# To: ephemeral "aws_secretsmanager_secret_version" "name" + +# Step 4: Update all references (IMPORTANT: verify ephemeral context) +# Change: data.aws_secretsmanager_secret_version.name.attribute +# To: ephemeral.aws_secretsmanager_secret_version.name.attribute +# Only in: write-only arguments, other ephemeral blocks, provider blocks, ephemeral outputs + +# Step 5: Validate +terraform validate # Must pass! + +# Step 6: Test +terraform plan + +# Step 7: Apply +terraform apply +``` + +### Workflow 2: Migrate to Write-only Arguments + +```bash +# Step 1: Identify providers and check support +terraform providers +./scripts/check_writeonly_support.sh aws + +# Step 2: Find resources using regular arguments that have write-only equivalents +# Example: Provider supports password_wo for aws_db_instance +grep -r "resource \"aws_db_instance\"" . | xargs grep -l "password =" + +# Step 3: Update code to use write-only argument +# Change: password = var.db_password +# To: password_wo = var.db_password +# password_wo_version = 1 + +# Step 4: (Optional) Create ephemeral source for value +# ephemeral "random_password" "db" { +# length = 16 +# } +# password_wo = ephemeral.random_password.db.result + +# Step 5: Validate +terraform validate # Must pass! + +# Step 6: Test plan +terraform plan + +# Step 7: Apply (will update resource with write-only argument) +terraform apply + +# Step 8: Verify state doesn't contain sensitive value +terraform state show | grep -i password +# Should show password_wo_version but NOT password_wo value +``` + +### Workflow 3: Combine Ephemeral + Write-only + +```bash +# Step 1: Check support for both features +./scripts/check_ephemeral_support.sh aws +./scripts/check_writeonly_support.sh aws + +# Step 2: Create ephemeral resource for sensitive data +# Add: +# ephemeral "aws_secretsmanager_secret_version" "db_password" { +# secret_id = "prod-db-password" +# } + +# Step 3: Use ephemeral value in write-only argument +# resource "aws_db_instance" "main" { +# password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string +# password_wo_version = 1 +# } + +# Step 4: Validate and apply +terraform validate +terraform plan +terraform apply +``` + +## Common Patterns + +See [references/migration-patterns.md](references/migration-patterns.md) for detailed examples. + +### Pattern 1: Secrets Manager to Ephemeral + Write-only + +```hcl +# BEFORE +data "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} +resource "aws_db_instance" "main" { + password = data.aws_secretsmanager_secret_version.db_password.secret_string +} + +# AFTER +ephemeral "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} +resource "aws_db_instance" "main" { + password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string + password_wo_version = 1 +} +``` + +### Pattern 2: Generated Password with Write-only + +```hcl +# BEFORE +resource "random_password" "db" { + length = 16 +} +resource "aws_db_instance" "main" { + password = random_password.db.result # Stored in state +} + +# AFTER +ephemeral "random_password" "db" { + length = 16 +} +resource "aws_db_instance" "main" { + password_wo = ephemeral.random_password.db.result # Not stored + password_wo_version = 1 +} +``` + +### Pattern 3: Provider Authentication + +```hcl +# BEFORE +data "aws_eks_cluster_auth" "cluster" { + name = "my-cluster" +} +provider "kubernetes" { + host = data.aws_eks_cluster.cluster.endpoint + token = data.aws_eks_cluster_auth.cluster.token # Stored in state +} + +# AFTER +ephemeral "aws_eks_cluster_auth" "cluster" { + name = "my-cluster" +} +provider "kubernetes" { + host = data.aws_eks_cluster.cluster.endpoint + token = ephemeral.aws_eks_cluster_auth.cluster.token # Not stored +} +``` + +## Troubleshooting + +### Error: "ephemeral resource type not supported" + +**Cause:** Provider doesn't support ephemeral version of the resource. + +**Solutions:** +- Check provider version (may be too old) +- Check Terraform version (must be >= 1.10) +- Run: `./scripts/check_ephemeral_support.sh ` +- Check provider documentation for ephemeral resource support +- If not available, use version-upgrades skill to update provider + +### Error: "write-only argument not recognized" or "unknown argument password_wo" + +**Cause:** Provider doesn't support write-only arguments or Terraform version too old. + +**Solutions:** +- Check Terraform version (must be >= 1.11) +- Run: `./scripts/check_writeonly_support.sh ` +- Check provider schema: `terraform providers schema -json | jq '.provider_schemas' | grep "_wo"` +- Verify argument name ends with `_wo` suffix +- Update provider if version is too old + +### Error: "ephemeral values cannot be used in this context" + +**Cause:** Trying to use ephemeral value in non-ephemeral context. + +**Valid contexts:** +- Write-only arguments (attributes ending in `_wo`) +- Other ephemeral resource blocks +- Provider configuration blocks +- Outputs marked with `ephemeral = true` + +**Invalid contexts:** +- Regular resource arguments +- Regular (non-ephemeral) outputs +- Module outputs (unless ephemeral) +- Data sources +- depends_on + +**Solution:** Refactor to use ephemeral value only in valid contexts, or use regular data source instead. + +### Error: "write-only argument missing corresponding _wo_version" + +**Cause:** Every `_wo` argument requires a `_wo_version` argument. + +**Solution:** +```hcl +resource "aws_db_instance" "main" { + password_wo = var.db_password + password_wo_version = 1 # Add this +} +``` + +### Validation fails after transformation + +**Steps to diagnose:** +1. Run `terraform validate` to see specific error +2. Verify all ephemeral references are in ephemeral contexts +3. Check that write-only arguments are paired with version arguments +4. Review provider schema for correct argument names: + ```bash + terraform providers schema -json | jq '.provider_schemas' + ``` + +## Safety Considerations + +⚠️ **Important Notes:** + +1. **Ephemeral context restrictions**: Ephemeral values can ONLY be used in: + - Write-only arguments (`_wo` suffix) + - Other ephemeral blocks + - Provider configuration blocks + - Outputs marked `ephemeral = true` + +2. **Write-only requires version tracking**: Always pair `_wo` with `_wo_version` + - Terraform can't detect changes to write-only values + - Increment version number to trigger updates + - Example: Change `password_wo_version = 1` to `password_wo_version = 2` + +3. **Write-only removes from state**: Cannot read values after apply + - Once written, values are not retrievable from state + - Plan accordingly for recovery/rotation scenarios + - Keep external records if needed + +4. **Validation is mandatory**: Always run `terraform validate` after transformations + - Catches ephemeral context violations + - Verifies write-only argument pairing + - Ensures provider support + +5. **Test in non-prod first**: Always test migrations in dev/staging + - Transformations affect state structure + - May require resource replacement + - Verify behavior before production + +6. **Backup state files**: Keep state backups before changes + - Use remote state with versioning + - Test rollback procedures + - Document recovery steps + +7. **Provider compatibility**: Not all providers support these features yet + - Check support before starting + - Update providers if needed + - Consult provider documentation + +## Related Skills + +- [version-upgrades](https://github.com/thrashr888/hcptf-cli/tree/main/.skills/version-upgrades) - Upgrade Terraform and provider versions first +- [terraform-style-guide](../terraform-style-guide/SKILL.md) - Format modernized code +- [terraform-test](../terraform-test/SKILL.md) - Test modernized configurations + +## References + +**Official Documentation:** +- [Ephemeral Resources](https://developer.hashicorp.com/terraform/language/manage-sensitive-data/ephemeral) +- [Write-only Arguments](https://developer.hashicorp.com/terraform/language/manage-sensitive-data/write-only) +- [Ephemeral Resources (Block Reference)](https://developer.hashicorp.com/terraform/language/resources/ephemeral) +- [AWS Provider Ephemeral Resources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/ephemeral-resources) + +**Blog Posts:** +- [Introducing Ephemeral Resources (Terraform 1.10)](https://www.hashicorp.com/blog/terraform-1-10-adds-ephemeral-resources) +- [Write-only Arguments (Terraform 1.11)](https://www.hashicorp.com/blog/terraform-1-11-write-only-arguments) diff --git a/terraform/code-generation/skills/terraform-modernize/references/ephemeral-contexts.md b/terraform/code-generation/skills/terraform-modernize/references/ephemeral-contexts.md new file mode 100644 index 0000000..dc38009 --- /dev/null +++ b/terraform/code-generation/skills/terraform-modernize/references/ephemeral-contexts.md @@ -0,0 +1,153 @@ +# Ephemeral Contexts Reference + +Comprehensive guide to where ephemeral values can and cannot be used in Terraform configurations. + +## What are Ephemeral Contexts? + +Ephemeral contexts are specific locations in Terraform configurations where ephemeral values are permitted. Because ephemeral values don't persist in state, Terraform restricts where they can be referenced. + +**Rule:** Ephemeral values can ONLY be used in ephemeral contexts. + +## Valid Ephemeral Contexts + +### 1. Write-only Arguments + +Write-only arguments (attributes ending in `_wo`) are ephemeral contexts. + +```hcl +ephemeral "random_password" "db" { + length = 16 +} + +resource "aws_db_instance" "main" { + password_wo = ephemeral.random_password.db.result # ✅ Valid + password_wo_version = 1 +} +``` + +### 2. Other Ephemeral Blocks + +Ephemeral resources can reference other ephemeral resources. + +```hcl +ephemeral "aws_secretsmanager_secret" "root" { + name = "root-secret" +} + +ephemeral "aws_secretsmanager_secret_version" "root_version" { + secret_id = ephemeral.aws_secretsmanager_secret.root.id # ✅ Valid +} +``` + +### 3. Provider Configuration Blocks + +Provider blocks can use ephemeral values for dynamic authentication. + +```hcl +ephemeral "aws_eks_cluster_auth" "cluster" { + name = "my-cluster" +} + +provider "kubernetes" { + host = data.aws_eks_cluster.cluster.endpoint + token = ephemeral.aws_eks_cluster_auth.cluster.token # ✅ Valid +} +``` + +### 4. Ephemeral Outputs + +Outputs marked with `ephemeral = true` can contain ephemeral values. + +```hcl +ephemeral "random_password" "temp" { + length = 16 +} + +output "temporary_password" { + value = ephemeral.random_password.temp.result # ✅ Valid + ephemeral = true # REQUIRED + sensitive = true # Recommended +} +``` + +### 5. Ephemeral Input Variables + +Input variables marked with `ephemeral = true` can accept and pass ephemeral values. + +```hcl +variable "db_password" { + type = string + ephemeral = true # Marks this variable as ephemeral + sensitive = true +} + +# Can pass ephemeral value to this variable +# terraform apply -var="db_password=$(get-secret)" + +resource "aws_db_instance" "main" { + password_wo = var.db_password # ✅ Valid - variable is ephemeral + password_wo_version = 1 +} +``` + +**Note:** Ephemeral variables behave like ephemeral resources - they can only be used in ephemeral contexts. + +## Invalid (Non-ephemeral) Contexts + +### 1. Regular Resource Arguments + +```hcl +ephemeral "random_password" "db" { + length = 16 +} + +resource "aws_db_instance" "main" { + password = ephemeral.random_password.db.result # ❌ ERROR +} +``` + +**Solution:** Use write-only argument. + +### 2. Regular Outputs + +```hcl +output "db_password" { + value = ephemeral.random_password.db.result # ❌ ERROR +} +``` + +**Solution:** Mark output as ephemeral with `ephemeral = true`. + +### 3. Data Sources + +```hcl +data "aws_instance" "app" { + filter { + values = [ephemeral.random_string.suffix.result] # ❌ ERROR + } +} +``` + +### 4. depends_on + +```hcl +resource "aws_instance" "app" { + depends_on = [ephemeral.random_password.db] # ❌ ERROR +} +``` + +**Solution:** Use implicit dependencies through regular resources. + +## Validation + +Always validate after using ephemeral values: + +```bash +terraform validate +``` + +## Related Documentation + +- [Ephemeral Resources](ephemeral-resources.md) +- [Write-only Arguments](write-only-arguments.md) +- [Migration Patterns](migration-patterns.md) diff --git a/terraform/code-generation/skills/terraform-modernize/references/ephemeral-resources.md b/terraform/code-generation/skills/terraform-modernize/references/ephemeral-resources.md new file mode 100644 index 0000000..79a3d5d --- /dev/null +++ b/terraform/code-generation/skills/terraform-modernize/references/ephemeral-resources.md @@ -0,0 +1,427 @@ +# Ephemeral Resources Reference + +Comprehensive guide to Terraform ephemeral resources. + +## What are Ephemeral Resources? + +Ephemeral resources are temporary infrastructure components that exist only during the current Terraform operation. Unlike managed resources or data sources, ephemeral resources: + +- **Do not persist in state files** +- **Do not appear in plan files** +- **Exist only during plan/apply operations** +- **Are re-evaluated on every Terraform run** + +**Available since:** Terraform 1.10.0 + +## Lifecycle + +Ephemeral resources have a unique lifecycle: + +``` +1. Plan/Apply starts +2. Ephemeral resource is created/read +3. Value is used in ephemeral contexts +4. Plan/Apply completes +5. Ephemeral resource is destroyed (no state saved) +``` + +On the next Terraform run, the ephemeral resource is recreated fresh. + +## Syntax + +```hcl +ephemeral "provider_resource_type" "name" { + # Configuration arguments + argument1 = "value1" + argument2 = "value2" +} +``` + +**Example:** +```hcl +ephemeral "aws_secretsmanager_secret_version" "api_key" { + secret_id = "prod-api-key" +} + +ephemeral "random_password" "database" { + length = 16 + special = true +} +``` + +## When to Use Ephemeral Resources + +### ✅ Good Use Cases + +1. **Secrets and Credentials** + - Fetching passwords from secret managers + - Retrieving API tokens + - Getting temporary credentials + +2. **Short-lived Data** + - Certificate data for TLS connections + - Authentication tokens for providers + - Temporary access keys + +3. **Sensitive Information** + - Data that shouldn't be stored in state + - Values that change frequently + - Credentials with short TTLs + +4. **Provider Configuration** + - Dynamic provider authentication + - EKS cluster credentials + - Vault tokens + +### ❌ Poor Use Cases + +1. **Long-lived Infrastructure** + - VPCs, subnets, security groups + - S3 buckets, databases + - Anything that needs to persist + +2. **Referenced in Outputs** + - Unless output is marked `ephemeral = true` + - Values needed by other tools/scripts + +3. **Used in depends_on** + - Ephemeral resources cannot be dependencies + - Use implicit dependencies instead + +## Ephemeral Contexts + +Ephemeral values can **ONLY** be used in these contexts: + +### 1. Write-only Arguments + +```hcl +ephemeral "random_password" "db" { + length = 16 +} + +resource "aws_db_instance" "main" { + password_wo = ephemeral.random_password.db.result + password_wo_version = 1 +} +``` + +### 2. Other Ephemeral Blocks + +```hcl +ephemeral "aws_iam_role" "temp_role" { + name = "temp-role" +} + +ephemeral "aws_iam_policy" "temp_policy" { + role = ephemeral.aws_iam_role.temp_role.name +} +``` + +### 3. Provider Configuration + +```hcl +ephemeral "aws_eks_cluster_auth" "cluster" { + name = "my-cluster" +} + +provider "kubernetes" { + host = data.aws_eks_cluster.cluster.endpoint + token = ephemeral.aws_eks_cluster_auth.cluster.token +} +``` + +### 4. Ephemeral Outputs + +```hcl +ephemeral "random_password" "temp" { + length = 16 +} + +output "temp_password" { + value = ephemeral.random_password.temp.result + ephemeral = true # REQUIRED + sensitive = true # Recommended +} +``` + +## Provider Support + +Not all providers support ephemeral resources yet. Support is growing: + +### AWS Provider (hashicorp/aws >= 5.70) + +Common ephemeral resources: +- `ephemeral.aws_secretsmanager_secret_version` - Secrets Manager secrets +- `ephemeral.aws_eks_cluster_auth` - EKS authentication tokens +- `ephemeral.aws_iam_role` - Temporary IAM roles + +### Random Provider (hashicorp/random >= 3.6) + +- `ephemeral.random_password` - Generate temporary passwords +- `ephemeral.random_string` - Generate temporary strings +- `ephemeral.random_id` - Generate temporary IDs + +### TLS Provider (hashicorp/tls >= 4.0) + +- `ephemeral.tls_certificate` - Fetch TLS certificates +- `ephemeral.tls_private_key` - Generate temporary private keys + +### Check Support + +Use the provided script: +```bash +./scripts/check_ephemeral_support.sh aws +``` + +## Naming Convention + +Providers use consistent naming across resource types: + +| Type | Format | Example | +|------|--------|---------| +| Data Source | `data..` | `data.aws_secretsmanager_secret_version.api_key` | +| Ephemeral | `ephemeral..` | `ephemeral.aws_secretsmanager_secret_version.api_key` | +| Managed Resource | `` | `aws_secretsmanager_secret_version` (if exists) | + +## Migration Examples + +### Example 1: Secrets Manager Secret + +**Before:** +```hcl +data "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password = data.aws_secretsmanager_secret_version.db_password.secret_string + # ... other config +} +``` + +**After:** +```hcl +ephemeral "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string + password_wo_version = 1 + # ... other config +} +``` + +**Benefits:** +- Password not stored in state +- Secret not in plan files +- Better security posture + +### Example 2: Random Password Generation + +**Before:** +```hcl +resource "random_password" "api_token" { + length = 32 + special = false +} + +resource "aws_ssm_parameter" "token" { + name = "/app/api-token" + type = "SecureString" + value = random_password.api_token.result # Stored in state +} +``` + +**After:** +```hcl +ephemeral "random_password" "api_token" { + length = 32 + special = false +} + +resource "aws_ssm_parameter" "token" { + name = "/app/api-token" + type = "SecureString" + value_wo = ephemeral.random_password.api_token.result # Not stored + value_wo_version = 1 +} +``` + +### Example 3: Provider Authentication + +**Before:** +```hcl +data "aws_eks_cluster" "main" { + name = "my-cluster" +} + +data "aws_eks_cluster_auth" "main" { + name = "my-cluster" +} + +provider "kubernetes" { + host = data.aws_eks_cluster.main.endpoint + cluster_ca_certificate = base64decode(data.aws_eks_cluster.main.certificate_authority[0].data) + token = data.aws_eks_cluster_auth.main.token # Stored in state +} +``` + +**After:** +```hcl +data "aws_eks_cluster" "main" { + name = "my-cluster" +} + +ephemeral "aws_eks_cluster_auth" "main" { + name = "my-cluster" +} + +provider "kubernetes" { + host = data.aws_eks_cluster.main.endpoint + cluster_ca_certificate = base64decode(data.aws_eks_cluster.main.certificate_authority[0].data) + token = ephemeral.aws_eks_cluster_auth.main.token # Not stored +} +``` + +## Common Errors + +### Error: "ephemeral values cannot be used in this context" + +**Cause:** Trying to use ephemeral value in non-ephemeral context. + +**Example:** +```hcl +ephemeral "random_password" "db" { + length = 16 +} + +resource "aws_db_instance" "main" { + password = ephemeral.random_password.db.result # ❌ ERROR +} +``` + +**Solution:** Use write-only argument: +```hcl +resource "aws_db_instance" "main" { + password_wo = ephemeral.random_password.db.result # ✅ Correct + password_wo_version = 1 +} +``` + +### Error: "ephemeral resource type not supported" + +**Cause:** Provider doesn't support ephemeral version of the resource. + +**Check support:** +```bash +./scripts/check_ephemeral_support.sh aws +``` + +**Solutions:** +1. Update provider to newer version +2. Check provider documentation +3. Use data source instead (less secure) + +### Error: "cannot use ephemeral value in output" + +**Cause:** Output not marked as ephemeral. + +**Wrong:** +```hcl +output "password" { + value = ephemeral.random_password.db.result # ❌ ERROR +} +``` + +**Correct:** +```hcl +output "password" { + value = ephemeral.random_password.db.result # ✅ + ephemeral = true # Required + sensitive = true # Recommended +} +``` + +## Edge Cases + +### Ephemeral with for_each + +```hcl +variable "secret_ids" { + type = list(string) + default = ["secret1", "secret2", "secret3"] +} + +ephemeral "aws_secretsmanager_secret_version" "secrets" { + for_each = toset(var.secret_ids) + secret_id = each.value +} + +# Use in ephemeral context: +resource "aws_instance" "app" { + for_each = ephemeral.aws_secretsmanager_secret_version.secrets + + user_data_wo = each.value.secret_string + user_data_wo_version = 1 +} +``` + +### Ephemeral with count + +```hcl +ephemeral "random_password" "passwords" { + count = 3 + length = 16 +} + +# Use in ephemeral context +resource "aws_db_instance" "dbs" { + count = 3 + + password_wo = ephemeral.random_password.passwords[count.index].result + password_wo_version = 1 +} +``` + +### Chaining Ephemeral Resources + +```hcl +ephemeral "vault_generic_secret" "root" { + path = "secret/root" +} + +ephemeral "vault_generic_secret" "derived" { + path = ephemeral.vault_generic_secret.root.data["derived_path"] +} + +resource "aws_instance" "app" { + user_data_wo = ephemeral.vault_generic_secret.derived.data["config"] + user_data_wo_version = 1 +} +``` + +## Best Practices + +1. **Use for sensitive data only** - Ephemeral has restrictions, only use when security benefit justifies complexity + +2. **Combine with write-only arguments** - Maximum security when both are used together + +3. **Document ephemeral outputs** - Make it clear to consumers that outputs are ephemeral + +4. **Test validation** - Always run `terraform validate` after changes + +5. **Check provider support** - Use scripts to verify support before starting + +6. **Use implicit dependencies** - Avoid explicit depends_on with ephemeral resources + +7. **Mark outputs as sensitive** - Ephemeral outputs should usually be sensitive too + +## Related Documentation + +- [Write-only Arguments](write-only-arguments.md) +- [Ephemeral Contexts](ephemeral-contexts.md) +- [Migration Patterns](migration-patterns.md) +- [Official Terraform Docs](https://developer.hashicorp.com/terraform/language/resources/ephemeral) diff --git a/terraform/code-generation/skills/terraform-modernize/references/migration-patterns.md b/terraform/code-generation/skills/terraform-modernize/references/migration-patterns.md new file mode 100644 index 0000000..0586a1d --- /dev/null +++ b/terraform/code-generation/skills/terraform-modernize/references/migration-patterns.md @@ -0,0 +1,354 @@ +# Migration Patterns + +Common patterns for migrating Terraform configurations to use ephemeral resources and write-only arguments. + +## Pattern 1: AWS Secrets Manager to Ephemeral + Write-only + +### Scenario +Database password stored in AWS Secrets Manager, currently fetched via data source and stored in state. + +### Before + +```hcl +data "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} + +resource "aws_db_instance" "main" { + identifier = "production-db" + engine = "postgres" + instance_class = "db.t3.medium" + + username = "admin" + password = data.aws_secretsmanager_secret_version.db_password.secret_string + + allocated_storage = 100 +} +``` + +**Issues:** +- Password visible in state file +- Password visible in plan output +- Security risk if state file is compromised + +### After + +```hcl +ephemeral "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} + +resource "aws_db_instance" "main" { + identifier = "production-db" + engine = "postgres" + instance_class = "db.t3.medium" + + username = "admin" + password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string + password_wo_version = 1 + + allocated_storage = 100 +} +``` + +**Benefits:** +- Password not stored in state +- Password not in plan output +- Better security posture + +### Migration Steps + +1. Check support: `./scripts/check_ephemeral_support.sh aws` +2. Change `data` to `ephemeral` +3. Change `password =` to `password_wo =` +4. Add `password_wo_version = 1` +5. Run `terraform validate` +6. Run `terraform plan` (review changes) +7. Run `terraform apply` + +--- + +## Pattern 2: Generated Password to Ephemeral + +### Scenario +Using random_password resource, password stored in state. + +### Before + +```hcl +resource "random_password" "db_password" { + length = 16 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password = random_password.db_password.result +} +``` + +**Issues:** +- Generated password stored in random_password state +- Password also stored in aws_db_instance state +- Double exposure in state file + +### After + +```hcl +ephemeral "random_password" "db_password" { + length = 16 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password_wo = ephemeral.random_password.db_password.result + password_wo_version = 1 +} +``` + +**Benefits:** +- Password not in random_password state (ephemeral) +- Password not in aws_db_instance state (write-only) +- Completely removed from state file + +### Migration Steps + +1. Change `resource "random_password"` to `ephemeral "random_password"` +2. Update reference to `ephemeral.random_password...` +3. Change `password =` to `password_wo =` +4. Add `password_wo_version = 1` +5. Validate and apply + +--- + +## Pattern 3: Variable-based Password to Write-only + +### Scenario +Password passed as variable, stored in state. + +### Before + +```hcl +variable "db_password" { + type = string + sensitive = true +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password = var.db_password +} +``` + +**Issues:** +- Password stored in state despite `sensitive = true` +- Sensitive flag only hides from console output + +### After + +```hcl +variable "db_password" { + type = string + sensitive = true + ephemeral = true # Optional: if passing ephemeral value +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password_wo = var.db_password + password_wo_version = 1 +} +``` + +**Benefits:** +- Password not stored in state +- Can mark variable as ephemeral if needed + +### Migration Steps + +1. Change `password =` to `password_wo =` +2. Add `password_wo_version = 1` +3. Optionally add `ephemeral = true` to variable +4. Validate and apply + +--- + +## Pattern 4: Provider Authentication (EKS/Kubernetes) + +### Scenario +Using EKS cluster auth token for Kubernetes provider. + +### Before + +```hcl +data "aws_eks_cluster" "main" { + name = "my-cluster" +} + +data "aws_eks_cluster_auth" "main" { + name = "my-cluster" +} + +provider "kubernetes" { + host = data.aws_eks_cluster.main.endpoint + cluster_ca_certificate = base64decode(data.aws_eks_cluster.main.certificate_authority[0].data) + token = data.aws_eks_cluster_auth.main.token +} +``` + +**Issues:** +- Auth token stored in state +- Token may be short-lived but persisted + +### After + +```hcl +data "aws_eks_cluster" "main" { + name = "my-cluster" +} + +ephemeral "aws_eks_cluster_auth" "main" { + name = "my-cluster" +} + +provider "kubernetes" { + host = data.aws_eks_cluster.main.endpoint + cluster_ca_certificate = base64decode(data.aws_eks_cluster.main.certificate_authority[0].data) + token = ephemeral.aws_eks_cluster_auth.main.token +} +``` + +**Benefits:** +- Token not stored in state +- Provider blocks are ephemeral contexts +- Token regenerated on each run + +### Migration Steps + +1. Change `data "aws_eks_cluster_auth"` to `ephemeral "aws_eks_cluster_auth"` +2. Update provider reference +3. Validate and apply + +--- + +## Pattern 5: API Key Rotation + +### Scenario +API key that needs periodic rotation. + +### Before + +```hcl +variable "api_key" { + type = string + sensitive = true +} + +resource "datadog_api_key" "monitoring" { + name = "production-monitoring" + key = var.api_key +} +``` + +**Issues:** +- Key stored in state +- Manual rotation difficult to track + +### After + +```hcl +variable "api_key_version" { + type = number + description = "Increment to rotate API key" + default = 1 +} + +variable "api_key" { + type = string + sensitive = true + ephemeral = true +} + +resource "datadog_api_key" "monitoring" { + name = "production-monitoring-v${var.api_key_version}" + key_wo = var.api_key + key_wo_version = var.api_key_version +} +``` + +**Benefits:** +- Key not stored in state +- Version tracking for rotation +- Easy to trigger rotation + +### Rotation Procedure + +1. Update secret in secret manager +2. Increment `api_key_version` variable +3. Run `terraform apply` + +--- + +## Common Combinations + +### Ephemeral + Write-only (Maximum Security) + +```hcl +ephemeral "aws_secretsmanager_secret_version" "creds" { + secret_id = "app-credentials" +} + +locals { + credentials = jsondecode(ephemeral.aws_secretsmanager_secret_version.creds.secret_string) +} + +resource "aws_db_instance" "main" { + username_wo = local.credentials.username + username_wo_version = 1 + password_wo = local.credentials.password + password_wo_version = 1 +} +``` + +### Multiple Ephemeral Sources + +```hcl +ephemeral "aws_secretsmanager_secret_version" "db_password" { + secret_id = "db-password" +} + +ephemeral "aws_secretsmanager_secret_version" "api_key" { + secret_id = "api-key" +} + +resource "aws_db_instance" "main" { + password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string + password_wo_version = 1 +} + +resource "datadog_api_key" "api" { + key_wo = ephemeral.aws_secretsmanager_secret_version.api_key.secret_string + key_wo_version = 1 +} +``` + +## Validation Checklist + +After each migration: + +- [ ] Run `terraform validate` - must pass +- [ ] Review `terraform plan` output +- [ ] Verify no unexpected resource replacements +- [ ] Check ephemeral values only in ephemeral contexts +- [ ] Confirm write-only arguments paired with `_wo_version` +- [ ] Test in non-production environment first +- [ ] Backup state file before applying + +## Related Documentation + +- [Ephemeral Resources](ephemeral-resources.md) +- [Write-only Arguments](write-only-arguments.md) +- [Ephemeral Contexts](ephemeral-contexts.md) diff --git a/terraform/code-generation/skills/terraform-modernize/references/write-only-arguments.md b/terraform/code-generation/skills/terraform-modernize/references/write-only-arguments.md new file mode 100644 index 0000000..a6c9962 --- /dev/null +++ b/terraform/code-generation/skills/terraform-modernize/references/write-only-arguments.md @@ -0,0 +1,504 @@ +# Write-only Arguments Reference + +Comprehensive guide to Terraform write-only arguments. + +## What are Write-only Arguments? + +Write-only arguments are resource arguments that accept sensitive values but do not store them in state or plan files. They use a special `_wo` suffix naming convention and require version tracking. + +**Available since:** Terraform 1.11.0 + +## How They Work + +``` +1. You pass a value to a _wo argument +2. Terraform sends value to provider +3. Provider applies the value +4. Terraform discards the value (never stored) +5. State contains only the _wo_version, not the value +``` + +On subsequent runs, Terraform cannot detect if the value changed. You must increment `_wo_version` to trigger updates. + +## Syntax + +Write-only arguments require **two** arguments: + +1. The write-only argument (`_wo`) +2. The version tracker (`_wo_version`) + +```hcl +resource "provider_resource_type" "name" { + argument_wo = "sensitive-value" + argument_wo_version = 1 # Increment to trigger updates +} +``` + +**Example:** +```hcl +resource "aws_db_instance" "main" { + identifier = "mydb" + password_wo = var.db_password + password_wo_version = 1 +} +``` + +## When to Use Write-only Arguments + +### ✅ Good Use Cases + +1. **Database Credentials** + - RDS passwords + - ElastiCache auth tokens + - Database connection strings + +2. **API Keys and Tokens** + - Service API keys + - Authentication tokens + - Bearer tokens + +3. **Private Keys** + - SSH private keys + - TLS private keys + - Encryption keys + +4. **Sensitive Configuration** + - Webhook secrets + - Integration credentials + - Service passwords + +### ❌ Not Suitable When + +1. **Need to Read Value Later** + - Values for use in other resources + - Values needed in outputs + - Values for conditional logic + +2. **Value Managed by Provider** + - Provider-generated IDs + - Auto-assigned attributes + - Computed values + +3. **Non-sensitive Data** + - Resource names, tags + - Configuration options + - Public information + +## Naming Convention + +Write-only arguments use `_wo` suffix and require matching `_wo_version`: + +| Regular Argument | Write-only Equivalent | Version Tracker | +|-----------------|----------------------|-----------------| +| `password` | `password_wo` | `password_wo_version` | +| `api_key` | `api_key_wo` | `api_key_wo_version` | +| `private_key` | `private_key_wo` | `private_key_wo_version` | +| `token` | `token_wo` | `token_wo_version` | + +## Version Tracking + +The `_wo_version` argument is crucial for triggering updates. + +### How Version Tracking Works + +```hcl +resource "aws_db_instance" "main" { + password_wo = var.db_password + password_wo_version = 1 # Initial creation +} + +# Later, when password changes: +resource "aws_db_instance" "main" { + password_wo = var.db_password # New value + password_wo_version = 2 # Incremented - triggers update! +} +``` + +**Why needed:** Terraform can't compare write-only values (they're not in state), so it can't detect changes. The version number is the signal. + +### Version Number Rules + +1. **Start with 1** - Initial value should be 1, not 0 +2. **Increment to update** - Increase by 1 each time value changes +3. **Any integer works** - Can use timestamps, build numbers, etc. +4. **Must change** - If version doesn't change, value won't update + +```hcl +# ✅ Good versioning strategies +password_wo_version = 1 # Simple incrementing +password_wo_version = 2 +password_wo_version = 3 + +password_wo_version = 20250306 # Date-based +password_wo_version = 20250307 + +# ❌ Bad - version not changed +password_wo = "new-password" +password_wo_version = 1 # Same as before - no update! +``` + +## Value Sources + +Write-only arguments can accept values from various sources: + +### 1. Variables (most common) + +```hcl +variable "db_password" { + type = string + sensitive = true +} + +resource "aws_db_instance" "main" { + password_wo = var.db_password + password_wo_version = 1 +} +``` + +### 2. Ephemeral Resources (recommended for secrets) + +```hcl +ephemeral "random_password" "db" { + length = 16 +} + +resource "aws_db_instance" "main" { + password_wo = ephemeral.random_password.db.result + password_wo_version = 1 +} +``` + +### 3. Ephemeral Data Sources + +```hcl +ephemeral "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} + +resource "aws_db_instance" "main" { + password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string + password_wo_version = 1 +} +``` + +### 4. Local Values + +```hcl +locals { + api_token = sensitive("secret-token-value") +} + +resource "datadog_api_key" "api" { + key_wo = local.api_token + key_wo_version = 1 +} +``` + +## Provider Support + +Not all providers or resources support write-only arguments yet. Check support: + +```bash +./scripts/check_writeonly_support.sh aws +./scripts/check_writeonly_support.sh aws aws_db_instance +``` + +### AWS Provider (hashicorp/aws >= 5.70) + +Common resources with write-only arguments: +- `aws_db_instance` - `password_wo`, `master_user_secret_kms_key_id_wo` +- `aws_elasticache_cluster` - `auth_token_wo` +- `aws_secretsmanager_secret_version` - `secret_string_wo`, `secret_binary_wo` + +### Azure Provider (hashicorp/azurerm >= 4.0) + +- `azurerm_mssql_server` - `administrator_login_password_wo` +- `azurerm_postgresql_server` - `administrator_login_password_wo` + +### GCP Provider (hashicorp/google >= 6.0) + +- `google_sql_user` - `password_wo` + +## Migration Examples + +### Example 1: Database Password + +**Before:** +```hcl +variable "db_password" { + type = string + sensitive = true +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password = var.db_password # Stored in state +} +``` + +**After:** +```hcl +variable "db_password" { + type = string + sensitive = true +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password_wo = var.db_password # Not stored in state + password_wo_version = 1 +} +``` + +**State changes:** +- Before: State contains encrypted password value +- After: State contains only `password_wo_version = 1` + +### Example 2: Generated Password with Ephemeral + +**Before:** +```hcl +resource "random_password" "db" { + length = 16 + special = true +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password = random_password.db.result # Both stored in state +} +``` + +**After:** +```hcl +ephemeral "random_password" "db" { + length = 16 + special = true +} + +resource "aws_db_instance" "main" { + identifier = "mydb" + password_wo = ephemeral.random_password.db.result # Nothing in state + password_wo_version = 1 +} +``` + +**Benefits:** +- Password not in random_password state +- Password not in aws_db_instance state +- Completely removed from state file + +### Example 3: API Key Rotation + +**Scenario:** Need to rotate API key every 90 days + +```hcl +variable "api_key_version" { + type = number + description = "Increment this to rotate the API key" + default = 1 +} + +ephemeral "aws_secretsmanager_secret_version" "api_key" { + secret_id = "prod-api-key" +} + +resource "datadog_api_key" "monitoring" { + name = "prod-monitoring-${var.api_key_version}" + key_wo = ephemeral.aws_secretsmanager_secret_version.api_key.secret_string + key_wo_version = var.api_key_version +} +``` + +**To rotate:** +1. Update secret in Secrets Manager +2. Increment `api_key_version` variable: `default = 2` +3. Run `terraform apply` + +## Common Errors + +### Error: "unknown argument password_wo" + +**Cause:** Provider doesn't support write-only arguments for this resource. + +**Check:** +```bash +./scripts/check_writeonly_support.sh aws aws_db_instance +``` + +**Solutions:** +1. Update provider to newer version +2. Check provider documentation +3. Use regular argument (less secure) + +### Error: "missing required argument password_wo_version" + +**Cause:** Every `_wo` argument requires matching `_wo_version`. + +**Wrong:** +```hcl +resource "aws_db_instance" "main" { + password_wo = var.db_password # ❌ Missing version +} +``` + +**Correct:** +```hcl +resource "aws_db_instance" "main" { + password_wo = var.db_password + password_wo_version = 1 # ✅ Version provided +} +``` + +### Error: "both password and password_wo cannot be set" + +**Cause:** Cannot use both regular and write-only versions of same argument. + +**Wrong:** +```hcl +resource "aws_db_instance" "main" { + password = var.old_password # ❌ Conflicting + password_wo = var.new_password # ❌ Conflicting + password_wo_version = 1 +} +``` + +**Correct:** Choose one +```hcl +resource "aws_db_instance" "main" { + password_wo = var.password # ✅ Use write-only + password_wo_version = 1 +} +``` + +### Value Not Updating + +**Symptom:** Changed password but resource not updating. + +**Cause:** Forgot to increment `_wo_version`. + +**Solution:** +```hcl +resource "aws_db_instance" "main" { + password_wo = var.new_password + password_wo_version = 2 # Increment from 1 to 2 +} +``` + +## Best Practices + +1. **Always pair with version** - Never use `_wo` without `_wo_version` + +2. **Use with ephemeral sources** - Maximum security: ephemeral + write-only + +3. **Increment version on changes** - Document when/why version was incremented + +4. **Start with version 1** - Not 0, makes incrementing clearer + +5. **Use semantic versioning** - Can use dates, build numbers, etc. + +6. **Document rotation procedures** - How to update passwords/keys + +7. **Test in non-prod first** - Verify behavior before production + +8. **Keep external records** - Write-only values can't be retrieved from state + +## State Management + +### What's in State + +**With regular arguments:** +```json +{ + "password": "super-secret-password", + "username": "admin" +} +``` + +**With write-only arguments:** +```json +{ + "password_wo_version": 1, + "username": "admin" +} +``` + +Note: `password_wo` value is NOT in state. + +### State Migration + +When migrating to write-only, Terraform will: + +1. Remove old password value from state +2. Add `password_wo_version` to state +3. Resource may require replacement (check plan) + +Always review plan carefully before applying. + +## Combination Patterns + +### Pattern: Ephemeral + Write-only + +Maximum security - nothing in state: + +```hcl +ephemeral "aws_secretsmanager_secret_version" "db_password" { + secret_id = "prod-db-password" +} + +resource "aws_db_instance" "main" { + password_wo = ephemeral.aws_secretsmanager_secret_version.db_password.secret_string + password_wo_version = 1 +} +``` + +Result: +- Secret not in ephemeral state ✓ +- Secret not in aws_db_instance state ✓ +- Completely removed from Terraform state ✓ + +### Pattern: Multiple Write-only Arguments + +```hcl +ephemeral "aws_secretsmanager_secret_version" "db_creds" { + secret_id = "prod-db-creds" +} + +locals { + db_creds = jsondecode(ephemeral.aws_secretsmanager_secret_version.db_creds.secret_string) +} + +resource "aws_db_instance" "main" { + username_wo = local.db_creds.username + username_wo_version = 1 + password_wo = local.db_creds.password + password_wo_version = 1 +} +``` + +### Pattern: Rotation Tracking + +```hcl +variable "password_rotation_date" { + type = string + description = "Date of last password rotation (YYYYMMDD)" + default = "20250306" +} + +resource "aws_db_instance" "main" { + password_wo = var.db_password + password_wo_version = parseint(var.password_rotation_date, 10) +} +``` + +Benefits: +- Version number is meaningful +- Easy to track rotation history +- Audit trail in version control + +## Related Documentation + +- [Ephemeral Resources](ephemeral-resources.md) +- [Ephemeral Contexts](ephemeral-contexts.md) +- [Migration Patterns](migration-patterns.md) +- [Official Terraform Docs](https://developer.hashicorp.com/terraform/language/manage-sensitive-data/write-only) diff --git a/terraform/code-generation/skills/terraform-modernize/scripts/check_ephemeral_support.sh b/terraform/code-generation/skills/terraform-modernize/scripts/check_ephemeral_support.sh new file mode 100755 index 0000000..2b614e5 --- /dev/null +++ b/terraform/code-generation/skills/terraform-modernize/scripts/check_ephemeral_support.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# Extract ephemeral resources supported by Terraform providers +# Usage: ./check_ephemeral_support.sh [provider_name] +# Requires: terraform, jq +# Note: Run from an initialized Terraform directory (terraform init) +# +# This script checks ONLY providers declared in the current configuration. +# It queries the provider schema after terraform init has downloaded the providers. + +set -e + +PROVIDER=$1 + +# Ensure terraform is initialized +if [ ! -d ".terraform" ]; then + echo "Initializing Terraform..." >&2 + terraform init -upgrade > /dev/null 2>&1 +fi + +# Get provider schema and extract ephemeral_resource_schemas +if [ -n "$PROVIDER" ]; then + # Specific provider (must be declared in current config) + provider_key=$(terraform providers schema -json 2>/dev/null | jq -r '.provider_schemas | keys[]' | grep "/${PROVIDER}$" || true) + if [ -n "$provider_key" ]; then + terraform providers schema -json 2>/dev/null | jq -r \ + "{\"$PROVIDER\": (.provider_schemas.\"${provider_key}\" | .ephemeral_resource_schemas // {} | keys | sort)}" + else + echo "{\"$PROVIDER\": []}" >&2 + echo "Note: Provider '${PROVIDER}' not found in current configuration." >&2 + echo "Add provider to terraform {} block and run terraform init first." >&2 + exit 1 + fi +else + # All providers declared in current config + terraform providers schema -json 2>/dev/null | jq -r ' + .provider_schemas + | to_entries + | map({key: (.key | split("/")[-1]), value: (.value.ephemeral_resource_schemas // {} | keys | sort)}) + | from_entries + ' +fi diff --git a/terraform/code-generation/skills/terraform-modernize/scripts/check_writeonly_support.sh b/terraform/code-generation/skills/terraform-modernize/scripts/check_writeonly_support.sh new file mode 100755 index 0000000..74a330c --- /dev/null +++ b/terraform/code-generation/skills/terraform-modernize/scripts/check_writeonly_support.sh @@ -0,0 +1,69 @@ +#!/bin/bash +# Check which resources/attributes support write-only arguments +# Usage: ./check_writeonly_support.sh [resource_type] +# Requires: terraform, jq +# Note: Run from an initialized Terraform directory (terraform init) +# +# This script checks ONLY providers declared in the current configuration. +# It looks for resource attributes ending in _wo (write-only suffix). + +set -e + +PROVIDER=$1 +RESOURCE=$2 + +if [ -z "$PROVIDER" ]; then + echo "Usage: $0 [resource_type]" >&2 + echo "Example: $0 aws" >&2 + echo "Example: $0 aws aws_db_instance" >&2 + exit 1 +fi + +# Ensure terraform is initialized +if [ ! -d ".terraform" ]; then + echo "Initializing Terraform..." >&2 + terraform init -upgrade > /dev/null 2>&1 +fi + +# Get provider key +provider_key=$(terraform providers schema -json 2>/dev/null | jq -r '.provider_schemas | keys[]' | grep "/${PROVIDER}$" || true) + +if [ -z "$provider_key" ]; then + echo "{}" >&2 + echo "Error: Provider '${PROVIDER}' not found in current configuration." >&2 + echo "Add provider to terraform {} block and run terraform init first." >&2 + exit 1 +fi + +# Extract write-only capable attributes from provider schema +if [ -n "$RESOURCE" ]; then + # Specific resource + terraform providers schema -json 2>/dev/null | jq -r --arg provider "$provider_key" --arg resource "$RESOURCE" ' + .provider_schemas[$provider].resource_schemas[$resource] // {} | + { + resource: $resource, + write_only_arguments: [ + (.block.attributes // {}) + | to_entries[] + | select(.key | endswith("_wo")) + | .key + ] + } + ' +else + # All resources for provider - show which have write-only arguments + terraform providers schema -json 2>/dev/null | jq -r --arg provider "$provider_key" ' + .provider_schemas[$provider].resource_schemas // {} | + to_entries | + map({ + resource: .key, + write_only_arguments: [ + (.value.block.attributes // {}) + | to_entries[] + | select(.key | endswith("_wo")) + | .key + ] + }) | + map(select(.write_only_arguments | length > 0)) + ' +fi From 33e8e794f749201b5eab03c439b53bd9398c5043 Mon Sep 17 00:00:00 2001 From: garvitarai1 Date: Tue, 10 Mar 2026 17:02:17 -0700 Subject: [PATCH 2/2] refactor: optimize scripts to cache provider schema output - Cache terraform providers schema -json output once per execution - Reuse cached SCHEMA variable instead of repeated calls - Reduces schema queries from 3 calls to 1 per script run - Addresses PR feedback from @quixoticmonk Co-Authored-By: Claude Sonnet 4.5 --- .../scripts/check_ephemeral_support.sh | 12 +++++++----- .../scripts/check_writeonly_support.sh | 13 +++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/terraform/code-generation/skills/terraform-modernize/scripts/check_ephemeral_support.sh b/terraform/code-generation/skills/terraform-modernize/scripts/check_ephemeral_support.sh index 2b614e5..183fa9f 100755 --- a/terraform/code-generation/skills/terraform-modernize/scripts/check_ephemeral_support.sh +++ b/terraform/code-generation/skills/terraform-modernize/scripts/check_ephemeral_support.sh @@ -17,13 +17,15 @@ if [ ! -d ".terraform" ]; then terraform init -upgrade > /dev/null 2>&1 fi +# Cache provider schema output for reuse within script +SCHEMA=$(terraform providers schema -json 2>/dev/null) + # Get provider schema and extract ephemeral_resource_schemas if [ -n "$PROVIDER" ]; then # Specific provider (must be declared in current config) - provider_key=$(terraform providers schema -json 2>/dev/null | jq -r '.provider_schemas | keys[]' | grep "/${PROVIDER}$" || true) + provider_key=$(jq -r '.provider_schemas | keys[]' <<< "$SCHEMA" | grep "/$1$" || true) if [ -n "$provider_key" ]; then - terraform providers schema -json 2>/dev/null | jq -r \ - "{\"$PROVIDER\": (.provider_schemas.\"${provider_key}\" | .ephemeral_resource_schemas // {} | keys | sort)}" + jq -r "{\"$PROVIDER\": (.provider_schemas.\"${provider_key}\" | .ephemeral_resource_schemas // {} | keys | sort)}" <<< "$SCHEMA" else echo "{\"$PROVIDER\": []}" >&2 echo "Note: Provider '${PROVIDER}' not found in current configuration." >&2 @@ -32,10 +34,10 @@ if [ -n "$PROVIDER" ]; then fi else # All providers declared in current config - terraform providers schema -json 2>/dev/null | jq -r ' + jq -r ' .provider_schemas | to_entries | map({key: (.key | split("/")[-1]), value: (.value.ephemeral_resource_schemas // {} | keys | sort)}) | from_entries - ' + ' <<< "$SCHEMA" fi diff --git a/terraform/code-generation/skills/terraform-modernize/scripts/check_writeonly_support.sh b/terraform/code-generation/skills/terraform-modernize/scripts/check_writeonly_support.sh index 74a330c..09cbfe6 100755 --- a/terraform/code-generation/skills/terraform-modernize/scripts/check_writeonly_support.sh +++ b/terraform/code-generation/skills/terraform-modernize/scripts/check_writeonly_support.sh @@ -25,8 +25,9 @@ if [ ! -d ".terraform" ]; then terraform init -upgrade > /dev/null 2>&1 fi -# Get provider key -provider_key=$(terraform providers schema -json 2>/dev/null | jq -r '.provider_schemas | keys[]' | grep "/${PROVIDER}$" || true) +# Cache provider schema output for reuse within script +SCHEMA=$(terraform providers schema -json 2>/dev/null) +provider_key=$(jq -r '.provider_schemas | keys[]' <<< "$SCHEMA" | grep "/$1$" || true) if [ -z "$provider_key" ]; then echo "{}" >&2 @@ -38,7 +39,7 @@ fi # Extract write-only capable attributes from provider schema if [ -n "$RESOURCE" ]; then # Specific resource - terraform providers schema -json 2>/dev/null | jq -r --arg provider "$provider_key" --arg resource "$RESOURCE" ' + jq -r --arg provider "$provider_key" --arg resource "$RESOURCE" ' .provider_schemas[$provider].resource_schemas[$resource] // {} | { resource: $resource, @@ -49,10 +50,10 @@ if [ -n "$RESOURCE" ]; then | .key ] } - ' + ' <<< "$SCHEMA" else # All resources for provider - show which have write-only arguments - terraform providers schema -json 2>/dev/null | jq -r --arg provider "$provider_key" ' + jq -r --arg provider "$provider_key" ' .provider_schemas[$provider].resource_schemas // {} | to_entries | map({ @@ -65,5 +66,5 @@ else ] }) | map(select(.write_only_arguments | length > 0)) - ' + ' <<< "$SCHEMA" fi