Import existing AWS infrastructure into Terraform

by Afanasy Barbarov

The recipe: migrate the existing blog to AWS

AWS has tons of services. Multiplying that to the number of regions gives us a lot more. It's hard to remember what and where something is applied in the AWS console. Let's fix that by migrating this blog to Terraform managed solution.

Since we don't create the infrastructure from scratch, we need to import it to Terraform. I use these services in my blog so far, so need to import them:

  • S3
  • Route53
  • CloudFront

Let's start importing them one by one. But first, create a folder, called aws/tf to keep everything inside and create an empty main.tf file inside. Naming is not strict, but I chose that one for logical separation.

We need a provider to work with AWS. Adding

provider "aws" {
  profile = "barbarov"
  region  = "eu-west-1"

  # Make it faster by skipping some checks
  skip_get_ec2_platforms      = true
  skip_metadata_api_check     = true
  skip_region_validation      = true
  skip_credentials_validation = true
  skip_requesting_account_id  = true
}

will tell Terraform that we are using AWS. The next two things are a backend (to store Terrafom's state), and a DynamoDB table (to handle concurrent locks of the state).

resource "aws_s3_bucket" "s3_terraform_state" {
  bucket = "terraform-state.barbarov.com"

  versioning {
    enabled = true
  }

  lifecycle {
    prevent_destroy = true
  }

  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        sse_algorithm = "AES256"
      }
    }
  }

  tags = {
    Name        = "S3 Remote Terraform State"
    Environment = "Production"
  }
}

resource "aws_dynamodb_table" "terraform_locks" {
  name         = "terraform-barbarov-com-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }

  tags = {
    Name        = "DynamoDB Table Terraform Locks"
    Environment = "Production"
  }
}

We first initialize DynamoDB and S3 bucket, and only then use them to keep the state. To physically create resources, navigate to the aws/tf folder and run terraform init and then terraform apply.

Since I have multiple AWS profiles installed, I need to run first export AWS_PROFILE="barbarov" to use the correct one, otherwise I'd face permission errors.

Now let's glue both resources to keep the state. Remove .terraform folder and .terraform lock files. Then add this part and running terraform init once again and then terraform apply will achieve that.

terraform {
  backend "s3" {
    encrypt        = true
    bucket         = "terraform-state.barbarov.com"
    region         = "eu-west-1"
    key            = "global/terraform.tfstate"
    dynamodb_table = "terraform-barbarov-com-locks"
  }
}

So we have initialized the infrastructure, so let's import services. Create an empty resource

resource "aws_s3_bucket" "static_site" {
}

And import configuration into it.

terraform import aws_s3_bucket.static_site barbarov.com

Now, the resource is imported, but we need to control it. So we must add some resource description to terraform. To achieve that, there is a hack - comment the resource and try terraform plan to see the changes and copy the configuration (but DON'T APPLY the changes).

My config become similar to this after the step from above:

resource "aws_s3_bucket" "static_site" {
  bucket = "barbarov.com"
  acl    = "private"

  tags = {
    "Project" = "portfolio"
    "Type"    = "S3"
  }

  website {
    index_document = "index.html"
  }

  force_destroy = false
}

Let's do the same for the CloudFront - create an empty resource definition

resource "aws_cloudfront_distribution" "static_site_distribution" {
}

and import it:

terraform import \
  aws_cloudfront_distribution.static_site_distribution DISTRIBUTIONID

And last, but not least, import Route53 - create a resource

resource "aws_cloudfront_distribution" "static_site_distribution" {
}

and import it:

terraform import aws_route53_record.main_domain ZONEID_barbarov.com_A

Now we are free to change everything via Terraform scripts


That's all, folks!

Written by Afanasy Barbarov — Tech Lead with 15+ years shipping production systems in Rust, Go, and TypeScript. Facing a similar challenge? Reach out on LinkedIn. Support my work.

More articles

Previous post

Cleanup GitHub resources after PR merged.

Read more

Next post

The recipe: Create a template for Go lambda functions running on Graviton processors.

Read more