April 26, 2023·4 min read·engineeringterraformplanningcorrectness

Terraform 1.4.6 preserves unknown values in plan output

Null strings and unknown map entries require more than formatting. Terraform 1.4.6 repairs how its plan renderer carries their meaning into the displayed diff.

A map entry can be absent from a plan's ordinary values because Terraform does not know its value yet. That does not mean the entry is absent from the proposed infrastructure. Confusing those cases makes a displayed plan omit information that still exists in the planner's model. Terraform 1.4.6, released on April 26, repairs this case alongside null-string rendering and several related metadata problems.

The context is a replacement of the plan renderer in Terraform 1.4. The 1.4 release notes describe a complete rewrite to support future Terraform Cloud integration, with no intended material change to the displayed output. This patch addresses distinctions that the new implementation had failed to preserve. Their significance lies in what an operator can infer from the text before approving an apply.

I depend on Terraform's plan to explain the implications of a declarative configuration. A display that loses a value's unknown status weakens that explanation even if the underlying resource operation is unchanged. Rendering needs to preserve the difference between a known absence and a value whose result is pending.

Null is different from the string containing its name

The string-rendering correction adds an explicit IsNull flag to the renderer's evaluated string representation. Previously, the evaluator represented null using display text. A later formatting step could treat that text like an ordinary string and quote it. The renderer needed to retain the original value category until formatting was complete.

The regression tests deliberately use a real string containing the word null so the distinction is visible. Creating that string should display quotes. Creating an actual null string value should display the null keyword without quotes. A transition between them must show which side contains text and which side represents absence.

For a string changing to null, the test expects the following display. This is plan output from the renderer test, not Terraform configuration.

"null" -> null

The reverse transition expects null followed by the quoted string. The patch also handles deletion of an already-null string without adding a redundant transition to null. Multiline strings and strings containing JSON have separate test cases because their display follows different formatting paths.

This is a type-preservation problem inside a formatting implementation. Once the evaluator turns distinct source values into the same undifferentiated text, a later formatter cannot reliably reconstruct their meaning. The additional flag allows the final formatting step to quote real strings while printing null as a value category.

Unknown map entries need their metadata

The map correction in the same patch addresses a different loss of information. The implementation comments explain that JSON plan processing removes unknown values from the ordinary after-value representation. Their existence remains in a separate unknown-value structure. A renderer that visits only the ordinary map can miss those entries entirely.

The regression fixture has an empty ordinary triggers map while unknown metadata identifies triggers.rotation as unknown. The expected rendered difference still contains the rotation key as an unknown value. A companion case contains the key with a known null value and expects null handling. These cases look similar if a renderer inspects only the ordinary after value, but the metadata gives them different meanings.

The dynamic-type correction extends this work. It carries unknown and sensitive information through dynamic values and distinguishes explicit keys from the full set of keys described by metadata. Its AllKeys helper combines keys from the before and after maps with keys present in the unknown and sensitivity structures.

That distinction has a practical purpose. An unknown child can require display even when its ordinary after value is missing. Nested blocks and nested attributes have different relationships between their data and metadata, so the patch retains an explicit-key path for those structures. It does not assume that every map-like representation can use the same enumeration rule.

A computed value can also require replacement

Another renderer fix in this release restores the # forces replacement suffix for computed values. The unknown-value renderer previously printed its unknown marker without appending that replacement information. The new test covers both creation-style and update-style differences marked as requiring replacement.

For the update case, the renderer test expects the following text. Both the future value and the replacement consequence appear on the same line.

0 -> (known after apply) # forces replacement

Unknown status and replacement status answer different questions. The former says Terraform cannot yet display the resulting value. The latter explains an operation required by the plan. Preserving both lets the reader understand why an attribute with no concrete new value still matters to resource identity.

I consider that explanation part of Terraform's operational usefulness. Reusable configuration puts infrastructure decisions into a common language, but approval still depends on seeing the planned consequences. These patches repair the renderer's account of those consequences at the attribute level.

The release also lists a correction for schema-derived sensitivity metadata in terraform show -json, plus duplicate rendering against older Terraform Enterprise backends. Together with the null and unknown fixes, these changes concern the information passed to people and downstream consumers. For the map and computed-value cases, the repaired output now retains facts that were already present in the structured change.