I have Terraform provisioning my EC2 instances, and Packer building AMIs, but how do you get those AMI's onto the EC2 instances Terraform deployed? ....and without copy and pasting the AMI IDs Packer spits out into my terraform files. I want to automate this!

Here is my first attempt at a quick solution:


Create a file called bastion.json:

{
  "variables": {
    "aws_access_key": "",
    "aws_secret_key": ""
  },
  "builders": [{
    "type": "amazon-ebs",
    "access_key": "{{user `aws_access_key`}}",
    "secret_key": "{{user `aws_secret_key`}}",
    "region": "us-west-1",
    "source_ami_filter": {
      "filters": {
      "virtualization-type": "hvm",
      "name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",
      "root-device-type": "ebs"
      },
      "owners": ["099720109477"],
      "most_recent": true
    },
    "instance_type": "t2.micro",
    "ssh_username": "ubuntu",
    "ami_name": "stage-bastion {{timestamp}}"
  }],
  "provisioners": [
    {   
      "type": "shell",
      "inline": [
        "sudo apt-get install -y ruby-full build-essential",
        "sudo apt-get update -y",
        "sudo apt-get install -y rubygems",
        "sudo gem install lolcat"
      ]   
    }   
  ]
}

The image is mostly copied from here. The only thing I changed was the region and the ami_name and I added a provisioners section:

{
  "provisioners": [
    {   
      "type": "shell",
      "inline": [
        "sudo apt-get install -y ruby-full build-essential",
        "sudo apt-get update -y",
        "sudo apt-get install -y rubygems",
        "sudo gem install lolcat"
      ]   
    }   
  ]
}

This installs the extremely important lolcat

Validate our packer syntax:

packer validate bastion.json

Build our AMI:
Note: You will have to add your personal keys to the script below

packer build \
  -var "aws_access_key=$AWS_ACCESS_KEY" \
  -var "aws_secret_key=$AWS_SECRET_KEY" \
  bastion.json | tee packer_output.txt

Extract out the AMI ID and save it to a packer_ami.tfvars file:

cat packer_output.txt | tail -n 2 \ 
  | sed '$ d' \
  | sed "s/us-west-1: /packer_built_bastion_ami = \"/" \
  | sed -e 's/[[:space:]]*$/\"/' > packer_ami.tfvars
  
  cat packer_ami.tfvars

Steps this script takes:

  • prints out the result of the packer build
  • grabs the last 2 lines of output
  • strips out the last blank line
  • replaces our region "us-west-1" with our variable name "packer_built_ami"
  • strips out the trailing white space, and closes the quotes
  • outputs the result to a file called packer_ami.tfvars

The packer_ami.tfvars file should look this:

packer_built_bastion_ami = "ami-c6af5bcd"

Altogether in a script build_ami.sh:

#!/usr/bin/env bash

set -e

packer build \
  -var "aws_access_key=$AWS_ACCESS_KEY" \
  -var "aws_secret_key=$AWS_SECRET_KEY" \
  bastion.json | tee packer_output.txt

cat packer_output.txt | tail -n 2 \ 
  | sed '$ d' \
  | sed "s/us-west-1: /packer_built_bastion_ami = \"/" \
  | sed -e 's/[[:space:]]*$/\"/' > packer_ami.tfvars

cat packer_ami.tfvars

Add code for the dynamic AMI in Terraform:
Note: The rest of the code for this Bastion can be found here

resource "aws_instance" "bastion" {
  ami = "${var.packer_built_bastion_ami}"
  # ...
}

variable "packer_built_bastion_ami" {}

Apply your terraform changes with the newly Created AMI:

terraform plan -var-file=packer_ami.tfvars 

Now when you SSH onto your bastion:

ssh -i ~/.aws/you_key_name ubuntu@43.33.124.124

Note: Your key name and IP address will be different. For more information about this setup, read here

And then I can finally see all the important events in a rainbow, without the tedious installation time!

cat /usr/share/calendar/calendar.history | lolcat

lolcat


TLDR

Add These Files


main.tf

provider "aws" {
  region = "us-west-1"
}

variable "packer_built_bastion_ami" {}

resource "aws_default_vpc" "default" {}

resource "aws_instance" "bastion" {
  ami                         = "${var.packer_built_bastion_ami}"
  key_name                    = "${aws_key_pair.bastion_key.key_name}"
  instance_type               = "t2.micro"
  security_groups             = ["${aws_security_group.bastion-sg.name}"]
  associate_public_ip_address = true
}

