New Lifecycle Options and Refactoring Capabilities in Terraform 1.1 and 1.2
Tags: terraform, programming, news, opensource
Published: 2022-05-05 13:14:48

In this blog, I would like to tell you about new cool features that Terraform 1.1 and 1.2 bring. It feels like Terraform has doubled its speed of delivering the new features after they released the 1.0. 🤩

**All code examples are based on the AWS Terraform provider ⛅️💛**

It’s been only a few months since Terraform 1.1 was released with the `moved` block that empowers the code refactoring.

Now Terraform 1.2 is almost [ready](*https://github.com/hashicorp/terraform/releases/tag/v1.2.0-rc1*) (as I am writing this blog in early May 2022) to bring three new efficient controls to the resource lifecycle.  
These are three new expressions: `precondition`, `postcondition`, and `replace_triggered_by`.

## Terraform Code Refactoring With the Moved Block

Starting from the 1.1 version, Terraform users can use the `moved` block to describe the changes in resource or module addresses (or resources inside a module) in the form of code.   
Once that is described, Terraform performs the movement of the resource within the state during the first apply.

In other words, what this feature gives you, is the ability to document your `terraform state mv` actions, so you and other project or module users don’t need to perform them manually.

As your code evolves, a resource or module can have several `moved` blocks associated with it, and Terraform will thoroughly reproduce the whole history of its movement within a state (i.e., renaming).

Let’s review some examples that illustrate how it works.

### Moving a resource

In a module, I have a bucket policy that has a generic, meaningless name. It is used in a module that creates a CloudFront distribution with an S3 bucket.

![An example resource](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/11recbc6esq28xis1gdk.png)

It’s pretty OK to name a resource like that if you have only a single instance of that kind in your code.

Later, when I need to add another policy to the module, I don’t want to name it “that”. Instead, I want my policies to have meaningful names now.  
For example, I could rename the old policy with the `terraform state mv` command, but other users of my module would not know about that.

That is where the `moved` block turns out to be helpful: I can document the name change, and later, everyone else who uses my module will get the same renaming.

![Resource address update with the Moved block](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4c3f9wvdtqwwy0cdcn00.png)

Terraform follows the instructions inside the `module` block to plan and apply changes. Although the resource address update is not counted as a change in the Plan output, Terraform will perform that update during apply.

![Terraform plan output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b38nfp4wp00xqb3ns374.png)

### Moving a module

The same approach can be applied to a module.

Here, I use two modules to create static hosting for a website with a custom TLS certificate: a combo of the CloudFront + S3 + ACM services.

![Two modules with generic names](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4uz7lbyvp31meqfuaxgq.png)

Again, if I need to add another couple of the CDN+Certificate modules, I would like to have meaningful names in my code so clearly distinguish one from another.

Therefore, I would add two `moved` blocks — one per module call.

And by the way, since I renamed the module (from `cert` to `example_com_cert`), I need to update all references to that module’s outputs in the code too.

![Two modules renamed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5vdxaa1z2g1pio25sr9a.png)

However, there is one nuance: when you rename a module and declare that in the `moved` block, you need to run the `terraform init` before applying the change because Terraform must initialize the module with the new name first.

![Terraform error: module not installed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/unp7ccm3nxpjesu1zt6c.png)

There are some more advanced actions you can make with the `moved` block:

- Implement count and for\_each meta-arguments to resources and modules
- Break one module into multiple Check the following detailed guide from HashiCorp that explains how to do that — [Refactoring](https://www.terraform.io/language/modules/develop/refactoring)

Introducing the `moved` blocks into your codebase defacto starts the refactoring process for your module users. But the finale of that refactoring happens when you ultimately remove these blocks.

Therefore, here is some advice on how to manage that:

💡 Keep the `moved` blocks in your code for long. For example, when removing a `moved` block from the code, Terraform does not treat the new object name as a renaming anymore. Instead, Terraform will plan to delete the resource or module with the old name instead of renaming it.
💡 Keep the complete chains of object renaming (sequence of moves). The whole history of object movement ensures that users with different module versions will get a consistent and predictable behavior of the refactoring.

## Lifecycle expressions: precondition, postcondition, and replace\_triggered\_by

Terraform 1.2 fundamentally improves the `lifecycle` meta-argument by adding three new configuration options with rich capabilities.

![New configuration options for the lifecycle meta-argument](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gm4sqdi9d1qecilksdmu.png)

### Precondition and Postcondition

When you need to make sure that specific condition is met before or after you create a resource, you can use `postcondition` and `precondition` blocks.

The _condition_ here — is some data or information about a resource you need to confirm to apply the code.

Here are a few examples of such conditions:

- Validate some attributes of the Data Source that you cannot check using filters or other available arguments;
- Confirm the Resource argument that can compound several variables (e.g., list);

💡 **Precondition** works as an expectation or a guess about some external (but within a module) value that a resource depends on.
💡 **Postcondition** works as the assurance that a resource fulfils a specific condition so other resources may rely on that. If postcondition fails for a resource, this prevents changes to all other resources that depend on it.

Let’s review this new feature with an example of `postcondition` usage.

Consider the following case: our module receives AMI ID as the input variable, and that AMI should be used in the Launch Template then; we also have the requirement for the EC2 instance created from that Launch Template — its root EBS size must be equal or bigger than 600 GB.

We cannot validate the EBS size using the variable that accepts the AMI ID. But we can write a **postcondition** for the Data Source that gets the information about the AMI and reference that Data Source in the Launch Template resource afterward.

![Data Source Postcondition](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/092czy7aobh7dta6toke.png)

The `condition` argument within the block accepts any of Terraform’s built-in functions or language operators.

The special `self` object is available only for the `postcondition` block because it assumes that validation can be performed after the object is created and its attributes are known.

Later, if a module user specifies the AMI with an EBS size lesser than 600 GB, Terraform will fail to create the Launch Template because it depends on the Data Source that did not pass the postcondition check.

![Resource postcondition error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3bx3a8weghiat9wkz1xa.png)

Terraform tries to evaluate the condition expressions as soonest: sometimes Terraform can check the value during the planning phase, but sometimes that can be done only after the resource is created if the value is unknown.

### Validating module output with precondition

The `precondition` block is also available for the module outputs.

Just like the variable validation block assures that module input meets certain expectations, the `precondition` is intended to ensure that a module produces the valid output.

Here is an example: a module that creates an ACM certificate must prevent the usage of a specific domain name in the certificate’s Common Name or its SANs.

![Module output precondition](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xjp0riwkwo5733iopzjn.png)

In this case, instead of validating several input variables, we can write the validation only once for the output.

### Trigger resource replacement with replace\_triggered\_by

Sometimes it’s needed to specify the dependency in the way that recreates a resource when another resource or its attribute changes.

This is useful when two (or more) resources do not have any explicit dependency.

Consider the following case: you have two EC2 instances, A and B, and need to recreate the B instance if the private IP of instance A is changed.

![replace_triggered_by example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aplcw3dp5c8ahgt9wopn.png)

This is extremely useful when you’re dealing with logical abstractions over the set of resources.

Replacement is triggered when:

💡 Any of the resources referenced in `replace_triggered_by` are updated
💡 Any value is set to the resource attribute that is referenced in `replace_triggered_by`

## Getting started with Terraform 1.1 and 1.2

If you’re still using older Terraform versions, these new features might be a good motivation for you to upgrade!

Before upgrading, be sure to read the upgrade notes for the specific version at the [releases page](https://github.com/hashicorp/terraform/releases).

Also, an excellent tool can help with fast switching between different Terraform versions while you’re experimenting — [tfswitch](https://tfswitch.warrensbox.com/).

And if you liked that article, you might also like the others wrote about Terraform: **[Terraform Proficiency](https://devdosvid.blog/series/terraform-proficiency/)**


---
{% user svasylenko %}