New plugin: vagrant

Example manifest file also added
This commit is contained in:
Anders Ingemann 2013-12-29 21:18:33 +01:00
parent 15661ae680
commit 31f3a71464
7 changed files with 471 additions and 0 deletions

View file

@ -0,0 +1,43 @@
{
"provider": "virtualbox",
"bootstrapper": {
"workspace": "/target",
"guest_additions": "/root/images/VBoxGuestAdditions.iso"
},
"image": {
"name": "debian-{system.release}-{system.architecture}-{%y}{%m}{%d}",
"description": "Debian {system.release} {system.architecture}"
},
"system": {
"release": "wheezy",
"architecture": "amd64",
"timezone": "UTC",
"locale": "en_US",
"charmap": "UTF-8"
},
"volume": {
"backing": "vmdk",
"partitions": {
"type": "mbr",
"boot": {
"size": 64,
"filesystem": "ext2"
},
"root": {
"size": 1855,
"filesystem": "ext4"
},
"swap": {"size": 128}
}
},
"plugins": {
"admin_user": {
"username": "vagrant"
},
"root_password": {
"password": "vagrant"
},
"vagrant": {
}
}
}

View file

@ -0,0 +1,33 @@
import tasks
def validate_manifest(data, schema_validate):
from os import path
schema_path = path.normpath(path.join(path.dirname(__file__), 'manifest-schema.json'))
schema_validate(data, schema_path)
def resolve_tasks(tasklist, manifest):
from common.tasks import security
from common.tasks import loopback
tasklist.remove(security.DisableSSHPasswordAuthentication,
loopback.MoveImage,
)
from common.tasks import volume
tasklist.add(tasks.CreateVagrantBoxDir,
tasks.AddPackages,
tasks.AddInsecurePublicKey,
tasks.PackageBox,
tasks.RemoveVagrantBoxDir,
volume.Delete,
)
def resolve_rollback_tasks(tasklist, tasks_completed, manifest):
completed = [type(task) for task in tasks_completed]
def counter_task(task, counter):
if task in completed and counter not in completed:
tasklist.add(counter)
counter_task(tasks.CreateVagrantBoxDir, tasks.RemoveVagrantBoxDir)

View file

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key

View file

