Losing Terraform state is one of the most unsettling events in infrastructure engineering. One misplaced backend deletion, a corrupted terraform.tfstate file, or an expired lock and suddenly Terraform no longer knows what it manages. When your Terraform state is gone, the best move is not to guess or rebuild from memory — it’s to use Ansible to rebuild before Pulumi import. Ansible inventory, especially in a dynamic or well-maintained environment, is effectively a living record of the infrastructure you built. In a rescue workflow, Ansible inventory can guide Pulumi import by identifying the resources that Terraform left behind and giving you the IDs and metadata needed to bring them under Pulumi control.
This article walks through a practical recovery sequence. You will learn how to convert an Ansible inventory into a resource map, generate a Pulumi import list, and verify the results without relying on stale cloud console tabs or memory.
Why Ansible Inventory Is the Best Recovery Source After Terraform State Loss
Cloud providers can show you every running resource, but they rarely tell you how those resources fit together. Terraform state, when healthy, stores that relational map: resource types, logical names, dependencies, and in-cloud IDs. When that state disappears, you need another layer of infrastructure data that already exists as code or structured output. Ansible inventory is often that layer.
In most infrastructure teams, Ansible inventory includes more than just hostnames. It contains groups, variables, IP addresses, cloud provider tags, and sometimes the very IDs Terraform needs for import. Even a static INI or YAML inventory gives you a list of logical names and services. A dynamic inventory can query AWS EC2, Azure VMs, GCP instances, or a CMDB to produce a rich JSON snapshot.
This is not a generic overview of “how to use Ansible.” This is a targeted rescue playbook that treats inventory as source-of-truth metadata for the next generation of infrastructure-as-code.
Step 1: Create a Clean Ansible Inventory Snapshot
Before importing anything, you need an unambiguous snapshot of the current infrastructure. Do not rely on ansible ping or a live in-memory inventory. Instead, dump the inventory to a static JSON file that you can reuse while building the Pulumi project.
Start with the ansible-inventory command:
ansible-inventory -i inventory.yml --list --yaml > inventory-snapshot.yml
This produces a resolved inventory with all groups, hosts, and variables. If you use a dynamic inventory script, this snapshot becomes even more valuable because it captures the cloud API’s view at the moment of recovery.
Next, enrich the snapshot with Ansible facts. A simple playbook that gathers facts and stores them locally can add instance IDs, public and private IPs, security groups, disks, and operating system details:
ansible all -i inventory.yml -m setup --tree ./facts/
These facts will help you match inventory hosts to Pulumi resource constructs. For example, an EC2 instance’s instance-id fact is exactly what pulumi import needs.
Step 2: Map Inventory Entries to Pulumi Resource Types
With a clean snapshot, the work shifts to mapping. Every Ansible group or hostname should map to a Pulumi resource type and logical name. For example:
[web]group hosts →aws:ec2/instance:Instance[database]group hosts →aws:rds/instance:Instanceoraws:db/instance:Instance[loadbalancer]group hosts →aws:lb/loadBalancer:LoadBalancer[cache]group hosts →aws:elasticache/cluster:Cluster
This mapping is the heart of the rescue workflow. The Ansible inventory gives you the set of resources to import; your own knowledge of the existing architecture tells you how those resources relate. Use inventory variables to enrich the mapping. If a host has a variable like vpc_id or subnet_id, carry that into the Pulumi resource configuration.
You are not trying to import every cloud resource at once. Start with the compute layer that inventory describes best. Then move to dependencies like subnets, security groups, and load balancers.
Step 3: Generate a Pulumi Import List
Pulumi’s import command is straightforward: pulumi import <type> <logicalName> <id>. The hard part is producing an accurate list quickly. Your inventory snapshot is the raw material.
You can generate the import list with a small script that reads the inventory JSON and emits Pulumi import commands. For example, a Python script could iterate through the _meta.hostvars section and create:
pulumi import aws:ec2/instance:Instance web-01 i-0abc123def456
Do not try to do this manually for more than a handful of resources. The entire point of using Ansible inventory is to automate the discovery phase. A generated import list is also easier to review because it is just text. You can compare it against your inventory snapshot and catch mistakes before running anything.
Keep the generated import list in the same directory as your new Pulumi project. You will likely need to rerun parts of it if the import fails due to dependency ordering.
Step 4: Try a Dry Run with Ansible Facts and Pulumi Preview
Import commands do not need to be executed blindly. Pulumi supports preview after resources are imported, but you can go further by using Ansible to verify the expected state before import.
Run an Ansible playbook that checks for the critical identifiers your inventory claims exist. For instance:
- Ping the host and verify the instance ID matches the inventory fact.
- Confirm security group names are still present.
- Check that tags and volumes exist as expected.
This step catches the worst failure mode: importing a resource that no longer exists or that has been replaced since the inventory was written. After the Ansible validation passes, run pulumi preview. Pulumi will show you what it thinks should happen. If it sees too many changes, you may have mapped the wrong resource IDs or omitted a dependency.
Step 5: Import, Reconcile, and Verify
When the preview looks clean, execute the import list. Start with foundational resources first: VPCs, subnets, security groups, and IAM roles. Then import compute resources that depend on them. If you have already generated a Pulumi program skeleton, the import command will fill in the resource definitions with real properties from the cloud provider.
After the import sequence completes, run pulumi up to reconcile any configuration drift. There may be small differences between your inventory-derived configuration and the actual cloud resource state. Pulumi will detect those differences. You can update the program to match reality or let Pulumi modify the resource to match your desired state.
Then use Ansible one more time for post-import verification. A final playbook that checks service health, endpoint availability, and configuration consistency ensures that the import did not disrupt running workloads. This is the moment when the Ansible inventory transitions from a recovery map back to its normal operational role.
When Ansible Inventory Isn’t Enough
Ansible inventory is rarely a complete record of every piece of infrastructure. Managed services, such as database clusters, message queues, and load balancers, are often represented indirectly or not at all. In those cases, combine the inventory with other recovery sources:
- Cloud provider resource explorers and CLI queries
- Infrastructure diagrams or runbooks
- DNS records and certificate transparency logs
- Cloud trail or activity logs showing resource creation events
Use the inventory as the skeleton, then fill in the missing pieces using whatever read-only evidence is available. The goal is not perfection; it is a complete enough resource map to make Pulumi import safe and deterministic.
Conclusion
Losing Terraform state does not have to mean losing control of your infrastructure. By treating Ansible inventory as a recovery map, you can systematically identify resources, generate Pulumi import commands, and restore infrastructure description as code. The workflow is practical, repeatable, and turns a moment of panic into a methodical process. The next time state disappears, you will already know exactly how to rebuild before Pulumi import.
