Skip to content

Commit 4afa8d2

Browse files
More filter spec validation and tests
Signed-off-by: Stew Francis <[email protected]>
1 parent 18be55e commit 4afa8d2

File tree

2 files changed

+418
-93
lines changed

2 files changed

+418
-93
lines changed

plugins/module_utils/cmci.py

Lines changed: 94 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -48,51 +48,6 @@
4848
GET_PARAMETERS = 'get_parameters'
4949

5050

51-
def _complex_filter():
52-
return {
53-
'type': 'dict',
54-
'required': False,
55-
'required_together': [
56-
('attribute', 'value')
57-
],
58-
'required_one_of': [
59-
('attribute', 'and', 'or')
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-
}
94-
95-
9651
def parameters_argument(name): # type: (str) -> Dict[str, Any]
9752
return {
9853
name: {
@@ -113,6 +68,9 @@ def parameters_argument(name): # type: (str) -> Dict[str, Any]
11368
}
11469

11570

71+
OPERATORS = ['<', '<=', '=', '>', '>=', '¬=', '==', '!=', 'EQ', 'NE', 'LT',
72+
'LE', 'GE', 'GT', 'IS']
73+
11674
RESOURCES_ARGUMENT = {
11775
RESOURCES: {
11876
'type': 'dict',
@@ -122,7 +80,47 @@ def parameters_argument(name): # type: (str) -> Dict[str, Any]
12280
'type': 'dict',
12381
'required': False
12482
},
125-
COMPLEX_FILTER: _complex_filter(),
83+
COMPLEX_FILTER: {
84+
'type': 'dict',
85+
'required': False,
86+
'required_together': [
87+
('attribute', 'value')
88+
],
89+
'required_one_of': [
90+
('attribute', 'and', 'or')
91+
],
92+
'required_by': {
93+
'operator': 'attribute'
94+
},
95+
'mutually_exclusive': [
96+
('attribute', 'and', 'or')
97+
],
98+
'options': {
99+
'attribute': {
100+
'type': 'str',
101+
'required': False
102+
},
103+
'operator': {
104+
'type': 'str',
105+
'required': False,
106+
'choices': OPERATORS
107+
},
108+
'value': {
109+
'type': 'str',
110+
'required': False
111+
},
112+
'and': {
113+
'type': 'list',
114+
'elements': 'dict',
115+
'required': False
116+
},
117+
'or': {
118+
'type': 'list',
119+
'elements': 'dict',
120+
'required': False
121+
}
122+
}
123+
},
126124
GET_PARAMETERS:
127125
parameters_argument(GET_PARAMETERS).get(GET_PARAMETERS)
128126
}
@@ -431,8 +429,9 @@ def get_resources_request_params(self): # type: () -> Dict[str, str]
431429
)
432430

433431
if attribute_item is not None:
434-
operator = _convert_filter_operator(
435-
complex_filter['operator']
432+
operator = self._convert_filter_operator(
433+
complex_filter['operator'],
434+
""
436435
)
437436
value = complex_filter['value']
438437

@@ -584,12 +583,35 @@ def append_attributes(self, element):
584583

585584
def _get_filter(self, list_of_filters, complex_filter_string, joiner, path):
586585
# type: (List[Dict], str, str, str) -> str
586+
587+
if not isinstance(list_of_filters, list):
588+
self._fail(
589+
"nested filters must be a list, was: %s found in "
590+
"resources -> complex_filter%s"
591+
% (type(list_of_filters), path)
592+
)
587593
for i in list_of_filters:
594+
if not isinstance(i, dict):
595+
self._fail(
596+
"nested filter must be of type dict, was: %s found in "
597+
"resources -> complex_filter%s" % (type(i), path)
598+
)
599+
588600
and_item = i.get('and')
589601
or_item = i.get('or')
590602
attribute = i.get('attribute')
591603
op = i.get('operator')
592604

605+
valid_keys = ['and', 'attribute', 'operator', 'or', 'value']
606+
diff = set(i.keys()) - set(valid_keys)
607+
if len(diff) != 0:
608+
self._fail(
609+
"Unsupported parameters for (basic.py) module: %s found"
610+
" in resources -> complex_filter%s. Supported parameters "
611+
"include: %s"
612+
% (", ".join(diff), path, ", ".join(valid_keys))
613+
)
614+
593615
if op and not attribute:
594616
self._fail(
595617
"missing parameter(s) required by 'operator': attribute"
@@ -600,7 +622,7 @@ def _get_filter(self, list_of_filters, complex_filter_string, joiner, path):
600622
(or_item and attribute):
601623
self._fail(
602624
'parameters are mutually exclusive: attribute|and|or found '
603-
'in resources -> complex_filter' + path
625+
'in resources -> complex_filter%s' % path
604626
)
605627

606628
if and_item is not None:
@@ -626,13 +648,13 @@ def _get_filter(self, list_of_filters, complex_filter_string, joiner, path):
626648
or_filter_string, joiner
627649
)
628650
if attribute is not None:
629-
operator = _convert_filter_operator(op)
651+
operator = self._convert_filter_operator(op, path)
630652
value = i.get('value')
631653

632654
if value is None:
633655
self._fail(
634656
'parameters are required together: attribute, value '
635-
'found in resources -> complex_filter' + path)
657+
'found in resources -> complex_filter%s' % path)
636658

637659
if operator == '¬=':
638660
# Provides a filter string in the format NOT(FOO=='BAR')
@@ -650,30 +672,34 @@ def _get_filter(self, list_of_filters, complex_filter_string, joiner, path):
650672

651673
return complex_filter_string
652674

675+
def _convert_filter_operator(self, operator, path):
676+
if operator in ['<', 'LT']:
677+
return '<'
678+
if operator in ['<=', 'LE']:
679+
return '<='
680+
if operator in ['=', 'EQ', None]:
681+
return '='
682+
if operator in ['>=', 'GE']:
683+
return '>='
684+
if operator in ['>', 'GT']:
685+
return '>'
686+
if operator in ['¬=', '!=', 'NE']:
687+
return '¬='
688+
if operator in ['==', 'IS']:
689+
return '=='
690+
self._fail(
691+
'value of operator must be one of: %s, got: %s found in '
692+
'resources -> complex_filter%s'
693+
% (", ".join(OPERATORS), operator, path)
694+
)
695+
653696
def _fail(self, msg): # type: (str) -> None
654697
self._module.fail_json(msg=msg, **self.result)
655698

656699
def _fail_tb(self, msg, tb): # type: (str, str) -> None
657700
self._module.fail_json(msg=msg, exception=tb, **self.result)
658701

659702

660-
def _convert_filter_operator(operator):
661-
if operator in ['<', 'LT']:
662-
return '<'
663-
if operator in ['<=', 'LE']:
664-
return '<='
665-
if operator in ['=', 'EQ', None]:
666-
return '='
667-
if operator in ['>=', 'GE']:
668-
return '>='
669-
if operator in ['>', 'GT']:
670-
return '>'
671-
if operator in ['¬=', '!=', 'NE']:
672-
return '¬='
673-
if operator in ['==', 'IS']:
674-
return '=='
675-
676-
677703
def _append_filter_string(existing, to_append, joiner=' AND '):
678704
# joiner is ' AND ' or ' OR '
679705
if not existing:

0 commit comments

Comments
 (0)