
We've already covered the mechanics of a single terraform import run, and separately looked at bulk import strategies like scripting and environment scans for larger batches. Both hold up fine once you already know which resources you're targeting. What neither one solves is the step before that: at the scale most brownfield environments actually present, the honest answer to “which resources need importing” is “we don't know, we inherited this.”
This piece picks up that specific problem: finding hundreds of unmanaged resources across accounts and clouds, deciding which ones matter first, and bringing them under management without turning the whole exercise into a six-month side project. That covers Terraform's own new bulk-discovery workflow, where it stops being enough on its own, and how CloudQuery and env zero's Cloud Compass close the gap between finding something and actually governing it.
Why single-resource import doesn't scale
terraform import and the declarative import block both assume you already know what you're importing: a resource address, a provider-specific ID, a resource block waiting to receive it. That's a reasonable assumption for one EC2 instance or one S3 bucket. It stops being reasonable once you're looking at an account that predates your IaC adoption by several years, or one that arrived through an acquisition with no documentation at all.
The actual bottleneck at that scale usually isn't the import command. It's discovery: knowing which resources exist, which of them are already managed somewhere, and which ones carry enough risk (an IAM role, a public S3 bucket, a security group with an open ingress rule) that they should jump the queue. Guessing your way through that with console tabs open across three accounts doesn't scale any better than running terraform import by hand for every resource does.
Terraform's native answer: list blocks and terraform query
Terraform 1.14, generally available since November 2025, shipped a native answer to part of this problem. A new .tfquery.hcl file type holds list blocks that query your provider for resources matching a filter, and the CLI gained a companion terraform query command to run those queries and, optionally, generate importable configuration from whatever it finds. Requirement-wise, resource-identity-based search needs Terraform 1.12 or newer, with the full workflow reaching general availability in 1.14.
A query file looks like this:
Run terraform query and Terraform prints what it found, each result labeled with its list.. reference and resource identity. Add the -generate-config-out flag and it writes matching resource and import blocks, identity included, to a file you review and copy into your configuration before running terraform apply:
That's a real improvement over writing a dozen import blocks by hand, and it's worth knowing on its own merits. Two limits are worth flagging before you plan around it, though. First, list support has to exist for the specific resource type in that specific provider, so coverage varies and you should check your provider's documentation rather than assume it. Second, the polished side-by-side review experience (comparing what the query found against what's already tracked elsewhere) lives in the HCP Terraform UI and needs the cloud block configured to connect. Running terraform query itself doesn't require HCP Terraform, but the discovery it does is scoped to one provider configuration at a time.
Where native search runs out of room
For a single AWS account and a resource type your provider already supports, native query blocks are the right tool and probably the only one you need. The gap shows up at the scale that actually produces years of undocumented ClickOps: multiple clouds, SaaS platforms and identity providers alongside them, and a need to ask questions that don't map cleanly onto one provider's filter syntax, like “which storage resources across every cloud we run have no Terraform-managed tag at all.” A list block answers questions about one resource type in one provider. It doesn't give you a single place to ask that question once and get an answer back.
CloudQuery: one inventory across everything, not just what a single provider's list block supports
This is the problem CloudQuery, which merged with env zero in 2026, was built to solve before Terraform's own query workflow existed. CloudQuery syncs resource data from over 70 sources, cloud providers, GitHub, Okta, and more, into a single normalized, SQL-queryable layer. Every synced resource lands in a unified Asset Inventory with consistent schemas across providers, full-text search, and filters you can save and share. For anything a saved filter can't express (joins across resource types, aggregations, multi-condition logic) the SQL Console runs queries directly against the same data.
That matters for a question people actually ask, in one form or another, constantly: which resources across the whole environment have no tags at all, broken out by type? A resource nobody bothered to tag is usually a resource no pipeline, Terraform included, ever touched, which makes it a decent starting signal for “probably unmanaged.” Answering that by hand means maintaining a list of which of AWS's roughly 330 resource types actually support tags (the unified tagging API only covers about 140 of them), calling the rest through separate per-service APIs, and remembering that things like IAM role policies have to be excluded entirely since they're inline JSON attached to a role rather than a taggable resource in their own right. None of that per-service, per-account legwork exists once everything is already sitting in one normalized inventory. The sketch below isn't a documented CloudQuery query, table and column names depend on which sources you've synced and how, but it shows the shape of the question:
For the real schema and supported query patterns, CloudQuery's SQL Console documentation is the source of record. The point holding up here isn't the exact syntax, it's that one query, run once, covers every connected cloud and account, where the manual version means hand-maintaining a taggable-type list and calling a different API per service, and a single list block still only covers one resource type in one provider.
From discovery to governed import with env zero
Finding a resource and safely bringing it under management are two different problems, and the second one is where env zero's own Cloud Compass comes in. Cloud Compass scans your connected cloud accounts, categorizes each resource by how it's actually been touched (ClickOps console changes, API or CLI calls, IaC operations) and scores each one on a severity scale that weighs both the mix of those operation types and how sensitive the service itself is. IAM, KMS, and EC2 carry more weight than a low-traffic S3 bucket, for the same reason a security team would care more about one than the other.
Select a resource in Cloud Compass and generate IaC code for it directly, Terraform or OpenTofu, ready to review and commit. Whether a given resource's config comes from Cloud Compass, from terraform query -generate-config-out, or from CloudQuery's inventory data feeding a hand-written import block, the resource lands in env zero the same way anything else does: through a normal PR-based plan. OPA policies evaluate the newly imported resource before it merges, the standard approval gates apply, and once it's in, drift detection owns it going forward the same as infrastructure env zero provisioned from scratch.
That's the practical version of the two companies' post-merger story: CloudQuery finds what exists across your entire environment, not just what one Terraform provider's list block happens to cover, and env zero is where that discovery turns into something governed rather than a one-time cleanup that drifts right back into ClickOps six months later.
A practical workflow for bulk import
- Inventory everything first. Run a CloudQuery sync across your connected accounts and integrations before importing anything. You want the full picture, not just the corner of it one provider's query supports.
- Prioritize by risk, not by convenience. Cloud Compass's severity scoring, or a manual pass weighting IAM, networking, and data stores above everything else, tells you which unmanaged resources represent real exposure versus which ones can wait.
- Generate configuration, don't hand-write hundreds of resource blocks. Use Cloud Compass's generate-code action,
terraform query -generate-config-outfor resource types it supports, or both, depending on where a given resource showed up. - Land it through a normal PR. Import in batches small enough to review, not one sweeping commit. Each batch gets a plan, a policy check, and an approval, the same as any other change.
- Re-run discovery and confirm. After each batch, check that the imported resources actually show up as IaC-managed rather than assuming the import succeeded because the apply didn't error.
Does OpenTofu support the same bulk query workflow?
Not yet, and this is one of the few places in this series where Terraform and OpenTofu genuinely diverge rather than matching each other feature for feature. an open feature request on OpenTofu's repository asks for a tofu query command and provider list resource support mirroring Terraform 1.14, and as of this writing it's still open. OpenTofu does support for_each on import blocks since version 1.7, which lets you bulk-import many resources of the same type through a single block once you already know their identifiers, the same for_each pattern that applies to resource and module blocks generally. What it doesn't yet have is the discovery half: a native way to ask a provider which resources exist that aren't in state yet. If you're on OpenTofu today, that gap is exactly where CloudQuery's provider-agnostic inventory carries more of the weight, since it doesn't depend on which engine eventually ships native query support.
Best practices for bulk import at scale
- Prioritize by service sensitivity first. An unmanaged IAM role or security group is worth importing before an unmanaged low-traffic storage bucket, regardless of which one your tooling happened to surface first.
- Import in reviewable batches. A single pull request with 200 new resource blocks defeats the purpose of routing imports through policy and approval in the first place.
- Pin provider versions across a batch. Resources imported under different provider versions can produce inconsistent generated configuration for the same resource type.
- Prefer
importblocks over one-off CLI commands for anything beyond a handful of resources, so the batch is reviewable as a diff rather than a sequence of commands someone ran locally. - Treat discovery as recurring, not a one-time project. Re-sync and re-scan on a schedule. New ClickOps resources accumulate the same way the original backlog did.
Frequently asked questions
Q. What's the difference between a single terraform import and bulk import?
A single import (terraform import or one import block) assumes you already know the resource address and provider ID you're targeting. Bulk import adds a discovery step in front of that, finding which resources exist and aren't yet managed, before any importing happens.
Q. Does terraform query require an HCP Terraform account?
No. terraform query runs from the Terraform CLI against your configured provider without needing HCP Terraform. HCP Terraform adds an optional UI for comparing query results against what's already tracked across your workspaces, but the underlying query and generate-config workflow works standalone.
Q. What is CloudQuery and how does it relate to env zero?
CloudQuery is a cloud asset inventory platform that syncs resource data from cloud providers and SaaS integrations into a single, SQL-queryable layer. env zero and CloudQuery merged in 2026, combining CloudQuery's discovery and inventory capabilities with env zero's IaC orchestration and governance.
Q. Should I use CloudQuery or Cloud Compass to find unmanaged resources?
They're complementary rather than competing. CloudQuery gives you a broad, cross-cloud, cross-integration inventory you can query with SQL. Cloud Compass builds on that with severity scoring specific to IaC coverage and a direct action to generate Terraform or OpenTofu code for a selected resource, inside the env zero workflow.
Q. Does OpenTofu support terraform query or list blocks?
Not currently. There's an open feature request on OpenTofu's GitHub repository asking for parity, but as of this writing OpenTofu's bulk-import story is for_each on import blocks (available since version 1.7), which simplifies importing many resources once you know their identifiers without adding a native discovery mechanism.
Q. How should I prioritize hundreds of unmanaged resources?
Weigh both the type of resource and how it's been touched. Services like IAM, KMS, and networking carry more risk than low-traffic storage or compute, and resources with a history of manual console changes carry more risk than ones only touched by scripts or existing IaC. Import the highest-risk combination first.
Key points
Single-resource import workflows, whether the CLI command or the declarative block, assume you already know what needs importing. At scale, that assumption is the actual bottleneck. Terraform's own terraform query workflow helps within a single provider's supported resource types. Beyond that, a provider-agnostic inventory like CloudQuery answers the broader question of what exists across your entire environment, and env zero's Cloud Compass turns that discovery into reviewed, policy-checked infrastructure rather than a spreadsheet that goes stale the day after you finish it.

































.avif)

