April 2, 2020·5 min read·engineeringterraformsentinelpolicy

Sentinel v2 imports give Terraform policies a richer data model

Sentinel's redesigned Terraform imports are generally available. Their relationship to Terraform's JSON representation explains the policy decisions that require more than a final value.

Infrastructure policy depends on the data a policy engine can inspect. A rule about the planned result of a change needs different evidence from a rule about the configuration that requested it. Terraform's richer data model makes that distinction useful, but an older policy API can prevent authors from using it directly.

HashiCorp's April 2 announcement declares the redesigned Sentinel imports stable and generally available for production policies. Authors still select them explicitly with the versioned import paths. General availability does not change the meaning of an existing unversioned import on that date.

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfstate/v2" as tfstate

I value Terraform's reusable configuration model because it gives infrastructure intent a form that tools can analyze. Policy needs an equally deliberate representation of that intent. Otherwise, a substantial portion of each policy becomes code for recovering information the infrastructure tool already knows.

Why a new import API was necessary

The March technical-preview explanation describes imports originally tied to lower-level Terraform internals. Diff values appeared as strings, nested differences used flattened keys, and discovering resources across modules required traversal boilerplate. A compatibility shim retained those limitations after Terraform 0.12 introduced its JSON exchange format.

The new imports use collections that are easier to filter. Their changes include structured before and after values with appropriate types, and operation lists that distinguish creation from replacement. These are changes to the evidence a policy receives, so migration requires attention to expression semantics as well as import spelling.

A numeric limit illustrates the underlying issue. Comparing a numeric quantity with a numeric threshold is straightforward when the representation preserves the quantity's type. If the representation supplies text, the policy must add conversion and define what a conversion failure means. That extra interpretation is part of the policy's correctness burden.

The exchange format explains the available evidence

The versioned Terraform JSON format documentation explains the representation underlying this redesign. It distinguishes a plan's prior state, configuration and proposed changes. It also supplies a partial description of planned values, which lets consumers examine a projected result without reconstructing every change themselves.

Terraform can emit that representation from a saved plan. These commands illustrate the documented relationship between a binary plan and its machine-readable form.

terraform plan -out=review.plan
terraform show -json review.plan

The binary plan format is not intended for external tools. The JSON representation provides a separate exchange format for those consumers. Its documentation still labels the format version experimental, with a zero major version. The announcement about stable Sentinel imports should therefore be read as a statement about those imports, without extending its promise to every underlying Terraform representation.

For a policy author, the separation of representations is useful because questions differ. Existing state describes what Terraform knows about current objects. Planned values describe the projected result as far as it is known. Configuration describes the expressions and references used to request it. A rule should select the evidence that answers its actual question.

A planned value can still be unknown

The values representation maps infrastructure values into ordinary JSON types. It also has limits. Lists, sets and tuples all become arrays, while maps and objects become JSON objects. A consumer cannot recover every original Terraform type distinction from that representation alone.

Unknown values introduce a more consequential distinction. The values representation can omit an unknown value or represent it as null, making it indistinguishable there from an absent value. Terraform provides plan-specific structures for consumers that need to distinguish those cases. A missing value in the simplified view is therefore insufficient evidence that the desired configuration deliberately omitted it.

Consider a policy that requires a property to have an approved value. If the property depends on an apply-time result, a partial planned-values view may not contain it. Treating every missing entry as an ordinary empty value would silently substitute the policy author's assumption for information Terraform has not yet obtained.

This is why policy design needs an explicit treatment of unknowns. Accepting an unknown property, rejecting it, or inspecting the configuration that produced it are different policies. A richer import API makes more evidence available, but it cannot decide which organizational requirement the author meant.

Actions describe replacement ordering

The JSON change representation includes an action list. A create action can appear alone or as part of a replacement. The documentation describes both delete-then-create and create-then-delete sequences, so order carries information about how Terraform plans to replace an object.

A policy concerned with every new object has a different selection criterion from one concerned only with objects that have no predecessor. Testing for the presence of a create operation includes replacements. Testing for the exact single-create list narrows that set. These expressions can both be valid while enforcing different requirements.

The same distinction affects reporting. A violation tied to a resource address should explain whether the policy rejected a new object, a replacement or a particular resulting value. Otherwise, a technically correct failure can still leave the configuration author unable to identify the intended correction.

Configuration is not evaluated state

Terraform's configuration representation exists before expression evaluation. The documented expression representation supplies constant values where possible and references where an expression depends on other objects. It does not evaluate arbitrary configuration expressions into final resource values.

A rule about a declared relationship can therefore inspect references, while a rule about an actual resulting value needs plan or state evidence. Confusing those questions can make a policy reject a valid indirect expression merely because it is not a literal constant.

I regard the versioned imports as a useful boundary for this change. They let policy sets adopt the revised representation deliberately while existing rules retain their established interpretation. The April announcement keeps the explicit /v2 paths, so a policy's source continues to identify which import contract its expressions expect.

The practical work in adopting these imports is deciding what each rule proves. Resource selection, unknown values and replacement actions all affect that proof. A clearer data model reduces the translation required to express a requirement, while leaving the requirement itself visible in the policy.