mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-10-23 20:44:00 -07:00
Add options sub spec validation (#27119)
* Add aggregate parameter validation
aggregate parameter validation will support checking each individual dict
to resolve conditions for aliases, no_log, mutually_exclusive,
required, type check, values, required_together, required_one_of
and required_if conditions in argspec. It will also set default values.
eg:
tasks:
- name: Configure interface attribute with aggregate
net_interface:
aggregate:
- {name: ge-0/0/1, description: test-interface-1, duplex: full, state: present}
- {name: ge-0/0/2, description: test-interface-2, active: False}
register: response
purge: Yes
Usage:
```
from ansible.module_utils.network_common import AggregateCollection
transform = AggregateCollection(module)
param = transform(module.params.get('aggregate'))
```
Aggregate allows supports for `purge` parameter, it will instruct the module
to remove resources from remote device that hasn’t been explicitly
defined in aggregate. This is not supported by with_* iterators
Also, it improves performace as compared to with_* iterator for network device
that has seperate candidate and running datastore.
For with_* iteration the sequence of operartion is
load-config-1 (candidate db) -> commit (running db) -> load_config-2
(candidate db) -> commit (running db) ...
With aggregate the sequence of operation is
load-config-1 (candidate db) -> load-config-2 (candidate db) -> commit
(running db)
As commit is executed only once per task for aggregate it has
huge perfomance benefit for large configurations.
* Fix CI issues
* Fix review comments
* Add support for options validation for aliases, no_log,
mutually_exclusive, required, type check, value check,
required_together, required_one_of and required_if
conditions in sub-argspec.
* Add unit test for options in argspec.
* Reverted aggregate implementaion.
* Minor change
* Add multi-level argspec support
* Multi-level argspec support with module's top most
conditionals options.
* Fix unit test failure
* Add parent context in errors for sub options
* Resolve merge conflict
* Fix CI issue
This commit is contained in:
parent
19fac707fa
commit
97a34cf008
2 changed files with 377 additions and 58 deletions
|
|
@ -342,6 +342,171 @@ class TestModuleUtilsBasic(ModuleTestCase):
|
|||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
def test_module_utils_basic_ansible_module_with_options_creation(self):
|
||||
from ansible.module_utils import basic
|
||||
|
||||
options_spec = dict(
|
||||
foo=dict(required=True, aliases=['dup']),
|
||||
bar=dict(),
|
||||
bam=dict(),
|
||||
baz=dict(),
|
||||
bam1=dict(default='test')
|
||||
)
|
||||
arg_spec = dict(foobar=dict(type='list', elements='dict', options=options_spec))
|
||||
mut_ex = (('bar', 'bam'),)
|
||||
req_to = (('bam', 'baz'),)
|
||||
|
||||
# should test ok
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{"foo": "hello"}, {"foo": "test"}]}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
am = basic.AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
mutually_exclusive=mut_ex,
|
||||
required_together=req_to,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# should test ok, handles aliases
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{"dup": "hello"}]}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
am = basic.AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
mutually_exclusive=mut_ex,
|
||||
required_together=req_to,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# fail, because a required param was not specified
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{}]}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
basic.AnsibleModule,
|
||||
argument_spec=arg_spec,
|
||||
mutually_exclusive=mut_ex,
|
||||
required_together=req_to,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# fail because of mutually exclusive parameters
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{"foo": "hello", "bar": "bad", "bam": "bad"}]}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
basic.AnsibleModule,
|
||||
argument_spec=arg_spec,
|
||||
mutually_exclusive=mut_ex,
|
||||
required_together=req_to,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# fail because a param required due to another param was not specified
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{"bam": "bad"}]}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
basic.AnsibleModule,
|
||||
argument_spec=arg_spec,
|
||||
mutually_exclusive=mut_ex,
|
||||
required_together=req_to,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# fail because one of param is required
|
||||
req_one_of = (('bar', 'bam'),)
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{"foo": "hello"}]}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
basic.AnsibleModule,
|
||||
argument_spec=arg_spec,
|
||||
required_one_of=req_one_of,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# fail because value of one param mandates presence of other param required
|
||||
req_if = (('foo', 'hello', ('bam')),)
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{"foo": "hello"}]}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
basic.AnsibleModule,
|
||||
argument_spec=arg_spec,
|
||||
required_if=req_if,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# should test ok, the required param is set by default from spec
|
||||
req_if = [('foo', 'hello', ('bam1',))]
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{"foo": "hello"}]}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
am = basic.AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
required_if=req_if,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# should test ok, for options in dict format.
|
||||
arg_spec = dict(foobar=dict(type='dict', options=options_spec))
|
||||
|
||||
# should test ok
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': {"foo": "hello"}}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
am = basic.AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
# should fail, check for invalid agrument
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': {"foo1": "hello"}}))
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
basic.AnsibleModule,
|
||||
argument_spec=arg_spec,
|
||||
no_log=True,
|
||||
check_invalid_arguments=True,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def test_module_utils_basic_ansible_module_type_check(self):
|
||||
from ansible.module_utils import basic
|
||||
|
||||
|
|
@ -387,6 +552,52 @@ class TestModuleUtilsBasic(ModuleTestCase):
|
|||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
def test_module_utils_basic_ansible_module_options_type_check(self):
|
||||
from ansible.module_utils import basic
|
||||
|
||||
options_spec = dict(
|
||||
foo=dict(type='float'),
|
||||
foo2=dict(type='float'),
|
||||
foo3=dict(type='float'),
|
||||
bar=dict(type='int'),
|
||||
bar2=dict(type='int'),
|
||||
)
|
||||
|
||||
arg_spec = dict(foobar=dict(type='list', elements='dict', options=options_spec))
|
||||
# should test ok
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{
|
||||
"foo": 123.0, # float
|
||||
"foo2": 123, # int
|
||||
"foo3": "123", # string
|
||||
"bar": 123, # int
|
||||
"bar2": "123", # string
|
||||
}]}))
|
||||
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
am = basic.AnsibleModule(
|
||||
argument_spec=arg_spec,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
# fail, because bar does not accept floating point numbers
|
||||
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'foobar': [{"bar": 123.0}]}))
|
||||
|
||||
with swap_stdin_and_argv(stdin_data=args):
|
||||
basic._ANSIBLE_ARGS = None
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
basic.AnsibleModule,
|
||||
argument_spec=arg_spec,
|
||||
no_log=True,
|
||||
check_invalid_arguments=False,
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
def test_module_utils_basic_ansible_module_load_file_common_arguments(self):
|
||||
from ansible.module_utils import basic
|
||||
basic._ANSIBLE_ARGS = None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue