This commit is contained in:
Olivier Sallou 2013-07-31 08:03:08 +02:00
commit cd6e10c6a1
21 changed files with 414 additions and 125 deletions

View file

@ -13,11 +13,13 @@ Pull requests are also welcome!
Dependencies
------------
You will need to run debian wheezy with python 2.7 and debootstrap installed.
You will need to run debian wheezy with **python 2.7** and **debootstrap** installed.
Also the following python libraries are required:
* boto
* jsomschema
* termcolor
* **boto**
* **jsomschema** ([version 2.0.0](https://pypi.python.org/pypi/jsonschema), only available through pip)
* **termcolor**
Bootstrapping instance store AMIs requires **euca2ools** to be installed.
Highlights
----------

View file

@ -3,4 +3,6 @@
class BootstrapInformation(object):
def __init__(self, manifest=None, debug=False):
self.manifest = manifest
self.debug = debug
self.debug = debug
import random
self.run_id = random.randrange(16**8)

View file

@ -49,7 +49,7 @@ class Manifest(object):
self.bootstrapper = data['bootstrapper']
if 'tarball' not in self.bootstrapper:
self.bootstrapper['tarball'] = False
if 'tarball_dir' not in self.bootstrapper:
if 'tarball_dir' not in self.bootstrapper and self.bootstrapper['tarball']:
self.bootstrapper['tarball_dir'] = '/tmp'
self.volume = data['volume']
self.system = data['system']

View file

@ -5,6 +5,7 @@ def log_check_call(command, input=None):
if status != 0:
from subprocess import CalledProcessError
raise CalledProcessError(status, ' '.join(command), '\n'.join(stderr))
return stdout
def log_call(command, input=None):

View file

@ -1,30 +1,29 @@
{
"provider" : "ec2",
"provider": "ec2",
"virtualization": "pvm",
"credentials" : {
"credentials": {
"access-key": null,
"secret-key": null
},
"bootstrapper": {
"mount_dir": "/target",
"tarball": true
"mount_dir": "/target"
},
"image": {
"name" : "debian-{release}-{architecture}-{virtualization}-{%y}{%m}{%d}",
"name": "debian-{release}-{architecture}-{virtualization}-{%y}{%m}{%d}",
"description": "Debian {release} {architecture} AMI ({virtualization})"
},
"system": {
"release" : "wheezy",
"release": "wheezy",
"architecture": "amd64",
"timezone" : "UTC",
"locale" : "en_US",
"charmap" : "UTF-8"
"timezone": "UTC",
"locale": "en_US",
"charmap": "UTF-8"
},
"volume": {
"backing" : "ebs",
"backing": "ebs",
"filesystem": "ext4",
"size" : 1024
"size": 1024
},
"plugins": {
"admin_user": {
@ -32,10 +31,10 @@
},
"build_metadata": {
"enabled": false,
"path" : "/root/build-metadata-{ami_name}"
"path": "/root/build-metadata-{ami_name}"
},
"prebootstrapped": {
"enabled": false,
"enabled": false,
"snapshot": ""
}
}

View file

@ -0,0 +1,38 @@
{
"provider": "ec2",
"virtualization": "pvm",
"credentials": {
"access-key": null,
"secret-key": null,
"certificate": null,
"private-key": null,
"user-id": null
},
"bootstrapper": {
"mount_dir": "/target"
},
"image": {
"name": "debian-{release}-{architecture}-{virtualization}-{%y}{%m}{%d}",
"description": "Debian {release} {architecture} AMI ({virtualization})"
"bucket": ""
},
"system": {
"release": "wheezy",
"architecture": "amd64",
"timezone": "UTC",
"locale": "en_US",
"charmap": "UTF-8"
},
"volume": {
"backing": "s3",
"filesystem": "ext4",
"size": 1024
},
"plugins": {
"prebootstrapped": {
"enabled": false,
"image": null
}
}
}

View file

@ -1,20 +1,35 @@
from tasks import CreateSnapshot
from tasks import CreateVolumeFromSnapshot
from tasks import Snapshot
from tasks import CopyImage
from tasks import CreateFromSnapshot
from tasks import CreateFromImage
from providers.ec2.tasks import ebs
from providers.ec2.tasks import loopback
def tasks(tasklist, manifest):
from providers.ec2.tasks import bootstrap
from providers.ec2.tasks import filesystem
if manifest.plugins['prebootstrapped']['snapshot'] == "":
tasklist.add(CreateSnapshot())
settings = manifest.plugins['prebootstrapped']
if manifest.volume['backing'] == 'ebs':
if 'snapshot' in settings and settings['snapshot'] is not None:
tasklist.replace(ebs.Create, CreateFromSnapshot())
tasklist.remove(filesystem.FormatVolume,
filesystem.TuneVolumeFS,
filesystem.AddXFSProgs,
bootstrap.MakeTarball,
bootstrap.Bootstrap)
else:
tasklist.add(Snapshot())
else:
tasklist.replace(ebs.CreateVolume, CreateVolumeFromSnapshot())
tasklist.remove(filesystem.FormatVolume,
filesystem.TuneVolumeFS,
filesystem.AddXFSProgs,
bootstrap.MakeTarball,
bootstrap.Bootstrap)
if 'image' in settings and settings['image'] is not None:
tasklist.replace(loopback.Create, CreateFromImage())
tasklist.remove(filesystem.FormatVolume,
filesystem.TuneVolumeFS,
filesystem.AddXFSProgs,
bootstrap.MakeTarball,
bootstrap.Bootstrap)
else:
tasklist.add(CopyImage())
def rollback_tasks(tasklist, tasks_completed, manifest):
@ -24,7 +39,10 @@ def rollback_tasks(tasklist, tasks_completed, manifest):
if task in completed and counter not in completed:
tasklist.add(counter())
counter_task(CreateVolumeFromSnapshot, ebs.DeleteVolume)
if manifest.volume['backing'] == 'ebs':
counter_task(CreateFromSnapshot, ebs.Delete)
else:
counter_task(CreateFromImage, loopback.Delete)
def validate_manifest(data, schema_validate):

View file

@ -11,9 +11,11 @@
"properties": {
"snapshot": {
"type": "string"
},
"image": {
"type": "string"
}
},
"required": ["snapshot"]
}
}
},
"required": ["prebootstrapped"]

View file

@ -1,18 +1,28 @@
from base import Task
from common import phases
from providers.ec2.tasks import connection
from providers.ec2.tasks import ebs
from providers.ec2.tasks import loopback
from providers.ec2.tasks import bootstrap
import time
import logging
log = logging.getLogger(__name__)
class CreateVolumeFromSnapshot(Task):
class Snapshot(ebs.Snapshot):
description = 'Creating a snapshot of the bootstrapped volume'
phase = phases.os_installation
after = [bootstrap.Bootstrap]
def run(self, info):
super(Snapshot, self).run(info)
msg = 'A snapshot of the bootstrapped volume was created. ID: {id}'.format(id=info.snapshot.id)
log.info(msg)
class CreateFromSnapshot(Task):
description = 'Creating EBS volume from a snapshot'
phase = phases.volume_creation
after = [connection.Connect]
before = [ebs.AttachVolume]
before = [ebs.Attach]
def run(self, info):
volume_size = int(info.manifest.volume['size']/1024)
@ -25,12 +35,30 @@ class CreateVolumeFromSnapshot(Task):
info.volume.update()
class CreateSnapshot(ebs.CreateSnapshot):
class CopyImage(Task):
description = 'Creating a snapshot of the bootstrapped volume'
phase = phases.os_installation
after = [bootstrap.Bootstrap]
def run(self, info):
super(CreateSnapshot, self).run(info)
msg = 'A snapshot of the bootstrapped volume was created. ID: {id}'.format(id=info.snapshot.id)
import os.path
from shutil import copyfile
loopback_backup_name = 'loopback-{id:x}.img.backup'.format(id=info.run_id)
image_copy_path = os.path.join('/tmp', loopback_backup_name)
copyfile(info.loopback_file, image_copy_path)
msg = 'A copy of the bootstrapped volume was created. Path: {path}'.format(path=image_copy_path)
log.info(msg)
class CreateFromImage(Task):
description = 'Creating loopback image from a copy'
phase = phases.volume_creation
before = [loopback.Attach]
def run(self, info):
loopback_filename = 'loopback-{id:x}.img'.format(id=info.run_id)
import os.path
info.loopback_file = os.path.join(info.manifest.volume['loopback_dir'], loopback_filename)
loopback_backup_path = info.manifest.plugins['prebootstrapped']['image']
from shutil import copyfile
copyfile(loopback_backup_path, info.loopback_file)

View file

@ -5,6 +5,7 @@ from tasks import connection
from tasks import host
from tasks import ami
from tasks import ebs
from tasks import loopback
from tasks import filesystem
from tasks import bootstrap
from tasks import locale
@ -28,20 +29,13 @@ def tasks(tasklist, manifest):
connection.GetCredentials(),
host.GetInfo(),
ami.AMIName(),
connection.Connect())
if manifest.volume['backing'].lower() == 'ebs':
tasklist.add(ebs.CreateVolume(),
ebs.AttachVolume())
tasklist.add(filesystem.FormatVolume())
if manifest.volume['filesystem'].lower() == 'xfs':
tasklist.add(filesystem.AddXFSProgs())
if manifest.volume['filesystem'].lower() in ['ext2', 'ext3', 'ext4']:
tasklist.add(filesystem.TuneVolumeFS())
tasklist.add(filesystem.CreateMountDir(),
filesystem.MountVolume())
if manifest.bootstrapper['tarball']:
tasklist.add(bootstrap.MakeTarball())
tasklist.add(bootstrap.Bootstrap(),
connection.Connect(),
filesystem.FormatVolume(),
filesystem.CreateMountDir(),
filesystem.MountVolume(),
bootstrap.Bootstrap(),
filesystem.MountSpecials(),
locale.GenerateLocale(),
locale.SetTimezone(),
@ -68,12 +62,31 @@ def tasks(tasklist, manifest):
apt.EnableDaemonAutostart(),
filesystem.UnmountSpecials(),
filesystem.UnmountVolume(),
filesystem.DeleteMountDir())
if manifest.volume['backing'].lower() == 'ebs':
tasklist.add(ebs.DetachVolume(),
ebs.CreateSnapshot(),
ebs.DeleteVolume())
tasklist.add(ami.RegisterAMI())
filesystem.DeleteMountDir(),
ami.RegisterAMI())
if manifest.bootstrapper['tarball']:
tasklist.add(bootstrap.MakeTarball())
backing_specific_tasks = {'ebs': [ebs.Create(),
ebs.Attach(),
ebs.Detach(),
ebs.Snapshot(),
ebs.Delete()],
's3': [loopback.Create(),
loopback.Attach(),
loopback.Detach(),
ami.BundleImage(),
ami.UploadImage(),
loopback.Delete(),
ami.RemoveBundle()]}
tasklist.add(*backing_specific_tasks.get(manifest.volume['backing'].lower()))
filesystem_specific_tasks = {'xfs': [filesystem.AddXFSProgs()],
'ext2': [filesystem.TuneVolumeFS()],
'ext3': [filesystem.TuneVolumeFS()],
'ext4': [filesystem.TuneVolumeFS()]}
tasklist.add(*filesystem_specific_tasks.get(manifest.volume['filesystem'].lower()))
def rollback_tasks(tasklist, tasks_completed, manifest):
@ -84,8 +97,11 @@ def rollback_tasks(tasklist, tasks_completed, manifest):
tasklist.add(counter())
if manifest.volume['backing'].lower() == 'ebs':
counter_task(ebs.CreateVolume, ebs.DeleteVolume)
counter_task(ebs.AttachVolume, ebs.DetachVolume)
counter_task(ebs.Create, ebs.Delete)
counter_task(ebs.Attach, ebs.Detach)
if manifest.volume['backing'].lower() == 's3':
counter_task(loopback.Create, loopback.Delete)
counter_task(loopback.Attach, loopback.Detach)
counter_task(filesystem.CreateMountDir, filesystem.DeleteMountDir)
counter_task(filesystem.MountVolume, filesystem.UnmountVolume)
counter_task(filesystem.MountSpecials, filesystem.UnmountSpecials)

