People using vagrant are driving things top-down through the new provisioner, so the idea of using ansible to fire against

vagrant which then calls ansible seems weird to me.  This module can still be maintained outside of core.
pull/2753/head
Michael DeHaan 11 years ago
parent 13e90c396c
commit 574061fe47

@ -1,574 +0,0 @@
#!/usr/bin/python
# 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 <http://www.gnu.org/licenses/>.
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'.
version_added: "1.1"
options:
state:
description:
- Should the VMs be "present" or "absent."
cmd:
description:
- vagrant subcommand to execute.
required: false
default: null
aliases: ['command']
choices: [ "up", "status", "config", "ssh", "halt", "destroy", "clear" ]
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", "python-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 'box_name' in this_instance_dict:
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 vm_name in instances:
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 'ram' in instance_dict:
vfile.write(VAGRANT_FILE_MEMORY_LINE % (name, instance_dict['ram']))
vfile.write(VAGRANT_FILE_HOSTNAME_LINE % (name, name.replace('_','-')))
if 'forward_ports' in instance_dict:
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: a vm_name is required when calling config.")
(changd, cnf) = vgw.config(vm_name)
module.exit_json(changed = changd, config = cnf)
elif cmd == 'ssh':
# this doesn't really seem to belong here, should just manage the VM with ansible -- MPD
if vm_name == None:
module.fail_json(msg = "Error: a vm_name is required 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
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()
Loading…
Cancel
Save