Skip to content

Commit 2ea1741

Browse files
pszulczewskiPatryk Szulczewskichadelljeffkala
authored
jdiff module (#269)
* Added jdiff module * Apply suggestions from code review Co-authored-by: Christian Adell <[email protected]> * Apply suggestions from code review * Update version_added * Apply suggestions from code review Co-authored-by: Jeff Kala <[email protected]> --------- Co-authored-by: Patryk Szulczewski <[email protected]> Co-authored-by: Christian Adell <[email protected]> Co-authored-by: Jeff Kala <[email protected]>
1 parent 6a28fd3 commit 2ea1741

File tree

4 files changed

+537
-0
lines changed

4 files changed

+537
-0
lines changed

plugins/action/jdiff.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright: (c) 2022, Network to Code (@networktocode) <[email protected]>
3+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4+
"""Jdiff Action Plugin for jdiff library."""
5+
6+
from __future__ import (absolute_import, division, print_function)
7+
8+
from ansible.plugins.action import ActionBase
9+
from ansible.errors import AnsibleError
10+
from ansible.module_utils.six import raise_from
11+
12+
try:
13+
from jdiff import CheckType, extract_data_from_json
14+
except ImportError as imp_exc:
15+
JDIFF_IMPORT_ERROR = imp_exc
16+
else:
17+
JDIFF_IMPORT_ERROR = None
18+
19+
20+
__metaclass__ = type
21+
22+
23+
def main(args):
24+
"""Module function."""
25+
if "evaluate_args" not in args:
26+
raise AnsibleError("Invalid arguments, 'evaluate_args' not found.")
27+
check_type = args.get('check_type')
28+
evaluate_args = args.get('evaluate_args')
29+
if not isinstance(evaluate_args, dict):
30+
raise AnsibleError(f"'evaluate_args' invalid type, expected <class 'dict'>, got {type(evaluate_args)}")
31+
if "value_to_compare" not in evaluate_args:
32+
raise AnsibleError("Key 'value_to_compare' missing in 'evaluate_arguments'.")
33+
reference_data = evaluate_args.get("reference_data")
34+
value = evaluate_args['value_to_compare']
35+
jpath = args.get('jmespath', '*')
36+
exclude = args.get('exclude')
37+
38+
try:
39+
check = CheckType.create(check_type)
40+
evaluate_args['value_to_compare'] = extract_data_from_json(value, jpath, exclude)
41+
if reference_data:
42+
evaluate_args['reference_data'] = extract_data_from_json(reference_data, jpath, exclude)
43+
eval_results, passed = check.evaluate(**evaluate_args)
44+
except NotImplementedError:
45+
raise AnsibleError(f"CheckType '{check_type}' not supported by jdiff")
46+
except Exception as e:
47+
raise AnsibleError(f"Exception in backend jdiff library: {e}")
48+
49+
return dict(
50+
success=passed,
51+
fail_details=eval_results,
52+
)
53+
54+
55+
class ActionModule(ActionBase):
56+
"""Ansible Action Module to interact with jdiff.
57+
Args:
58+
ActionBase (ActionBase): Ansible Action Plugin
59+
"""
60+
61+
def run(self, tmp=None, task_vars=None):
62+
"""Run of action plugin for interacting with jdiff.
63+
Args:
64+
tmp ([type], optional): [description]. Defaults to None.
65+
task_vars ([type], optional): [description]. Defaults to None.
66+
"""
67+
if JDIFF_IMPORT_ERROR:
68+
raise_from(
69+
AnsibleError("jdiff library must be installed to use this plugin"),
70+
JDIFF_IMPORT_ERROR,
71+
)
72+
73+
self._supports_check_mode = True
74+
self._supports_async = False
75+
76+
result = super(ActionModule, self).run(tmp, task_vars)
77+
del tmp
78+
79+
if result.get("skipped"):
80+
return None
81+
82+
if result.get("invocation", {}).get("module_args"):
83+
# avoid passing to modules in case of no_log
84+
# should not be set anymore but here for backwards compatibility
85+
del result["invocation"]["module_args"]
86+
87+
args = self._task.args
88+
return main(args=args)

plugins/modules/jdiff.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
#!/usr/bin/python
2+
# -*- coding: utf-8 -*-
3+
# Copyright: (c) 2022, Network to Code (@networktocode) <[email protected]>
4+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
"""Ansible plugin definition for jdiff action plugin."""
6+
from __future__ import (absolute_import, division, print_function)
7+
8+
__metaclass__ = type
9+
10+
11+
DOCUMENTATION = """
12+
---
13+
module: jdiff
14+
short_description: Ansible module for jdiff.
15+
version_added: '1.1.0'
16+
description:
17+
- Ansible module wrapper on jdiff python library.
18+
requirements:
19+
- jdiff
20+
author: Patryk Szulczewski (@pszulczewski)
21+
options:
22+
check_type:
23+
description:
24+
- Check type supported by jdiff
25+
required: true
26+
type: str
27+
evaluate_args:
28+
description:
29+
- arguments for evaluate() method
30+
required: true
31+
type: dict
32+
jmespath:
33+
description:
34+
- JMESPath to extract specific values
35+
type: str
36+
default: "*"
37+
exclude:
38+
description:
39+
- list of keys to exclude
40+
type: list
41+
elements: str
42+
"""
43+
44+
EXAMPLES = """
45+
- name: "EXACT_MATCH - VALIDATE INTERFACE STATUS"
46+
networktocode.netauto.jdiff:
47+
check_type: "exact_match"
48+
evaluate_args:
49+
reference_data: "{{ ref_data }}"
50+
value_to_compare: "{{ data_to_compare }}"
51+
exclude:
52+
- interfaceStatistics
53+
register: result
54+
vars:
55+
ref_data: |
56+
{
57+
"Ethernet1": {
58+
"status": "up",
59+
"interfaceStatistics": {
60+
"inBitsRate": 3403.4362520883615,
61+
"inPktsRate": 3.7424095978179257,
62+
"outBitsRate": 16249.69114419833,
63+
"updateInterval": 300,
64+
"outPktsRate": 2.1111866059750692
65+
}
66+
}
67+
}
68+
data_to_compare: |
69+
{
70+
"Ethernet1": {
71+
"status": "down",
72+
"interfaceStatistics": {
73+
"inBitsRate": 3413.4362520883615,
74+
"inPktsRate": 3.7624095978179257,
75+
"outBitsRate": 16259.69114419833,
76+
"updateInterval": 300,
77+
"outPktsRate": 2.1211866059750692
78+
}
79+
}
80+
}
81+
82+
- name: "TOLERANCE - VALIDATE PREFIXES ARE WITH 10% TOLERANCE"
83+
networktocode.netauto.jdiff:
84+
check_type: "tolerance"
85+
evaluate_args:
86+
reference_data: "{{ ref_data }}"
87+
value_to_compare: "{{ data_to_compare }}"
88+
tolerance: 5
89+
jmespath: "*.*.ipv4.[accepted_prefixes]"
90+
register: result
91+
vars:
92+
ref_data: |
93+
{
94+
"10.1.0.0": {
95+
"address_family": {
96+
"ipv4": {
97+
"accepted_prefixes": 100,
98+
"sent_prefixes": 1
99+
}
100+
}
101+
}
102+
}
103+
data_to_compare: |
104+
{
105+
"10.1.0.0": {
106+
"address_family": {
107+
"ipv4": {
108+
"accepted_prefixes": 90,
109+
"sent_prefixes": 0
110+
}
111+
}
112+
}
113+
}
114+
115+
- name: "PARAMETER - VALIDATE PEER TYPE"
116+
networktocode.netauto.jdiff:
117+
check_type: "parameter_match"
118+
evaluate_args:
119+
value_to_compare: "{{ data_to_compare }}"
120+
mode: match
121+
params:
122+
linkType: external
123+
jmespath: peers[*].[$ip$,linkType]
124+
register: result
125+
vars:
126+
data_to_compare: |
127+
{
128+
"peers": [
129+
{
130+
"ip": "10.1.0.0",
131+
"linkType": "external"
132+
},
133+
{
134+
"ip": "10.2.0.0",
135+
"linkType": "external"
136+
}
137+
]
138+
}
139+
140+
- name: "REGEX - VALIDATE MAC FORMAT"
141+
networktocode.netauto.jdiff:
142+
check_type: "regex"
143+
evaluate_args:
144+
value_to_compare: "{{ data_to_compare }}"
145+
regex: "^([0-9a-fA-F]{2}(:|-)){5}([0-9a-fA-F]{2})$"
146+
mode: match
147+
jmespath: interfaces.*.[$name$,burnedInAddress]
148+
register: result
149+
vars:
150+
data_to_compare: |
151+
{
152+
"interfaces": {
153+
"Management1": {
154+
"burnedInAddress": "08:00:27:e6:b2:f8",
155+
"name": "Management1"
156+
},
157+
"Management2": {
158+
"burnedInAddress": "08-00-27-e6-b2-f9",
159+
"name": "Management2"
160+
}
161+
}
162+
}
163+
164+
- name: "OPERATOR - VALIDATE RX LEVEL WITHIN RANGE"
165+
networktocode.netauto.jdiff:
166+
check_type: "operator"
167+
evaluate_args:
168+
value_to_compare: "{{ data_to_compare }}"
169+
params:
170+
params:
171+
mode: in-range
172+
operator_data: [-8, -2]
173+
jmespath: ports[*].[$name$,RxPower]
174+
register: result
175+
vars:
176+
data_to_compare: |
177+
{
178+
"ports": [
179+
{
180+
"name": "1/1",
181+
"RxPower": -3.83,
182+
"state": "Connected"
183+
},
184+
{
185+
"name": "1/2",
186+
"RxPower": -3.21,
187+
"state": "Connected"
188+
}
189+
]
190+
}
191+
"""
192+
193+
RETURN = """
194+
changed:
195+
description: Indicates if change was made - always False.
196+
returned: success
197+
type: bool
198+
fail_details:
199+
description: output indicating where the check failed
200+
returned: success
201+
type: dict
202+
success:
203+
description: Indicates if the check was successful.
204+
returned: success
205+
type: bool
206+
"""
207+
208+
from ansible.module_utils.basic import AnsibleModule
209+
210+
211+
def main():
212+
"""Module function."""
213+
argument_spec = dict(
214+
check_type=dict(type='str', required=True),
215+
evaluate_args=dict(type='dict', required=True),
216+
jmespath=dict(type='str', default='*'),
217+
exclude=dict(type='list', elements='str', default=[]),
218+
)
219+
220+
AnsibleModule(
221+
argument_spec=argument_spec,
222+
required_together=[['check_type', 'evaluate_args']],
223+
supports_check_mode=True
224+
)
225+
226+
227+
if __name__ == "__main__": # pragma: no cover
228+
main()

0 commit comments

Comments
 (0)