mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Fixed ordering issue
This commit is contained in:
parent
cf071eaffa
commit
3adb731abc
2 changed files with 201 additions and 33 deletions
|
@ -407,17 +407,22 @@ class ConfigurationMeta(six.with_metaclass(Prepareable, type)):
|
|||
attrs['__flatten__'] = False
|
||||
|
||||
# Let's record the configuration items/sections
|
||||
items = OrderedDict()
|
||||
sections = OrderedDict()
|
||||
items = {}
|
||||
sections = {}
|
||||
order = []
|
||||
# items from parent classes
|
||||
for base in reversed(bases):
|
||||
if hasattr(base, '_items'):
|
||||
items.update(base._items)
|
||||
if hasattr(base, '_sections'):
|
||||
sections.update(base._sections)
|
||||
if hasattr(base, '_order'):
|
||||
order.extend(base._order)
|
||||
|
||||
# Iterate through attrs to discover items/config sections
|
||||
for key, value in six.iteritems(attrs):
|
||||
if not hasattr(value, '__item__') and not hasattr(value, '__config__'):
|
||||
continue
|
||||
if hasattr(value, '__item__'):
|
||||
# the value is an item instance
|
||||
if hasattr(value, 'title') and value.title is None:
|
||||
|
@ -426,13 +431,10 @@ class ConfigurationMeta(six.with_metaclass(Prepareable, type)):
|
|||
value.title = key
|
||||
items[key] = value
|
||||
if hasattr(value, '__config__'):
|
||||
if value.__flatten__ is True:
|
||||
# Should not be considered as a section
|
||||
items[key] = value
|
||||
else:
|
||||
# the value is a configuration section
|
||||
sections[key] = value
|
||||
sections[key] = value
|
||||
order.append(key)
|
||||
|
||||
attrs['_order'] = order
|
||||
attrs['_items'] = items
|
||||
attrs['_sections'] = sections
|
||||
return type.__new__(mcs, name, bases, attrs)
|
||||
|
@ -544,34 +546,42 @@ class Configuration(six.with_metaclass(ConfigurationMeta, object)):
|
|||
ordering = []
|
||||
serialized['type'] = 'object'
|
||||
properties = OrderedDict()
|
||||
for name, section in cls._sections.items():
|
||||
serialized_section = section.serialize(None if section.__flatten__ is True else name)
|
||||
if section.__flatten__ is True:
|
||||
# Flatten the configuration section into the parent
|
||||
# configuration
|
||||
properties.update(serialized_section['properties'])
|
||||
if 'x-ordering' in serialized_section:
|
||||
ordering.extend(serialized_section['x-ordering'])
|
||||
if 'required' in serialized:
|
||||
required.extend(serialized_section['required'])
|
||||
else:
|
||||
# Store it as a configuration section
|
||||
properties[name] = serialized_section
|
||||
|
||||
# Handle the configuration items defined in the class instance
|
||||
after_items_update = OrderedDict()
|
||||
for name, config in cls._items.items():
|
||||
if config.__flatten__ is True:
|
||||
after_items_update.update(config.serialize())
|
||||
else:
|
||||
properties[name] = config.serialize()
|
||||
for name in cls._order:
|
||||
skip_order = False
|
||||
if name in cls._sections:
|
||||
section = cls._sections[name]
|
||||
serialized_section = section.serialize(None if section.__flatten__ is True else name)
|
||||
if section.__flatten__ is True:
|
||||
# Flatten the configuration section into the parent
|
||||
# configuration
|
||||
properties.update(serialized_section['properties'])
|
||||
if 'x-ordering' in serialized_section:
|
||||
ordering.extend(serialized_section['x-ordering'])
|
||||
if 'required' in serialized:
|
||||
required.extend(serialized_section['required'])
|
||||
else:
|
||||
# Store it as a configuration section
|
||||
properties[name] = serialized_section
|
||||
|
||||
# Store the order of the item
|
||||
ordering.append(name)
|
||||
if name in cls._items:
|
||||
config = cls._items[name]
|
||||
# Handle the configuration items defined in the class instance
|
||||
if config.__flatten__ is True:
|
||||
after_items_update.update(config.serialize())
|
||||
skip_order = True
|
||||
else:
|
||||
properties[name] = config.serialize()
|
||||
|
||||
if config.required:
|
||||
# If it's a required item, add it to the required list
|
||||
required.append(name)
|
||||
|
||||
if skip_order is False:
|
||||
# Store the order of the item
|
||||
if name not in ordering:
|
||||
ordering.append(name)
|
||||
|
||||
if isinstance(config, BaseItem) and config.required:
|
||||
# If it's a required item, add it to the required list
|
||||
required.append(name)
|
||||
serialized['properties'] = properties
|
||||
|
||||
# Update the serialized object with any items to include after properties
|
||||
|
|
|
@ -94,6 +94,164 @@ class ConfigTestCase(TestCase):
|
|||
|
||||
self.assertEqual(Final.serialize()['x-ordering'], ['one', 'two', 'three'])
|
||||
|
||||
def test_optional_requirements_config(self):
|
||||
class BaseRequirements(config.Configuration):
|
||||
driver = config.StringConfig(default='digital_ocean', format='hidden')
|
||||
|
||||
class SSHKeyFileConfiguration(config.Configuration):
|
||||
ssh_key_file = config.StringConfig(
|
||||
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 SSHKeyNamesConfiguration(config.Configuration):
|
||||
ssh_key_names = config.StringConfig(
|
||||
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',
|
||||
required=True)
|
||||
|
||||
class Requirements(BaseRequirements):
|
||||
title = 'Digital Ocean'
|
||||
description = 'Digital Ocean Cloud VM configuration requirements.'
|
||||
|
||||
personal_access_token = config.StringConfig(
|
||||
title='Personal Access Token',
|
||||
description='This is the API access token which can be generated '
|
||||
'under the API/Application on your account',
|
||||
required=True)
|
||||
|
||||
requirements_definition = config.AnyOfConfig(
|
||||
items=(
|
||||
SSHKeyFileConfiguration.as_requirements_item(),
|
||||
SSHKeyNamesConfiguration.as_requirements_item()
|
||||
),
|
||||
)(flatten=True)
|
||||
ssh_key_file = SSHKeyFileConfiguration(flatten=True)
|
||||
ssh_key_names = SSHKeyNamesConfiguration(flatten=True)
|
||||
|
||||
self.maxDiff = None
|
||||
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"
|
||||
}
|
||||
},
|
||||
"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())
|
||||
|
||||
@skipIf(HAS_JSONSCHEMA is False, 'The \'jsonschema\' library is missing')
|
||||
def test_optional_requirements_config_validation(self):
|
||||
class BaseRequirements(config.Configuration):
|
||||
driver = config.StringConfig(default='digital_ocean', format='hidden')
|
||||
|
||||
class SSHKeyFileConfiguration(config.Configuration):
|
||||
ssh_key_file = config.StringConfig(
|
||||
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 SSHKeyNamesConfiguration(config.Configuration):
|
||||
ssh_key_names = config.StringConfig(
|
||||
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)
|
||||
|
||||
class Requirements(BaseRequirements):
|
||||
title = 'Digital Ocean'
|
||||
description = 'Digital Ocean Cloud VM configuration requirements.'
|
||||
|
||||
personal_access_token = config.StringConfig(
|
||||
title='Personal Access Token',
|
||||
description='This is the API access token which can be generated '
|
||||
'under the API/Application on your account',
|
||||
required=True)
|
||||
|
||||
requirements_definition = config.AnyOfConfig(
|
||||
items=(
|
||||
SSHKeyFileConfiguration.as_requirements_item(),
|
||||
SSHKeyNamesConfiguration.as_requirements_item()
|
||||
),
|
||||
)(flatten=True)
|
||||
ssh_key_file = SSHKeyFileConfiguration(flatten=True)
|
||||
ssh_key_names = SSHKeyNamesConfiguration(flatten=True)
|
||||
|
||||
try:
|
||||
jsonschema.validate(
|
||||
{"personal_access_token": "foo", "ssh_key_names": "bar", "ssh_key_file": "test"},
|
||||
Requirements.serialize()
|
||||
)
|
||||
except jsonschema.exceptions.ValidationError as exc:
|
||||
self.fail('ValidationError raised: {0}'.format(exc))
|
||||
|
||||
try:
|
||||
jsonschema.validate(
|
||||
{"personal_access_token": "foo", "ssh_key_names": "bar"},
|
||||
Requirements.serialize()
|
||||
)
|
||||
except jsonschema.exceptions.ValidationError as exc:
|
||||
self.fail('ValidationError raised: {0}'.format(exc))
|
||||
|
||||
try:
|
||||
jsonschema.validate(
|
||||
{"personal_access_token": "foo", "ssh_key_file": "test"},
|
||||
Requirements.serialize()
|
||||
)
|
||||
except jsonschema.exceptions.ValidationError as exc:
|
||||
self.fail('ValidationError raised: {0}'.format(exc))
|
||||
|
||||
with self.assertRaises(jsonschema.exceptions.ValidationError) as excinfo:
|
||||
jsonschema.validate(
|
||||
{"personal_access_token": "foo"},
|
||||
Requirements.serialize()
|
||||
)
|
||||
self.assertIn('is not valid under any of the given schemas', excinfo.exception.message)
|
||||
|
||||
def test_boolean_config(self):
|
||||
item = config.BooleanConfig(title='Hungry', description='Are you hungry?')
|
||||
self.assertDictEqual(
|
||||
|
|
Loading…
Add table
Reference in a new issue