Documentation / Infrastructure runs
OpenTofu compatibility
The Terraform constructs an OpenTofu version cannot run, how each is reported on an upload and on a pull request, and how to fix it or choose a version that has it.
Hosted runs execute OpenTofu (why), and almost every Terraform configuration runs unchanged. The exceptions are a short list of constructs Terraform added that OpenTofu gained a release or two later, or has not gained at all. OpenTofu before 1.12 also reads Terraform's required_version as a constraint on its own version number.
So every configuration is checked against the OpenTofu version its workspace runs, and each construct that version cannot run is reported with the file, the line, what goes wrong, and how to fix it.
When a configuration is uploaded. That covers
terraform planandterraform applyfrom your machine, and every run started from a connected repository. A configuration containing an error (a construct the workspace's OpenTofu version refuses) is turned away with HTTP 422, naming each construct, file and line, before any run is created. Without the check, OpenTofu would have stopped the run anyway, and for some constructs with a message that does not name what you wrote.On a commit that starts a run from a connected repository (a push to the workspace's branch, a tag it watches, or a pull request when speculative plans are on). The workspace gets a second commit status beside its run status, named
otf/<workspace>/opentofu. It passes when the commit has no errors and fails when it has any. On a pull request with findings, the workspace also leaves one comment listing them, which is edited in place on every push and says so once the findings are fixed. A pull request with no findings gets no comment.
Warnings never stop anything. A warning marks a construct that runs, but not the way it does under Terraform: a file OpenTofu ignores, or a block whose default behaviour differs.
The check reads .tf and .tofu files, test files (.tftest.hcl), and Terraform query and Stacks files, within the workspace's working directory. It reports a .tf.json file as not checked rather than passing it.
The check uses the version the workspace runs:
A workspace set to
latestruns the newest OpenTofu release we have vetted, and is checked against that.A workspace pinned to an OpenTofu release is checked against that release.
A workspace the Terraform CLI created holds the Terraform version that created it. A hosted run resolves a number newer than our newest vetted OpenTofu release down to that release, and the check does the same.
To clear an error, either change the configuration as the finding says, or move the workspace to an OpenTofu version that has the construct. Every rule below says which version that is. The rules were measured against a real OpenTofu binary of every line from 1.6 to 1.13.
The same rules are published as a tflint plugin and as a standalone CLI, in runners-io/tflint-ruleset-tofucompat (MPL-2.0). Run either before you push, or in your own CI, and for the same OpenTofu version you get the answer the upload check would give.
With tflint, add this to .tflint.hcl, then run tflint --init and tflint:
plugin "tofucompat" {
enabled = true
version = "0.1.1"
source = "github.com/runners-io/tflint-ruleset-tofucompat"
# "latest" (the default), a line such as "1.10", or a full version.
opentofu_version = "latest"
}tflint 0.55 and later check the release's signature on their own. For an earlier tflint, also add the signing_key from the repository's README. tflint --fix applies the mechanical fixes. tflint shows a plugin only the .tf files of one module at a time, so the rules about test files, .tofu files, query and Stacks files, and import blocks in child modules report only from the CLI.
With the CLI, download tofucompat for your platform from the releases page (the README says how to verify it), or build it with go install github.com/runners-io/tflint-ruleset-tofucompat/cmd/tofucompat@v0.1.1. Then:
tofucompat . # against the latest OpenTofu
tofucompat -opentofu-version 1.10 infra/ # against OpenTofu 1.10
tofucompat -fix . # apply the mechanical fixesIt exits 1 when it finds an error (or, with -strict, a warning), 0 when it finds none, and 2 when it could not check anything.
In GitHub Actions, the repository is also an action. It builds the CLI from the source at the release you name:
- uses: runners-io/tflint-ruleset-tofucompat@v0.1.1
with:
opentofu-version: latestFindings become annotations on the pull request, and an error fails the step.
Each rule below is one construct. An error is a construct OpenTofu refuses. A mechanical fix is a rewrite that keeps the configuration's meaning on the older release; the pull request comment says when one applies.
<!-- BEGIN rule reference: generated by tofucompat/compat/docs_test.go -->
action block, added in Terraform 1.14. No OpenTofu release supports it yet. Error: OpenTofu refuses the configuration.
A top-level action block (Terraform 1.14 actions) is rejected at init with "Unsupported block type".
Fix: Remove the action block and run the operation another way: a provisioner, a terraform_data resource with local-exec, or a step in your pipeline after apply.
action_trigger block in lifecycle, added in Terraform 1.14. No OpenTofu release supports it yet. Error: OpenTofu refuses the configuration.
An action_trigger block inside a resource's lifecycle (Terraform 1.14 actions) is rejected at init with "Unsupported block type".
Fix: Remove the action_trigger block, together with the action it invokes.
convert() function, added in Terraform 1.15. OpenTofu supports it from 1.13, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
convert() fails the PLAN with "Invalid reference", a diagnostic that does not name the function.
Fix: Use the type-specific conversion function instead: tonumber, tostring, tobool, tolist, toset or tomap, or set the OpenTofu version to 1.13 or later.
Mechanical fix: Rewrites convert(x, number|string|bool) to tonumber(x), tostring(x) or tobool(x), which convert exactly the same values. Calls converting to a collection or object type are left alone, because tolist and friends do not convert the elements.
ephemeral resource block, added in Terraform 1.10. OpenTofu supports it from 1.11, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
A top-level ephemeral block (an ephemeral resource) is rejected at init with "Unsupported block type".
Fix: Set the OpenTofu version to 1.11 or later. On an older release, read the value with a data source instead, accepting that it is then stored in the plan and state.
ephemeral argument on a variable, added in Terraform 1.10. OpenTofu supports it from 1.11, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
ephemeral = true on a variable is rejected with "Unsupported argument".
Fix: Set the OpenTofu version to 1.11 or later. On an older release, remove ephemeral and mark the variable sensitive = true, accepting that its value is then stored in the plan and state.
ephemeral argument on an output, added in Terraform 1.10. OpenTofu supports it from 1.11, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
ephemeral = true on an output is rejected with "Unsupported argument".
Fix: Set the OpenTofu version to 1.11 or later. On an older release, remove ephemeral and mark the output sensitive = true, accepting that its value is then stored in state.
ephemeralasnull() function, added in Terraform 1.10. OpenTofu supports it from 1.11, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
ephemeralasnull() is rejected with "Call to unknown function".
Fix: Set the OpenTofu version to 1.11 or later.
terraform.applying, added in Terraform 1.10. OpenTofu supports it from 1.11, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
terraform.applying is rejected with "Invalid "terraform" attribute".
Fix: Set the OpenTofu version to 1.11 or later.
for_each in an import block, added in Terraform 1.7. OpenTofu supports it from 1.7, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
for_each inside an import block is rejected with "Unsupported argument".
Fix: Set the OpenTofu version to 1.7 or later, or write one import block per instance.
removed block, added in Terraform 1.7. OpenTofu supports it from 1.7, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
A removed block is rejected at init with "Unsupported block type".
Fix: Set the OpenTofu version to 1.7 or later, or remove the block and run tofu state rm once instead.
lifecycle or provisioner in a removed block, added in Terraform 1.7. OpenTofu supports it from 1.10, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
A lifecycle or provisioner block inside removed is rejected with "Unsupported block type".
Fix: Set the OpenTofu version to 1.10 or later. On 1.7 to 1.9, lifecycle { destroy = false } can be deleted: a bare removed block already forgets the resource without destroying it.
Mechanical fix: Deletes a lifecycle block whose only content is destroy = false, which is what a bare removed block does anyway on OpenTofu 1.7 to 1.9. A lifecycle with destroy = true or any other content, and every provisioner, is left for a human.
moved between resource types, added in Terraform 1.8. OpenTofu supports it from 1.10, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
A moved block whose from and to are different resource types fails the plan with "Resource type mismatch".
Fix: Set the OpenTofu version to 1.10 or later, or replace the move with a removed block for the old resource and an import block for the new one.
Variable validation referring to other values, added in Terraform 1.9. OpenTofu supports it from 1.9, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
A validation condition that refers to anything but its own variable is rejected with "Invalid reference in variable validation".
Fix: Set the OpenTofu version to 1.9 or later, or move the check into a check block or a resource precondition.
Provider-defined function (provider::...), added in Terraform 1.8. OpenTofu supports it from 1.7, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
OpenTofu 1.6 cannot parse a provider:: function name and stops with "Invalid single-argument block definition" or "Missing newline after argument".
Fix: Set the OpenTofu version to 1.7 or later.
templatestring() function, added in Terraform 1.9. OpenTofu supports it from 1.7, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
templatestring() is rejected with "Call to unknown function".
Fix: Set the OpenTofu version to 1.7 or later, or move the template into a file and use templatefile().
issensitive() function, added in Terraform 1.8. OpenTofu supports it from 1.7, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
issensitive() is rejected with "Call to unknown function".
Fix: Set the OpenTofu version to 1.7 or later.
deprecated argument on a variable or output, added in Terraform 1.15. OpenTofu supports it from 1.10, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
deprecated on a variable or output is rejected with "Unsupported argument".
Fix: Set the OpenTofu version to 1.10 or later, or delete the argument: it only prints a notice and changes no value.
Mechanical fix: Deletes the deprecated argument. It only prints a notice to callers; no value changes.
type argument on an output, added in Terraform 1.15. No OpenTofu release supports it yet. Error: OpenTofu refuses the configuration.
A type constraint on an output is rejected with "Unsupported argument".
Fix: Delete the type argument. It only checks the value's type; the value itself is unchanged.
Mechanical fix: Deletes the output's type argument. The output's value is unchanged; only the type check goes.
.tfquery.hcl file, added in Terraform 1.14. No OpenTofu release supports it yet. Warning: the configuration runs, but not the way it does under Terraform.
OpenTofu does not read .tfquery.hcl files and has no query command, so the list blocks in this file are silently ignored. Plans and applies are not affected.
Fix: Nothing to do for plans and applies. Run terraform query locally if you rely on it.
Terraform Stacks file (.tfcomponent.hcl, .tfdeploy.hcl, .tfstack.hcl), added in Terraform 1.13. No OpenTofu release supports it yet. Warning: the configuration runs, but not the way it does under Terraform.
Terraform Stacks is an HCP Terraform feature OpenTofu does not implement; this file is silently ignored.
Fix: Model each stack component as a workspace with its own working directory.
required_version that the OpenTofu version does not satisfy, added in Terraform 0.12. OpenTofu supports it from 1.12, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
OpenTofu before 1.12 checks required_version against its OWN version number, so a Terraform version pin it does not satisfy stops init with "Unsupported OpenTofu Core version".
Fix: Set the OpenTofu version to 1.12 or later, which ignores required_version in .tf files; or widen the constraint so it also admits the OpenTofu version; or put an OpenTofu-specific constraint in a .tofu file.
import block in a child module, added in Terraform 1.16. No OpenTofu release supports it yet. Error: OpenTofu refuses the configuration.
An import block in a module called by another is rejected with "Invalid import configuration: Import blocks are only allowed in the root module".
Fix: Move the import block to the root module and prefix its to address with the module path, e.g. to = module.network.aws_vpc.main.
destroy in a resource's lifecycle, added in Terraform 1.16. OpenTofu supports it from 1.12, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
destroy inside a resource's lifecycle is rejected with "Unsupported argument".
Fix: Set the OpenTofu version to 1.12 or later.
Module source or version that is not a literal string, added in Terraform 1.15. OpenTofu supports it from 1.8, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
A module source or version built from a variable or local is rejected at init with "Variables not allowed".
Fix: Set the OpenTofu version to 1.8 or later, which evaluates these early, or write the value as a literal string.
store block in terraform_data, added in Terraform 1.16. No OpenTofu release supports it yet. Error: OpenTofu refuses the configuration.
A store block inside a terraform_data resource is rejected with "Unsupported block type".
Fix: Keep the value in the resource's input argument and read it back from output.
provider::terraform:: function without declaring the built-in provider, added in Terraform 1.8. No OpenTofu release supports it yet. Error: OpenTofu refuses the configuration.
OpenTofu runs provider::terraform:: functions only when the module declares the built-in provider; without it every release fails with "Unknown function provider".
Fix: Add terraform = { source = "terraform.io/builtin/terraform" } to this module's required_providers (OpenTofu 1.7 or later).
cloud block without hostname, added in Terraform 1.1. No OpenTofu release supports it yet. Error: OpenTofu refuses the configuration.
Terraform defaults the cloud block's hostname to app.terraform.io; OpenTofu has no default and stops init with "Hostname is required for the cloud backend".
Fix: Set hostname in the cloud block (or the TF_CLOUD_HOSTNAME environment variable) to the service you run on.
Mechanical fix: Adds hostname = "app.terraform.io", the host Terraform used implicitly, so the block means what it meant under Terraform. Change it to the service you run on.
Backend OpenTofu does not have, added in Terraform 1.12. No OpenTofu release supports it yet. Error: OpenTofu refuses the configuration.
OpenTofu has no oci backend and stops init with "Unsupported backend type".
Fix: Move the state to a backend OpenTofu supports, such as s3 (OCI Object Storage has an S3-compatible API) or http, with tofu init -migrate-state.
use_lockfile in the s3 backend, added in Terraform 1.10. OpenTofu supports it from 1.10, so the rule applies to earlier versions only. Error: OpenTofu refuses the configuration.
use_lockfile in the s3 backend is rejected at init with "Unsupported argument".
Fix: Set the OpenTofu version to 1.10 or later, or lock with dynamodb_table instead.
removed block without lifecycle { destroy = ... }, added in Terraform 1.7. No OpenTofu release supports it yet. Warning: the configuration runs, but not the way it does under Terraform.
Without an explicit lifecycle { destroy = ... }, OpenTofu FORGETS the resource (removes it from state and leaves it running), where Terraform destroys it.
Fix: Say which you mean: add lifecycle { destroy = true } to destroy it (OpenTofu 1.10 or later), or lifecycle { destroy = false } to forget it. On OpenTofu 1.7 to 1.9, which cannot express either, a bare removed block forgets; destroy the resource by deleting its block instead.
Write-only resource attribute (*_wo), added in Terraform 1.11. OpenTofu supports it from 1.11, so the rule applies to earlier versions only. Warning: the configuration runs, but not the way it does under Terraform.
This attribute is named like a write-only attribute (_wo suffix). Providers refuse write-only attributes on OpenTofu before 1.11 ("WriteOnly Attribute Not Allowed").
Fix: Set the OpenTofu version to 1.11 or later. On an older release use the attribute without _wo, accepting that its value is then stored in state.
mock_provider or override_* in a test file, added in Terraform 1.7. OpenTofu supports it from 1.8, so the rule applies to earlier versions only. Warning: the configuration runs, but not the way it does under Terraform.
tofu test rejects mock_provider, override_resource, override_data and override_module with "Unsupported block type". Plans and applies are not affected.
Fix: Set the OpenTofu version to 1.8 or later to run these tests.
parallel or state_key in a test run block, added in Terraform 1.11. No OpenTofu release supports it yet. Warning: the configuration runs, but not the way it does under Terraform.
tofu test rejects parallel and state_key in a run block with "Unsupported argument". Plans and applies are not affected.
Fix: Remove parallel and state_key; OpenTofu runs test run blocks one after another against one state per module.
Mechanical fix: Deletes the parallel or state_key argument. Tests then run one after another, which is how OpenTofu runs them anyway.
<!-- END rule reference -->