Terraform 0.12.18 signs and notarizes its macOS releases
The December release addresses macOS download verification and warns about external references that can make destroy-time provisioners impossible to order.
A Terraform binary can be a valid release and still fail to start under the operating system's software verification rules. Terraform 0.12.18, released on December 11, addresses that distribution problem by signing and notarizing its macOS releases. The same release warns about a separate execution problem in destroy-time provisioners that depend on external values.
The tagged changelog describes the macOS failure for users of version 10.15 and later who downloaded Terraform directly through a browser. The operating system could refuse to open it because it could not verify the developer. HashiCorp recommends upgrading those installations to 0.12.18 or newer.
I regard the installation path as part of making Terraform useful. A configuration language over infrastructure APIs still starts with an executable that must run on the operator's machine. Correct release packaging removes a prerequisite failure before configuration evaluation even begins.
Distribution must satisfy the host platform
Apple's April 10 announcement on notarization requirements describes its work to improve verification of software distributed both inside and outside the App Store. It identifies new requirements associated with macOS 10.14.5 for first-time Developer ID certificate users and kernel extensions. Terraform's December notes connect its own distribution change to Apple's requirements and the errors users reported.
These are platform distribution checks, not checks on a Terraform configuration. A startup refusal does not establish that a module contains invalid HCL or that the backend rejected its credentials. Terraform has not reached those stages when the operating system will not launch the executable.
The release's response is to change the official Darwin artifacts. This matters because instructions that begin by adjusting infrastructure configuration cannot repair the verification status of a downloaded program. The correction belongs in the process that produces and distributes the executable.
The claim should remain as specific as the release note. It says the macOS releases are signed and notarized beginning with this version. It does not claim to certify a configuration's safety, a provider's behavior, or the infrastructure changes a user later approves. Those remain separate questions after the program can launch.
Destruction cannot depend on arbitrary live values
The other substantial compatibility note concerns provisioners configured with when = destroy. Terraform now warns when their configuration, or their connection configuration, refers to objects outside the supported instance context. The release still permits the configuration, but marks the behavior deprecated.
The reasoning appears in pull request 23559. A destroy node needs to evaluate using its instance state. An external reference can make its destruction depend on another object's existing state, introducing cycles or incorrect destroy ordering.
This requirement follows from the difference between creating a resource and removing it. Creation can depend on the values needed to build the new object. During destruction, some related objects may themselves be scheduled for replacement or removal. A cleanup expression that reaches into those objects adds ordering requirements at a stage where their continued availability cannot be assumed.
The patch checks references early during configuration loading. Its implementation permits self, count.index, and each.key. Other references produce a warning identifying deprecated external references. This is more precise than reading the release note's shorthand reference to count and each as permission to use all their possible attributes.
Connection blocks belong to the same restriction
A destroy-time action often needs a connection to the object it is cleaning up. The patch checks connection blocks nested inside a destroy provisioner. It also checks a resource-level connection block when the resource contains a destroy provisioner that can use it.
That means moving a problematic expression into a shared connection block does not remove the dependency. The expression still needs evaluation for the destroy-time action. The validation follows that use, instead of looking only at the text of the provisioner's command.
A connection that uses self.public_ip obtains the address from the provisioner's parent resource. The versioned provisioner documentation explains that self represents that parent and exposes its attributes. Referring to the parent by its ordinary resource name would create a dependency cycle, so the special object already has a defined role here.
Cleanup also has lifecycle limits
The provisioner documentation says destroy-time provisioners execute before their resource is destroyed. A failure stops the operation and the next apply can run the provisioner again. Cleanup must therefore tolerate repeated execution rather than assuming it runs exactly once.
It also requires the provisioner to remain in configuration. Deleting the entire resource block removes the cleanup configuration as well, so Terraform cannot run it when destroying the now-unconfigured object. The documented removal procedure first sets the resource's count to zero and applies, then removes the block after its instances have been destroyed.
I prefer provider-managed behavior when the API operation can be represented by a resource. The provisioner documentation recommends that approach because Terraform can model ongoing changes through the provider, while an arbitrary cleanup script remains outside that declarative model.
Version 0.12.18 adds a warning rather than a hard error for external destroy-time references. The supported references let the action use its own instance context, while the diagnostic exposes dependencies that can prevent teardown from being ordered correctly. The macOS packaging change and the provisioner warning address different stages of execution, each with a specific failure mechanism described by the release.