View file

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDzjCCAzegAwIBAgIJALDnZV+lpZdSMA0GCSqGSIb3DQEBBQUAMIGhMQswCQYD
VQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRv
d24xJzAlBgNVBAoTHkFtYXpvbiBEZXZlbG9wbWVudCBDZW50cmUgKFNBKTEMMAoG
A1UECxMDQUVTMREwDwYDVQQDEwhBRVMgVGVzdDEdMBsGCSqGSIb3DQEJARYOYWVz
QGFtYXpvbi5jb20wHhcNMDUwODA5MTYwMTA5WhcNMDYwODA5MTYwMTA5WjCBoTEL
MAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2Fw
ZSBUb3duMScwJQYDVQQKEx5BbWF6b24gRGV2ZWxvcG1lbnQgQ2VudHJlIChTQSkx
DDAKBgNVBAsTA0FFUzERMA8GA1UEAxMIQUVTIFRlc3QxHTAbBgkqhkiG9w0BCQEW
DmFlc0BhbWF6b24uY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8v/X5
zZv8CAVfNmvBM0br/RUcf1wU8xC5d2otFQQsQKB3qiWoj3oHeOWskOlTPFVZ8N+/
hEaMjyOUkg2+g6XEagCQtFCEBzUVoMjiQIBPiWj5CWkFtlav2zt33LZ0ErTND4xl
j7FQFqbaytHU9xuQcFO2p12bdITiBs5Kwoi9bQIDAQABo4IBCjCCAQYwHQYDVR0O
BBYEFPQnsX1kDVzPtX+38ACV8RhoYcw8MIHWBgNVHSMEgc4wgcuAFPQnsX1kDVzP
tX+38ACV8RhoYcw8oYGnpIGkMIGhMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2Vz
dGVybiBDYXBlMRIwEAYDVQQHEwlDYXBlIFRvd24xJzAlBgNVBAoTHkFtYXpvbiBE
ZXZlbG9wbWVudCBDZW50cmUgKFNBKTEMMAoGA1UECxMDQUVTMREwDwYDVQQDEwhB
RVMgVGVzdDEdMBsGCSqGSIb3DQEJARYOYWVzQGFtYXpvbi5jb22CCQCw52VfpaWX
UjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAJJlWll4uGlrqBzeIw7u
M3RvomlxMESwGKb9gI+ZeORlnHAyZxvd9XngIcjPuU+8uc3wc10LRQUCn45a5hFs
zaCp9BSewLCCirn6awZn2tP8JlagSbjrN9YShStt8S3S/Jj+eBoRvc7jJnmEeMkx
O0wHOzp5ZHRDK7tGULD6jCfU
-----END CERTIFICATE-----

