Commits

Thomas Pelletier  committed 14b45d9

fixes #2 : add central provisions

  • Participants
  • Parent commits 1062269

Comments (0)

Files changed (5)

 
 This will show the list of the commands.
 
-# For non-BASH shells
+## For non-BASH shells
 
 I use a trick which only works on bash to guess the path of`virtual-manager`
 directory. If you are using another shell (such as ZSH), you have to set the
 Each created box has its own section, with only this IP inside (for now). The
 core section describes the behavior of the script. It's structure is very
 straightforward.
+
+## Provisions
+
+Revision 7 brought the support of provisions. It allows you to automatically
+provision your VM when you load it. Here is an example shell session to deal
+with provisioning using virtual-manager:
+
+    $ vm
+    Use one of the following commands:
+        * add name base
+        * add_provision name type source provisioner
+        * bind_provision name vm_name
+        * cd name
+        * halt name
+        * list
+        * provisions_list
+        * reload name
+        * remove name
+        * remove_provision name
+        * ssh name
+        * unbind_provision vm_name
+        * up name
+
+    $ vm add_provision baseprov link /Users/thomas/code/base-provision puppet
+    New provision registered.
+
+    $ vm provisions_list
+    Available provisions:
+        * baseprov
+
+    $ vm add foo lucid32
+    Password for sudo?
+    [...]
+    Virtual machine successfully created
+
+    $ vm bind_provision baseprov foo
+    Provision baseprov bound to foo.
+
+    $ vm reload foo
+    [default] Attempting graceful shutdown of linux.
+    [...]
+
+    $ vm unbind_provision foo
+    Provision unbound from foo.
+
+    $ vm remove foo
+    [default] Forcing shutdown of VM...
+    [...]
+    Virtual machine removed.
+
+    $ vm remove_provision baseprov
+    Provision removed.
+
 from os import path
-from ConfigParser import SafeConfigParser
-
+from ConfigParser import SafeConfigParser, NoOptionError
 
 class ConfigItem(SafeConfigParser):
     
         setattr(self, name, ci)
 
 
