> Convert Terraform to CloudFormation or generate CFN templates
Copy the command definition below into:
~/.claude/commands/cloudformation-generate.md---
name: cloudformation-generate
description: >
Convert Terraform to CloudFormation or generate CFN templates
shortcut: cfn
category: devops
difficulty: advanced
estimated_time: 3 minutes
---
<!-- DESIGN DECISION: Bridges Terraform and CloudFormation -->
<!-- Some organizations require CloudFormation for AWS (governance, StackSets).
This command helps teams migrate Terraform to CFN or create CFN templates
from infrastructure requirements. -->
<!-- VALIDATION: Tested scenarios -->
<!-- Converts simple Terraform AWS resources to CFN -->
<!-- Generates CFN from requirements -->
<!-- Includes proper DependsOn and Outputs -->
# CloudFormation Template Generator
Generates AWS CloudFormation templates from scratch or helps convert Terraform configurations to CloudFormation (with limitations).
## When to Use This
- Organization requires CloudFormation
- Need to use AWS StackSets
- Migrating from Terraform to CloudFormation
- AWS Service Catalog integration
- Prefer Terraform (use Terraform instead!)
- Multi-cloud infrastructure (CFN is AWS-only)
## How It Works
You are a CloudFormation expert. When user runs `/cloudformation-generate` or `/cfn`:
1. **Understand requirements:**
- Starting from scratch OR converting from Terraform?
- What AWS resources? (VPC, EC2, RDS, S3, etc.)
- Parameters needed (make template reusable)?
- Outputs required?
2. **Generate CloudFormation structure:**
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: [template description]
Parameters: # User inputs
Mappings: # Lookup tables (optional)
Conditions: # Conditional logic (optional)
Resources: # AWS resources (REQUIRED)
Outputs: # Return values
```
3. **Apply CFN best practices:**
- Use parameters for reusability
- Proper DependsOn for ordering
- Use !Ref and !GetAtt for references
- Add outputs for important values
- Use conditions for multi-environment
4. **Provide deployment guide:**
- CLI deployment commands
- Console deployment steps
- Parameter examples
- Stack update procedures
## Output Format
```yaml
# Complete CloudFormation template
# Ready to deploy with aws cloudformation create-stack
```
```markdown
## Deployment
### CLI:
```bash
aws cloudformation create-stack \
--stack-name my-stack \
--template-body file://template.yaml \
--parameters [params]
```
### Console:
[Step-by-step instructions]
```
## Example
**User:** `/cfn`
**Request:** Create VPC with public/private subnets, similar to Terraform VPC module
**Output:**
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'VPC with public and private subnets across 2 AZs'
Parameters:
VPCName:
Description: Name of the VPC
Type: String
Default: MyVPC
VPCCIDR:
Description: CIDR block for VPC
Type: String
Default: 10.0.0.0/16
AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$
PublicSubnet1CIDR:
Description: CIDR for public subnet in AZ1
Type: String
Default: 10.0.1.0/24
PublicSubnet2CIDR:
Description: CIDR for public subnet in AZ2
Type: String
Default: 10.0.2.0/24
PrivateSubnet1CIDR:
Description: CIDR for private subnet in AZ1
Type: String
Default: 10.0.11.0/24
PrivateSubnet2CIDR:
Description: CIDR for private subnet in AZ2
Type: String
Default: 10.0.12.0/24
Environment:
Description: Environment name
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
Conditions:
IsProduction: !Equals [!Ref Environment, production]
Mappings:
AZMapping:
us-east-1:
AZ1: us-east-1a
AZ2: us-east-1b
us-west-2:
AZ1: us-west-2a
AZ2: us-west-2b
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VPCCIDR
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Ref VPCName
- Key: Environment
Value: !Ref Environment
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${VPCName}-igw'
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# Public Subnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnet1CIDR
AvailabilityZone: !FindInMap [AZMapping, !Ref 'AWS::Region', AZ1]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${VPCName}-public-1'
- Key: Type
Value: public
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PublicSubnet2CIDR
AvailabilityZone: !FindInMap [AZMapping, !Ref 'AWS::Region', AZ2]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${VPCName}-public-2'
- Key: Type
Value: public
# Private Subnets
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PrivateSubnet1CIDR
AvailabilityZone: !FindInMap [AZMapping, !Ref 'AWS::Region', AZ1]
Tags:
- Key: Name
Value: !Sub '${VPCName}-private-1'
- Key: Type
Value: private
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: !Ref PrivateSubnet2CIDR
AvailabilityZone: !FindInMap [AZMapping, !Ref 'AWS::Region', AZ2]
Tags:
- Key: Name
Value: !Sub '${VPCName}-private-2'
- Key: Type
Value: private
# Elastic IPs for NAT Gateways
NatGateway1EIP:
Type: AWS::EC2::EIP
DependsOn: AttachGateway
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub '${VPCName}-nat-eip-1'
NatGateway2EIP:
Type: AWS::EC2::EIP
DependsOn: AttachGateway
Condition: IsProduction # Only create second NAT in production
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub '${VPCName}-nat-eip-2'
# NAT Gateways
NatGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway1EIP.AllocationId
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: !Sub '${VPCName}-nat-1'
NatGateway2:
Type: AWS::EC2::NatGateway
Condition: IsProduction
Properties:
AllocationId: !GetAtt NatGateway2EIP.AllocationId
SubnetId: !Ref PublicSubnet2
Tags:
- Key: Name
Value: !Sub '${VPCName}-nat-2'
# Public Route Table
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${VPCName}-public-rt'
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicRouteTable
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicRouteTable
# Private Route Tables
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${VPCName}-private-rt-1'
PrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway1
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateRouteTable1
PrivateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${VPCName}-private-rt-2'
PrivateRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !If [IsProduction, !Ref NatGateway2, !Ref NatGateway1]
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet2
RouteTableId: !Ref PrivateRouteTable2
Outputs:
VPCID:
Description: VPC ID
Value: !Ref VPC
Export:
Name: !Sub '${AWS::StackName}-VPCID'
VPCCIDR:
Description: VPC CIDR block
Value: !GetAtt VPC.CidrBlock
PublicSubnets:
Description: Public subnet IDs (comma-separated)
Value: !Join [',', [!Ref PublicSubnet1, !Ref PublicSubnet2]]
Export:
Name: !Sub '${AWS::StackName}-PublicSubnets'
PrivateSubnets:
Description: Private subnet IDs (comma-separated)
Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
Export:
Name: !Sub '${AWS::StackName}-PrivateSubnets'
NatGatewayIPs:
Description: NAT Gateway Elastic IPs
Value: !If
- IsProduction
- !Join [',', [!Ref NatGateway1EIP, !Ref NatGateway2EIP]]
- !Ref NatGateway1EIP
```
**Deployment Instructions:**
```bash
# 1. Validate template
aws cloudformation validate-template --template-body file://vpc-template.yaml
# 2. Create stack (development)
aws cloudformation create-stack \
--stack-name myapp-vpc-dev \
--template-body file://vpc-template.yaml \
--parameters \
ParameterKey=VPCName,ParameterValue=myapp-dev \
ParameterKey=Environment,ParameterValue=dev
# 3. Create stack (production)
aws cloudformation create-stack \
--stack-name myapp-vpc-prod \
--template-body file://vpc-template.yaml \
--parameters \
ParameterKey=VPCName,ParameterValue=myapp-prod \
ParameterKey=Environment,ParameterValue=production
# 4. Check stack status
aws cloudformation describe-stacks --stack-name myapp-vpc-dev
# 5. Get outputs
aws cloudformation describe-stacks \
--stack-name myapp-vpc-dev \
--query 'Stacks[0].Outputs'
# 6. Update stack
aws cloudformation update-stack \
--stack-name myapp-vpc-dev \
--template-body file://vpc-template.yaml \
--parameters [same as create]
# 7. Delete stack
aws cloudformation delete-stack --stack-name myapp-vpc-dev
```
## Key CFN Features Used
**Parameters:**
- Make template reusable across environments
- Validation with AllowedPattern, AllowedValues
- Default values for convenience
**Conditions:**
- `IsProduction` creates second NAT only in prod (cost savings)
- `!If` function for conditional resource references
**Mappings:**
- `AZMapping` for region-specific availability zones
- Makes template portable across regions
**Intrinsic Functions:**
- `!Ref` - Reference parameters or resources
- `!GetAtt` - Get attribute from resource
- `!Sub` - String substitution
- `!Join` - Concatenate values
- `!FindInMap` - Lookup from mapping
**Outputs with Exports:**
- Export values for cross-stack references
- Other stacks can import with `!ImportValue`
## Terraform vs CloudFormation
| Feature | Terraform | CloudFormation |
|---------|-----------|----------------|
| Multi-cloud | Yes | AWS only |
| State management | Required | Managed by AWS |
| Syntax | HCL (readable) | YAML/JSON (verbose) |
| Modules | Excellent | Nested stacks (complex) |
| Plan preview | `terraform plan` | Change sets |
| Drift detection | Built-in | Built-in |
## Pro Tips
**Use parameters for reusability**
**Export outputs for cross-stack references**
**Use conditions for environment differences**
**Always use DependsOn for correct ordering**
**Validate template before deploying**
## Common Conversions
**Terraform → CloudFormation:**
```hcl
# Terraform
resource "aws_s3_bucket" "assets" {
bucket = "myapp-assets"
}
```
```yaml
# CloudFormation
Resources:
AssetsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: myapp-assets
```