View file

@ -0,0 +1,31 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "EC2 manifest for instance store AMIs",
"type": "object",
"properties": {
"credentials": {
"type": "object",
"properties": {
"certificate": {
"type": "string"
},
"private-key": {
"type": "string"
},
"user-id": {
"type": "string"
}
}
},
"image": {
"type": "object",
"properties": {
"bucket": {
"type": "string"
}
},
"required": ["bucket"]
}
},
"required": ["image"]
}

View file

@ -3,6 +3,17 @@
"title": "EC2 manifest",
"type": "object",
"properties": {
"credentials": {
"type": "object",
"properties": {
"access-key": {
"type": "string"
},
"secret-key": {
"type": "string"
}
}
},
"volume": {
"type": "object",
"properties": {

View file

@ -8,9 +8,13 @@ class Manifest(base.Manifest):
from os import path
schema_path = path.join(path.dirname(__file__), 'manifest-schema.json')
self.schema_validate(data, schema_path)
if data['volume']['backing'] == 'ebs' and data['volume']['size'] % 1024 != 0:
msg = 'The volume size must be a multiple of 1024 when using EBS backing'
raise ManifestError(msg, self)
if data['volume']['backing'] == 'ebs':
if data['volume']['size'] % 1024 != 0:
msg = 'The volume size must be a multiple of 1024 when using EBS backing'
raise ManifestError(msg, self)
else:
schema_path = path.join(path.dirname(__file__), 'manifest-schema-s3.json')
self.schema_validate(data, schema_path)
def parse(self, data):
super(Manifest, self).parse(data)
@ -19,3 +23,7 @@ class Manifest(base.Manifest):
self.image = data['image']
if data['volume']['backing'] == 'ebs':
self.ebs_volume_size = data['volume']['size'] / 1024
if 'loopback_dir' not in self.volume and self.volume['backing'].lower() == 's3':
self.volume['loopback_dir'] = '/tmp'
if 'bundle_dir' not in self.image and self.volume['backing'].lower() == 's3':
self.image['bundle_dir'] = '/tmp'

View file

@ -1,8 +1,12 @@
from base import Task
from common import phases
from ebs import CreateSnapshot
from connection import Connect
from common.exceptions import TaskError
from common.tools import log_check_call
from ebs import Snapshot
from connection import Connect
import os.path
cert_ec2 = os.path.normpath(os.path.join(os.path.dirname(__file__), '../assets/certs/cert-ec2.pem'))
class AMIName(Task):
@ -35,42 +39,99 @@ class AMIName(Task):
info.ami_description = ami_description
class BundleImage(Task):
description = 'Bundling the image'
phase = phases.image_registration
def run(self, info):
bundle_name = 'bundle-{id:x}'.format(id=info.run_id)
info.bundle_path = os.path.join(info.manifest.image['bundle_dir'], bundle_name)
log_check_call(['/usr/bin/euca-bundle-image',
'--image', info.loopback_file,
'--user', info.credentials['user-id'],
'--privatekey', info.credentials['private-key'],
'--cert', info.credentials['certificate'],
'--ec2cert', cert_ec2,
'--destination', info.bundle_path,
'--prefix', info.ami_name])
class UploadImage(Task):
description = 'Uploading the image bundle'
phase = phases.image_registration
after = [BundleImage]
def run(self, info):
manifest_file = os.path.join(info.bundle_path, info.ami_name + '.manifest.xml')
if info.host['region'] == 'us-east-1':
s3_url = 'https://s3.amazonaws.com/'
else:
s3_url = 'https://s3-{region}.amazonaws.com/'.format(region=info.host['region'])
log_check_call(['/usr/bin/euca-upload-bundle',
'--bucket', info.manifest.image['bucket'],
'--manifest', manifest_file,
'--access-key', info.credentials['access-key'],
'--secret-key', info.credentials['secret-key'],
'--url', s3_url,
'--region', info.host['region'],
'--ec2cert', cert_ec2])
class RemoveBundle(Task):
description = 'Removing the bundle files'
phase = phases.cleaning
def run(self, info):
from shutil import rmtree
rmtree(info.bundle_path)
del info.bundle_path
class RegisterAMI(Task):
description = 'Registering the image as an AMI'
phase = phases.image_registration
after = [CreateSnapshot]
after = [Snapshot, UploadImage]
kernel_mapping = {'us-east-1': {'amd64': 'aki-88aa75e1',
'i386': 'aki-b6aa75df'},
'us-west-1': {'amd64': 'aki-f77e26b2',
'i386': 'aki-f57e26b0'},
'us-west-2': {'amd64': 'aki-fc37bacc',
'i386': 'aki-fa37baca'},
'eu-west-1': {'amd64': 'aki-71665e05',
'i386': 'aki-75665e01'},
'ap-southeast-1': {'amd64': 'aki-fe1354ac',
'i386': 'aki-f81354aa'},
'ap-southeast-2': {'amd64': 'aki-31990e0b',
'i386': 'aki-33990e09'},
'ap-northeast-1': {'amd64': 'aki-44992845',
'i386': 'aki-42992843'},
'sa-east-1': {'amd64': 'aki-c48f51d9',
'i386': 'aki-ca8f51d7'},
'us-gov-west-1': {'amd64': 'aki-79a4c05a',
'i386': 'aki-7ba4c058'}}
def run(self, info):
arch = {'i386': 'i386',
'amd64': 'x86_64'}.get(info.manifest.system['architecture'])
kernel_mapping = {'us-east-1': {'amd64': 'aki-88aa75e1',
'i386': 'aki-b6aa75df'},
'us-west-1': {'amd64': 'aki-f77e26b2',
'i386': 'aki-f57e26b0'},
'us-west-2': {'amd64': 'aki-fc37bacc',
'i386': 'aki-fa37baca'},
'eu-west-1': {'amd64': 'aki-71665e05',
'i386': 'aki-75665e01'},
'ap-southeast-1': {'amd64': 'aki-fe1354ac',
'i386': 'aki-f81354aa'},
'ap-southeast-2': {'amd64': 'aki-31990e0b',
'i386': 'aki-33990e09'},
'ap-northeast-1': {'amd64': 'aki-44992845',
'i386': 'aki-42992843'},
'sa-east-1': {'amd64': 'aki-c48f51d9',
'i386': 'aki-ca8f51d7'},
'us-gov-west-1': {'amd64': 'aki-79a4c05a',
'i386': 'aki-7ba4c058'}}
kernel_id = kernel_mapping.get(info.host['region']).get(info.manifest.system['architecture'])
arch = {'i386': 'i386', 'amd64': 'x86_64'}.get(info.manifest.system['architecture'])
kernel_id = self.kernel_mapping.get(info.host['region']).get(info.manifest.system['architecture'])
from boto.ec2.blockdevicemapping import BlockDeviceType
from boto.ec2.blockdevicemapping import BlockDeviceMapping
block_device = BlockDeviceType(snapshot_id=info.snapshot.id, delete_on_termination=True,
size=info.manifest.ebs_volume_size)
block_device_map = BlockDeviceMapping()
block_device_map['/dev/sda1'] = block_device
if info.manifest.volume['backing'] == 'ebs':
from boto.ec2.blockdevicemapping import BlockDeviceType
from boto.ec2.blockdevicemapping import BlockDeviceMapping
block_device = BlockDeviceType(snapshot_id=info.snapshot.id, delete_on_termination=True,
size=info.manifest.ebs_volume_size)
block_device_map = BlockDeviceMapping()
block_device_map['/dev/sda1'] = block_device
info.image = info.connection.register_image(name=info.ami_name, description=info.ami_description,
architecture=arch, kernel_id=kernel_id,
root_device_name='/dev/sda1',
block_device_map=block_device_map)
info.image = info.connection.register_image(name=info.ami_name, description=info.ami_description,
architecture=arch, kernel_id=kernel_id,
root_device_name='/dev/sda1',
block_device_map=block_device_map)
if info.manifest.volume['backing'] == 's3':
image_location = ('{bucket}/{ami_name}.manifest.xml'
.format(bucket=info.manifest.image['bucket'],
ami_name=info.ami_name))
info.image = info.connection.register_image(description=info.ami_description,
architecture=arch, kernel_id=kernel_id,
root_device_name='/dev/sda1',
image_location=image_location)

View file

@ -8,24 +8,24 @@ class GetCredentials(Task):
phase = phases.preparation
def run(self, info):
info.credentials = self.get_credentials(info.manifest)
keys = ['access-key', 'secret-key']
if info.manifest.volume['backing'] == 's3':
keys.extend(['certificate', 'private-key', 'user-id'])
info.credentials = self.get_credentials(info.manifest, keys)
def get_credentials(self, manifest):
def get_credentials(self, manifest, keys):
from os import getenv
# manifest overrides environment
if(manifest.credentials['access-key'] and manifest.credentials['secret-key']):
return {'access_key': manifest.credentials['access-key'],
'secret_key': manifest.credentials['secret-key']}
if(getenv('EC2_ACCESS_KEY') and getenv('EC2_SECRET_KEY')):
return {'access_key': getenv('EC2_ACCESS_KEY'),
'secret_key': getenv('EC2_SECRET_KEY')}
if(bool(manifest.credentials['access-key']) != bool(manifest.credentials['secret-key'])):
raise RuntimeError('Both the access key and secret key must be specified in the manifest.')
if(bool(getenv('EC2_ACCESS_KEY')) != bool(getenv('EC2_SECRET_KEY'))):
raise RuntimeError('Both the access key and secret key must be specified as environment variables.')
raise RuntimeError('No ec2 credentials found.')
creds = {}
if all(key in manifest.credentials for key in keys):
for key in keys:
creds[key] = manifest.credentials[key]
return creds
if all(getenv(key) is not None for key in keys):
for key in keys:
creds[key] = getenv(key)
return creds
raise RuntimeError(('No ec2 credentials found, they must all be specified '
'exclusively via environment variables or through the manifest.'))
class Connect(Task):
@ -36,5 +36,5 @@ class Connect(Task):
def run(self, info):
from boto.ec2 import connect_to_region
info.connection = connect_to_region(info.host['region'],
aws_access_key_id=info.credentials['access_key'],
aws_secret_access_key=info.credentials['secret_key'])
aws_access_key_id=info.credentials['access-key'],
aws_secret_access_key=info.credentials['secret-key'])

View file

@ -5,7 +5,7 @@ from filesystem import UnmountVolume
import time
class CreateVolume(Task):
class Create(Task):
description = 'Creating an EBS volume for bootstrapping'
phase = phases.volume_creation
@ -17,10 +17,10 @@ class CreateVolume(Task):
info.volume.update()
class AttachVolume(Task):
class Attach(Task):
description = 'Attaching the EBS volume'
phase = phases.volume_creation
after = [CreateVolume]
after = [Create]
def run(self, info):
def char_range(c1, c2):
@ -45,7 +45,7 @@ class AttachVolume(Task):
info.volume.update()
class DetachVolume(Task):
class Detach(Task):
description = 'Detaching the EBS volume'
phase = phases.volume_unmounting
after = [UnmountVolume]
@ -57,7 +57,7 @@ class DetachVolume(Task):
info.volume.update()
class CreateSnapshot(Task):
class Snapshot(Task):
description = 'Creating a snapshot of the EBS volume'
phase = phases.image_registration
@ -68,10 +68,9 @@ class CreateSnapshot(Task):
info.snapshot.update()
class DeleteVolume(Task):
class Delete(Task):
description = 'Deleting the EBS volume'
phase = phases.cleaning
after = []
def run(self, info):
info.volume.delete()

View file

@ -42,7 +42,7 @@ class CreateMountDir(Task):
def run(self, info):
import os
mount_dir = info.manifest.bootstrapper['mount_dir']
info.root = '{mount_dir}/{vol_id}'.format(mount_dir=mount_dir, vol_id=info.volume.id)
info.root = '{mount_dir}/{id:x}'.format(mount_dir=mount_dir, id=info.run_id)
# Works recursively, fails if last part exists, which is exaclty what we want.
os.makedirs(info.root)

View file

@ -16,7 +16,7 @@ class GenerateLocale(Task):
search = '# ' + locale_str
sed_i(locale_gen, search, locale_str)
command = ['/usr/sbin/chroot', info.root, '/usr/sbin/dpkg-reconfigure', '--priority=critical', 'locales']
command = ['/usr/sbin/chroot', info.root, '/usr/sbin/locale-gen']
log_check_call(command)

View file

@ -0,0 +1,48 @@
from base import Task
from common import phases
from filesystem import UnmountVolume
from common.tools import log_check_call
class Create(Task):
description = 'Creating a loopback volume'
phase = phases.volume_creation
def run(self, info):
loopback_filename = 'loopback-{id:x}.img'.format(id=info.run_id)
import os.path
info.loopback_file = os.path.join(info.manifest.volume['loopback_dir'], loopback_filename)
log_check_call(['/bin/dd',
'if=/dev/zero', 'of='+info.loopback_file,
'bs=1M', 'seek='+str(info.manifest.volume['size']), 'count=0'])
class Attach(Task):
description = 'Attaching the loopback volume'
phase = phases.volume_creation
after = [Create]
def run(self, info):
info.bootstrap_device = {}
[info.bootstrap_device['path']] = log_check_call(['/sbin/losetup', '--find'])
log_check_call(['/sbin/losetup', info.bootstrap_device['path'], info.loopback_file])
class Detach(Task):
description = 'Detaching the loopback volume'
phase = phases.volume_unmounting
after = [UnmountVolume]
def run(self, info):
log_check_call(['/sbin/losetup', '-d', info.bootstrap_device['path']])
del info.bootstrap_device
class Delete(Task):
description = 'Deleting the loopback volume'
phase = phases.cleaning
def run(self, info):
from os import remove
remove(info.loopback_file)
del info.loopback_file

View file

@ -10,6 +10,8 @@ class HostPackages(Task):
packages = set(['debootstrap'])
if info.manifest.volume['filesystem'] == 'xfs':
packages.add('xfsprogs')
if info.manifest.volume['backing'] == 's3':
packages.add('euca2ools')
info.host_packages = packages