A declarative code generator for OpenTofu state migrations. Define resource moves, renames, imports, and removals in YAML, and statebridge generates the corresponding HCL code (import, moved, removed blocks) in your layer directories.
go install github.com/terraprovider/statebridge@latestOr build from source:
git clone https://github.com/terraprovider/statebridge.git
cd statebridge
go build -o statebridge .- Create a migration YAML file:
# migrations/001_move_web_server.yaml
description: "Move web server from shared compute to dedicated app layer"
operations:
- type: move
source_layer: "./layers/compute"
destination_layer: "./layers/app"
resources:
- from: "aws_instance.web"
import_id: "i-0abc123def456"- Generate the HCL:
statebridge generate migrations/001_move_web_server.yaml- This produces two files (one per layer, named with a content hash):
./layers/compute/migration.001_move_web_server.a1b2c3d4.tf
removed {
from = aws_instance.web
lifecycle {
destroy = false
}
}./layers/app/migration.001_move_web_server.e5f6a7b8.tf
import {
to = aws_instance.web
id = "i-0abc123def456"
}- Run
tofu planin each layer to verify, thentofu apply.
- type: move
source_layer: "./layers/compute"
destination_layer: "./layers/app"
resources:
- from: "aws_instance.web" # import_id auto-resolved from state
- from: "aws_instance.api"
import_id: "i-0abc123def456" # or provide explicitlyGenerates removed blocks in the source layer and import blocks in the destination.
When source_layer and destination_layer are the same, generates moved blocks by default (set use_moved_blocks: false to force removed + import instead):
- type: move
source_layer: "./layers/app"
destination_layer: "./layers/app"
source_prefix: "module.v1"
destination_prefix: "module.v2"
resources:
- from: "aws_instance.web"Use source_prefix / destination_prefix for independent control over each side's address prefix (move operations only). address_prefix remains available as a shorthand that applies to both sides.
- type: rename
layer: "./layers/networking"
renames:
- from: "aws_subnet.old"
to: "aws_subnet.new"Generates moved blocks.
- type: remove
layer: "./layers/iam"
entries:
- address: "aws_iam_role.deprecated" # keeps infrastructure by default
- address: "aws_iam_policy.old_policy"
destroy: true # or destroy it- type: import
layer: "./layers/database"
imports:
- address: "aws_db_instance.primary"
id: "my-database-identifier"Derive import IDs from an existing resource's state attributes. Useful when splitting a resource into sub-resources (e.g., splitting azuread_application into azuread_application_registration + azuread_api_access):
- type: import
layer: "./layers/identity"
imports:
- address: "azuread_application_registration.all"
id: '{{ .Attributes.id }}'
source:
layer: "./layers/identity"
address: "azuread_application.all"With attribute expansion — each element of a list attribute produces a separate import block:
- type: import
layer: "./layers/identity"
imports:
- address: "azuread_api_access.all"
id: '{{ .Attributes.id }}/apiAccess/{{ .Item.resource_app_id }}'
key: '{{ .Key }}_{{ .Item.resource_app_id }}'
source:
layer: "./layers/identity"
address: "azuread_application.all"
expand: "required_resource_access"# Move all resources under a module
- type: move
source_layer: "./layers/old"
destination_layer: "./layers/new"
resources:
- from: "module.foo"
# Or move everything from one layer to another
- type: move
source_layer: "./layers/old"
destination_layer: "./layers/new"
all_resources: true# Generate from a single file
statebridge generate migrations/001_move.yaml
# Generate from a directory (files sorted by name)
statebridge generate migrations/
# Preview without writing files
statebridge generate --dry-run migrations/| Flag | Description |
|---|---|
--dry-run |
Print generated HCL to stdout without writing files |
--tofu-path <path> |
Override path to the tofu binary |
--strict |
Treat missing layer directories as hard errors |
Conditions are automatically inferred from block types, making all migrations safe to re-run:
| Block type | Auto condition | Rationale |
|---|---|---|
removed |
resources_exist for source |
Skip if already gone |
import |
resources_not_exist for target |
Skip if already imported |
moved |
Both of the above | Skip if already renamed |
# 1. Generate and upload (from repo root)
statebridge generate --upload --backend-config=storage_account_name=myacct migrations/
# 2. Per layer: download applicable migrations
cd layers/compute && statebridge download --backend-config=storage_account_name=myacct
# 3. Plan and apply
statebridge plan --out=tfplan --detailed-exitcode
if [ $? -eq 2 ]; then
tofu apply tfplan
fi| Document | Description |
|---|---|
| CLI Reference | Full command and flag reference for generate, upload, download, plan, and prune |
| Migration File Format | Complete YAML schema — all operation types, fields, and validation rules |
| Keyed Moves | Moving for_each resources with key remapping, prefix patterns, and cross-operation splitting |
| Conditions | Auto-inferred and explicit conditions, layer_exists/layer_not_exists, address matching |
| Go Templates | Template context variables and all available functions for key transformations and import IDs |
| Azure Blob Storage | Upload, download, pruning, backend discovery, upload guard, and authentication |
| CI Workflow | Full CI integration guide, resilient processing, module consolidation, and strict mode |
pkg/
migration/ - YAML schema, parsing, and validation
state/ - OpenTofu state reading via terraform-exec
template/ - Go template evaluation with custom functions
generator/ - HCL block rendering, file output, and migration metadata
engine/ - Pipeline orchestration, key matching, wildcard tracking
conditions/ - Shared condition evaluation for upload guard and download
auth/ - Azure credential management (azcore, azidentity)
upload/ - Azure Blob Storage upload with overwrite protection guard
download/ - Download orchestration with condition evaluation
tofu/ - OpenTofu command execution and migration target scanning
- Go 1.26.1+ (for building)
- OpenTofu (
tofu) in PATH — required by all commands for state reading, condition evaluation, and backend initialization