Source code for COT.edit_properties

#!/usr/bin/env python
#
# edit_properties.py - Implements "edit-properties" sub-command
#
# August 2013, Glenn F. Matthews
# Copyright (c) 2013-2015 the COT project developers.
# See the COPYRIGHT.txt file at the top-level directory of this distribution
# and at https://github.com/glennmatthews/cot/blob/master/COPYRIGHT.txt.
#
# This file is part of the Common OVF Tool (COT) project.
# It is subject to the license terms in the LICENSE.txt file found in the
# top-level directory of this distribution and at
# https://github.com/glennmatthews/cot/blob/master/LICENSE.txt. No part
# of COT, including this file, may be copied, modified, propagated, or
# distributed except according to the terms contained in the LICENSE.txt file.

"""Module for managing VM environment configuration properties.

**Classes**

.. autosummary::
  :nosignatures:

  COTEditProperties
"""

import logging
import os.path
import textwrap

from .submodule import COTSubmodule
from .data_validation import ValueUnsupportedError, InvalidInputError

logger = logging.getLogger(__name__)


[docs]class COTEditProperties(COTSubmodule): """Edit OVF environment XML properties. Inherited attributes: :attr:`~COTGenericSubmodule.UI`, :attr:`~COTSubmodule.package`, :attr:`~COTSubmodule.output` Attributes: :attr:`config_file`, :attr:`properties` """ def __init__(self, UI): """Instantiate this submodule with the given UI.""" super(COTEditProperties, self).__init__(UI) self._config_file = None self._properties = {} @property def config_file(self): """Path to plaintext file to read configuration lines from. :raise: :exc:`InvalidInputError` if the file does not exist. """ return self._config_file @config_file.setter
[docs] def config_file(self, value): if not os.path.exists(value): raise InvalidInputError("Specified config file {0} does not exist!" .format(value)) self._config_file = value
@property def properties(self): """List of property (key, value) tuples to update.""" return self._properties @properties.setter
[docs] def properties(self, value): new_value = [] for key_value_pair in value: try: (k, v) = key_value_pair.split('=', 1) logger.debug("key: {0} value: {1}".format(k, v)) if k == '': raise ValueError() new_value.append((k, v)) except ValueError: raise InvalidInputError("Invalid property '{0}' - properties " "must be in 'key=value' form" .format(key_value_pair)) self._properties = new_value
[docs] def run(self): """Do the actual work of this submodule. :raises InvalidInputError: if :func:`ready_to_run` reports ``False`` """ super(COTEditProperties, self).run() if self.config_file is not None: self.vm.config_file_to_properties(self.config_file) if self.properties: for key, value in self.properties: if value == '': value = self.UI.get_input( "Enter value for property '{0}'", value) curr_value = self.vm.get_property_value(key) if curr_value is None: self.UI.confirm_or_die( "Property '{0}' does not yet exist.\n" "Create it?".format(key)) # TODO - for new property, prompt for label/descr/type? self.vm.set_property_value(key, value) if not self.config_file and not self.properties: logger.info("No changes specified in CLI; " "entering interactive mode.") # Interactive mode! self.edit_properties_interactive()
[docs] def edit_properties_interactive(self): """Present an interactive UI for the user to edit properties.""" wrapper = textwrap.TextWrapper(initial_indent='', subsequent_indent=' ') format_str = '{0:15} "{1}"' pa = self.vm.environment_properties while True: key_list = [p['key'] for p in pa] string_list = ["""{0:25} "{1}" """.format(p['key'], p['label']) for p in pa] input = self.UI.choose_from_list( header="Please choose a property to edit:", option_list=key_list, info_list=string_list, footer=("Enter property key or number to edit, or " "'q' to write changes and quit"), default_value='q') if input == 'q' or input == 'Q': break p = next(p for p in pa if p['key'] == input) key = p['key'] old_value = p['value'] prompt = "\n".join([ wrapper.fill(format_str.format("Key:", p['key'])), wrapper.fill(format_str.format("Label:", p['label'])), wrapper.fill(format_str.format("Description:", p['description'])), wrapper.fill(format_str.format("Type:", p['type'])), wrapper.fill(format_str.format("Qualifiers:", p['qualifiers'])), wrapper.fill(format_str.format("Current Value:", old_value)), "", "Enter new value for this property", ]) while True: new_value = self.UI.get_input(prompt, default_value=old_value) if new_value == old_value: logger.info("Value for property '{0}' is unchanged" .format(key)) break else: try: new_value = self.vm.set_property_value(key, new_value) logger.info("Successfully updated property '{0}' " "value to '{1}'".format(key, new_value)) # Refresh! pa = self.vm.environment_properties break except ValueUnsupportedError as e: logger.error(e) continue continue
[docs] def create_subparser(self, parent): """Add subparser for the CLI of this submodule. :param object parent: Subparser grouping object returned by :func:`ArgumentParser.add_subparsers` :returns: ``('edit-properties', subparser)`` """ p = parent.add_parser( 'edit-properties', add_help=False, help="""Edit environment properties of an OVF""", usage=self.UI.fill_usage("edit-properties", [ "PACKAGE -p KEY1=VALUE1 [KEY2=VALUE2 ...] [-o OUTPUT]", "PACKAGE -c CONFIG_FILE [-o OUTPUT]", "PACKAGE [-o OUTPUT]", ]), description=""" Configure environment properties of the given OVF or OVA. The user may specify key-value pairs as command-line arguments or may provide a config-file to read from. If neither are specified, the program will run interactively.""") p.add_argument('PACKAGE', help="""OVF descriptor or OVA file to edit""") g = p.add_argument_group("general options") g.add_argument('-h', '--help', action='help', help="""Show this help message and exit""") g.add_argument('-o', '--output', help="Name/path of new OVF/OVA package to create " "instead of updating the existing OVF") g = p.add_argument_group("property setting options") g.add_argument('-c', '--config-file', help="Read configuration CLI from this text file and " "generate generic properties for each line of CLI") g.add_argument('-p', '--properties', action='append', nargs='+', metavar=('KEY1=VALUE1', 'KEY2=VALUE2'), help="Set the given property key-value pair(s). " "This argument may be repeated as needed to specify " "multiple properties to edit.") p.set_defaults(instance=self) return 'edit-properties', p