@ -0,0 +1,197 @@
<?xml version="1.0"?>
<Envelope ovf:version="1.0" xml:lang="en-US" xmlns="http://schemas.dmtf.org/ovf/envelope/1" xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1" xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData" xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:vbox="http://www.virtualbox.org/ovf/machine">
<References>
<File ovf:href="[disk_name]" ovf:id="file1"/>
</References>
<DiskSection>
<Info>List of the virtual disks used in the package</Info>
<Disk ovf:capacity="[DISK_SIZE]" ovf:diskId="vmdisk1" ovf:fileRef="file1" ovf:format="[DISK_FORMAT]" vbox:uuid="[DISK_UUID]"/>
</DiskSection>
<NetworkSection>
<Info>Logical networks used in the package</Info>
<Network ovf:name="NAT">
<Description>Logical network used by this appliance.</Description>
</Network>
</NetworkSection>
<VirtualSystem ovf:id="[BOXNAME]">
<Info>A virtual machine</Info>
<OperatingSystemSection ovf:id="96">
<Info>The kind of installed guest operating system</Info>
<Description>Debian_64</Description>
<vbox:OSType ovf:required="false">Debian_64</vbox:OSType>
</OperatingSystemSection>
<VirtualHardwareSection>
<Info>Virtual hardware requirements for a virtual machine</Info>
<System>
<vssd:ElementName>Virtual Hardware Family</vssd:ElementName>
<vssd:InstanceID>0</vssd:InstanceID>
<vssd:VirtualSystemIdentifier>[BOXNAME]</vssd:VirtualSystemIdentifier>
<vssd:VirtualSystemType>virtualbox-2.2</vssd:VirtualSystemType>
</System>
<Item>
<rasd:Caption>1 virtual CPU</rasd:Caption>
<rasd:Description>Number of virtual CPUs</rasd:Description>
<rasd:ElementName>1 virtual CPU</rasd:ElementName>
<rasd:InstanceID>1</rasd:InstanceID>
<rasd:ResourceType>3</rasd:ResourceType>
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
<rasd:Caption>256 MB of memory</rasd:Caption>
<rasd:Description>Memory Size</rasd:Description>
<rasd:ElementName>256 MB of memory</rasd:ElementName>
<rasd:InstanceID>2</rasd:InstanceID>
<rasd:ResourceType>4</rasd:ResourceType>
<rasd:VirtualQuantity>256</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:Address>0</rasd:Address>
<rasd:Caption>ideController0</rasd:Caption>
<rasd:Description>IDE Controller</rasd:Description>
<rasd:ElementName>ideController0</rasd:ElementName>
<rasd:InstanceID>3</rasd:InstanceID>
<rasd:ResourceSubType>PIIX4</rasd:ResourceSubType>
<rasd:ResourceType>5</rasd:ResourceType>
</Item>
<Item>
<rasd:Address>1</rasd:Address>
<rasd:Caption>ideController1</rasd:Caption>
<rasd:Description>IDE Controller</rasd:Description>
<rasd:ElementName>ideController1</rasd:ElementName>
<rasd:InstanceID>4</rasd:InstanceID>
<rasd:ResourceSubType>PIIX4</rasd:ResourceSubType>
<rasd:ResourceType>5</rasd:ResourceType>
</Item>
<Item>
<rasd:Address>0</rasd:Address>
<rasd:Caption>sataController0</rasd:Caption>
<rasd:Description>SATA Controller</rasd:Description>
<rasd:ElementName>sataController0</rasd:ElementName>
<rasd:InstanceID>5</rasd:InstanceID>
<rasd:ResourceSubType>AHCI</rasd:ResourceSubType>
<rasd:ResourceType>20</rasd:ResourceType>
</Item>
<Item>
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
<rasd:Caption>Ethernet adapter on 'NAT'</rasd:Caption>
<rasd:Connection>NAT</rasd:Connection>
<rasd:ElementName>Ethernet adapter on 'NAT'</rasd:ElementName>
<rasd:InstanceID>6</rasd:InstanceID>
<rasd:ResourceSubType>E1000</rasd:ResourceSubType>
<rasd:ResourceType>10</rasd:ResourceType>
</Item>
<Item>
<rasd:AddressOnParent>0</rasd:AddressOnParent>
<rasd:Caption>disk1</rasd:Caption>
<rasd:Description>Disk Image</rasd:Description>
<rasd:ElementName>disk1</rasd:ElementName>
<rasd:HostResource>/disk/vmdisk1</rasd:HostResource>
<rasd:InstanceID>7</rasd:InstanceID>
<rasd:Parent>5</rasd:Parent>
<rasd:ResourceType>17</rasd:ResourceType>
</Item>
<Item>
<rasd:AddressOnParent>0</rasd:AddressOnParent>
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
<rasd:Caption>cdrom1</rasd:Caption>
<rasd:Description>CD-ROM Drive</rasd:Description>
<rasd:ElementName>cdrom1</rasd:ElementName>
<rasd:InstanceID>8</rasd:InstanceID>
<rasd:Parent>3</rasd:Parent>
<rasd:ResourceType>15</rasd:ResourceType>
</Item>
<Item>
<rasd:AddressOnParent>0</rasd:AddressOnParent>
<rasd:AutomaticAllocation>true</rasd:AutomaticAllocation>
<rasd:Caption>cdrom2</rasd:Caption>
<rasd:Description>CD-ROM Drive</rasd:Description>
<rasd:ElementName>cdrom2</rasd:ElementName>
<rasd:InstanceID>9</rasd:InstanceID>
<rasd:Parent>4</rasd:Parent>
<rasd:ResourceType>15</rasd:ResourceType>
</Item>
</VirtualHardwareSection>
<vbox:Machine ovf:required="false" version="none" uuid="{[SYSTEM_UUID]}" name="[BOXNAME]" OSType="Debian_64" snapshotFolder="Snapshots" lastStateChange="[LAST_CHANGED]">
<ovf:Info>Complete VirtualBox machine configuration in VirtualBox format</ovf:Info>
<ExtraData></ExtraData>
<Hardware version="2">
<CPU count="1" hotplug="false">
<HardwareVirtEx enabled="true" exclusive="false"/>
<HardwareVirtExNestedPaging enabled="true"/>
<HardwareVirtExVPID enabled="true"/>
<PAE enabled="true"/>
<HardwareVirtExLargePages enabled="true"/>
<HardwareVirtForce enabled="false"/>
</CPU>
<Memory RAMSize="256" PageFusion="false"/>
<HID Pointing="PS2Mouse" Keyboard="PS2Keyboard"/>
<HPET enabled="false"/>
<Chipset type="PIIX3"/>
<Boot>
<Order position="1" device="HardDisk"/>
<Order position="2" device="DVD"/>
<Order position="3" device="None"/>
<Order position="4" device="None"/>
</Boot>
<Display VRAMSize="8" monitorCount="1" accelerate3D="false" accelerate2DVideo="false"/>
<VideoRecording enabled="false" file="Test.webm" horzRes="640" vertRes="480"/>
<RemoteDisplay enabled="false" authType="Null"/>
<BIOS>
<ACPI enabled="true"/>
<IOAPIC enabled="true"/>
<Logo fadeIn="true" fadeOut="true" displayTime="0"/>
<BootMenu mode="MessageAndMenu"/>
<TimeOffset value="0"/>
<PXEDebug enabled="false"/>
</BIOS>
<USBController enabled="false" enabledEhci="false"/>
<Network>
<Adapter slot="0" enabled="true" MACAddress="[MAC_ADDRESS]" cable="true" speed="0" type="82540EM">
<DisabledModes/>
<NAT>
<DNS pass-domain="true" use-proxy="false" use-host-resolver="false"/>
<Alias logging="false" proxy-only="false" use-same-ports="false"/>
</NAT>
</Adapter>
</Network>
<UART>
<Port slot="0" enabled="false" IOBase="0x3f8" IRQ="4" hostMode="Disconnected"/>
<Port slot="1" enabled="false" IOBase="0x2f8" IRQ="3" hostMode="Disconnected"/>
</UART>
<LPT>
<Port slot="0" enabled="false" IOBase="0x378" IRQ="7"/>
<Port slot="1" enabled="false" IOBase="0x378" IRQ="7"/>
</LPT>
<AudioAdapter controller="AC97" driver="CoreAudio" enabled="false"/>
<RTC localOrUTC="local"/>
<SharedFolders></SharedFolders>
<Clipboard mode="Disabled"/>
<DragAndDrop mode="Disabled"/>
<IO>
<IoCache enabled="true" size="5"/>
<BandwidthGroups/>
</IO>
<HostPci>
<Devices/>
</HostPci>
<EmulatedUSB>
<CardReader enabled="false"/>
</EmulatedUSB>
<Guest memoryBalloonSize="0"/>
<GuestProperties/>
</Hardware>
<StorageControllers>
<StorageController name="IDE Controller" type="PIIX4" PortCount="2" useHostIOCache="true" Bootable="true">
<AttachedDevice passthrough="false" type="DVD" port="0" device="0"/>
<AttachedDevice passthrough="false" type="DVD" port="1" device="0"/>
</StorageController>
<StorageController name="SATA Controller" type="AHCI" PortCount="1" useHostIOCache="false" Bootable="true" IDE0MasterEmulationPort="0" IDE0SlaveEmulationPort="1" IDE1MasterEmulationPort="2" IDE1SlaveEmulationPort="3">
<AttachedDevice type="HardDisk" port="0" device="0">
<Image uuid="{[DISK_UUID]}"/>
</AttachedDevice>
</StorageController>
</StorageControllers>
</vbox:Machine>
</VirtualSystem>
</Envelope>