+def path_for(name):
+    return path.join(path.dirname(path.abspath(__file__)), name)
+
 
 config = Config()
 
 config.register('core', initial_sections=['core'], defaults={
-    'vms_path'        : "~/.vm.d/machines",
-    'base_ip'         : "33.33.33.",
-    'vagrant_template': path.join(path.dirname(path.abspath(__file__)),\
-                                  'Vagrantfile'),
+    'vms_path'              : "~/.vm.d/machines",
+    'base_ip'               : "33.33.33.",
+    'vagrant_template'      : path_for('templates/Vagrantfile'),
+    'provisions_path'       : "~/.vm.d/provisions",
+    'provisioners_templates': path_for('templates/provisioners/')
 })
 config.register('provisions')
 config.register('vms', defaults={

File provisions.py

+from os import path
+from cli import cli
+from subprocess import call
+from config import config, NoOptionError
+from vms import vm_path, list_virtual_machines
+from utils import nostdout, normalize, render_template
+
+
+
+class SourceTypeBase(object):
+
+    def __init__(self, source_path, provisioner):
+        self.source_path = source_path
+        self.provisioner = provisioner
+
+    def initialize(self, vm_path):
+        # This method will be called when the user executes the `bind` action.
+        raise NotImplementedError()
+
+    def clean(self, vm_path):
+        # This method will be called when the user executes the `unbind`
+        # action.
+        raise NotImplementedError()
+
+    def update(self, vm_path):
+        # This method will be called when the user executes the `update`
+        # action.
+        raise NotImplementedError()
+
+    def destination_path(self, vm_path):
+        return path.join(vm_path, self.provisioner)
+
+class LinkType(SourceTypeBase):
+
+    def initialize(self, vm_path):
+        destination_path = self.destination_path(vm_path)
+        call("ln -s %s %s" % (self.source_path, destination_path), shell=True)
+
+    def clean(self, vm_path):
+        destination_path = self.destination_path(vm_path)
+        call("rm %s" % destination_path, shell=True)
+
+    @staticmethod
+    def update(self, vm_path):
+        pass
+
+class CopyType(SourceTypeBase):
+
+    def clean(self, vm_path):
+        destination_path = self.destination_path(vm_path)
+        call("rm -R %s" % destination_path, shell=True)
+
+    @staticmethod
+    def update(self, vm_path):
+        destination_path = self.destination_path(vm_path)
+        call("cp -R %s %s" % (self.source_path, destination_path), shell=True)
+
+setattr(CopyType, 'initialize', CopyType.update)
+
+
+class Provision(object):
+
+    props = ['name', 'type', 'source', 'provisioner']
+    source_classes = {
+        'link': LinkType,
+        'copy': CopyType,
+    }
+
+    def __init__(self, **kwargs):
+        for prop in self.props:
+            value = kwargs.get(prop, None)
+            setattr(self, prop, value)
+
+        self.src = self.source_classes[self.type](self.source, self.provisioner)
+            
+    def save(self, config_instance):
+        config_instance.add_section(self.name)
+        for prop in self.props:
+            if prop == 'name': continue
+            config_instance.set(self.name, prop, getattr(self, prop))
+        config_instance.write()
+
+    @classmethod
+    def load(cls, name, config_instance):
+        values = [name] + [config_instance.get(name, x)
+                            for x in cls.props
+                            if not x == 'name']
+        return cls(**dict(zip(cls.props, values)))
+
+    @staticmethod
+    def remove(name, config_instance):
+        config_instance.remove_section(name)
+        config_instance.write()
+
+
+@cli.register()
+def provisions_list():
+    """List the registered provisions."""
+    sections = config.provisions.sections()
+
+    print "Available provisions:"
+    for provision in sections:
+        print "\t* %s" % provision
+    return sections
+
+@cli.register()
+def add_provision(name, type, source, provisioner):
+    name = normalize(name)
+    with nostdout():
+        provisions = provisions_list()
+    if name in provisions:
+        print "A provision with the same name already exists."
+        return -1
+    kwargs = {
+        'name':        name,
+        'type':        type,
+        'source':      source,
+        'provisioner': provisioner
+    }
+    new_prov = Provision(**kwargs)
+    new_prov.save(config.provisions)
+
+    print "New provision registered."
+
+@cli.register()
+def remove_provision(name):
+    with nostdout():
+        provisions = provisions_list()
+    if not name in provisions:
+        print "No such registered provision."
+        return -1
+    Provision.remove(name, config.provisions)
+    print "Provision removed."
+    return 0
+
+@cli.register()
+def bind_provision(name, vm_name):
+    # Check that the machine exists.
+    with nostdout():
+        vms = list_virtual_machines()
+    if not vm_name in vms:
+        print "VM %s does not exist." % name
+        list_virtual_machines()
+        return -1
+
+    with nostdout():
+        provisions = provisions_list()
+    if not name in provisions:
+        print "No such registered provision."
+        return -1
+
+    provision = Provision.load(name, config.provisions)
+    provisioners_path = config.core.get("core", "provisioners_templates")
+
+    vmpath = vm_path(vm_name)
+
+    template_path = path.join(provisioners_path, provision.provisioner)
+    snippet = render_template(template_path)
+
+    vagrant_path = path.join(vmpath, "Vagrantfile")
+    vagrantfile = open(vagrant_path, 'r')
+    vf_content = vagrantfile.read()
+    vagrantfile.close()
+
+    vf_new_content = vf_content.replace('# VM:PROVISIONER', snippet)
+
+    vagrantfile = open(vagrant_path, 'w')
+    vagrantfile.write(vf_new_content)
+    vagrantfile.close()
+
+    provision.src.initialize(vmpath)
+
+    config.vms.set(vm_name, 'provision', name)
+    config.vms.write()
+
+    print "Provision %s bound to %s." % (name, vm_name)
+
+@cli.register()
+def unbind_provision(vm_name):
+    with nostdout():
+        vms = list_virtual_machines()
+    if not vm_name in vms:
+        print "VM %s does not exist." % vm_name
+        list_virtual_machines()
+        return -1
+
+    try:
+        provision = config.vms.get(vm_name, 'provision')
+    except NoOptionError:
+        print "No provision is bound to this VM."
+        return -1
+
+    provision = Provision.load(provision, config.provisions)
+    config.vms.remove_option(vm_name, 'provision')
+    config.vms.write()
+
+    vmpath = vm_path(vm_name)
+
+    provision.src.clean(vmpath)
+
+    vf_buffer = ""
+
+    vagrant_path = path.join(vmpath, "Vagrantfile")
+    vagrantfile = open(vagrant_path, 'r')
+    skip = False
+    for line in vagrantfile.readlines():
+        if "VM:PROVISIONER:START" in line:
+            skip = True
+            vf_buffer = vf_buffer + "# VM:PROVISIONER\n"
+        elif "VM:PROVISIONER:STOP" in line:
+            skip = False
+        elif not skip:
+            vf_buffer = vf_buffer + line + '\n'
+    vagrantfile.close()
+
+    vagrantfile = open(vagrant_path, 'w')
+    vagrantfile.write(vf_buffer)
+    vagrantfile.close()
+
+    print "Provision unbound from %s." % vm_name
 import sys
+from os import remove, path
 from subprocess import call
-from os import remove
+from string import Template
 
 
 class nostdout:
     call('sudo cp /tmp/hosts_final /etc/hosts', shell=True)
     call('sudo touch /etc/hosts', shell=True)
     remove('/tmp/hosts_final')
+
+
+def normalize(string):
+    return string.lower().replace(' ', "_")
+
+
+def render_template(template_path, data={}, output=None):
+    descr = open(path.expanduser(template_path), 'r')
+    template = Template(descr.read())
+    descr.close()
+
+    content = template.substitute(data)
+
+    if not output == None:
+        descr = open(output, 'w')
+        descr.write(content)
+        descr.close()
+
+    return content
 from config import config
-from string import Template
 from subprocess import call
 from os import path, makedirs
 from cli import cli
-from utils import nostdout, update_hostfile
+from utils import nostdout, update_hostfile, normalize, render_template
 
 
 
         return 0
 
 # Register built-in Vagrant commands to the CLI.
-for name in ["up", "halt", "ssh"]:
+for name in ["up", "halt", "ssh", "reload"]:
     register_vagrant_wrap(cli, name)
 
 
     with nostdout():
         vms = list_virtual_machines()
     if not name in vms:
-        print "VM %s does not exist."
+        print "VM %s does not exist." % name
         list_virtual_machines()
         return -1
 
 def add(name, base):
     """Create and register a new virtual machine."""
     # Normalize the name of the VM
-    name = name.lower().replace(' ', "_")
+    name = normalize(name)
 
     # Check for doubles
     with nostdout():
 
     # Create the directory.
     vagrant_dir = vm_path(name)
-    makedirs(vagrant_dir)
+    makedirs(path.join(vagrant_dir, 'link'))
 
     # Grab the default vagrant file.
     vagrant_template_path = config.core.get("core", "vagrant_template")
-    vagrant_template_descr = open(path.expanduser(vagrant_template_path), 'r')
-    vagrant_template = Template(vagrant_template_descr.read())
-    vagrant_template_descr.close()
-
-    # Write the new one.
-    vagrant_content = vagrant_template.substitute(mapping)
     vagrant_final_path = path.join(vagrant_dir, "Vagrantfile")
-    vagrant_descr = open(vagrant_final_path, 'w')
-    vagrant_descr.write(vagrant_content)
-    vagrant_descr.close()
+    render_template(vagrant_template_path, mapping, vagrant_final_path)
 
     # Create the configuration file.
     config.vms.add_section(name)