diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ba1497f..f4773ae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,8 @@ This project adheres to `Semantic Versioning`_. - 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** @@ -593,6 +595,7 @@ Initial public release. .. _#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/ diff --git a/COT/edit_properties.py b/COT/edit_properties.py index d9106af..5648491 100644 --- a/COT/edit_properties.py +++ b/COT/edit_properties.py @@ -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. # @@ -88,14 +88,57 @@ def config_file(self, value): @property def properties(self): - """List of property (key, value, type) tuples to update.""" + """List of property (key, value, type) tuples to update. + + Properties may also be set from strings (such as by CLI) + with the syntax ``[=][+]``. + + 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(["{0:15} {1:10} {2}".format(*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" @@ -113,7 +156,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 = { diff --git a/COT/tests/test_doctests.py b/COT/tests/test_doctests.py index 81fe9e2..2265bad 100644 --- a/COT/tests/test_doctests.py +++ b/COT/tests/test_doctests.py @@ -3,7 +3,7 @@ # test_doctests.py - test runner for COT doctests # # July 2016, Glenn F. Matthews -# Copyright (c) 2016 the COT project developers. +# Copyright (c) 2016-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. # @@ -29,5 +29,6 @@ def load_tests(*_): suite.addTests(DocTestSuite('COT.cli')) suite.addTests(DocTestSuite('COT.deploy')) suite.addTests(DocTestSuite('COT.edit_hardware')) + suite.addTests(DocTestSuite('COT.edit_properties')) suite.addTests(DocTestSuite('COT.file_reference')) return suite