It is possible to manage imports.tf
with a simple script instead of managing terraform.tfstate
. In contrast to the state, imports.tf
doesn’t contain credentials and can be safely managed with source version control systems.
Intro
We like
Terraform
as a tool to manage infrastructure as code. terraform apply
aligns the managed infrastructure’s actual state with a desired state defined in Terraform configuration files. Terraform maintains a file terraform.tfstate
which
the official documentation says
is required:
Terraform must store state about your managed infrastructure and configuration. This state is used by Terraform to map real world resources to your configuration, keep track of metadata, and to improve performance for large infrastructures.
But how does Terraform know the state is up-to-date if it relies on a local or shared file? It doesn’t:
Prior to any operation, Terraform does a refresh to update the state with the real infrastructure.
State is always a source of error and complexity. Also, the .tfstate
file may contain confidential information like credentials. This article shows how we use Terraform without sharing .tfstate
.
Swapping .tfstate
for imports.tf
The state file is required by Terraform to understand how resources defined in the desired state relate to those on the infrastructure. This is done by mapping identifiers. Terraform understands from the state if a resource shall be created or changed.
But what if infrastructure that wasn’t created with Terraform shall be managed with it? For this, Terraform supports importing resources into the state with the import
block.
Example
The desired state is an OpenStack Public Floating IP defined in main.tf
:
resource "openstack_networking_floatingip_v2" "my_ip" {
pool = "my-pool"
}
After terraform apply
, the resulting terraform.tfstate
contains a relation between identifiers inside the Terraform configuration and the cloud resource:
{
// ...
"resources": [
{
// Identification of the resource inside the terraform files (desired state)
"mode": "managed",
"type": "openstack_networking_floatingip_v2",
"name": "my_ip",
"provider": "provider[\"registry.terraform.io/terraform-provider-openstack/openstack\"]",
"instances": [
{
"attributes": {
// Identification of the resource in the managed infrastructure
"id": "2b13d767-7446-4a91-a9ff-6fa63a25303e",
// ...
}
// ...
}
]
}
// ...
]
}
The same state can be obtained by importing the existing resource using imports.tf
with terraform refresh
(or apply
):
import {
to = openstack_networking_floatingip_v2.my_ip
id = "2b13d767-7446-4a91-a9ff-6fa63a25303e"
}
A Jsonnet script to generate imports.tf
After terraform apply
or terraform refresh
, terraform.tfstate
is up to date. imports.tf
can then be derived from it with a script.
We use this
Jsonnet
script to generate imports.tf
from terraform.tfstate
:
# tfstate.jsonnet
# LICENSE: MIT. Copyright 2025 Daydream Unicorn GmbH & Co. KG
function(tfstate)
std.join('\n', [
] +
std.flattenArrays(
std.map(
function(e) ['import {\n to = %(module)s%(type)s.%(name)s%(index_key)s\n id = "%(id)s"\n}' % {
module: if std.objectHas(e, 'module') then e.module + '.' else '',
type: e.type,
name: e.name,
id: x.id,
index_key: if std.get(x, 'index_key') == null then '' else '["' + std.get(x, 'index_key') + '"]'
} for x in [{ id: i.attributes.id, index_key: std.get(i, 'index_key') } for i in e.instances] ],
std.filter(
function(e) e != null && std.objectHas(e, "mode") && e.mode == "managed",
tfstate.resources
)
)
) +[
]
)
Usage:
#/bin/bash
jsonnet tfstate.jsonnet --tla-code-file tfstate=terraform.tfstate -S > imports.tf
Workflow
#!/bin/bash
git clone <your terraform repo>
cd <your terraform repo>
# Import and refresh the state
terraform refresh
# This will print "Warning: Empty or non-existent state", which can be safely ignored
# Make your changes, then apply
terraform apply
# When satisified with the result, save the state
jsonnet tfstate.jsonnet --tla-code-file tfstate=terraform.tfstate -S > imports.tf
rm terraform.tfstate
git add <changed files>
git add imports.tf
imports.tf
must be regenerated whenterraform.tfstate
has changed, it can be committed together with related changes.- When resource identification changes in the configuration, e.g. due to renaming of resources inside the Terraform configuration files, IDs must be updated in
imports.tf
andterraform.tfstate
. Ensure thatimports.tf
is up to date before making such changes. - Unreadable secrets can be handled by using
terraform apply -var=my_secret=(password manager command to obtain the secret) ...
Limitations
- It has not been investigated if resources.instances.attributes.id is always the identifying field, this might or might not depend on the provider.
- The statefiles format might change.
- Terraform uses the state for performance optimization, importing many resources can take a while. The stateless approach might be too slow if many resources are managed with the same
terraform apply
. - Dependencies might have to be
modeled explicitly in the Terraform configuration with
depends_on
- Some properties might not be readable from the infrastructure. While this is a limitation of the respective infrastructure provider and not of this workflow, Terraform shows a warning about it more often because it assumes those properties are known if it can read them from state.