From: Nikos Skalkotos Date: Mon, 10 Jun 2013 07:33:35 +0000 (+0300) Subject: Code Cleanup X-Git-Tag: 0.4.1~3^2~2 X-Git-Url: https://code.grnet.gr/git/snf-image-creator/commitdiff_plain/121f3bc0cf7fc8591c01e21dc686e07c685ef23a Code Cleanup * Prefix all private methods with _ * Write missing docstrings * Move all public methods above the private ones * Add module docstrings where missing * Define source code encodings on all modules --- diff --git a/image_creator/__init__.py b/image_creator/__init__.py index f564b96..06d6ecc 100644 --- a/image_creator/__init__.py +++ b/image_creator/__init__.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,10 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""Package for creating images to be used with Synnefo open source cloud +software. +""" + from image_creator.version import __version__ # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/bundle_volume.py b/image_creator/bundle_volume.py index 18d0245..eec084f 100644 --- a/image_creator/bundle_volume.py +++ b/image_creator/bundle_volume.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,11 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module hosts the code that performes the host bundling operation. By +using the create_image method of the BundleVolume class the user can create an +image out of the running system. +""" + import os import re import tempfile diff --git a/image_creator/dialog_main.py b/image_creator/dialog_main.py index 024c37f..d8e86b1 100644 --- a/image_creator/dialog_main.py +++ b/image_creator/dialog_main.py @@ -1,5 +1,6 @@ #!/usr/bin/env python - +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -33,6 +34,11 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module is the entrance point for the dialog-based version of the +snf-image-creator program. The main function will create a dialog where the +user is asked if he wants to use the program in expert or wizard mode. +""" + import dialog import sys import os diff --git a/image_creator/dialog_menu.py b/image_creator/dialog_menu.py index 448060b..5b5526c 100644 --- a/image_creator/dialog_menu.py +++ b/image_creator/dialog_menu.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python - +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -33,6 +33,10 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module implements the "expert" mode of the dialog-based version of +snf-image-creator. +""" + import os import textwrap import StringIO @@ -313,7 +317,7 @@ def kamaki_menu(session): if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): continue if len(answer) == 0 and "account" in session: - del session["account"] + del session["account"] else: token = answer.strip() session['account'] = Kamaki.get_account(token) diff --git a/image_creator/dialog_util.py b/image_creator/dialog_util.py index 046ee3e..6c3b7ce 100644 --- a/image_creator/dialog_util.py +++ b/image_creator/dialog_util.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python - +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -33,6 +33,10 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""Module providing useful functions for the dialog-based version of +snf-image-creator. +""" + import os from image_creator.output.dialog import GaugeOutput from image_creator.util import MD5 diff --git a/image_creator/dialog_wizard.py b/image_creator/dialog_wizard.py index 79d18db..20fe45a 100644 --- a/image_creator/dialog_wizard.py +++ b/image_creator/dialog_wizard.py @@ -1,5 +1,5 @@ -#!/usr/bin/env python - +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -33,6 +33,10 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module implements the "wizard" mode of the dialog-based version of +snf-image-creator. +""" + import time import StringIO diff --git a/image_creator/disk.py b/image_creator/disk.py index 38372c5..76a53aa 100644 --- a/image_creator/disk.py +++ b/image_creator/disk.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""Module hosting the Disk class.""" + from image_creator.util import get_command from image_creator.util import try_fail_repeat from image_creator.util import free_space @@ -50,7 +54,26 @@ losetup = get_command('losetup') blockdev = get_command('blockdev') -TMP_CANDIDATES = ['/var/tmp', os.path.expanduser('~'), '/mnt'] +def get_tmp_dir(default=None): + """Check tmp directory candidates and return the one with the most + available space. + """ + if default is not None: + return default + + TMP_CANDIDATES = ['/var/tmp', os.path.expanduser('~'), '/mnt'] + + space = map(free_space, TMP_CANDIDATES) + + max_idx = 0 + max_val = space[0] + for i, val in zip(range(len(space)), space): + if val > max_val: + max_val = val + max_idx = i + + # Return the candidate path with more available space + return TMP_CANDIDATES[max_idx] class Disk(object): @@ -71,29 +94,10 @@ class Disk(object): self.out = output self.meta = {} self.tmp = tempfile.mkdtemp(prefix='.snf_image_creator.', - dir=self._get_tmp_dir(tmp)) + dir=get_tmp_dir(tmp)) self._add_cleanup(shutil.rmtree, self.tmp) - def _get_tmp_dir(self, default=None): - """Check tmp directory candidates and return the one with the most - available space. - """ - if default is not None: - return default - - space = map(free_space, TMP_CANDIDATES) - - max_idx = 0 - max_val = space[0] - for i, val in zip(range(len(space)), space): - if val > max_val: - max_val = val - max_idx = i - - # Return the candidate path with more available space - return TMP_CANDIDATES[max_idx] - def _add_cleanup(self, job, *args): """Add a new job in the cleanup list""" self._cleanup_jobs.append((job, args)) diff --git a/image_creator/gpt.py b/image_creator/gpt.py index 968a918..82ae09e 100644 --- a/image_creator/gpt.py +++ b/image_creator/gpt.py @@ -1,3 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +34,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module provides the code for handling GUID partition tables""" + import struct import sys import uuid diff --git a/image_creator/help/__init__.py b/image_creator/help/__init__.py index faaf697..7f78d35 100644 --- a/image_creator/help/__init__.py +++ b/image_creator/help/__init__.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,11 +33,14 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This package hosts the help files of the programe.""" + import sys import os def get_help_file(name): + """Returns the full path of a helpfile""" dirname = os.path.dirname(sys.modules[__name__].__file__) return "%s%s%s.rst" % (dirname, os.sep, name) diff --git a/image_creator/image.py b/image_creator/image.py index 1ad915d..b27c1eb 100644 --- a/image_creator/image.py +++ b/image_creator/image.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2013 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or diff --git a/image_creator/kamaki_wrapper.py b/image_creator/kamaki_wrapper.py index e2012d2..7788cc3 100644 --- a/image_creator/kamaki_wrapper.py +++ b/image_creator/kamaki_wrapper.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,11 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This modules provides the interface for working with the ./kamaki library. +The library is used to upload images to and register them with a Synnefo +deployment. +""" + from os.path import basename from kamaki.cli.config import Config @@ -41,7 +48,7 @@ from kamaki.clients.astakos import AstakosClient class Kamaki(object): - + """Wrapper class for the ./kamaki library""" CONTAINER = "images" @staticmethod diff --git a/image_creator/main.py b/image_creator/main.py index a32dbd0..b78bca7 100644 --- a/image_creator/main.py +++ b/image_creator/main.py @@ -1,5 +1,6 @@ #!/usr/bin/env python - +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -33,6 +34,10 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module is the entrance point for the non-interactive version of the +snf-image-creator program. +""" + from image_creator import __version__ as version from image_creator.disk import Disk from image_creator.util import FatalError, MD5 diff --git a/image_creator/os_type/__init__.py b/image_creator/os_type/__init__.py index d093172..ec4f20d 100644 --- a/image_creator/os_type/__init__.py +++ b/image_creator/os_type/__init__.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,10 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This package provides various classes for preparing different Operating +Systems for image creation. +""" + from image_creator.util import FatalError import textwrap @@ -81,7 +87,6 @@ class OSBase(object): def collect_metadata(self): """Collect metadata about the OS""" - try: if not self.mount(readonly=True): raise FatalError("Unable to mount the media read-only") @@ -92,26 +97,17 @@ class OSBase(object): finally: self.umount() - def _do_collect_metadata(self): - - self.meta['ROOT_PARTITION'] = "%d" % self.g.part_to_partnum(self.root) - self.meta['OSFAMILY'] = self.g.inspect_get_type(self.root) - self.meta['OS'] = self.g.inspect_get_distro(self.root) - if self.meta['OS'] == "unknown": - self.meta['OS'] = self.meta['OSFAMILY'] - self.meta['DESCRIPTION'] = self.g.inspect_get_product_name(self.root) - - def _is_sysprep(self, obj): - return getattr(obj, 'sysprep', False) and callable(obj) + self.out.output() def list_syspreps(self): - + """Returns a list of sysprep objects""" objs = [getattr(self, name) for name in dir(self) if not name.startswith('_')] return [x for x in objs if self._is_sysprep(x) and x.executed is False] def sysprep_info(self, obj): + """Returns information about a sysprep object""" assert self._is_sysprep(obj), "Object is not a sysprep" return (obj.__name__.replace('_', '-'), textwrap.dedent(obj.__doc__)) @@ -171,17 +167,69 @@ class OSBase(object): descr = wrapper.fill(textwrap.dedent(sysprep.__doc__)) self.out.output(' %s:\n%s\n' % (name, descr)) + def do_sysprep(self): + """Prepare system for image creation.""" + + try: + if not self.mount(readonly=False): + raise FatalError("Unable to mount the media read-write") + + self.out.output('Preparing system for image creation:') + + tasks = self.list_syspreps() + enabled = filter(lambda x: x.enabled, tasks) + + size = len(enabled) + cnt = 0 + for task in enabled: + cnt += 1 + self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False) + task() + setattr(task.im_func, 'executed', True) + finally: + self.umount() + + self.out.output() + + def mount(self, readonly=False): + """Mount image.""" + + if getattr(self, "mounted", False): + return True + + mount_type = 'read-only' if readonly else 'read-write' + self.out.output("Mounting the media %s ..." % mount_type, False) + + if not self._do_mount(readonly): + return False + + self.mounted = True + self.out.success('done') + return True + + def umount(self): + """Umount all mounted filesystems.""" + + self.out.output("Umounting the media ...", False) + self.g.umount_all() + self.mounted = False + self.out.success('done') + + def _is_sysprep(self, obj): + """Checks if an object is a sysprep""" + return getattr(obj, 'sysprep', False) and callable(obj) + @add_prefix - def ls(self, directory): + def _ls(self, directory): """List the name of all files under a directory""" return self.g.ls(directory) @add_prefix - def find(self, directory): + def _find(self, directory): """List the name of all files recursively under a directory""" return self.g.find(directory) - def foreach_file(self, directory, action, **kargs): + def _foreach_file(self, directory, action, **kargs): """Perform an action recursively on all files under a directory. The following options are allowed: @@ -218,35 +266,22 @@ class OSBase(object): continue if has_ftype(f, 'd'): - self.foreach_file(full_path, action, **kargs) + self._foreach_file(full_path, action, **kargs) if has_ftype(f, ftype): action(full_path) - def do_sysprep(self): - """Prepere system for image creation.""" - - try: - if not self.mount(readonly=False): - raise FatalError("Unable to mount the media read-write") - - self.out.output('Preparing system for image creation:') - - tasks = self.list_syspreps() - enabled = filter(lambda x: x.enabled, tasks) - - size = len(enabled) - cnt = 0 - for task in enabled: - cnt += 1 - self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False) - task() - setattr(task.im_func, 'executed', True) - self.out.output() - finally: - self.umount() + def _do_collect_metadata(self): + """helper method for collect_metadata""" + self.meta['ROOT_PARTITION'] = "%d" % self.g.part_to_partnum(self.root) + self.meta['OSFAMILY'] = self.g.inspect_get_type(self.root) + self.meta['OS'] = self.g.inspect_get_distro(self.root) + if self.meta['OS'] == "unknown": + self.meta['OS'] = self.meta['OSFAMILY'] + self.meta['DESCRIPTION'] = self.g.inspect_get_product_name(self.root) def _do_mount(self, readonly): + """helper method for mount""" try: self.g.mount_options('ro' if readonly else 'rw', self.root, '/') except RuntimeError as msg: @@ -255,25 +290,4 @@ class OSBase(object): return True - def mount(self, readonly=False): - """Mount image.""" - - if getattr(self, "mounted", False): - return True - - mount_type = 'read-only' if readonly else 'read-write' - self.out.output("Mount the media %s ..." % mount_type, False) - - if not self._do_mount(readonly): - return False - - self.mounted = True - self.out.success('done') - return True - - def umount(self): - """Umount all mounted filesystems.""" - self.g.umount_all() - self.mounted = False - # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/os_type/freebsd.py b/image_creator/os_type/freebsd.py index 28d58bd..02bd8dc 100644 --- a/image_creator/os_type/freebsd.py +++ b/image_creator/os_type/freebsd.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module hosts OS-specific code for FreeBSD.""" + from image_creator.os_type.unix import Unix, sysprep import re @@ -41,8 +45,36 @@ class Freebsd(Unix): def __init__(self, rootdev, ghandler, output): super(Freebsd, self).__init__(rootdev, ghandler, output) - def _do_collect_metadata(self): + @sysprep() + def cleanup_password(self, print_header=True): + """Remove all passwords and lock all user accounts""" + + if print_header: + self.out.output("Cleaning up passwords & locking all user " + "accounts") + + master_passwd = [] + + for line in self.g.cat('/etc/master.passwd').splitlines(): + + # Check for empty or comment lines + if len(line.split('#')[0]) == 0: + master_passwd.append(line) + continue + + fields = line.split(':') + if fields[1] not in ('*', '!'): + fields[1] = '!' + + master_passwd.append(":".join(fields)) + + self.g.write('/etc/master.passwd', "\n".join(master_passwd) + '\n') + + # Make sure no one can login on the system + self.g.rm_rf('/etc/spwd.db') + def _do_collect_metadata(self): + """Collect metadata about the OS""" super(Freebsd, self)._do_collect_metadata() self.meta["USERS"] = " ".join(self._get_passworded_users()) @@ -56,6 +88,7 @@ class Freebsd(Unix): del self.meta['USERS'] def _get_passworded_users(self): + """Returns a list of non-locked user accounts""" users = [] regexp = re.compile( '^([^:]+):((?:![^:]+)|(?:[^!*][^:]+)|):(?:[^:]*:){7}(?:[^:]*)' @@ -80,18 +113,18 @@ class Freebsd(Unix): critical_mpoints = ('/', '/etc', '/root', '/home', '/var') - # libguestfs can't handle correct freebsd partitions on GUID - # Partition Table. We have to do the translation to linux - # device names ourselves + # libguestfs can't handle correct freebsd partitions on a GUID + # Partition Table. We have to do the translation to linux device names + # ourselves guid_device = re.compile('^/dev/((?:ada)|(?:vtbd))(\d+)p(\d+)$') mopts = "ufstype=ufs2,%s" % ('ro' if readonly else 'rw') for mp, dev in self._mountpoints(): match = guid_device.match(dev) if match: - m2 = int(match.group(2)) - m3 = int(match.group(3)) - dev = '/dev/sd%c%d' % (chr(ord('a') + m2), m3) + group2 = int(match.group(2)) + group3 = int(match.group(3)) + dev = '/dev/sd%c%d' % (chr(ord('a') + group2), group3) try: self.g.mount_vfs(mopts, 'ufs', dev, mp) except RuntimeError as msg: @@ -103,32 +136,4 @@ class Freebsd(Unix): return True - @sysprep() - def cleanup_password(self, print_header=True): - """Remove all passwords and lock all user accounts""" - - if print_header: - self.out.output("Cleaning up passwords & locking all user " - "accounts") - - master_passwd = [] - - for line in self.g.cat('/etc/master.passwd').splitlines(): - - # Check for empty or comment lines - if len(line.split('#')[0]) == 0: - master_passwd.append(line) - continue - - fields = line.split(':') - if fields[1] not in ('*', '!'): - fields[1] = '!' - - master_passwd.append(":".join(fields)) - - self.g.write('/etc/master.passwd', "\n".join(master_passwd) + '\n') - - # Make sure no one can login on the system - self.g.rm_rf('/etc/spwd.db') - # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/os_type/hurd.py b/image_creator/os_type/hurd.py index 64c4a67..2f3e82c 100644 --- a/image_creator/os_type/hurd.py +++ b/image_creator/os_type/hurd.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module hosts OS-specific code for GNU Hurd.""" + from image_creator.os_type.unix import Unix diff --git a/image_creator/os_type/linux.py b/image_creator/os_type/linux.py index 5a3d29d..4724336 100644 --- a/image_creator/os_type/linux.py +++ b/image_creator/os_type/linux.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module hosts OS-specific code for Linux""" + from image_creator.os_type.unix import Unix, sysprep import re @@ -44,46 +48,6 @@ class Linux(Unix): self._uuid = dict() self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*') - def _do_collect_metadata(self): - """Collect metadata about the OS""" - - super(Linux, self)._do_collect_metadata() - self.meta["USERS"] = " ".join(self._get_passworded_users()) - - # Delete the USERS metadata if empty - if not len(self.meta['USERS']): - self.out.warn("No passworded users found!") - del self.meta['USERS'] - - def _get_passworded_users(self): - users = [] - regexp = re.compile('(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}') - - for line in self.g.cat('/etc/shadow').splitlines(): - match = regexp.match(line) - if not match: - continue - - user, passwd = match.groups() - if len(passwd) > 0 and passwd[0] == '!': - self.out.warn("Ignoring locked %s account." % user) - else: - users.append(user) - - return users - - def is_persistent(self, dev): - return not self._persistent.match(dev) - - def get_uuid(self, dev): - if dev in self._uuid: - return self._uuid[dev] - - uuid = self.g.vfs_uuid(dev) - assert len(uuid) - self._uuid[dev] = uuid - return uuid - @sysprep(enabled=False) def remove_user_accounts(self, print_header=True): """Remove all user accounts with id greater than 1000""" @@ -179,21 +143,21 @@ class Linux(Unix): event_exp = re.compile('event=(.+)', re.I) action_exp = re.compile('action=(.+)', re.I) - for f in self.g.readdir(events_dir): - if f['ftyp'] != 'r': + for events_file in self.g.readdir(events_dir): + if events_file['ftyp'] != 'r': continue - fullpath = "%s/%s" % (events_dir, f['name']) + fullpath = "%s/%s" % (events_dir, events_file['name']) event = "" action = "" for line in self.g.cat(fullpath).splitlines(): - m = event_exp.match(line) - if m: - event = m.group(1) + match = event_exp.match(line) + if match: + event = match.group(1) continue - m = action_exp.match(line) - if m: - action = m.group(1) + match = action_exp.match(line) + if match: + action = match.group(1) continue if event.strip() in ("button[ /]power", "button/power.*"): @@ -275,6 +239,9 @@ class Linux(Unix): self._persistent_grub1(persistent_root) def _persistent_grub1(self, new_root): + """Replaces non-persistent device name occurencies with persistent + ones in GRUB1 configuration files. + """ if self.g.is_file('/boot/grub/menu.lst'): grub1 = '/boot/grub/menu.lst' elif self.g.is_file('/etc/grub.conf'): @@ -287,7 +254,7 @@ class Linux(Unix): roots = self.g.aug_match('/files%s/title[*]/kernel/root' % grub1) for root in roots: dev = self.g.aug_get(root) - if not self.is_persistent(dev): + if not self._is_persistent(dev): # This is not always correct. Grub may contain root entries # for other systems, but we only support 1 OS per hard # disk, so this shouldn't harm. @@ -297,6 +264,9 @@ class Linux(Unix): self.g.aug_close() def _persistent_fstab(self): + """Replaces non-persistent device name occurencies in /etc/fstab with + persistent ones. + """ mpoints = self.g.mountpoints() if len(mpoints) == 0: pass # TODO: error handling @@ -321,6 +291,9 @@ class Linux(Unix): return root_dev def _convert_fstab_line(self, line, devices): + """Replace non-persistent device names in an fstab line to their UUID + equivalent + """ orig = line line = line.split('#')[0].strip() if len(line) == 0: @@ -334,9 +307,9 @@ class Linux(Unix): dev = entry[0] mpoint = entry[1] - if not self.is_persistent(dev): + if not self._is_persistent(dev): if mpoint in devices: - dev = "UUID=%s" % self.get_uuid(devices[mpoint]) + dev = "UUID=%s" % self._get_uuid(devices[mpoint]) entry[0] = dev else: # comment out the entry @@ -345,4 +318,46 @@ class Linux(Unix): return orig, dev, mpoint + def _do_collect_metadata(self): + """Collect metadata about the OS""" + super(Linux, self)._do_collect_metadata() + self.meta["USERS"] = " ".join(self._get_passworded_users()) + + # Delete the USERS metadata if empty + if not len(self.meta['USERS']): + self.out.warn("No passworded users found!") + del self.meta['USERS'] + + def _get_passworded_users(self): + """Returns a list of non-locked user accounts""" + users = [] + regexp = re.compile('(\S+):((?:!\S+)|(?:[^!*]\S+)|):(?:\S*:){6}') + + for line in self.g.cat('/etc/shadow').splitlines(): + match = regexp.match(line) + if not match: + continue + + user, passwd = match.groups() + if len(passwd) > 0 and passwd[0] == '!': + self.out.warn("Ignoring locked %s account." % user) + else: + users.append(user) + + return users + + def _is_persistent(self, dev): + """Checks if a device name is persistent.""" + return not self._persistent.match(dev) + + def _get_uuid(self, dev): + """Returns the UUID corresponding to a device""" + if dev in self._uuid: + return self._uuid[dev] + + uuid = self.g.vfs_uuid(dev) + assert len(uuid) + self._uuid[dev] = uuid + return uuid + # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/os_type/netbsd.py b/image_creator/os_type/netbsd.py index da5443f..f8c7863 100644 --- a/image_creator/os_type/netbsd.py +++ b/image_creator/os_type/netbsd.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module hosts OS-specific code for NetBSD.""" + from image_creator.os_type.unix import Unix diff --git a/image_creator/os_type/slackware.py b/image_creator/os_type/slackware.py index 5b0b571..e8f5a9e 100644 --- a/image_creator/os_type/slackware.py +++ b/image_creator/os_type/slackware.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module hosts OS-specific code for Slackware Linux""" + from image_creator.os_type.linux import Linux, sysprep @@ -46,7 +50,7 @@ class Slackware(Linux): # In slackware the metadata about installed packages are # stored in /var/log/packages. Clearing all /var/log files # will destroy the package management system. - self.foreach_file('/var/log', self.g.truncate, ftype='r', - exclude='/var/log/packages') + self._foreach_file('/var/log', self.g.truncate, ftype='r', + exclude='/var/log/packages') # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/os_type/ubuntu.py b/image_creator/os_type/ubuntu.py index 8bfe04d..b5706fc 100644 --- a/image_creator/os_type/ubuntu.py +++ b/image_creator/os_type/ubuntu.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module hosts OS-specific code for Ubuntu Linux""" + from image_creator.os_type.linux import Linux diff --git a/image_creator/os_type/unix.py b/image_creator/os_type/unix.py index e368231..8b88b2f 100644 --- a/image_creator/os_type/unix.py +++ b/image_creator/os_type/unix.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module hosts OS-specific code common to all Unix-like OSs.""" + import re from image_creator.os_type import OSBase, sysprep @@ -75,7 +79,7 @@ class Unix(OSBase): try: self.g.mount_options(mopts, dev, mp) except RuntimeError as msg: - if mp in critical_mpoint: + if mp in critical_mpoints: self.out.warn('unable to mount %s. Reason: %s' % (mp, msg)) return False else: @@ -90,7 +94,7 @@ class Unix(OSBase): if print_header: self.out.output('Removing files under /var/cache') - self.foreach_file('/var/cache', self.g.rm, ftype='r') + self._foreach_file('/var/cache', self.g.rm, ftype='r') @sysprep() def cleanup_tmp(self, print_header=True): @@ -99,8 +103,8 @@ class Unix(OSBase): if print_header: 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) + self._foreach_file('/tmp', self.g.rm_rf, maxdepth=1) + self._foreach_file('/var/tmp', self.g.rm_rf, maxdepth=1) @sysprep() def cleanup_log(self, print_header=True): @@ -109,7 +113,7 @@ class Unix(OSBase): if print_header: self.out.output('Emptying all files under /var/log') - self.foreach_file('/var/log', self.g.truncate, ftype='r') + self._foreach_file('/var/log', self.g.truncate, ftype='r') @sysprep(enabled=False) def cleanup_mail(self, print_header=True): @@ -119,9 +123,9 @@ class Unix(OSBase): self.out.output('Removing files under /var/mail & /var/spool/mail') if self.g.is_dir('/var/spool/mail'): - self.foreach_file('/var/spool/mail', self.g.rm_rf, maxdepth=1) + self._foreach_file('/var/spool/mail', self.g.rm_rf, maxdepth=1) - self.foreach_file('/var/mail', self.g.rm_rf, maxdepth=1) + self._foreach_file('/var/mail', self.g.rm_rf, maxdepth=1) @sysprep() def cleanup_userdata(self, print_header=True): @@ -129,7 +133,7 @@ class Unix(OSBase): homedirs = ['/root'] if self.g.is_dir('/home/'): - homedirs += self.ls('/home/') + homedirs += self._ls('/home/') if print_header: self.out.output("Removing sensitive user data under %s" % @@ -141,6 +145,6 @@ class Unix(OSBase): if self.g.is_file(fname): self.g.scrub_file(fname) elif self.g.is_dir(fname): - self.foreach_file(fname, self.g.scrub_file, ftype='r') + self._foreach_file(fname, self.g.scrub_file, ftype='r') # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/os_type/windows.py b/image_creator/os_type/windows.py index 6ba200e..9fb8218 100644 --- a/image_creator/os_type/windows.py +++ b/image_creator/os_type/windows.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,9 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module hosts OS-specific code common for the various Microsoft +Windows OSs.""" + from image_creator.os_type import OSBase import hivex @@ -46,6 +51,7 @@ class Windows(OSBase): self.meta["USERS"] = " ".join(self._get_users()) def _get_users(self): + """Returns a list of users found in the images""" samfd, sam = tempfile.mkstemp() try: systemroot = self.g.inspect_get_windows_systemroot(self.root) diff --git a/image_creator/output/__init__.py b/image_creator/output/__init__.py index e421c75..b4aa7c0 100644 --- a/image_creator/output/__init__.py +++ b/image_creator/output/__init__.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,12 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This package is intended to provide output classes for printing messages and +progress bars. The user can change the output behaviour of the program by +subclassing the Output class and assigning the derived one as the output class +of the various parts of the image-creator package. +""" + class Output(object): """A class for printing program output""" diff --git a/image_creator/output/cli.py b/image_creator/output/cli.py index f88fdbf..46c84c9 100644 --- a/image_creator/output/cli.py +++ b/image_creator/output/cli.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -61,7 +63,7 @@ def success(msg, new_line, colored, stream): def clear(stream): - #clear the page + """Clears the terminal screen.""" if stream.isatty(): stream.write('\033[H\033[2J') diff --git a/image_creator/output/composite.py b/image_creator/output/composite.py index a9502ec..5228504 100644 --- a/image_creator/output/composite.py +++ b/image_creator/output/composite.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module implements the CompositeOutput output class""" + from image_creator.output import Output diff --git a/image_creator/output/dialog.py b/image_creator/output/dialog.py index 2790b1c..f2b10e8 100644 --- a/image_creator/output/dialog.py +++ b/image_creator/output/dialog.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module provides various dialog-based Output classes""" + from image_creator.output import Output import time import fcntl diff --git a/image_creator/rsync.py b/image_creator/rsync.py index 08bc27a..833d0be 100644 --- a/image_creator/rsync.py +++ b/image_creator/rsync.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,8 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module provides an interface for the rsync utility""" + import subprocess import time import signal @@ -98,7 +102,7 @@ class Rsync: stdout=subprocess.PIPE, bufsize=0) try: total = 0 - for line in iter(dry_run.stdout.readline, b''): + for _ in iter(dry_run.stdout.readline, b''): total += 1 finally: dry_run.communicate() @@ -113,7 +117,7 @@ class Rsync: try: t = time.time() i = 0 - for line in iter(run.stdout.readline, b''): + for _ in iter(run.stdout.readline, b''): i += 1 current = time.time() if current - t > 0.1: diff --git a/image_creator/util.py b/image_creator/util.py index 61a97e9..fb0d0fa 100644 --- a/image_creator/util.py +++ b/image_creator/util.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or @@ -31,6 +33,10 @@ # interpreted as representing official policies, either expressed # or implied, of GRNET S.A. +"""This module provides various helper functions to be used by other parts of +the package. +""" + import sh import hashlib import time diff --git a/setup.py b/setup.py index a524373..3c67366 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,6 @@ #!/usr/bin/env python - +# -*- coding: utf-8 -*- +# # Copyright 2012 GRNET S.A. All rights reserved. # # Redistribution and use in source and binary forms, with or