OpenTofu Planning And State Encryption Notes


OpenTofu had a compact but useful week for teams that use IaC as part of data platform delivery. The activity sits around plan correctness, replacement ordering, state encryption, and release process traceability, which are the parts that decide whether a routine platform change stays boring.

The most important planning change is in replacement flow. In the DeleteThenCreate ordering change, OpenTofu now plans both the create and destroy legs before applying either one. The change lives in internal/engine/planning/execgraph_managed.go, which is exactly where operators want this kind of behavior to be explicit.

The practical effect is simple. A provider can report final plan errors that were not visible earlier, especially when unknown values become known late. Before this change, a DeleteThenCreate replacement could delete the old object before the create leg learned enough to fail. The new ordering reduces that failure mode by making the delete leg wait for a successful create final plan.

This does not make replacement atomic. A create can still fail at apply time. A destroy can still fail after a successful create in the other ordering. But it does move one class of provider validation failure earlier, before the old object is gone. For data teams that manage object stores, queues, service accounts, and scheduler infrastructure with OpenTofu, that is the difference between a failed plan and a cleanup window.

The larger internal change is the create before destroy graph work. It rewrites how execution graph edges are added when replace orders and dependencies interact. The touched path, internal/engine/planning/execgraph_resource.go, moved from a simpler result reference map toward explicit resource instance subgraphs with named methods such as HasCreateOrUpdateLeg, HasDeleteLeg, CreateOrUpdateDependsOn, and DeleteDependsOn.

That is mostly internal refactor, but it matters. Replacement graphs are where rare IaC failures hide. A database parameter group, a bucket policy, a service account, and a compute role can all depend on each other in ways that look obvious in HCL and less obvious in an execution graph. The commit tries to encode the awkward rules up front instead of building a graph and patching it afterward.

The code comments call out cases such as create before destroy on one resource, ordinary replacement on another, create only objects, delete only objects, and state based dependencies. That is the right level of detail for this file. It also leaves a clear risk marker: the function still has a note about possible cycle detection and mentions Tarjan strongly connected components as a future validation path.

State moves got a small but sharp correction. The MovedResourceState fix changes OpenTofu so provider MovedResourceState is called only when there is an explicit moved block. The change records whether a move is implied and adds AddrMovedExplicit in internal/refactoring/move_execute.go.

This fixes a regression introduced in the v1.12.4 and v1.11.12 lines, according to the commit message. The bad behavior was subtle: if the provider host, namespace, type, or resource type changed, OpenTofu could send a provider move request even without an explicit moved block. That is too eager. A provider move hook can rewrite state, and it should not run just because OpenTofu inferred a shape change.

The test changes in internal/tofu/context_plan2_test.go cover different provider addresses with the same schema in both explicit and implied cases. The explicit case can use the provider move path. The implied case expects upgrade behavior instead. That difference is important during provider migrations, especially when teams are changing provider sources or moving modules while keeping state history understandable.

State encryption also moved. The OpenBao associated data change adds an optional associated_data setting to the OpenBao key provider. The config validates that the value is base64, stores it in key metadata, passes it to data key generation, and uses the stored value again during decrypt.

The visible docs change is in website/docs/language/state/encryption.mdx, with the example showing associated_data = base64encode("myapp"). For teams already using Transit style encryption, that gives a cleaner binding between ciphertext and context. It does add an operational rule: changing associated data is not just a cosmetic config edit. The value becomes part of the metadata needed to decrypt old state.

OpenTofu also added docs for an OVHcloud KMS external key provider in the OVHcloud KMS documentation commit. That does not add a built in provider. It points users at the external key provider path and the OVH maintained repository. The signal is that OpenTofu state encryption is still expanding through both native and external provider routes.

Several commits touched release mechanics rather than runtime behavior. The post release hook addition created .github/workflows/post-release.yml and connected release publication to get.opentofu.org redeployment. Then the manual sync step changed that process again, adding a manual step in CONTRIBUTING.RELEASE.md.

There is also a release note process update aimed at backport traceability. If a patch line contains a planning fix, a state encryption fix, or a provider migration correction, the release process needs enough traceability for downstream teams to decide when to pick it up.

The tradeoff is visible. More manual release steps mean more process surface. The benefit is clearer provenance for generated release assets and backports. For a tool that can alter cloud resources and state files, that is a reasonable trade when the process is documented instead of living in maintainer memory.

Watch the new planning engine tests. The create before destroy graph work still leans on broader context tests, and the commit text says direct planning package tests are expected later.

Track the next patch notes for the MovedResourceState fix if you are on v1.12.4 or v1.11.12 and use provider or resource moves. This is a narrow bug, but it sits on a state mutation path.

Treat OpenBao associated_data as durable state encryption context. Add it deliberately, document the value source, and test decrypt before rolling it across shared environments.