01. March 2018 · Comments Off on PHP YAML data collector to build Vultr servers · Categories: Ansible, Cloud, Linux, PHP, Vultr · Tags: , , , , , ,

A few days ago I posted an Ansible playbook to provision & deprovision virtual servers on VULTR. One of the key features of that playbook would read a separate YAML file for its list of servers to build. The following is a simple yaml data collector written in PHP to generate the YAML for an Ansible playbook to consume.

VR Server Request

Long story short the heart of the script is the yaml_emit_file function that I use to dump POST data directly into a YAML file. As stated in my previous posting, anything could generate that file, be it the user themselves or a ticketing system.

yaml-data-collector.php: PHP script, ASCII text

< ?php
/*
   # yaml-data-collector
   # Simple php form to collect information from a user on what servers to build.
   2017 (v1.0) - Script from www.davideaves.com
*/
 
if (basename($_SERVER["PHP_SELF"]) == basename(__FILE__) && isset($_GET["source"])) {
  header("Content-type: text/plain");
  exit(file_get_contents(basename($_SERVER["PHP_SELF"])));
}
 
// Uncomment to view debug points in the code.
//$debug = "yes";
 
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  // Update $yamlfile
  yaml_emit_file("$_POST[tag].yaml", $_POST);
}
 
if(isset($_REQUEST['DATA'])):
  if (file_exists($_REQUEST['DATA'])) {
    $yamldata = yaml_parse_file($_REQUEST['DATA']);
    // Reindex the SERVERS array numerically incase broken.
    $yamldata['vultr']['servers'] = array_values($yamldata['vultr']['servers']);
  }
endif
?>
< !DOCTYPE html>
<html>
  <head>
     <title>< ?php echo gethostname(); ?>: VULTR Server Req.</title>
     <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
     <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"/>
     <meta http-equiv="Pragma" content="no-cache"/>
     <script>
     /**
      * Projet: Dynamic dataTable.
      * Add/Delet rows from an html table.
      */
 
     var maxRows = 5;
 
     function addRow(tableID) {
          var table = document.getElementById(tableID);
          var rowCount = table.rows.length;
          if(rowCount < maxRows) {   // limit the user from creating fields more than your limits
               var row = table.insertRow(rowCount);
               var colCount = table.rows[0].cells.length;
               for(var i=0; i<colCount; i++) {
                    var newcell = row.insertCell(i);
                    newcell.innerHTML = table.rows[0].cells[i].innerHTML.replace(RegExp('\[[0-9]+\]'), '\[' + rowCount + '\]');
               }
          } else {
               alert("Maximum rows reached.");
          }
     }
     function deleteRow(tableID) {
          var table = document.getElementById(tableID);
          var rowCount = table.rows.length;
          for(var i=0; i<rowCount; i++) {
               var row = table.rows[i];
               var chkbox = row.cells[0].childNodes[0];
               if(null != chkbox && true == chkbox.checked) {
                    if(rowCount <= 1) {   // limit the user from removing all the fields
                         alert("Cannot remove all rows.");
                         break;
                    }
                    table.deleteRow(i);
                    rowCount--;
                    i--;
               }
          }
     }
     </script>
     <style>
     body {background-color: grey;}
     form {margin-top: 15px; margin-left: 15px; margin-right: 15px;}
     form a {color: blue; text-decoration: none; margin: 0px;}
     form label {display:inline-block; width:48%;}
     form input[type="text"] {width:48%;}
     form input[type="submit"] {width:150px;}
     form select {width:48%;}
     form .line {clear:both;}
     #DATANAV {background-color: #4ff; opacity: 0.5; border: solid 1px #999; border-radius: 5px; font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; text-decoration: none; text-align: center; position: absolute; top: 15px; right: 15px; padding: 0 15px 0 15px; align: center;}
     #DATANAV a {text-decoration: none; font-size: 15px; color: red;}
     #DATANAV a:hover {font-weight: bold; color: maroon;}
     #DATANAV label {text-decoration: underline; color: black;}
     #TEMPLATE {width:1000px; margin:20px auto; background: #EEE; border: solid 1px #999; position: float; vertical-align: middle; border-radius: 5px;}
     #TEMPLATE fieldset {border-radius: 5px; border-width:2px; border-style:dotted; margin:5px;}
     #TEMPLATE h2 {margin:0; font-family:Arial, Sans-Serif; background:#5f9be3; color:#fff; white-space: nowrap; font-weight:bold; padding:0px; text-align: center;}
     #TEMPLATE span.config {background-color:black; color:white; font-family:monospace; white-space:pre; display:block;}
     #TEMPLATE mark {background-color:black; color:yellow; font-style:italic;}
     #TEMPLATE form {margin-top: 15px; margin-left: 15px; margin-right: 15px;}
     #TEMPLATE form label {display:inline-block; width:48%;}
     #TEMPLATE form a {color: blue; text-decoration: none; margin: 0px;}
     #TEMPLATE form input[type="text"] {width:50%;}
     #TEMPLATE form input[type="submit"] {width:100%;}
     #TEMPLATE form select {width:50.6%;}
     #TEMPLATE form .line {clear:both;}
     </style>
  </script></head>
