Hello!
In this post i will explain what is lifecycle, dependent_on and outputs in terraform.
Lifecycle
create_before_destroy
Lifecycle is a meta parameter supported by most resources. And with its help, you can control the behavior when creating/deleting resources.
I will take the terraform ec2 creation code from one of the previous posts and add the lifecycle block to it. Inside of it I will specify create_before_destroy = true
. Now, if I make changes that require recreating ec2, terraform will first create a new server and only then delete the old one.
resource "aws_instance" "foo" {
ami = "ami-0cff7528ff583bf9a"
instance_type = "t2.micro"
subnet_id = "subnet-222a93327f9a744ed"
vpc_security_group_ids = ["sg-2220a119757753b6e"]
tags = {
Env = "Dev"
}
volume_tags = {
"Env" = "Dev"
}
lifecycle {
create_before_destroy = true
}
}
Plan: 1 to add, 0 to change, 1 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_instance.foo: Creating...
aws_instance.foo: Still creating... [10s elapsed]
aws_instance.foo: Still creating... [20s elapsed]
aws_instance.foo: Still creating... [30s elapsed]
aws_instance.foo: Creation complete after 34s [id=i-0c9601ce9bd144f5e]
aws_instance.foo (deposed object aba959f0): Destroying... [id=i-082a6b3816e202929]
aws_instance.foo: Still destroying... [id=i-082a6b3816e202929, 10s elapsed]
aws_instance.foo: Still destroying... [id=i-082a6b3816e202929, 20s elapsed]
aws_instance.foo: Still destroying... [id=i-082a6b3816e202929, 30s elapsed]
aws_instance.foo: Destruction complete after 31s
prevent_destroy
The next parameter is prevent_destroy
. If it is specified, then terraform will not be able to delete the resource in which prevent_destroy = true
is specified.
lifecycle {
prevent_destroy = true
}
I immediately get an error as it is not possible to delete the resource.
terraform apply
aws_instance.foo: Refreshing state... [id=i-0c9601ce9bd144f5e]
╷
│ Error: Instance cannot be destroyed
│
│ on main.tf line 1:
│ 1: resource "aws_instance" "foo" {
│
│ Resource aws_instance.foo has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the
│ scope of the plan using the -target flag.
ignore_changes
The next parameter is ignore_changes
. It receives a list of attributes which terraform should ignore. For example, a new version of the AMI for EC2 was released, but now i don’t want to rebuild server with new AMI. In that case, I can tell terraform to ignore the AMI attribute
lifecycle {
ignore_changes = [
ami
]
}
terraform plan
aws_instance.foo: Refreshing state... [id=i-0c9601ce9bd144f5e]
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
replace_triggered_by
The last parameter is replace_triggered_by
. It recreates the resource in case some attribute of resource has changed (Added in terraform version 1.2)
lifecycle {
replace_triggered_by = [
aws_instance.foo
]
}
I will add a new resource that will generate a random string and specify it as a replace trigger.
resource "random_id" "server" {
keepers = {
ami_id = "ami-0cff7528ff583bf9a"
}
byte_length = 12
}
resource "aws_instance" "foo" {
ami = "ami-0cff7528ff583bf9a"
instance_type = "t2.micro"
subnet_id = "subnet-db73f0ac"
vpc_security_group_ids = ["sg-b04b8cd4"]
tags = {
Env = "Dev222"
}
volume_tags = {
"Env" = "Dev"
}
lifecycle {
replace_triggered_by = [
random_id.server
]
}
}
And as soon as the generated string changes, the server will also be recreated
terraform apply
random_id.server: Refreshing state... [id=dS2PD5rErdg]
aws_instance.foo: Refreshing state... [id=i-0747f2f517d71ae85]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# aws_instance.foo will be replaced due to changes in replace_triggered_by
-/+ resource "aws_instance" "foo" {
~ arn = "arn:aws:ec2:us-east-1:241394805508:instance/i-0747f2f517d71ae85" -> (known after apply)
~ associate_public_ip_address = true -> (known after apply)
~ availability_zone = "us-east-1c" -> (known after apply)
~ cpu_core_count = 1 -> (known after apply)
~ cpu_threads_per_core = 2 -> (known after apply)
~ disable_api_stop = false -> (known after apply)
~ disable_api_termination = false -> (known after apply)
~ ebs_optimized = false -> (known after apply)
- hibernation = false -> null
+ host_id = (known after apply)
~ id = "i-0747f2f517d71ae85" -> (known after apply)
~ instance_initiated_shutdown_behavior = "stop" -> (known after apply)
~ instance_state = "running" -> (known after apply)
~ ipv6_address_count = 0 -> (known after apply)
~ ipv6_addresses = [] -> (known after apply)
+ key_name = (known after apply)
~ monitoring = false -> (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
~ primary_network_interface_id = "eni-034b12b8b4d792825" -> (known after apply)
~ private_dns = "ip-172-31-38-182.ec2.internal" -> (known after apply)
~ private_ip = "172.31.38.182" -> (known after apply)
~ public_dns = "ec2-3-85-112-28.compute-1.amazonaws.com" -> (known after apply)
~ public_ip = "3.85.112.28" -> (known after apply)
~ secondary_private_ips = [] -> (known after apply)
~ security_groups = [
- "default",
] -> (known after apply)
tags = {
"Env" = "Dev222"
}
~ tenancy = "default" -> (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
# (9 unchanged attributes hidden)
~ capacity_reservation_specification {
~ capacity_reservation_preference = "open" -> (known after apply)
+ capacity_reservation_target {
+ capacity_reservation_id = (known after apply)
+ capacity_reservation_resource_group_arn = (known after apply)
}
}
- credit_specification {
- cpu_credits = "unlimited" -> null
}
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
~ enclave_options {
~ enabled = false -> (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
~ maintenance_options {
~ auto_recovery = "default" -> (known after apply)
}
~ metadata_options {
~ http_endpoint = "enabled" -> (known after apply)
~ http_put_response_hop_limit = 1 -> (known after apply)
~ http_tokens = "optional" -> (known after apply)
~ instance_metadata_tags = "disabled" -> (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_card_index = (known after apply)
+ network_interface_id = (known after apply)
}
~ private_dns_name_options {
~ enable_resource_name_dns_a_record = false -> (known after apply)
~ enable_resource_name_dns_aaaa_record = false -> (known after apply)
~ hostname_type = "ip-name" -> (known after apply)
}
~ root_block_device {
~ delete_on_termination = true -> (known after apply)
~ device_name = "/dev/xvda" -> (known after apply)
~ encrypted = false -> (known after apply)
~ iops = 100 -> (known after apply)
+ kms_key_id = (known after apply)
~ tags = {} -> (known after apply)
~ throughput = 0 -> (known after apply)
~ volume_id = "vol-0b353de4ae7778314" -> (known after apply)
~ volume_size = 8 -> (known after apply)
~ volume_type = "gp2" -> (known after apply)
}
}
# random_id.server must be replaced
-/+ resource "random_id" "server" {
~ b64_std = "dS2PD5rErdg=" -> (known after apply)
~ b64_url = "dS2PD5rErdg" -> (known after apply)
~ dec = "8443562173573410264" -> (known after apply)
~ hex = "752d8f0f9ac4add8" -> (known after apply)
~ id = "dS2PD5rErdg" -> (known after apply)
+ keepers = {
+ "ami_id" = "ami-0cff7528ff583bf9a"
} # forces replacement
# (1 unchanged attribute hidden)
}
Plan: 2 to add, 0 to change, 2 to destroy.
Depends On
depends_on
is another meta parameter supported by most terraform resources. And it allows you to create resources in a certain sequence. For example, I want to create an ec2 server and a security group. And I can specify that the server should be created only after the security group has already been created. I can do this by specifying the depends_on
parameter in the aws_instance resource.
resource "aws_instance" "foo" {
ami = "ami-0cff7528ff583bf9a"
instance_type = "t2.micro"
subnet_id = "subnet-222a93327f9a744ed"
vpc_security_group_ids = ["sg-2220a119757753b6e"]
tags = {
Env = "Dev"
}
volume_tags = {
"Env" = "Dev"
}
depends_on = [
aws_security_group.test_sg
]
}
resource "aws_security_group" "test_sg" {
name = "test-sg"
description = "allow traffic to web app"
vpc_id = "vcp-123"
dynamic "ingress" {
for_each = [8080, 443, 22, 80, 3000, 8082]
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = [
"0.0.0.0/0"
]
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_security_group.test_sg: Creating...
aws_security_group.test_sg: Creation complete after 3s [id=sg-0512f173f605de311]
aws_instance.foo: Creating...
aws_instance.foo: Still creating... [10s elapsed]
aws_instance.foo: Creation complete after 15s [id=i-070da129fdd9d4c92]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
TODO: Add terraform output
Outputs
Outputs is similar to return in programming languages. It returns values that will be printed to the console after terraform is executed, and these values can also be used by other terraform code. Most often, this is used in modules when the child module transmits some values to the parent module, or as a simple output to the terminal.
If terraform creates ec2 and after creation I want to display its IP address, then it can be done like this
resource "aws_instance" "foo" {
ami = "ami-0cff7528ff583bf9a"
instance_type = "t2.micro"
subnet_id = "subnet-222a93327f9a744ed"
vpc_security_group_ids = ["sg-2220a119757753b6e"]
tags = {
Env = "Dev"
}
volume_tags = {
"Env" = "Dev"
}
}
output "instance_ip_addr" {
value = aws_instance.foo.private_ip
}
Plan: 1 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ instance_ip_addr = (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_instance.foo: Creating...
aws_instance.foo: Still creating... [10s elapsed]
aws_instance.foo: Creation complete after 14s [id=i-02b842be3d4556465]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
instance_ip_addr = "172.31.43.168"