Terraform Removed Block
Tags: terraform, hashi, tutorial
Published: 2023-12-07 14:55:37

Decided to test out the new state block which looks to be released in v1.7. 

Stemmed from this request in the hashi forums. 
https://discuss.hashicorp.com/t/request-for-testing-removed-block/60511

For now, you can find some documentation and usage here: 
https://github.com/hashicorp/terraform/pull/34339/commits/9af31fb5ce8fe983fc212390659215cf6501c85d

At this point it looks like a holy trinity of state blocks will be finished - import, moved, and now removed. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64gjil1be8rxzo2tbtd9.png)

I conducted a simple test, and similar to import and move, additional feature support has been added.

Also looks like there will be an additional lifecycle argument which support this -- in this case it was destroy = false. This argument is required per docs. 

> The `lifecycle` block is required. The `destroy` argument determines whether Terraform will attempt to destroy the objects managed by the module or not. A value of `false` means that Terraform will remove the resources from state without destroying them.

# Creating then removing local_file from state

Created a simple module which only creates one local file resource.

For this release, I downloaded the latest beta version. 

https://releases.hashicorp.com/terraform/1.7.0-beta1/

My initial setup:

```hcl
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "2.4.0"
    }
  }
}

resource "local_file" "file" {
  filename = "${path.module}/destination"
  source   = "${path.module}/source"
} 
```

Ran an init + apply:

```hcl
local_file.file: Refreshing state... [id=5291767cb169477c0a03c53439ec85c8dc3aaf46]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.file will be created
  + resource "local_file" "file" {
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "./destination"
      + id                   = (known after apply)
      + source               = "./source"
    }

Plan: 1 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

local_file.file: Creating...
local_file.file: Creation complete after 0s [id=5291767cb169477c0a03c53439ec85c8dc3aaf46]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

Updated the file to remove the local_file resource and added removed block. I'm guessing lifecycle + destroy is a new argument. 

```hcl
terraform {
  required_providers {
    local = {
      source  = "hashicorp/local"
      version = "2.4.0"
    }
  }
}

# resource "local_file" "file" {
#   filename = "${path.module}/destination"
#   source   = "${path.module}/source"
# }

removed {
  from = local_file.file
  lifecycle {
    destroy = false
  }
}
```

Ran an apply and received the expected feedback. 

```hcl
local_file.file: Refreshing state... [id=5291767cb169477c0a03c53439ec85c8dc3aaf46]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

 # local_file.file will no longer be managed by Terraform, but will not be destroyed
 # (destroy = false is set in the configuration)
 . resource "local_file" "file" {
      - content_base64sha256 = "N+FgMd5soTK/NPAhWpf9OwVevfnr6DOVhD8CTislQ5s=" -> null
      - content_base64sha512 = "/uaHo4s7YXkWFCLPOobf2izKhUbZtBKmlE3fnEfjM5kFzN4MAC4e8IK+Yl6QlbuqBPnpiI36dGCgtCX/7lVBCw==" -> null
      - content_md5          = "f352cac61c815fe9d44770e65345367d" -> null
      - content_sha1         = "5291767cb169477c0a03c53439ec85c8dc3aaf46" -> null
      - content_sha256       = "37e16031de6ca132bf34f0215a97fd3b055ebdf9ebe83395843f024e2b25439b" -> null
      - content_sha512       = "fee687a38b3b6179161422cf3a86dfda2cca8546d9b412a6944ddf9c47e3339905ccde0c002e1ef082be625e9095bbaa04f9e9888dfa7460a0b425ffee55410b" -> null
      - directory_permission = "0777" -> null
      - file_permission      = "0777" -> null
      - filename             = "./destination" -> null
      - id                   = "5291767cb169477c0a03c53439ec85c8dc3aaf46" -> null
      - source               = "./source" -> null
    }

Plan: 0 to add, 0 to change, 0 to destroy.
╷
│ Warning: Some objects will no longer be managed by Terraform
│ 
│ If you apply this plan, Terraform will discard its tracking information for the following objects, but it will not delete them:
│  - local_file.file
│ 
│ After applying this plan, Terraform will no longer manage these objects. You will need to import them into Terraform to manage them again.
╵

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


Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
```

#Let us try something a bit different. 

In this case, I will be creating an ECR resource via the AWS provider, then deleting that resource from state and importing the resource to the AWS Cloud Control provider. 

Why? Cause my friend @drewmullen wanted me to try this. 

Set up my configuration. Commented out the AWSCC resource for now. 

```hcl
terraform {
  required_providers {
    awscc = {
      source  = "hashicorp/awscc"
      version = "0.66.0"
    }
    aws = {
      source  = "hashicorp/aws"
      version = "5.29.0"
    }
  }
}