<body>< ?php if (isset($debug)) {
  echo "<span class=\"debug\">";
  print_r(yaml_emit($_POST));
  echo "<hr /><br />\n";
} ?>
 
<div id="DATANAV">
<p><a class="button" href="?">New Request</a></p>
<p><label>Previous Tags</label>
<br />< ?php // SELECT THE TEMPLATE
  $templates = glob("*.yaml");
  foreach ($templates as $file) {
    echo "<a href='" . $_SERVER['PHP_SELF'] . "?DATA=" . $file . "'>" . preg_replace('/\\.[^.\\s]{3,4}$/', '', $file). "<br />\n";
  }
?></p></div>
 
 
<div id="TEMPLATE">
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST" onsubmit="return confirm('Are you sure you want to submit this request?');">
<h2>Vultr Server Request</h2>
<fieldset class="row1">
 <legend>Request Details</legend>
 
 <p>
  <div class="line"><label>Tag</label>
   <input name="tag" type="text" required="required" value="<?php if(isset($yamldata['tag'])) { echo $yamldata['tag']; } ?/>" />
  </div>
  <div class="line"><label>Firewall Group</label>
   <select name="firewall_group">
    < ?php if(!isset($yamldata['firewall_group'])) { echo "<option value=\"\" disabled selected>Select your option"; } ?>
    <option value="shellserver" <?php if(isset($yamldata['firewall_group']) && $yamldata['firewall_group'] == "shellserver") { echo selected; } ?>>shellserver</option>
    <option value="webserver" <?php if(isset($yamldata['firewall_group']) && $yamldata['firewall_group'] == "webserver") { echo selected; } ?>>webserver</option>
   </select>
  </div>
 </p>
 
</fieldset>
 
<input type="hidden" name="modified" value="<?php echo date(DATE_RFC2822);?/>">
 
