Using terraform import to migrate resources

Sven Illert -

Terraform is a nice tool when managing infrastructures in your cloud environment. It becomes really powerful when it comes to mass deployment and managing resources where you don’t need to repeat yourself too much. I think many of us experienced the start of a project with singleton resources when we were bloody newbies. But as time passes by the need for a more scalable solution emerges and you need to transform that singleton into a more iterative manner of resource management.

Of course when starting a project right, you would have used for_each or count to manage multiple resources of the same kind. But as explained, the ideal world isn’t already there at the beginning. This is the point where terraform import becomes handy.

Let’s have a look at an example with the following VCN configuration in the Oracle Cloud Infrastructure which creates one terraform resource for one virtual cloud network.

resource "oci_core_vcn" "vcn-ek-web" {
  dns_label      = replace(var.default-vcn-name, "-", "")
  cidr_block     = "10.42.0.0/16"
  compartment_id = oci_identity_compartment.cp-ek-web.id
  display_name   = "vcn-${var.default-vcn-name}"
  is_ipv6enabled = false
}

But if you want to create e.g. 20 virtual cloud networks or more you would have to create copies of that code above. And if you want to make structural changes like enabling IPv6 for all VCNs, you would have to make that change for all of these copies. This is the opposite of DRY principle and of course not the purpose of IaC. Instead, if you would have started in an efficient manner from the start, you would have built code like the following block, which introduces a variable for the VCNs and implements them in a loop. It might not be perfect, as count has it’s drawbacks, but it should work as an example to explain the benefits.

variable "vcns" {
  type = list(object({
    name = string
    cidr_block = string
  }))  
}

resource "oci_core_vcn" "vcns" {
  count = length(var.vcns)
  dns_label      = replace(var.vcns[count.index].name, "-", "")
  cidr_block     = var.vcns[count.index].cidr_block
  compartment_id = oci_identity_compartment.cp-ek-web.id
  display_name   = "vcn-${var.vcns[count.index].name}"
  is_ipv6enabled = true
}

If you introduce such changes later, terraform would not know of the resources in it’s state file that would be created by this loop. So if you would execute terraform plan using a loop with elements that were created without that you might get something like the following output.

% terraform plan 

[...]

Plan: 69 to add, 26 to change, 69 to destroy.

The problem here is, that the resource with the name oci_core_vcn.vcn-ek-web is not the same as oci_core_vcn.vcns[0]. That would be the case, if the declaration of the first singleton VCN would be the first item of the list in the variable vcns of type list which was introduced in the second listing above. But since we didn’t start from scratch it would be a nice thing if we could move the old resource into the new one. And there is one command to do so called terraform import which takes a terraform resource name as target and an OCID as source.

But for this command to work you first have to create the target resource within your terraform code. In the above listings that is missing, because we have not transferred the configuration into the variable. So using the information from the resource oci_core_vcn.vcn-ek-web we have to move it into the new resource oci_core_vcn.vcns[0] which is built using the variable vcns. So let’s fill the first element of the list with the data of the old reource.

vcns = [
  {
    name       = "ek-vpn",
    cidr_block = "10.42.0.0/16"
  }
]

And now after importing the resource, we would see that no new changes would be made if we would plan the terraform deployment.

% terraform import "oci_core_vcn.vcns[0]" ocid1.vcn.oc1.eu-frankfurt-1.xxx
% terraform plan -target oci_core_vcn.vcns

No changes. Your infrastructure matches the configuration.

But what happens with the old resource oci_core_vcn.vcn-ek-web? If we just remove the resource from any files, let’s see what happens if we plan our changes.

% terraform plan

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

Terraform will perform the following actions:

  # oci_core_vcn.vcn-ek-web will be destroyed
  # (because oci_core_vcn.vcn-ek-web is not in configuration)
  - resource "oci_core_vcn" "vcn-ek-web" {
      - byoipv6cidr_blocks       = [] -> null
      - cidr_block               = "10.42.0.0/16" -> null
      - [...]
      - id                       = "ocid1.vcn.oc1.eu-frankfurt-1.xxx" -> null
      - [...]
    }

Plan: 0 to add, 0 to change, 1 to destroy.

It would be a really bad ide to apply this change since this would destroy our resource in the cloud which was freshly moved into the resource oci_core_vcn.vcns[0]. So terraform in fact knows our cloud resource with the ID ocid1.vcn.oc1.eu-frankfurt-1.xxx twice in it’s state file. To remove that redundant information we must remove the resource from our state file without deleting the actual resource in the OCI. This can be done easily with the terraform state rm command as follow.

% terraform state rm oci_core_vcn.vcn-ek-web
% terraform plan

No changes. Your infrastructure matches the configuration.

That’s it for now. Hope this helps!