resource "aws_ecr_repository" "this" {
  name                 = "delete-test"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}

# resource "awscc_ecr_repository" "this" {
#   repository_name      = "delete-test"
#   image_tag_mutability = "MUTABLE"
#   image_scanning_configuration = {
#     scan_on_push = true
#   }
# }

```

Ran an apply. 

```hcl
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_ecr_repository.this will be created
  + resource "aws_ecr_repository" "this" {
      + arn                  = (known after apply)
      + id                   = (known after apply)
      + image_tag_mutability = "MUTABLE"
      + name                 = "delete-test"
      + registry_id          = (known after apply)
      + repository_url       = (known after apply)
      + tags_all             = (known after apply)

      + image_scanning_configuration {
          + scan_on_push = true
        }
    }

Plan: 1 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_ecr_repository.this: Creating...
aws_ecr_repository.this: Creation complete after 1s [id=delete-test]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```

Ok, resource is now created. For the next step, I will add a removed block and add an import block. I will also comment out (remove) the previous AWS ECR resource from code. 

```hcl
terraform {
  required_providers {
    awscc = {
      source  = "hashicorp/awscc"
      version = "0.66.0"
    }
    aws = {
      source  = "hashicorp/aws"
      version = "5.29.0"
    }
  }
}

# resource "aws_ecr_repository" "this" {
#   name                 = "delete-test"
#   image_tag_mutability = "MUTABLE"

#   image_scanning_configuration {
#     scan_on_push = true
#   }
# }

removed {
  from = aws_ecr_repository.this
  lifecycle {
    destroy = false
  }
}

import {
  id = "delete-test"
  to = awscc_ecr_repository.this
}

resource "awscc_ecr_repository" "this" {
  repository_name      = "delete-test"
  image_tag_mutability = "MUTABLE"
  image_scanning_configuration = {
    scan_on_push = true
  }
}
```

After updating, received the following results: 

```hcl
awscc_ecr_repository.this: Preparing import... [id=delete-test]
aws_ecr_repository.this: Refreshing state... [id=delete-test]
awscc_ecr_repository.this: Refreshing state... [id=delete-test]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

 # aws_ecr_repository.this will no longer be managed by Terraform, but will not be destroyed
 # (destroy = false is set in the configuration)
 . resource "aws_ecr_repository" "this" {
      - arn                  = "arn:aws:ecr:us-east-1:634211456147:repository/delete-test" -> null
      - id                   = "delete-test" -> null
      - image_tag_mutability = "MUTABLE" -> null
      - name                 = "delete-test" -> null
      - registry_id          = "634211456147" -> null
      - repository_url       = "634211456147.dkr.ecr.us-east-1.amazonaws.com/delete-test" -> null
      - tags                 = {} -> null
      - tags_all             = {} -> null

      - encryption_configuration {
          - encryption_type = "AES256" -> null
        }

      - image_scanning_configuration {
          - scan_on_push = true -> null
        }
    }

  # awscc_ecr_repository.this will be updated in-place
  # (imported from "delete-test")
  ~ resource "awscc_ecr_repository" "this" {
        arn                          = "arn:aws:ecr:us-east-1:634211456147:repository/delete-test"
      + empty_on_delete              = (known after apply)
        encryption_configuration     = {
            encryption_type = "AES256"
        }
        id                           = "delete-test"
        image_scanning_configuration = {
            scan_on_push = true
        }
        image_tag_mutability         = "MUTABLE"
      + lifecycle_policy             = (known after apply)
        repository_name              = "delete-test"
      + repository_policy_text       = (known after apply)
        repository_uri               = "634211456147.dkr.ecr.us-east-1.amazonaws.com/delete-test"
      + tags                         = (known after apply)
    }

Plan: 1 to import, 0 to add, 1 to change, 0 to destroy.
╷
│ Warning: Some objects will no longer be managed by Terraform
│ 
│ If you apply this plan, Terraform will discard its tracking information for the following objects, but it will not delete them:
│  - aws_ecr_repository.this
│ 
│ After applying this plan, Terraform will no longer manage these objects. You will need to import them into Terraform to manage them again.
╵

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

awscc_ecr_repository.this: Importing... [id=delete-test]
awscc_ecr_repository.this: Import complete [id=delete-test]
```

#Wrapping up

Nice feature that was added, especially for beginning terraformers. I've never enjoyed dealing with state, but this provides a much more controlled option. 

I assume features such as for_each and other meta arguments are still not available, I haven't dug into the terraform code to confirm what is and isn't available. I think just recently import now accepts for_each and provider arguments. 

Thanks to @danquack and @drewmullen for the review and ideas!