<fieldset class="row2">
 <legend>Server Details</legend>
 
  <p>
   <input type="button" value="Add Server" onClick="addRow('dataTable')" />
   <input type="button" value="Remove Server" onClick="deleteRow('dataTable')" />
  </p>
 
  <table id="dataTable" class="form">
   <tbody>< ?php if(isset($yamldata['vultr']['servers'])) { foreach($yamldata['vultr']['servers'] as $COUNT=>$SERVER){ ?><tr>
    <td><input type="checkbox" /></td>
    <td>
     <label>Domain Name</label>
     <input type="text" required="required" name="vultr[servers][<?php echo $COUNT; ?/>][name]" value="< ?php echo $SERVER['name']; ?>" />
    </td>
    <td>
     <label>Server OS</label>
     <select name="vultr[servers][<?php echo $COUNT; ?>][os]" required="required">
      < ?php if(!isset($yamldata['vultr']['servers'])) { echo "<option value=\"\" disabled selected>Select your option"; } ?>
      <option value="CentOS 7 x64" <?php if(isset($yamldata[vultr][servers][$COUNT][os]) && $yamldata[vultr][servers][$COUNT][os] == "CentOS 7 x64") { echo selected; } ?>>CentOS 7</option>
      <option value="Debian 9 x64 (stretch)" <?php if(isset($yamldata[vultr][servers][$COUNT][os]) && $yamldata[vultr][servers][$COUNT][os] == "Debian 9 x64 (stretch)") { echo selected; } ?>>Debian 9</option>
      <option value="Fedora 27 x64" <?php if(isset($yamldata[vultr][servers][$COUNT][os]) && $yamldata[vultr][servers][$COUNT][os] == "Fedora 27 x64") { echo selected; } ?>>Fedora 27</option>
      <option value="Ubuntu 17.10 x64" <?php if(isset($yamldata[vultr][servers][$COUNT][os]) && $yamldata[vultr][servers][$COUNT][os] == "Ubuntu 17.10 x64") { echo selected; } ?>>Ubuntu 17.10</option>
     </select>
    </td>
    <td>
     <label>Plan</label>
     <select name="vultr[servers][<?php echo $COUNT; ?>][plan]" required="required">
      < ?php if(!isset($yamldata['vultr']['servers'])) { echo "<option value=\"\" disabled selected>Select your option"; } ?>
      <option value="1024 MB RAM,25 GB SSD,1.00 TB BW" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['plan']) && $yamldata['vultr']['servers'][$COUNT]['plan'] == "1024 MB RAM,25 GB SSD,1.00 TB BW") { echo selected; } ?>>$5/month</option>
      <option value="2048 MB RAM,40 GB SSD,2.00 TB BW" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['plan']) && $yamldata['vultr']['servers'][$COUNT]['plan'] == "2048 MB RAM,40 GB SSD,2.00 TB BW") { echo selected; } ?>>$10/month</option>
      <option value="4096 MB RAM,60 GB SSD,3.00 TB BW" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['plan']) && $yamldata['vultr']['servers'][$COUNT]['plan'] == "4096 MB RAM,60 GB SSD,3.00 TB BW") { echo selected; } ?>>$20/month</option>
      <option value="8192 MB RAM,100 GB SSD,4.00 TB BW" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['plan']) && $yamldata['vultr']['servers'][$COUNT]['plan'] == "8192 MB RAM,100 GB SSD,4.00 TB BW") { echo selected; } ?>>$40/month</option>
      <option value="16384 MB RAM,200 GB SSD,5.00 TB BW" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['plan']) && $yamldata['vultr']['servers'][$COUNT]['plan'] == "16384 MB RAM,200 GB SSD,5.00 TB BW") { echo selected; } ?>>$80/month</option>
      <option value="32768 MB RAM,300 GB SSD,6.00 TB BW" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['plan']) && $yamldata['vultr']['servers'][$COUNT]['plan'] == "32768 MB RAM,300 GB SSD,6.00 TB BW") { echo selected; } ?>>$160/month</option>
     </select>
    </td>
    <td>
     <label>Region</label>
     <select name="vultr[servers][<?php echo $COUNT; ?>][region]" required="required">
      < ?php if(!isset($yamldata['vultr']['servers'])) { echo "<option value=\"\" disabled selected>Select your option"; } ?>
      <option value="Atlanta" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['region']) && $yamldata['vultr']['servers'][$COUNT]['region'] == "Atlanta") { echo selected; } ?>>Atlanta</option>
      <option value="Chicago" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['region']) && $yamldata['vultr']['servers'][$COUNT]['region'] == "Chicago") { echo selected; } ?>>Chicago</option>
      <option value="Dallas" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['region']) && $yamldata['vultr']['servers'][$COUNT]['region'] == "Dallas") { echo selected; } ?>>Dallas</option>
      <option value="Los Angeles" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['region']) && $yamldata['vultr']['servers'][$COUNT]['region'] == "Los Angeles") { echo selected; } ?>>Los Angeles</option>
      <option value="Miami" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['region']) && $yamldata['vultr']['servers'][$COUNT]['region'] == "Miami") { echo selected; } ?>>Miami</option>
      <option value="New Jersey" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['region']) && $yamldata['vultr']['servers'][$COUNT]['region'] == "New Jersey") { echo selected; } ?>>New Jersey</option>
      <option value="Seattle" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['region']) && $yamldata['vultr']['servers'][$COUNT]['region'] == "Seattle") { echo selected; } ?>>Seattle</option>
      <option value="Silicon Valley" <?php if(isset($yamldata['vultr']['servers'][$COUNT]['region']) && $yamldata['vultr']['servers'][$COUNT]['region'] == "Silicon Valley") { echo selected; } ?>>Silicon Valley</option>
     </select>
    </td>
   </tr>< ?php } } else { ?><tr>
    <td><input type="checkbox" /></td>
    <td>
     <label>Domain Name</label>
     <input type="text" required="required" name="vultr[servers][0][name]" />
    </td>
    <td>
     <label>Server OS</label>
     <select name="vultr[servers][0][os]" required="required">
      < ?php if(!isset($yamldata['vultr[servers][0][os]'])) { echo "<option value=\"\" disabled selected>Select your option"; } ?>
      <option value="CentOS 7 x64">CentOS 7</option>
      <option value="Debian 9 x64 (stretch)">Debian 9</option>
      <option value="Fedora 27 x64">Fedora 27</option>
      <option value="Ubuntu 17.10 x64">Ubuntu 17.10</option>
     </select>
    </td>
    <td>
     <label>Plan</label>
     <select name="vultr[servers][0][plan]" required="required">
      <option value="" disabled selected>Select your option</option>
      <option value="1024 MB RAM,25 GB SSD,1.00 TB BW">$5/Month</option>
      <option value="2048 MB RAM,40 GB SSD,2.00 TB BW">$10/Month</option>
      <option value="4096 MB RAM,60 GB SSD,3.00 TB BW">$20/Month</option>
      <option value="8192 MB RAM,100 GB SSD,4.00 TB BW">$40/Month</option>
      <option value="16384 MB RAM,200 GB SSD,5.00 TB BW">$80/Month</option>
      <option value="32768 MB RAM,300 GB SSD,6.00 TB BW">$160/Month</option>
     </select>
    </td>
    <td>
     <label>Region</label>
     <select name="vultr[servers][0][region]" required="required">
      <option value="" disabled selected>Select your option</option>
      <option value="Atlanta">Atlanta</option>
      <option value="New Jersey">New Jersey</option>
      <option value="Chicago">Chicago</option>
      <option value="Dallas">Dallas</option>
      <option value="Los Angeles">Los Angeles</option>
      <option value="Miami">Miami</option>
      <option value="New Jersey">New Jersey</option>
      <option value="Seattle">Seattle</option>
      <option value="Silicon Valley">Silicon Valley</option>
     </select>
    </td>
   </tr>< ?php } ?></tbody>
  </table>
 
