diff --git a/library/vagrant b/library/vagrant
new file mode 100644
index 00000000000..779dc66bca6
--- /dev/null
+++ b/library/vagrant
@@ -0,0 +1,568 @@
+#!/usr/bin/env python -tt
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+
+DOCUMENTATION = '''
+---
+module: vagrant
+short_description: create a local instance via vagrant
+description:
+ - creates VM instances via vagrant and optionally waits for it to be 'running'. This module has a dependency on python-vagrant.
+version_added: "100.0"
+options:
+ state:
+ description: Should the VMs be "present" or "absent."
+ cmd:
+ description:
+ - vagrant subcommand to execute. Can be "up," "status," "config," "ssh," "halt," "destroy" or "clear."
+ required: false
+ default: null
+ aliases: ['command']
+ box_name:
+ description:
+ - vagrant boxed image to start
+ required: false
+ default: null
+ aliases: ['image']
+ box_path:
+ description:
+ - path to vagrant boxed image to start
+ required: false
+ default: null
+ aliases: []
+ vm_name:
+ description:
+ - name to give an associated VM
+ required: false
+ default: null
+ aliases: []
+ count:
+ description:
+ - number of instances to launch
+ required: False
+ default: 1
+ aliases: []
+ forward_ports:
+ description:
+ - comma separated list of ports to forward to the host. If the port is under 1024, the host port will be the guest port +10000
+ required: False
+ aliases: []
+ memory:
+ description:
+ - memory in MB
+ required: False
+
+examples:
+ - code: 'local_action: vagrant cmd=up box_name=lucid32 vm_name=webserver'
+ description:
+requirements: [ "vagrant" ]
+author: Rob Parrott
+'''
+
+VAGRANT_FILE = "./Vagrantfile"
+VAGRANT_DICT_FILE = "./Vagrantfile.json"
+VAGRANT_LOCKFILE = "./.vagrant-lock"
+
+VAGRANT_FILE_HEAD = "Vagrant::Config.run do |config|\n"
+VAGRANT_FILE_BOX_NAME = " config.vm.box = \"%s\"\n"
+VAGRANT_FILE_VM_STANZA_HEAD = """
+ config.vm.define :%s do |%s_config|
+ %s_config.vm.network :hostonly, "%s"
+ %s_config.vm.box = "%s"
+"""
+VAGRANT_FILE_HOSTNAME_LINE = " %s_config.vm.host_name = \"%s\"\n"
+VAGRANT_FILE_PORT_FORWARD_LINE = " %s_config.vm.forward_port %s, %s\n"
+VAGRANT_FILE_MEMORY_LINE = " %s_config.vm.customize [\"modifyvm\", :id, \"--memory\", %s]\n"
+VAGRANT_FILE_VM_STANZA_TAIL=" end\n"
+
+VAGRANT_FILE_TAIL = "\nend\n"
+
+# If this is already a network on your machine, this may fail ... change it here.
+VAGRANT_INT_IP = "192.168.179.%s"
+
+DEFAULT_VM_NAME = "ansible"
+DEFAULT_VM_RAM = 1024
+
+import sys
+import subprocess
+#import time
+import os.path
+import json
+
+try:
+ import lockfile
+except ImportError:
+ print "Python module lockfile is not installed. Falling back to using flock(), which will fail on Windows."
+
+try:
+ import vagrant
+except ImportError:
+ print "failed=True msg='python-vagrant required for this module'"
+ sys.exit(1)
+
+class VagrantWrapper(object):
+
+ def __init__(self):
+ '''
+ Wrapper around the python-vagrant module for use with ansible.
+ Note that Vagrant itself is non-thread safe, as is the python-vagrant lib, so we need to lock on basically all operations ...
+ '''
+ # Get a lock
+ self.lock = None
+
+ try:
+ self.lock = lockfile.FileLock(VAGRANT_LOCKFILE)
+ self.lock.acquire()
+ except:
+ # fall back to using flock instead ...
+ try:
+ import fcntl
+ self.lock = open(VAGRANT_LOCKFILE, 'w')
+ fcntl.flock(self.lock, fcntl.LOCK_EX)
+ except:
+ print "failed=True msg='Could not get a lock for using vagrant. Install python module \"lockfile\" to use vagrant on non-POSIX filesytems.'"
+ sys.exit(1)
+
+ # Initialize vagrant and state files
+ self.vg = vagrant.Vagrant()
+
+ # operation will create a default data structure if none present
+ self._deserialize()
+ self._serialize()
+
+ def __del__(self):
+ '''Clean up file locks'''
+ try:
+ self.lock.release()
+ except:
+ os.close(self.lock)
+ os.unlink(self.lock)
+
+ def prepare_box(self, box_name, box_path):
+ '''Given a specified name and URL, import a Vagrant "box" for use.'''
+ changed = False
+ if box_name == None:
+ raise Exception("You must specify a box_name with a box_path for vagrant.")
+ boxes = self.vg.box_list()
+ if not box_name in boxes:
+ self.vg.box_add(box_name, box_path)
+ changed = True
+
+ return changed
+
+ def up(self, box_name, vm_name=None, count=1, box_path=None, ports=[]):
+ '''Fire up a given VM and name it, using vagrant's multi-VM mode.'''
+
+ changed = False
+ if vm_name == None:
+ vm_name = DEFAULT_VM_NAME
+
+ if box_name == None:
+ raise Exception("You must specify a box name for Vagrant.")
+ if box_path != None:
+ changed = self.prepare_box(box_name, box_path)
+
+ for icount in range(int(count)):
+
+ self._deserialize()
+
+ this_instance_dict = self._get_instance(vm_name,icount)
+ if not this_instance_dict.has_key('box_name'):
+ this_instance_dict['box_name'] = box_name
+
+ this_instance_dict['forward_ports'] = ports
+
+ # Save our changes and run
+ inst_array = self._instances()[vm_name]
+ inst_array[icount] = this_instance_dict
+
+ self._serialize()
+
+ # See if we need to fire it up ...
+ vgn = this_instance_dict['vagrant_name']
+ status = self.vg.status(vgn)
+ if status != 'running':
+ self.vg.up(False, this_instance_dict['vagrant_name'])
+ changed =True
+
+ ansible_instance_array = self._build_instance_array_for_ansible(vm_name)
+ return (changed, ansible_instance_array)
+
+ def status(self, vm_name = None, index = -1):
+ '''Return the run status of the VM instance. If no instance N is given, returns first instance.'''
+ vm_names = []
+ if vm_name != None: vm_names = [vm_name]
+ else:
+ vm_names = self._instances().keys()
+
+ statuses = {}
+ for vmn in vm_names:
+ stat_array = []
+ instance_array = self.vg_data['instances'][vmn]
+ if index >= 0:
+ instance_array = [ self._get_instance(vmn,index) ]
+ for inst in instance_array:
+ vgn = inst['vagrant_name']
+ stat_array.append(self.vg.status(vgn))
+ statuses[vmn] = stat_array
+
+ return (False, statuses)
+
+ def config(self, vm_name, index = -1):
+ '''Return info on SSH for the running instance.'''
+ vm_names = []
+ if vm_name != None: vm_names = [vm_name]
+ else:
+ vm_names = self._instances().keys()
+
+ configs = {}
+ for vmn in vm_names:
+ conf_array = []
+ instance_array = self.vg_data['instances'][vmn]
+ if index >= 0:
+ instance_array = [ self._get_instance(vmn,index) ]
+ for inst in instance_array:
+ cnf = self.vg.conf(None, inst['vagrant_name'])
+ conf_array.append(cnf)
+ configs[vmn] = conf_array
+
+ return (False, configs)
+
+ def halt(self, vm_name = None, index = -1):
+ '''Shuts down a vm_name or all VMs.'''
+
+ changed = False
+ vm_names = []
+ if vm_name != None: vm_names = [vm_name]
+ else:
+ vm_names = self._instances().keys()
+
+ statuses = {}
+ for vmn in vm_names:
+ stat_array = []
+ instance_array = self.vg_data['instances'][vmn]
+ if index >= 0:
+ instance_array = [ self.vg_data['instances'][vmn][index] ]
+ for inst in instance_array:
+ vgn = inst['vagrant_name']
+ if self.vg.status(vgn) == 'running':
+ self.vg.halt(vgn)
+ changed = True
+ stat_array.append(self.vg.status(vgn))
+ statuses[vmn] = stat_array
+
+ return (changed, statuses)
+
+ def destroy(self, vm_name=None, index = -1):
+ '''Halt and remove data for a VM, or all VMs.'''
+
+ self._deserialize()
+
+ (changed, stats) = self.halt(vm_name, index)
+
+ self.vg.destroy(vm_name)
+ if vm_name != None:
+ self._instances().pop(vm_name)
+ else:
+ self.vg_data['instances'] = {}
+
+ self._serialize()
+
+ changed = True
+
+ return changed
+
+ def clear(self, vm_name=None):
+ '''Halt and remove data for a VM, or all VMs. Also clear all state data.'''
+
+ changed = self.vg.destroy(vm_name)
+
+ if os.path.isfile(VAGRANT_FILE):
+ os.remove(VAGRANT_FILE)
+ if os.path.isfile(VAGRANT_DICT_FILE):
+ os.remove(VAGRANT_DICT_FILE)
+
+ return changed
+#
+# Helper Methods
+#
+ def _instances(self):
+ return self.vg_data['instances']
+
+ def _get_instance(self, vm_name, index):
+
+ instances = self._instances()
+
+ inst_array = []
+ if instances.has_key(vm_name):
+ inst_array = instances[vm_name]
+
+ if len(inst_array) > index:
+ return inst_array[index]
+
+ #
+ # otherwise create one afresh
+ #
+ this_instance_N = self.vg_data['num_inst']+1
+ name_for_vagrant = "%s%d" % (vm_name.replace("-","_"),index)
+
+ instance_dict = dict(
+ n = index,
+ N = this_instance_N,
+ name = vm_name,
+ vagrant_name = name_for_vagrant,
+ internal_ip = VAGRANT_INT_IP % (255-this_instance_N),
+ forward_ports = [],
+ ram = DEFAULT_VM_RAM,
+ )
+
+ # Save this ...
+ self.vg_data['num_inst'] = this_instance_N
+ inst_array.append(instance_dict)
+
+ self._instances()[vm_name] = inst_array
+
+ return instance_dict
+
+ #
+ # Serialize/Deserialize current state to a JSON representation, and
+ # a file format for Vagrant.
+ #
+ # This is where we need to deal with file locking, since multiple threads/procs
+ # may be trying to operate on the same files
+ #
+
+ def _serialize(self):
+ '''Save state to a JSON file, and write the Vagrantfile based on this.'''
+ self._save_state()
+ self._write_vagrantfile()
+
+ def _deserialize(self):
+ '''Load in data from the JSON state file.'''
+ self._load_state()
+
+
+ #
+ # Manage a JSON representation of vagrantfile for statefulness across invocations.
+ #
+ def _load_state(self):
+ self.vg_data = dict(num_inst=0, instances = {})
+ if os.path.isfile(VAGRANT_DICT_FILE):
+ json_file=open(VAGRANT_DICT_FILE)
+ self.vg_data = json.load(json_file)
+ json_file.close()
+
+# def _state_as_string(self):
+# from StringIO import StringIO
+# io = StringIO()
+# json.dump(self.vg_data, io)
+# return io.getvalue()
+
+ def _save_state(self):
+ json_file=open(VAGRANT_DICT_FILE, 'w')
+ json.dump(self.vg_data,json_file, sort_keys=True, indent=4, separators=(',', ': '))
+ json_file.close()
+
+ #
+ # Translate the state dictionary into the Vagrantfile
+ #
+ def _write_vagrantfile(self):
+
+ vfile = open(VAGRANT_FILE, 'w')
+ vfile.write(VAGRANT_FILE_HEAD)
+
+ instances = self._instances()
+ for vm_name in instances.keys():
+ inst_array = instances[vm_name]
+ for index in range(len(inst_array)):
+ instance_dict = inst_array[index]
+ name = instance_dict['vagrant_name']
+ ip = instance_dict['internal_ip']
+ box_name = instance_dict['box_name']
+ vfile.write(VAGRANT_FILE_VM_STANZA_HEAD %
+ (name, name, name, ip, name, box_name) )
+ if instance_dict.has_key('ram'):
+ vfile.write(VAGRANT_FILE_MEMORY_LINE % (name, instance_dict['ram']) )
+ vfile.write(VAGRANT_FILE_HOSTNAME_LINE % (name, name.replace('_','-')) )
+ if instance_dict.has_key('forward_ports'):
+ for port in instance_dict['forward_ports']:
+ port = int(port)
+ host_port = port
+ if port < 1024:
+ host_port = port + 10000
+ vfile.write(VAGRANT_FILE_PORT_FORWARD_LINE % (name, port, host_port) )
+ vfile.write(VAGRANT_FILE_VM_STANZA_TAIL)
+
+ vfile.write(VAGRANT_FILE_TAIL)
+ vfile.close()
+
+ #
+ # To be returned to ansible with info about instances
+ #
+ def _build_instance_array_for_ansible(self, vmname=None):
+
+ vm_names = []
+ instances = self._instances()
+ if vmname != None:
+ vm_names = [vmname]
+ else:
+ vm_names = instances.keys()
+
+ ans_instances = []
+ for vm_name in vm_names:
+ for inst in instances[vm_name]:
+ vagrant_name = inst['vagrant_name']
+ cnf = self.vg.conf(None,vagrant_name)
+ vg_data = instances[vm_name]
+ if cnf != None:
+ instance_dict = dict(
+ name = vm_name,
+ vagrant_name = vagrant_name,
+ id = cnf['Host'],
+ public_ip = cnf['HostName'],
+ internal_ip = inst['internal_ip'],
+ public_dns_name = cnf['HostName'],
+ port = cnf['Port'],
+ username = cnf['User'],
+ key = cnf['IdentityFile'],
+ status = self.vg.status(vagrant_name)
+ )
+ ans_instances.append(instance_dict)
+
+ return ans_instances
+
+#--------
+# MAIN
+#--------
+def main():
+
+ module = AnsibleModule(
+ argument_spec = dict(
+ state=dict(),
+ cmd=dict(required=False, aliases = ['command']),
+ box_name=dict(required=False, aliases = ['image']),
+ box_path=dict(),
+ vm_name=dict(),
+ forward_ports=dict(),
+ count = dict(default='1'),
+ )
+ )
+
+ state = module.params.get('state')
+ cmd = module.params.get('cmd')
+ box_name = module.params.get('box_name')
+ box_path = module.params.get('box_path')
+ vm_name = module.params.get('vm_name')
+ forward_ports = module.params.get('forward_ports')
+
+ if forward_ports != None:
+ forward_ports=forward_ports.split(',')
+ if forward_ports == None:
+ forward_ports=[]
+
+ count = module.params.get('count')
+
+ # Initialize vagrant
+ vgw = VagrantWrapper()
+
+ #
+ # Check if we are being invoked under an idempotency idiom of "state=present" or "state=absent"
+ #
+ try:
+ if state != None:
+
+ if state != 'present' and state != 'absent':
+ module.fail_json(msg = "State must be \"present\" or \"absent\" in vagrant module.")
+
+ if state == 'present':
+
+ changd, insts = vgw.up(box_name, vm_name, count, box_path, forward_ports)
+ module.exit_json(changed = changd, instances = insts)
+
+ if state == 'absent':
+ changd = vgw.halt(vm_name)
+ module.exit_json(changed = changd, status = vgw.status(vm_name))
+
+
+ #
+ # Main command tree for old style invocation
+ #
+
+ else:
+
+ if cmd == 'up':
+
+ if count == None:
+ count = 1
+ (changd, insts) = vgw.up(box_name, vm_name, count, box_path, forward_ports)
+ module.exit_json(changed = changd, instances = insts)
+
+ elif cmd == 'status':
+
+# if vm_name == None:
+# module.fail_json(msg = "Error: you must specify a vm_name when calling status." )
+
+ (changd, result) = vgw.status(vm_name)
+ module.exit_json(changed = changd, status = result)
+
+ elif cmd == "config" or cmd == "conf":
+
+ if vm_name == None:
+ module.fail_json(msg = "Error: you must specify a vm_name when calling config." )
+ (changd, cnf) = vgw.config(vm_name)
+ module.exit_json(changed = changd, config = cnf)
+
+ elif cmd == 'ssh':
+
+ if vm_name == None:
+ module.fail_json(msg = "Error: you must specify a vm_name when calling ssh." )
+
+ (changd, cnf) = vgw.config(vm_name)
+ sshcmd = "ssh -i %s -p %s %s@%s" % (cnf["IdentityFile"], cnf["Port"], cnf["User"], cnf["HostName"])
+ sshmsg = "Execute the command \"vagrant ssh %s\"" % (vm_name)
+ module.exit_json(changed = changd, msg = sshmsg, SshCommand = sshcmd)
+
+ elif cmd == 'halt':
+
+ (changd, stats) = vgw.halt(vm_name)
+ module.exit_json(changed = changd, status = stats)
+
+ elif cmd == 'destroy':
+
+ changd = vgw.destroy(vm_name)
+ module.exit_json(changed = changd, status = vgw.status(vm_name))
+
+ elif cmd == 'clear':
+
+ changd = vgw.clear()
+ module.exit_json(changed = changd)
+
+ else:
+
+ module.fail_json(msg = "Unknown vagrant subcommand: \"%s\"." % (cmd))
+
+ except subprocess.CalledProcessError as errer:
+ module.fail_json(msg = "Vagrant command failed: %s." % (errer))
+ except Exception as errer:
+ module.fail_json(msg = errer.__str__())
+ module.exit_json(status = "success")
+
+
+
+
+# this is magic, see lib/ansible/module_common.py
+#<>
+
+main()