Skip to content

Commit 18be55e

Browse files
More filter spec
Signed-off-by: Stew Francis <[email protected]>
1 parent 6d4af2c commit 18be55e

File tree

4 files changed

+465
-145
lines changed

4 files changed

+465
-145
lines changed

plugins/doc_fragments/cmci.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ class ModuleDocFragment(object):
163163
- Filters can be nested. At most four nesting layers are allowed.
164164
- When supplying the C(attribute) option, you must also supply a
165165
C(value) for the filter. You can also override the default operator
166-
with the C(=) option.
166+
of C(=) with the C(operator) option.
167167
- For examples, see M(cmci_get)
168168
type: dict
169169
required: false
@@ -176,6 +176,7 @@ class ModuleDocFragment(object):
176176
nested filter expression can be either an C(attribute), C(and)
177177
or C(or) complex filter expression.
178178
type: list
179+
elements: dict
179180
required: false
180181
or:
181182
description:
@@ -185,6 +186,7 @@ class ModuleDocFragment(object):
185186
nested filter expression can be either an C(attribute), C(and)
186187
or C(or) complex filter expression.
187188
type: list
189+
elements: dict
188190
required: false
189191
attribute:
190192
description:
@@ -227,6 +229,7 @@ class ModuleDocFragment(object):
227229
as documented in the resource table reference, for example,
228230
L(PROGDEF resource table reference,
229231
https://www.ibm.com/support/knowledgecenter/en/SSGMCP_5.6.0/reference-cpsm-restables/cpsm-restables/PROGDEFtab.html).
232+
type: str
230233
required: false
231234
get_parameters:
232235
description: >
@@ -239,6 +242,7 @@ class ModuleDocFragment(object):
239242
as found in the L(PROGDEF resource table reference,
240243
https://www.ibm.com/support/knowledgecenter/en/SSGMCP_5.6.0/reference-cpsm-restables/cpsm-restables/PROGDEFtab.html).
241244
type: list
245+
elements: dict
242246
suboptions:
243247
name:
244248
description: Parameter name available for the GET operation.

plugins/module_utils/cmci.py

Lines changed: 118 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
from ansible.module_utils.basic import AnsibleModule, missing_required_lib,\
1010
env_fallback
11-
from typing import Optional, Dict, Any
12-
from itertools import chain
11+
from typing import Optional, Dict, Any, List
1312
from collections import OrderedDict
1413
from sys import version_info
1514
import re
@@ -49,74 +48,49 @@
4948
GET_PARAMETERS = 'get_parameters'
5049

5150

52-
def _attribute_arguments(required):
53-
return [
54-
('attribute', {
55-
'type': 'str',
56-
'required': required
57-
}),
58-
('operator', {
59-
'type': 'str',
60-
'required': False,
61-
'default': 'EQ',
62-
'choices': ['<', '<=', '=', '>', '>=', '¬=', '==', '!=', 'EQ', 'NE',
63-
'LT', 'LE', 'GE', 'GT', 'IS']
64-
}),
65-
('value', {
66-
'type': 'str',
67-
'required': required
68-
})
69-
]
70-
71-
72-
def _cf_node(options): # type: ([(str, Any)]) -> [(str, Any)]
73-
74-
sub = dict(chain(
75-
options,
76-
[
77-
('type', 'list'),
78-
('elements', 'dict')
79-
]
80-
))
81-
82-
return [
83-
('required', False),
84-
('required_together', [
51+
def _complex_filter():
52+
return {
53+
'type': 'dict',
54+
'required': False,
55+
'required_together': [
8556
('attribute', 'value')
86-
]),
87-
('required_one_of', [
57+
],
58+
'required_one_of': [
8859
('attribute', 'and', 'or')
89-
]),
90-
('mutually_exclusive', [
91-
('attribute', 'and', 'or'),
92-
('and', 'operator'),
93-
('and', 'value'),
94-
('or', 'operator'),
95-
('or', 'value')
96-
]),
97-
('options', dict(chain(
98-
_attribute_arguments(False),
99-
[
100-
('and', sub),
101-
('or', sub)
102-
]
103-
)))
104-
]
105-
106-
107-
def _complex_filter():
108-
return dict(chain(
109-
_cf_node(
110-
_cf_node(
111-
_cf_node(
112-
_cf_node([
113-
('options', dict(_attribute_arguments(True)))
114-
])
115-
)
116-
)
117-
),
118-
[('type', 'dict')]
119-
))
60+
],
61+
'required_by': {
62+
'operator': 'attribute'
63+
},
64+
'mutually_exclusive': [
65+
('attribute', 'and', 'or')
66+
],
67+
'options': {
68+
'attribute': {
69+
'type': 'str',
70+
'required': False
71+
},
72+
'operator': {
73+
'type': 'str',
74+
'required': False,
75+
'choices': ['<', '<=', '=', '>', '>=', '¬=', '==', '!=', 'EQ',
76+
'NE', 'LT', 'LE', 'GE', 'GT', 'IS']
77+
},
78+
'value': {
79+
'type': 'str',
80+
'required': False
81+
},
82+
'and': {
83+
'type': 'list',
84+
'elements': 'dict',
85+
'required': False
86+
},
87+
'or': {
88+
'type': 'list',
89+
'elements': 'dict',
90+
'required': False
91+
}
92+
}
93+
}
12094

12195

12296
def parameters_argument(name): # type: (str) -> Dict[str, Any]
@@ -410,6 +384,7 @@ def init_url(self): # type: () -> str
410384
def init_request_params(self): # type: () -> Optional[Dict[str, str]]
411385
return None
412386

387+
# TODO: why do we have this and/or parsing twice?
413388
def get_resources_request_params(self): # type: () -> Dict[str, str]
414389
# get, delete, put will all need CRITERIA{}
415390
request_params = OrderedDict({})
@@ -440,17 +415,19 @@ def get_resources_request_params(self): # type: () -> Dict[str, str]
440415
attribute_item = complex_filter['attribute']
441416

442417
if and_item is not None:
443-
complex_filter_string = _get_filter(
418+
complex_filter_string = self._get_filter(
444419
and_item,
445420
complex_filter_string,
446-
' AND '
421+
' AND ',
422+
' -> and'
447423
)
448424

449425
if or_item is not None:
450-
complex_filter_string = _get_filter(
426+
complex_filter_string = self._get_filter(
451427
or_item,
452428
complex_filter_string,
453-
' OR '
429+
' OR ',
430+
' -> or'
454431
)
455432

456433
if attribute_item is not None:
@@ -605,6 +582,74 @@ def append_attributes(self, element):
605582
{'@' + key: value for key, value in items}
606583
)
607584

585+
def _get_filter(self, list_of_filters, complex_filter_string, joiner, path):
586+
# type: (List[Dict], str, str, str) -> str
587+
for i in list_of_filters:
588+
and_item = i.get('and')
589+
or_item = i.get('or')
590+
attribute = i.get('attribute')
591+
op = i.get('operator')
592+
593+
if op and not attribute:
594+
self._fail(
595+
"missing parameter(s) required by 'operator': attribute"
596+
)
597+
598+
# Validate mutually exclusive parameters
599+
if (and_item and or_item) or (and_item and attribute) or \
600+
(or_item and attribute):
601+
self._fail(
602+
'parameters are mutually exclusive: attribute|and|or found '
603+
'in resources -> complex_filter' + path
604+
)
605+
606+
if and_item is not None:
607+
and_filter_string = self._get_filter(
608+
and_item,
609+
'',
610+
' AND ',
611+
path + ' -> and'
612+
)
613+
complex_filter_string = _append_filter_string(
614+
complex_filter_string,
615+
and_filter_string, joiner
616+
)
617+
if or_item is not None:
618+
or_filter_string = self._get_filter(
619+
or_item,
620+
'',
621+
' OR ',
622+
path + ' -> or'
623+
)
624+
complex_filter_string = _append_filter_string(
625+
complex_filter_string,
626+
or_filter_string, joiner
627+
)
628+
if attribute is not None:
629+
operator = _convert_filter_operator(op)
630+
value = i.get('value')
631+
632+
if value is None:
633+
self._fail(
634+
'parameters are required together: attribute, value '
635+
'found in resources -> complex_filter' + path)
636+
637+
if operator == '¬=':
638+
# Provides a filter string in the format NOT(FOO=='BAR')
639+
attribute_filter_string = \
640+
'NOT(' + attribute + '==' + '\'' + value + '\'' + ')'
641+
else:
642+
attribute_filter_string = \
643+
attribute + operator + '\'' + value + '\''
644+
645+
complex_filter_string = _append_filter_string(
646+
complex_filter_string,
647+
attribute_filter_string,
648+
joiner
649+
)
650+
651+
return complex_filter_string
652+
608653
def _fail(self, msg): # type: (str) -> None
609654
self._module.fail_json(msg=msg, **self.result)
610655

@@ -617,7 +662,7 @@ def _convert_filter_operator(operator):
617662
return '<'
618663
if operator in ['<=', 'LE']:
619664
return '<='
620-
if operator in ['=', 'EQ']:
665+
if operator in ['=', 'EQ', None]:
621666
return '='
622667
if operator in ['>=', 'GE']:
623668
return '>='
@@ -629,45 +674,6 @@ def _convert_filter_operator(operator):
629674
return '=='
630675

631676

632-
def _get_filter(list_of_filters, complex_filter_string, joiner):
633-
for i in list_of_filters:
634-
and_item = i.get('and')
635-
or_item = i.get('or')
636-
attribute = i.get('attribute')
637-
638-
if and_item is not None:
639-
and_filter_string = _get_filter(and_item, '', ' AND ')
640-
complex_filter_string = _append_filter_string(
641-
complex_filter_string,
642-
and_filter_string, joiner
643-
)
644-
if or_item is not None:
645-
or_filter_string = _get_filter(or_item, '', ' OR ')
646-
complex_filter_string = _append_filter_string(
647-
complex_filter_string,
648-
or_filter_string, joiner
649-
)
650-
if attribute is not None:
651-
operator = _convert_filter_operator(i.get('operator'))
652-
value = i.get('value')
653-
654-
if operator == '¬=':
655-
# Provides a filter string in the format NOT(FOO=='BAR')
656-
attribute_filter_string =\
657-
'NOT(' + attribute + '==' + '\'' + value + '\'' + ')'
658-
else:
659-
attribute_filter_string = \
660-
attribute + operator + '\'' + value + '\''
661-
662-
complex_filter_string = _append_filter_string(
663-
complex_filter_string,
664-
attribute_filter_string,
665-
joiner
666-
)
667-
668-
return complex_filter_string
669-
670-
671677
def _append_filter_string(existing, to_append, joiner=' AND '):
672678
# joiner is ' AND ' or ' OR '
673679
if not existing:

tests/sanity/ignore-2.10.txt

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,6 @@
11
docs/make.bat line-endings!skip # Windows batch file requires windows line endings
22
plugins/modules/cmci_get.py validate-modules:missing-gplv3-license # Licence is Apache-2.0
3-
plugins/modules/cmci_get.py validate-modules:doc-choices-do-not-match-spec # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
4-
plugins/modules/cmci_get.py validate-modules:doc-default-does-not-match-spec # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
5-
plugins/modules/cmci_get.py validate-modules:missing-suboption-docs # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
6-
plugins/modules/cmci_get.py validate-modules:parameter-type-not-in-doc # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
7-
plugins/modules/cmci_get.py validate-modules:undocumented-parameter # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
8-
plugins/modules/cmci_get.py validate-modules:doc-elements-mismatch # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
9-
plugins/modules/cmci_get.py validate-modules:doc-required-mismatch # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
103
plugins/modules/cmci_action.py validate-modules:missing-gplv3-license # Licence is Apache-2.0
11-
plugins/modules/cmci_action.py validate-modules:doc-choices-do-not-match-spec # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
12-
plugins/modules/cmci_action.py validate-modules:doc-default-does-not-match-spec # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
13-
plugins/modules/cmci_action.py validate-modules:missing-suboption-docs # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
14-
plugins/modules/cmci_action.py validate-modules:parameter-type-not-in-doc # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
15-
plugins/modules/cmci_action.py validate-modules:undocumented-parameter # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
16-
plugins/modules/cmci_action.py validate-modules:doc-elements-mismatch # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
17-
plugins/modules/cmci_action.py validate-modules:doc-required-mismatch # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
184
plugins/modules/cmci_create.py validate-modules:missing-gplv3-license # Licence is Apache-2.0
195
plugins/modules/cmci_delete.py validate-modules:missing-gplv3-license # Licence is Apache-2.0
20-
plugins/modules/cmci_delete.py validate-modules:doc-choices-do-not-match-spec # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
21-
plugins/modules/cmci_delete.py validate-modules:doc-default-does-not-match-spec # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
22-
plugins/modules/cmci_delete.py validate-modules:missing-suboption-docs # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
23-
plugins/modules/cmci_delete.py validate-modules:parameter-type-not-in-doc # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
24-
plugins/modules/cmci_delete.py validate-modules:undocumented-parameter # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
25-
plugins/modules/cmci_delete.py validate-modules:doc-elements-mismatch # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
26-
plugins/modules/cmci_delete.py validate-modules:doc-required-mismatch # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
27-
plugins/modules/cmci_update.py validate-modules:missing-gplv3-license # Licence is Apache-2.0
28-
plugins/modules/cmci_update.py validate-modules:doc-choices-do-not-match-spec # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
29-
plugins/modules/cmci_update.py validate-modules:doc-default-does-not-match-spec # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
30-
plugins/modules/cmci_update.py validate-modules:missing-suboption-docs # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
31-
plugins/modules/cmci_update.py validate-modules:parameter-type-not-in-doc # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
32-
plugins/modules/cmci_update.py validate-modules:undocumented-parameter # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
33-
plugins/modules/cmci_update.py validate-modules:doc-elements-mismatch # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
34-
plugins/modules/cmci_update.py validate-modules:doc-required-mismatch # We have a complex_filter DSL as part of our plugin YAML. The same config is accepted 4 levels deep. We only include the (quite long) documentation for this at the highest level, which causes this error
6+
plugins/modules/cmci_update.py validate-modules:missing-gplv3-license # Licence is Apache-2.0

0 commit comments

Comments
 (0)