Skip to content

Instantly share code, notes, and snippets.

@iMilnb
Last active September 26, 2018 16:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iMilnb/f819e5bb19b530e8bb24577e4cf63eba to your computer and use it in GitHub Desktop.
Save iMilnb/f819e5bb19b530e8bb24577e4cf63eba to your computer and use it in GitHub Desktop.
Custom AWS sandbox using packer, Troposphere / CloudFormation and Ansible

packer template

{
  "variables": {
    "aws_access_key": "",
    "aws_secret_key": ""
  },
  "builders": [{
    "type": "amazon-ebs",
    "access_key": "{{user `aws_access_key`}}",
    "secret_key": "{{user `aws_secret_key`}}",
    "region": "eu-central-1",
    "source_ami": "ami-5900cc36", # debian Jessie
    "instance_type": "t2.micro",
    "ssh_username": "admin",
    "ami_name": "sandbox_base_packer_{{timestamp}}",
    "subnet_id": "subnet-b12121212",
    "security_group_id": "sg-12121212",
    "associate_public_ip_address": false
  }],
  "provisioners": [{
    "type": "shell",
    "inline": [
      "echo 'Acquire::ForceIPv4 \"true\";' | sudo tee /etc/apt/apt.conf.d/99force-ipv4",
      "echo 'deb http://cloudfront.debian.net/debian jessie-backports main' |sudo tee /etc/apt/sources.list.d/backports.list",
      "sudo apt-get update",
      "sudo apt-get -t jessie-backports install -y ansible"
    ]
  }, {
    "type": "ansible-local",
    "playbook_file": "essentials.yml",
    "command": "PYTHONUNBUFFERED=1 ansible-playbook"
  }],
  "post-processors": [
     [
      {
        "output": "/tmp/manifest.json",
        "strip_path": true,
        "type": "manifest"
      }
     ]
  ]
}

Ansible playbook used by this packer template

---

- hosts: all
  become: true

  vars:
    vimrc: |
      syntax on
      set ts=8
      set noai
      set ruler
      set tw=80
      set t_Co=256
      colorscheme molokai-transparent
      set laststatus=2

    loadpowerline: |
      powerline-daemon -q
      POWERLINE_BASH_CONTINUATION=1
      POWERLINE_BASH_SELECT=1

    tmuxconf: |
      set-option -g prefix2 C-q
      set-option -g default-terminal "screen-256color"

    bashrc: |
      EDITOR=vim
      VISUAL=$EDITOR
      export EDITOR VISUAL

    myuser: bob

    mykey: |
      ssh-rsa key n_1
      ssh-rsa key n_2

  tasks:
    - name: installs essential tools
      apt: name={{item}} state=installed
      with_items:
        - vim-nox
        - sudo
        - tmux
        - build-essential
        - git
        - python
        - python-pip

## sudo

    - name: make sudo group passwordless
      blockinfile:
        dest: /etc/sudoers
        block: |
          %sudo ALL=(ALL) NOPASSWD: ALL

## shell

    - name: install powerline from pip
      pip: name=powerline-status

    - file:
        path: "/home/{{myuser}}/.profile.d"
        state: directory
        mode: 0755

    - name: record module path
      command: python -c "import powerline, os; print(os.path.dirname(powerline.__file__))"
      register: powerlinepath
      failed_when: powerlinepath is undefined

    - name: create powerline autoload
      copy: content="{{loadpowerline}}\n. {{powerlinepath.stdout_lines[0]}}/bindings/bash/powerline.sh\n" dest="/home/{{myuser}}/.profile.d/powerline"

    - name: append profile.d autoload
      copy: content="{{bashrc}}\n[ -d ~/.profile.d ] && for s in ~/.profile.d/*;do . $s; done" dest="/home/{{myuser}}/.profile"

## vim

    - name: fetch vim themes
      git:
        repo: https://github.com/hugoroy/.vim
        dest: "/home/{{myuser}}/.vim"

    - name: create basic vimrc
      copy: content="{{vimrc}}\nset rtp+={{powerlinepath.stdout_lines[0]}}/bindings/vim\n" dest="/home/{{myuser}}/.vimrc"

## tmux

    - name: powerline support for tmux
      copy: content="{{tmuxconf}}\nsource {{powerlinepath.stdout_lines[0]}}/bindings/tmux/powerline.conf\n" dest="/home/{{myuser}}/.tmux.conf"

