Skip to content

Commit

Permalink
Merge branch 'release/1.8.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
glennmatthews committed Jan 18, 2017
2 parents 4e9990f + c406cb1 commit 01eafac
Show file tree
Hide file tree
Showing 17 changed files with 395 additions and 65 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,27 @@ Change Log
All notable changes to the COT project will be documented in this file.
This project adheres to `Semantic Versioning`_.

`1.8.2`_ - 2017-01-18
---------------------

**Fixed**

- Issue (`#58`_) where various multi-value CLI options for the
``edit-hardware`` and ``inject-config`` commands did not append properly.
- Issue in which explicitly specified NIC names were being overwritten by
names auto-derived from network names when attempting to set both NIC name
and network names in a single ``cot edit-hardware`` call.
- ``cot edit-properties`` again accepts property values containing the characters
``+`` and ``=`` (`#59`_).

**Added**

- COT can now detect the presence of an .ovf file in a TAR archive even if
the archive is not named ``*.ova*`` and even if the .ovf file is not the first
file in the archive as specified by the OVF specification. This allows
(read-only at present) handling of VirtualBox Vagrant .box files,
which are approximately equivalent to non-standards-compliant OVAs.

`1.8.1`_ - 2016-11-12
---------------------

Expand Down Expand Up @@ -573,6 +594,8 @@ Initial public release.
.. _#52: https://github.com/glennmatthews/cot/issues/52
.. _#53: https://github.com/glennmatthews/cot/issues/53
.. _#54: https://github.com/glennmatthews/cot/issues/54
.. _#58: https://github.com/glennmatthews/cot/issues/58
.. _#59: https://github.com/glennmatthews/cot/issues/59

.. _Semantic Versioning: http://semver.org/
.. _`PEP 8`: https://www.python.org/dev/peps/pep-0008/
Expand Down Expand Up @@ -604,6 +627,7 @@ Initial public release.
.. _napoleon: http://www.sphinx-doc.org/en/latest/ext/napoleon.html

.. _Unreleased: https://github.com/glennmatthews/cot/compare/master...develop
.. _1.8.2: https://github.com/glennmatthews/cot/compare/v1.8.1...v1.8.2
.. _1.8.1: https://github.com/glennmatthews/cot/compare/v1.8.0...v1.8.1
.. _1.8.0: https://github.com/glennmatthews/cot/compare/v1.7.4...v1.8.0
.. _1.7.4: https://github.com/glennmatthews/cot/compare/v1.7.3...v1.7.4
Expand Down
3 changes: 2 additions & 1 deletion COT/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ def args_to_dict(args):
# When argparse is using both "nargs='+'" and "action=append",
# this allows some flexibility in the user CLI, but the parsed
# output is a nested list of lists. E.g., "-a 1 2 -a 3" would parse
# as [[1, 2][3]] rather than the desired [1, 2, 3].
# as [[1, 2], [3]] rather than the desired [1, 2, 3].
# Flatten it back out before we pass it through to the submodule!
if (isinstance(value, list) and
all(isinstance(v, list) for v in value)):
Expand Down Expand Up @@ -683,5 +683,6 @@ def main():
"""Launch COT from the CLI."""
CLI().run(sys.argv[1:])


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions COT/data_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def canonicalize_ide_subtype(subtype):
],
re.IGNORECASE)


