if len(field[0]) > label_len:
label_len = len(field[0])
- input_len = width - label_len - 2
+ input_len = width - label_len - 1
line = 1
for field in fields:
item = field[1]
item_len = field[2]
cmd.extend((label, str(line), str(1), item, str(line),
- str(label_len + 2), str(input_len), str(item_len)))
+ str(label_len + 1), str(input_len), str(item_len)))
line += 1
code, output = self._perform(*(cmd,), **kwargs)
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"],
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:
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
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)]
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"
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")]
"""
import os
+import re
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
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
+ assert 'url' in info, "Cloud: `%s' does not have a url attr" % name
+ assert 'token' in info, "Cloud: `%s' does not have a token attr" % name
+
+ description = info['description'] if 'description' in info else ""
+ url = info['url']
+ token = info['token']
+
+ 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 :
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
def start_wizard(session):
"""Run the image creation wizard"""
- init_token = Kamaki.get_token()
- if init_token is None:
- init_token = ""
+
+ d = session['dialog']
+ clouds = Kamaki.get_clouds()
+ if not len(clouds):
+ if not add_cloud(session):
+ return False
+ else:
+ while 1:
+ 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 select existing cloud account to use "
+ " or add new ones. Press <Select> to select an existing "
+ "account or <Add> to add a new one.", height=18,
+ width=PAGE_WIDTH, choices=choices, menu_height=10,
+ ok_label="Select", extra_button=1, extra_label="Add",
+ title="Clouds")
+
+ if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
+ return False
+ elif code == d.DIALOG_OK: # Select button
+ account = Kamaki.get_account(choice)
+ if not account:
+ if not d.yesno("Then cloud you have selected is not "
+ "valid! Would you like to edit it?",
+ width=PAGE_WIDTH, defaultno=0):
+ edit_cloud(session, choice)
+ continue
+ break
+ elif code == d.DIALOG_EXTRA: # Add button
+ add_cloud(session)
distro = session['image'].distro
ostype = session['image'].ostype
("Public", "Everyone can create VMs from this image")],
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(name)
w.add_page(descr)
w.add_page(registration)
- w.add_page(account)
if w.run():
- create_image(session)
+ create_image(session, account)
else:
return False
return True
-def create_image(session):
+def create_image(session, account):
"""Create an image using the information collected by the wizard"""
d = session['dialog']
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)
out.output()
try:
out.output("Uploading image to pithos:")
- kamaki = Kamaki(wizard['Account'], out)
+ kamaki = Kamaki(account, out)
name = "%s-%s.diskdump" % (wizard['ImageName'],
time.strftime("%Y%m%d%H%M"))
is_public = True if wizard['ImageRegistration'] == "Public" else \
False
- out.output('Registering %s image with ~okeanos ...' %
+ out.output('Registering %s image with cyclades ...' %
wizard['ImageRegistration'].lower(), False)
kamaki.register(wizard['ImageName'], pithos_file, metadata,
is_public)
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?" \
+ msg = "The %s image was successfully uploaded to Pithos and registered " \
+ "with Cyclades. Would you like to keep a local copy of the image?" \
% wizard['ImageRegistration'].lower()
if not d.yesno(msg, width=PAGE_WIDTH):
extract_image(session)
from kamaki.clients.astakos import AstakosClient
+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 set_default_cloud(name):
+ """Sets a cloud account as default"""
+ config.set('global', 'default_cloud', name)
+ config.write()
+
+ @staticmethod
+ 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_token(token):
- """Save this token to the configuration file"""
- config = Config()
- config.set('global', 'token', token)
+ 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 get_account(token):
- """Return the account corresponding to this token"""
- config = Config()
- astakos = AstakosClient(config.get('user', 'url'), token)
+ 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"""
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
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"""
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
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("--print-sysprep", dest="print_sysprep", default=False,
help="print the enabled and disabled system preparation "
"operations for this input media", action="store_true")
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):
+ if options.url is None:
+ err = "No authentication URL is specified. Use -a to set a URL"
+ else:
+ err = "No autentication token is specified. Use -t to set a token"
+
+ 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 "
"(use --force to overwrite it)." % filename)
# Check if the authentication token is valid. The earlier the better
- if options.token is not None:
+ 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: