Migrating from Terraform Cloud
To move from HCP Terraform (Terraform Cloud) or Terraform Enterprise to Stategraph, run a script that reads your workspaces through the API. It writes .stategraph/config.yml and the workspace variables, downloads the state of each workspace, and imports it into Infrastructure as a Database or pushes it to a bucket backend. Resolve the items that the script reports, and roll out on a pull request.
Plans and applies move from Terraform Cloud to your GitHub Actions or GitLab CI runners. Your Terraform code stays, without its cloud block.
How Terraform Cloud maps to Stategraph
| Terraform Cloud | Stategraph |
|---|---|
| A workspace | A dirs entry and its workspace, tagged with the workspace name |
Working directory, or the directory of the cloud block |
The dirs key |
| Several workspaces in one directory | Workspaces of one directory, one state each |
| Trigger prefixes, trigger patterns | when_modified.file_patterns |
| Automatic speculative plans off | autoplan: false |
| Auto-apply | autoapply |
| Apply after merge (VCS workflow) | apply_requirements with apply_after_merge |
| Terraform version | engine |
| Terraform variables, variable sets | A var file per workspace in .stategraph/tfc/vars |
| Environment variables | An env step that sources a file per workspace |
| Sensitive variables | GitHub secrets |
| Run triggers | depends_on, as layered runs |
| Dynamic provider credentials | oidc steps |
| VCS branch | destination_branches |
| Health assessments | drift.schedules |
| Workspace state | A state in Infrastructure as a Database, or a bucket backend |
Before you begin
- Connect the repository to Orchestration. See Stategraph Cloud or Self-hosted.
- Install Python 3.8 or later and PyYAML, and the Stategraph CLI.
- Create an organization API token in HCP Terraform under Settings → API tokens. It reads workspaces, variables, and state.
- For Infrastructure as a Database, export
STATEGRAPH_API_BASE,STATEGRAPH_API_KEY, andSTATEGRAPH_TENANT_ID. See Use with Orchestration.
Convert the workspaces
Run the script from the root of your repository. --repo selects the VCS workspaces of the repository. Add CLI-driven workspaces with --workspace:
curl -sSLO https://stategraph.com/assets/migrate/tfc-to-stategraph.py
pip install pyyaml
export TFC_TOKEN=<organization token>
python3 tfc-to-stategraph.py --org acme --repo acme/infra \
--workspace network --state-dir ../tfc-state \
--rewrite-code --report tfc-migration-report.md
The script writes:
.stategraph/config.yml.stategraph/tfc/vars/<dir>/<workspace>.tfvars: the Terraform variables, after the precedence of workspace variables and variable sets. The plan reads it as a var file, so these values still win over*.tfvarsin the repository, as in Terraform Cloud..stategraph/tfc/vars/<dir>/<workspace>.env: the environment variables. At run time it also writes the sensitive Terraform variables from GitHub secrets to a temporary var file.../tfc-state/<workspace>.tfstateand../tfc-state/migrate-state.sh: the current state of each workspace, and the commands that import it.--state-dirmust be outside the repository, because state files hold secrets.- With
--rewrite-code, the Terraform code without itscloudandbackend "remote"blocks.
A CLI-driven workspace has no working directory in the API. The script finds its directory from the cloud or backend "remote" block that names it, by name, tags, or prefix. Pass --workspace-dir <workspace>=<dir> when no block names it.
A directory with one workspace gets the workspace default, unless the code reads terraform.workspace. Then, and for several workspaces in a directory, each workspace keeps its Terraform Cloud name, or the part after the prefix of a backend "remote" block.
Output:
# Terraform Cloud to Stategraph Orchestration conversion report
Converter: tfc-to-stategraph.py 1.0.0
Source: Terraform Cloud organization acme
Output: .stategraph/config.yml
...
## Manual steps (3)
- `secrets`: Terraform Cloud does not reveal sensitive values. Create these GitHub secrets with the values: `TFC_NETWORK__DB_PASSWORD`. For example: `gh secret set TFC_NETWORK__DB_PASSWORD`.
- `runner secrets`: Set the GitHub secrets `STATEGRAPH_API_BASE`, `STATEGRAPH_API_KEY`, and `STATEGRAPH_TENANT_ID`, so that the runner can reach Infrastructure as a Database.
- `state`: Run `sh ../tfc-state/migrate-state.sh` to move the state, then delete the state files.
...
Manual steps need your action: resolve each one before you roll out. Notes describe a difference in behavior.
| Flag | Description |
|---|---|
--org |
The organization. |
--repo |
Convert the workspaces whose VCS repository is OWNER/NAME. |
--workspace |
Also convert this workspace. Repeatable. |
--workspace-dir |
The directory of a CLI-driven workspace, as NAME=DIR. Repeatable. |
--hostname |
The host of Terraform Enterprise. Default is app.terraform.io. |
--token-env |
The environment variable with the API token. Default is TFC_TOKEN. |
--state-dir |
Download the state of each workspace into this directory, and write migrate-state.sh. |
--backend |
Move the state to an s3, gcs, or azurerm backend, not to Infrastructure as a Database. |
--backend-config |
A setting of the backend block, as KEY=VALUE. Repeatable. |
--rewrite-code |
Remove the cloud and backend "remote" blocks, or replace them with the --backend block. |
--save-snapshot |
Write the API data that the conversion reads to a file. |
--snapshot |
Convert from that file, with no API access. |
--output |
The configuration to write. Default is .stategraph/config.yml. |
--report |
Also write the report to a file. |
--dry-run |
Print the files, and write nothing. |
--force |
Overwrite existing files. |
Exit status 0 means no manual steps, 2 means that the report lists manual steps, and 1 is an error.
Move the state
Into Infrastructure as a Database
This is the default. Each Terraform Cloud workspace becomes a state in Infrastructure as a Database, and the converted configuration runs it with the stategraph engine.
- Export the connection to your Stategraph server, and the values of the sensitive variables that the report lists. The import evaluates the configuration, so it needs every variable:
export STATEGRAPH_API_BASE=https://stategraph.example.com
export STATEGRAPH_API_KEY=<api key>
export STATEGRAPH_TENANT_ID=<tenant id>
export TFC_NETWORK__DB_PASSWORD=<value>
- Run the script that the conversion wrote:
sh ../tfc-state/migrate-state.sh
For each workspace, it runs the equivalent of this command in the workspace directory, with the var files of the workspace:
stategraph import tf --tenant "$STATEGRAPH_TENANT_ID" --name network --workspace default \
--var-file=../.stategraph/tfc/vars/network/default.tfvars ../../tfc-state/network.tfstate
-
Commit the
stategraph.jsonthat each import writes. Several workspaces in one directory share the group in that file. -
Check the states:
stategraph states list --tenant "$STATEGRAPH_TENANT_ID"
stategraph states summary --state <state-id>
- Delete the state files.
For the import on its own, see Import your Terraform state. For the engine and the runner secrets, see Use with Orchestration.
Into a bucket backend
To keep Terraform state files, pass --backend and the settings of the backend:
python3 tfc-to-stategraph.py --org acme --repo acme/infra --state-dir ../tfc-state \
--backend s3 --backend-config bucket=acme-terraform-state \
--backend-config key=terraform.tfstate --backend-config region=us-west-2 \
--rewrite-code
--rewrite-code replaces each cloud block with the backend block, and the configuration uses the terraform engine. For each workspace, migrate-state.sh runs terraform init, selects the workspace, and runs terraform state push. Give the runner access to the bucket, for example with an OIDC role. Delete the state files after the push.
Differences in behavior
- Each pull request plans the directories that it changes, and each plan comments on the pull request. Terraform Cloud ran speculative plans, and posted a commit status.
- A VCS workspace applies after the merge, as in Terraform Cloud. Comment
stategraph applyon the merged pull request, or setautoapply. Removeapply_after_mergeto apply before the merge. - A CLI-driven workspace now plans on each pull request that changes its directory.
- A run trigger makes the dependent plan and apply in the same pull request, after its dependency applies. Terraform Cloud queued a new run after the apply.
- Dynamic provider credentials become
oidcsteps. The role or identity must now trust the GitHub OIDC issuer, notapp.terraform.io. - Orchestration reads
apply_requirementsanddestination_branchesfrom the default branch.
Roll out
- Open a pull request that adds
.stategraph/config.yml,.stategraph/tfc/, eachstategraph.json, the code withoutcloudblocks, and the Orchestration workflow file. - Check that each directory plans with no changes. A change means that a variable or the state differs from Terraform Cloud.
- Merge it.
- In Terraform Cloud, lock each workspace, so that nobody applies there, and disconnect its VCS repository.
- When every directory has moved, delete the workspaces and the variable sets in Terraform Cloud.
Next steps
- Use with Orchestration: the
stategraphengine - Configuration: the
.stategraph/config.ymlreference - Cloud credentials: OIDC for AWS, Google Cloud, and Azure
- Migrating from Atlantis: move from Atlantis