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"

Video