Migrate ClickOps to IaC
Transform manually-created AWS resources into version-controlled Terraform code without recreating them.
Scenario
Your team has been creating AWS resources through the console for months or years. You now need to:
- Bring everything under version control
- Enable reproducible deployments
- Meet compliance requirements
- Stop "who changed what?" mysteries
This guide shows how to use ops0's Discovery feature to scan, import, and manage existing resources.
Prerequisites
Step 1: Run a Discovery Scan
What Gets Scanned
ops0 discovers resources across these categories:
| Category | Resources |
|---|---|
| Compute | EC2 instances, Auto Scaling groups, Lambda functions |
| Networking | VPCs, subnets, security groups, NAT gateways, load balancers |
| Storage | S3 buckets, EBS volumes, EFS file systems |
| Database | RDS instances, DynamoDB tables, ElastiCache clusters |
| Security | IAM roles, policies, KMS keys |
| Containers | ECS clusters, EKS clusters, ECR repositories |
Step 2: Review Discovered Resources
After the scan completes, you'll see a list of all discovered resources.
Understanding the Status
| Status | Meaning |
|---|---|
| Unmanaged | Resource exists in AWS but not in any Terraform state |
| Managed | Resource is already tracked by an ops0 project |
| Drifted | Resource differs from Terraform state (manual change detected) |
Filtering Results
Use filters to focus on what matters:
Step 3: Select Resources to Import
Not all resources need to be imported immediately. Start with a logical group:
Recommended Import Order
Select resources by clicking the checkbox next to each one, or use Select All for the filtered view.
Step 4: Create Terraform Code
With resources selected, click Create Terraform.
ops0 will:
- Analyze each resource's current configuration
- Produce Terraform code that matches the live state
- Organize code into logical files (vpc.tf, ec2.tf, etc.)
Example Code
For an EC2 instance:
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.medium"
subnet_id = aws_subnet.private_1a.id
vpc_security_group_ids = [
aws_security_group.web.id
]
root_block_device {
volume_size = 50
volume_type = "gp3"
encrypted = true
}
tags = {
Name = "web-server"
Environment = "production"
}
}
Review the Code
ops0 highlights potential issues:
• Hardcoded AMI IDs (consider using data sources)
• Missing lifecycle rules (prevent accidental destruction)
• Secrets in environment variables (move to Secrets Manager)
Step 5: Import into Terraform State
This is the critical step - importing resources into Terraform state without recreating them.
terraform import for each resourceImport Log Example
Importing aws_vpc.main...
terraform import aws_vpc.main vpc-0123456789abcdef
Import successful!
Importing aws_subnet.private_1a...
terraform import aws_subnet.private_1a subnet-0123456789abcdef
Import successful!
Importing aws_instance.web_server...
terraform import aws_instance.web_server i-0123456789abcdef
Import successful!
Import complete: 3 resources imported
Step 6: Verify No Changes
After import, run a Terraform plan to confirm the code matches reality:
Expected Output
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration
and found no differences, so no changes are needed.
If there are differences, ops0's AI can help you fix the Terraform code to match the live resource exactly.
Step 7: Set Up Ongoing Drift Detection
Now that resources are managed, detect future manual changes:
When drift is detected, you can:
- View what changed
- Update Terraform to match (accept the manual change)
- Revert to Terraform state (undo the manual change)
Troubleshooting
Import Fails with "Resource already managed"
The resource is already in another Terraform state. Check:
- Other ops0 projects
- External Terraform workspaces
- Terraform Cloud/Enterprise
Plan Shows Unexpected Changes
Common causes:
| Issue | Solution |
|---|---|
| Default values | AWS adds defaults that weren't in original config. Add them explicitly to Terraform. |
| Computed attributes | Some attributes are computed. Add lifecycle { ignore_changes = [...] }. |
| Order differences | Lists may be in different order. Sort them in Terraform. |
Large Number of Resources
For 100+ resources:
- Import in batches by resource type
- Start with one environment (dev) before production
- Use Discovery filters to focus on specific VPCs or regions
Best Practices
prevent_destroy = true to avoid accidents.