</fieldset>
<br />
<input class="submit" type="submit" value="Save Request &raquo;" />
</form><br />
</div>
</body>
</html>
20. February 2018 · Comments Off on Using Ansible to provision Vultr virtual servers · Categories: Ansible, Cloud, Linux, Networking, Vultr · Tags: , , ,

Ansible is a great automation tool, for work I’ve been using it primarily as a network policy enforcement and automation tool, like to manage ACLs on Cisco routers. But personally I use it more as a server deployment and automation tool. For a while now I’ve been wanting to migrate a few of my general use virtual servers to Vultr [LINK], but hit a road block with free time. Last week in my lab I upgraded to the development version of Ansible (2.6) and noticed that it Vultr support has been added to the cloud modules so started experimenting with them. After a day or two of experimentation it sprouted into a full blown provisioning playbook. The following is what I created…

What I want is a single playbook that can build a server from scratch and have another playbook that can do the same in reverse. For cost saving and security reasons, the deprovisioning playbook I want to be able to run it in crontab to automatically destroy any LAB VMs I spin up at the end of the day.

The following is the provisioning playbook… I have a host_var file that contains my API key and other things common for all my instances. The instances to be provisioned are passed via a variable called “VARS” when the playbook is ran. Since my vars are modular, if I had a real ticketing system, I could have it output a properly formatted instance.yaml file for the playbook to use. Concerning the Ansible Modules; there is one quirky things about them… It requires an ENVRONMENT variable called VULTR_API_KEY. It also requires a vultr.ini file with the same VULTR_API_KEY to be set there. Just to make things easier I try to deal with that transparently within the playbook so it will create that INI file if it does not automatically exist.

vr_provision.yaml: a /usr/local/bin/ansible-playbook script, Vultr server provisioning

