From da974ed216625ff261f3ddeb9d2dc8d412fff17c Mon Sep 17 00:00:00 2001 From: Zane Bitter Date: Fri, 4 Jan 2019 14:34:50 +1300 Subject: [PATCH] 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 --- doc/source/ext/resources.py | 2 ++ heat/engine/properties.py | 8 +++++++- heat/engine/rsrc_defn.py | 3 ++- heat/tests/test_properties.py | 25 +++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/doc/source/ext/resources.py b/doc/source/ext/resources.py index 9def933f3e..0a004d48bc 100644 --- a/doc/source/ext/resources.py +++ b/doc/source/ext/resources.py @@ -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)) diff --git a/heat/engine/properties.py b/heat/engine/properties.py index 14d688fa1c..3e3cf5a437 100644 --- a/heat/engine/properties.py +++ b/heat/engine/properties.py @@ -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) diff --git a/heat/engine/rsrc_defn.py b/heat/engine/rsrc_defn.py index 6de61c3732..2933631e9b 100644 --- a/heat/engine/rsrc_defn.py +++ b/heat/engine/rsrc_defn.py @@ -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 diff --git a/heat/tests/test_properties.py b/heat/tests/test_properties.py index cf5dd14ece..166856617b 100644 --- a/heat/tests/test_properties.py +++ b/heat/tests/test_properties.py @@ -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):