X-Git-Url: https://code.grnet.gr/git/snf-image-creator/blobdiff_plain/330fd6187836ec541efe6fada9514ad0ad3b23da..aeb95900f10211f2d3c8ffead154a44121a2d5b5:/image_creator/dialog_main.py diff --git a/image_creator/dialog_main.py b/image_creator/dialog_main.py index fae4c2e..3082a1c 100644 --- a/image_creator/dialog_main.py +++ b/image_creator/dialog_main.py @@ -39,6 +39,7 @@ import os import textwrap import signal import StringIO +import optparse from image_creator import __version__ as version from image_creator.util import FatalError, MD5 @@ -47,6 +48,7 @@ from image_creator.disk import Disk from image_creator.os_type import os_cls from image_creator.kamaki_wrapper import Kamaki, ClientError from image_creator.help import get_help_file +from image_creator.dialog_wizard import wizard MSGBOX_WIDTH = 60 YESNO_WIDTH = 50 @@ -57,20 +59,20 @@ HELP_WIDTH = 70 INFOBOX_WIDTH = 70 CONFIGURATION_TASKS = [ - ("Partition table manipulation", ["FixPartitionTable"], - ["linux", "windows"]), - ("File system resize", - ["FilesystemResizeUnmounted", "FilesystemResizeMounted"], - ["linux", "windows"]), - ("Swap partition configuration", ["AddSwap"], ["linux"]), - ("SSH keys removal", ["DeleteSSHKeys"], ["linux"]), - ("Temporal RDP disabling", ["DisableRemoteDesktopConnections"], ["windows"]), - ("SELinux relabeling at next boot", ["SELinuxAutorelabel"], - ["linux"]), - ("Hostname/Computer Name assignment", ["AssignHostname"], - ["windows", "linux"]), - ("Password change", ["ChangePassword"], ["windows", "linux"]), - ("File injection", ["EnforcePersonality"], ["windows", "linux"]) + ("Partition table manipulation", ["FixPartitionTable"], + ["linux", "windows"]), + ("File system resize", + ["FilesystemResizeUnmounted", "FilesystemResizeMounted"], + ["linux", "windows"]), + ("Swap partition configuration", ["AddSwap"], ["linux"]), + ("SSH keys removal", ["DeleteSSHKeys"], ["linux"]), + ("Temporal RDP disabling", ["DisableRemoteDesktopConnections"], + ["windows"]), + ("SELinux relabeling at next boot", ["SELinuxAutorelabel"], ["linux"]), + ("Hostname/Computer Name assignment", ["AssignHostname"], + ["windows", "linux"]), + ("Password change", ["ChangePassword"], ["windows", "linux"]), + ("File injection", ["EnforcePersonality"], ["windows", "linux"]) ] @@ -120,13 +122,22 @@ class metadata_monitor(object): d.msgbox(msg, title="Image Property Changes", width=MSGBOX_WIDTH) +def extract_metadata_string(session): + metadata = ['%s=%s' % (k, v) for (k, v) in session['metadata'].items()] + + if 'task_metadata' in session: + metadata.extend("%s=yes" % m for m in session['task_metadata']) + + return '\n'.join(metadata) + '\n' + + def confirm_exit(d, msg=''): return not d.yesno("%s Do you want to exit?" % msg, width=YESNO_WIDTH) def confirm_reset(d): return not d.yesno("Are you sure you want to reset everything?", - width=YESNO_WIDTH) + width=YESNO_WIDTH, defaultno=1) def update_background_title(session): @@ -135,8 +146,8 @@ def update_background_title(session): MB = 2 ** 20 - size = (dev.meta['SIZE'] + MB - 1) // MB - shrinked = 'shrinked' in session and session['shrinked'] == True + size = (dev.size + MB - 1) // MB + shrinked = 'shrinked' in session and session['shrinked'] postfix = " (shrinked)" if shrinked else '' title = "OS: %s, Distro: %s, Size: %dMB%s" % \ @@ -186,15 +197,15 @@ def extract_image(session): if len(overwrite) > 0: if d.yesno("The following file(s) exist:\n" - "%s\nDo you want to overwrite them?" % - "\n".join(overwrite), width=YESNO_WIDTH): + "%s\nDo you want to overwrite them?" % + "\n".join(overwrite), width=YESNO_WIDTH): continue out = GaugeOutput(d, "Image Extraction", "Extracting image...") try: dev = session['device'] if "checksum" not in session: - size = dev.meta['SIZE'] + size = dev.size md5 = MD5(out) session['checksum'] = md5.compute(session['snapshot'], size) @@ -204,11 +215,8 @@ def extract_image(session): # Extract metadata file out.output("Extracting metadata file...") - metastring = '\n'.join( - ['%s=%s' % (k, v) for (k, v) in session['metadata'].items()]) - metastring += '\n' with open('%s.meta' % path, 'w') as f: - f.write(metastring) + f.write(extract_metadata_string(session)) out.success('done') # Extract md5sum file @@ -229,7 +237,7 @@ def extract_image(session): def upload_image(session): d = session["dialog"] - size = session['device'].meta['SIZE'] + size = session['device'].size if "account" not in session: d.msgbox("You need to provide your ~okeanos login username before you " @@ -258,10 +266,10 @@ def upload_image(session): break out = GaugeOutput(d, "Image Upload", "Uploading...") - if 'checksum' not in session: - md5 = MD5(out) - session['checksum'] = md5.compute(session['snapshot'], size) try: + if 'checksum' not in session: + md5 = MD5(out) + session['checksum'] = md5.compute(session['snapshot'], size) kamaki = Kamaki(session['account'], session['token'], out) try: # Upload image file @@ -271,9 +279,7 @@ def upload_image(session): "Uploading missing blocks") # Upload metadata file out.output("Uploading metadata file...") - metastring = '\n'.join( - ['%s=%s' % (k, v) for (k, v) in session['metadata'].items()]) - metastring += '\n' + metastring = extract_metadata_string(session) kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), remote_path="%s.meta" % filename) out.success("done") @@ -316,14 +322,13 @@ def register_image(session): return False if "upload" not in session: - d.msgbox("You need to have an image uploaded to pithos+ before you " - "can register it to cyclades", - width=MSGBOX_WIDTH) + d.msgbox("You need to upload the image to pithos+ before you can " + "register it to cyclades", width=MSGBOX_WIDTH) return False while 1: - (code, answer) = d.inputbox("Please provide a registration name:" - " be registered:", width=INPUTBOX_WIDTH) + (code, answer) = d.inputbox("Please provide a registration name:", + width=INPUTBOX_WIDTH) if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): return False @@ -333,12 +338,18 @@ def register_image(session): continue break + metadata = {} + metadata.update(session['metadata']) + if 'task_metadata' in session: + for key in session['task_metadata']: + metadata[key] = 'yes' + out = GaugeOutput(d, "Image Registration", "Registrating image...") try: out.output("Registring image to cyclades...") try: kamaki = Kamaki(session['account'], session['token'], out) - kamaki.register(name, session['upload'], session['metadata']) + kamaki.register(name, session['upload'], metadata) out.success('done') except ClientError as e: d.msgbox("Error in pithos+ client: %s" % e.message) @@ -354,6 +365,15 @@ def register_image(session): def kamaki_menu(session): d = session['dialog'] default_item = "Account" + + account = Kamaki.get_account() + if account: + session['account'] = account + + token = Kamaki.get_token() + if token: + session['token'] = token + while 1: account = session["account"] if "account" in session else "" token = session["token"] if "token" in session else "" @@ -362,12 +382,13 @@ def kamaki_menu(session): choices = [("Account", "Change your ~okeanos username: %s" % account), ("Token", "Change your ~okeanos token: %s" % token), ("Upload", "Upload image to pithos+"), - ("Register", "Register image to cyclades: %s" % upload)] + ("Register", "Register the image to cyclades: %s" % upload)] (code, choice) = d.menu( text="Choose one of the following or press to go back.", - width=MENU_WIDTH, choices=choices, cancel="Back", help_button=1, - default_item=default_item, title="Image Registration Menu") + width=MENU_WIDTH, choices=choices, cancel="Back", height=13, + menu_height=5, default_item=default_item, + title="Image Registration Menu") if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): return False @@ -384,6 +405,7 @@ def kamaki_menu(session): del session["account"] else: session["account"] = answer.strip() + Kamaki.save_account(session['account']) default_item = "Token" elif choice == "Token": default_item = "Token" @@ -397,6 +419,7 @@ def kamaki_menu(session): del session["token"] else: session["token"] = answer.strip() + Kamaki.save_token(session['account']) default_item = "Upload" elif choice == "Upload": if upload_image(session): @@ -428,7 +451,7 @@ def add_property(session): while 1: (code, answer) = d.inputbox("Please provide a value for image " - "property %s" % name, width=INPUTBOX_WIDTH) + "property %s" % name, width=INPUTBOX_WIDTH) if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): return False @@ -459,15 +482,17 @@ def modify_properties(session): "information about image properties. Press when done.", height=18, width=MENU_WIDTH, choices=choices, menu_height=10, ok_label="Edit", extra_button=1, extra_label="Add", cancel="Back", - help_button=1, title="Image Metadata") + help_button=1, title="Image Properties") if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): - break + return True # Edit button elif code == d.DIALOG_OK: - (code, answer) = d.inputbox("Please provide a new value for " - "the image property with name `%s':" % choice, - init=session['metadata'][choice], width=INPUTBOX_WIDTH) + (code, answer) = d.inputbox("Please provide a new value for the " + "image property with name `%s':" % + choice, + init=session['metadata'][choice], + width=INPUTBOX_WIDTH) if code not in (d.DIALOG_CANCEL, d.DIALOG_ESC): value = answer.strip() if len(value) == 0: @@ -478,6 +503,10 @@ def modify_properties(session): # ADD button elif code == d.DIALOG_EXTRA: add_property(session) + elif code == 'help': + help_file = get_help_file("image_properties") + assert os.path.exists(help_file) + d.textbox(help_file, title="Image Properties", width=70, height=40) def delete_properties(session): @@ -497,6 +526,9 @@ def delete_properties(session): cnt = len(to_delete) if cnt > 0: d.msgbox("%d image properties were deleted." % cnt, width=MSGBOX_WIDTH) + return True + else: + return False def exclude_tasks(session): @@ -514,7 +546,7 @@ def exclude_tasks(session): "Do you wish to enable it?", width=YESNO_WIDTH): session['excluded_tasks'].remove(-1) else: - return + return False for (msg, task, osfamily) in CONFIGURATION_TASKS: if session['metadata']['OSFAMILY'] in osfamily: @@ -536,7 +568,7 @@ def exclude_tasks(session): title="Exclude Configuration Tasks") if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): - break + return False elif code == d.DIALOG_HELP: help_file = get_help_file("configuration_tasks") assert os.path.exists(help_file) @@ -556,17 +588,19 @@ def exclude_tasks(session): for task in session['excluded_tasks']: exclude_metadata.extend(CONFIGURATION_TASKS[task][1]) - session['task_metadata'] = \ - map(lambda x: "EXCLUDE_TASK_%s" % x, exclude_metadata) + session['task_metadata'] = map(lambda x: "EXCLUDE_TASK_%s" % x, + exclude_metadata) break + return True + def sysprep(session): d = session['dialog'] image_os = session['image_os'] # Is the image already shrinked? - if 'shrinked' in session and session['shrinked'] == True: + if 'shrinked' in session and session['shrinked']: msg = "It seems you have shrinked the image. Running system " \ "preparation tasks on a shrinked image is dangerous." @@ -587,7 +621,8 @@ def sysprep(session): syspreps = [s for s in all_syspreps if s not in session['exec_syspreps']] if len(syspreps) == 0: - d.msgbox("No system preparation task left to run!", width=MSGBOX_WIDTH) + d.msgbox("No system preparation task available to run!", + title="System Preperation", width=MSGBOX_WIDTH) return while 1: @@ -610,7 +645,7 @@ def sysprep(session): choices=choices, width=70, ok_label="Run", help_button=1) if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): - break + return False elif code == d.DIALOG_HELP: d.scrollbox(sysprep_help, width=HELP_WIDTH) elif code == d.DIALOG_OK: @@ -647,17 +682,19 @@ def sysprep(session): finally: out.cleanup() break + return True def shrink(session): d = session['dialog'] dev = session['device'] - shrinked = 'shrinked' in session and session['shrinked'] == True + shrinked = 'shrinked' in session and session['shrinked'] if shrinked: - d.msgbox("You have already shrinked your image!") - return + d.msgbox("The image is already shrinked!", title="Image Shrinking", + width=MSGBOX_WIDTH) + return True msg = "This operation will shrink the last partition of the image to " \ "reduce the total image size. If the last partition is a swap " \ @@ -667,14 +704,17 @@ def shrink(session): if not d.yesno("%s\n\nDo you want to continue?" % msg, width=70, height=12, title="Image Shrinking"): - with metadata_monitor(session, dev.meta): - dev.out = InfoBoxOutput(d, "Image Shrinking", height=3) + dev.out = InfoBoxOutput(d, "Image Shrinking", height=4) dev.shrink() dev.out.finalize() session['shrinked'] = True update_background_title(session) + else: + return False + + return True def customization_menu(session): @@ -686,7 +726,7 @@ def customization_menu(session): ("Delete", "Delete image properties"), ("Exclude", "Exclude various deployment tasks from running")] - default_item = "Sysprep" + default_item = 0 actions = {"Sysprep": sysprep, "Shrink": shrink, @@ -697,14 +737,15 @@ def customization_menu(session): (code, choice) = d.menu( text="Choose one of the following or press to exit.", width=MENU_WIDTH, choices=choices, cancel="Back", height=13, - menu_height=len(choices), default_item=default_item, + menu_height=len(choices), default_item=choices[default_item][0], title="Image Customization Menu") if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): break elif choice in actions: - default_item = choice - actions[choice](session) + default_item = [entry[0] for entry in choices].index(choice) + if actions[choice](session): + default_item = (default_item + 1) % len(choices) def main_menu(session): @@ -748,13 +789,13 @@ def select_file(d, media): while 1: if media is not None: if not os.path.exists(media): - d.msgbox("The file you choose does not exist", + d.msgbox("The file `%s' you choose does not exist." % media, width=MSGBOX_WIDTH) else: break (code, media) = d.fselect(root, 10, 50, - title="Please select input media") + title="Please select input media") if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): if confirm_exit(d, "You canceled the media selection dialog box."): sys.exit(0) @@ -766,18 +807,21 @@ def select_file(d, media): def image_creator(d): - basename = os.path.basename(sys.argv[0]) - usage = "Usage: %s [input_media]" % basename - if len(sys.argv) > 2: - sys.stderr.write("%s\n" % usage) - return 1 + + usage = "Usage: %prog [options] []" + parser = optparse.OptionParser(version=version, usage=usage) + + options, args = parser.parse_args(sys.argv[1:]) + + if len(args) > 1: + parser.error("Wrong numver of arguments") d.setBackgroundTitle('snf-image-creator') if os.geteuid() != 0: - raise FatalError("You must run %s as root" % basename) + raise FatalError("You must run %s as root" % parser.get_prog_name()) - media = select_file(d, sys.argv[1] if len(sys.argv) == 2 else None) + media = select_file(d, args[0] if len(args) == 1 else None) out = GaugeOutput(d, "Initialization", "Initializing...") disk = Disk(media, out) @@ -791,13 +835,12 @@ def image_creator(d): snapshot = disk.snapshot() dev = disk.get_device(snapshot) - out.output("Collecting image metadata...") - metadata = {} for (key, value) in dev.meta.items(): metadata[str(key)] = str(value) dev.mount(readonly=True) + out.output("Collecting image metadata...") cls = os_cls(dev.distro, dev.ostype) image_os = cls(dev.root, dev.g, out) dev.umount() @@ -820,7 +863,28 @@ def image_creator(d): "image_os": image_os, "metadata": metadata} - main_menu(session) + msg = "snf-image-creator detected a %s system on the input media. " \ + "Would you like to run a wizards to assists you through the " \ + "image creation process?\n\nChoose to run the wizard, " \ + " to run the snf-image-creator in expert mode or press " \ + "ESC to quit the program." \ + % (dev.ostype if dev.ostype == dev.distro else "%s (%s)" % + (dev.ostype, dev.distro)) + + update_background_title(session) + + while True: + code = d.yesno(msg, width=YESNO_WIDTH, height=12) + if code == d.DIALOG_OK: + if wizard(session): + break + elif code == d.DIALOG_CANCEL: + main_menu(session) + break + + if confirm_exit(d): + break + d.infobox("Thank you for using snf-image-creator. Bye", width=53) finally: disk.cleanup()