mirror of
				https://github.com/ansible-collections/community.general.git
				synced 2025-10-25 05:23:58 -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