Skip to content

Commit

Permalink
jdiff module (#269)
Browse files Browse the repository at this point in the history
* 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
4 people authored Apr 12, 2023
1 parent 6a28fd3 commit 2ea1741
Show file tree
Hide file tree
Showing 4 changed files with 537 additions and 0 deletions.
88 changes: 88 additions & 0 deletions plugins/action/jdiff.py
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)
228 changes: 228 additions & 0 deletions plugins/modules/jdiff.py
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()
Loading

0 comments on commit 2ea1741

Please sign in to comment.