#!/usr/local/bin/ansible-playbook
## Provision virtual instances on VULTR.
## 2018 (v.01) - Playbook from www.davideaves.com
---
- name: "VULTR provision instance"
  hosts: localhost
  connection: local
  gather_facts: false

  environment:
    VULTR_API_KEY: "{{ vultr_common.apikey }}"

  tasks:

    - name: "GET user running the deploy"
      local_action: command whoami
      changed_when: False
      register: WHOAMI
 
    ### Prerequisite validation ###

    - block:
        - name: "Playbook external variables include"
          include_vars: "{{ VARS }}"
          when: (VARS is defined)
      always:
        - name: "Playbook external variables example"
          local_action:
            debug msg="playbook.yaml -e VARS=tag.yaml"
          when: (VARS is not defined)

    - name: "Playbook requirement check"
      fail:
        msg: |
          Required variable is undefined!
              > vultr_common.apikey
              > vultr_common.inventory_file
              > vultr.servers: ...
      when: (vultr_common is undefined) or
            (vultr_common.apikey is undefined) or
            (vultr_common.inventory_file is undefined) or
            (vultr is undefined) or
            (vultr.servers is undefined)

    - name: "Inventory file status"
      stat: path="{{ vultr_common.inventory_file }}"
      register: INVENTORY_FILE

    - name: "Inventory file writeable"
      fail:
        msg: "{{ vultr_common.inventory_file }} not writeable"
      when: not(INVENTORY_FILE.stat.writeable)

    - name: "~/.vultr.ini handling block"
      block:
        - name: "Validate ini file exits"
          file:
            path: "~/.vultr.ini"
            mode: 0600
            state: touch
          changed_when: False
        - name: "VULTR_API_KEY is present"
          ini_file:
            path: "~/.vultr.ini"
            section: default
            option: "VULTR_API_KEY"
            value: "{{ vultr_common.apikey }}"
            no_extra_spaces: yes
            state: present
      rescue:
        - fail:
            msg: "Unable to handle ~/.vultr.ini"
 
    ### Collect account balance and Fail if too low ###

    - name: "VULTR account facts"
      local_action:
        module: vr_account_facts

    - name: "Account balance requirement check"
      fail:
        msg: "Account balance low: {{ ansible_facts.vultr_account_facts.balance|int }}"
      when: (ansible_facts.vultr_account_facts.balance|int > vultr_common.min_balance)
 
    ### Configure SSH Keys ###

    - name: "VULTR user authorized_key"
      local_action:
        module: vr_ssh_key
        name: "{{ WHOAMI.stdout }}"
        ssh_key: "{{ lookup('file', '~/.ssh/authorized_keys') }}"
 
    ### Configure Firewall ###
    # These are additive, nothing is removed unless specified or done manually.

    - name: "VULTR firewall groups"
      local_action:
        module: vr_firewall_group
        name: "{{ firewall_group }}"
      when: (firewall_group is defined) and
            (vultr_common.firewall_group is defined) and
            (vultr_common.firewall_group[firewall_group] is defined)

    - name: "Get public IP of ansible host"
      local_action:
        module: ipify_facts
      when: not(ansible_facts.ipify_public_ip is defined)

    - name: "VULTR firewall rule: {{ firewall_group }}/management"
      local_action:
        module: vr_firewall_rule
        group: "{{ firewall_group }}"
        protocol: tcp
        port: 22
        ip_version: v4
        cidr: "{{ ansible_facts.ipify_public_ip | ipv4 }}/32"
      when: (firewall_group is defined) and
            (ansible_facts.ipify_public_ip is defined) and
            (vultr_common.firewall_group[firewall_group] is defined)

    - name: "VULTR firewall rule: {{ firewall_group }}"
      local_action:
        module: vr_firewall_rule
        group: "{{ firewall_group }}"
        protocol: "{{ item.protocol | default('tcp') }}"
        port: "{{ item.port | default('0') }}"
        ip_version: "{{ item.ip_version | default('v4') }}"
        state: "{{ item.state | default('present') }}"
      with_items: "{{ (vultr_common.firewall_group[firewall_group]) }}"
      when: (firewall_group is defined) and
            (vultr_common.firewall_group[firewall_group] is defined)
 
    ### Deploy Instances ###

    - name: "VULTR provision instances"
      local_action:
        module: vr_server
        name: "{{ item.name }}"
        hostname: "{{ item.name }}"
        ipv6_enabled: yes
        os: "{{ item.os }}"
        plan: "{{ item.plan }}"
        private_network_enabled: yes
        region: "{{ item.region }}"
        ssh_key: "{{ WHOAMI.stdout }}"
        state: present
        firewall_group: "{{ firewall_group | default('') }}"
        force: False
        tag: "{{ tag | default('none') }}"
      with_items: "{{ vultr.servers }}"
      register: BUILD
 
    ### Update Ansible inventory ###

    - name: "Initialize SERVER list"
      set_fact: SERVER=[]
      when: (BUILD is defined)
      no_log: True

    - name: "Populate SERVER list"
      set_fact:
        SERVER: "{{ SERVER }} + [ '{{ item.vultr_server.name }},{{ item.vultr_server.v4_main_ip }},{{ item.vultr_server.v6_main_ip }}' ]"
      with_items: "{{ BUILD.results }}"
      when: (SERVER is defined)
      no_log: True

    - name: "Update inventory file with SERVER list"
      ini_file:
        path: "{{ vultr_common.inventory_file }}"
        section: vultr
        option: "{{ item.split(',')[0] }} ansible_host"
        value: "{{ item.split(',')[1] }}"
        no_extra_spaces: yes
        mode: 0666
        state: present
        backup: yes
      with_items: "{{ SERVER }}"
      when: (SERVER is defined)
 
    ### Update DNS ###

    - name: "VULTR DNS domain"
      local_action:
        module: vr_dns_domain
        name: "{{ item.split(',')[0] | regex_replace('^\\w+.') }}"
        server_ip: 127.0.0.1
        state: present
      with_items: "{{ SERVER }}"
      when: (SERVER is defined)

    - name: "VULTR DNS A record"
      vr_dns_record:
        record_type: A
        name: "{{ item.split(',')[0] | regex_replace('[.]\\w*') }}"
        domain: "{{ item.split(',')[0] | regex_replace('^\\w+.') }}"
        data: "{{ item.split(',')[1] }}"
        ttl: 300
      with_items: "{{ SERVER }}"
      when: (SERVER is defined)

    - name: "VULTR DNS AAAA record"
      vr_dns_record:
        record_type: AAAA
        name: "{{ item.split(',')[0] | regex_replace('[.]\\w*') }}"
        domain: "{{ item.split(',')[0] | regex_replace('^\\w+.') }}"
        data: "{{ item.split(',')[2] }}"
        ttl: 300
      with_items: "{{ SERVER }}"
      when: (SERVER is defined)

