Use resource description as default description property

We allow users to provide a description for a resource that shows up in
the Heat API. Many resource types also allow users to add descriptions,
and these have typically been exposed as properties rather than by
trying to read the resource's own description.

To prevent the need to duplicate information, use the resource's
description as the default for any top-level properties named
'description'.

One downside of this is that any changes to the resource description
could cause updates to the resource. (However, there are no issues
specific to updgrades, because the subsitution is also done on the
previous properties.) To guard against the worst of this, only enable
this behaviour if the description can be updated without resource
replacement.

Change-Id: I56560d014a02b5f2ddbc08689d39147fbe4ffca4
This commit is contained in:
Zane Bitter 2019-01-04 14:34:50 +13:00
parent 6f0f14a3a0
commit da974ed216
4 changed files with 36 additions and 2 deletions

View File

@ -284,6 +284,8 @@ resources:
default = nodes.literal('', json.dumps(prop.default))
para.append(default)
definition.append(para)
elif prop_key == 'description' and prop.update_allowed:
para = nodes.line('', _('Defaults to the resource description'))
for constraint in prop.constraints:
para = nodes.line('', str(constraint))

View File

@ -377,7 +377,8 @@ class Property(object):
class Properties(collections.Mapping):
def __init__(self, schema, data, resolver=lambda d: d, parent_name=None,
context=None, section=None, translation=None):
context=None, section=None, translation=None,
rsrc_description=None):
self.props = dict((k, Property(s, k, context, path=parent_name))
for k, s in schema.items())
self.resolve = resolver
@ -387,6 +388,7 @@ class Properties(collections.Mapping):
self.context = context
self.translation = (trans.Translation(properties=self)
if translation is None else translation)
self.rsrc_description = rsrc_description or None
def update_translation(self, rules, client_resolve=True,
ignore_resolve_error=False):
@ -507,6 +509,10 @@ class Properties(collections.Mapping):
translation=self.translation)
elif prop.required():
raise ValueError(_('Property %s not assigned') % key)
elif key == 'description' and prop.schema.update_allowed:
return self.rsrc_description
else:
return None
def __getitem__(self, key):
return self._get_property_value(key)

View File

@ -306,7 +306,8 @@ class ResourceDefinition(object):
"""
props = properties.Properties(schema, self._properties or {},
function.resolve, context=context,
section=PROPERTIES)
section=PROPERTIES,
rsrc_description=self.description)
props.update_translation(self._rules, self._client_resolve)
return props

View File

@ -23,6 +23,7 @@ from heat.engine import parameters
from heat.engine import plugin_manager
from heat.engine import properties
from heat.engine import resources
from heat.engine import rsrc_defn
from heat.engine import support
from heat.engine import translation
from heat.tests import common
@ -1651,6 +1652,30 @@ class PropertiesTest(common.HeatTestCase):
props_b = properties.Properties(schema, {'foo': 1})
self.assertTrue(props_a != props_b)
def test_description_substitution(self):
schema = {
'description': properties.Schema('String',
update_allowed=True),
'not_description': properties.Schema('String',
update_allowed=True),
}
blank_rsrc = rsrc_defn.ResourceDefinition('foo', 'FooResource', {},
description='Foo resource')
bar_rsrc = rsrc_defn.ResourceDefinition('foo', 'FooResource',
{'description': 'bar'},
description='Foo resource')
blank_props = blank_rsrc.properties(schema)
self.assertEqual('Foo resource', blank_props['description'])
self.assertEqual(None, blank_props['not_description'])
replace_schema = {'description': properties.Schema('String')}
empty_props = blank_rsrc.properties(replace_schema)
self.assertEqual(None, empty_props['description'])
bar_props = bar_rsrc.properties(schema)
self.assertEqual('bar', bar_props['description'])
class PropertiesValidationTest(common.HeatTestCase):
def test_required(self):