## chown user

    - name: user add
      user:
        name: "{{myuser}}"
        groups: sudo
        shell: /bin/bash

    - name: create .ssh directory
      file:
        path: "/home/{{myuser}}/.ssh"
        state: directory
        mode: 0700

    - name: copy SSH pubkey
      copy:
        content: "{{mykey}}"
        dest: "/home/{{myuser}}/.ssh/authorized_keys"
        mode: 0600

    - name: chown user
      file:
        path: "/home/{{myuser}}"
        owner: "{{myuser}}"
        recurse: yes

Create the AMI:

$ packer build basic.tpl

troposphere to generate CloudFormation template

from troposphere import Base64, Join, Split
from troposphere import Parameter, Ref, Template, Tags
import troposphere.ec2 as ec2
import sys

params = {
    'AmiId': 'AMI Id',
    'InstanceName': 'Name tag of the instance',
    'SecurityGroup': 'Security Group' ,
    'KeyName': 'SSH Key Name' ,
    'InstanceType': 'Instance Type',
    'SubnetA': 'Subnet A',
}

t = Template()

for p in params.keys():
    vars()[p] = t.add_parameter(Parameter(
        p,
        Type = "String",
        Description = params[p]
    ))

for n in range(int(sys.argv[1])):
    if n == 0:
        name = 'master'
    else:
        name = 'slave_{0}'.format(n)
    t.add_resource(ec2.Instance(
        "Ec2Instance{0}".format(n),
        ImageId = Ref(AmiId),
        InstanceType = Ref(InstanceType),
        KeyName = Ref(KeyName),
        SecurityGroupIds = Split(',', Ref(SecurityGroup)),
        SubnetId = Ref(SubnetA),
        Tags = Tags(Name = Join('', [Ref(InstanceName), name])),
    ))

print(t.to_json())

Parameters file:

[
        {
                "ParameterKey": "InstanceType",
                "ParameterValue": "t2.micro",
                "UsePreviousValue": false
        },
        {
                "ParameterKey": "AmiId",
                "ParameterValue": "ami-12121212",
                "UsePreviousValue": false
        },
        {
                "ParameterKey": "KeyName",
                "ParameterValue": "test-eu-central-1",
                "UsePreviousValue": false
        },
        {
                "ParameterKey": "SecurityGroup",
                "ParameterValue": "sg-12121212,sg-21212121",
                "UsePreviousValue": false
        },
        {
                "ParameterKey": "SubnetA",
                "ParameterValue": "subnet-12121212",
                "UsePreviousValue": false
        },
        {
                "ParameterKey": "InstanceName",
                "ParameterValue": "my_test_instance_",
                "UsePreviousValue": false
        }
]

Generate CloudFormation template:

$ python ec2instance.py 3 > cf-ec2instances.json

Create the stack:

$ aws cloudformation create-stack --stack-name my_sandbox --parameters file://cf-ec2instances.params --template-body file://cf-ec2instances.json

Master / Slave example, deploy a master / minion SaltStack

  • Fetch dynamic inventory addon ec2.py
  • Make sure to have an ec2.ini file containing:
[ec2]
# example
regions = eu-central-1
destination_variable = private_dns_name
vpc_destination_variable = private_ip_address

Create the following playbook:

---

- hosts: tag_Name_my_test_instance*
  become: true

  tasks:
    - apt_key:
        url: https://repo.saltstack.com/apt/debian/8/amd64/latest/SALTSTACK-GPG-KEY.pub
        state: present

    - apt_repository:
        repo: deb http://repo.saltstack.com/apt/debian/8/amd64/latest jessie main
        state: present

    - apt: update_cache=true

- hosts: tag_Name_my_test_instance_master
  become: true

  tasks:
    - apt: name=salt-master state=installed


- hosts: tag_Name_my_test_instance_slave*
  become: true

  tasks:
    - apt: name=salt-minion state=installed

    - blockinfile:
        block: "master: salt"
        dest: "/etc/salt/minion"

    - name: update hosts
      blockinfile:
        block: "{{groups['tag_Name_my_test_instance_master'][0]}}      salt"
        dest: "/etc/hosts"

Deploy the playbook:

$ ansible-playbook -i ec2.py salt.yml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment