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 else: # Select the newly added cloud session['current_cloud'] = Kamaki.get_clouds().keys()[0] 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", title="Clouds") if code in (d.CANCEL, d.ESC): return True elif code == d.OK: # Edit button edit_cloud(session, choice) elif code == d.EXTRA: # Add button add_cloud(session)
def _check_cloud(session, name, url, token): """Checks if the provided info for a cloud are valid""" d = session['dialog'] regexp = re.compile(r'^[~@#$:\-\w]+$') 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 cloud_choices(): """Returns the available clouds""" choices = [] for (name, cloud) in Kamaki.get_clouds().items(): descr = cloud['description'] if 'description' in cloud else '' choices.append((name, descr)) return choices
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:", create_form_elements(fields), height=13, width=WIDTH, form_height=4) if code in (d.CANCEL, d.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, 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 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:", create_form_elements(fields), height=13, width=WIDTH, form_height=4) if code in (d.CANCEL, d.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, 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 cloud_validate(cloud): """Checks if a cloud is valid""" if not Kamaki.get_account(cloud): if session['dialog'].yesno( "The cloud you have selected is not valid! Would you " "like to edit it now?", width=PAGE_WIDTH, defaultno=0) == session['dialog'].OK: if edit_cloud(session, cloud): return cloud raise WizardReloadPage return cloud
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) to_delete = [x.strip('"') for x in to_delete] # Needed for OpenSUSE if code in (d.CANCEL, d.ESC): return False if not len(to_delete): d.msgbox("Nothing selected!", width=SMALL_WIDTH) return False if d.yesno("Are you sure you want to remove the selected accounts?", width=WIDTH, defaultno=1) == d.OK: for i in to_delete: Kamaki.remove_cloud(i) if i in session['clouds']: del session['clouds'][i] if 'current_cloud' in session and session['current_cloud'] == i: del session['current_cloud'] else: return False d.msgbox("%d cloud accounts were deleted." % len(to_delete), width=SMALL_WIDTH) 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, create_form_elements(fields), height=13, width=WIDTH, form_height=3) if code in (d.CANCEL, d.ESC): return False description, url, token = output description = description.strip() url = url.strip() token = token.strip() if _check_cloud(session, name, url, token): 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, create_form_elements(fields), height=13, width=WIDTH, form_height=3) if code in (d.CANCEL, d.ESC): return False description, url, token = output description = description.strip() url = url.strip() token = token.strip() if _check_cloud(session, name, url, token): Kamaki.save_cloud(name, url, token, description) break continue return True
def select_cloud(session): """Select one of the existing cloud accounts""" d = session['dialog'] clouds = Kamaki.get_clouds() if not len(clouds): d.msgbox("No clouds available. Please add a new cloud!", width=SMALL_WIDTH) return False try: current = session['current_cloud'] except KeyError: current = clouds.keys()[0] session['current_cloud'] = current choices = [] for name, info in clouds.items(): default = 1 if current == 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.CANCEL, d.ESC): return True if answer not in session['clouds']: session['clouds'][answer] = {} cloud = session['clouds'][answer] cloud['account'] = Kamaki.get_account(answer) if cloud['account'] is None: # invalid account if d.yesno("The cloud %s' is not valid! Would you like to edit it?" % answer, width=WIDTH) == d.OK: if edit_cloud(session, answer): session['current_cloud'] = answer cloud['account'] = Kamaki.get_account(answer) Kamaki.set_default_cloud(answer) if cloud['account'] is not None: session['current_cloud'] = answer Kamaki.set_default_cloud(answer) return True else: del cloud['account'] del session['current_cloud']
def _check_cloud(session, name, url, token): """Checks if the provided info for a cloud are valid""" d = session['dialog'] regexp = re.compile(r'^[~@#$:\-\w]+$') 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 kamaki_menu(session): """Show kamaki related actions""" d = session['dialog'] try: clouds = session['clouds'] except KeyError: clouds = {} session['clouds'] = clouds default_item = "Add/Edit" try: current = session['current_cloud'] except KeyError: current = Kamaki.get_default_cloud_name() if not current: try: current = Kamaki.get_clouds().keys()[0] except IndexError: # No available cloud pass session['current_cloud'] = current if current: if current not in clouds: clouds[current] = {} try: account = clouds[current]['account'] except KeyError: account = Kamaki.get_account(current) clouds[current]['account'] = account if account: if 'registered' in clouds[current]: default_item = "Info" elif 'uploaded' in clouds[current]: default_item = "Register" else: default_item = "Upload" while 1: current = session['current_cloud'] if 'current_cloud' in session else \ None if current: if current not in session['clouds']: session['clouds'][current] = {} cloud = current if 'account' not in clouds[current]: clouds[current]['account'] = Kamaki.get_account(current) if not clouds[current]['account']: cloud += " <invalid>" else: cloud = '<none>' 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")] if current and 'uploaded' in clouds[current]: _, _, _, _, name = clouds[current]['uploaded'].split('/') choices.append(("Register", "Register image with the cloud: %s" % name)) if current and 'registered' in clouds[current]: choices.append(("Info", "Show registration info for \"%s\"" % clouds[current]['registered']['name'])) (code, choice) = d.menu( text="Choose one of the following or press <Back> to go back.", width=WIDTH, choices=choices, cancel="Back", height=8+len(choices), menu_height=len(choices), default_item=default_item, title="Image Registration Menu") if code in (d.CANCEL, d.ESC): return False 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_item = "Add/Edit" else: default_item = "Delete" elif choice == "Cloud": if select_cloud(session): default_item = "Cloud" else: default_item = 'Add/Edit' elif choice == "Upload": if upload_image(session): default_item = "Register" else: default_item = "Upload" elif choice == "Register": if register_image(session): default_item = "Info" else: default_item = "Register" elif choice == "Info": show_info(session)
def register_image(session): """Register image with the compute service""" d = session["dialog"] image = session['image'] assert 'clouds' in session try: cloud_name = session['current_cloud'] cloud = session['clouds'][cloud_name] account = cloud['account'] except KeyError: cloud = None if cloud is None or account is None: d.msgbox("You need to select a valid cloud before you can register an " "images with it", width=SMALL_WIDTH) return False if "uploaded" not in cloud: d.msgbox("You need to upload the image to a cloud before you can " "register it", width=SMALL_WIDTH) return False is_public = False _, _, _, container, remote = cloud['uploaded'].split('/') name = "" if 'registered' not in cloud else cloud['registered'] descr = image.meta['DESCRIPTION'] if 'DESCRIPTION' in image.meta else "" while 1: fields = [("Registration name:", name, 60), ("Description (optional):", descr, 80)] (code, output) = d.form( "Please provide the following registration info:", create_form_elements(fields), height=11, width=WIDTH, form_height=2) if code in (d.CANCEL, d.ESC): return False name, descr = output name = name.strip() descr = descr.strip() if len(name) == 0: d.msgbox("Registration name cannot be empty", width=SMALL_WIDTH) continue answer = d.yesno("Make the image public?\\nA public image is " "accessible by every user of the service.", defaultno=1, width=WIDTH) if answer == d.ESC: continue is_public = (answer == d.OK) break image.meta['DESCRIPTION'] = descr metadata = {} metadata.update(image.meta) if 'task_metadata' in session: for key in session['task_metadata']: metadata[key] = 'yes' img_type = "public" if is_public else "private" gauge = GaugeOutput(d, "Image Registration", "Registering image ...") try: out = session['image'].out out.append(gauge) try: try: out.info("Registering %s image with the cloud ..." % img_type, False) kamaki = Kamaki(cloud['account'], out) cloud['registered'] = kamaki.register( name, cloud['uploaded'], metadata, is_public) out.success('done') # Upload metadata file out.info("Uploading metadata file ...", False) metastring = json.dumps(cloud['registered'], indent=4, ensure_ascii=False) kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), remote_path="%s.meta" % remote, container=container, content_type="application/json") out.success("done") if is_public: out.info("Sharing metadata and md5sum files ...", False) kamaki.share("%s.meta" % remote) kamaki.share("%s.md5sum" % remote) out.success('done') except ClientError as error: d.msgbox("Error in storage service client: %s" % error.message) return False finally: out.remove(gauge) finally: gauge.cleanup() d.msgbox("%s image `%s' was successfully registered with the cloud as `%s'" % (img_type.title(), remote, name), width=SMALL_WIDTH) return True
def upload_image(session): """Upload the image to the storage service""" d = session["dialog"] image = session['image'] assert 'clouds' in session try: cloud_name = session['current_cloud'] cloud = session['clouds'][cloud_name] account = cloud['account'] except KeyError: cloud = None if cloud is None or not account: d.msgbox("You need to select a valid cloud before you can upload " "images to it", width=SMALL_WIDTH) return False while 1: if 'uploaded' in cloud: _, _, _, container, name = cloud['uploaded'].split('/') elif 'OS' in session['image'].meta: name = "%s.diskdump" % session['image'].meta['OS'] container = CONTAINER else: name = "" container = CONTAINER fields = [("Remote Name:", name, 60), ("Container:", container, 60)] (code, output) = d.form("Please provide the following upload info:", create_form_elements(fields), height=11, width=WIDTH, form_height=2) if code in (d.CANCEL, d.ESC): return False name, container = output name = name.strip() container = container.strip() if len(name) == 0: d.msgbox("Remote Name cannot be empty", width=SMALL_WIDTH) continue if len(container) == 0: d.msgbox("Container cannot be empty", width=SMALL_WIDTH) continue kamaki = Kamaki(cloud['account'], None) overwrite = [] for f in (name, "%s.md5sum" % name, "%s.meta" % name): if kamaki.object_exists(container, f): overwrite.append(f) if len(overwrite) > 0: 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 ) != d.OK: continue break gauge = GaugeOutput(d, "Image Upload", "Uploading ...") try: out = image.out out.append(gauge) kamaki.out = out try: if 'checksum' not in session: session['checksum'] = image.md5() try: # Upload image file with image.raw_device() as raw: with open(raw, 'rb') as f: cloud["uploaded"] = \ kamaki.upload(f, image.size, name, container, None, "Calculating block hashes", "Uploading missing blocks") # Upload md5sum file out.info("Uploading md5sum file ...") md5str = "%s %s\n" % (session['checksum'], name) kamaki.upload(StringIO.StringIO(md5str), size=len(md5str), remote_path="%s.md5sum" % name, container=container, content_type="text/plain") out.success("done") except ClientError as e: d.msgbox( "Error in storage service client: %s" % e.message, title="Storage Service Client Error", width=SMALL_WIDTH) if 'uploaded' in cloud: del cloud['uploaded'] return False finally: out.remove(gauge) finally: gauge.cleanup() d.msgbox("Image file `%s' was successfully uploaded" % name, width=SMALL_WIDTH) return True
def upload_image(session): """Upload the image to the storage service""" d = session["dialog"] image = session['image'] assert 'clouds' in session try: cloud_name = session['current_cloud'] cloud = session['clouds'][cloud_name] account = cloud['account'] except KeyError: cloud = None if cloud is None or not account: d.msgbox( "You need to select a valid cloud before you can upload " "images to it", width=SMALL_WIDTH) return False while 1: if 'uploaded' in cloud: _, _, _, container, name = cloud['uploaded'].split('/') elif 'OS' in session['image'].meta: name = "%s.diskdump" % session['image'].meta['OS'] container = CONTAINER else: name = "" container = CONTAINER fields = [("Remote Name:", name, 60), ("Container:", container, 60)] (code, output) = d.form("Please provide the following upload info:", create_form_elements(fields), height=11, width=WIDTH, form_height=2) if code in (d.CANCEL, d.ESC): return False name, container = output name = name.strip() container = container.strip() if len(name) == 0: d.msgbox("Remote Name cannot be empty", width=SMALL_WIDTH) continue if len(container) == 0: d.msgbox("Container cannot be empty", width=SMALL_WIDTH) continue kamaki = Kamaki(cloud['account'], None) overwrite = [] for f in (name, "%s.md5sum" % name, "%s.meta" % name): if kamaki.object_exists(container, f): overwrite.append(f) if len(overwrite) > 0: 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) != d.OK: continue break gauge = GaugeOutput(d, "Image Upload", "Uploading ...") try: out = image.out out.append(gauge) kamaki.out = out try: if 'checksum' not in session: session['checksum'] = image.md5() try: # Upload image file with image.raw_device() as raw: with open(raw, 'rb') as f: cloud["uploaded"] = \ kamaki.upload(f, image.size, name, container, None, "Calculating block hashes", "Uploading missing blocks") # Upload md5sum file out.info("Uploading md5sum file ...") md5str = "%s %s\n" % (session['checksum'], name) kamaki.upload(StringIO.StringIO(md5str), size=len(md5str), remote_path="%s.md5sum" % name, container=container, content_type="text/plain") out.success("done") except ClientError as e: d.msgbox("Error in storage service client: %s" % e.message, title="Storage Service Client Error", width=SMALL_WIDTH) if 'uploaded' in cloud: del cloud['uploaded'] return False finally: out.remove(gauge) finally: gauge.cleanup() d.msgbox("Image file `%s' was successfully uploaded" % name, width=SMALL_WIDTH) return True
def kamaki_menu(session): """Show kamaki related actions""" d = session['dialog'] try: clouds = session['clouds'] except KeyError: clouds = {} session['clouds'] = clouds default_item = "Add/Edit" try: current = session['current_cloud'] except KeyError: current = Kamaki.get_default_cloud_name() if not current: try: current = Kamaki.get_clouds().keys()[0] except IndexError: # No available cloud pass session['current_cloud'] = current if current: if current not in clouds: clouds[current] = {} try: account = clouds[current]['account'] except KeyError: account = Kamaki.get_account(current) clouds[current]['account'] = account if account: if 'registered' in clouds[current]: default_item = "Info" elif 'uploaded' in clouds[current]: default_item = "Register" else: default_item = "Upload" while 1: current = session['current_cloud'] if 'current_cloud' in session else \ None if current: if current not in session['clouds']: session['clouds'][current] = {} cloud = current if 'account' not in clouds[current]: clouds[current]['account'] = Kamaki.get_account(current) if not clouds[current]['account']: cloud += " <invalid>" else: cloud = '<none>' 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")] if current and 'uploaded' in clouds[current]: _, _, _, _, name = clouds[current]['uploaded'].split('/') choices.append( ("Register", "Register image with the cloud: %s" % name)) if current and 'registered' in clouds[current]: choices.append(("Info", "Show registration info for \"%s\"" % clouds[current]['registered']['name'])) (code, choice) = d.menu( text="Choose one of the following or press <Back> to go back.", width=WIDTH, choices=choices, cancel="Back", height=8 + len(choices), menu_height=len(choices), default_item=default_item, title="Image Registration Menu") if code in (d.CANCEL, d.ESC): return False if choice == "Add/Edit": if modify_clouds(session): if len(Kamaki.get_clouds()) == 1: default_item = "Upload" else: default_item = "Cloud" elif choice == "Delete": if delete_clouds(session): if len(Kamaki.get_clouds()): default_item = "Cloud" else: default_item = "Add/Edit" else: default_item = "Delete" elif choice == "Cloud": if select_cloud(session): default_item = "Cloud" else: default_item = 'Add/Edit' elif choice == "Upload": if upload_image(session): default_item = "Register" else: default_item = "Upload" elif choice == "Register": if register_image(session): default_item = "Info" else: default_item = "Register" elif choice == "Info": show_info(session)
def image_creator(options, out): """snf-mkimage main function""" if os.geteuid() != 0: raise FatalError("You must run %s as root" % os.path.basename(sys.argv[0])) # 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.create_account(options.url, options.token) if account is None: 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: raise FatalError("Astakos client: %d %s" % (e.status, e.message)) if options.upload and not options.force: if kamaki.object_exists(options.container, options.upload): raise FatalError("Remote storage service object: `%s' exists " "(use --force to overwrite it)." % options.upload) if kamaki.object_exists(options.container, "%s.md5sum" % 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(options.container, "%s.meta" % options.upload): raise FatalError("Remote storage service object `%s.meta' exists " "(use --force to overwrite it)." % options.upload) disk = Disk(options.source, out, options.tmp) # pylint: disable=unused-argument def signal_handler(signum, frame): disk.cleanup() signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) try: # There is no need to snapshot the media if it was created by the Disk # instance as a temporary object. device = disk.file if not options.snapshot else disk.snapshot() image = disk.get_image(device, sysprep_params=options.sysprep_params) if image.is_unsupported() and not options.allow_unsupported: raise FatalError( "The media seems to be unsupported.\n\n" + textwrap.fill("To create an image from an unsupported media, " "you'll need to use the`--allow-unsupported' " "command line option. Using this is highly " "discouraged, since the resulting image will " "not be cleared out of sensitive data and will " "not get customized during the deployment.")) if len(options.host_run) != 0 and not image.mount_local_support: raise FatalError("Running scripts against the guest media is not " "supported for this build of libguestfs.") if len(options.host_run) != 0: for script in options.host_run: if not os.path.isfile(script): raise FatalError("File: `%s' does not exist." % script) if not os.access(script, os.X_OK): raise FatalError("File: `%s' is not executable." % script) for name in options.disabled_syspreps: sysprep = image.os.get_sysprep_by_name(name) if sysprep is not None: image.os.disable_sysprep(sysprep) else: out.warn("Sysprep: `%s' does not exist. Can't disable it." % name) for name in options.enabled_syspreps: sysprep = image.os.get_sysprep_by_name(name) if sysprep is not None: image.os.enable_sysprep(sysprep) else: out.warn("Sysprep: `%s' does not exist. Can't enable it." % name) if image.is_unsupported(): image.meta['EXCLUDE_ALL_TASKS'] = "yes" # Add command line metadata to the collected ones... image.meta.update(options.metadata) if options.print_syspreps: image.os.print_syspreps() out.info() if options.print_sysprep_params: image.os.print_sysprep_params() out.info() if options.print_metadata: image.os.print_metadata() out.info() if options.outfile is None and not options.upload: return 0 if options.virtio is not None and \ hasattr(image.os, 'install_virtio_drivers'): image.os.install_virtio_drivers() if len(options.host_run) != 0: # Export image metadata as environment variables to make them # visible by the scripts for key, value in image.meta.items(): os.environ["SNF_IMAGE_CREATOR_METADATA_%s" % key] = str(value) out.info("Running scripts on the input media:") mpoint = tempfile.mkdtemp() try: image.mount(mpoint) if not image.is_mounted(): raise FatalError("Mounting the media on the host failed.") try: size = len(options.host_run) cnt = 1 for script in options.host_run: script = os.path.abspath(script) out.info(("(%d/%d)" % (cnt, size)).ljust(7), False) out.info("Running `%s'" % script) ret = subprocess.Popen([script], cwd=mpoint).wait() if ret != 0: raise FatalError("Script: `%s' failed (rc=%d)" % (script, ret)) cnt += 1 finally: while not image.umount(): out.warn("Unable to umount the media. Retrying ...") time.sleep(1) out.info() finally: os.rmdir(mpoint) if options.sysprep: image.os.do_sysprep() checksum = image.md5() image_meta = {} for k, v in image.meta.items(): image_meta[str(k)] = str(v) metastring = json.dumps( {'properties': image_meta, 'disk-format': 'diskdump'}, ensure_ascii=False) img_properties = json.dumps(image_meta, ensure_ascii=False) if options.outfile is not None: if os.path.realpath(options.outfile) == '/dev/null': out.warn('Not dumping file to /dev/null') else: image.dump(options.outfile) out.info('Dumping metadata file ...', False) with open('%s.%s' % (options.outfile, 'meta'), 'w') as f: f.write(metastring) out.success('done') out.info('Dumping md5sum file ...', False) with open('%s.%s' % (options.outfile, 'md5sum'), 'w') as f: f.write('%s %s\n' % (checksum, os.path.basename(options.outfile))) out.success('done') out.info('Dumping variant file ...', False) with open('%s.%s' % (options.outfile, 'variant'), 'w') as f: f.write(to_shell(IMG_ID=options.outfile, IMG_FORMAT="diskdump", IMG_PROPERTIES=img_properties)) out.success('done') out.info() try: if options.upload: out.info("Uploading image to the storage service:") with image.raw_device() as raw: with open(raw, 'rb') as f: remote = kamaki.upload( f, image.size, options.upload, options.container, None, "(1/3) Calculating block hashes", "(2/3) Uploading missing blocks") out.info("(3/3) Uploading md5sum file ...", False) md5sumstr = '%s %s\n' % (checksum, os.path.basename(options.upload)) kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr), remote_path="%s.%s" % (options.upload, 'md5sum'), container=options.container, content_type="text/plain") out.success('done') out.info() if options.register: img_type = 'public' if options.public else 'private' out.info('Registering %s image with the compute service ...' % img_type, False) result = kamaki.register(options.register, remote, image.meta, options.public) out.success('done') out.info("Uploading metadata file ...", False) metastring = unicode(json.dumps(result, ensure_ascii=False, indent=4)) kamaki.upload(StringIO.StringIO(metastring.encode('utf8')), size=len(metastring), remote_path="%s.%s" % (options.upload, 'meta'), container=options.container, content_type="application/json") out.success('done') if options.public: out.info("Sharing md5sum file ...", False) kamaki.share("%s.md5sum" % options.upload) out.success('done') out.info("Sharing metadata file ...", False) kamaki.share("%s.meta" % options.upload) out.success('done') out.result(json.dumps(result, indent=4, ensure_ascii=False)) out.info() except ClientError as e: raise FatalError("Service client: %d %s" % (e.status, e.message)) finally: out.info('cleaning up ...') disk.cleanup() out.success("snf-image-creator exited without errors") return 0
def register_image(session): """Register image with the compute service""" d = session["dialog"] image = session['image'] assert 'clouds' in session try: cloud_name = session['current_cloud'] cloud = session['clouds'][cloud_name] account = cloud['account'] except KeyError: cloud = None if cloud is None or account is None: d.msgbox( "You need to select a valid cloud before you can register an " "images with it", width=SMALL_WIDTH) return False if "uploaded" not in cloud: d.msgbox( "You need to upload the image to a cloud before you can " "register it", width=SMALL_WIDTH) return False is_public = False _, _, _, container, remote = cloud['uploaded'].split('/') name = "" if 'registered' not in cloud else cloud['registered'] descr = image.meta['DESCRIPTION'] if 'DESCRIPTION' in image.meta else "" while 1: fields = [("Registration name:", name, 60), ("Description (optional):", descr, 80)] (code, output) = d.form("Please provide the following registration info:", create_form_elements(fields), height=11, width=WIDTH, form_height=2) if code in (d.CANCEL, d.ESC): return False name, descr = output name = name.strip() descr = descr.strip() if len(name) == 0: d.msgbox("Registration name cannot be empty", width=SMALL_WIDTH) continue answer = d.yesno( "Make the image public?\\nA public image is " "accessible by every user of the service.", defaultno=1, width=WIDTH) if answer == d.ESC: continue is_public = (answer == d.OK) break image.meta['DESCRIPTION'] = descr metadata = {} metadata.update(image.meta) if 'task_metadata' in session: for key in session['task_metadata']: metadata[key] = 'yes' img_type = "public" if is_public else "private" gauge = GaugeOutput(d, "Image Registration", "Registering image ...") try: out = session['image'].out out.append(gauge) try: try: out.info("Registering %s image with the cloud ..." % img_type, False) kamaki = Kamaki(cloud['account'], out) cloud['registered'] = kamaki.register(name, cloud['uploaded'], metadata, is_public) out.success('done') # Upload metadata file out.info("Uploading metadata file ...", False) metastring = json.dumps(cloud['registered'], indent=4, ensure_ascii=False) kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), remote_path="%s.meta" % remote, container=container, content_type="application/json") out.success("done") if is_public: out.info("Sharing metadata and md5sum files ...", False) kamaki.share("%s.meta" % remote) kamaki.share("%s.md5sum" % remote) out.success('done') except ClientError as error: d.msgbox("Error in storage service client: %s" % error.message) return False finally: out.remove(gauge) finally: gauge.cleanup() d.msgbox( "%s image `%s' was successfully registered with the cloud as `%s'" % (img_type.title(), remote, name), width=SMALL_WIDTH) return True
def create_image(session, answers): """Create an image using the information collected by the wizard""" image = session['image'] with_progress = OutputWthProgress() image.out.append(with_progress) try: image.out.clear() if 'virtio' in answers and image.os.sysprep_params['virtio'].value: image.os.install_virtio_drivers() # Sysprep image.os.do_sysprep() metadata = image.os.meta update_background_title(session) metadata['DESCRIPTION'] = answers['ImageDescription'] # MD5 session['checksum'] = image.md5() image.out.info() try: image.out.info("Uploading image to the cloud:") account = Kamaki.get_account(answers['Cloud']) assert account, "Cloud: %s is not valid" % answers['Cloud'] kamaki = Kamaki(account, image.out) name = "%s-%s.diskdump" % (answers['ImageName'], time.strftime("%Y%m%d%H%M")) with image.raw_device() as raw: with open(raw, 'rb') as device: remote = kamaki.upload(device, image.size, name, CONTAINER, None, "(1/3) Calculating block hashes", "(2/3) Uploading image blocks") image.out.info("(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'), container=CONTAINER, content_type="text/plain") image.out.success('done') image.out.info() image.out.info('Registering %s image with the cloud ...' % answers['RegistrationType'].lower(), False) result = kamaki.register(answers['ImageName'], remote, metadata, answers['RegistrationType'] == "Public") image.out.success('done') image.out.info("Uploading metadata file ...", False) metastring = unicode( json.dumps(result, ensure_ascii=False)).encode('utf8') kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), remote_path="%s.%s" % (name, 'meta'), container=CONTAINER, content_type="application/json") image.out.success('done') if answers['RegistrationType'] == "Public": image.out.info("Sharing md5sum file ...", False) kamaki.share("%s.md5sum" % name) image.out.success('done') image.out.info("Sharing metadata file ...", False) kamaki.share("%s.meta" % name) image.out.success('done') image.out.info() except ClientError as error: raise FatalError("Storage service client: %d %s" % (error.status, error.message)) finally: image.out.remove(with_progress) 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?" % \ (answers['RegistrationType'].lower(), answers['Cloud']) if session['dialog'].yesno(text, width=PAGE_WIDTH) == session['dialog'].OK: extract_image(session)
def image_creator(options, out): """snf-mkimage main function""" if os.geteuid() != 0: raise FatalError("You must run %s as root" % os.path.basename(sys.argv[0])) # 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.create_account(options.url, options.token) if account is None: 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: raise FatalError("Astakos client: %d %s" % (e.status, e.message)) if options.upload and not options.force: if kamaki.object_exists(options.container, options.upload): raise FatalError("Remote storage service object: `%s' exists " "(use --force to overwrite it)." % options.upload) if kamaki.object_exists(options.container, "%s.md5sum" % 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(options.container, "%s.meta" % options.upload): raise FatalError("Remote storage service object `%s.meta' exists " "(use --force to overwrite it)." % options.upload) disk = Disk(options.source, out, options.tmp) # pylint: disable=unused-argument def signal_handler(signum, frame): disk.cleanup() signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) try: # There is no need to snapshot the media if it was created by the Disk # instance as a temporary object. device = disk.file if not options.snapshot else disk.snapshot() image = disk.get_image(device, sysprep_params=options.sysprep_params) if image.is_unsupported() and not options.allow_unsupported: raise FatalError( "The media seems to be unsupported.\n\n" + textwrap.fill("To create an image from an unsupported media, " "you'll need to use the`--allow-unsupported' " "command line option. Using this is highly " "discouraged, since the resulting image will " "not be cleared out of sensitive data and will " "not get customized during the deployment.")) if len(options.host_run) != 0 and not image.mount_local_support: raise FatalError("Running scripts against the guest media is not " "supported for this build of libguestfs.") if len(options.host_run) != 0: for script in options.host_run: if not os.path.isfile(script): raise FatalError("File: `%s' does not exist." % script) if not os.access(script, os.X_OK): raise FatalError("File: `%s' is not executable." % script) for name in options.disabled_syspreps: sysprep = image.os.get_sysprep_by_name(name) if sysprep is not None: image.os.disable_sysprep(sysprep) else: out.warn("Sysprep: `%s' does not exist. Can't disable it." % name) for name in options.enabled_syspreps: sysprep = image.os.get_sysprep_by_name(name) if sysprep is not None: image.os.enable_sysprep(sysprep) else: out.warn("Sysprep: `%s' does not exist. Can't enable it." % name) if image.is_unsupported(): image.meta['EXCLUDE_ALL_TASKS'] = "yes" # Add command line metadata to the collected ones... image.meta.update(options.metadata) if options.print_syspreps: image.os.print_syspreps() out.info() if options.print_sysprep_params: image.os.print_sysprep_params() out.info() if options.print_metadata: image.os.print_metadata() out.info() if options.outfile is None and not options.upload: return 0 if options.virtio is not None and \ hasattr(image.os, 'install_virtio_drivers'): image.os.install_virtio_drivers() if len(options.host_run) != 0: # Export image metadata as environment variables to make them # visible by the scripts for key, value in image.meta.items(): os.environ["SNF_IMAGE_CREATOR_METADATA_%s" % key] = str(value) out.info("Running scripts on the input media:") mpoint = tempfile.mkdtemp() try: image.mount(mpoint) if not image.is_mounted(): raise FatalError("Mounting the media on the host failed.") try: size = len(options.host_run) cnt = 1 for script in options.host_run: script = os.path.abspath(script) out.info(("(%d/%d)" % (cnt, size)).ljust(7), False) out.info("Running `%s'" % script) ret = subprocess.Popen([script], cwd=mpoint).wait() if ret != 0: raise FatalError("Script: `%s' failed (rc=%d)" % (script, ret)) cnt += 1 finally: while not image.umount(): out.warn("Unable to umount the media. Retrying ...") time.sleep(1) out.info() finally: os.rmdir(mpoint) if options.sysprep: image.os.do_sysprep() checksum = image.md5() image_meta = {} for k, v in image.meta.items(): image_meta[str(k)] = str(v) metastring = json.dumps( { 'properties': image_meta, 'disk-format': 'diskdump' }, ensure_ascii=False) img_properties = json.dumps(image_meta, ensure_ascii=False) if options.outfile is not None: if os.path.realpath(options.outfile) == '/dev/null': out.warn('Not dumping file to /dev/null') else: image.dump(options.outfile) out.info('Dumping metadata file ...', False) with open('%s.%s' % (options.outfile, 'meta'), 'w') as f: f.write(metastring) out.success('done') out.info('Dumping md5sum file ...', False) with open('%s.%s' % (options.outfile, 'md5sum'), 'w') as f: f.write('%s %s\n' % (checksum, os.path.basename(options.outfile))) out.success('done') out.info('Dumping variant file ...', False) with open('%s.%s' % (options.outfile, 'variant'), 'w') as f: f.write( to_shell(IMG_ID=options.outfile, IMG_FORMAT="diskdump", IMG_PROPERTIES=img_properties)) out.success('done') out.info() try: if options.upload: out.info("Uploading image to the storage service:") with image.raw_device() as raw: with open(raw, 'rb') as f: remote = kamaki.upload( f, image.size, options.upload, options.container, None, "(1/3) Calculating block hashes", "(2/3) Uploading missing blocks") out.info("(3/3) Uploading md5sum file ...", False) md5sumstr = '%s %s\n' % (checksum, os.path.basename(options.upload)) kamaki.upload(StringIO.StringIO(md5sumstr), size=len(md5sumstr), remote_path="%s.%s" % (options.upload, 'md5sum'), container=options.container, content_type="text/plain") out.success('done') out.info() if options.register: img_type = 'public' if options.public else 'private' out.info( 'Registering %s image with the compute service ...' % img_type, False) result = kamaki.register(options.register, remote, image.meta, options.public) out.success('done') out.info("Uploading metadata file ...", False) metastring = unicode( json.dumps(result, ensure_ascii=False, indent=4)) kamaki.upload(StringIO.StringIO(metastring.encode('utf8')), size=len(metastring), remote_path="%s.%s" % (options.upload, 'meta'), container=options.container, content_type="application/json") out.success('done') if options.public: out.info("Sharing md5sum file ...", False) kamaki.share("%s.md5sum" % options.upload) out.success('done') out.info("Sharing metadata file ...", False) kamaki.share("%s.meta" % options.upload) out.success('done') out.result(json.dumps(result, indent=4, ensure_ascii=False)) out.info() except ClientError as e: raise FatalError("Service client: %d %s" % (e.status, e.message)) finally: out.info('cleaning up ...') disk.cleanup() out.success("snf-image-creator exited without errors") return 0
def create_image(session, answers): """Create an image using the information collected by the wizard""" image = session['image'] with_progress = OutputWthProgress() image.out.append(with_progress) try: image.out.clear() if 'virtio' in answers and image.os.sysprep_params['virtio'].value: image.os.install_virtio_drivers() # Sysprep image.os.do_sysprep() metadata = image.os.meta update_background_title(session) metadata['DESCRIPTION'] = answers['ImageDescription'] # MD5 session['checksum'] = image.md5() image.out.info() try: image.out.info("Uploading image to the cloud:") account = Kamaki.get_account(answers['Cloud']) assert account, "Cloud: %s is not valid" % answers['Cloud'] kamaki = Kamaki(account, image.out) name = "%s-%s.diskdump" % (answers['ImageName'], time.strftime("%Y%m%d%H%M")) with image.raw_device() as raw: with open(raw, 'rb') as device: remote = kamaki.upload(device, image.size, name, CONTAINER, None, "(1/3) Calculating block hashes", "(2/3) Uploading image blocks") image.out.info("(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'), container=CONTAINER, content_type="text/plain") image.out.success('done') image.out.info() image.out.info( 'Registering %s image with the cloud ...' % answers['RegistrationType'].lower(), False) result = kamaki.register(answers['ImageName'], remote, metadata, answers['RegistrationType'] == "Public") image.out.success('done') image.out.info("Uploading metadata file ...", False) metastring = unicode(json.dumps(result, ensure_ascii=False)).encode('utf8') kamaki.upload(StringIO.StringIO(metastring), size=len(metastring), remote_path="%s.%s" % (name, 'meta'), container=CONTAINER, content_type="application/json") image.out.success('done') if answers['RegistrationType'] == "Public": image.out.info("Sharing md5sum file ...", False) kamaki.share("%s.md5sum" % name) image.out.success('done') image.out.info("Sharing metadata file ...", False) kamaki.share("%s.meta" % name) image.out.success('done') image.out.info() except ClientError as error: raise FatalError("Storage service client: %d %s" % (error.status, error.message)) finally: image.out.remove(with_progress) 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?" % \ (answers['RegistrationType'].lower(), answers['Cloud']) if session['dialog'].yesno(text, width=PAGE_WIDTH) == session['dialog'].OK: extract_image(session)