View file

@ -0,0 +1 @@
{"provider": "virtualbox"}

View file

@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Vagrant plugin manifest",
"type": "object",
"properties": {
"provider": {
"type": "string",
"enum": ["virtualbox"]
},
"volume": {
"type": "object",
"properties": {
"backing": {
"type": "string",
"enum": ["vmdk"]
// VirtualBox only supports vmdk or raw when importing via OVF:
// https://www.virtualbox.org/browser/vbox/trunk/src/VBox/Main/src-server/ApplianceImplImport.cpp#L636
}
},
"required": ["backing"]
},
"plugins": {
"type": "object",
"properties": {
"root_password": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
},
"required": ["password"]
}
},
"required": ["root_password"]
}
},
"required": ["plugins"]
}

157
plugins/vagrant/tasks.py Normal file
View file

@ -0,0 +1,157 @@
from base import Task
from common import phases
from common.tasks import workspace
from common.tasks import apt
from plugins.admin_user.tasks import CreateAdminUser
import os
import shutil
assets = os.path.normpath(os.path.join(os.path.dirname(__file__), 'assets'))
class CreateVagrantBoxDir(Task):
description = 'Creating directory for the vagrant box'
phase = phases.preparation
predecessors = [workspace.CreateWorkspace]
def run(self, 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
predecessors = [apt.AddDefaultSources]
def run(self, info):
info.packages.add('openssh-server')
class AddInsecurePublicKey(Task):
description = 'Adding vagrant insecure public key'
phase = phases.system_modification
predecessors = [CreateAdminUser]
def run(self, 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)
class PackageBox(Task):
description = 'Packaging the volume as a vagrant box'
phase = phases.image_registration
def run(self, info):
box_basename = info.manifest.image['name'].format(**info.manifest_vars)
box_name = '{name}.box'.format(name=box_basename)
box_path = os.path.join(info.manifest.bootstrapper['workspace'], box_name)
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 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 common.tools import log_check_call
disk_name = 'box-disk1.{ext}'.format(ext=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')
self.write_ovf(info, ovf_path, box_name, mac_address, disk_name)
box_files = os.listdir(info.vagrant_folder)
log_check_call(['tar', '--create', '--gzip', '--dereference',
'--file', box_path,
'--directory', info.vagrant_folder]
+ box_files
)
import logging
logging.getLogger(__name__).info('The vagrant box has been placed at {box_path}'
.format(box_path=box_path))
def write_ovf(self, info, destination, box_name, 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.partition_map.get_total_size() * 1024 * 1024)
attr(disk, 'ovf:format', info.volume.ovf_uri)
attr(disk, 'ovf:uuid', volume_uuid)
[system] = root.findall('./ovf:VirtualSystem', namespaces)
attr(system, 'ovf:id', box_name)
[sysid] = system.findall('./ovf:VirtualHardwareSection/ovf:System/'
'vssd:VirtualSystemIdentifier', namespaces)
sysid.text = box_name
[machine] = system.findall('./vbox:Machine', namespaces)
import uuid
attr(machine, 'ovf:uuid', uuid.uuid4())
attr(machine, 'ovf:name', 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, 'ovf: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]
def run(self, info):
shutil.rmtree(info.vagrant_folder)
del info.vagrant_folder