From: Nikos Skalkotos Date: Thu, 28 Mar 2013 12:26:27 +0000 (+0200) Subject: Add missing docstrings X-Git-Tag: v0.2.8~2 X-Git-Url: https://code.grnet.gr/git/snf-image-creator/commitdiff_plain/88f83027df65608a70e1a88f96b4a3055427dd52 Add missing docstrings --- diff --git a/image_creator/bundle_volume.py b/image_creator/bundle_volume.py index aca14b3..c4cc83c 100644 --- a/image_creator/bundle_volume.py +++ b/image_creator/bundle_volume.py @@ -92,6 +92,7 @@ class BundleVolume(object): self.disk = parted.Disk(device) def _read_fstable(self, f): + """Use this generator to iterate over the lines of and fstab file""" if not os.path.isfile(f): raise FatalError("Unable to open: `%s'. File is missing." % f) @@ -106,6 +107,7 @@ class BundleVolume(object): yield FileSystemTableEntry(*entry) def _get_root_partition(self): + """Return the fstab entry accosiated with the root filesystem""" for entry in self._read_fstable('/etc/fstab'): if entry.mpoint == '/': return entry.dev @@ -113,12 +115,14 @@ class BundleVolume(object): raise FatalError("Unable to find root device in /etc/fstab") def _is_mpoint(self, path): + """Check if a directory is currently a mount point""" for entry in self._read_fstable('/proc/mounts'): if entry.mpoint == path: return True return False def _get_mount_options(self, device): + """Return the mount entry associated with a mounted device""" for entry in self._read_fstable('/proc/mounts'): if not entry.dev.startswith('/'): continue @@ -129,6 +133,7 @@ class BundleVolume(object): return None def _create_partition_table(self, image): + """Copy the partition table of the host system into the image""" # Copy the MBR and the space between the MBR and the first partition. # In msdos partition tables Grub Stage 1.5 is located there. @@ -163,6 +168,7 @@ class BundleVolume(object): start = logical[i].geometry.end + 1 def _get_partitions(self, disk): + """Returns a list with the partitions of the provided disk""" Partition = namedtuple('Partition', 'num start end type fs') partitions = [] @@ -177,7 +183,10 @@ class BundleVolume(object): return partitions def _shrink_partitions(self, image): - + """Remove the last partition of the image if it is a swap partition and + shrink the partition before that. Make sure it can still host all the + files the corresponding host file system hosts + """ new_end = self.disk.device.length image_disk = parted.Disk(parted.Device(image)) @@ -246,6 +255,7 @@ class BundleVolume(object): return (new_end, self._get_partitions(image_disk)) def _map_partition(self, dev, num, start, end): + """Map a partition into a block device using the device mapper""" name = os.path.basename(dev) + "_" + uuid.uuid4().hex tablefd, table = tempfile.mkstemp() try: @@ -258,13 +268,14 @@ class BundleVolume(object): return "/dev/mapper/%sp%d" % (name, num) def _unmap_partition(self, dev): + """Unmap a previously mapped partition""" if not os.path.exists(dev): return try_fail_repeat(dmsetup, 'remove', dev.split('/dev/mapper/')[1]) def _mount(self, target, devs): - + """Mount a list of filesystems in mountpoints relative to target""" devs.sort(key=lambda d: d[1]) for dev, mpoint in devs: absmpoint = os.path.abspath(target + mpoint) @@ -273,6 +284,8 @@ class BundleVolume(object): mount(dev, absmpoint) def _umount_all(self, target): + """Unmount all filesystems that are mounted under the directory target + """ mpoints = [] for entry in self._read_fstable('/proc/mounts'): if entry.mpoint.startswith(os.path.abspath(target)): @@ -283,6 +296,10 @@ class BundleVolume(object): try_fail_repeat(umount, mpoint) def _to_exclude(self): + """Find which directories to exclude during the image copy. This is + accompliced by checking which directories serve as mount points for + virtual file systems + """ excluded = ['/tmp', '/var/tmp'] if self.tmp is not None: excluded.append(self.tmp) @@ -318,6 +335,9 @@ class BundleVolume(object): return excluded def _replace_uuids(self, target, new_uuid): + """Replace UUID references in various files. This is needed after + copying system files of the host into a new filesystem + """ files = ['/etc/fstab', '/boot/grub/grub.cfg', @@ -343,6 +363,10 @@ class BundleVolume(object): dest.write(line) def _create_filesystems(self, image, partitions): + """Fill the image with data. Host file systems that are not currently + mounted are binary copied into the image. For mounted file systems, a + file system level copy is performed. + """ filesystem = {} for p in self.disk.partitions: diff --git a/image_creator/dialog_main.py b/image_creator/dialog_main.py index 205ec8c..d2c9182 100644 --- a/image_creator/dialog_main.py +++ b/image_creator/dialog_main.py @@ -48,14 +48,14 @@ from image_creator.output.cli import SimpleOutput from image_creator.output.dialog import GaugeOutput from image_creator.output.composite import CompositeOutput from image_creator.disk import Disk -from image_creator.dialog_wizard import wizard +from image_creator.dialog_wizard import start_wizard from image_creator.dialog_menu import main_menu from image_creator.dialog_util import SMALL_WIDTH, WIDTH, confirm_exit, \ Reset, update_background_title def create_image(d, media, out, tmp): - + """Create an image out of `media'""" d.setBackgroundTitle('snf-image-creator') gauge = GaugeOutput(d, "Initialization", "Initializing...") @@ -108,7 +108,7 @@ def create_image(d, media, out, tmp): code = d.yesno(msg, width=WIDTH, height=12, yes_label="Wizard", no_label="Expert") if code == d.DIALOG_OK: - if wizard(session): + if start_wizard(session): break elif code == d.DIALOG_CANCEL: main_menu(session) @@ -125,7 +125,7 @@ def create_image(d, media, out, tmp): def select_file(d, media): - + """Select a media file""" if media == '/': return '/' diff --git a/image_creator/dialog_menu.py b/image_creator/dialog_menu.py index 3524969..0e32167 100644 --- a/image_creator/dialog_menu.py +++ b/image_creator/dialog_menu.py @@ -65,6 +65,7 @@ CONFIGURATION_TASKS = [ class MetadataMonitor(object): + """Monitors image metadata chages""" def __init__(self, session, meta): self.session = session self.meta = meta @@ -107,6 +108,7 @@ class MetadataMonitor(object): def upload_image(session): + """Upload the image to pithos+""" d = session["dialog"] image = session['image'] meta = session['metadata'] @@ -187,6 +189,7 @@ def upload_image(session): def register_image(session): + """Register image with cyclades""" d = session["dialog"] is_public = False @@ -254,6 +257,7 @@ def register_image(session): def kamaki_menu(session): + """Show kamaki related actions""" d = session['dialog'] default_item = "Account" @@ -316,6 +320,7 @@ def kamaki_menu(session): def add_property(session): + """Add a new property to the image""" d = session['dialog'] while 1: @@ -350,6 +355,7 @@ def add_property(session): def modify_properties(session): + """Modify an existing image property""" d = session['dialog'] while 1: @@ -392,6 +398,7 @@ def modify_properties(session): def delete_properties(session): + """Delete an image property""" d = session['dialog'] choices = [] @@ -414,6 +421,7 @@ def delete_properties(session): def exclude_tasks(session): + """Exclude specific tasks from running during image deployment""" d = session['dialog'] index = 0 @@ -478,6 +486,7 @@ def exclude_tasks(session): def sysprep(session): + """Perform various system preperation tasks on the image""" d = session['dialog'] image = session['image'] @@ -568,6 +577,7 @@ def sysprep(session): def shrink(session): + """Shrink the image""" d = session['dialog'] image = session['image'] @@ -604,6 +614,7 @@ def shrink(session): def customization_menu(session): + """Show image customization menu""" d = session['dialog'] choices = [("Sysprep", "Run various image preparation tasks"), @@ -635,6 +646,7 @@ def customization_menu(session): def main_menu(session): + """Show the main menu of the program""" d = session['dialog'] update_background_title(session) diff --git a/image_creator/dialog_util.py b/image_creator/dialog_util.py index f7add02..e18fb89 100644 --- a/image_creator/dialog_util.py +++ b/image_creator/dialog_util.py @@ -42,6 +42,7 @@ WIDTH = 70 def update_background_title(session): + """Update the backgroud title of the dialog page""" d = session['dialog'] disk = session['disk'] image = session['image'] @@ -60,19 +61,23 @@ def update_background_title(session): def confirm_exit(d, msg=''): + """Ask the user to confirm when exiting the program""" return not d.yesno("%s Do you want to exit?" % msg, width=SMALL_WIDTH) def confirm_reset(d): + """Ask the user to confirm a reset action""" return not d.yesno("Are you sure you want to reset everything?", width=SMALL_WIDTH, defaultno=1) class Reset(Exception): + """Exception used to reset the program""" pass def extract_metadata_string(session): + """Convert image metadata to text""" metadata = ['%s=%s' % (k, v) for (k, v) in session['metadata'].items()] if 'task_metadata' in session: @@ -82,6 +87,7 @@ def extract_metadata_string(session): def extract_image(session): + """Dump the image to a local file""" d = session['dialog'] dir = os.getcwd() while 1: diff --git a/image_creator/dialog_wizard.py b/image_creator/dialog_wizard.py index e1719cc..5605cc7 100644 --- a/image_creator/dialog_wizard.py +++ b/image_creator/dialog_wizard.py @@ -45,14 +45,22 @@ PAGE_WIDTH = 70 class WizardExit(Exception): + """Exception used to exit the wizard""" pass class WizardInvalidData(Exception): + """Exception triggered when the user provided data are invalid""" pass class Wizard: + """Represents a dialog-based wizard + + The wizard is a collection of pages that have a "Next" and a "Back" button + on them. The pages are used to collect user data. + """ + def __init__(self, session): self.session = session self.pages = [] @@ -60,9 +68,11 @@ class Wizard: self.d = session['dialog'] def add_page(self, page): + """Add a new page to the wizard""" self.pages.append(page) def run(self): + """Run the wizard""" idx = 0 while True: try: @@ -95,6 +105,7 @@ class Wizard: class WizardPage(object): + """Represents a page in a wizard""" NEXT = 1 PREV = -1 @@ -106,11 +117,15 @@ class WizardPage(object): setattr(self, "display", display) def run(self, session, index, total): + """Display this wizard page + + This function is used by the wizard program when accessing a page. + """ raise NotImplementedError class WizardRadioListPage(WizardPage): - + """Represent a Radio List in a wizard""" def __init__(self, name, printable, message, choices, **kargs): super(WizardRadioListPage, self).__init__(**kargs) self.name = name @@ -145,12 +160,13 @@ class WizardRadioListPage(WizardPage): class WizardInputPage(WizardPage): - + """Represents an input field in a wizard""" def __init__(self, name, printable, message, **kargs): super(WizardInputPage, self).__init__(**kargs) self.name = name self.printable = printable self.message = message + self.info = "%s: " % self.printable self.title = kargs['title'] if 'title' in kargs else '' self.init = kargs['init'] if 'init' in kargs else '' @@ -173,7 +189,8 @@ class WizardInputPage(WizardPage): return self.NEXT -def wizard(session): +def start_wizard(session): + """Run the image creation wizard""" init_token = Kamaki.get_token() if init_token is None: init_token = "" @@ -196,6 +213,7 @@ def wizard(session): title="Registration Type", default="Private") def validate_account(token): + """Check if a token is valid""" d = session['dialog'] if len(token) == 0: @@ -231,6 +249,7 @@ def wizard(session): def create_image(session): + """Create an image using the information collected by the wizard""" d = session['dialog'] image = session['image'] wizard = session['wizard'] diff --git a/image_creator/disk.py b/image_creator/disk.py index 0645496..2d6135c 100644 --- a/image_creator/disk.py +++ b/image_creator/disk.py @@ -76,6 +76,9 @@ class Disk(object): 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 @@ -92,15 +95,20 @@ class Disk(object): 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)) def _losetup(self, fname): + """Setup a loop device and add it to the cleanup list. The loop device + will be detached when cleanup is called. + """ loop = losetup('-f', '--show', fname) loop = loop.strip() # remove the new-line char self._add_cleanup(try_fail_repeat, losetup, '-d', loop) return loop def _dir_to_disk(self): + """Create a disk out of a directory""" if self.source == '/': bundle = BundleVolume(self.out, self.meta) image = '%s/%s.diskdump' % (self.tmp, uuid.uuid4().hex) diff --git a/image_creator/gpt.py b/image_creator/gpt.py index 98f7cc8..3be594e 100644 --- a/image_creator/gpt.py +++ b/image_creator/gpt.py @@ -42,9 +42,11 @@ BLOCKSIZE = 512 class MBR(object): """Represents a Master Boot Record.""" class Partition(object): + """Represents a partition entry in MBR""" format = "