diff --git a/docs/man/snf-image-creator.rst b/docs/man/snf-image-creator.rst
index f55652dfebc79a2f5995bc827f64aea1b017dd2f..9d388595c62c9a8f551d8240957d68b6bf52f425 100644
--- a/docs/man/snf-image-creator.rst
+++ b/docs/man/snf-image-creator.rst
@@ -16,6 +16,12 @@ itself.
 
 Options
 -------
+-a URL, --authentication-url=URL
+	use this authentication URL when uploading/registering images
+
+-c CLOUD, --cloud=CLOUD
+        use this saved cloud account to authenticate against a cloud when
+        uploading/registering images
 
 --disable-sysprep=SYSPREP
 	prevent SYSPREP operation from running on the input media
@@ -42,27 +48,26 @@ Options
 	dump image to FILE
 
 --public
-	register image with cyclades as public
+	register image with the storage service as public
 
 --print-sysprep
 	print the enabled and disabled system preparation operations for this
 	input media
 
 -r IMAGENAME, --register=IMAGENAME
-	register the image with cyclades as IMAGENAME
+	register the image with the compute service with name IMAGENAME
 
 -s, --silent
 	output only errors
 
 -t TOKEN, --token=TOKEN
-	use this token when uploading/registering images to a Synnefo
-	deployment
+	use this token when uploading/registering images
 
 --tmpdir=DIR
 	create large temporary image files under DIR
 
 -u FILENAME, --upload=FILENAME
-	upload the image to pithos with name FILENAME
+	save the image to the storage service with remote name FILENAME
 
 --version
 	show program's version number and exit
diff --git a/docs/snapshots/confirm.png b/docs/snapshots/confirm.png
index 2ac9d6b1376544de8ac7827f94a29d31e701fca9..1a8614ba77f78c197dc0564d076f03741d277c96 100644
Binary files a/docs/snapshots/confirm.png and b/docs/snapshots/confirm.png differ
diff --git a/docs/snapshots/main_menu.png b/docs/snapshots/main_menu.png
index 536eec0296b12ec9e54327070c37155e427fbdf2..8dd8f8b89ee262993d56819fe9ccbe4ff68b3ff9 100644
Binary files a/docs/snapshots/main_menu.png and b/docs/snapshots/main_menu.png differ
diff --git a/docs/snapshots/wizard.png b/docs/snapshots/wizard.png
index 0e270189fedbcf353d1e8f5a2d2030b904ea32a7..cfff8c46385a3118454a9048cc3be49030c46562 100644
Binary files a/docs/snapshots/wizard.png and b/docs/snapshots/wizard.png differ
diff --git a/docs/usage.rst b/docs/usage.rst
index c43fb29b946a3bf8c98c8ab78ed091357f8e75e5..679b319a8ea4476262c9cc44e5461f7f1e1de59b 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -17,42 +17,50 @@ snf-image-creator receives the following options:
 
 .. code-block:: console
 
- $ snf-image-creator --help
- Usage: snf-image-creator [options] <input_media>
-
- Options:
-   --version             show program's version number and exit
-   -h, --help            show this help message and exit
-   -o FILE, --outfile=FILE
-                         dump image to FILE
-   -f, --force           overwrite output files if they exist
-   -s, --silent          output only errors
-   -u FILENAME, --upload=FILENAME
-                         upload the image to pithos with name FILENAME
-   -r IMAGENAME, --register=IMAGENAME
-                         register the image with ~okeanos as IMAGENAME
-   -m KEY=VALUE, --metadata=KEY=VALUE
-                         add custom KEY=VALUE metadata to the image
-   -t TOKEN, --token=TOKEN
-                         use this token when uploading/registering images
-                         [Default: None]
-   --print-sysprep       print the available enabled and disabled system
-                         preparation operations for this input media
-   --enable-sysprep=SYSPREP
-                         run SYSPREP operation on the input media
-   --disable-sysprep=SYSPREP
-                         prevent SYSPREP operation from running on the input
-                         media
-   --no-sysprep          don't perform any system preparation operation
-   --no-shrink           don't shrink the image
-   --public              register image with cyclades as public
-   --tmpdir=DIR          create large temporary image files under DIR
+   $ snf-image-creator --help
+   Usage: snf-image-creator [options] <input_media>
+   
+   Options:
+     --version             show program's version number and exit
+     -h, --help            show this help message and exit
+     -o FILE, --outfile=FILE
+                           dump image to FILE
+     -f, --force           overwrite output files if they exist
+     -s, --silent          output only errors
+     -u FILENAME, --upload=FILENAME
+                           upload the image to the storage service with name FILENAME
+     -r IMAGENAME, --register=IMAGENAME
+                           register the image with the compute service as IMAGENAME
+     -m KEY=VALUE, --metadata=KEY=VALUE
+                           add custom KEY=VALUE metadata to the image
+     -t TOKEN, --token=TOKEN
+                           use this authentication token when
+                           uploading/registering images
+     -a URL, --authentication-url=URL
+                           use this authentication URL when uploading/registering
+                           images
+     -c CLOUD, --cloud=CLOUD
+                           use this saved cloud account to authenticate against a
+                           cloud when uploading/registering images
+     --print-sysprep       print the enabled and disabled system preparation
+                           operations for this input media
+     --enable-sysprep=SYSPREP
+                           run SYSPREP operation on the input media
+     --disable-sysprep=SYSPREP
+                           prevent SYSPREP operation from running on the input
+                           media
+     --no-sysprep          don't perform any system preparation operation
+     --no-shrink           don't shrink any partition
+     --public              register image with the compute service as public
+     --tmpdir=DIR          create large temporary image files under DIR
 
 Most input options are self-describing. If you want to save a local copy of
 the image you create, provide a filename using the *-o* option. To upload the
-image to *pithos+*, provide a valid authentication token using *-t* and a
-filename using *-u*. If you also want to register the image with *~okeanos*, in
-addition to *-u* provide a registration name using *-r*. All images are
+image to the storage service of a cloud, provide valid cloud API access info
+(by either using a token and a URL with *-t* and *-a* respectively, or a cloud
+name with *-c*) and a remote filename using *-u*. If you also want to register
+the image with the compute service of the cloud, in addition to *-u* provide a
+registration name using *-r*. All images are
 registered as *private*. Only the user that registers the image can create
 VM's out of it. If you want the image to be visible by other user too, use the
 *--public* option.
@@ -75,61 +83,62 @@ debian system, we print the following output:
 
 .. code-block:: console
 
-   $ snf-image-creator --print-sysprep debian_desktop.img
-
-   snf-image-creator 0.1
+   $ snf-image-creator --print-sysprep ubuntu.raw
+   snf-image-creator 0.3
    =====================
-   Examining source media `debian_desktop.img'... looks like an image file
-   Snapshotting media source... done
+   Examining source media `ubuntu_hd.raw' ... looks like an image file
+   Snapshotting media source ... done
    Enabling recovery proc
-   Launching helper VM... done
-   Inspecting Operating System... found a(n) debian system
-   Mounting the media read-only... done
-
+   Launching helper VM (may take a while) ... done
+   Inspecting Operating System ... ubuntu
+   Mounting the media read-only ... done
+   Collecting image metadata ... done
+   Umounting the media ... done
+   
    Enabled system preparation operations:
        cleanup-cache:
-   	Remove all regular files under /var/cache
-
+           Remove all regular files under /var/cache
+   
        cleanup-log:
-   	Empty all files under /var/log
-
+           Empty all files under /var/log
+   
        cleanup-passwords:
-   	Remove all passwords and lock all user accounts
-
+           Remove all passwords and lock all user accounts
+   
        cleanup-tmp:
-   	Remove all files under /tmp and /var/tmp
-
+           Remove all files under /tmp and /var/tmp
+   
        cleanup-userdata:
-   	Delete sensitive userdata
-
+           Delete sensitive userdata
+   
        fix-acpid:
-   	Replace acpid powerdown action scripts to immediately shutdown the
-   	system without checking if a GUI is running.
-
+           Replace acpid powerdown action scripts to immediately shutdown the
+           system without checking if a GUI is running.
+   
        remove-persistent-net-rules:
-   	Remove udev rules that will keep network interface names persistent
-   	after hardware changes and reboots. Those rules will be created again
-   	the next time the image runs.
-
+           Remove udev rules that will keep network interface names persistent
+           after hardware changes and reboots. Those rules will be created again
+           the next time the image runs.
+   
        remove-swap-entry:
-   	Remove swap entry from /etc/fstab. If swap is the last partition
-   	then the partition will be removed when shrinking is performed. If the
-   	swap partition is not the last partition in the disk or if you are not
-   	going to shrink the image you should probably disable this.
-
+           Remove swap entry from /etc/fstab. If swap is the last partition
+           then the partition will be removed when shrinking is performed. If the
+           swap partition is not the last partition in the disk or if you are not
+           going to shrink the image you should probably disable this.
+   
        use-persistent-block-device-names:
-   	Scan fstab & grub configuration files and replace all non-persistent
-   	device references with UUIDs.
-
+           Scan fstab & grub configuration files and replace all non-persistent
+           device references with UUIDs.
+   
    Disabled system preparation operations:
        cleanup-mail:
-   	Remove all files under /var/mail and /var/spool/mail
-
+           Remove all files under /var/mail and /var/spool/mail
+   
        remove-user-accounts:
-   	Remove all user accounts with id greater than 1000
-
-
-   cleaning up...
+           Remove all user accounts with id greater than 1000
+   
+   
+   cleaning up ...
 
 If you want the image to have all normal user accounts and all mail files
 removed, you should use *--enable-sysprep* option like this:
@@ -173,15 +182,15 @@ Wizard mode
 When *snf-mkimage* runs in *wizard* mode, the user is just asked to provide the
 following basic information:
 
