and….maybe advanced 😂

Terraform Import Existing AWS Resources

Bmwitcher
Nerd For Tech
Published in
8 min readApr 10, 2021

--

using variables & conditional statements

Before we get started…

Make sure you have an AWS account to test this in your own environment. You will also need to know how to write some basic terraform. In this article [for the most part] we will not be writing high-level terraform. In fact, a few days before this article, I myself had never written a conditional statement inside of terraform until a situation arose at work.

Use-Case:

Your place of employment has recently (like most companies) adopted infrastructure as a code (in its cloud-agnostic form) tool Terraform. Recently the infrastructure has been stood up in terraform using modules (re-usable code) but you now need to import all of the ec2 instances, rds databases, and elastic ips into your repository.

Thankfully HashiCorp has great documentation that we will be referring to.

Set-Up

In my AWS account, I have manually launched 2 ec2 instances (t2.micro — free tiers), a free tier MySQL rds instance, and 2 elastic IPs.

ec2 instances
elastic ips
rds MySQL instance

Terraform Modules

Many companies use modules that are known as reusable code. Modules are cleaner looking but the “child module” a.k.a the resource block configurations are what powers the Modules (parent modules) I will notate the difference in parent and child module briefly as we go on. Let's review our terraform code. We can make changes and additions to our terraform configuration when we get more details back from terraform plan after import.

# --- ec2 instance resources --- #resource "aws_instance" "tfimportec2" {
ami = var.ami[var.region]
#ami IS REGION SPECIFIC - please check which region you are in if you receive an error
instance_type = var.instance_override == false ? var.instance_type : var.no_instance_type_change
key_name = "tf-demo"
}
resource "aws_eip" "tfeip1" {
vpc = true
tags = {
Name = "Tf-eip"
}
}
resource "aws_eip_association" "eip_assoc1" {
instance_id = aws_instance.tfimportec2.id
allocation_id = aws_eip.tfeip1.id
}
# --- Rds instance resource--- #resource "aws_db_instance" "tfdatabase" {
engine = "mysql"
engine_version = "8.0.20"
instance_class = "db.t2.micro"
name = "tf-import-mysql"
allocated_storage = 20
availability_zone = "us-east-1c"
skip_final_snapshot = true
backup_retention_period = 0
apply_immediately = true
username = var.username

Now that we have somewhat of a baseline code (It may error but we will deal with the errors as they come)

Terraform conditionals

This is the key to using variables, values, or null to accomplish different situations for your imports.

Use-case:

Your company has a legacy customer who is on older instance types. You don’t want to stop their ec2 instances or force re-creation so you need a value in place to override it. Terraform conditional uses simple logic to accomplish this objective. We will cover one option of the conditionals not to confuse:

condition ? true_val : false_val

Let us compare this to the instance_type attribute of our aws_instance resource.

instance_type = var.instance_override == false ? var.instance_type : var.set_instance_type

Explanation: our value of instance_type will equal the value of the variable instance_override (which is a boolean value type — true/false), which equals “false” then (?) terraform will evaluate choose the true statement (left of the :). If the value of instance_override was equal to “true” this statement would be rendered false and give us the value of var.set_instance_type or the false statement of : var.set_instance_type or right of the (:)

So if we wanted to override the current instance type set the var.instance_override to true and change the variable of set_instance_type to something like t3.micro.

# --- Partent Module Variables ---child module variables should be empty or default to false ---variable "instance_override" {
type = bool
default = true
}
variable "instance_type" {
type = string
default = "t3.micro"
}

new terraform plan:

# module.instance.aws_instance.tfimportec2[0] will be updated in-place
~ resource "aws_instance" "tfimportec2" {
id = "i-078654854b88d8fe8"
~ instance_type = "t2.micro" -> "t3.micro"
tags = {
"Name" = "ec2One"
"Terraform Managed" = "False"
}
# (27 unchanged attributes hidden)
# (6 unchanged blocks hidden)
}

Now let's start our import of an ec2 instance into the module…first below is the parent module.

