bootstrap-vz/bootstrapvz/plugins/vagrant/tasks.py
Anders Ingemann c880a6849a Remove image section in manifest
All provider specific settings have been moved to the
provider section. The image name itself is now located
at the top level and called "name". It is required for all providers.
2015-12-13 19:41:18 +01:00

232 lines
8.6 KiB
Python

from bootstrapvz.base import Task
from bootstrapvz.common import phases
from bootstrapvz.common.tasks import workspace
import os
import shutil
assets = os.path.normpath(os.path.join(os.path.dirname(__file__), 'assets'))
class CheckBoxPath(Task):
description = 'Checking if the vagrant box file already exists'
phase = phases.preparation
@classmethod
def run(cls, info):
box_basename = info.manifest.name.format(**info.manifest_vars)
box_name = box_basename + '.box'
box_path = os.path.join(info.manifest.bootstrapper['workspace'], box_name)
if os.path.exists(box_path):
from bootstrapvz.common.exceptions import TaskError
msg = 'The vagrant box `{name}\' already exists at `{path}\''.format(name=box_name, path=box_path)
raise TaskError(msg)
info._vagrant['box_name'] = box_name
info._vagrant['box_path'] = box_path
class CreateVagrantBoxDir(Task):
description = 'Creating directory for the vagrant box'
phase = phases.preparation
predecessors = [workspace.CreateWorkspace, CheckBoxPath]
@classmethod
def run(cls, info):
info._vagrant['folder'] = os.path.join(info.workspace, 'vagrant')
os.mkdir(info._vagrant['folder'])
class AddPackages(Task):
description = 'Add packages that vagrant depends on'
phase = phases.preparation
@classmethod
def run(cls, info):
info.packages.add('openssh-server')
info.packages.add('sudo')
info.packages.add('nfs-client')
class CreateVagrantUser(Task):
description = 'Creating the vagrant user'
phase = phases.system_modification
@classmethod
def run(cls, info):
from bootstrapvz.common.tools import log_check_call
log_check_call(['chroot', info.root,
'useradd',
'--create-home', '--shell', '/bin/bash',
'vagrant'])
class PasswordlessSudo(Task):
description = 'Allowing the vagrant user to use sudo without a password'
phase = phases.system_modification
@classmethod
def run(cls, info):
sudo_vagrant_path = os.path.join(info.root, 'etc/sudoers.d/vagrant')
with open(sudo_vagrant_path, 'w') as sudo_vagrant:
sudo_vagrant.write('vagrant ALL=(ALL) NOPASSWD:ALL')
import stat
ug_read_only = (stat.S_IRUSR | stat.S_IRGRP)
os.chmod(sudo_vagrant_path, ug_read_only)
class AddInsecurePublicKey(Task):
description = 'Adding vagrant insecure public key'
phase = phases.system_modification
predecessors = [CreateVagrantUser]
@classmethod
def run(cls, info):
ssh_dir = os.path.join(info.root, 'home/vagrant/.ssh')
os.mkdir(ssh_dir)
authorized_keys_source_path = os.path.join(assets, 'authorized_keys')
with open(authorized_keys_source_path, 'r') as authorized_keys_source:
insecure_public_key = authorized_keys_source.read()
authorized_keys_path = os.path.join(ssh_dir, 'authorized_keys')
with open(authorized_keys_path, 'a') as authorized_keys:
authorized_keys.write(insecure_public_key)
import stat
os.chmod(ssh_dir, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
os.chmod(authorized_keys_path, stat.S_IRUSR | stat.S_IWUSR)
# We can't do this directly with python, since getpwnam gets its info from the host
from bootstrapvz.common.tools import log_check_call
log_check_call(['chroot', info.root,
'chown', 'vagrant:vagrant',
'/home/vagrant/.ssh', '/home/vagrant/.ssh/authorized_keys'])
class SetRootPassword(Task):
description = 'Setting the root password to `vagrant\''
phase = phases.system_modification
@classmethod
def run(cls, info):
from bootstrapvz.common.tools import log_check_call
log_check_call(['chroot', info.root, 'chpasswd'], 'root:vagrant')
class PackageBox(Task):
description = 'Packaging the volume as a vagrant box'
phase = phases.image_registration
@classmethod
def run(cls, info):
vagrantfile_source = os.path.join(assets, 'Vagrantfile')
vagrantfile = os.path.join(info._vagrant['folder'], 'Vagrantfile')
shutil.copy(vagrantfile_source, vagrantfile)
import random
mac_address = '080027{mac:06X}'.format(mac=random.randrange(16 ** 6))
from bootstrapvz.common.tools import sed_i
sed_i(vagrantfile, '\\[MAC_ADDRESS\\]', mac_address)
metadata_source = os.path.join(assets, 'metadata.json')
metadata = os.path.join(info._vagrant['folder'], 'metadata.json')
shutil.copy(metadata_source, metadata)
from bootstrapvz.common.tools import log_check_call
disk_name = 'box-disk1.' + info.volume.extension
disk_link = os.path.join(info._vagrant['folder'], disk_name)
log_check_call(['ln', '-s', info.volume.image_path, disk_link])
ovf_path = os.path.join(info._vagrant['folder'], 'box.ovf')
cls.write_ovf(info, ovf_path, mac_address, disk_name)
box_files = os.listdir(info._vagrant['folder'])
log_check_call(['tar', '--create', '--gzip', '--dereference',
'--file', info._vagrant['box_path'],
'--directory', info._vagrant['folder']] + box_files
)
import logging
logging.getLogger(__name__).info('The vagrant box has been placed at ' + info._vagrant['box_path'])
@classmethod
def write_ovf(cls, info, destination, mac_address, disk_name):
namespaces = {'ovf': 'http://schemas.dmtf.org/ovf/envelope/1',
'rasd': 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData',
'vssd': 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'vbox': 'http://www.virtualbox.org/ovf/machine',
}
def attr(element, name, value=None):
for prefix, ns in namespaces.iteritems():
name = name.replace(prefix + ':', '{' + ns + '}')
if value is None:
return element.attrib[name]
else:
element.attrib[name] = str(value)
template_path = os.path.join(assets, 'box.ovf')
import xml.etree.ElementTree as ET
template = ET.parse(template_path)
root = template.getroot()
[disk_ref] = root.findall('./ovf:References/ovf:File', namespaces)
attr(disk_ref, 'ovf:href', disk_name)
# List of OVF disk format URIs
# Snatched from VBox source (src/VBox/Main/src-server/ApplianceImpl.cpp:47)
# ISOURI = "http://www.ecma-international.org/publications/standards/Ecma-119.htm"
# VMDKStreamURI = "http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized"
# VMDKSparseURI = "http://www.vmware.com/specifications/vmdk.html#sparse"
# VMDKCompressedURI = "http://www.vmware.com/specifications/vmdk.html#compressed"
# VMDKCompressedURI2 = "http://www.vmware.com/interfaces/specifications/vmdk.html#compressed"
# VHDURI = "http://go.microsoft.com/fwlink/?LinkId=137171"
volume_uuid = info.volume.get_uuid()
[disk] = root.findall('./ovf:DiskSection/ovf:Disk', namespaces)
attr(disk, 'ovf:capacity', info.volume.size.bytes.get_qty_in('B'))
attr(disk, 'ovf:format', info.volume.ovf_uri)
attr(disk, 'vbox:uuid', volume_uuid)
[system] = root.findall('./ovf:VirtualSystem', namespaces)
attr(system, 'ovf:id', info._vagrant['box_name'])
# Set the operating system
[os_section] = system.findall('./ovf:OperatingSystemSection', namespaces)
os_info = {'i386': {'id': 96, 'name': 'Debian'},
'amd64': {'id': 96, 'name': 'Debian_64'}
}.get(info.manifest.system['architecture'])
attr(os_section, 'ovf:id', os_info['id'])
[os_desc] = os_section.findall('./ovf:Description', namespaces)
os_desc.text = os_info['name']
[os_type] = os_section.findall('./vbox:OSType', namespaces)
os_type.text = os_info['name']
[sysid] = system.findall('./ovf:VirtualHardwareSection/ovf:System/'
'vssd:VirtualSystemIdentifier', namespaces)
sysid.text = info._vagrant['box_name']
[machine] = system.findall('./vbox:Machine', namespaces)
import uuid
attr(machine, 'ovf:uuid', uuid.uuid4())
attr(machine, 'ovf:name', info._vagrant['box_name'])
from datetime import datetime
attr(machine, 'ovf:lastStateChange', datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ'))
[nic] = machine.findall('./ovf:Hardware/ovf:Network/ovf:Adapter', namespaces)
attr(machine, 'ovf:MACAddress', mac_address)
[device_img] = machine.findall('./ovf:StorageControllers'
'/ovf:StorageController[@name="SATA Controller"]'
'/ovf:AttachedDevice/ovf:Image', namespaces)
attr(device_img, 'uuid', '{' + str(volume_uuid) + '}')
template.write(destination, xml_declaration=True) # , default_namespace=namespaces['ovf']
class RemoveVagrantBoxDir(Task):
description = 'Removing the vagrant box directory'
phase = phases.cleaning
successors = [workspace.DeleteWorkspace]
@classmethod
def run(cls, info):
shutil.rmtree(info._vagrant['folder'])
del info._vagrant['folder']