Hello!

Today we will see how you can import existing resources into terraform and how to organize the code into a module for future reuse.

Import

There is a situation when the resources have already been created and you need to start managing them using terraform. Or the resource was removed from the terraform state and you need to add it there again. This is very easy to do with terraform import.

There is a code that creates an ec2 server, but the server already exists

resource "aws_instance" "foo" {
  ami                    = data.aws_ami.amzn.id
  instance_type          = var.instance_type
  subnet_id              = "subnet-db73f0ac"
  vpc_security_group_ids = ["sg-b04b8cd4"]
  tags = {
    Env = "Dev"
  }
  volume_tags = {
    "Env" = "Dev"
  }
}

If you run terraform, the server will be created one more time

$ terraform plan
data.aws_ami.amzn: Reading...
data.aws_ami.amzn: Read complete after 0s [id=ami-05fa00d4c63e32376]

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_instance.foo will be created
  + resource "aws_instance" "foo" {
      + ami                                  = "ami-05fa00d4c63e32376"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "c5.large"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + monitoring                           = (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         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = "subnet-db73f0ac"
      + tags                                 = {
          + "Env" = "Dev"
        }
      + tags_all                             = {
          + "Env" = "Dev"
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + volume_tags                          = {
          + "Env" = "Dev"
        }
      + vpc_security_group_ids               = [
          + "sg-b04b8cd4",
        ]

      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)

          + capacity_reservation_target {
              + capacity_reservation_id                 = (known after apply)
              + capacity_reservation_resource_group_arn = (known after apply)
            }
        }

      + 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 = (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 = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
          + instance_metadata_tags      = (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    = (known after apply)
          + enable_resource_name_dns_aaaa_record = (known after apply)
          + hostname_type                        = (known after apply)
        }

      + root_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)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

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

To import the existing server into terraform, you need to run terraform import tf_resource.resource_name existing_resource_id. Since I want to import aws_instance named foo with id i-0ea46560d61c09724, the command will look like terraform import aws_instance.foo i-0ea46560d61c09724.

$ terraform import aws_instance.foo i-0ea46560d61c09724
aws_instance.foo: Importing from ID "i-0ea46560d61c09724"...
aws_instance.foo: Import prepared!
  Prepared aws_instance for import
aws_instance.foo: Refreshing state... [id=i-0ea46560d61c09724]

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.

If you run terraform plan again, the result will be different.

$ terraform plan
data.aws_ami.amzn: Reading...
data.aws_ami.amzn: Read complete after 0s [id=ami-05fa00d4c63e32376]
aws_instance.foo: Refreshing state... [id=i-0ea46560d61c09724]

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.

Modules

The module allows you to combine several terraform resources into one abstraction for convenient reuse. I will take the code from one of the previous posts that creates a server and security group. And let’s assume that you need to create 5 more similar servers and security groups. Of course, you can copy this code 5 times and change the attributes, or you can parameterize it and create a module.

resource "aws_instance" "foo" {
  ami                    = data.aws_ami.amzn.id
  instance_type          = var.instance_type
  subnet_id              = "subnet-db73f0ac"
  vpc_security_group_ids = [aws_security_group.test_sg.id]
  tags = {
    Env = "Dev"
  }
  volume_tags = {
    "Env" = "Dev"
  }
}

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"]
  }
}

Let’s add parameters. For security_group, I will create two parameters vpc and another with a list of ports that should be opened. There will be four parameters for the server: ami, subnet, instance_type, and security group. And one general parameter will be called prefix. It is needed to create different names for resources, or you can simply make a name parameter that will set the name for everything

variable "instance_type" {
  default     = "t2.micro"
  description = "Instance type for test instance"
  type        = string
}

variable "subnet_id" {
  description = "Subnet id where server will be created"
  type        = string
}

variable "security_groups" {
  description = "List of sg for instance"
  type        = list(string)
}

variable "ports_list" {
  description = "List of port which should be open"
  type        = list(number)
}

variable "ami_id" {
  description = "Id of ami which should be used for instance"
  type        = string
}

variable "vpc_id" {
  description = "Id of vpc where sg will be created"
  type        = string
}

variable "prefix" {
  description = "Prefix to be added to all created resources"
  type        = string
}

And in the code itself, we will specify to use of these parameters. Inside of vpc_security_group_ids i am using concat method to add security group id from new security group to list with security groups var.security_groups.