+ * Cloud: The cloud account to use to upload and register the resulting image
  * Name: A short name for the image (ex. "Slackware")
  * Description: An one-line description for the image
    (ex. "Slackware Linux 14.0 with KDE")
  * Registration Type: Private or Public
- * Account: The authentication token for an *~okeanos* account
 
-After confirming, the image will be extracted, uploaded to *pithos+* and
-registered with *~okeanos*. The user will also be given the choice to keep a
-local copy of it.
+After confirming, the image will be extracted, uploaded to the storage service
+and registered with the compute service of the selected cloud. The user will
+also be given the choice to keep a local copy of it.
 
 For most users the functionality this mode provides should be sufficient.
 
@@ -202,11 +211,10 @@ In the *Customize* sub-menu the user can control:
 
 In the *Register* sub-menu the user can provide:
 
- * The credentials (authentication token) to use when authenticating
-   to *~okeanos*
- * A *pithos+* filename for the uploaded *diskdump* image
- * A name for the image to use when registering it with *~okeanos*, as well as
-   the registration type (*private* or *public*)
+ * Which cloud account to use
+ * A filename for the uploaded *diskdump* image
+ * A name for the image to use when registering it with the storage service of
+   the cloud, as well as the registration type (*private* or *public*)
 
 By choosing the *Extract* menu entry, the user can dump the image to the local
 file system. Finally, if the user selects *Reset*, the system will ignore
@@ -244,13 +252,13 @@ Create a 2G sparse file to host the new system:
 
 .. code-block:: console
 
-   $ truncate -s 2G ubuntu_hd.raw
+   $ truncate -s 2G ubuntu.raw
 
 And install the Ubuntu system on this file:
 
 .. code-block:: console
 
-   $ sudo kvm -boot d -drive file=ubuntu_hd.raw,format=raw,cache=none,if=virtio \
+   $ sudo kvm -boot d -drive file=ubuntu.raw,format=raw,cache=none,if=virtio \
      -m 1G -cdrom ubuntu-12.04.2-server-amd64.iso
 
 .. warning::
@@ -261,7 +269,7 @@ And install the Ubuntu system on this file:
 You will be able to boot your installed OS and make any changes you want
 (e.g. install openssh-server) using the following command::
 
-   $ sudo kvm -m 1G -boot c -drive file=ubuntu_hd.raw,format=raw,cache=none,if=virtio
+   $ sudo kvm -m 1G -boot c -drive file=ubuntu.raw,format=raw,cache=none,if=virtio
 
 After you're done, you may use *snf-mkimage* as root to create and upload the
 image:
@@ -269,20 +277,20 @@ image:
 .. code-block:: console
 
    $ sudo -s
-   $ snf-mkimage ubuntu_hd.raw
+   $ snf-mkimage ubuntu.raw
 
 In the first screen you will be asked to choose if you want to run the program
 in *Wizard* or *Expert* mode. Choose *Wizard*.
 
 .. image:: /snapshots/wizard.png
 
-Then you will be asked to provide a name, a description, a registration type
-(*private* or *public*) and the authentication token corresponding to your
-*~okeanos* account. Finally, you'll be asked to confirm the provided data.
+Then you will be asked to select a cloud and provide a name, a description and
+a registration type (*private* or *public*). Finally, you'll be asked to
+confirm the provided data.
 
 .. image:: /snapshots/confirm.png
 
-Choosing *YES* will create and upload the image to your *~okeanos* account.
+Choosing *YES* will create and upload the image to your cloud account.
 
 Limitations
 ===========
@@ -304,13 +312,13 @@ contain primary or logical partitions.
 Para-virtualized drivers
 ------------------------
 
-*~Okeanos* uses the *VirtIO* framework. The disk I/O controller and the
-Ethernet cards on the VM instances are para-virtualized and need special
-*VirtIO* drivers. Those drivers are included in the Linux Kernel mainline since
-version 2.6.25 and are shipped with all the popular Linux distributions. The
-problem is that if the driver for the para-virtualized disk I/O controller is
-built as module, it needs to be preloaded using an initial ramdisk, otherwise
-the VM won't be able to boot.
+Most synnefo deployments uses the *VirtIO* framework. The disk I/O controller
+and the Ethernet cards on the VM instances are para-virtualized and need
+special *VirtIO* drivers. Those drivers are included in the Linux Kernel
+mainline since version 2.6.25 and are shipped with all the popular Linux
+distributions. The problem is that if the driver for the para-virtualized disk
+I/O controller is built as module, it needs to be preloaded using an initial
+ramdisk, otherwise the VM won't be able to boot.
 
 Many popular Linux distributions, like Ubuntu and Debian, will automatically
 create a generic initial ramdisk file that contains many different modules,
diff --git a/image_creator/__init__.py b/image_creator/__init__.py
index f564b96c1d3c3d1da15f03aa3aa7ff5dca94e908..06d6eccdad78229a40811e167d8cabf4249b2744 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 18d0245e29334f63d2fd882b7f5d50e87673da41..eec084f847f58c967a68da67858b0a417f817ae2 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 024c37f4af21d35e476fb9551acfcb2134532825..0834cb44c2639291c2c0278bef07c04f47fff3a7 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
@@ -40,6 +46,7 @@ import stat
 import textwrap
 import signal
 import optparse
+import types
 
 from image_creator import __version__ as version
 from image_creator.util import FatalError
@@ -157,6 +164,39 @@ def select_file(d, media):
     return media
 
 
+def _dialog_form(self, text, height=20, width=60, form_height=15, fields=[],
+                 **kwargs):
+    """Display a form box.
+
+    fields is in the form: [(label1, item1, item_length1), ...]
+    """
+
+    cmd = ["--form", text, str(height), str(width), str(form_height)]
+
+    label_len = 0
+    for field in fields:
+        if len(field[0]) > label_len:
+            label_len = len(field[0])
+
+    input_len = width - label_len - 1
+
+    line = 1
+    for field in fields:
+        label = field[0]
+        item = field[1]
+        item_len = field[2]
+        cmd.extend((label, str(line), str(1), item, str(line),
+                   str(label_len + 1), str(input_len), str(item_len)))
+        line += 1
+
+    code, output = self._perform(*(cmd,), **kwargs)
+
+    if not output:
+        return (code, [])
+
+    return (code, output.splitlines())
+
+
 def main():
 
     d = dialog.Dialog(dialog="dialog")
@@ -175,6 +215,10 @@ def main():
     dialog._common_args_syntax["no_label"] = \
         lambda string: ("--no-label", string)
 
