-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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]>
- Loading branch information
1 parent
6a28fd3
commit 2ea1741
Showing
4 changed files
with
537 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright: (c) 2022, Network to Code (@networktocode) <[email protected]> | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
"""Jdiff Action Plugin for jdiff library.""" | ||
|
||
from __future__ import (absolute_import, division, print_function) | ||
|
||
from ansible.plugins.action import ActionBase | ||
from ansible.errors import AnsibleError | ||
from ansible.module_utils.six import raise_from | ||
|
||
try: | ||
from jdiff import CheckType, extract_data_from_json | ||
except ImportError as imp_exc: | ||
JDIFF_IMPORT_ERROR = imp_exc | ||
else: | ||
JDIFF_IMPORT_ERROR = None | ||
|
||
|
||
__metaclass__ = type | ||
|
||
|
||
def main(args): | ||
"""Module function.""" | ||
if "evaluate_args" not in args: | ||
raise AnsibleError("Invalid arguments, 'evaluate_args' not found.") | ||
check_type = args.get('check_type') | ||
evaluate_args = args.get('evaluate_args') | ||
if not isinstance(evaluate_args, dict): | ||
raise AnsibleError(f"'evaluate_args' invalid type, expected <class 'dict'>, got {type(evaluate_args)}") | ||
if "value_to_compare" not in evaluate_args: | ||
raise AnsibleError("Key 'value_to_compare' missing in 'evaluate_arguments'.") | ||
reference_data = evaluate_args.get("reference_data") | ||
value = evaluate_args['value_to_compare'] | ||
jpath = args.get('jmespath', '*') | ||
exclude = args.get('exclude') | ||
|
||
try: | ||
check = CheckType.create(check_type) | ||
evaluate_args['value_to_compare'] = extract_data_from_json(value, jpath, exclude) | ||
if reference_data: | ||
evaluate_args['reference_data'] = extract_data_from_json(reference_data, jpath, exclude) | ||
eval_results, passed = check.evaluate(**evaluate_args) | ||
except NotImplementedError: | ||
raise AnsibleError(f"CheckType '{check_type}' not supported by jdiff") | ||
except Exception as e: | ||
raise AnsibleError(f"Exception in backend jdiff library: {e}") | ||
|
||
return dict( | ||
success=passed, | ||
fail_details=eval_results, | ||
) | ||
|
||
|
||
class ActionModule(ActionBase): | ||
"""Ansible Action Module to interact with jdiff. | ||
Args: | ||
ActionBase (ActionBase): Ansible Action Plugin | ||
""" | ||
|
||
def run(self, tmp=None, task_vars=None): | ||
"""Run of action plugin for interacting with jdiff. | ||
Args: | ||
tmp ([type], optional): [description]. Defaults to None. | ||
task_vars ([type], optional): [description]. Defaults to None. | ||
""" | ||
if JDIFF_IMPORT_ERROR: | ||
raise_from( | ||
AnsibleError("jdiff library must be installed to use this plugin"), | ||
JDIFF_IMPORT_ERROR, | ||
) | ||
|
||
self._supports_check_mode = True | ||
self._supports_async = False | ||
|
||
result = super(ActionModule, self).run(tmp, task_vars) | ||
del tmp | ||
|
||
if result.get("skipped"): | ||
return None | ||
|
||
if result.get("invocation", {}).get("module_args"): | ||
# avoid passing to modules in case of no_log | ||
# should not be set anymore but here for backwards compatibility | ||
del result["invocation"]["module_args"] | ||
|
||
args = self._task.args | ||
return main(args=args) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
#!/usr/bin/python | ||
# -*- coding: utf-8 -*- | ||
# Copyright: (c) 2022, Network to Code (@networktocode) <[email protected]> | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
"""Ansible plugin definition for jdiff action plugin.""" | ||
from __future__ import (absolute_import, division, print_function) | ||
|
||
__metaclass__ = type | ||
|
||
|
||
DOCUMENTATION = """ | ||
--- | ||
module: jdiff | ||
short_description: Ansible module for jdiff. | ||
version_added: '1.1.0' | ||
description: | ||
- Ansible module wrapper on jdiff python library. | ||
requirements: | ||
- jdiff | ||
author: Patryk Szulczewski (@pszulczewski) | ||
options: | ||
check_type: | ||
description: | ||
- Check type supported by jdiff | ||
required: true | ||
type: str | ||
evaluate_args: | ||
description: | ||
- arguments for evaluate() method | ||
required: true | ||
type: dict | ||
jmespath: | ||
description: | ||
- JMESPath to extract specific values | ||
type: str | ||
default: "*" | ||
exclude: | ||
description: | ||
- list of keys to exclude | ||
type: list | ||
elements: str | ||
""" | ||
|
||
EXAMPLES = """ | ||
- name: "EXACT_MATCH - VALIDATE INTERFACE STATUS" | ||
networktocode.netauto.jdiff: | ||
check_type: "exact_match" | ||
evaluate_args: | ||
reference_data: "{{ ref_data }}" | ||
value_to_compare: "{{ data_to_compare }}" | ||
exclude: | ||
- interfaceStatistics | ||
register: result | ||
vars: | ||
ref_data: | | ||
{ | ||
"Ethernet1": { | ||
"status": "up", | ||
"interfaceStatistics": { | ||
"inBitsRate": 3403.4362520883615, | ||
"inPktsRate": 3.7424095978179257, | ||
"outBitsRate": 16249.69114419833, | ||
"updateInterval": 300, | ||
"outPktsRate": 2.1111866059750692 | ||
} | ||
} | ||
} | ||
data_to_compare: | | ||
{ | ||
"Ethernet1": { | ||
"status": "down", | ||
"interfaceStatistics": { | ||
"inBitsRate": 3413.4362520883615, | ||
"inPktsRate": 3.7624095978179257, | ||
"outBitsRate": 16259.69114419833, | ||
"updateInterval": 300, | ||
"outPktsRate": 2.1211866059750692 | ||
} | ||
} | ||
} | ||
- name: "TOLERANCE - VALIDATE PREFIXES ARE WITH 10% TOLERANCE" | ||
networktocode.netauto.jdiff: | ||
check_type: "tolerance" | ||
evaluate_args: | ||
reference_data: "{{ ref_data }}" | ||
value_to_compare: "{{ data_to_compare }}" | ||
tolerance: 5 | ||
jmespath: "*.*.ipv4.[accepted_prefixes]" | ||
register: result | ||
vars: | ||
ref_data: | | ||
{ | ||
"10.1.0.0": { | ||
"address_family": { | ||
"ipv4": { | ||
"accepted_prefixes": 100, | ||
"sent_prefixes": 1 | ||
} | ||
} | ||
} | ||
} | ||
data_to_compare: | | ||
{ | ||
"10.1.0.0": { | ||
"address_family": { | ||
"ipv4": { | ||
"accepted_prefixes": 90, | ||
"sent_prefixes": 0 | ||
} | ||
} | ||
} | ||
} | ||
- name: "PARAMETER - VALIDATE PEER TYPE" | ||
networktocode.netauto.jdiff: | ||
check_type: "parameter_match" | ||
evaluate_args: | ||
value_to_compare: "{{ data_to_compare }}" | ||
mode: match | ||
params: | ||
linkType: external | ||
jmespath: peers[*].[$ip$,linkType] | ||
register: result | ||
vars: | ||
data_to_compare: | | ||
{ | ||
"peers": [ | ||
{ | ||
"ip": "10.1.0.0", | ||
"linkType": "external" | ||
}, | ||
{ | ||
"ip": "10.2.0.0", | ||
"linkType": "external" | ||
} | ||
] | ||
} | ||
- name: "REGEX - VALIDATE MAC FORMAT" | ||
networktocode.netauto.jdiff: | ||
check_type: "regex" | ||
evaluate_args: | ||
value_to_compare: "{{ data_to_compare }}" | ||
regex: "^([0-9a-fA-F]{2}(:|-)){5}([0-9a-fA-F]{2})$" | ||
mode: match | ||
jmespath: interfaces.*.[$name$,burnedInAddress] | ||
register: result | ||
vars: | ||
data_to_compare: | | ||
{ | ||
"interfaces": { | ||
"Management1": { | ||
"burnedInAddress": "08:00:27:e6:b2:f8", | ||
"name": "Management1" | ||
}, | ||
"Management2": { | ||
"burnedInAddress": "08-00-27-e6-b2-f9", | ||
"name": "Management2" | ||
} | ||
} | ||
} | ||
- name: "OPERATOR - VALIDATE RX LEVEL WITHIN RANGE" | ||
networktocode.netauto.jdiff: | ||
check_type: "operator" | ||
evaluate_args: | ||
value_to_compare: "{{ data_to_compare }}" | ||
params: | ||
params: | ||
mode: in-range | ||
operator_data: [-8, -2] | ||
jmespath: ports[*].[$name$,RxPower] | ||
register: result | ||
vars: | ||
data_to_compare: | | ||
{ | ||
"ports": [ | ||
{ | ||
"name": "1/1", | ||
"RxPower": -3.83, | ||
"state": "Connected" | ||
}, | ||
{ | ||
"name": "1/2", | ||
"RxPower": -3.21, | ||
"state": "Connected" | ||
} | ||
] | ||
} | ||
""" | ||
|
||
RETURN = """ | ||
changed: | ||
description: Indicates if change was made - always False. | ||
returned: success | ||
type: bool | ||
fail_details: | ||
description: output indicating where the check failed | ||
returned: success | ||
type: dict | ||
success: | ||
description: Indicates if the check was successful. | ||
returned: success | ||
type: bool | ||
""" | ||
|
||
from ansible.module_utils.basic import AnsibleModule | ||
|
||
|
||
def main(): | ||
"""Module function.""" | ||
argument_spec = dict( | ||
check_type=dict(type='str', required=True), | ||
evaluate_args=dict(type='dict', required=True), | ||
jmespath=dict(type='str', default='*'), | ||
exclude=dict(type='list', elements='str', default=[]), | ||
) | ||
|
||
AnsibleModule( | ||
argument_spec=argument_spec, | ||
required_together=[['check_type', 'evaluate_args']], | ||
supports_check_mode=True | ||
) | ||
|
||
|
||
if __name__ == "__main__": # pragma: no cover | ||
main() |
Oops, something went wrong.