Merge pull request #25515 from s0undt3ch/2015.8

salt.utils.schema fixes
This commit is contained in:
Mike Place 2015-07-20 11:17:35 -06:00
commit 25f910e472
2 changed files with 339 additions and 69 deletions

View File

@ -546,7 +546,7 @@ class Schema(six.with_metaclass(SchemaMeta, object)):
ordering = []
serialized['type'] = 'object'
properties = OrderedDict()
after_items_update = OrderedDict()
cls.after_items_update = []
for name in cls._order:
skip_order = False
if name in cls._sections:
@ -558,8 +558,11 @@ class Schema(six.with_metaclass(SchemaMeta, object)):
properties.update(serialized_section['properties'])
if 'x-ordering' in serialized_section:
ordering.extend(serialized_section['x-ordering'])
if 'required' in serialized:
if 'required' in serialized_section:
required.extend(serialized_section['required'])
if hasattr(section, 'after_items_update'):
cls.after_items_update.extend(section.after_items_update)
skip_order = True
else:
# Store it as a configuration section
properties[name] = serialized_section
@ -568,7 +571,8 @@ class Schema(six.with_metaclass(SchemaMeta, object)):
config = cls._items[name]
# Handle the configuration items defined in the class instance
if config.__flatten__ is True:
after_items_update.update(config.serialize())
serialized_config = config.serialize()
cls.after_items_update.append(serialized_config)
skip_order = True
else:
properties[name] = config.serialize()
@ -586,7 +590,15 @@ class Schema(six.with_metaclass(SchemaMeta, object)):
serialized['properties'] = properties
# Update the serialized object with any items to include after properties
serialized.update(after_items_update)
if cls.after_items_update:
after_items_update = {}
for entry in cls.after_items_update:
name, data = next(six.iteritems(entry))
if name in after_items_update:
after_items_update[name].extend(data)
else:
after_items_update[name] = data
serialized.update(after_items_update)
if required:
# Only include required if not empty
@ -599,7 +611,12 @@ class Schema(six.with_metaclass(SchemaMeta, object)):
@classmethod
def as_requirements_item(cls):
return RequirementsItem(requirements=cls())
serialized_schema = cls.serialize()
required = serialized_schema.get('required', [])
for name in serialized_schema['properties']:
if name not in required:
required.append(name)
return RequirementsItem(requirements=required)
#@classmethod
#def render_as_rst(cls):
@ -1253,34 +1270,29 @@ class RequirementsItem(SchemaItem):
raise RuntimeError(
'The passed requirements must not be empty'
)
if not isinstance(self.requirements, (Schema, SchemaItem, list, tuple, set)):
if not isinstance(self.requirements, (SchemaItem, list, tuple, set)):
raise RuntimeError(
'The passed requirements must be passed as a list, tuple, '
'set SchemaItem, BaseSchemaItem or Schema, not \'{0}\''.format(self.requirements)
'set SchemaItem or BaseSchemaItem, not \'{0}\''.format(self.requirements)
)
if not isinstance(self.requirements, (SchemaItem, Schema)):
if not isinstance(self.requirements, SchemaItem):
if not isinstance(self.requirements, list):
self.requirements = list(self.requirements)
for idx, item in enumerate(self.requirements):
if not isinstance(item, (six.string_types, (SchemaItem, Schema))):
if not isinstance(item, (six.string_types, SchemaItem)):
raise RuntimeError(
'The passed requirement at the {0} index must be of type '
'str or Schema, not \'{1}\''.format(idx, type(item))
'str or SchemaItem, not \'{1}\''.format(idx, type(item))
)
def serialize(self):
if isinstance(self.requirements, Schema):
requirements = self.requirements.serialize()['required']
elif isinstance(self.requirements, SchemaItem):
if isinstance(self.requirements, SchemaItem):
requirements = self.requirements.serialize()
else:
requirements = []
for requirement in self.requirements:
if isinstance(requirement, Schema):
requirements.extend(requirement.serialize()['required'])
continue
if isinstance(requirement, SchemaItem):
requirements.append(requirement.serialize())
continue

View File