Below is the host_vars file that I am using for this playbook. The vultr_common.firewall_groups contain the firewall rules to create and must match whats specified in the server.yaml file.

host_vars/localhost.yaml: vultr_common variables

---
vultr_common:
  apikey: !!! YOURAPIKEY !!!
  inventory_file: /etc/ansible/hosts
  min_balance: -50
  firewall_group:
    shellserver:
    -
      protocol: icmp
      ip_version: v4
    -
      protocol: icmp
      ip_version: v6
    webserver:
    -
      protocol: tcp
      port: 80
      ip_version: v4
    -
      protocol: tcp
      port: 443
      ip_version: v4
    -
      protocol: tcp
      port: 80
      ip_version: v6
    -
      protocol: tcp
      port: 443
      ip_version: v6
    -
      protocol: icmp
      ip_version: v4
    -
      protocol: icmp
      ip_version: v6

Below is the list of servers to be created or destroyed by the playbooks.

PROD-WEB.yaml: vultr server list

---
tag: PROD-WEB
firewall_group: webserver
modified: Mon, 5 Feb 2018 21:53:52 -0500
vultr:
  servers:
  - name: curly.example.com
    os: Debian 9 x64 (stretch)
    plan: 1024 MB RAM,25 GB SSD,1.00 TB BW
    region: Chicago
  - name: larry.example.com
    os: Debian 9 x64 (stretch)
    plan: 1024 MB RAM,25 GB SSD,1.00 TB BW
    region: Chicago
  - name: moe.example.com
    os: Debian 9 x64 (stretch)
    plan: 1024 MB RAM,25 GB SSD,1.00 TB BW
    region: Chicago
...

Once the servers are created, I just simply run a followup playbooks against the “vultr” servers section of my inventory file to install, configure and secure any software on the individual servers.


