TL;DR: Import adoption in waves: inventory from cloud APIs (not from memory), generate config per wave, import state, then verify with a plan that shows zero changes before moving on. Never import everything at once, never let the first real plan be a surprise, and treat resources nobody can explain as incidents to investigate rather than lines to import.
How to approach it
Structure by waves with an acceptance gate per wave. Name the tooling (import blocks or import command, plus config generation), but score comes from the process: how you handle unknown resources, dependencies, and the moment of truth when you first plan against imported state.
A strong answer
Wave 0: inventory from APIs, not from humans. List actual resources via cloud provider APIs or AWS Config, Resource Explorer or explicitly read-only provider inventory tools. Human memory and old wiki pages miss resources systematically: the test environment someone forgot, the S3 bucket created during an incident two years ago. Diff the API truth against whatever documentation exists; every mismatch is a finding, not noise.
Wave 1: the boring and low-risk. Start where mistakes are cheap and patterns are learnable: an isolated non-production stack with known owners and a verified recovery path. VPCs, routes and security groups may have a large blast radius despite simple configuration.
Per-resource loop within a wave:
- Write the configuration (generated scaffolds from
terraform plan -generate-config-outprovide a starting point; hand-finish for correctness). - Add an
importblock withto = aws_subnet.mainandid = "subnet-123"on separate lines, or use the equivalent CLI import. - Generate and review a saved import plan. Configuration-driven import can also propose updates or replacements. Apply only after verifying that this adoption plan imports the intended bindings without resource mutations; otherwise fix the configuration first.
- Plan must return "No changes." This is the acceptance gate. Every diff is either config that does not match reality (fix the HCL) or reality that should not match config (a manual change to keep, documented). A wave ships only when its plan reads clean.
Where the difficulty concentrates:
- Defaults that are not default. Providers often show explicit values for attributes you never set (an instance field populated by the API differently per region). Plans surface them as perpetual diffs until you align config or add ignore rules judiciously. Blanket
ignore_changeshides real drift; use it surgically with comments explaining why. - Computed and interdependent values. Hand-built environments have implicit wiring (that subnet chosen because of an ACL added later) that modules express as references. Recreating the graph in code occasionally reveals decisions nobody remembers making; those become architecture conversations, which is a feature of this exercise.
- Resources that cannot be safely expressed yet. Some legacy things (a database with years of unmanaged configuration, a DNS zone others mutate directly) resist clean codification. Marking a resource as explicitly out-of-scope with a dated decision beats importing it badly.
- State hygiene throughout. Imports go into the same remote backend conventions as the rest of the estate: locking on, least-privilege access, and backups enabled before the first apply, because a corrupted state file mid-adoption is worse than no adoption.
Cutover discipline. Once a wave is adopted, the rule changes behaviour: changes to imported resources happen only through pull requests. Announce it, enforce it in review, expect violations in week one, and respond by fixing the workflow friction rather than scolding, because drift re-emerges exactly where the new path is harder than the console.
What interviewers probe next
"Plan shows a diff on an attribute nobody set. Options?" Align config to observed value if meaningful; ignore with documented reason if cosmetic; investigate if unexpected (it may be another automation writing to the same field).
"How do you prevent the console habit returning?" Read-only IAM for human identities on adopted resources, with break-glass roles logged and alarmed. Make the right path the low-friction path.
"What about secrets the hand-built setup embedded?" Expect to find credentials in user data, tags and AMIs. Rotation into a secrets manager becomes part of the migration backlog, flagged early because rotation has its own risk window.
Common mistakes
Big-bang import of everything, producing a thousand-line plan nobody can review and a team afraid to apply anything.
Trusting generated config blindly. Generation reflects current state including its accidents; without review you codify the mess you were escaping.
Declaring victory at import completion. Adoption completes when the console path is closed and plans stay clean, not when state files fill up.
Sources: Configuration-driven import.