resource "aws_instance" "foo" {
  ami                    = var.ami_id
  instance_type          = var.instance_type
  subnet_id              = var.subnet_id
  vpc_security_group_ids = concat(var.security_groups, [aws_security_group.test_sg.id])
  tags = {
    Name = "${var.prefix}-common-instance"
  }
}

resource "aws_security_group" "test_sg" {
  name        = "${var.prefix}-common-sg"
  description = "allow traffic to ${var.prefix}-common-instance"
  vpc_id      = var.vpc_id

  dynamic "ingress" {
    for_each = var.ports_list
    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"]
  }
}

Now all that’s left is to move it to a separate folder and use it as a module. I will create a modules folder in which I will create another common_server into which I will move the created code.

$ tree modules
modules
└── common_server
    ├── main.tf
    └── variables.tf

1 directory, 2 files

Now I can create a new terraform file(main.tf) and specify to use the module. Where a source is module location in a file system.

module "common_server" {
  source          = "./modules/common_server"
  ami_id          = data.aws_ami.amzn.id
  security_groups = ["sg-b04b8cd4"]
  vpc_id          = "vpc-e5543380"
  subnet_id       = "subnet-5f70bf74"
  ports_list      = [80, 443]
  prefix          = "maksym"
  instance_type   = "t2.micro"
}

If I run terraform right away I get an error

$ terraform plan
│ Error: Module not installed
│   on main.tf line 1:
│    1: module "common_server" {
│ This module is not yet installed. Run "terraform init" to install all modules required by this configuration.

First, you need to run terraform init to initialize the module.

$ terraform init
Initializing modules...
- common_server in modules/common_server

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v4.23.0

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

After that, you can run terraform plan.

terraform plan
data.aws_ami.amzn: Reading...
data.aws_ami.amzn: Read complete after 1s [id=ami-05fa00d4c63e32376]

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:

  # module.common_server.aws_instance.foo will be created
  + resource "aws_instance" "foo" {
      + ami                                  = "ami-05fa00d4c63e32376"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t2.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + monitoring                           = (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         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = "subnet-5f70bf74"
      + tags                                 = {
          + "Name" = "maksym-common-instance"
        }
      + tags_all                             = {
          + "Name" = "maksym-common-instance"
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)

      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)

          + capacity_reservation_target {
              + capacity_reservation_id                 = (known after apply)
              + capacity_reservation_resource_group_arn = (known after apply)
            }
        }

      + 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 = (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 = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
          + instance_metadata_tags      = (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    = (known after apply)
          + enable_resource_name_dns_aaaa_record = (known after apply)
          + hostname_type                        = (known after apply)
        }

      + root_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)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # module.common_server.aws_security_group.test_sg will be created
  + resource "aws_security_group" "test_sg" {
      + arn                    = (known after apply)
      + description            = "allow traffic to maksym-common-instance"
      + egress                 = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 0
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "-1"
              + security_groups  = []
              + self             = false
              + to_port          = 0
            },
        ]
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 443
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 443
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 80
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 80
            },
        ]
      + name                   = "maksym-common-sg"
      + name_prefix            = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags_all               = (known after apply)
      + vpc_id                 = "vpc-e5543380"
    }

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

Now, to create several versions of servers, you just need to copy the module in main.tf and change the parameters.

module "common_server" {
  source          = "./modules/common_server"
  ami_id          = data.aws_ami.amzn.id
  security_groups = ["sg-b04b8cd4"]
  vpc_id          = "vpc-e5543380"
  subnet_id       = "subnet-5f70bf74"
  ports_list      = [80, 443]
  prefix          = "maksym"
  instance_type   = "t2.micro"
}

module "appserver" {
  source          = "./modules/common_server"
  ami_id          = data.aws_ami.amzn.id
  security_groups = ["sg-b04a9dc5"]
  vpc_id          = "vpc-e5543380"
  subnet_id       = "subnet-5f70bf74"
  ports_list      = [80, 443, 9090, 8888]
  prefix          = "app"
  instance_type   = "m5.large"
}

The module does not necessarily have to be located on the file system to be used, as a source you can specify a link to source control such as GitHub or to terraform registry, and the module will be automatically downloaded.

Modules import

Now, if you try to import the resource as we did at the beginning, it will not work because now the resources have different terraform names. For example, aws_instance before the module was displayed as aws_instance.foo, now the new name will be module.common_server.aws_instance.foo. Accordingly, to import this same server, the command will look like terraform import module.common_server.aws_instance.foo i-0ea46560d61c09724.

Video