To clean things up, the following is my deprovisioning playbook. This will destroy the servers, related DNS records and host entries in the Ansible inventory. Firewall groups, DNS zones are left intact for future deployments or for other servers that may still be running.

vr_deprovision.yaml: a /usr/local/bin/ansible-playbook script: Vultr server deprovisioning

#!/usr/local/bin/ansible-playbook
## Deprovision virtual instances on VULTR.
## 2018 (v.01) - Playbook from www.davideaves.com
---
- name: "VULTR deprovision instance"
  hosts: localhost
  connection: local
  gather_facts: false

  environment:
    VULTR_API_KEY: "{{ vultr_common.apikey }}"

  tasks:

    ### Prerequisite validation ###

    - block:
        - name: "Playbook external variables include"
          include_vars: "{{ VARS }}"
          when: (VARS is defined)
      always:
        - name: "Playbook external variables example"
          local_action:
            debug msg="playbook.yaml -e VARS=tag.yaml"
          when: (VARS is not defined)

    - name: "Playbook requirement check"
      fail:
        msg: |
          Required variable is undefined!
              > vultr_common.apikey
              > vultr_common.inventory_file
              > vultr.servers: ...
      when: (vultr_common is undefined) or
            (vultr_common.apikey is undefined) or
            (vultr_common.inventory_file is undefined) or
            (vultr is undefined) or
            (vultr.servers is undefined)

    - name: "Inventory file status"
      stat: path="{{ vultr_common.inventory_file }}"
      register: INVENTORY_FILE

    - name: "Inventory file writeable"
      fail:
        msg: "{{ vultr_common.inventory_file }} not writeable"
      when: not(INVENTORY_FILE.stat.writeable)

    - name: "~/.vultr.ini handling block"
      block:
        - name: "Validate ini file exits"
          file:
            path: "~/.vultr.ini"
            mode: 0600
            state: touch
          changed_when: False
        - name: "VULTR_API_KEY is present"
          ini_file:
            path: "~/.vultr.ini"
            section: default
            option: "VULTR_API_KEY"
            value: "{{ vultr_common.apikey }}"
            no_extra_spaces: yes
            state: present
      rescue:
        - fail:
            msg: "Unable to handle ~/.vultr.ini"
 
    ### Destroy Instances ###

    - name: "VULTR deprovision instances"
      local_action:
        module: vr_server
        name: "{{ item.name }}"
        state: absent
      with_items: "{{ vultr.servers }}"
      register: BUILD
 
    ### Update Ansible inventory ###

    - name: "Initialize empty list (SERVER)"
      set_fact: SERVER=[]
      when: (BUILD is defined) and (BUILD.changed)
      no_log: True

    - name: "Populate empty list (SERVER)"
      set_fact:
        SERVER: "{{ SERVER }} + [ '{{ item.vultr_server.name }},{{ item.vultr_server.v4_main_ip }},{{ item.vultr_server.v6_main_ip }}' ]"
      with_items: "{{ BUILD.results }}"
      when: (SERVER is defined)
      no_log: True

    - name: "Remove servers from inventory file."
      ini_file:
        path: "{{ vultr_common.inventory_file }}"
        section: vultr
        option: "{{ item.split(',')[0] }} ansible_host"
        value: "{{ item.split(',')[1] }}"
        no_extra_spaces: yes
        mode: 0666
        state: absent
        backup: yes
      with_items: "{{ SERVER }}"
      when: (SERVER is defined)
 
    ### Update DNS ###

    - name: "VULTR DNS A record"
      vr_dns_record:
        record_type: A
        name: "{{ item.split(',')[0] | regex_replace('[.]\\w*') }}"
        domain: "{{ item.split(',')[0] | regex_replace('^\\w+.') }}"
        state: absent
      with_items: "{{ SERVER }}"
      when: (SERVER is defined)

    - name: "VULTR DNS AAAA record"
      vr_dns_record:
        record_type: AAAA
        name: "{{ item.split(',')[0] | regex_replace('[.]\\w*') }}"
        domain: "{{ item.split(',')[0] | regex_replace('^\\w+.') }}"
        state: absent
      with_items: "{{ SERVER }}"
      when: (SERVER is defined)