mirror of
https://github.com/kevingruesser/bootstrap-vz.git
synced 2025-08-24 07:26:29 +00:00

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.
232 lines
8.6 KiB
Python
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']
|