# --- PARENT MODULE --- this calls on the child module which contains the meat and potatoes of the config --- #module "instance" {
source = "../"
#note: if you are using a git repo you can use the same link from the clone/download option for the source attribute above, but it will only read from master. Check terraform doucmentation on how to read from branches other than masterinstance_override = var.instance_override
# or you can just enter the boolen value above true or false
set_instance_type = var.set_instance_type
# or you can enter the instance_type in "string" format
region = var.region
#or you can enter the region in "string" format
}

Note: CHILD module variables supersede PARENT module variables unless they are explicitly defined in the module block. The best practice is to leave child module variables null or empty “ ”, allowing you to set them in the parent module variables.

Commands to import our AWS resources:

https://www.terraform.io/docs/cli/commands/import.html

terraform init

Ec2 import:

terraform import module.<module name>.aws_instance.<instance name from tf> <instance id from aws>terraform import module.instance.aws_instance.tfimportec2 i-078654854b88d8fe8Elastic ip assocaited with the instance:terraform import module.instance.aws_eip.tfeip1 eipalloc-0f6e1a3dc02ffdf90terraform import module.instance.aws_eip_association.eip_assoc1 eipassoc-098a46cff0b605ded

Now try and import the 3 above resources…

Before we import you can see I currently have no resources in my terraform state

Now import your ec2 instance then run:

terraform state list

Boom! We’re in

~/Desktop/tfimporttest/modules -- terraform import module.instance.aws_instance.tfimportec2 i-078654854b88d8fe8
module.instance.aws_instance.tfimportec2: Importing from ID "i-078654854b88d8fe8"...
module.instance.aws_instance.tfimportec2: Import prepared!
Prepared aws_instance for import
module.instance.aws_instance.tfimportec2: Refreshing state... [id=i-078654854b88d8fe8]
Import successful!The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
~/Desktop/tfimporttest/modules -- terraform state list
module.instance.aws_instance.tfimportec2

Now run a terraform plan and you can then add in any attribute to satisfy the plan. You want to see an update in place not replaced or #forces replacement.

# module.instance.aws_instance.tfimportec2 will be updated in-place
~ resource "aws_instance" "tfimportec2" {
- iam_instance_profile = "Ec2Full" -> null
id = "i-078654854b88d8fe8"
~ tags = {
- "Name" = "ec2One" -> null
- "Terraform Managed" = "False" -> null
}
# (27 unchanged attributes hidden)
# (6 unchanged blocks hidden)
}

You will see in the plan above for this resource that we need to add a few things to our resource configuration to stop some attributes from being deleted (I.e. tags, I am instance profile)

New config:

# --- CHILD MODULE --- #
resource "aws_instance" "tfimportec2" {
iam_instance_profile = "Ec2Full"
ami = var.ami[var.region]
#ami IS REGION SPECIFIC - please check which region you are in if you receive an errorinstance_type = var.instance_override == false ? var.instance_type : var.set_instance_typekey_name = "tf-demo"
tags = {
"Name" = "ec2One"
"Terraform Managed" = "False"
}
}

Now import the rest of whatever resources you would like and we will like at the final plan and then finally talk about the “count =” attribute that will change how you import each resource that is provisioned form more than one of the same resource.

Quick look at importing the rds resource:

terraform import module.<module name>.aws_db_instance.<db instance name from tf> <name of rds db>terraform import module.instance.aws_db_instance.tfdatabase tf-import-mysql

Now our resources should all be showing under terraform state list:

~/Desktop/tfimporttest/modules  terraform state list
module.instance.aws_db_instance.tfdatabase
module.instance.aws_eip.tfeip1
module.instance.aws_eip_association.eip_assoc1
module.instance.aws_instance.tfimportec2

I still have a couple of updated to make but very minor and no replacements:

