Code Cleanup
authorNikos Skalkotos <skalkoto@grnet.gr>
Mon, 10 Jun 2013 07:33:35 +0000 (10:33 +0300)
committerNikos Skalkotos <skalkoto@grnet.gr>
Tue, 11 Jun 2013 14:41:32 +0000 (17:41 +0300)
 * 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

28 files changed:
image_creator/__init__.py
image_creator/bundle_volume.py
image_creator/dialog_main.py
image_creator/dialog_menu.py
image_creator/dialog_util.py
image_creator/dialog_wizard.py
image_creator/disk.py
image_creator/gpt.py
image_creator/help/__init__.py
image_creator/image.py
image_creator/kamaki_wrapper.py
image_creator/main.py
image_creator/os_type/__init__.py
image_creator/os_type/freebsd.py
image_creator/os_type/hurd.py
image_creator/os_type/linux.py
image_creator/os_type/netbsd.py
image_creator/os_type/slackware.py
image_creator/os_type/ubuntu.py
image_creator/os_type/unix.py
image_creator/os_type/windows.py
image_creator/output/__init__.py
image_creator/output/cli.py
image_creator/output/composite.py
image_creator/output/dialog.py
image_creator/rsync.py
image_creator/util.py
setup.py

index f564b96..06d6ecc 100644 (file)
@@ -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
 # 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 :
index 18d0245..eec084f 100644 (file)
@@ -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
 # 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
index 024c37f..d8e86b1 100644 (file)
@@ -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
 # 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
index 448060b..5b5526c 100644 (file)
@@ -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
 # 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)
index 046ee3e..6c3b7ce 100644 (file)
@@ -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
 # 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
index 79d18db..20fe45a 100644 (file)
@@ -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
 # 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
 
index 38372c5..76a53aa 100644 (file)
@@ -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))
index 968a918..82ae09e 100644 (file)
@@ -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
index faaf697..7f78d35 100644 (file)
@@ -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
 # 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)
 
index 1ad915d..b27c1eb 100644 (file)
@@ -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
index e2012d2..7788cc3 100644 (file)
@@ -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
 # 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
index a32dbd0..b78bca7 100644 (file)
@@ -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
 # 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
index d093172..ec4f20d 100644 (file)
@@ -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
 # 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 :
index 28d58bd..02bd8dc 100644 (file)
@@ -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 :
index 64c4a67..2f3e82c 100644 (file)
@@ -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
 
 
index 5a3d29d..4724336 100644 (file)
@@ -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 :
index da5443f..f8c7863 100644 (file)
@@ -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
 
 
index 5b0b571..e8f5a9e 100644 (file)
@@ -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 :
index 8bfe04d..b5706fc 100644 (file)
@@ -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
 
 
index e368231..8b88b2f 100644 (file)
@@ -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 :
index 6ba200e..9fb8218 100644 (file)
@@ -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)
index e421c75..b4aa7c0 100644 (file)
@@ -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
 # 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"""
index f88fdbf..46c84c9 100644 (file)
@@ -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')
 
index a9502ec..5228504 100644 (file)
@@ -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
 
 
index 2790b1c..f2b10e8 100644 (file)
@@ -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
index 08bc27a..833d0be 100644 (file)
@@ -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:
index 61a97e9..fb0d0fa 100644 (file)
@@ -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
 # 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
index a524373..3c67366 100755 (executable)
--- 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