_NIC_MAPPINGS = [
("e1000e", 'E1000e'),
("e1000", 'E1000'),
Expand Down
36 changes: 21 additions & 15 deletions COT/edit_hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# edit_hardware.py - Implements "edit-hardware" sub-command
#
# September 2013, Glenn F. Matthews
# Copyright (c) 2013-2016 the COT project developers.
# Copyright (c) 2013-2017 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.
#
Expand Down Expand Up @@ -447,7 +447,6 @@ def _run_update_nics(self):
vm = self.vm

nics_dict = vm.get_nic_count(self.profiles)
max_nics = max(nics_dict.values())
if self.nics is not None:
for (profile, count) in nics_dict.items():
if self.nics < count:
Expand All @@ -461,16 +460,9 @@ def _run_update_nics(self):
if self.nic_types is not None:
vm.set_nic_types(self.nic_types, self.profiles)

nics_dict = vm.get_nic_count(self.profiles)
max_nics = max(nics_dict.values())

if self.mac_addresses_list is not None:
vm.set_nic_mac_addresses(self.mac_addresses_list, self.profiles)

if self.nic_names is not None:
names = expand_list_wildcard(self.nic_names, max_nics)
vm.set_nic_names(names, self.profiles)

def _run_update_networks(self):
"""Handle network changes. Helper for :meth:`run`."""
vm = self.vm
Expand Down Expand Up @@ -519,6 +511,15 @@ def _run_update_networks(self):

vm.set_nic_networks(new_networks, self.profiles)

def _run_update_nic_names(self):
"""Update NIC names. Helper for :meth:`run`."""
if self.nic_names is not None:
vm = self.vm
nics_dict = vm.get_nic_count(self.profiles)
max_nics = max(nics_dict.values())
names = expand_list_wildcard(self.nic_names, max_nics)
vm.set_nic_names(names, self.profiles)

def _run_update_serial(self):
"""Handle serial port changes. Helper for :meth:`run`."""
if self.serial_ports is not None:
Expand Down Expand Up @@ -573,6 +574,10 @@ def run(self):

self._run_update_networks()

# Update NIC names *after* updating networks, as we don't want
# network-induced name changes to overwrite user-specified names.
self._run_update_nic_names()

self._run_update_serial()

if self.scsi_subtypes is not None:
Expand Down Expand Up @@ -637,12 +642,13 @@ def create_subparser(self):
g.add_argument('-o', '--output',
help="Name/path of new OVF/OVA package to create "
"instead of updating the existing OVF")
g.add_argument('-v', '--virtual-system-type', nargs='+',
g.add_argument('-v', '--virtual-system-type',
action='append', nargs='+',
type=no_whitespace, metavar=('TYPE', 'TYPE2'),
help="Change virtual system type(s) supported by "
"this OVF/OVA package.")
g.add_argument('-p', '--profiles', nargs='+', type=no_whitespace,
metavar=('PROFILE', 'PROFILE2'),
g.add_argument('-p', '--profiles', action='append', nargs='+',
type=no_whitespace, metavar=('PROFILE', 'PROFILE2'),
help="Make hardware changes only under the given "
"configuration profile(s). (default: changes apply "
"to all profiles)")
Expand All @@ -662,7 +668,7 @@ def create_subparser(self):

g.add_argument('-n', '--nics', type=non_negative_int,
help="Set the number of NICs.")
g.add_argument('--nic-types', nargs='+',
g.add_argument('--nic-types', action='append', nargs='+',
metavar=('TYPE', 'TYPE2'),
help="Set the hardware type(s) for all NICs. "
"(default: do not change existing NICs, and new "
Expand All @@ -671,11 +677,11 @@ def create_subparser(self):
metavar=('NAME1', 'NAME2'),
help="Specify a list of one or more NIC names or "
"patterns to apply to NIC devices. See Notes.")
g.add_argument('-N', '--nic-networks', nargs='+',
g.add_argument('-N', '--nic-networks', action='append', nargs='+',
metavar=('NETWORK', 'NETWORK2'),
help="Specify a series of one or more network names "
"or patterns to map NICs to. See Notes.")
g.add_argument('--network-descriptions', nargs='+',
g.add_argument('--network-descriptions', action='append', nargs='+',
metavar=('NAME1', 'NAME2'),
help="Specify a list of one or more network "
"descriptions or patterns to apply to the networks. "
Expand Down
50 changes: 46 additions & 4 deletions COT/edit_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# edit_properties.py - Implements "edit-properties" sub-command
#
# August 2013, Glenn F. Matthews
# Copyright (c) 2013-2016 the COT project developers.
# Copyright (c) 2013-2017 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.
#
Expand Down Expand Up @@ -88,14 +88,56 @@ def config_file(self, value):

@property
def properties(self):
"""List of property (key, value, type) tuples to update."""
r"""List of property (key, value, type) tuples to update.
Properties may also be set from strings (such as by CLI)
with the syntax ``<key>[=<value>][+<type>]``.
Examples:
::
>>> from COT.ui_shared import UI
>>> i = COTEditProperties(UI())
>>> i.properties
[]
>>> i.properties = [
... "no_value",
... "key=value",
... "string_type+string",
... "full-type=yes+boolean",
... ]
>>> print("\n".join([str(p) for p in i.properties]))
('no_value', None, None)
('key', 'value', None)
('string_type', None, 'string')
('full-type', 'yes', 'boolean')
>>> i.properties = [
... "ssh=autopubkey=ssh-rsa AA...q+t0...Tuw== root@MASTER",
... "tricky=+foo",
... "tricky_value=++foo==++",
... "trickiest=bar+foo=hello+boolean",
... ]
>>> print("\n".join([str(p) for p in i.properties]))
('ssh', 'autopubkey=ssh-rsa AA...q+t0...Tuw== root@MASTER', None)
('tricky', '', 'foo')
('tricky_value', '++foo==++', None)
('trickiest', 'bar+foo=hello', 'boolean')
"""
return self._properties

@properties.setter
def properties(self, value):
new_value = []
for prop in value:
match = re.match(r"^([^=+]+?)(=[^=+]*?)?(\+[^=+]*?)?$", prop)
# While our string is delimited by '+' and '=' as "key=value+type",
# those characters may also be included in the actual value,
# as in an SSH private key:
# 'autopubkey=ssh-rsa AA...gl/p...q+t0...Tuw== root@MASTER'
# or other base64-encoded value ([A-Za-z0-9+/=] or [A-Za-z0-9-_=])
# So we have to be "clever" in how we parse things.
# To handle ambiguity, we presume that the characters '+' and '='
# MAY appear in a value string but NOT in a key or prop_type.
match = re.match(r"^([^=+]+)(=.*?)?(\+[^=+]+?)?$", prop)
if not match:
raise InvalidInputError("Invalid property '{0}' - properties "
"must be in 'key[=value][+type]' form"
Expand All @@ -113,7 +155,7 @@ def properties(self, value):

@property
def transports(self):
"""Transport mechanism(s) for environment properties.."""
"""Transport mechanism(s) for environment properties."""
return self._transports

_KNOWN_TRANSPORTS = {
Expand Down
9 changes: 8 additions & 1 deletion COT/file_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ def __init__(self, tarfile_path, filename):
if not tarfile.is_tarfile(tarfile_path):
raise IOError("{0} is not a valid TAR file.".format(tarfile_path))
self.tarfile_path = tarfile_path
self.filename = filename
self.filename = os.path.normpath(filename)
if not self.exists:
raise IOError("{0} does not exist in {1}"
.format(filename, tarfile_path))
Expand Down Expand Up @@ -187,6 +187,13 @@ def exists(self):
tarf.getmember(self.filename)
return True
except KeyError:
# Perhaps an issue with 'foo.txt' versus './foo.txt'?
for mem in tarf.getmembers():
if os.path.normpath(mem.name) == self.filename:
logger.verbose("Found {0} at {1} in TAR file"
.format(self.filename, mem.name))
self.filename = mem.name
return True
return False

@property
Expand Down
9 changes: 6 additions & 3 deletions COT/helpers/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,8 +264,11 @@ def version(self):
output = self.call(self._version_args, require_success=False)
match = re.search(self._version_regexp, output)
if not match:
raise RuntimeError("Unable to find version number in output:"
"\n{0}".format(output))
raise RuntimeError(
"Unable to find version number for '{0}' in output from"
" '{0} {1}':\n{2}".format(self.name,
' '.join(self._version_args),
output))
self._version = StrictVersion(match.group(1))
return self._version

Expand Down Expand Up @@ -301,7 +304,7 @@ def call(self, args,
"Please install it and/or check your $PATH."
.format(self.name))
self.install()
args.insert(0, self.name)
args = [self.name] + args
if capture_output:
return check_output(args, **kwargs)
else:
Expand Down
2 changes: 1 addition & 1 deletion COT/inject_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def create_subparser(self):
p.add_argument('-s', '--secondary-config-file',
help="Text file to embed as secondary configuration"
" (currently only used for IOS XR admin config)")
p.add_argument('-e', '--extra-files', nargs='+',
p.add_argument('-e', '--extra-files', action='append', nargs='+',
metavar=('EXTRA_FILE', 'EXTRA_FILE2'),
help="Additional file(s) to include as-is")
p.add_argument('PACKAGE',
Expand Down
29 changes: 15 additions & 14 deletions COT/ovf/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# item.py - OVFItem class
#
# June 2016, Glenn F. Matthews
# Copyright (c) 2013-2016 the COT project developers.
# Copyright (c) 2013-2017 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.
#
Expand Down Expand Up @@ -280,10 +280,11 @@ def add_item(self, item):
def value_add_wildcards(self, name, value, profiles):
"""Add wildcard placeholders to a string that may need updating.
If the ElementName or Description references the VirtualQuantity,
Connection, or ResourceSubType, replace that reference with a
placeholder that we can regenerate at output time. That way, if the
VirtualQuantity or ResourceSubType changes, these can change too.
If the Description references the ElementName, or the
ElementName or Description references the VirtualQuantity,
Connection, or ResourceSubType, replace such references with a
placeholder that we can regenerate at output time. That way, if
any of the linked items change, these strings can change too.
Args:
name (str): Property name
Expand All @@ -296,6 +297,11 @@ def value_add_wildcards(self, name, value, profiles):
.. seealso::
:meth:`value_replace_wildcards`
"""
if name == self.ITEM_DESCRIPTION:
en_val = self.get_value(self.ELEMENT_NAME, profiles)
if en_val is not None:
value = re.sub(en_val, "_EN_", value)

if name == self.ELEMENT_NAME or name == self.ITEM_DESCRIPTION:
vq_val = self.get_value(self.VIRTUAL_QUANTITY, profiles)
if vq_val is not None:
Expand All @@ -309,11 +315,6 @@ def value_add_wildcards(self, name, value, profiles):
if conn_val is not None:
value = re.sub(conn_val, "_CONN_", value)

# Similarly, if the Description references the ElementName...
if name == self.ITEM_DESCRIPTION:
en_val = self.get_value(self.ELEMENT_NAME, profiles)
if en_val is not None:
value = re.sub(en_val, "_EN_", value)
return value

def value_replace_wildcards(self, name, value, profiles):
Expand All @@ -332,6 +333,10 @@ def value_replace_wildcards(self, name, value, profiles):
"""
if not value:
return value
if name == self.ITEM_DESCRIPTION:
en_val = self._get_value(self.ELEMENT_NAME, profiles)
if en_val is not None:
value = re.sub("_EN_", str(en_val), str(value))
if name == self.ELEMENT_NAME or name == self.ITEM_DESCRIPTION:
# To regenerate text that depends on these values:
rst_val = self._get_value(self.RESOURCE_SUB_TYPE, profiles)
Expand All @@ -345,10 +350,6 @@ def value_replace_wildcards(self, name, value, profiles):
value = re.sub("_VQ_", str(vq_val), str(value))
if conn_val is not None:
value = re.sub("_CONN_", str(conn_val), str(value))
if name == self.ITEM_DESCRIPTION:
en_val = self._get_value(self.ELEMENT_NAME, profiles)
if en_val is not None:
value = re.sub("_EN_", str(en_val), str(value))
return value

def _set_new_property(self, name, value, profiles):
Expand Down
Loading

0 comments on commit 01eafac

Please sign in to comment.