Commit e77e66a9 authored by Nikos Skalkotos's avatar Nikos Skalkotos
Browse files

Unify output by creating a seperated output module

All image-creator components now output messages using an instance
of one of the output classes.
parent 5886f568
......@@ -32,7 +32,7 @@
# or implied, of GRNET S.A.
from image_creator.util import get_command
from image_creator.util import warn, progress, success, output, FatalError
from image_creator.util import FatalError
from image_creator.gpt import GPTPartitionTable
import stat
import os
......@@ -62,12 +62,13 @@ class Disk(object):
the Linux kernel.
"""
def __init__(self, source):
def __init__(self, source, output):
"""Create a new Disk instance out of a source media. The source
media can be an image file, a block device or a directory."""
self._cleanup_jobs = []
self._devices = []
self.source = source
self.out = output
def _add_cleanup(self, job, *args):
self._cleanup_jobs.append((job, args))
......@@ -98,7 +99,7 @@ class Disk(object):
instance.
"""
output("Examining source media `%s'..." % self.source, False)
self.out.output("Examining source media `%s'..." % self.source, False)
sourcedev = self.source
mode = os.stat(self.source).st_mode
if stat.S_ISDIR(mode):
......@@ -111,10 +112,10 @@ class Disk(object):
raise ValueError("Invalid media source. Only block devices, "
"regular files and directories are supported.")
else:
success('looks like a block device')
self.out.success('looks like a block device')
# Take a snapshot and return it to the user
output("Snapshotting media source...", False)
self.out.output("Snapshotting media source...", False)
size = blockdev('--getsize', sourcedev)
cowfd, cow = tempfile.mkstemp()
os.close(cowfd)
......@@ -137,13 +138,13 @@ class Disk(object):
finally:
os.unlink(table)
success('done')
self.out.success('done')
return "/dev/mapper/%s" % snapshot
def get_device(self, media):
"""Returns a newly created DiskDevice instance."""
new_device = DiskDevice(media)
new_device = DiskDevice(media, self.out)
self._devices.append(new_device)
new_device.enable()
return new_device
......@@ -161,10 +162,11 @@ class DiskDevice(object):
as created by the device-mapper.
"""
def __init__(self, device, bootable=True):
def __init__(self, device, output, bootable=True):
"""Create a new DiskDevice."""
self.real_device = device
self.out = output
self.bootable = bootable
self.progress_bar = None
self.guestfs_device = None
......@@ -180,7 +182,7 @@ class DiskDevice(object):
def enable(self):
"""Enable a newly created DiskDevice"""
self.progressbar = progress("Launching helper VM: ", "percent")
self.progressbar = self.out.Progress("Launching helper VM", "percent")
self.progressbar.max = 100
self.progressbar.goto(1)
eh = self.g.set_event_callback(self.progress_callback,
......@@ -188,12 +190,10 @@ class DiskDevice(object):
self.g.launch()
self.guestfs_enabled = True
self.g.delete_event_callback(eh)
if self.progressbar is not None:
output("\rLaunching helper VM...\033[K", False)
success("done")
self.progressbar = None
self.progressbar.success('done')
self.progressbar = None
output('Inspecting Operating System...', False)
self.out.output('Inspecting Operating System...', False)
roots = self.g.inspect_os()
if len(roots) == 0:
raise FatalError("No operating system found")
......@@ -208,7 +208,7 @@ class DiskDevice(object):
self.ostype = self.g.inspect_get_type(self.root)
self.distro = self.g.inspect_get_distro(self.root)
success('found a(n) %s system' % self.distro)
self.out.success('found a(n) %s system' % self.distro)
def destroy(self):
"""Destroy this DiskDevice instance."""
......@@ -229,7 +229,7 @@ class DiskDevice(object):
def mount(self):
"""Mount all disk partitions in a correct order."""
output("Mounting image...", False)
self.out.output("Mounting image...", False)
mps = self.g.inspect_get_mountpoints(self.root)
# Sort the keys to mount the fs in a correct order.
......@@ -246,8 +246,8 @@ class DiskDevice(object):
try:
self.g.mount(dev, mp)
except RuntimeError as msg:
warn("%s (ignored)" % msg)
success("done")
self.out.warn("%s (ignored)" % msg)
self.out.success("done")
def umount(self):
"""Umount all mounted filesystems."""
......@@ -307,7 +307,7 @@ class DiskDevice(object):
MB = 2 ** 20
output("Shrinking image (this may take a while)...", False)
self.out.output("Shrinking image (this may take a while)...", False)
last_part = None
fstype = None
......@@ -329,7 +329,7 @@ class DiskDevice(object):
break
if not re.match("ext[234]", fstype):
warn("Don't know how to resize %s partitions." % fstype)
self.out.warn("Don't know how to resize %s partitions." % fstype)
return self.meta['SIZE']
part_dev = "%s%d" % (self.guestfs_device, last_part['part_num'])
......@@ -387,7 +387,7 @@ class DiskDevice(object):
part_set_id(last_part['part_num'], last_part['id'])
new_size = (end + 1) * sector_size
success("new size is %dMB" % ((new_size + MB - 1) // MB))
self.out.success("new size is %dMB" % ((new_size + MB - 1) // MB))
if self.meta['PARTITION_TABLE'] == 'gpt':
ptable = GPTPartitionTable(self.real_device)
......@@ -407,7 +407,7 @@ class DiskDevice(object):
blocksize = 4 * MB # 4MB
size = self.meta['SIZE']
progress_size = (size + MB - 1) // MB # in MB
progressbar = progress("Dumping image file: ", 'mb')
progressbar = self.out.Progress("Dumping image file", 'mb')
progressbar.max = progress_size
with open(self.real_device, 'r') as src:
......@@ -421,7 +421,6 @@ class DiskDevice(object):
offset += sent
left -= sent
progressbar.goto((size - left) // MB)
output("\rDumping image file...\033[K", False)
success('image file %s was successfully created' % outfile)
progressbar.success('image file %s was successfully created' % outfile)
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
......@@ -39,37 +39,17 @@ from kamaki.clients.image import ImageClient
from kamaki.clients.pithos import PithosClient
from progress.bar import Bar
from image_creator.util import FatalError, output, success
from image_creator.util import FatalError
from image_creator.output import output, warn
CONTAINER = "images"
def progress(message):
MSG_LENGTH = 30
def progress_gen(n):
msg = "%s:" % message
progressbar = Bar(msg.ljust(MSG_LENGTH))
progressbar.max = n
progressbar.fill = '#'
progressbar.bar_prefix = ' ['
progressbar.bar_suffix = '] '
for _ in range(n):
yield
progressbar.next()
output("\r%s...\033[K" % message, False)
success("done")
yield
return progress_gen
class Kamaki:
def __init__(self, account, token):
class Kamaki(object):
def __init__(self, account, token, output):
self.account = account
self.token = token
self.out = output
config = Config()
......@@ -95,8 +75,8 @@ class Kamaki:
raise FatalError("Pithos client: %d %s" % \
(e.status, e.message))
try:
hash_cb = progress(hp) if hp is not None else None
upload_cb = progress(up) if up is not None else None
hash_cb = self.out.progress_gen(hp) if hp is not None else None
upload_cb = self.out.progress_gen(up) if up is not None else None
self.pithos_client.create_object(remote_path, file_obj, size,
hash_cb, upload_cb)
return "pithos://%s/%s/%s" % \
......
......@@ -36,8 +36,8 @@
from image_creator import __version__ as version
from image_creator import util
from image_creator.disk import Disk
from image_creator.util import get_command, error, success, output, \
FatalError, progress, md5
from image_creator.util import get_command, FatalError, MD5
from image_creator.output import Output, Output_with_progress, Silent, error
from image_creator.os_type import get_os_class
from image_creator.kamaki_wrapper import Kamaki
import sys
......@@ -153,17 +153,19 @@ def parse_options(input_args):
def image_creator():
options = parse_options(sys.argv[1:])
if options.silent:
util.silent = True
if options.outfile is None and not options.upload \
and not options.print_sysprep:
raise FatalError("At least one of `-o', `-u' or `--print-sysprep' " \
"must be set")
if options.silent:
out = Silent()
else:
out = Output_with_progress()
title = 'snf-image-creator %s' % version
output(title)
output('=' * len(title))
out.output(title)
out.output('=' * len(title))
if os.geteuid() != 0:
raise FatalError("You must run %s as root" \
......@@ -176,7 +178,7 @@ def image_creator():
raise FatalError("Output file %s exists "
"(use --force to overwrite it)." % filename)
disk = Disk(options.source)
disk = Disk(options.source, out)
try:
snapshot = disk.snapshot()
......@@ -184,8 +186,8 @@ def image_creator():
dev.mount()
osclass = get_os_class(dev.distro, dev.ostype)
image_os = osclass(dev.root, dev.g)
output()
image_os = osclass(dev.root, dev.g, out)
out.output()
for sysprep in options.disabled_syspreps:
image_os.disable_sysprep(sysprep)
......@@ -195,7 +197,7 @@ def image_creator():
if options.print_sysprep:
image_os.print_syspreps()
output()
out.output()
if options.outfile is None and not options.upload:
return 0
......@@ -206,13 +208,14 @@ def image_creator():
metadata = image_os.meta
dev.umount()
size = options.shrink and dev.shrink() or dev.size
size = options.shrink and dev.shrink() or dev.meta['SIZE']
metadata.update(dev.meta)
# Add command line metadata to the collected ones...
metadata.update(options.metadata)
checksum = md5(snapshot, size)
md5 = MD5(out)
checksum = md5.compute(snapshot, size)
metastring = '\n'.join(
['%s=%s' % (key, value) for (key, value) in metadata.items()])
......@@ -221,54 +224,54 @@ def image_creator():
if options.outfile is not None:
dev.dump(options.outfile)
output('Dumping metadata file...', False)
out.output('Dumping metadata file...', False)
with open('%s.%s' % (options.outfile, 'meta'), 'w') as f:
f.write(metastring)
success('done')
out.success('done')
output('Dumping md5sum file...', False)
out.output('Dumping md5sum file...', False)
with open('%s.%s' % (options.outfile, 'md5sum'), 'w') as f:
f.write('%s %s\n' % (checksum, \
os.path.basename(options.outfile)))
success('done')
out.success('done')
# Destroy the device. We only need the snapshot from now on
disk.destroy_device(dev)
output()
out.output()
uploaded_obj = ""
if options.upload:
output("Uploading image to pithos:")
kamaki = Kamaki(options.account, options.token)
out.output("Uploading image to pithos:")
kamaki = Kamaki(options.account, options.token, out)
with open(snapshot) as f:
uploaded_obj = kamaki.upload(f, size, options.upload,
"(1/4) Calculating block hashes",
"(2/4) Uploading missing blocks")
output("(3/4) Uploading metadata file...", False)
out.output("(3/4) Uploading metadata file...", False)
kamaki.upload(StringIO.StringIO(metastring), size=len(metastring),
remote_path="%s.%s" % (options.upload, 'meta'))
success('done')
output("(4/4) Uploading md5sum file...", False)
out.success('done')
out.output("(4/4) Uploading md5sum file...", False)
md5sumstr = '%s %s\n' % (
checksum, os.path.basename(options.upload))
kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr),
remote_path="%s.%s" % (options.upload, 'md5sum'))
success('done')
output()
out.success('done')
out.output()
if options.register:
output('Registring image to ~okeanos...', False)
out.output('Registring image to ~okeanos...', False)
kamaki.register(options.register, uploaded_obj, metadata)
success('done')
output()
out.success('done')
out.output()
finally:
output('cleaning up...')
out.output('cleaning up...')
disk.cleanup()
success("snf-image-creator exited without errors")
out.success("snf-image-creator exited without errors")
return 0
......
......@@ -31,7 +31,7 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
from image_creator.util import output, FatalError
from image_creator.util import FatalError
import textwrap
import re
......@@ -70,9 +70,10 @@ def sysprep(enabled=True):
class OSBase(object):
"""Basic operating system class"""
def __init__(self, rootdev, ghandler):
def __init__(self, rootdev, ghandler, output):
self.root = rootdev
self.g = ghandler
self.out = output
# Collect metadata about the OS
self.meta = {}
......@@ -129,23 +130,23 @@ class OSBase(object):
wrapper.initial_indent = '\t'
wrapper.width = 72
output("Enabled system preperation operations:")
self.out.output("Enabled system preperation operations:")
if len(enabled) == 0:
output("(none)")
self.out.output("(none)")
else:
for sysprep in enabled:
name = sysprep.__name__.replace('_', '-')
descr = wrapper.fill(textwrap.dedent(sysprep.__doc__))
output(' %s:\n%s\n' % (name, descr))
self.out.output(' %s:\n%s\n' % (name, descr))
output("Disabled system preperation operations:")
self.out.output("Disabled system preperation operations:")
if len(disabled) == 0:
output("(none)")
self.out.output("(none)")
else:
for sysprep in disabled:
name = sysprep.__name__.replace('_', '-')
descr = wrapper.fill(textwrap.dedent(sysprep.__doc__))
output(' %s:\n%s\n' % (name, descr))
self.out.output(' %s:\n%s\n' % (name, descr))
@add_prefix
def ls(self, directory):
......@@ -202,15 +203,15 @@ class OSBase(object):
def do_sysprep(self):
"""Prepere system for image creation."""
output('Preparing system for image creation:')
self.out.output('Preparing system for image creation:')
tasks, _ = self.list_syspreps()
size = len(tasks)
cnt = 0
for task in tasks:
cnt += 1
output(('(%d/%d)' % (cnt, size)).ljust(7), False)
self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
task()
output()
self.out.output()
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
......@@ -32,15 +32,14 @@
# or implied, of GRNET S.A.
from image_creator.os_type.unix import Unix, sysprep
from image_creator.util import warn, output
import re
import time
class Linux(Unix):
def __init__(self, rootdev, ghandler):
super(Linux, self).__init__(rootdev, ghandler)
def __init__(self, rootdev, ghandler, output):
super(Linux, self).__init__(rootdev, ghandler, output)
self._uuid = dict()
self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*')
......@@ -63,14 +62,14 @@ class Linux(Unix):
"""
if print_header:
output('Fixing acpid powerdown action')
self.out.output('Fixing acpid powerdown action')
powerbtn_action = '#!/bin/sh\n\nPATH=/sbin:/bin:/usr/bin\n' \
'shutdown -h now \"Power button pressed\"\n'
events_dir = '/etc/acpi/events'
if not self.g.is_dir(events_dir):
warn("No acpid event directory found")
self.out.warn("No acpid event directory found")
return
event_exp = re.compile('event=(.+)', re.I)
......@@ -95,18 +94,21 @@ class Linux(Unix):
if event.strip() == "button[ /]power":
if action:
if not self.g.is_file(action):
warn("Acpid action file: %s does not exist" % action)
self.out.warn("Acpid action file: %s does not exist" %
action)
return
self.g.copy_file_to_file(action, \
"%s.orig.snf-image-creator-%d" % (action, time.time()))
self.g.write(action, powerbtn_action)
return
else:
warn("Acpid event file %s does not contain and action")
self.out.warn(
"Acpid event file %s does not contain and action")
return
elif event.strip() == ".*":
warn("Found action `.*'. Don't know how to handle this." \
" Please edit \%s' image file manually to make the " \
self.out.warn(
"Found action `.*'. Don't know how to handle this. " \
"Please edit \%s' image file manually to make the " \
"system immediatelly shutdown when an power button acpi " \
"event occures" % action)
return
......@@ -119,7 +121,7 @@ class Linux(Unix):
"""
if print_header:
output('Removing persistent network interface names')
self.out.output('Removing persistent network interface names')
rule_file = '/etc/udev/rules.d/70-persistent-net.rules'
if self.g.is_file(rule_file):
......@@ -134,7 +136,7 @@ class Linux(Unix):
"""
if print_header:
output('Removing swap entry from fstab')
self.out.output('Removing swap entry from fstab')
new_fstab = ""
fstab = self.g.cat('/etc/fstab')
......@@ -155,7 +157,8 @@ class Linux(Unix):
"""
if print_header:
output('Replacing fstab & grub non-persistent device appearences')
self.out.output(
'Replacing fstab & grub non-persistent device appearences')
# convert all devices in fstab to persistent
persistent_root = self._persistent_fstab()
......@@ -217,7 +220,7 @@ class Linux(Unix):
entry = line.split()
if len(entry) != 6:
warn("Detected abnormal entry in fstab")
self.out.warn("Detected abnormal entry in fstab")
return orig, "", ""
dev = entry[0]
......
......@@ -35,8 +35,8 @@ from image_creator.os_type.linux import Linux, sysprep
class Ubuntu(Linux):
def __init__(self, rootdev, ghandler):
super(Ubuntu, self).__init__(rootdev, ghandler)
def __init__(self, rootdev, ghandler, output):
super(Ubuntu, self).__init__(rootdev, ghandler, output)
apps = self.g.inspect_list_applications(self.root)
for app in apps:
......
......@@ -35,7 +35,6 @@ import re
import sys
from image_creator.os_type import OSBase, sysprep
from image_creator.util import warn, output
class Unix(OSBase):
......@@ -48,8 +47,8 @@ class Unix(OSBase):
'.thunderbird'
]
def __init__(self, rootdev, ghandler):
super(Unix, self).__init__(rootdev, ghandler)
def __init__(self, rootdev, ghandler, output):
super(Unix, self).__init__(rootdev, ghandler, output)
self.meta["USERS"] = " ".join(self._get_passworded_users())
......@@ -75,7 +74,8 @@ class Unix(OSBase):
"""Remove all user accounts with id greater than 1000"""
if print_header:
output('Removing all user accounts with id greater than 1000')
self.out.output(
'Removing all user accounts with id greater than 1000')
# Remove users from /etc/passwd
passwd = []
......@@ -123,7 +123,8 @@ class Unix(OSBase):
"""Remove all passwords and lock all user accounts"""
if print_header:
output('Cleaning up passwords & locking all user accounts')
self.out.output(
'Cleaning up passwords & locking all user accounts')
shadow = []
......@@ -141,7 +142,7 @@ class Unix(OSBase):
"""Remove all regular files under /var/cache"""
if print_header:
output('Removing files under /var/cache')
self.out.output('Removing files under /var/cache')
self.foreach_file('/var/cache', self.g.rm, ftype='r')
......@@ -150,7 +151,7 @@ class Unix(OSBase):
"""Remove all files under /tmp and /var/tmp"""
if print_header:
output('Removing files under /tmp and /var/tmp')
self.out.output('Removing files under /tmp and /var/tmp')
self.foreach_file('/tmp', self.g.rm_rf, maxdepth=1)
self.foreach_file('/var/tmp', self.g.rm_rf, maxdepth=1)
......@@ -160,7 +161,7 @@ class Unix(OSBase):
"""Empty all files under /var/log"""
if print_header:
output('Emptying all files under /var/log')
self.out.output('Emptying all files under /var/log')
self.foreach_file('/var/log', self.g.truncate, ftype='r')
......@@ -169,7 +170,7 @@ class Unix(OSBase):
"""Remove all files under /var/mail and /var/spool/mail"""
if print_header:
output('Removing files unde