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

Merge branch 'feature-synnefo-0.14' into develop

parents 121f3bc0 8d66cd4c
docs/snapshots/confirm.png

19.6 KB | W: | H:

docs/snapshots/confirm.png

18.2 KB | W: | H:

docs/snapshots/confirm.png
docs/snapshots/confirm.png
docs/snapshots/confirm.png
docs/snapshots/confirm.png
  • 2-up
  • Swipe
  • Onion skin
docs/snapshots/main_menu.png

18.3 KB | W: | H:

docs/snapshots/main_menu.png

23.8 KB | W: | H:

docs/snapshots/main_menu.png
docs/snapshots/main_menu.png
docs/snapshots/main_menu.png
docs/snapshots/main_menu.png
  • 2-up
  • Swipe
  • Onion skin
docs/snapshots/wizard.png

17.7 KB | W: | H:

docs/snapshots/wizard.png

17.6 KB | W: | H:

docs/snapshots/wizard.png
docs/snapshots/wizard.png
docs/snapshots/wizard.png
docs/snapshots/wizard.png
  • 2-up
  • Swipe
  • Onion skin
......@@ -17,40 +17,47 @@ 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 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 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 cyclades 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
image to *pithos+*, provide valid cloud API access info (by either using a
token with *-t* and a URL with *-a* pair or a cloud name with *-c*) 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
registered as *private*. Only the user that registers the image can create
......@@ -75,61 +82,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,11 +181,11 @@ 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
......@@ -202,10 +210,9 @@ 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*
* Which cloud account to use
* A *pithos+* filename for the uploaded *diskdump* image
* A name for the image to use when registering it with *~okeanos*, as well as
* A name for the image to use when registering it with *~cyclades*, as well as
the registration type (*private* or *public*)
By choosing the *Extract* menu entry, the user can dump the image to the local
......@@ -244,13 +251,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 +268,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,7 +276,7 @@ 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*.
......
......@@ -46,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
......@@ -163,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")
......@@ -181,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",
......
......@@ -40,6 +40,7 @@ snf-image-creator.
import os
import textwrap
import StringIO
import json
from image_creator import __version__ as version
from image_creator.util import MD5, FatalError
......@@ -48,7 +49,7 @@ 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"],
......@@ -119,8 +120,8 @@ def upload_image(session):
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 pithos+", width=SMALL_WIDTH)
return False
while 1:
......@@ -204,7 +205,7 @@ def register_image(session):
is_public = False
if "account" not in session:
d.msgbox("You need to provide your ~okeanos credentians before you "
d.msgbox("You need to select a valid cloud before you "
"can register an images with cyclades", width=SMALL_WIDTH)
return False
......@@ -249,12 +250,12 @@ def register_image(session):
try:
out.output("Registering %s image with Cyclades..." % 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'])
......@@ -277,25 +278,102 @@ def register_image(session):
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),
choices = [("Add/Edit", "Add/Edit cloud accounts"),
("Delete", "Delete existing cloud accounts"),
("Cloud", "Select cloud account to use: %s" % cloud),
("Upload", "Upload image to pithos+"),
("Register", "Register the image to cyclades: %s" % upload)]
......@@ -308,26 +386,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"
......@@ -668,8 +776,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")]
......
......@@ -38,8 +38,11 @@ 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
......@@ -82,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):
......@@ -171,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),
(