#!/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()
vm = self.vm
if self.config_file is not None:
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 = 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?
vm.set_property_value(key, value)
if self.config_file is None and self.properties is None:
# Interactive mode!
self.edit_properties_interactive(vm)
[docs] def edit_properties_interactive(self, vm):
"""Present an interactive UI for the user to edit properties.
:param vm: TODO shouldn't be necessary - use self.vm?
"""
wrapper = textwrap.TextWrapper(initial_indent='',
subsequent_indent=' ')
format_str = '{0:15} "{1}"'
pa = vm.get_property_array()
while True:
print("")
print("Please choose a property to edit:")
i = 1
for p in pa:
print("""{i:4d}) {label:40} ({key})"""
.format(i=i, label='"'+p['label']+'"', key=p['key']))
i += 1
input = self.UI.get_input("""Enter property number to edit, """
"""or "q" to quit and write changes""",
default_value="q")
if input is None or input == 'q' or input == 'Q':
break
input = int(input)
if input <= 0 or input > len(pa):
continue
print("")
p = pa[input-1]
key = p['key']
old_value = p['value']
print(wrapper.fill(format_str.format("Key:", p['key'])))
print(wrapper.fill(format_str.format("Label:", p['label'])))
print(wrapper.fill(format_str.format("Description:",
p['description'])))
print(wrapper.fill(format_str.format("Type:", p['type'])))
print(wrapper.fill(format_str.format("Qualifiers:",
p['qualifiers'])))
print(wrapper.fill(format_str.format("Current Value:", old_value)))
print("")
while True:
new_value = self.UI.get_input("New value for this property",
default_value=old_value)
if new_value == old_value:
print("(no change)")
break
else:
try:
new_value = vm.set_property_value(key, new_value)
print("""Successfully set the value of """
"""property "{0}" to "{1}" """
.format(key, new_value))
break
except ValueUnsupportedError as e:
print(e)
print("")
[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