Allow lists and strings for Json parameters via provider resources
Currently we have a somewhat bogus internal equivalence between the "json" parameter type and the MAP property type. This is a problem when exposing json parameters via provider resources, because the schema of the exposed property does not exactly match the capabilities of the underlying parameter. Specifically, json parameters can take strings (json serialized list or map), and also lists and maps directly. Currently, we can only pass maps, which means some data, for example a list of maps (which is possible when interacting directly with a json parameter) is not possible when using the same parameter abstracted via a TemplateResource. To work around this add a flag which relaxes the type rules and enables some coercion of map values, only when they're going via a schema derived from a json parameter. Change-Id: I8eff36c4343a07b644b84aa9f2f74eceeb62b4a9 Closes-Bug: #1420196
This commit is contained in:
parent
1dc46db686
commit
413fde3247
@ -13,6 +13,7 @@
|
||||
|
||||
import collections
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
@ -58,13 +59,15 @@ class Schema(constr.Schema):
|
||||
implemented=True,
|
||||
update_allowed=False,
|
||||
immutable=False,
|
||||
support_status=support.SupportStatus()):
|
||||
support_status=support.SupportStatus(),
|
||||
allow_conversion=False):
|
||||
super(Schema, self).__init__(data_type, description, default,
|
||||
schema, required, constraints)
|
||||
self.implemented = implemented
|
||||
self.update_allowed = update_allowed
|
||||
self.immutable = immutable
|
||||
self.support_status = support_status
|
||||
self.allow_conversion = allow_conversion
|
||||
# validate structural correctness of schema itself
|
||||
self.validate()
|
||||
|
||||
@ -146,6 +149,13 @@ class Schema(constr.Schema):
|
||||
param.BOOLEAN: cls.BOOLEAN
|
||||
}
|
||||
|
||||
# allow_conversion allows slightly more flexible type conversion
|
||||
# where property->parameter types don't align, primarily when
|
||||
# a json parameter value is passed via a Map property, which requires
|
||||
# some coercion to pass strings or lists (which are both valid for
|
||||
# Json parameters but not for Map properties).
|
||||
allow_conversion = param.type == param.MAP
|
||||
|
||||
# make update_allowed true by default on TemplateResources
|
||||
# as the template should deal with this.
|
||||
return cls(data_type=param_type_map.get(param.type, cls.MAP),
|
||||
@ -153,7 +163,8 @@ class Schema(constr.Schema):
|
||||
required=param.required,
|
||||
constraints=param.constraints,
|
||||
update_allowed=True,
|
||||
immutable=False)
|
||||
immutable=False,
|
||||
allow_conversion=allow_conversion)
|
||||
|
||||
def allowed_param_prop_type(self):
|
||||
"""
|
||||
@ -269,6 +280,14 @@ class Property(object):
|
||||
if value is None:
|
||||
value = self.has_default() and self.default() or {}
|
||||
if not isinstance(value, collections.Mapping):
|
||||
# This is to handle passing Lists via Json parameters exposed
|
||||
# via a provider resource, in particular lists-of-dicts which
|
||||
# cannot be handled correctly via comma_delimited_list
|
||||
if self.schema.allow_conversion:
|
||||
if isinstance(value, six.string_types):
|
||||
return value
|
||||
elif isinstance(value, collections.Sequence):
|
||||
return jsonutils.dumps(value)
|
||||
raise TypeError(_('"%s" is not a map') % value)
|
||||
|
||||
return dict(self._get_children(six.iteritems(value),
|
||||
|
@ -11,6 +11,7 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
import testtools
|
||||
|
||||
@ -468,6 +469,7 @@ class PropertySchemaTest(testtools.TestCase):
|
||||
self.assertTrue(schema.required)
|
||||
self.assertIsNone(schema.default)
|
||||
self.assertEqual(0, len(schema.constraints))
|
||||
self.assertFalse(schema.allow_conversion)
|
||||
|
||||
def test_from_number_param_min(self):
|
||||
default = "42"
|
||||
@ -546,6 +548,7 @@ class PropertySchemaTest(testtools.TestCase):
|
||||
self.assertIsNone(schema.default)
|
||||
self.assertFalse(schema.required)
|
||||
self.assertEqual(1, len(schema.constraints))
|
||||
self.assertFalse(schema.allow_conversion)
|
||||
|
||||
allowed_constraint = schema.constraints[0]
|
||||
|
||||
@ -563,6 +566,7 @@ class PropertySchemaTest(testtools.TestCase):
|
||||
self.assertEqual(properties.Schema.LIST, schema.type)
|
||||
self.assertIsNone(schema.default)
|
||||
self.assertFalse(schema.required)
|
||||
self.assertFalse(schema.allow_conversion)
|
||||
|
||||
def test_from_json_param(self):
|
||||
param = parameters.Schema.from_dict('name', {
|
||||
@ -575,6 +579,7 @@ class PropertySchemaTest(testtools.TestCase):
|
||||
self.assertEqual(properties.Schema.MAP, schema.type)
|
||||
self.assertIsNone(schema.default)
|
||||
self.assertFalse(schema.required)
|
||||
self.assertTrue(schema.allow_conversion)
|
||||
|
||||
|
||||
class PropertyTest(testtools.TestCase):
|
||||
@ -862,6 +867,12 @@ class PropertyTest(testtools.TestCase):
|
||||
p = properties.Property({'Type': 'Map'})
|
||||
self.assertRaises(TypeError, p.get_value, ['foo'])
|
||||
|
||||
def test_map_allow_conversion(self):
|
||||
p = properties.Property({'Type': 'Map'})
|
||||
p.schema.allow_conversion = True
|
||||
self.assertEqual('foo', p.get_value('foo'))
|
||||
self.assertEqual(jsonutils.dumps(['foo']), p.get_value(['foo']))
|
||||
|
||||
def test_map_schema_good(self):
|
||||
map_schema = {'valid': {'Type': 'Boolean'}}
|
||||
p = properties.Property({'Type': 'Map', 'Schema': map_schema})
|
||||
|
Loading…
Reference in New Issue
Block a user