@ -80,6 +80,43 @@ class ConfigTestCase(TestCase):
}
)
class MergedConfigClass(schema.Schema):
thirsty = schema.BooleanItem(title='Thirsty', description='Are you thirsty?', required=True)
merge_subclassed = SubClassedConfig(flatten=True)
expected = {
'$schema': 'http://json-schema.org/draft-04/schema#',
'type': 'object',
'properties': {
'thirsty': {
'type': 'boolean',
'description': 'Are you thirsty?',
'title': 'Thirsty'
},
'base': {
'default': True,
'type': 'boolean',
'title': 'base'
},
'hungry': {
'type': 'boolean',
'description': 'Are you hungry?',
'title': 'Hungry'
}
},
'required': ['thirsty', 'base', 'hungry'],
'x-ordering': ['thirsty', 'base', 'hungry'],
'additionalProperties': False,
}
self.assertDictContainsSubset(
MergedConfigClass.serialize()['properties'],
expected['properties']
)
self.assertDictContainsSubset(
expected,
MergedConfigClass.serialize()
)
def test_configuration_items_order(self):
class One(schema.Schema):
@ -103,7 +140,7 @@ class ConfigTestCase(TestCase):
title='SSH Private Key',
description='The path to an SSH private key which will be used '
'to authenticate on the deployed VMs',
required=True)
)
class SSHKeyNamesSchema(schema.Schema):
ssh_key_names = schema.StringItem(
@ -111,7 +148,7 @@ class ConfigTestCase(TestCase):
description='The names of an SSH key being managed on '
'Digital Ocean account which will be used to '
'authenticate on the deployed VMs',
required=True)
)
class Requirements(BaseRequirements):
title = 'Digital Ocean'
@ -132,54 +169,277 @@ class ConfigTestCase(TestCase):
ssh_key_file = SSHKeyFileSchema(flatten=True)
ssh_key_names = SSHKeyNamesSchema(flatten=True)
expexcted = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Digital Ocean",
"description": "Digital Ocean Cloud VM configuration requirements.",
"type": "object",
"properties": {
"driver": {
"default": "digital_ocean",
"format": "hidden",
"type": "string",
"title": "driver"
},
"personal_access_token": {
"type": "string",
"description": "This is the API access token which can be "
"generated under the API/Application on your account",
"title": "Personal Access Token"
},
"ssh_key_file": {
"type": "string",
"description": "The path to an SSH private key which will "
"be used to authenticate on the deployed VMs",
"title": "SSH Private Key"
},
"ssh_key_names": {
"type": "string",
"description": "The names of an SSH key being managed on Digital "
"Ocean account which will be used to authenticate "
"on the deployed VMs",
"title": "SSH Key Names"
}
expected = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Digital Ocean",
"description": "Digital Ocean Cloud VM configuration requirements.",
"type": "object",
"properties": {
"driver": {
"default": "digital_ocean",
"format": "hidden",
"type": "string",
"title": "driver"
},
"anyOf": [
{"required": ["ssh_key_file"]},
{"required": ["ssh_key_names"]}
],
"required": [
"personal_access_token"
],
"x-ordering": [
"driver",
"personal_access_token",
"ssh_key_file",
"ssh_key_names",
],
"additionalProperties": False
}
self.assertDictEqual(expexcted, Requirements.serialize())
"personal_access_token": {
"type": "string",
"description": "This is the API access token which can be "
"generated under the API/Application on your account",
"title": "Personal Access Token"
},
"ssh_key_file": {
"type": "string",
"description": "The path to an SSH private key which will "
"be used to authenticate on the deployed VMs",
"title": "SSH Private Key"
},
"ssh_key_names": {
"type": "string",
"description": "The names of an SSH key being managed on Digital "
"Ocean account which will be used to authenticate "
"on the deployed VMs",
"title": "SSH Key Names"
}
},
"anyOf": [
{"required": ["ssh_key_file"]},
{"required": ["ssh_key_names"]}
],
"required": [
"personal_access_token"
],
"x-ordering": [
"driver",
"personal_access_token",
"ssh_key_file",
"ssh_key_names",
],
"additionalProperties": False
}
self.assertDictEqual(expected, Requirements.serialize())
class Requirements2(BaseRequirements):
title = 'Digital Ocean'
description = 'Digital Ocean Cloud VM configuration requirements.'
personal_access_token = schema.StringItem(
title='Personal Access Token',
description='This is the API access token which can be generated '
'under the API/Application on your account',
required=True)
ssh_key_file = schema.StringItem(
title='SSH Private Key',
description='The path to an SSH private key which will be used '
'to authenticate on the deployed VMs')
ssh_key_names = schema.StringItem(
title='SSH Key Names',
description='The names of an SSH key being managed on '
'Digital Ocean account which will be used to '
'authenticate on the deployed VMs')
requirements_definition = schema.AnyOfItem(
items=(
schema.RequirementsItem(requirements=['ssh_key_file']),
schema.RequirementsItem(requirements=['ssh_key_names'])
),
)(flatten=True)
expected = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Digital Ocean",
"description": "Digital Ocean Cloud VM configuration requirements.",
"type": "object",
"properties": {
"driver": {
"default": "digital_ocean",
"format": "hidden",
"type": "string",
"title": "driver"
},
"personal_access_token": {
"type": "string",
"description": "This is the API access token which can be "
"generated under the API/Application on your account",
"title": "Personal Access Token"
},
"ssh_key_file": {
"type": "string",
"description": "The path to an SSH private key which will "
"be used to authenticate on the deployed VMs",
"title": "SSH Private Key"
},
"ssh_key_names": {
"type": "string",
"description": "The names of an SSH key being managed on Digital "
"Ocean account which will be used to authenticate "
"on the deployed VMs",
"title": "SSH Key Names"
}
},
"anyOf": [
{"required": ["ssh_key_file"]},
{"required": ["ssh_key_names"]}
],
"required": [
"personal_access_token"
],
"x-ordering": [
"driver",
"personal_access_token",
"ssh_key_file",
"ssh_key_names",
],
"additionalProperties": False
}
self.assertDictContainsSubset(expected, Requirements2.serialize())
class Requirements3(schema.Schema):
title = 'Digital Ocean'
description = 'Digital Ocean Cloud VM configuration requirements.'
merge_reqs = Requirements(flatten=True)
expected = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Digital Ocean",
"description": "Digital Ocean Cloud VM configuration requirements.",
"type": "object",
"properties": {
"driver": {
"default": "digital_ocean",
"format": "hidden",
"type": "string",
"title": "driver"
},
"personal_access_token": {
"type": "string",
"description": "This is the API access token which can be "
"generated under the API/Application on your account",
"title": "Personal Access Token"
},
"ssh_key_file": {
"type": "string",
"description": "The path to an SSH private key which will "
"be used to authenticate on the deployed VMs",
"title": "SSH Private Key"
},
"ssh_key_names": {
"type": "string",
"description": "The names of an SSH key being managed on Digital "
"Ocean account which will be used to authenticate "
"on the deployed VMs",
"title": "SSH Key Names"
}
},
"anyOf": [
{"required": ["ssh_key_file"]},
{"required": ["ssh_key_names"]}
],
"required": [
"personal_access_token"
],
"x-ordering": [
"driver",
"personal_access_token",
"ssh_key_file",
"ssh_key_names",
],
"additionalProperties": False
}
self.assertDictContainsSubset(expected, Requirements3.serialize())
class Requirements4(schema.Schema):
title = 'Digital Ocean'
description = 'Digital Ocean Cloud VM configuration requirements.'
merge_reqs = Requirements(flatten=True)
ssh_key_file_2 = schema.StringItem(
title='SSH Private Key',
description='The path to an SSH private key which will be used '
'to authenticate on the deployed VMs')
ssh_key_names_2 = schema.StringItem(
title='SSH Key Names',
description='The names of an SSH key being managed on '
'Digital Ocean account which will be used to '
'authenticate on the deployed VMs')
requirements_definition_2 = schema.AnyOfItem(
items=(
schema.RequirementsItem(requirements=['ssh_key_file_2']),
schema.RequirementsItem(requirements=['ssh_key_names_2'])
),
)(flatten=True)
expected = {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Digital Ocean",
"description": "Digital Ocean Cloud VM configuration requirements.",
"type": "object",
"properties": {
"driver": {
"default": "digital_ocean",
"format": "hidden",
"type": "string",
"title": "driver"
},
"personal_access_token": {
"type": "string",
"description": "This is the API access token which can be "
"generated under the API/Application on your account",
"title": "Personal Access Token"
},
"ssh_key_file": {
"type": "string",
"description": "The path to an SSH private key which will "
"be used to authenticate on the deployed VMs",
"title": "SSH Private Key"
},
"ssh_key_names": {
"type": "string",
"description": "The names of an SSH key being managed on Digital "
"Ocean account which will be used to authenticate "
"on the deployed VMs",
"title": "SSH Key Names"
},
"ssh_key_file_2": {
"type": "string",
"description": "The path to an SSH private key which will "
"be used to authenticate on the deployed VMs",
"title": "SSH Private Key"
},
"ssh_key_names_2": {
"type": "string",
"description": "The names of an SSH key being managed on Digital "
"Ocean account which will be used to authenticate "
"on the deployed VMs",
"title": "SSH Key Names"
}
},
"anyOf": [
{"required": ["ssh_key_file"]},
{"required": ["ssh_key_names"]},
{"required": ["ssh_key_file_2"]},
{"required": ["ssh_key_names_2"]}
],
"required": [
"personal_access_token"
],
"x-ordering": [
"driver",
"personal_access_token",
"ssh_key_file",
"ssh_key_names",
"ssh_key_file_2",
"ssh_key_names_2",
],
"additionalProperties": False
}
self.assertDictContainsSubset(expected, Requirements4.serialize())
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
def test_optional_requirements_config_validation(self):
@ -190,16 +450,14 @@ class ConfigTestCase(TestCase):
ssh_key_file = schema.StringItem(
title='SSH Private Key',
description='The path to an SSH private key which will be used '
'to authenticate on the deployed VMs',
required=True)
'to authenticate on the deployed VMs')
class SSHKeyNamesSchema(schema.Schema):
ssh_key_names = schema.StringItem(
title='SSH Key Names',
description='The names of an SSH key being managed on '
'Digial Ocean account which will be used to '
'authenticate on the deployed VMs',
required=True)
'authenticate on the deployed VMs')
class Requirements(BaseRequirements):
title = 'Digital Ocean'