+    # Monkey-patch pythondialog to include support for form dialog boxes
+    if not hasattr(dialog, 'form'):
+        d.form = types.MethodType(_dialog_form, d)
+
     usage = "Usage: %prog [options] [<input_media>]"
     parser = optparse.OptionParser(version=version, usage=usage)
     parser.add_option("-l", "--logfile", type="string", dest="logfile",
diff --git a/image_creator/dialog_menu.py b/image_creator/dialog_menu.py
index b6d31624a7c68724db80d36d0c4f0e6f14dc107f..fd7bc4b0ede0e8516a873dbefebe6934592ba449 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,18 +33,23 @@
 # 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
+import json
 
 from image_creator import __version__ as version
-from image_creator.util import MD5
+from image_creator.util import MD5, FatalError
 from image_creator.output.dialog import GaugeOutput, InfoBoxOutput
 from image_creator.kamaki_wrapper import Kamaki, ClientError
 from image_creator.help import get_help_file
 from image_creator.dialog_util import SMALL_WIDTH, WIDTH, \
     update_background_title, confirm_reset, confirm_exit, Reset, \
-    extract_image, extract_metadata_string
+    extract_image, extract_metadata_string, add_cloud, edit_cloud
 
 CONFIGURATION_TASKS = [
     ("Partition table manipulation", ["FixPartitionTable"],
@@ -108,15 +113,15 @@ class MetadataMonitor(object):
 
 
 def upload_image(session):
-    """Upload the image to pithos+"""
+    """Upload the image to the storage service"""
     d = session["dialog"]
     image = session['image']
     meta = session['metadata']
     size = image.size
 
     if "account" not in session:
-        d.msgbox("You need to provide your ~okeanos credentials before you "
-                 "can upload images to pithos+", width=SMALL_WIDTH)
+        d.msgbox("You need to select a valid cloud before you can upload "
+                 "images to it", width=SMALL_WIDTH)
         return False
 
     while 1:
@@ -144,8 +149,8 @@ def upload_image(session):
                 overwrite.append(f)
 
         if len(overwrite) > 0:
-            if d.yesno("The following pithos object(s) already exist(s):\n"
-                       "%s\nDo you want to overwrite them?" %
+            if d.yesno("The following storage service object(s) already "
+                       "exist(s):\n%s\nDo you want to overwrite them?" %
                        "\n".join(overwrite), width=WIDTH, defaultno=1):
                 continue
 
@@ -177,8 +182,9 @@ def upload_image(session):
                 out.success("done")
 
             except ClientError as e:
-                d.msgbox("Error in pithos+ client: %s" % e.message,
-                         title="Pithos+ Client Error", width=SMALL_WIDTH)
+                d.msgbox(
+                    "Error in storage service client: %s" % e.message,
+                    title="Storage Service Client Error", width=SMALL_WIDTH)
                 if 'pithos_uri' in session:
                     del session['pithos_uri']
                 return False
@@ -187,26 +193,26 @@ def upload_image(session):
     finally:
         gauge.cleanup()
 
-    d.msgbox("Image file `%s' was successfully uploaded to pithos+" % filename,
+    d.msgbox("Image file `%s' was successfully uploaded" % filename,
              width=SMALL_WIDTH)
 
     return True
 
 
 def register_image(session):
-    """Register image with cyclades"""
+    """Register image with the compute service"""
     d = session["dialog"]
 
     is_public = False
 
     if "account" not in session:
-        d.msgbox("You need to provide your ~okeanos credentians before you "
-                 "can register an images with cyclades", width=SMALL_WIDTH)
+        d.msgbox("You need to select a valid cloud before you "
+                 "can register an images with it", width=SMALL_WIDTH)
         return False
 
     if "pithos_uri" not in session:
-        d.msgbox("You need to upload the image to pithos+ before you can "
-                 "register it with cyclades", width=SMALL_WIDTH)
+        d.msgbox("You need to upload the image to the cloud before you can "
+                 "register it", width=SMALL_WIDTH)
         return False
 
     while 1:
@@ -243,14 +249,14 @@ def register_image(session):
         out.add(gauge)
         try:
             try:
-                out.output("Registering %s image with Cyclades..." % img_type)
+                out.output("Registering %s image with the cloud..." % img_type)
                 kamaki = Kamaki(session['account'], out)
-                kamaki.register(name, session['pithos_uri'], metadata,
-                                is_public)
+                result = kamaki.register(name, session['pithos_uri'], metadata,
+                                         is_public)
                 out.success('done')
                 # Upload metadata file
                 out.output("Uploading metadata file...")
-                metastring = extract_metadata_string(session)
+                metastring = unicode(json.dumps(result, ensure_ascii=False))
                 kamaki.upload(StringIO.StringIO(metastring),
                               size=len(metastring),
                               remote_path="%s.meta" % session['upload'])
@@ -261,39 +267,116 @@ def register_image(session):
                     kamaki.share("%s.md5sum" % session['upload'])
                     out.success('done')
             except ClientError as e:
-                d.msgbox("Error in pithos+ client: %s" % e.message)
+                d.msgbox("Error in storage service client: %s" % e.message)
                 return False
         finally:
             out.remove(gauge)
     finally:
         gauge.cleanup()
 
-    d.msgbox("%s image `%s' was successfully registered with Cyclades as `%s'"
+    d.msgbox("%s image `%s' was successfully registered with the cloud as `%s'"
              % (img_type.title(), session['upload'], name), width=SMALL_WIDTH)
     return True
 
 
+def modify_clouds(session):
+    """Modify existing cloud accounts"""
+    d = session['dialog']
+
+    while 1:
+        clouds = Kamaki.get_clouds()
+        if not len(clouds):
+            if not add_cloud(session):
+                break
+            continue
+
+        choices = []
+        for (name, cloud) in clouds.items():
+            descr = cloud['description'] if 'description' in cloud else ''
+            choices.append((name, descr))
+
+        (code, choice) = d.menu(
+            "In this menu you can edit existing cloud accounts or add new "
+            " ones. Press <Edit> to edit an existing account or <Add> to add "
+            " a new one. Press <Back> or hit <ESC> when done.", height=18,
+            width=WIDTH, choices=choices, menu_height=10, ok_label="Edit",
+            extra_button=1, extra_label="Add", cancel="Back", help_button=1,
+            title="Clouds")
+
+        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+            return True
+        elif code == d.DIALOG_OK:  # Edit button
+            edit_cloud(session, choice)
+        elif code == d.DIALOG_EXTRA:  # Add button
+            add_cloud(session)
+
+
+def delete_clouds(session):
+    """Delete existing cloud accounts"""
+    d = session['dialog']
+
+    choices = []
+    for (name, cloud) in Kamaki.get_clouds().items():
+        descr = cloud['description'] if 'description' in cloud else ''
+        choices.append((name, descr, 0))
+
+    if len(choices) == 0:
+        d.msgbox("No available clouds to delete!", width=SMALL_WIDTH)
+        return True
+
+    (code, to_delete) = d.checklist("Choose which cloud accounts to delete:",
+                                    choices=choices, width=WIDTH)
+
+    if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+        return False
+
+    if not len(to_delete):
+        d.msgbox("Nothing selected!", width=SMALL_WIDTH)
+        return False
+
+    if not d.yesno("Are you sure you want to remove the selected cloud "
+                   "accounts?", width=WIDTH, defaultno=1):
+        for i in to_delete:
+            Kamaki.remove_cloud(i)
+            if 'cloud' in session and session['cloud'] == i:
+                del session['cloud']
+                if 'account' in session:
+                    del session['account']
+    else:
+        return False
+
+    d.msgbox("%d cloud accounts were deleted." % len(to_delete),
+             width=SMALL_WIDTH)
+    return True
+
+
 def kamaki_menu(session):
     """Show kamaki related actions"""
     d = session['dialog']
-    default_item = "Account"
+    default_item = "Cloud"
 
-    if 'account' not in session:
-        token = Kamaki.get_token()
-        if token:
-            session['account'] = Kamaki.get_account(token)
+    if 'cloud' not in session:
+        cloud = Kamaki.get_default_cloud_name()
+        if cloud:
+            session['cloud'] = cloud
+            session['account'] = Kamaki.get_account(cloud)
             if not session['account']:
                 del session['account']
-                Kamaki.save_token('')  # The token was not valid. Remove it
+        else:
+            default_item = "Add/Edit"
 
     while 1:
-        account = session["account"]['username'] if "account" in session else \
-            "<none>"
+        cloud = session["cloud"] if "cloud" in session else "<none>"
+        if 'account' not in session and 'cloud' in session:
+            cloud += " <invalid>"
+
         upload = session["upload"] if "upload" in session else "<none>"
 
-        choices = [("Account", "Change your ~okeanos account: %s" % account),
-                   ("Upload", "Upload image to pithos+"),
-                   ("Register", "Register the image to cyclades: %s" % upload)]
+        choices = [("Add/Edit", "Add/Edit cloud accounts"),
+                   ("Delete", "Delete existing cloud accounts"),
+                   ("Cloud", "Select cloud account to use: %s" % cloud),
+                   ("Upload", "Upload image to the cloud"),
+                   ("Register", "Register image with the cloud: %s" % upload)]
 
         (code, choice) = d.menu(
             text="Choose one of the following or press <Back> to go back.",
@@ -304,26 +387,56 @@ def kamaki_menu(session):
         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
             return False
 
-        if choice == "Account":
-            default_item = "Account"
-            (code, answer) = d.inputbox(
-                "Please provide your ~okeanos authentication token:",
-                init=session["account"]['auth_token'] if "account" in session
-                else '', width=WIDTH)
+        if choice == "Add/Edit":
+            if modify_clouds(session):
+                default_item = "Cloud"
+        elif choice == "Delete":
+            if delete_clouds(session):
+                if len(Kamaki.get_clouds()):
+                    default_item = "Cloud"
+                else:
+                    default_time = "Add/Edit"
+            else:
+                default_time = "Delete"
+        elif choice == "Cloud":
+            default_item = "Cloud"
+            clouds = Kamaki.get_clouds()
+            if not len(clouds):
+                d.msgbox("No clouds available. Please add a new cloud!",
+                         width=SMALL_WIDTH)
+                default_item = "Add/Edit"
+                continue
+
+            if 'cloud' not in session:
+                session['cloud'] = clouds.keys()[0]
+
+            choices = []
+            for name, info in clouds.items():
+                default = 1 if session['cloud'] == name else 0
+                descr = info['description'] if 'description' in info else ""
+                choices.append((name, descr, default))
+
+            (code, answer) = d.radiolist("Please select a cloud:",
+                                         width=WIDTH, choices=choices)
             if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
                 continue
-            if len(answer) == 0 and "account" in session:
-                    del session["account"]
             else:
-                token = answer.strip()
-                session['account'] = Kamaki.get_account(token)
+                session['account'] = Kamaki.get_account(answer)
+
+                if session['account'] is None:  # invalid account
+                    if not d.yesno("The cloud %s' is not valid! Would you "
+                                   "like to edit it?" % answer, width=WIDTH):
+                        if edit_cloud(session, answer):
+                            session['account'] = Kamaki.get_account(answer)
+                            Kamaki.set_default_cloud(answer)
+
                 if session['account'] is not None:
-                    Kamaki.save_token(token)
+                    session['cloud'] = answer
+                    Kamaki.set_default_cloud(answer)
                     default_item = "Upload"
                 else:
                     del session['account']
-                    d.msgbox("The token you provided is not valid!",
-                             width=SMALL_WIDTH)
+                    del session['cloud']
         elif choice == "Upload":
             if upload_image(session):
                 default_item = "Register"
@@ -568,30 +681,19 @@ def sysprep(session):
             try:
                 image.out.add(infobox)
                 try:
-                    image.mount(readonly=False)
-                    try:
-                        err = "Unable to execute the system preparation " \
-                            "tasks. Couldn't mount the media%s."
-                        title = "System Preparation"
-                        if not image.mounted:
-                            d.msgbox(err % "", title=title, width=SMALL_WIDTH)
-                            return
-                        elif image.mounted_ro:
-                            d.msgbox(err % " read-write", title=title,
-                                     width=SMALL_WIDTH)
-                            return
-
-                        # The checksum is invalid. We have mounted the image rw
-                        if 'checksum' in session:
-                            del session['checksum']
-
-                        # Monitor the metadata changes during syspreps
-                        with MetadataMonitor(session, image.os.meta):
+                    # The checksum is invalid. We have mounted the image rw
+                    if 'checksum' in session:
+                        del session['checksum']
+
+                    # Monitor the metadata changes during syspreps
+                    with MetadataMonitor(session, image.os.meta):
+                        try:
                             image.os.do_sysprep()
                             infobox.finalize()
-
-                    finally:
-                        image.umount()
+                        except FatalError as e:
+                            title = "System Preparation"
+                            d.msgbox("System Preparation failed: %s" % e,
+                                     title=title, width=SMALL_WIDTH)
                 finally:
                     image.out.remove(infobox)
             finally:
@@ -675,8 +777,8 @@ def main_menu(session):
 
     update_background_title(session)
 
-    choices = [("Customize", "Customize image & ~okeanos deployment options"),
-               ("Register", "Register image to ~okeanos"),
+    choices = [("Customize", "Customize image & cloud deployment options"),
+               ("Register", "Register image to a cloud"),
                ("Extract", "Dump image to local file system"),
                ("Reset", "Reset everything and start over again"),
                ("Help", "Get help for using snf-image-creator")]
@@ -690,7 +792,7 @@ def main_menu(session):
             text="Choose one of the following or press <Exit> to exit.",
             width=WIDTH, choices=choices, cancel="Exit", height=13,
             default_item=default_item, menu_height=len(choices),
-            title="Image Creator for ~okeanos (snf-image-creator version %s)" %
+            title="Image Creator for synnefo (snf-image-creator version %s)" %
                   version)
 
         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
diff --git a/image_creator/dialog_util.py b/image_creator/dialog_util.py
index 046ee3ee1505fbc138513960a16f26395b9005c9..ec5be5ff8998bf0f1f432c16ab02043fc0a0572f 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,9 +33,16 @@
 # 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
+import re
+import json
 from image_creator.output.dialog import GaugeOutput
 from image_creator.util import MD5
+from image_creator.kamaki_wrapper import Kamaki
 
 SMALL_WIDTH = 60
 WIDTH = 70
@@ -78,12 +85,14 @@ class Reset(Exception):
 
 def extract_metadata_string(session):
     """Convert image metadata to text"""
-    metadata = ['%s=%s' % (k, v) for (k, v) in session['metadata'].items()]
-
+    metadata = {}
+    metadata.update(session['metadata'])
     if 'task_metadata' in session:
-        metadata.extend("%s=yes" % m for m in session['task_metadata'])
+        for key in session['task_metadata']:
+            metadata[key] = 'yes'
 
-    return '\n'.join(metadata) + '\n'
+    return unicode(json.dumps({'properties': metadata,
+                               'disk-format': 'diskdump'}, ensure_ascii=False))
 
 
 def extract_image(session):
@@ -167,4 +176,113 @@ def extract_image(session):
 
     return True
 
+
+def _check_cloud(session, name, description, url, token):
+    """Checks if the provided info for a cloud are valid"""
+    d = session['dialog']
+    regexp = re.compile('^[a-zA-Z0-9_]+$')
+
+    if not re.match(regexp, name):
+        d.msgbox("Allowed characters for name: [a-zA-Z0-9_]", width=WIDTH)
+        return False
+
+    if len(url) == 0:
+        d.msgbox("Url cannot be empty!", width=WIDTH)
+        return False
+
+    if len(token) == 0:
+        d.msgbox("Token cannot be empty!", width=WIDTH)
+        return False
+
+    if Kamaki.create_account(url, token) is None:
+        d.msgbox("The cloud info you provided is not valid. Please check the "
+                 "Authentication URL and the token values again!", width=WIDTH)
+        return False
+
+    return True
+
+
+def add_cloud(session):
+    """Add a new cloud account"""
+
+    d = session['dialog']
+
+    name = ""
+    description = ""
+    url = ""
+    token = ""
+
+    while 1:
+        fields = [
+            ("Name:", name, 60),
+            ("Description (optional): ", description, 80),
+            ("Authentication URL: ", url, 200),
+            ("Token:", token, 100)]
+
+        (code, output) = d.form("Add a new cloud account:", height=13,
+                                width=WIDTH, form_height=4, fields=fields)
+
+        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+            return False
+
+        name, description, url, token = output
+
+        name = name.strip()
+        description = description.strip()
+        url = url.strip()
+        token = token.strip()
+
+        if _check_cloud(session, name, description, url, token):
+            if name in Kamaki.get_clouds().keys():
+                d.msgbox("A cloud with name `%s' already exists. If you want "
+                         "to edit the existing cloud account, use the edit "
+                         "menu." % name, width=WIDTH)
+            else:
+                Kamaki.save_cloud(name, url, token, description)
+                break
+
+        continue
+
+    return True
+
+
+def edit_cloud(session, name):
+    """Edit a cloud account"""
+
+    info = Kamaki.get_cloud_by_name(name)
+
+    assert info, "Cloud: `%s' does not exist" % name
+
+    description = info['description'] if 'description' in info else ""
+    url = info['url'] if 'url' in info else ""
+    token = info['token'] if 'token' in info else ""
+
+    d = session['dialog']
+
+    while 1:
+        fields = [
+            ("Description (optional): ", description, 80),
+            ("Authentication URL: ", url, 200),
+            ("Token:", token, 100)]
+
+        (code, output) = d.form("Edit cloud account: `%s'" % name, height=13,
+                                width=WIDTH, form_height=3, fields=fields)
+
+        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+            return False
+
+        description, url, token = output
+
+        description = description.strip()
+        url = url.strip()
+        token = token.strip()
+
+        if _check_cloud(session, name, description, url, token):
+            Kamaki.save_cloud(name, url, token, description)
+            break
+
+        continue
+
+    return True
+
 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
diff --git a/image_creator/dialog_wizard.py b/image_creator/dialog_wizard.py
index 30dc7c622b10dbe704b853e54527e241a97040ff..8abcfed6e90dbb718572bc6080e4fcaf194ec42d 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,15 +33,22 @@
 # 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
+import json
 
 from image_creator.kamaki_wrapper import Kamaki, ClientError
 from image_creator.util import MD5, FatalError
 from image_creator.output.cli import OutputWthProgress
-from image_creator.dialog_util import extract_image, update_background_title
+from image_creator.dialog_util import extract_image, update_background_title, \
+    add_cloud, edit_cloud
 
 PAGE_WIDTH = 70
+PAGE_HEIGHT = 10
 
 
 class WizardExit(Exception):
@@ -49,8 +56,8 @@ class WizardExit(Exception):
     pass
 
 
-class WizardInvalidData(Exception):
-    """Exception triggered when the user provided data are invalid"""
+class WizardReloadPage(Exception):
+    """Exception that reloads the last WizardPage"""
     pass
 
 
@@ -76,20 +83,22 @@ class Wizard:
         idx = 0
         while True:
             try:
-                idx += self.pages[idx].run(self.session, idx, len(self.pages))
+                total = len(self.pages)
+                title = "(%d/%d) %s" % (idx + 1, total, self.pages[idx].title)
+                idx += self.pages[idx].run(self.session, title)
             except WizardExit:
                 return False
-            except WizardInvalidData:
+            except WizardReloadPage:
                 continue
 
             if idx >= len(self.pages):
-                msg = "All necessary information has been gathered:\n\n"
+                text = "All necessary information has been gathered:\n\n"
                 for page in self.pages:
-                    msg += " * %s\n" % page.info
-                msg += "\nContinue with the image creation process?"
+                    text += " * %s\n" % page.info
+                text += "\nContinue with the image creation process?"
 
                 ret = self.d.yesno(
-                    msg, width=PAGE_WIDTH, height=8 + len(self.pages),
+                    text, width=PAGE_WIDTH, height=8 + len(self.pages),
                     ok_label="Yes", cancel="Back", extra_button=1,
                     extra_label="Quit", title="Confirmation")
 
@@ -109,14 +118,26 @@ class WizardPage(object):
     NEXT = 1
     PREV = -1
 
-    def __init__(self, **kargs):
+    def __init__(self, name, display_name, text, **kargs):
+        self.name = name
+        self.display_name = display_name
+        self.text = text
+
+        self.title = kargs['title'] if 'title' in kargs else ""
+        self.default = kargs['default'] if 'default' in kargs else ""
+        self.extra = kargs['extra'] if 'extra' in kargs else None
+        self.extra_label = \
+            kargs['extra_label'] if 'extra_label' in kargs else 'Extra'
+
+        self.info = "%s: <none>" % self.display_name
+
         validate = kargs['validate'] if 'validate' in kargs else lambda x: x
         setattr(self, "validate", validate)
 
         display = kargs['display'] if 'display' in kargs else lambda x: x
         setattr(self, "display", display)
 
-    def run(self, session, index, total):
+    def run(self, session, title):
         """Display this wizard page
 
         This function is used by the wizard program when accessing a page.
@@ -124,123 +145,175 @@ class WizardPage(object):
         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
-        self.printable = printable
-        self.message = message
+class WizardPageWthChoices(WizardPage):
+    """Represents a Wizard Page that allows the user to select something from
+    a list of choices.
+
+    The available choices are created by a function passed to the class through
+    the choices variable. If the choices function returns an empty list, a
+    fallback funtion is executed if available.
+    """
+    def __init__(self, name, display_name, text, choices, **kargs):
+        super(WizardPageWthChoices, self).__init__(name, display_name, text,
+                                                   **kargs)
         self.choices = choices
-        self.title = kargs['title'] if 'title' in kargs else ''
-        self.default = kargs['default'] if 'default' in kargs else ""
+        self.fallback = kargs['fallback'] if 'fallback' in kargs else None
+
+
+class WizardRadioListPage(WizardPageWthChoices):
+    """Represent a Radio List in a wizard"""
 
-    def run(self, session, index, total):
+    def run(self, session, title):
         d = session['dialog']
         w = session['wizard']
 
         choices = []
-        for i in range(len(self.choices)):
-            default = 1 if self.choices[i][0] == self.default else 0
-            choices.append((self.choices[i][0], self.choices[i][1], default))
+        for choice in self.choices():
+            default = 1 if choice[0] == self.default else 0
+            choices.append((choice[0], choice[1], default))
 
         (code, answer) = d.radiolist(
-            self.message, height=10, width=PAGE_WIDTH, ok_label="Next",
-            cancel="Back", choices=choices,
-            title="(%d/%d) %s" % (index + 1, total, self.title))
+            self.text, width=PAGE_WIDTH, ok_label="Next", cancel="Back",
+            choices=choices, height=PAGE_HEIGHT, title=title)
 
         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
             return self.PREV
 
         w[self.name] = self.validate(answer)
         self.default = answer
-        self.info = "%s: %s" % (self.printable, self.display(w[self.name]))
+        self.info = "%s: %s" % (self.display_name, self.display(w[self.name]))
 
         return self.NEXT
 
 
 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: <none>" % self.printable
-        self.title = kargs['title'] if 'title' in kargs else ''
-        self.init = kargs['init'] if 'init' in kargs else ''
 
-    def run(self, session, index, total):
+    def run(self, session, title):
         d = session['dialog']
         w = session['wizard']
 
         (code, answer) = d.inputbox(
-            self.message, init=self.init, width=PAGE_WIDTH, ok_label="Next",
-            cancel="Back", title="(%d/%d) %s" % (index + 1, total, self.title))
+            self.text, init=self.default, width=PAGE_WIDTH, ok_label="Next",
+            cancel="Back", height=PAGE_HEIGHT, title=title)
 
         if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
             return self.PREV
 
         value = answer.strip()
-        self.init = value
+        self.default = value
         w[self.name] = self.validate(value)
-        self.info = "%s: %s" % (self.printable, self.display(w[self.name]))
+        self.info = "%s: %s" % (self.display_name, self.display(w[self.name]))
+
+        return self.NEXT
+
+
+class WizardMenuPage(WizardPageWthChoices):
+    """Represents a menu dialog with available choices in a wizard"""
+
+    def run(self, session, title):
+        d = session['dialog']
+        w = session['wizard']
+
+        extra_button = 1 if self.extra else 0
+
+        choices = self.choices()
+
+        if len(choices) == 0:
+            assert self.fallback, "Zero choices and no fallback"
+            if self.fallback():
+                raise WizardReloadPage
+            else:
+                return self.PREV
+
+        default_item = self.default if self.default else choices[0][0]
+
+        (code, choice) = d.menu(
+            self.text, width=PAGE_WIDTH, ok_label="Next", cancel="Back",
+            title=title, choices=choices, height=PAGE_HEIGHT,
+            default_item=default_item, extra_label=self.extra_label,
+            extra_button=extra_button)
+
+        if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+            return self.PREV
+        elif code == d.DIALOG_EXTRA:
+            self.extra()
+            raise WizardReloadPage
+
+        self.default = choice
+        w[self.name] = self.validate(choice)
+        self.info = "%s: %s" % (self.display_name, self.display(w[self.name]))
 
         return self.NEXT
 
 
 def start_wizard(session):
     """Run the image creation wizard"""
-    init_token = Kamaki.get_token()
-    if init_token is None:
-        init_token = ""
 
     distro = session['image'].distro
     ostype = session['image'].ostype
+
+    def cloud_choices():
+        choices = []
+        for (name, cloud) in Kamaki.get_clouds().items():
+            descr = cloud['description'] if 'description' in cloud else ''
+            choices.append((name, descr))
+
+        return choices
+
+    def cloud_add():
+        return add_cloud(session)
+
+    def cloud_none_available():
+        if not session['dialog'].yesno(
+                "No available clouds found. Would you like to add one now?",
+                width=PAGE_WIDTH, defaultno=0):
+            return add_cloud(session)
+        return False
+
+    def cloud_validate(cloud):
+        if not Kamaki.get_account(cloud):
+            if not session['dialog'].yesno(
+                    "The cloud you have selected is not valid! Would you "
+                    "like to edit it now?", width=PAGE_WIDTH, defaultno=0):
+                if edit_cloud(session, cloud):
+                    return cloud
+
+            raise WizardInvalidData
+
+        return cloud
+
+    cloud = WizardMenuPage(
+        "Cloud", "Cloud",
+        "Please select a cloud account or press <Add> to add a new one:",
+        choices=cloud_choices, extra_label="Add", extra=cloud_add,
+        title="Clouds", validate=cloud_validate, fallback=cloud_none_available)
+
     name = WizardInputPage(
         "ImageName", "Image Name", "Please provide a name for the image:",
-        title="Image Name", init=ostype if distro == "unknown" else distro)
+        title="Image Name", default=ostype if distro == "unknown" else distro)
 
     descr = WizardInputPage(
         "ImageDescription", "Image Description",
         "Please provide a description for the image:",
-        title="Image Description", init=session['metadata']['DESCRIPTION'] if
-        'DESCRIPTION' in session['metadata'] else '')
+        title="Image Description", default=session['metadata']['DESCRIPTION']
+        if 'DESCRIPTION' in session['metadata'] else '')
+
+    def registration_choices():
+        return [("Private", "Image is accessible only by this user"),
+                ("Public", "Everyone can create VMs from this image")]
 
     registration = WizardRadioListPage(
         "ImageRegistration", "Registration Type",
-        "Please provide a registration type:",
-        [("Private", "Image is accessible only by this user"),
-         ("Public", "Everyone can create VMs from this image")],
+        "Please provide a registration type:", registration_choices,
         title="Registration Type", default="Private")
 
-    def validate_account(token):
-        """Check if a token is valid"""
-        d = session['dialog']
-
-        if len(token) == 0:
-            d.msgbox("The token cannot be empty", width=PAGE_WIDTH)
-            raise WizardInvalidData
-
-        account = Kamaki.get_account(token)
-        if account is None:
-            d.msgbox("The token you provided in not valid!", width=PAGE_WIDTH)
-            raise WizardInvalidData
-
-        return account
-
-    account = WizardInputPage(
-        "Account", "Account",
-        "Please provide your ~okeanos authentication token:",
-        title="~okeanos account", init=init_token, validate=validate_account,
-        display=lambda account: account['username'])
-
     w = Wizard(session)
 
+    w.add_page(cloud)
     w.add_page(name)
     w.add_page(descr)
     w.add_page(registration)
-    w.add_page(account)
 
     if w.run():
         create_image(session)
@@ -256,9 +329,6 @@ def create_image(session):
     image = session['image']
     wizard = session['wizard']
 
-    # Save Kamaki credentials
-    Kamaki.save_token(wizard['Account']['auth_token'])
-
     with_progress = OutputWthProgress(True)
     out = image.out
     out.add(with_progress)
@@ -266,16 +336,8 @@ def create_image(session):
         out.clear()
 
         #Sysprep
-        image.mount(False)
-        err_msg = "Unable to execute the system preparation tasks."
-        if not image.mounted:
-            raise FatalError("%s Couldn't mount the media." % err_msg)
-        elif image.mounted_ro:
-            raise FatalError("%s Couldn't mount the media read-write."
-                             % err_msg)
         image.os.do_sysprep()
         metadata = image.os.meta
-        image.umount()
 
         #Shrink
         size = image.shrink()
@@ -289,29 +351,22 @@ def create_image(session):
         md5 = MD5(out)
         session['checksum'] = md5.compute(image.device, size)
 
-        #Metadata
-        metastring = '\n'.join(
-            ['%s=%s' % (key, value) for (key, value) in metadata.items()])
-        metastring += '\n'
-
         out.output()
         try:
-            out.output("Uploading image to pithos:")
-            kamaki = Kamaki(wizard['Account'], out)
+            out.output("Uploading image to the cloud:")
+            account = Kamaki.get_account(wizard['Cloud'])
+            assert account, "Cloud: %s is not valid" % wizard['Cloud']
+            kamaki = Kamaki(account, out)
 
             name = "%s-%s.diskdump" % (wizard['ImageName'],
                                        time.strftime("%Y%m%d%H%M"))
             pithos_file = ""
             with open(image.device, 'rb') as f:
                 pithos_file = kamaki.upload(f, size, name,
-                                            "(1/4)  Calculating block hashes",
-                                            "(2/4)  Uploading missing blocks")
+                                            "(1/3)  Calculating block hashes",
+                                            "(2/3)  Uploading missing blocks")
 
-            out.output("(3/4)  Uploading metadata file ...", False)
-            kamaki.upload(StringIO.StringIO(metastring), size=len(metastring),
-                          remote_path="%s.%s" % (name, 'meta'))
-            out.success('done')
-            out.output("(4/4)  Uploading md5sum file ...", False)
+            out.output("(3/3)  Uploading md5sum file ...", False)
             md5sumstr = '%s %s\n' % (session['checksum'], name)
             kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr),
                           remote_path="%s.%s" % (name, 'md5sum'))
@@ -320,11 +375,17 @@ def create_image(session):
 
             is_public = True if wizard['ImageRegistration'] == "Public" else \
                 False
-            out.output('Registering %s image with ~okeanos ...' %
+            out.output('Registering %s image with the cloud ...' %
                        wizard['ImageRegistration'].lower(), False)
-            kamaki.register(wizard['ImageName'], pithos_file, metadata,
-                            is_public)
+            result = kamaki.register(wizard['ImageName'], pithos_file,
+                                     metadata, is_public)
+            out.success('done')
+            out.output("Uploading metadata file ...", False)
+            metastring = unicode(json.dumps(result, ensure_ascii=False))
+            kamaki.upload(StringIO.StringIO(metastring), size=len(metastring),
+                          remote_path="%s.%s" % (name, 'meta'))
             out.success('done')
+
             if is_public:
                 out.output("Sharing md5sum file ...", False)
                 kamaki.share("%s.md5sum" % name)
@@ -336,14 +397,17 @@ def create_image(session):
             out.output()
 
         except ClientError as e:
-            raise FatalError("Pithos client: %d %s" % (e.status, e.message))
+            raise FatalError("Storage service client: %d %s" %
+                             (e.status, e.message))
     finally:
         out.remove(with_progress)
 
-    msg = "The %s image was successfully uploaded and registered with " \
-          "~okeanos. Would you like to keep a local copy of the image?" \
-          % wizard['ImageRegistration'].lower()
-    if not d.yesno(msg, width=PAGE_WIDTH):
+    text = "The %s image was successfully uploaded to the storage service " \
+           "and registered with the compute service of %s. Would you like " \
+           "to keep a local copy?" % \
+           (wizard['Cloud'], wizard['ImageRegistration'].lower())
+
+    if not d.yesno(text, width=PAGE_WIDTH):
         extract_image(session)
 
 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
diff --git a/image_creator/disk.py b/image_creator/disk.py
index 38372c52fc3f155ca34232d133fabd88178f0f07..76a53aa4102a9054a459aa7214c3108786d7365c 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 968a918e5c5013272b3ad5062e317abad7c9be20..82ae09ed3b7d00b75b0ffc0d52cb677e60257de7 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 faaf6976adefbeedcca765ceeb94b0d54dda794a..7f78d35bbf6c7c0f69769526769701199ade36f5 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/help/image_properties.rst b/image_creator/help/image_properties.rst
index 64fbb8729e3fb23f501c4c71083f1a00f1c358f0..8e65f2af9cb4e4fb741f60d447aeb16b9744eea0 100644
--- a/image_creator/help/image_properties.rst
+++ b/image_creator/help/image_properties.rst
@@ -11,11 +11,11 @@ Properties used during image deployment
      This is a space-seperated list of users, whose password will
      be reset during deployment.
  - SWAP=<n>:<size>
-     If this property is present, cyclades will create a swap
-     partition with given size at the end of the instance's disk.
+     If this property is present, a swap partition with given
+     size will be created at the end of the instance's disk.
      This property only makes sense for Linux images.
 
-Properties used by the ~okeanos User Interface
+Properties used by the synnefo User Interface
 ----------------------------------------------
  - OS
      The value of this property is used to associate the image
diff --git a/image_creator/image.py b/image_creator/image.py
index de287568d697464b93f37c4df719ac37e53e979e..b27c1ebcd5af51c9aea36157d235eb19b02edeff 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
@@ -53,8 +55,6 @@ class Image(object):
         self.progress_bar = None
         self.guestfs_device = None
         self.size = 0
-        self.mounted = False
-        self.mounted_ro = False
 
         self.g = guestfs.GuestFS()
         self.g.add_drive_opts(self.device, readonly=0, format="raw")
@@ -120,19 +120,10 @@ class Image(object):
         if not self.guestfs_enabled:
             self.enable()
 
-        if not self.mounted:
-            do_unmount = True
-            self.mount(readonly=True)
-        else:
-            do_unmount = False
+        cls = os_cls(self.distro, self.ostype)
+        self._os = cls(self.root, self.g, self.out)
 
-        try:
-            cls = os_cls(self.distro, self.ostype)
-            self._os = cls(self.root, self.g, self.out)
-
-        finally:
-            if do_unmount:
-                self.umount()
+        self._os.collect_metadata()
 
         return self._os
 
@@ -156,70 +147,6 @@ class Image(object):
 #
 #        self.progressbar.goto((position * 100) // total)
 
-    def mount(self, readonly=False):
-        """Mount all disk partitions in a correct order."""
-
-        msg = "Mounting the media%s ..." % (" read-only" if readonly else "")
-        self.out.output(msg, False)
-
-        #If something goes wrong when mounting rw, remount the filesystem ro
-        remount_ro = False
-        rw_mpoints = ('/', '/etc', '/root', '/home', '/var')
-
-        # Sort the keys to mount the fs in a correct order.
-        # / should be mounted befor /boot, etc
-        def compare(a, b):
-            if len(a[0]) > len(b[0]):
-                return 1
-            elif len(a[0]) == len(b[0]):
-                return 0
-            else:
-                return -1
-        mps = self.g.inspect_get_mountpoints(self.root)
-        mps.sort(compare)
-
-        mopts = 'ro' if readonly else 'rw'
-        for mp, dev in mps:
-            if self.ostype == 'freebsd':
-                # libguestfs can't handle correct freebsd partitions on GUID
-                # Partition Table. We have to do the translation to linux
-                # device names ourselves
-                m = re.match('^/dev/((?:ada)|(?:vtbd))(\d+)p(\d+)$', dev)
-                if m:
-                    m2 = int(m.group(2))
-                    m3 = int(m.group(3))
-                    dev = '/dev/sd%c%d' % (chr(ord('a') + m2), m3)
-            try:
-                self.g.mount_options(mopts, dev, mp)
-            except RuntimeError as msg:
-                if self.ostype == 'freebsd':
-                    freebsd_mopts = "ufstype=ufs2,%s" % mopts
-                    try:
-                        self.g.mount_vfs(freebsd_mopts, 'ufs', dev, mp)
-                    except RuntimeError as msg:
-                        if readonly is False and mp in rw_mpoints:
-                            remount_ro = True
-                            break
-                elif readonly is False and mp in rw_mpoints:
-                    remount_ro = True
-                    break
-                else:
-                    self.out.warn("%s (ignored)" % msg)
-        if remount_ro:
-            self.out.warn("Unable to mount %s read-write. "
-                          "Remounting everything read-only..." % mp)
-            self.umount()
-            self.mount(True)
-        else:
-            self.mounted = True
-            self.mounted_ro = readonly
-            self.out.success("done")
-
-    def umount(self):
-        """Umount all mounted filesystems."""
-        self.g.umount_all()
-        self.mounted = False
-
     def _last_partition(self):
         """Return the last partition of the image disk"""
         if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt':
diff --git a/image_creator/kamaki_wrapper.py b/image_creator/kamaki_wrapper.py
index e2012d27c84228bb794b3cdea7b554b5a50e5f2c..c515fa65764bc18557747e0b13bdcc157840e180 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
@@ -40,51 +47,101 @@ from kamaki.clients.pithos import PithosClient
 from kamaki.clients.astakos import AstakosClient
 
 
-class Kamaki(object):
+config = Config()
+
 
+class Kamaki(object):
+    """Wrapper class for the ./kamaki library"""
     CONTAINER = "images"
 
     @staticmethod
-    def get_token():
-        """Get the saved token"""
-        config = Config()
-        return config.get('global', 'token')
+    def get_default_cloud_name():
+        """Returns the name of the default cloud"""
+        clouds = config.keys('cloud')
+        default = config.get('global', 'default_cloud')
+        if not default:
+            return clouds[0] if len(clouds) else ""
+        return default if default in clouds else ""
 
     @staticmethod
-    def save_token(token):
-        """Save this token to the configuration file"""
-        config = Config()
-        config.set('global', 'token', token)
+    def set_default_cloud(name):
+        """Sets a cloud account as default"""
+        config.set('global', 'default_cloud', name)
         config.write()
 
     @staticmethod
-    def get_account(token):
-        """Return the account corresponding to this token"""
-        config = Config()
-        astakos = AstakosClient(config.get('user', 'url'), token)
+    def get_clouds():
+        """Returns the list of available clouds"""
+        names = config.keys('cloud')
+
+        clouds = {}
+        for name in names:
+            clouds[name] = config.get('cloud', name)
+
+        return clouds
+
+    @staticmethod
+    def get_cloud_by_name(name):
+        """Returns a dict with cloud info"""
+        return config.get('cloud', name)
+
+    @staticmethod
+    def save_cloud(name, url, token, description=""):
+        """Save a new cloud account"""
+        cloud = {'url': url, 'token': token}
+        if len(description):
+            cloud['description'] = description
+        config.set('cloud', name, cloud)
+
+        # Make the saved cloud the default one
+        config.set('global', 'default_cloud', name)
+        config.write()
+
+    @staticmethod
+    def remove_cloud(name):
+        """Deletes an existing cloud from the Kamaki configuration file"""
+        config.remove_option('cloud', name)
+        config.write()
+
+    @staticmethod
+    def create_account(url, token):
+        """Given a valid (URL, tokens) pair this method returns an Astakos
+        client instance
+        """
+        client = AstakosClient(url, token)
         try:
-            account = astakos.info()
-        except ClientError as e:
-            if e.status == 401:  # Unauthorized: invalid token
-                return None
-            else:
-                raise
-        return account
+            client.authenticate()
+        except ClientError:
+            return None
+
+        return client
+
+    @staticmethod
+    def get_account(cloud_name):
+        """Given a saved cloud name this method returns an Astakos client
+        instance
+        """
+        cloud = config.get('cloud', cloud_name)
+        assert cloud, "cloud: `%s' does not exist" % cloud_name
+        assert 'url' in cloud, "url attr is missing in %s" % cloud_name
+        assert 'token' in cloud, "token attr is missing in %s" % cloud_name
+
+        return Kamaki.create_account(cloud['url'], cloud['token'])
 
     def __init__(self, account, output):
         """Create a Kamaki instance"""
         self.account = account
         self.out = output
 
-        config = Config()
-
-        pithos_url = config.get('file', 'url')
-        self.pithos_client = PithosClient(
-            pithos_url, self.account['auth_token'], self.account['uuid'],
+        self.pithos = PithosClient(
+            self.account.get_service_endpoints('object-store')['publicURL'],
+            self.account.token,
+            self.account.user_info()['id'],
             self.CONTAINER)
 
-        image_url = config.get('image', 'url')
-        self.image_client = ImageClient(image_url, self.account['auth_token'])
+        self.image = ImageClient(
+            self.account.get_service_endpoints('image')['publicURL'],
+            self.account.token)
 
     def upload(self, file_obj, size=None, remote_path=None, hp=None, up=None):
         """Upload a file to pithos"""
@@ -92,7 +149,7 @@ class Kamaki(object):
         path = basename(file_obj.name) if remote_path is None else remote_path
 
         try:
-            self.pithos_client.create_container(self.CONTAINER)
+            self.pithos.create_container(self.CONTAINER)
         except ClientError as e:
             if e.status != 202:  # Ignore container already exists errors
                 raise e
@@ -100,14 +157,13 @@ class Kamaki(object):
         hash_cb = self.out.progress_generator(hp) if hp is not None else None
         upload_cb = self.out.progress_generator(up) if up is not None else None
 
-        self.pithos_client.upload_object(path, file_obj, size, hash_cb,
-                                         upload_cb)
+        self.pithos.upload_object(path, file_obj, size, hash_cb, upload_cb)
 
-        return "pithos://%s/%s/%s" % (self.account['uuid'], self.CONTAINER,
-                                      path)
+        return "pithos://%s/%s/%s" % (self.account.user_info()['id'],
+                                      self.CONTAINER, path)
 
     def register(self, name, location, metadata, public=False):
-        """Register an image to ~okeanos"""
+        """Register an image with cyclades"""
 
         # Convert all metadata to strings
         str_metadata = {}
@@ -115,18 +171,18 @@ class Kamaki(object):
             str_metadata[str(key)] = str(value)
         is_public = 'true' if public else 'false'
         params = {'is_public': is_public, 'disk_format': 'diskdump'}
-        self.image_client.register(name, location, params, str_metadata)
+        return self.image.register(name, location, params, str_metadata)
 
     def share(self, location):
         """Share this file with all the users"""
 
-        self.pithos_client.set_object_sharing(location, "*")
+        self.pithos.set_object_sharing(location, "*")
 
     def object_exists(self, location):
         """Check if an object exists in pythos"""
 
         try:
-            self.pithos_client.get_object_info(location)
+            self.pithos.get_object_info(location)
         except ClientError as e:
             if e.status == 404:  # Object not found error
                 return False
diff --git a/image_creator/main.py b/image_creator/main.py
index 10fc0b5e50818b71e7186a9f615425154e80ccf6..bff95d964fcf18cdebb843ea6c42ab7c671ff5dd 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
@@ -44,6 +49,7 @@ import os
 import optparse
 import StringIO
 import signal
+import json
 
 
 def check_writable_dir(option, opt_str, value, parser):
@@ -77,12 +83,12 @@ def parse_options(input_args):
 
     parser.add_option("-u", "--upload", dest="upload", type="string",
                       default=False,
-                      help="upload the image to pithos with name FILENAME",
+                      help="upload the image to the cloud with name FILENAME",
                       metavar="FILENAME")
 
     parser.add_option("-r", "--register", dest="register", type="string",
                       default=False,
-                      help="register the image with ~okeanos as IMAGENAME",
+                      help="register the image with a cloud as IMAGENAME",
                       metavar="IMAGENAME")
 
     parser.add_option("-m", "--metadata", dest="metadata", default=[],
@@ -93,6 +99,15 @@ def parse_options(input_args):
                       default=None, help="use this authentication token when "
                       "uploading/registering images")
 
+    parser.add_option("-a", "--authentication-url", dest="url", type="string",
+                      default=None, help="use this authentication URL when "
+                      "uploading/registering images")
+
+    parser.add_option("-c", "--cloud", dest="cloud", type="string",
+                      default=None, help="use this saved cloud account to "
+                      "authenticate against a cloud when "
+                      "uploading/registering images")
+
     parser.add_option("--print-sysprep", dest="print_sysprep", default=False,
                       help="print the enabled and disabled system preparation "
                       "operations for this input media", action="store_true")
@@ -114,7 +129,7 @@ def parse_options(input_args):
                       help="don't shrink any partition", action="store_false")
 
     parser.add_option("--public", dest="public", default=False,
-                      help="register image with cyclades as public",
+                      help="register image with the cloud as public",
                       action="store_true")
 
     parser.add_option("--tmpdir", dest="tmp", type="string", default=None,
@@ -133,10 +148,13 @@ def parse_options(input_args):
     if options.register and not options.upload:
         raise FatalError("You also need to set -u when -r option is set")
 
-    if options.upload and options.token is None:
-        raise FatalError(
-            "Image uploading cannot be performed. "
-            "No authentication token is specified. Use -t to set a token")
+    if options.upload and (options.token is None or options.url is None) and \
+            options.cloud is None:
+
+        err = "You need to either specify an authentication URL and token " \
+              "pair or an available cloud name."
+
+        raise FatalError("Image uploading cannot be performed. %s" % err)
 
     if options.tmp is not None and not os.path.isdir(options.tmp):
         raise FatalError("The directory `%s' specified with --tmpdir is not "
@@ -184,13 +202,28 @@ def image_creator():
                 raise FatalError("Output file `%s' exists "
                                  "(use --force to overwrite it)." % filename)
 
-    # Check if the authentication token is valid. The earlier the better
-    if options.token is not None:
+    # Check if the authentication info is valid. The earlier the better
+    if options.token is not None and options.url is not None:
         try:
-            account = Kamaki.get_account(options.token)
+            account = Kamaki.create_account(options.url, options.token)
             if account is None:
-                raise FatalError("The authentication token you provided is not"
-                                 " valid!")
+                raise FatalError("The authentication token and/or URL you "
+                                 "provided is not valid!")
+            else:
+                kamaki = Kamaki(account, out)
+        except ClientError as e:
+            raise FatalError("Astakos client: %d %s" % (e.status, e.message))
+    elif options.cloud:
+        avail_clouds = Kamaki.get_clouds()
+        if options.cloud not in avail_clouds.keys():
+            raise FatalError(
+                "Cloud: `%s' does not exist.\n\nAvailable clouds:\n\n\t%s\n"
+                % (options.cloud, "\n\t".join(avail_clouds.keys())))
+        try:
+            account = Kamaki.get_account(options.cloud)
+            if account is None:
+                raise FatalError(
+                    "Cloud: `$s' exists but is not valid!" % options.cloud)
             else:
                 kamaki = Kamaki(account, out)
         except ClientError as e:
@@ -198,15 +231,16 @@ def image_creator():
 
     if options.upload and not options.force:
         if kamaki.object_exists(options.upload):
-            raise FatalError("Remote pithos object `%s' exists "
+            raise FatalError("Remote storage service object: `%s' exists "
                              "(use --force to overwrite it)." % options.upload)
         if kamaki.object_exists("%s.md5sum" % options.upload):
-            raise FatalError("Remote pithos object `%s.md5sum' exists "
-                             "(use --force to overwrite it)." % options.upload)
+            raise FatalError("Remote storage service object: `%s.md5sum' "
+                             "exists (use --force to overwrite it)." %
+                             options.upload)
 
     if options.register and not options.force:
         if kamaki.object_exists("%s.meta" % options.upload):
-            raise FatalError("Remote pithos object `%s.meta' exists "
+            raise FatalError("Remote storage service object `%s.meta' exists "
                              "(use --force to overwrite it)." % options.upload)
 
     disk = Disk(options.source, out, options.tmp)
@@ -221,36 +255,23 @@ def image_creator():
 
         image = disk.get_image(snapshot)
 
-        # If no customization is to be done, the image should be mounted ro
-        ro = (not (options.sysprep or options.shrink) or options.print_sysprep)
-        image.mount(ro)
-        try:
-            for sysprep in options.disabled_syspreps:
-                image.os.disable_sysprep(image.os.get_sysprep_by_name(sysprep))
+        for sysprep in options.disabled_syspreps:
+            image.os.disable_sysprep(image.os.get_sysprep_by_name(sysprep))
 
-            for sysprep in options.enabled_syspreps:
-                image.os.enable_sysprep(image.os.get_sysprep_by_name(sysprep))
+        for sysprep in options.enabled_syspreps:
+            image.os.enable_sysprep(image.os.get_sysprep_by_name(sysprep))
 
-            if options.print_sysprep:
-                image.os.print_syspreps()
-                out.output()
+        if options.print_sysprep:
+            image.os.print_syspreps()
+            out.output()
 
-            if options.outfile is None and not options.upload:
-                return 0
+        if options.outfile is None and not options.upload:
+            return 0
 
-            if options.sysprep:
-                err_msg = "Unable to perform the system preparation tasks. " \
-                    "Couldn't mount the media%s. Use --no-sysprep if you " \
-                    "don't won't to perform any system preparation task."
-                if not image.mounted:
-                    raise FatalError(err_msg % "")
-                elif image.mounted_ro:
-                    raise FatalError(err_msg % " read-write")
-                image.os.do_sysprep()
+        if options.sysprep:
+            image.os.do_sysprep()
 
-            metadata = image.os.meta
-        finally:
-            image.umount()
+        metadata = image.os.meta
 
         size = options.shrink and image.shrink() or image.size
         metadata.update(image.meta)
@@ -261,9 +282,9 @@ def image_creator():
         md5 = MD5(out)
         checksum = md5.compute(image.device, size)
 
-        metastring = '\n'.join(
-            ['%s=%s' % (key, value) for (key, value) in metadata.items()])
-        metastring += '\n'
+        metastring = unicode(json.dumps(
+            {'properties': metadata,
+             'disk-format': 'diskdump'}, ensure_ascii=False))
 
         if options.outfile is not None:
             image.dump(options.outfile)
@@ -286,7 +307,7 @@ def image_creator():
         try:
             uploaded_obj = ""
             if options.upload:
-                out.output("Uploading image to pithos:")
+                out.output("Uploading image to the storage service:")
                 with open(snapshot, 'rb') as f:
                     uploaded_obj = kamaki.upload(
                         f, size, options.upload,
@@ -303,12 +324,13 @@ def image_creator():
 
             if options.register:
                 img_type = 'public' if options.public else 'private'
-                out.output('Registering %s image with ~okeanos ...' % img_type,
-                           False)
-                kamaki.register(options.register, uploaded_obj, metadata,
-                                options.public)
+                out.output('Registering %s image with the compute service ...'
+                           % img_type, False)
+                result = kamaki.register(options.register, uploaded_obj,
+                                         metadata, options.public)
                 out.success('done')
                 out.output("Uploading metadata file ...", False)
+                metastring = unicode(json.dumps(result, ensure_ascii=False))
                 kamaki.upload(StringIO.StringIO(metastring),
                               size=len(metastring),
                               remote_path="%s.%s" % (options.upload, 'meta'))
@@ -323,7 +345,7 @@ def image_creator():
 
                 out.output()
         except ClientError as e:
-            raise FatalError("Pithos client: %d %s" % (e.status, e.message))
+            raise FatalError("Service client: %d %s" % (e.status, e.message))
 
     finally:
         out.output('cleaning up ...')
diff --git a/image_creator/os_type/__init__.py b/image_creator/os_type/__init__.py
index cea08a32a8ac43575f4c0e167a858674ca5e8fc7..ec4f20dffcda845b1beade073d09441396cb9955 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
@@ -77,27 +83,31 @@ class OSBase(object):
         self.root = rootdev
         self.g = ghandler
         self.out = output
-
-        # Collect metadata about the OS
         self.meta = {}
-        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)
+    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")
 
-    def list_syspreps(self):
+            self.out.output('Collecting image metadata ...', False)
+            self._do_collect_metadata()
+            self.out.success('done')
+        finally:
+            self.umount()
+
+        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__))
@@ -157,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:
@@ -204,26 +266,28 @@ 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."""
-
-        self.out.output('Preparing system for image creation:')
+    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)
 
-        tasks = self.list_syspreps()
-        enabled = filter(lambda x: x.enabled, tasks)
+    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:
+            self.out.warn("unable to mount the root partition: %s" % msg)
+            return False
 
-        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()
+        return True
 
 # 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 d0ed78b20b1e6e6ac31c3174d0573e75f643a7d3..02bd8dc8ae2a9747cc810ba447bb6b1f9ce5adda 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,6 +45,37 @@ class Freebsd(Unix):
     def __init__(self, rootdev, ghandler, output):
         super(Freebsd, self).__init__(rootdev, ghandler, output)
 
+    @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())
 
         #The original product name key is long and ugly
@@ -53,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}(?:[^:]*)'
@@ -72,32 +108,32 @@ class Freebsd(Unix):
 
         return users
 
-    @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_mount(self, readonly):
+        """Mount partitions in the correct order"""
+
+        critical_mpoints = ('/', '/etc', '/root', '/home', '/var')
+
+        # 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:
+                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:
+                if mp in critical_mpoints:
+                    self.out.warn('unable to mount %s. Reason: %s' % (mp, msg))
+                    return False
+                else:
+                    self.out.warn('%s (ignored)' % msg)
+
+        return True
 
 # 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 64c4a67d564f8a9611e1971b028c5c96e5c20df7..2f3e82c968fac6de179c7f9aeb80a48ec7969525 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 86638c02092b1bd4bb1ea39a5b47ee6455c25801..47243368f1fadcf41178341813818dbf6c52878e 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,42 +48,6 @@ class Linux(Unix):
         self._uuid = dict()
         self._persistent = re.compile('/dev/[hsv]d[a-z][1-9]*')
 
-        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"""
@@ -175,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.*"):
@@ -271,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'):
@@ -283,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.
@@ -293,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
@@ -317,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:
@@ -330,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
@@ -341,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 da5443f9993917fec03c18cbadb203fbfaa395f3..f8c7863259433e3b187ed87b4b5103e12a6042a1 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 5b0b571d0ecf6f060ecf8bda01e84d467e7fec51..e8f5a9ee7085d666f7e431accfc2c65fc7e46f15 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 c0ed91318949fafa62954846ad6d94aa2503f42a..b5706fcefc87ea6aa65df6ed101761103350216b 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
 
 
@@ -39,6 +43,10 @@ class Ubuntu(Linux):
     def __init__(self, rootdev, ghandler, output):
         super(Ubuntu, self).__init__(rootdev, ghandler, output)
 
+    def _do_collect_metadata(self):
+        """Collect metadata about the OS"""
+
+        super(Ubuntu, self)._do_collect_metadata()
         apps = self.g.inspect_list_applications(self.root)
         for app in apps:
             if app['app_name'] == 'kubuntu-desktop':
diff --git a/image_creator/os_type/unix.py b/image_creator/os_type/unix.py
index b0b3ae62e56ebe40d609048467e2cd5ca100ccfb..8b88b2fe60b92f1bdfaedd8080b44577cb5bca3a 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
@@ -47,8 +51,41 @@ class Unix(OSBase):
         '.kamaki.history'
     ]
 
-    def __init__(self, rootdev, ghandler, output):
-        super(Unix, self).__init__(rootdev, ghandler, output)
+    def _mountpoints(self):
+        """Return mountpoints in the correct order.
+        / should be mounted before /boot or /usr, /usr befor /usr/bin ...
+        """
+        mps = self.g.inspect_get_mountpoints(self.root)
+
+        def compare(a, b):
+            if len(a[0]) > len(b[0]):
+                return 1
+            elif len(a[0]) == len(b[0]):
+                return 0
+            else:
+                return -1
+        mps.sort(compare)
+
+        for mp in mps:
+            yield mp
+
+    def _do_mount(self, readonly):
+        """Mount partitions in the correct order"""
+
+        critical_mpoints = ('/', '/etc', '/root', '/home', '/var')
+
+        mopts = 'ro' if readonly else 'rw'
+        for mp, dev in self._mountpoints():
+            try:
+                self.g.mount_options(mopts, dev, mp)
+            except RuntimeError as msg:
+                if mp in critical_mpoints:
+                    self.out.warn('unable to mount %s. Reason: %s' % (mp, msg))
+                    return False
+                else:
+                    self.out.warn('%s (ignored)' % msg)
+
+        return True
 
     @sysprep()
     def cleanup_cache(self, print_header=True):
@@ -57,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):
@@ -66,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):
@@ -76,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):
@@ -86,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):
@@ -96,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" %
@@ -108,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 f1e69210e3d11a905b08e955d2475ab6e4134858..9fb8218c02c4fd8e5dc6dc5bd7d620deb68e0116 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,11 +33,46 @@
 # 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
+import tempfile
+import os
+
 
 class Windows(OSBase):
     """OS class for Windows"""
-    pass
+    def __init__(self, rootdev, ghandler, output):
+        super(Windows, self).__init__(rootdev, ghandler, output)
+
+        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)
+            path = "%s/system32/config/sam" % systemroot
+            path = self.g.case_sensitive_path(path)
+            self.g.download(path, sam)
+
+            h = hivex.Hivex(sam)
+
+            key = h.root()
+
+            # Navigate to /SAM/Domains/Account/Users/Names
+            for child in ('SAM', 'Domains', 'Account', 'Users', 'Names'):
+                key = h.node_get_child(key, child)
+
+            users = [h.node_name(x) for x in h.node_children(key)]
+
+        finally:
+            os.unlink(sam)
+
+        # Filter out the guest account
+        return filter(lambda x: x != "Guest", users)
 
 # vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
diff --git a/image_creator/output/__init__.py b/image_creator/output/__init__.py
index e421c75aec020d1bf260562b0681cb7ff8d62d1e..b4aa7c080a09c5399d1ce0282f751560b576959e 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 f88fdbf1d4ca5a500e9db195dc9880500cfa572f..46c84c9d14bde79436ff2847da09e21ba356d819 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 a9502ecc6a62ee4c259592b1ddc2f30cc8d5a9b6..52285047d2ed8486ffbfeb660b76dbf8b8306c9a 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 2790b1c7c9be2e10a5839fff63a7400ebd17635a..f2b10e84a7697ffa04fa21220ce57ca928a32b05 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 08bc27a6d353a2f0f30bd125d27119ae85e4af69..833d0be745cb50af218eaefa06579a25d9c3b710 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 61a97e933a64c045b335148633d997c01c3a35a4..fb0d0fa0b319c3c52c25a262de23eee1695f499e 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/image_creator/version.py b/image_creator/version.py
index 2b8b1b29c59e4e8d24416ee737169186761843e5..249fd433585e50447bb0e75c1314ba364ddb68e3 100644
--- a/image_creator/version.py
+++ b/image_creator/version.py
@@ -1,7 +1,8 @@
-__version__ = "0.3"
-__version_info__ = ['0', '3']
-__version_vcs_info__ = {'branch': 'master',
- 'revid': 'ef30380',
- 'revno': 294,
- 'toplevel': '/home/skalkoto/src/snf-image-creator'}
+__version__ = "0.3next"
+__version_info__ = ['0', '3next']
+__version_vcs_info__ = {
+    'branch': 'develop',
+    'revid': '9c060ab',
+    'revno': 297,
+    'toplevel': '/home/skalkoto/src/snf-image-creator'}
 __version_user_info__ = "skalkoto@darkstar.admin.grnet.gr"
diff --git a/setup.py b/setup.py
index a524373cb65f92379566e7b84c8067eddc329a07..3c673663109a1bf9c128a7fa57a7c522865ff11c 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
diff --git a/version b/version
index be586341736ee60d6ca2be0f3762a307e8fe79f9..51c4eb3d97a7ff09073a5346899cf0560cb93746 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-0.3
+0.3next