resource "aws_security_group" "bastion-sg" {
  name   = "bastion-security-group"
  vpc_id = "${aws_default_vpc.default.id}"

  ingress {
    protocol    = "tcp"
    from_port   = 22
    to_port     = 22
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    protocol    = -1
    from_port   = 0 
    to_port     = 0 
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_key_pair" "bastion_key" {
  key_name   = "your_key_name"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDWbz6ur89BKQ+am87EovJsv6g9QpbOiw13lTF7Kw1StbQAmkcGGrNTK2LIWsP3cQf+P+gptRAJbuqB1jQKZ283TwwREIv+l5AMKrbEkanOF4zsc8a9zitejlOLvVUxtVoMi5ROVYD2dLKjqAbDtqIC9LmMD+hcpqcXLhS6t+HVSVI862dTNVFY1EGukLGQ3IEJfw5v7FDzLn72NsuUiXEeCZu8DtlXLCTYRnqv+XkJQWVocPdFDUWISSIQ0CTFu+GJvJjdqDyAhYo3it7Eybj6XuSgLDwkQcNU45Ewz4Nn7LwV+f4Av8D25m4FZOfpWaj5+q9Fc9nRdIsB7P0oFgj5YoaTngQKy27MJ5UppMO7OOhriurJ/PBOrGpeqPcftWKLpcHLIGrm3ndoDKQx12R1s0gyYpA4JuNUWHYcxNrFa2rs/6AoFuS7wNUmM+DYB8iTjOl6dT8dS5AgMxGoZ3NepMPYilw1gf+gw9Ft3pHs2IMfDfqwZpXga8KdYwxBmRakpHdA7Nzje8ufvP/TBawsqVcW7z5gG9uPhYtfnYYezSIxv56PMSWEfqchkz+raPsElzIGtPcC1snncQlau95utV25r88BzXhCMJwNy9aDNEfSrm5SORlA97xicroCOuRjw2PnQyIXKvWDZtyqX5799x37K/HDYpJnvcgwpTlDZQ== your_email@example.com"
}

output "bastion_public_ip" {
  value = "${aws_instance.bastion.public_ip}"
}

bastion.json

{
  "variables": {
    "aws_access_key": "", 
    "aws_secret_key": ""
  },  
  "builders": [
    {   
      "type": "amazon-ebs",
      "access_key": "{{user `aws_access_key`}}",
      "secret_key": "{{user `aws_secret_key`}}",
      "region": "us-west-1",
      "source_ami_filter": {
        "filters": {
          "virtualization-type": "hvm",
          "name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",
          "root-device-type": "ebs"
        },  
        "owners": [
          "099720109477"
        ],  
        "most_recent": true
      },  
      "instance_type": "t2.micro",
      "ssh_username": "ubuntu",
      "ami_name": "stage-bastion {{timestamp}}"
    }   
  ],  
  "provisioners": [
    {   
      "type": "shell",
      "inline": [
        "sudo apt-get update -y",
        "sudo apt-get install -y ruby-full build-essential",
        "sudo apt-get install -y rubygems",
        "sudo gem install lolcat"
      ]   
    }   
  ]
}

build_ami.sh

#!/usr/bin/env bash

set -e

packer build \
  -var "aws_access_key=$AWS_ACCESS_KEY" \
  -var "aws_secret_key=$AWS_SECRET_KEY" \
  bastion.json | tee packer_output.txt

cat packer_output.txt | tail -n 2 \ 
  | sed '$ d' \
  | sed "s/us-west-1: /packer_built_bastion_ami = \"/" \
  | sed -e 's/[[:space:]]*$/\"/' > packer_ami.tfvars

cat packer_ami.tfvars

Run these scripts

export AWS_ACCESS_KEY=<YOU PERSONAL AWS ACCESS KEY GOES HERE>
export AWS_SECRET_KEY=<YOU PERSONAL AWS SECRET KEY GOES HERE>
chmod +x build_ami.sh
./build_ami.sh
terraform init
terraform apply -var-file=packer_ami.tfvars

Profit!

ssh -i ~/.aws/you_key_name ubuntu@43.33.124.124
cat /usr/share/calendar/calendar.history | lolcat