# module.instance.aws_db_instance.tfdatabase will be updated in-place
~ resource "aws_db_instance" "tfdatabase" {
+ apply_immediately = true
~ backup_retention_period = 7 -> 0
~ copy_tags_to_snapshot = true -> false
id = "tf-import-mysql"
- max_allocated_storage = 21 -> null
tags = {}
# (39 unchanged attributes hidden)
# (1 unchanged block hidden)
}
# module.instance.aws_eip.tfeip1 will be updated in-place
~ resource "aws_eip" "tfeip1" {
id = "eipalloc-0f6e1a3dc02ffdf90"
~ tags = {
~ "Name" = "eip1" -> "Tf-eip"
}
# (11 unchanged attributes hidden)
# (1 unchanged block hidden)
}
Plan: 0 to add, 2 to change, 0 to destroy.

Count parameter on any resource:
When importing resources with a count parameter like below your terraform import command has to include single quotes around the resource with the value (count) of the resource.

# --- ec2 instance resources --- ## --- CHILD MODULE --- #resource "aws_instance" "tfimportec2" {
count = var.count_id
iam_instance_profile = "Ec2Full"
ami = var.ami[var.region]
#ami IS REGION SPECIFIC - please check which region you are in if you receive an error
instance_type = var.instance_override == false ? var.instance_type : var.set_instance_type
key_name = "tf-demo"
tags = {
"Name" = "ec2One"
"Terraform Managed" = "False"
}
}
# --- PARENT MODULE ---module "instance" {
source = "../"

instance_override = true
set_instance_type = var.instance_type
region = var.region
count = var.count_id
}
#---- add this variable in to both the child and parent module for consistency --- #variable "count_id" {
type = number
default = 2
}

Importing our aws_instance with a count of 2, we will do our second instance that was shown in the set-up section:

terraform import 'module.instance.aws_instance.tfimportec2[1]' i-02cd6a5f093b9e204

[1] serves as the second instance as we have already imported the first one which would’ve been [0] with the proper id. For your eip and eip_association you would have to use the count.index value before importing.

resource "aws_eip" "tfeip1" {
count = var.count_id
instance = aws_instance.tfimportec2[count.index].id
vpc = true
tags = {
Name = "Tf-eip"
}
}
resource "aws_eip_association" "eip_assoc1" {
count = var.count_id
instance_id = aws_instance.tfimportec2[count.index].id
allocation_id = aws_eip.tfeip1[count.index].id
}
~/Desktop/tfimporttest/modules  terraform import 'module.instance.aws_instance.tfimportec2[1]' i-02cd6a5f093b9e204
module.instance.aws_instance.tfimportec2[1]: Importing from ID "i-02cd6a5f093b9e204"...
module.instance.aws_instance.tfimportec2[1]: Import prepared!
Prepared aws_instance for import
module.instance.aws_instance.tfimportec2[1]: Refreshing state... [id=i-02cd6a5f093b9e204]
Import successful!The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
~/Desktop/tfimporttest/modules  terraform import 'module.instance.aws_eip.tfeip1[1]' eipalloc-03a089d76beec0c7a module.instance.aws_eip.tfeip1[1]: Importing from ID "eipalloc-03a089d76beec0c7a"...
module.instance.aws_eip.tfeip1[1]: Import prepared!
Prepared aws_eip for import
module.instance.aws_eip.tfeip1[1]: Refreshing state... [id=eipalloc-03a089d76beec0c7a]
Import successful!The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
~/Desktop/tfimporttest/modules  terraform import module.'instance.aws_eip_association.eip_assoc1[1]' eipassoc-03a089d76beec0c7a
module.instance.aws_eip_association.eip_assoc1[1]: Importing from ID "eipassoc-03a089d76beec0c7a"...
module.instance.aws_eip_association.eip_assoc1[1]: Import prepared!
Prepared aws_eip_association for import
module.instance.aws_eip_association.eip_assoc1[1]: Refreshing state... [id=eipassoc-03a089d76beec0c7a]
Import successful!The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.

Now that we have all of our resources imported we can run the command below and tear it all down.

terraform destroy --auto-approve 

Thanks for reading! If anything is unclear please comment and I will reply if I am able to assist!

--

--

Bmwitcher
Nerd For Tech

DevSecOps Professional — AWS Certified DevOps Professional/Security Specialty/SA Pro, Gitlab Certified, Terraform Associate GCP-ACE Certfied and more…