Overview
Terraform offers a range of capabilities to handle infrastructure changes in an elegant and controlled manner. One such capability is the lifecycle configuration block.
The lifecycle block provides several meta-arguments to manage how Terraform creates, updates, checks and deletes resources. In this post, we will dive into Terraform's lifecycle block and demonstrate its usage with a few examples with Microsoft Azure resources.
Understanding the Lifecycle Block
Note: This post was written using Terraform (v1.4.x)
The lifecycle block in Terraform provides control over how a resource is managed. It's a configuration block that is nested within a resource block and supports four meta-arguments:
resource "provider_resource" "block" {
// ... some resource configuration ...
lifecycle {
argument = value
}
}
The lifecycle block also has an option to configure custom condition checks using preconditions and postconditions:
resource "provider_resource" "block" {
// ... some resource configuration ...
lifecycle {
precondition {
condition = expression
error_message = "error(string)"
}
postcondition {
condition = expression
error_message = "error(string)"
}
}
}
We will take a closer look at preconditions and postconditions a bit later, but let's first look at a few examples using meta-arguments:
- create_before_destroy
- prevent_destroy
- ignore_changes
- replace_triggered_by
1. Create Before Destroy
Argument Type: Boolean
In some scenarios, destroying a resource before creating a new one can lead to downtime. To circumvent this, we can set create_before_destroy
to true
. This can be particularly useful when working with Azure Virtual Machines or App Services, where you'd want to minimize downtime.
Here is an example with an Azure Virtual Machine:
resource "azurerm_virtual_machine" "example" {
// ... other configuration ...
lifecycle {
create_before_destroy = true
}
}
In this scenario, when an update is required that can't be performed in place, Terraform will first create the replacement VM and then destroy the old one, reducing potential downtime.
2. Prevent Destroy
Argument Type: Boolean
Setting prevent_destroy
to true
is a protective measure to prevent accidental deletion of critical resources. If you attempt to destroy such a resource, Terraform will return an error and stop the operation. This can be useful when working with Azure SQL Databases, Storage Accounts, or any resource that holds important data.
Here is an example with an Azure SQL Database:
resource "azurerm_sql_database" "example" {
// ... other configuration ...
lifecycle {
prevent_destroy = true
}
}
With this configuration, Terraform will prevent the SQL database from being accidentally destroyed.
3. Ignore Changes
Argument Type: list of attribute names
The ignore_changes
argument is useful when you want to manage certain resource attributes outside of Terraform, or when you want to avoid spurious diffs.
Here is an example with an Azure App Service:
resource "azurerm_app_service" "example" {
// ... other configuration ...
lifecycle {
ignore_changes = [
app_settings, // Ignore changes to app_settings attribute
]
}
}
Say a different team manages an App Services app_settings
for example, you may be provisioning that App Service, but the configuration is left up to someone else, or maybe even a different automation all together is taking care of the app_settings
configuration, and you do not want Terraform to revert, interfere or potentially remove those settings.
In this case, any changes to the app_settings
of the App Service will be ignored by Terraform.
Tip: You can also use a special value all
that will ignore all settings once a resource is provisioned.
resource "azurerm_app_service" "example" {
// ... other configuration ...
lifecycle {
ignore_changes = all
}
}
This will provision the resource any any subsequent configuration outside of Terraform will be ignored by Terraform.
4. Replace Triggered By
Argument Type: list of resource or attribute references
The replace_triggered_by
argument allows you to replace a resource when another resource changes. You can only reference managed resources in replace_triggered_by
expressions. Supply a list of expressions referencing managed resources, instances, or instance attributes.
resource "azurerm_sql_database" "example" {
// ... other configuration ...
}
resource "azurerm_app_service" "example" {
// ... other configuration ...
lifecycle {
replace_triggered_by = [
azurerm_sql_database.example.id, //Replace `azurerm_app_service` each time `azurerm_sql_database` id changes
]
}
}
replace_triggered_by
allows only resource addresses because the decision is based on the planned actions for all of the given resources, meaning that variables, data sources and modules are not supported.
Plain values such as local values or input variables do not have planned actions of their own, but you can treat them with a resource-like lifecycle by using them with the terraform_data resource type.
Custom Condition Checks
You can add precondition
and postcondition
blocks with a lifecycle block to specify assumptions and guarantees about how resources and data sources operate. The following examples creates a precondition that checks whether the AMI is properly configured.
data "azurerm_mssql_server" "example" {
// ... other configuration ...
}
resource "azurerm_mssql_database" "test" {
// ... other configuration ...
lifecycle {
precondition {
condition = data.azurerm_mssql_server.example.version == "12.0"
error_message = "MSSQL server version incorrect (Needs to be version 12.0)."
}
postcondition {
condition = self.transparent_data_encryption_enabled == true
error_message = "The Database must have TDE enabled."
}
}
}
NOTE: The self object above in the postcondition
block refers to attributes of the instance under evaluation (e.g. the MSSQL database).
You can implement a validation check as either a postcondition
of the resource producing the data, or as a precondition
of a resource or output value using the data. To decide which is most appropriate, consider whether the check is representing an assumption or a guarantee.
In our example above:
-
Assumption: Validate using
preconditions
that the database is being created, is on a MSSQL server that is version12.0
. -
Guarantee: Validating using
postcondition
that the MSSQL database being created (SELF), hastransparent_data_encryption_enabled
set totrue
.
Conclusion
Terraform's lifecycle block provides a powerful way to control and manage your resources. Whether it's preventing accidental destruction of critical resources, managing zero-downtime updates, or ignoring changes to certain attributes, the lifecycle block offers you the flexibility you need. As always, be sure to test these configurations in a non-production environment before rolling out to production to ensure they work as expected. Happy Terraforming!
I hope you have enjoyed this post and have learned something new. ❤️
Author
Like, share, follow me on: 🐙 GitHub | 🐧 X/Twitter | 👾 LinkedIn
Top comments (0)