mirror of https://github.com/ansible/ansible.git
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.reviewable/pr18780/r1
parent
7835ab09b8
commit
62d95a58cb
@ -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…
Reference in New Issue