Add validate action/lookup/filter/test plugin (#13)
* Initial commit * Add validate lookup/filter/test plugin * Update agrspec validation logic * Add validate lookup/filter/test plugin * minor updates * remve unwanted code * doc update * update jsonschema validator plugin * Add validate sub-plugin configurable option * fix black failures * fix ansible-test validate module issue * fix santiy issues * more santiy fixes * remove GA black formatting * add black * fix black formatting * fix black issues * Add integration test and fix review comments * add jsonschema to requirments * fix ci issues * update GA to install requirments * fix GA to install requirments * move ValidateBase to base file * fix library not found issue in CI * add unit testpull/19/head
parent
c20ba34c7d
commit
1807ee7c7b
|
@ -97,8 +97,8 @@ jobs:
|
||||||
|
|
||||||
# OPTIONAL If your unit test requires Python libraries from other collections
|
# OPTIONAL If your unit test requires Python libraries from other collections
|
||||||
# Install them like this
|
# Install them like this
|
||||||
# - name: Install collection dependencies
|
- name: Install collection dependencies
|
||||||
# run: ansible-galaxy collection install ansible.netcommon -p .
|
run: ansible-galaxy collection install ansible.utils -p .
|
||||||
|
|
||||||
# Run the unit tests
|
# Run the unit tests
|
||||||
- name: Run unit test
|
- name: Run unit test
|
||||||
|
@ -156,10 +156,16 @@ jobs:
|
||||||
- name: Install ansible-base (${{ matrix.ansible }})
|
- name: Install ansible-base (${{ matrix.ansible }})
|
||||||
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
|
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
|
||||||
|
|
||||||
|
# - name: Install collection dependencies
|
||||||
|
# run: |
|
||||||
|
# python -m pip install --upgrade pip
|
||||||
|
# pip install -r requirements.txt
|
||||||
|
# working-directory: ./ansible_collections/ansible/utils
|
||||||
|
|
||||||
# OPTIONAL If your integration test requires Python libraries or modules from other collections
|
# OPTIONAL If your integration test requires Python libraries or modules from other collections
|
||||||
# Install them like this
|
# Install them like this
|
||||||
# - name: Install collection dependencies
|
- name: Install collection dependencies
|
||||||
# run: ansible-galaxy collection install ansible.netcommon -p .
|
run: ansible-galaxy collection install ansible.utils -p .
|
||||||
|
|
||||||
# Run the integration tests
|
# Run the integration tests
|
||||||
- name: Run integration test
|
- name: Run integration test
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
name: Publish
|
name: Publish
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
extends: default
|
||||||
|
|
||||||
|
ignore: |
|
||||||
|
.tox
|
||||||
|
changelogs/*
|
||||||
|
|
||||||
|
rules:
|
||||||
|
braces:
|
||||||
|
max-spaces-inside: 1
|
||||||
|
level: error
|
||||||
|
brackets:
|
||||||
|
max-spaces-inside: 1
|
||||||
|
level: error
|
||||||
|
line-length: disable
|
|
@ -1,3 +1,3 @@
|
||||||
---
|
---
|
||||||
minor_changes:
|
major_changes:
|
||||||
- Add fact_diff module. Find the difference between text, files or facts
|
- Added validate module/lookup/filter/test plugin to validate data based on given criteria
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from ansible.plugins.action import ActionBase
|
from ansible.plugins.action import ActionBase
|
||||||
from ansible.errors import AnsibleActionFail
|
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
from ansible_collections.ansible.utils.plugins.modules.fact_diff import (
|
from ansible_collections.ansible.utils.plugins.modules.fact_diff import (
|
||||||
DOCUMENTATION,
|
DOCUMENTATION,
|
||||||
|
|
|
@ -6,18 +6,17 @@
|
||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
from ansible.plugins.action import ActionBase
|
from ansible.plugins.action import ActionBase
|
||||||
from ansible.errors import AnsibleActionFail
|
|
||||||
|
|
||||||
from ansible.module_utils.common._collections_compat import (
|
from ansible.module_utils.common._collections_compat import (
|
||||||
MutableMapping,
|
MutableMapping,
|
||||||
MutableSequence,
|
MutableSequence,
|
||||||
)
|
)
|
||||||
from ansible.module_utils import basic
|
|
||||||
from ansible.module_utils._text import to_bytes, to_native
|
from ansible.module_utils._text import to_native
|
||||||
from jinja2 import Template, TemplateSyntaxError
|
from jinja2 import Template, TemplateSyntaxError
|
||||||
from ansible_collections.ansible.utils.plugins.modules.update_fact import (
|
from ansible_collections.ansible.utils.plugins.modules.update_fact import (
|
||||||
DOCUMENTATION,
|
DOCUMENTATION,
|
||||||
|
@ -40,9 +39,7 @@ class ActionModule(ActionBase):
|
||||||
|
|
||||||
def _check_argspec(self):
|
def _check_argspec(self):
|
||||||
aav = AnsibleArgSpecValidator(
|
aav = AnsibleArgSpecValidator(
|
||||||
data=self._task.args,
|
data=self._task.args, schema=DOCUMENTATION, name=self._task.action
|
||||||
schema=DOCUMENTATION,
|
|
||||||
name=self._task.action,
|
|
||||||
)
|
)
|
||||||
valid, errors, self._task.args = aav.validate()
|
valid, errors, self._task.args = aav.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
"""
|
||||||
|
The action plugin file for validate
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleActionFail, AnsibleError
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible.plugins.action import ActionBase
|
||||||
|
|
||||||
|
from ansible_collections.ansible.utils.plugins.modules.validate import (
|
||||||
|
DOCUMENTATION,
|
||||||
|
)
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.validate.base import (
|
||||||
|
load_validator,
|
||||||
|
)
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import (
|
||||||
|
check_argspec,
|
||||||
|
)
|
||||||
|
|
||||||
|
ARGSPEC_CONDITIONALS = {}
|
||||||
|
|
||||||
|
|
||||||
|
class ActionModule(ActionBase):
|
||||||
|
"""action module"""
|
||||||
|
|
||||||
|
VALIDATE_CLS_NAME = "Validate"
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ActionModule, self).__init__(*args, **kwargs)
|
||||||
|
self._validator_name = None
|
||||||
|
self._result = {}
|
||||||
|
|
||||||
|
def _debug(self, name, msg):
|
||||||
|
"""Output text using ansible's display
|
||||||
|
|
||||||
|
:param msg: The message
|
||||||
|
:type msg: str
|
||||||
|
"""
|
||||||
|
msg = "<{phost}> {name} {msg}".format(
|
||||||
|
phost=self._playhost, name=name, msg=msg
|
||||||
|
)
|
||||||
|
self._display.vvvv(msg)
|
||||||
|
|
||||||
|
def run(self, tmp=None, task_vars=None):
|
||||||
|
"""The std execution entry pt for an action plugin
|
||||||
|
|
||||||
|
:param tmp: no longer used
|
||||||
|
:type tmp: none
|
||||||
|
:param task_vars: The vars provided when the task is run
|
||||||
|
:type task_vars: dict
|
||||||
|
:return: The results from the parser
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
|
valid, argspec_result, updated_params = check_argspec(
|
||||||
|
DOCUMENTATION,
|
||||||
|
"validate module",
|
||||||
|
schema_conditionals=ARGSPEC_CONDITIONALS,
|
||||||
|
**self._task.args
|
||||||
|
)
|
||||||
|
if not valid:
|
||||||
|
return argspec_result
|
||||||
|
|
||||||
|
self._task_vars = task_vars
|
||||||
|
self._playhost = task_vars.get("inventory_hostname") if task_vars else None
|
||||||
|
|
||||||
|
self._validator_engine, validator_result = load_validator(
|
||||||
|
engine=updated_params["engine"],
|
||||||
|
data=updated_params["data"],
|
||||||
|
criteria=updated_params["criteria"],
|
||||||
|
plugin_vars=task_vars,
|
||||||
|
)
|
||||||
|
if validator_result.get("failed"):
|
||||||
|
return validator_result
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self._validator_engine.validate()
|
||||||
|
except AnsibleError as exc:
|
||||||
|
raise AnsibleActionFail(
|
||||||
|
to_text(exc, errors="surrogate_then_replace")
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
raise AnsibleActionFail(
|
||||||
|
"Unhandled exception from validator '{validator}'. Error: {err}".format(
|
||||||
|
validator=self._validator_engine,
|
||||||
|
err=to_text(exc, errors="surrogate_then_replace"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.get("errors"):
|
||||||
|
self._result["errors"] = result["errors"]
|
||||||
|
self._result.update({"failed": True})
|
||||||
|
if "msg" in result:
|
||||||
|
self._result["msg"] = (
|
||||||
|
"Validation errors were found.\n" + result["msg"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._result["msg"] = "Validation errors were found."
|
||||||
|
else:
|
||||||
|
self._result["msg"] = "all checks passed"
|
||||||
|
return self._result
|
|
@ -36,14 +36,18 @@ class FactDiff(FactDiffBase):
|
||||||
self._debug("'after' is a string, splitting lines")
|
self._debug("'after' is a string, splitting lines")
|
||||||
self._after = self._after.splitlines()
|
self._after = self._after.splitlines()
|
||||||
self._before = [
|
self._before = [
|
||||||
l
|
line
|
||||||
for l in self._before
|
for line in self._before
|
||||||
if not any(regex.match(str(l)) for regex in self._skip_lines)
|
if not any(
|
||||||
|
regex.match(str(line)) for regex in self._skip_lines
|
||||||
|
)
|
||||||
]
|
]
|
||||||
self._after = [
|
self._after = [
|
||||||
l
|
line
|
||||||
for l in self._after
|
for line in self._after
|
||||||
if not any(regex.match(str(l)) for regex in self._skip_lines)
|
if not any(
|
||||||
|
regex.match(str(line)) for regex in self._skip_lines
|
||||||
|
)
|
||||||
]
|
]
|
||||||
if isinstance(self._before, list):
|
if isinstance(self._before, list):
|
||||||
self._debug("'before' is a list, joining with \n")
|
self._debug("'before' is a list, joining with \n")
|
||||||
|
|
|
@ -167,9 +167,7 @@ def _get_path(*args, **kwargs):
|
||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
environment = data.pop("environment")
|
environment = data.pop("environment")
|
||||||
aav = AnsibleArgSpecValidator(
|
aav = AnsibleArgSpecValidator(
|
||||||
data=data,
|
data=data, schema=DOCUMENTATION, name="get_path"
|
||||||
schema=DOCUMENTATION,
|
|
||||||
name="get_path",
|
|
||||||
)
|
)
|
||||||
valid, errors, updated_data = aav.validate()
|
valid, errors, updated_data = aav.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
|
|
|
@ -321,9 +321,7 @@ def _index_of(*args, **kwargs):
|
||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
environment = data.pop("environment")
|
environment = data.pop("environment")
|
||||||
aav = AnsibleArgSpecValidator(
|
aav = AnsibleArgSpecValidator(
|
||||||
data=data,
|
data=data, schema=DOCUMENTATION, name="index_of"
|
||||||
schema=DOCUMENTATION,
|
|
||||||
name="index_of",
|
|
||||||
)
|
)
|
||||||
valid, errors, updated_data = aav.validate()
|
valid, errors, updated_data = aav.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
|
|
|
@ -132,9 +132,7 @@ def _to_paths(*args, **kwargs):
|
||||||
data = dict(zip(keys, args))
|
data = dict(zip(keys, args))
|
||||||
data.update(kwargs)
|
data.update(kwargs)
|
||||||
aav = AnsibleArgSpecValidator(
|
aav = AnsibleArgSpecValidator(
|
||||||
data=data,
|
data=data, schema=DOCUMENTATION, name="to_paths"
|
||||||
schema=DOCUMENTATION,
|
|
||||||
name="to_paths",
|
|
||||||
)
|
)
|
||||||
valid, errors, updated_data = aav.validate()
|
valid, errors, updated_data = aav.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
filter: validate
|
||||||
|
author: Ganesh Nalawade (@ganeshrn)
|
||||||
|
version_added: "1.0.0"
|
||||||
|
short_description: Validate data with provided criteria
|
||||||
|
description:
|
||||||
|
- Validate C(data) with provided C(criteria) based on the validation C(engine).
|
||||||
|
options:
|
||||||
|
data:
|
||||||
|
type: raw
|
||||||
|
description:
|
||||||
|
- A data that will be validated against C(criteria).
|
||||||
|
- This option represents the value that is passed to filter plugin in pipe format.
|
||||||
|
For example I(config_data|ansible.utils.validate()), in this case I(config_data)
|
||||||
|
represents this option.
|
||||||
|
- For the type of C(data) that represents this value refer documentation of individual validator plugins.
|
||||||
|
required: True
|
||||||
|
criteria:
|
||||||
|
type: raw
|
||||||
|
description:
|
||||||
|
- The criteria used for validation of value that represents C(data) options.
|
||||||
|
- This option represents the first argument passed in the filter plugin
|
||||||
|
For example I(config_data|ansible.utils.validate(config_criteria)), in
|
||||||
|
this case the value of I(config_criteria) represents this option.
|
||||||
|
- For the type of C(criteria) that represents this value refer documentation of individual validator plugins.
|
||||||
|
required: True
|
||||||
|
engine:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- The name of the validator plugin to use.
|
||||||
|
- This option can be passed in lookup plugin as a key, value pair
|
||||||
|
For example I(config_data|ansible.utils.validate(config_criteria, engine='ansible.utils.jsonschema')), in
|
||||||
|
this case the value I(ansible.utils.jsonschema) represents the engine to be use for data valdiation.
|
||||||
|
If the value is not provided the default value that is I(ansible.uitls.jsonschema) will be used.
|
||||||
|
- The value should be in fully qualified collection name format that is
|
||||||
|
I(<org-name>.<collection-name>.<validator-plugin-name>).
|
||||||
|
default: ansible.utils.jsonschema
|
||||||
|
notes:
|
||||||
|
- For the type of options C(data) and C(criteria) refer the individual C(validate) plugin
|
||||||
|
documentation that is represented in the value of C(engine) option.
|
||||||
|
- For additional plugin configuration options refer the individual C(validate) plugin
|
||||||
|
documentation that is represented by the value of C(engine) option.
|
||||||
|
- The plugin configuration option can be either passed as I(key=value) pairs within filter plugin
|
||||||
|
or environment variables.
|
||||||
|
- The precedence of the C(validate) plugin configurable option is the variable passed within filter plugin
|
||||||
|
as I(key=value) pairs followed by the environment variables.
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = r"""
|
||||||
|
- name: set facts for data and criteria
|
||||||
|
set_fact:
|
||||||
|
data: "{{ lookup('file', './validate/data/show_interfaces_iosxr.json')}}"
|
||||||
|
criteria: "{{ lookup('file', './validate/criteria/jsonschema/show_interfaces_iosxr.json')}}"
|
||||||
|
|
||||||
|
- name: validate data in json format using jsonschema with by passing plugin configuration variable as key/value pairs
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_validilty: "{{ data|ansible.utils.validate(criteria, engine='ansible.utils.jsonschema', draft='draft7') }}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
_raw:
|
||||||
|
description:
|
||||||
|
- If data is valid returns empty list
|
||||||
|
- If data is invalid returns list of errors in data
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError, AnsibleFilterError
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.validate.base import (
|
||||||
|
load_validator,
|
||||||
|
)
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.utils import (
|
||||||
|
to_list,
|
||||||
|
)
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import (
|
||||||
|
check_argspec,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ARGSPEC_CONDITIONALS = {}
|
||||||
|
|
||||||
|
|
||||||
|
def validate(*args, **kwargs):
|
||||||
|
if len(args) < 2:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
"Missing either 'data' or 'criteria' value in filter input,"
|
||||||
|
" refer 'ansible.utils.validate' filter plugin documentation for details"
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {"data": args[0], "criteria": args[1]}
|
||||||
|
if kwargs.get("engine"):
|
||||||
|
params.update({"engine": kwargs["engine"]})
|
||||||
|
|
||||||
|
valid, argspec_result, updated_params = check_argspec(
|
||||||
|
DOCUMENTATION,
|
||||||
|
"validate filter",
|
||||||
|
schema_conditionals=ARGSPEC_CONDITIONALS,
|
||||||
|
**params
|
||||||
|
)
|
||||||
|
if not valid:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
"{argspec_result} with errors: {argspec_errors}".format(
|
||||||
|
argspec_result=argspec_result.get("msg"),
|
||||||
|
argspec_errors=argspec_result.get("errors"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
validator_engine, validator_result = load_validator(
|
||||||
|
engine=updated_params["engine"],
|
||||||
|
data=updated_params["data"],
|
||||||
|
criteria=updated_params["criteria"],
|
||||||
|
kwargs=kwargs,
|
||||||
|
)
|
||||||
|
if validator_result.get("failed"):
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
"validate lookup plugin failed with errors: {msg}".format(
|
||||||
|
msg=validator_result.get("msg")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = validator_engine.validate()
|
||||||
|
except AnsibleError as exc:
|
||||||
|
raise AnsibleFilterError(to_text(exc, errors="surrogate_then_replace"))
|
||||||
|
except Exception as exc:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
"Unhandled exception from validator '{validator}'. Error: {err}".format(
|
||||||
|
validator=updated_params["engine"],
|
||||||
|
err=to_text(exc, errors="surrogate_then_replace"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return to_list(result.get("errors", []))
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
""" index_of """
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
"""a mapping of filter names to functions"""
|
||||||
|
return {"validate": validate}
|
|
@ -171,9 +171,7 @@ class LookupModule(LookupBase):
|
||||||
terms = dict(zip(keys, terms))
|
terms = dict(zip(keys, terms))
|
||||||
terms.update(kwargs)
|
terms.update(kwargs)
|
||||||
aav = AnsibleArgSpecValidator(
|
aav = AnsibleArgSpecValidator(
|
||||||
data=terms,
|
data=terms, schema=DOCUMENTATION, name="get_path"
|
||||||
schema=DOCUMENTATION,
|
|
||||||
name="get_path",
|
|
||||||
)
|
)
|
||||||
valid, errors, updated_data = aav.validate()
|
valid, errors, updated_data = aav.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
|
|
|
@ -338,9 +338,7 @@ class LookupModule(LookupBase):
|
||||||
terms = dict(zip(keys, terms))
|
terms = dict(zip(keys, terms))
|
||||||
terms.update(kwargs)
|
terms.update(kwargs)
|
||||||
aav = AnsibleArgSpecValidator(
|
aav = AnsibleArgSpecValidator(
|
||||||
data=terms,
|
data=terms, schema=DOCUMENTATION, name="index_of"
|
||||||
schema=DOCUMENTATION,
|
|
||||||
name="index_of",
|
|
||||||
)
|
)
|
||||||
valid, errors, updated_data = aav.validate()
|
valid, errors, updated_data = aav.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
|
|
|
@ -142,9 +142,7 @@ class LookupModule(LookupBase):
|
||||||
terms = dict(zip(keys, terms))
|
terms = dict(zip(keys, terms))
|
||||||
terms.update(kwargs)
|
terms.update(kwargs)
|
||||||
aav = AnsibleArgSpecValidator(
|
aav = AnsibleArgSpecValidator(
|
||||||
data=terms,
|
data=terms, schema=DOCUMENTATION, name="to_paths"
|
||||||
schema=DOCUMENTATION,
|
|
||||||
name="to_paths",
|
|
||||||
)
|
)
|
||||||
valid, errors, updated_data = aav.validate()
|
valid, errors, updated_data = aav.validate()
|
||||||
if not valid:
|
if not valid:
|
||||||
|
|
|
@ -0,0 +1,154 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
lookup: validate
|
||||||
|
author: Ganesh Nalawade (@ganeshrn)
|
||||||
|
version_added: "1.0.0"
|
||||||
|
short_description: Validate data with provided criteria
|
||||||
|
description:
|
||||||
|
- Validate C(data) with provided C(criteria) based on the validation C(engine).
|
||||||
|
options:
|
||||||
|
data:
|
||||||
|
type: raw
|
||||||
|
description:
|
||||||
|
- A data that will be validated against C(criteria).
|
||||||
|
- This option represents the value that is passed to lookup plugin as first argument.
|
||||||
|
For example I(lookup(config_data, config_criteria, engine='ansible.utils.jsonschema')),
|
||||||
|
in this case I(config_data) represents this option.
|
||||||
|
- For the type of C(data) that represents this value refer documentation of individual validate plugins.
|
||||||
|
required: True
|
||||||
|
criteria:
|
||||||
|
type: raw
|
||||||
|
description:
|
||||||
|
- The criteria used for validation of value that represents C(data) options.
|
||||||
|
- This option represents the second argument passed in the lookup plugin
|
||||||
|
For example I(lookup(config_data, config_criteria, engine='ansible.utils.jsonschema')),
|
||||||
|
in this case the value of I(config_criteria) represents this option.
|
||||||
|
- For the type of C(criteria) that represents this value refer documentation of individual
|
||||||
|
validate plugins.
|
||||||
|
required: True
|
||||||
|
engine:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- The name of the validate plugin to use.
|
||||||
|
- This option can be passed in lookup plugin as a key, value pair
|
||||||
|
For example I(lookup(config_data, config_criteria, engine='ansible.utils.jsonschema')), in
|
||||||
|
this case the value I(ansible.utils.jsonschema) represents the engine to be use for data valdiation.
|
||||||
|
If the value is not provided the default value that is I(ansible.uitls.jsonschema) will be used.
|
||||||
|
- The value should be in fully qualified collection name format that is
|
||||||
|
I(<org-name>.<collection-name>.<validate-plugin-name>).
|
||||||
|
default: ansible.utils.jsonschema
|
||||||
|
notes:
|
||||||
|
- For the type of options C(data) and C(criteria) refer the individual C(validate) plugin
|
||||||
|
documentation that is represented in the value of C(engine) option.
|
||||||
|
- For additional plugin configuration options refer the individual C(validate) plugin
|
||||||
|
documentation that is represented by the value of C(engine) option.
|
||||||
|
- The plugin configuration option can be either passed as I(key=value) pairs within lookup plugin
|
||||||
|
or task or environment variables.
|
||||||
|
- The precedence the C(validate) plugin configurable option is the variable passed within lookup plugin
|
||||||
|
as I(key=value) pairs followed by task variables followed by environment variables.
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = r"""
|
||||||
|
- name: set facts for data and criteria
|
||||||
|
set_fact:
|
||||||
|
data: "{{ lookup('file', './validate/data/show_interfaces_iosxr.json')}}"
|
||||||
|
criteria: "{{ lookup('file', './validate/criteria/jsonschema/show_interfaces_iosxr.json')}}"
|
||||||
|
|
||||||
|
- name: validate data in json format using jsonschema with lookup plugin by passing plugin configuration variable as key/value pairs
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup(data, criteria, engine='ansible.utils.jsonschema', draft='draft7') }}"
|
||||||
|
|
||||||
|
- name: validate data in json format using jsonschema with lookup plugin by passing plugin configuration variable as task variable
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', data, criteria, engine='ansible.utils.jsonschema', draft='draft7') }}"
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft3
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
_raw:
|
||||||
|
description:
|
||||||
|
- If data is valid returns empty list
|
||||||
|
- If data is invalid returns list of errors in data
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError, AnsibleLookupError
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible.plugins.lookup import LookupBase
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.validate.base import (
|
||||||
|
load_validator,
|
||||||
|
)
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.utils import (
|
||||||
|
to_list,
|
||||||
|
)
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import (
|
||||||
|
check_argspec,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ARGSPEC_CONDITIONALS = {}
|
||||||
|
|
||||||
|
|
||||||
|
class LookupModule(LookupBase):
|
||||||
|
def run(self, terms, variables, **kwargs):
|
||||||
|
if len(terms) < 2:
|
||||||
|
raise AnsibleLookupError(
|
||||||
|
"missing either 'data' or 'criteria' value in lookup input,"
|
||||||
|
" refer ansible.utils.validate lookup plugin documentation for details"
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {"data": terms[0], "criteria": terms[1]}
|
||||||
|
if kwargs.get("engine"):
|
||||||
|
params.update({"engine": kwargs["engine"]})
|
||||||
|
|
||||||
|
valid, argspec_result, updated_params = check_argspec(
|
||||||
|
DOCUMENTATION,
|
||||||
|
"validate lookup",
|
||||||
|
schema_conditionals=ARGSPEC_CONDITIONALS,
|
||||||
|
**params
|
||||||
|
)
|
||||||
|
if not valid:
|
||||||
|
raise AnsibleLookupError(
|
||||||
|
"{argspec_result} with errors: {argspec_errors}".format(
|
||||||
|
argspec_result=argspec_result.get("msg"),
|
||||||
|
argspec_errors=argspec_result.get("errors"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
validator_engine, validator_result = load_validator(
|
||||||
|
engine=updated_params["engine"],
|
||||||
|
data=updated_params["data"],
|
||||||
|
criteria=updated_params["criteria"],
|
||||||
|
plugin_vars=variables,
|
||||||
|
kwargs=kwargs,
|
||||||
|
)
|
||||||
|
if validator_result.get("failed"):
|
||||||
|
raise AnsibleLookupError(
|
||||||
|
"validate lookup plugin failed with errors: {validator_result}".format(
|
||||||
|
validator_result=validator_result.get("msg")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = validator_engine.validate()
|
||||||
|
except AnsibleError as exc:
|
||||||
|
raise AnsibleLookupError(
|
||||||
|
to_text(exc, errors="surrogate_then_replace")
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
raise AnsibleLookupError(
|
||||||
|
"Unhandled exception from validator '{validator}'. Error: {err}".format(
|
||||||
|
validator=updated_params["engine"],
|
||||||
|
err=to_text(exc, errors="surrogate_then_replace"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return to_list(result.get("errors", []))
|
|
@ -23,14 +23,12 @@ from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible_collections.ansible.utils.plugins.module_utils.common.utils import (
|
from ansible_collections.ansible.utils.plugins.module_utils.common.utils import (
|
||||||
dict_merge,
|
dict_merge,
|
||||||
)
|
)
|
||||||
from ansible.module_utils.six import iteritems, string_types
|
from ansible.module_utils.six import iteritems
|
||||||
from ansible.module_utils._text import to_bytes
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import yaml
|
import yaml
|
||||||
|
@ -48,7 +46,9 @@ except ImportError:
|
||||||
# ansible-base 2.11 should expose argspec validation outside of the
|
# ansible-base 2.11 should expose argspec validation outside of the
|
||||||
# ansiblemodule class
|
# ansiblemodule class
|
||||||
try:
|
try:
|
||||||
from ansible.module_utils.somefile import FutureBaseArgspecValidator
|
from ansible.module_utils.somefile import ( # noqa: F401
|
||||||
|
FutureBaseArgspecValidator,
|
||||||
|
)
|
||||||
|
|
||||||
HAS_ANSIBLE_ARG_SPEC_VALIDATOR = True
|
HAS_ANSIBLE_ARG_SPEC_VALIDATOR = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -107,9 +107,7 @@ class MonkeyModule(AnsibleModule):
|
||||||
"""
|
"""
|
||||||
if self.name:
|
if self.name:
|
||||||
msg = re.sub(
|
msg = re.sub(
|
||||||
r"\(basic\.pyc?\)",
|
r"\(basic\.pyc?\)", "'{name}'".format(name=self.name), msg
|
||||||
"'{name}'".format(name=self.name),
|
|
||||||
msg,
|
|
||||||
)
|
)
|
||||||
self._valid = False
|
self._valid = False
|
||||||
self._errors = msg
|
self._errors = msg
|
||||||
|
@ -243,3 +241,28 @@ class AnsibleArgSpecValidator:
|
||||||
return self._validate()
|
return self._validate()
|
||||||
else:
|
else:
|
||||||
return self._validate()
|
return self._validate()
|
||||||
|
|
||||||
|
|
||||||
|
def check_argspec(
|
||||||
|
schema, name, schema_format="doc", schema_conditionals=None, **args
|
||||||
|
):
|
||||||
|
if schema_conditionals is None:
|
||||||
|
schema_conditionals = {}
|
||||||
|
|
||||||
|
aav = AnsibleArgSpecValidator(
|
||||||
|
data=args,
|
||||||
|
schema=schema,
|
||||||
|
schema_format=schema_format,
|
||||||
|
schema_conditionals=schema_conditionals,
|
||||||
|
name=name,
|
||||||
|
)
|
||||||
|
result = {}
|
||||||
|
valid, errors, updated_params = aav.validate()
|
||||||
|
if not valid:
|
||||||
|
result["errors"] = errors
|
||||||
|
result["failed"] = True
|
||||||
|
result["msg"] = "argspec validation failed for {name} plugin".format(
|
||||||
|
name=name
|
||||||
|
)
|
||||||
|
|
||||||
|
return valid, result, updated_params
|
||||||
|
|
|
@ -12,7 +12,7 @@ from __future__ import absolute_import, division, print_function
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from ansible.module_utils.common.collections import is_sequence
|
|
||||||
from ansible.module_utils.six import string_types, integer_types
|
from ansible.module_utils.six import string_types, integer_types
|
||||||
from ansible.module_utils._text import to_native
|
from ansible.module_utils._text import to_native
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,9 @@ from __future__ import absolute_import, division, print_function
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from ansible.module_utils.common._collections_compat import Mapping
|
from ansible.module_utils.common._collections_compat import Mapping
|
||||||
from ansible.module_utils.six import iteritems, string_types
|
from ansible.module_utils.six import iteritems
|
||||||
|
|
||||||
|
|
||||||
def sort_list(val):
|
def sort_list(val):
|
||||||
|
@ -102,3 +101,12 @@ def dict_merge(base, other):
|
||||||
combined[key] = other.get(key)
|
combined[key] = other.get(key)
|
||||||
|
|
||||||
return combined
|
return combined
|
||||||
|
|
||||||
|
|
||||||
|
def to_list(val):
|
||||||
|
if isinstance(val, (list, tuple, set)):
|
||||||
|
return list(val)
|
||||||
|
elif val is not None:
|
||||||
|
return [val]
|
||||||
|
else:
|
||||||
|
return list()
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
"""
|
||||||
|
The base class for validator
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
try:
|
||||||
|
from importlib import import_module
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_native
|
||||||
|
|
||||||
|
|
||||||
|
def load_validator(
|
||||||
|
engine, data, criteria, plugin_vars=None, cls_name="Validate", kwargs=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Load the validate plugin from engine name
|
||||||
|
:param engine: Name of the validate engine in format <org-name>.<collection-name>.<validate-plugin>
|
||||||
|
:param vars: Variables for validate plugins. The variable information for each validate plugins can
|
||||||
|
be referred in individual plugin documentation.
|
||||||
|
:param cls_name: Base class name for validate plugin. Defaults to ``Validate``.
|
||||||
|
:param kwargs: The base name of the class for validate plugin
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
if plugin_vars is None:
|
||||||
|
plugin_vars = {}
|
||||||
|
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if len(engine.split(".")) != 3:
|
||||||
|
result["failed"] = True
|
||||||
|
result[
|
||||||
|
"msg"
|
||||||
|
] = "Parser name should be provided as a full name including collection"
|
||||||
|
return None, result
|
||||||
|
|
||||||
|
cref = dict(zip(["corg", "cname", "plugin"], engine.split(".")))
|
||||||
|
validatorlib = (
|
||||||
|
"ansible_collections.{corg}.{cname}.plugins.validate.{plugin}".format(
|
||||||
|
**cref
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
validatorcls = getattr(import_module(validatorlib), cls_name)
|
||||||
|
validator = validatorcls(
|
||||||
|
data=data,
|
||||||
|
criteria=criteria,
|
||||||
|
engine=engine,
|
||||||
|
plugin_vars=plugin_vars,
|
||||||
|
kwargs=kwargs,
|
||||||
|
)
|
||||||
|
return validator, result
|
||||||
|
except Exception as exc:
|
||||||
|
result["failed"] = True
|
||||||
|
result[
|
||||||
|
"msg"
|
||||||
|
] = "For engine '{engine}' error loading the corresponding validate plugin: {err}".format(
|
||||||
|
engine=engine, err=to_native(exc)
|
||||||
|
)
|
||||||
|
return None, result
|
|
@ -0,0 +1,79 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
module: validate
|
||||||
|
author:
|
||||||
|
- Bradley Thornton (@cidrblock)
|
||||||
|
- Ganesh Nalawade (@ganeshrn)
|
||||||
|
short_description: Validate data with provided criteria
|
||||||
|
description:
|
||||||
|
- Validate data with provided criteria based on the validation engine.
|
||||||
|
version_added: 1.0.0
|
||||||
|
options:
|
||||||
|
data:
|
||||||
|
type: raw
|
||||||
|
description:
|
||||||
|
- A data that will be validated against C(criteria). For the type of data refer
|
||||||
|
documentation of individual validate plugins
|
||||||
|
required: True
|
||||||
|
engine:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- The name of the validate plugin to use. The engine value should follow
|
||||||
|
the fully qualified collection name format that is
|
||||||
|
<org-name>.<collection-name>.<validate-plugin-name>.
|
||||||
|
default: ansible.utils.jsonschema
|
||||||
|
criteria:
|
||||||
|
type: raw
|
||||||
|
description:
|
||||||
|
- The criteria used for validation of C(data). For the type of criteria refer
|
||||||
|
documentation of individual validate plugins.
|
||||||
|
required: True
|
||||||
|
notes:
|
||||||
|
- For the type of options C(data) and C(criteria) refer the individual C(validate) plugin
|
||||||
|
documentation that is represented in the value of C(engine) option.
|
||||||
|
- For additional plugin configuration options refer the individual C(validate) plugin
|
||||||
|
documentation that is represented by the value of C(engine) option.
|
||||||
|
- The plugin configuration option can be either passed as task or environment variables.
|
||||||
|
- The precedence the C(validate) plugin configurable option is task variables followed
|
||||||
|
by the environment variables.
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = r"""
|
||||||
|
- name: set facts for data and criteria
|
||||||
|
set_fact:
|
||||||
|
data: "{{ lookup('file', './validate/data/show_interfaces_iosxr.json')}}"
|
||||||
|
criteria: "{{ lookup('file', './validate/criteria/jsonschema/show_interfaces_iosxr.json')}}"
|
||||||
|
|
||||||
|
- name: validate data in with jsonschema engine (by passing task vars as configurable plugin options)
|
||||||
|
ansible.utils.validate:
|
||||||
|
data: "{{ data }}"
|
||||||
|
criteria: "{{ criteria }}"
|
||||||
|
engine: ansible.utils.jsonschema
|
||||||
|
vars:
|
||||||
|
ansible_jsonschema_draft: draft7
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = r"""
|
||||||
|
msg:
|
||||||
|
description:
|
||||||
|
- The msg indicates if the C(data) is valid as per the C(criteria).
|
||||||
|
- In case data is valid return success message I(all checks passed)
|
||||||
|
- In case data is invalid return error message I(Validation errors were found)
|
||||||
|
along with more information on error is available.
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
errors:
|
||||||
|
description: The list of errors in C(data) based on the C(criteria).
|
||||||
|
returned: when C(data) value is invalid
|
||||||
|
type: list
|
||||||
|
"""
|
|
@ -0,0 +1,154 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
DOCUMENTATION = """
|
||||||
|
test: validate
|
||||||
|
author: Ganesh Nalawade (@ganeshrn)
|
||||||
|
version_added: "1.0.0"
|
||||||
|
short_description: Validate data with provided criteria
|
||||||
|
description:
|
||||||
|
- Validate C(data) with provided C(criteria) based on the validation C(engine).
|
||||||
|
options:
|
||||||
|
data:
|
||||||
|
type: raw
|
||||||
|
description:
|
||||||
|
- A data that will be validated against C(criteria).
|
||||||
|
- This option represents the value that is passed to test plugin as check.
|
||||||
|
For example I(config_data is ansible.utils.validate(criteria=criteria), in this case I(config_data)
|
||||||
|
represents this option.
|
||||||
|
- For the type of C(data) that represents this value refer documentation of individual validate plugins.
|
||||||
|
required: True
|
||||||
|
criteria:
|
||||||
|
type: raw
|
||||||
|
description:
|
||||||
|
- The criteria used for validation of value that represents C(data) options.
|
||||||
|
- This option is passed to the test plugin as key, value pair
|
||||||
|
For example I(config_data is ansible.utils.validate(criteria=criteria)), in
|
||||||
|
this case the value of I(criteria) key represents this criteria for data validation.
|
||||||
|
- For the type of C(criteria) that represents this value refer documentation of individual validate plugins.
|
||||||
|
required: True
|
||||||
|
engine:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- The name of the validate plugin to use.
|
||||||
|
- This option can be passed in test plugin as a key, value pair
|
||||||
|
For example I(config_data is ansible.utils.validate(engine='ansible.utils.jsonschema', criteria=criteria)), in
|
||||||
|
this case the value of I(engine) key represents the engine to be use for data valdiation.
|
||||||
|
If the value is not provided the default value that is I(ansible.uitls.jsonschema) will be used.
|
||||||
|
- The value should be in fully qualified collection name format that is
|
||||||
|
I(<org-name>.<collection-name>.<validate-plugin-name>).
|
||||||
|
default: ansible.utils.jsonschema
|
||||||
|
notes:
|
||||||
|
- For the type of options C(data) and C(criteria) refer the individual C(validate) plugin
|
||||||
|
documentation that is represented in the value of C(engine) option.
|
||||||
|
- For additional plugin configuration options refer the individual C(validate) plugin
|
||||||
|
documentation that is represented by the value of C(engine) option.
|
||||||
|
- The plugin configuration option can be either passed as I(key=value) pairs within test plugin
|
||||||
|
or set as environment variables.
|
||||||
|
- The precedence the C(validate) plugin configurable option is the variable passed within test plugin
|
||||||
|
as I(key=value) pairs followed by task variables followed by environment variables.
|
||||||
|
"""
|
||||||
|
|
||||||
|
EXAMPLES = r"""
|
||||||
|
- name: set facts for data and criteria
|
||||||
|
set_fact:
|
||||||
|
data: "{{ lookup('file', './validate/data/show_interfaces_iosxr.json')}}"
|
||||||
|
criteria: "{{ lookup('file', './validate/criteria/jsonschema/show_interfaces_iosxr.json')}}"
|
||||||
|
|
||||||
|
- name: validate data in json format using jsonschema with test plugin
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
is_data_valid: "{{ data is ansible.utils.validate(engine='ansible.utils.jsonschema', criteria=criteria, draft='draft7') }}"
|
||||||
|
"""
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
_raw:
|
||||||
|
description:
|
||||||
|
- If data is valid return C(true)
|
||||||
|
- If data is invalid return C(false)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.validate.base import (
|
||||||
|
load_validator,
|
||||||
|
)
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.utils import (
|
||||||
|
to_list,
|
||||||
|
)
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import (
|
||||||
|
check_argspec,
|
||||||
|
)
|
||||||
|
|
||||||
|
ARGSPEC_CONDITIONALS = {}
|
||||||
|
|
||||||
|
|
||||||
|
def validate(*args, **kwargs):
|
||||||
|
if not len(args):
|
||||||
|
raise AnsibleError(
|
||||||
|
"Missing either 'data' value in test plugin input,"
|
||||||
|
"refer ansible.utils.validate test plugin documentation for details"
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {"data": args[0]}
|
||||||
|
|
||||||
|
for item in ["engine", "criteria"]:
|
||||||
|
if kwargs.get(item):
|
||||||
|
params.update({item: kwargs[item]})
|
||||||
|
|
||||||
|
valid, argspec_result, updated_params = check_argspec(
|
||||||
|
DOCUMENTATION,
|
||||||
|
"validate test",
|
||||||
|
schema_conditionals=ARGSPEC_CONDITIONALS,
|
||||||
|
**params
|
||||||
|
)
|
||||||
|
if not valid:
|
||||||
|
raise AnsibleError(
|
||||||
|
"{argspec_result} with errors: {argspec_errors}".format(
|
||||||
|
argspec_result=argspec_result.get("msg"),
|
||||||
|
argspec_errors=argspec_result.get("errors"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
validator_engine, validator_result = load_validator(
|
||||||
|
engine=updated_params["engine"],
|
||||||
|
data=updated_params["data"],
|
||||||
|
criteria=updated_params["criteria"],
|
||||||
|
kwargs=kwargs,
|
||||||
|
)
|
||||||
|
if validator_result.get("failed"):
|
||||||
|
raise AnsibleError(
|
||||||
|
"validate lookup plugin failed with errors: %s"
|
||||||
|
% validator_result.get("msg")
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = validator_engine.validate()
|
||||||
|
except AnsibleError as exc:
|
||||||
|
raise AnsibleError(to_text(exc, errors="surrogate_then_replace"))
|
||||||
|
except Exception as exc:
|
||||||
|
raise AnsibleError(
|
||||||
|
"Unhandled exception from validator '{validator}'. Error: {err}".format(
|
||||||
|
validator=updated_params["engine"],
|
||||||
|
err=to_text(exc, errors="surrogate_then_replace"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
errors = to_list(result.get("errors", []))
|
||||||
|
if len(errors):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class TestModule(object):
|
||||||
|
"""data validation test"""
|
||||||
|
|
||||||
|
test_map = {"validate": validate}
|
||||||
|
|
||||||
|
def tests(self):
|
||||||
|
return self.test_map
|
|
@ -0,0 +1,140 @@
|
||||||
|
"""
|
||||||
|
The base class for validator
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError
|
||||||
|
from ansible.module_utils.six import iteritems
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import (
|
||||||
|
check_argspec,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.utils import (
|
||||||
|
to_list,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
try:
|
||||||
|
from yaml import CSafeLoader as SafeLoader
|
||||||
|
except ImportError:
|
||||||
|
from yaml import SafeLoader
|
||||||
|
HAS_YAML = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_YAML = False
|
||||||
|
|
||||||
|
|
||||||
|
class ValidateBase(object):
|
||||||
|
"""The base class for data validators
|
||||||
|
Provides a _debug function to normalize debug output
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, data, criteria, engine, plugin_vars=None, kwargs=None):
|
||||||
|
self._data = data
|
||||||
|
self._criteria = criteria
|
||||||
|
self._engine = engine
|
||||||
|
self._plugin_vars = plugin_vars if plugin_vars is not None else {}
|
||||||
|
self._result = {}
|
||||||
|
self._kwargs = kwargs if kwargs is not None else {}
|
||||||
|
self._sub_plugin_options = {}
|
||||||
|
|
||||||
|
cref = dict(zip(["corg", "cname", "plugin"], engine.split(".")))
|
||||||
|
validatorlib = "ansible_collections.{corg}.{cname}.plugins.validate.{plugin}".format(
|
||||||
|
**cref
|
||||||
|
)
|
||||||
|
|
||||||
|
validatordoc = getattr(import_module(validatorlib), "DOCUMENTATION")
|
||||||
|
if validatordoc:
|
||||||
|
self._set_sub_plugin_options(validatordoc)
|
||||||
|
|
||||||
|
def _set_sub_plugin_options(self, doc):
|
||||||
|
params = {}
|
||||||
|
try:
|
||||||
|
argspec_obj = yaml.load(doc, SafeLoader)
|
||||||
|
except Exception as exc:
|
||||||
|
raise AnsibleError(
|
||||||
|
"Error '{err}' while reading validate plugin {engine} documentation: '{argspec}'".format(
|
||||||
|
err=to_text(exc, errors="surrogate_or_strict"),
|
||||||
|
engine=self._engine,
|
||||||
|
argspec=doc,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
options = argspec_obj.get("options", {})
|
||||||
|
|
||||||
|
if not options:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for option_name, option_value in iteritems(options):
|
||||||
|
|
||||||
|
option_var_name_list = option_value.get("vars", [])
|
||||||
|
option_env_name_list = option_value.get("env", [])
|
||||||
|
|
||||||
|
# check if plugin configuration option passed as kwargs
|
||||||
|
# valid for lookup, filter, test plugins or pass through
|
||||||
|
# variables if supported by the module.
|
||||||
|
if option_name in self._kwargs:
|
||||||
|
params[option_name] = self._kwargs[option_name]
|
||||||
|
continue
|
||||||
|
|
||||||
|
# check if plugin configuration option passed in task vars eg.
|
||||||
|
# vars:
|
||||||
|
# - name: ansible_validate_jsonschema_draft
|
||||||
|
# - name: ansible_validate_jsonschema_draft_type
|
||||||
|
if option_var_name_list and (option_name not in params):
|
||||||
|
for var_name_entry in to_list(option_var_name_list):
|
||||||
|
if not isinstance(var_name_entry, dict):
|
||||||
|
raise AnsibleError(
|
||||||
|
"invalid type '{var_name_type}' for the value of '{var_name_entry}' option,"
|
||||||
|
" should to be type dict".format(
|
||||||
|
var_name_type=type(var_name_entry),
|
||||||
|
var_name_entry=var_name_entry,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
var_name = var_name_entry.get("name")
|
||||||
|
if var_name and var_name in self._plugin_vars:
|
||||||
|
params[option_name] = self._plugin_vars[var_name]
|
||||||
|
break
|
||||||
|
|
||||||
|
# check if plugin configuration option as passed as enviornment eg.
|
||||||
|
# env:
|
||||||
|
# - name: ANSIBLE_VALIDATE_JSONSCHEMA_DRAFT
|
||||||
|
if option_env_name_list and (option_name not in params):
|
||||||
|
for env_name_entry in to_list(option_env_name_list):
|
||||||
|
if not isinstance(env_name_entry, dict):
|
||||||
|
raise AnsibleError(
|
||||||
|
"invalid type '{env_name_entry_type}' for the value of '{env_name_entry}' option,"
|
||||||
|
" should to be type dict".format(
|
||||||
|
env_name_entry_type=type(env_name_entry),
|
||||||
|
env_name_entry=env_name_entry,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
env_name = env_name_entry.get("name")
|
||||||
|
if env_name in os.environ:
|
||||||
|
params[option_name] = os.environ[env_name]
|
||||||
|
break
|
||||||
|
|
||||||
|
valid, argspec_result, updated_params = check_argspec(
|
||||||
|
yaml.dump(argspec_obj), self._engine, **params
|
||||||
|
)
|
||||||
|
if not valid:
|
||||||
|
raise AnsibleError(
|
||||||
|
"{argspec_result} with errors: {argspec_errors}".format(
|
||||||
|
argspec_result=argspec_result.get("msg"),
|
||||||
|
argspec_errors=argspec_result.get("errors"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if updated_params:
|
||||||
|
self._sub_plugin_options = updated_params
|
||||||
|
|
||||||
|
def _get_sub_plugin_options(self, name):
|
||||||
|
return self._sub_plugin_options.get(name)
|
|
@ -0,0 +1,214 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
author: Ganesh Nalawade (@ganeshrn)
|
||||||
|
validate: jsonschema
|
||||||
|
short_description: Define configurable options for jsonschema validate plugin
|
||||||
|
description:
|
||||||
|
- This plugin documentation provides the configurable options that can be passed
|
||||||
|
to the validate plugins when I(ansible.utils.json) is used as a value for
|
||||||
|
engine option.
|
||||||
|
version_added: 1.0.0
|
||||||
|
options:
|
||||||
|
draft:
|
||||||
|
description:
|
||||||
|
- This option provides the jsonschema specification that should be used
|
||||||
|
for the validating the data. The C(criteria) option in the C(validate)
|
||||||
|
plugin should follow the specifiaction as mentined by this option
|
||||||
|
default: draft7
|
||||||
|
choices:
|
||||||
|
- draft3
|
||||||
|
- draft4
|
||||||
|
- draft6
|
||||||
|
- draft7
|
||||||
|
env:
|
||||||
|
- name: ANSIBLE_VALIDATE_JSONSCHEMA_DRAFT
|
||||||
|
vars:
|
||||||
|
- name: ansible_validate_jsonschema_draft
|
||||||
|
notes:
|
||||||
|
- The value of C(data) option should be either of type I(dict) or I(strings) which should be
|
||||||
|
a valid I(dict) when read in python.
|
||||||
|
- The value of C(criteria) should be I(list) of I(dict) or I(list) of I(strings) and each
|
||||||
|
I(string) within the I(list) entry should be a valid I(dict) when read in python.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
from ansible.module_utils._text import to_text
|
||||||
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
|
from ansible.errors import AnsibleError
|
||||||
|
from ansible.module_utils.six import string_types
|
||||||
|
|
||||||
|
from ansible_collections.ansible.utils.plugins.validate._base import ValidateBase
|
||||||
|
|
||||||
|
from ansible_collections.ansible.utils.plugins.module_utils.common.utils import (
|
||||||
|
to_list,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import jsonschema
|
||||||
|
|
||||||
|
HAS_JSONSCHEMA = True
|
||||||
|
except ImportError:
|
||||||
|
HAS_JSONSCHEMA = False
|
||||||
|
|
||||||
|
|
||||||
|
def to_path(fpath):
|
||||||
|
return ".".join(str(index) for index in fpath)
|
||||||
|
|
||||||
|
|
||||||
|
def json_path(absolute_path):
|
||||||
|
path = "$"
|
||||||
|
for elem in absolute_path:
|
||||||
|
if isinstance(elem, int):
|
||||||
|
path += "[" + str(elem) + "]"
|
||||||
|
else:
|
||||||
|
path += "." + elem
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
class Validate(ValidateBase):
|
||||||
|
@staticmethod
|
||||||
|
def _check_reqs():
|
||||||
|
"""Check the prerequisites are installed for jsonschema
|
||||||
|
|
||||||
|
:return None: In case all prerequisites are satisfised
|
||||||
|
"""
|
||||||
|
if not HAS_JSONSCHEMA:
|
||||||
|
raise AnsibleError(missing_required_lib("jsonschema"))
|
||||||
|
|
||||||
|
def _check_args(self):
|
||||||
|
"""Ensure specific args are set
|
||||||
|
|
||||||
|
:return: None: In case all arguments passed are valid
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if isinstance(self._data, dict):
|
||||||
|
self._data = json.loads(json.dumps(self._data))
|
||||||
|
elif isinstance(self._data, string_types):
|
||||||
|
self._data = json.loads(self._data)
|
||||||
|
else:
|
||||||
|
msg = "Expected value of 'data' option is either dict or str, received type '{data_type}'".format(
|
||||||
|
data_type=type(self._data)
|
||||||
|
)
|
||||||
|
raise AnsibleError(msg)
|
||||||
|
|
||||||
|
except (TypeError, json.decoder.JSONDecodeError) as exe:
|
||||||
|
msg = (
|
||||||
|
"'data' option value is invalid, value should of type dict or str format of dict."
|
||||||
|
" Failed to read with error '{err}'".format(
|
||||||
|
err=to_text(exe, errors="surrogate_then_replace")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
raise AnsibleError(msg)
|
||||||
|
|
||||||
|
try:
|
||||||
|
criteria = []
|
||||||
|
for item in to_list(self._criteria):
|
||||||
|
if isinstance(item, dict):
|
||||||
|
criteria.append(json.loads(json.dumps(item)))
|
||||||
|
elif isinstance(self._criteria, string_types):
|
||||||
|
criteria.append(json.loads(item))
|
||||||
|
else:
|
||||||
|
msg = "Expected value of 'criteria' option is either list of dict/str or dict or str, received type '{criteria_type}'".format(
|
||||||
|
criteria_type=type(criteria)
|
||||||
|
)
|
||||||
|
raise AnsibleError(msg)
|
||||||
|
|
||||||
|
self._criteria = criteria
|
||||||
|
except (TypeError, json.decoder.JSONDecodeError) as exe:
|
||||||
|
msg = (
|
||||||
|
"'criteria' option value is invalid, value should of type dict or str format of dict."
|
||||||
|
" Failed to read with error '{err}'".format(
|
||||||
|
err=to_text(exe, errors="surrogate_then_replace")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
raise AnsibleError(msg)
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
"""Std entry point for a validate execution
|
||||||
|
|
||||||
|
:return: Errors or parsed text as structured data
|
||||||
|
:rtype: dict
|
||||||
|
|
||||||
|
:example:
|
||||||
|
|
||||||
|
The parse function of a parser should return a dict:
|
||||||
|
{"errors": [a list of errors]}
|
||||||
|
or
|
||||||
|
{"parsed": obj}
|
||||||
|
"""
|
||||||
|
self._check_reqs()
|
||||||
|
self._check_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._validate_jsonschema()
|
||||||
|
except Exception as exc:
|
||||||
|
return {"errors": to_text(exc, errors="surrogate_then_replace")}
|
||||||
|
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
def _validate_jsonschema(self):
|
||||||
|
error_messages = None
|
||||||
|
|
||||||
|
draft = self._get_sub_plugin_options("draft")
|
||||||
|
error_messages = []
|
||||||
|
|
||||||
|
for criteria in self._criteria:
|
||||||
|
if draft == "draft3":
|
||||||
|
validator = jsonschema.Draft3Validator(criteria)
|
||||||
|
elif draft == "draft4":
|
||||||
|
validator = jsonschema.Draft4Validator(criteria)
|
||||||
|
elif draft == "draft6":
|
||||||
|
validator = jsonschema.Draft6Validator(criteria)
|
||||||
|
else:
|
||||||
|
validator = jsonschema.Draft7Validator(criteria)
|
||||||
|
|
||||||
|
validation_errors = sorted(
|
||||||
|
validator.iter_errors(self._data), key=lambda e: e.path
|
||||||
|
)
|
||||||
|
|
||||||
|
if validation_errors:
|
||||||
|
if "errors" not in self._result:
|
||||||
|
self._result["errors"] = []
|
||||||
|
|
||||||
|
for validation_error in validation_errors:
|
||||||
|
if isinstance(
|
||||||
|
validation_error, jsonschema.ValidationError
|
||||||
|
):
|
||||||
|
error = {
|
||||||
|
"message": validation_error.message,
|
||||||
|
"data_path": to_path(
|
||||||
|
validation_error.absolute_path
|
||||||
|
),
|
||||||
|
"json_path": json_path(
|
||||||
|
validation_error.absolute_path
|
||||||
|
),
|
||||||
|
"schema_path": to_path(
|
||||||
|
validation_error.relative_schema_path
|
||||||
|
),
|
||||||
|
"relative_schema": validation_error.schema,
|
||||||
|
"expected": validation_error.validator_value,
|
||||||
|
"validator": validation_error.validator,
|
||||||
|
"found": validation_error.instance,
|
||||||
|
}
|
||||||
|
self._result["errors"].append(error)
|
||||||
|
error_message = (
|
||||||
|
"At '{schema_path}' {message}. ".format(
|
||||||
|
schema_path=error["schema_path"],
|
||||||
|
message=error["message"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
error_messages.append(error_message)
|
||||||
|
if error_messages:
|
||||||
|
if "msg" not in self._result:
|
||||||
|
self._result["msg"] = "\n".join(error_messages)
|
||||||
|
else:
|
||||||
|
self._result["msg"] += "\n".join(error_messages)
|
|
@ -0,0 +1,4 @@
|
||||||
|
ansible
|
||||||
|
# The follow are 3rd party libs for validate
|
||||||
|
jsonschema
|
||||||
|
# /valiate
|
|
@ -0,0 +1,9 @@
|
||||||
|
black==19.3b0 ; python_version > '3.5'
|
||||||
|
coverage==4.5.4
|
||||||
|
flake8
|
||||||
|
mock ; python_version < '3.5'
|
||||||
|
pytest-xdist
|
||||||
|
yamllint
|
||||||
|
# The follow are 3rd party libs for valiate
|
||||||
|
jsonschema
|
||||||
|
# /valiate
|
|
@ -1,6 +1,7 @@
|
||||||
|
---
|
||||||
- name: Check argspec validation
|
- name: Check argspec validation
|
||||||
ansible.utils.fact_diff:
|
ansible.utils.fact_diff:
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -19,8 +20,8 @@
|
||||||
plugin:
|
plugin:
|
||||||
vars:
|
vars:
|
||||||
skip_lines:
|
skip_lines:
|
||||||
a_dict: False
|
a_dict: false
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- set_fact:
|
- set_fact:
|
||||||
before:
|
before:
|
||||||
a:
|
a:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- name: Check for graceful fail of invalid regex
|
- name: Check for graceful fail of invalid regex
|
||||||
ansible.utils.fact_diff:
|
ansible.utils.fact_diff:
|
||||||
before: [0, 1, 2]
|
before: [0, 1, 2]
|
||||||
|
@ -6,7 +7,7 @@
|
||||||
vars:
|
vars:
|
||||||
skip_lines:
|
skip_lines:
|
||||||
- '+'
|
- '+'
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
---
|
||||||
- name: Recursively find all test files
|
- name: Recursively find all test files
|
||||||
find:
|
find:
|
||||||
file_type: file
|
file_type: file
|
||||||
paths: "{{ role_path }}/tasks/include"
|
paths: "{{ role_path }}/tasks/include"
|
||||||
recurse: yes
|
recurse: true
|
||||||
use_regex: yes
|
use_regex: true
|
||||||
patterns:
|
patterns:
|
||||||
- '^(?!_).+$'
|
- '^(?!_).+$'
|
||||||
register: found
|
register: found
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- set_fact:
|
- set_fact:
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
|
@ -9,7 +10,7 @@
|
||||||
- name: Check argspec validation with filter
|
- name: Check argspec validation with filter
|
||||||
set_fact:
|
set_fact:
|
||||||
_result: "{{ a|ansible.utils.get_path() }}"
|
_result: "{{ a|ansible.utils.get_path() }}"
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
- name: Check argspec validation with lookup
|
- name: Check argspec validation with lookup
|
||||||
set_fact:
|
set_fact:
|
||||||
_result: "{{ lookup('ansible.utils.get_path') }}"
|
_result: "{{ lookup('ansible.utils.get_path') }}"
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- ansible.builtin.set_fact:
|
- ansible.builtin.set_fact:
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
|
@ -6,8 +7,8 @@
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
e:
|
e:
|
||||||
- True
|
- true
|
||||||
- False
|
- false
|
||||||
|
|
||||||
- name: Retrieve a value deep inside a using a path
|
- name: Retrieve a value deep inside a using a path
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- ansible.builtin.set_fact:
|
- ansible.builtin.set_fact:
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
|
@ -6,8 +7,8 @@
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
e:
|
e:
|
||||||
- True
|
- true
|
||||||
- False
|
- false
|
||||||
|
|
||||||
- name: Retrieve a value deep inside a using a path
|
- name: Retrieve a value deep inside a using a path
|
||||||
ansible.builtin.set_fact:
|
ansible.builtin.set_fact:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- set_fact:
|
- set_fact:
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
---
|
||||||
- name: Recursively find all test files
|
- name: Recursively find all test files
|
||||||
find:
|
find:
|
||||||
file_type: file
|
file_type: file
|
||||||
paths: "{{ role_path }}/tasks/include"
|
paths: "{{ role_path }}/tasks/include"
|
||||||
recurse: yes
|
recurse: true
|
||||||
use_regex: yes
|
use_regex: true
|
||||||
patterns:
|
patterns:
|
||||||
- '^(?!_).+$'
|
- '^(?!_).+$'
|
||||||
register: found
|
register: found
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- set_fact:
|
- set_fact:
|
||||||
complex:
|
complex:
|
||||||
a:
|
a:
|
||||||
|
@ -6,14 +7,14 @@
|
||||||
d:
|
d:
|
||||||
- e0: 0
|
- e0: 0
|
||||||
e1: ansible
|
e1: ansible
|
||||||
e2: True
|
e2: true
|
||||||
- e0: 1
|
- e0: 1
|
||||||
e1: redhat
|
e1: redhat
|
||||||
|
|
||||||
- name: Check argspec validation with filter (not a list)
|
- name: Check argspec validation with filter (not a list)
|
||||||
set_fact:
|
set_fact:
|
||||||
_result: "{{ complex|ansible.utils.index_of() }}"
|
_result: "{{ complex|ansible.utils.index_of() }}"
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -24,7 +25,7 @@
|
||||||
- name: Check argspec validation with filter (missing params)
|
- name: Check argspec validation with filter (missing params)
|
||||||
set_fact:
|
set_fact:
|
||||||
_result: "{{ complex.a.b.c.d|ansible.utils.index_of() }}"
|
_result: "{{ complex.a.b.c.d|ansible.utils.index_of() }}"
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -35,7 +36,7 @@
|
||||||
- name: Check argspec validation with lookup (not a list)
|
- name: Check argspec validation with lookup (not a list)
|
||||||
set_fact:
|
set_fact:
|
||||||
_result: "{{ lookup('ansible.utils.index_of', complex) }}"
|
_result: "{{ lookup('ansible.utils.index_of', complex) }}"
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -46,7 +47,7 @@
|
||||||
- name: Check argspec validation with lookup (missing params)
|
- name: Check argspec validation with lookup (missing params)
|
||||||
set_fact:
|
set_fact:
|
||||||
_result: "{{ lookup('ansible.utils.index_of') }}"
|
_result: "{{ lookup('ansible.utils.index_of') }}"
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
---
|
||||||
#### Simple examples
|
#### Simple examples
|
||||||
|
|
||||||
- set_fact:
|
- set_fact:
|
||||||
data:
|
data:
|
||||||
- 1
|
- 1
|
||||||
|
@ -162,8 +162,8 @@
|
||||||
interface:
|
interface:
|
||||||
- config:
|
- config:
|
||||||
description: configured by Ansible - 1
|
description: configured by Ansible - 1
|
||||||
enabled: True
|
enabled: true
|
||||||
loopback-mode: False
|
loopback-mode: false
|
||||||
mtu: 1024
|
mtu: 1024
|
||||||
name: loopback0000
|
name: loopback0000
|
||||||
type: eth
|
type: eth
|
||||||
|
@ -172,18 +172,18 @@
|
||||||
subinterface:
|
subinterface:
|
||||||
- config:
|
- config:
|
||||||
description: subinterface configured by Ansible - 1
|
description: subinterface configured by Ansible - 1
|
||||||
enabled: True
|
enabled: true
|
||||||
index: 5
|
index: 5
|
||||||
index: 5
|
index: 5
|
||||||
- config:
|
- config:
|
||||||
description: subinterface configured by Ansible - 2
|
description: subinterface configured by Ansible - 2
|
||||||
enabled: False
|
enabled: false
|
||||||
index: 2
|
index: 2
|
||||||
index: 2
|
index: 2
|
||||||
- config:
|
- config:
|
||||||
description: configured by Ansible - 2
|
description: configured by Ansible - 2
|
||||||
enabled: False
|
enabled: false
|
||||||
loopback-mode: False
|
loopback-mode: false
|
||||||
mtu: 2048
|
mtu: 2048
|
||||||
name: loopback1111
|
name: loopback1111
|
||||||
type: virt
|
type: virt
|
||||||
|
@ -192,12 +192,12 @@
|
||||||
subinterface:
|
subinterface:
|
||||||
- config:
|
- config:
|
||||||
description: subinterface configured by Ansible - 3
|
description: subinterface configured by Ansible - 3
|
||||||
enabled: True
|
enabled: true
|
||||||
index: 10
|
index: 10
|
||||||
index: 10
|
index: 10
|
||||||
- config:
|
- config:
|
||||||
description: subinterface configured by Ansible - 4
|
description: subinterface configured by Ansible - 4
|
||||||
enabled: False
|
enabled: false
|
||||||
index: 3
|
index: 3
|
||||||
index: 3
|
index: 3
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
---
|
||||||
#### Simple examples
|
#### Simple examples
|
||||||
|
|
||||||
- set_fact:
|
- set_fact:
|
||||||
data:
|
data:
|
||||||
- 1
|
- 1
|
||||||
|
@ -178,8 +178,8 @@
|
||||||
interface:
|
interface:
|
||||||
- config:
|
- config:
|
||||||
description: configured by Ansible - 1
|
description: configured by Ansible - 1
|
||||||
enabled: True
|
enabled: true
|
||||||
loopback-mode: False
|
loopback-mode: false
|
||||||
mtu: 1024
|
mtu: 1024
|
||||||
name: loopback0000
|
name: loopback0000
|
||||||
type: eth
|
type: eth
|
||||||
|
@ -188,18 +188,18 @@
|
||||||
subinterface:
|
subinterface:
|
||||||
- config:
|
- config:
|
||||||
description: subinterface configured by Ansible - 1
|
description: subinterface configured by Ansible - 1
|
||||||
enabled: True
|
enabled: true
|
||||||
index: 5
|
index: 5
|
||||||
index: 5
|
index: 5
|
||||||
- config:
|
- config:
|
||||||
description: subinterface configured by Ansible - 2
|
description: subinterface configured by Ansible - 2
|
||||||
enabled: False
|
enabled: false
|
||||||
index: 2
|
index: 2
|
||||||
index: 2
|
index: 2
|
||||||
- config:
|
- config:
|
||||||
description: configured by Ansible - 2
|
description: configured by Ansible - 2
|
||||||
enabled: False
|
enabled: false
|
||||||
loopback-mode: False
|
loopback-mode: false
|
||||||
mtu: 2048
|
mtu: 2048
|
||||||
name: loopback1111
|
name: loopback1111
|
||||||
type: virt
|
type: virt
|
||||||
|
@ -208,12 +208,12 @@
|
||||||
subinterface:
|
subinterface:
|
||||||
- config:
|
- config:
|
||||||
description: subinterface configured by Ansible - 3
|
description: subinterface configured by Ansible - 3
|
||||||
enabled: True
|
enabled: true
|
||||||
index: 10
|
index: 10
|
||||||
index: 10
|
index: 10
|
||||||
- config:
|
- config:
|
||||||
description: subinterface configured by Ansible - 4
|
description: subinterface configured by Ansible - 4
|
||||||
enabled: False
|
enabled: false
|
||||||
index: 3
|
index: 3
|
||||||
index: 3
|
index: 3
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
---
|
||||||
- set_fact:
|
- set_fact:
|
||||||
complex:
|
complex:
|
||||||
a:
|
a:
|
||||||
- True
|
- true
|
||||||
- True
|
- true
|
||||||
- False
|
- false
|
||||||
- 5
|
- 5
|
||||||
|
|
||||||
b:
|
b:
|
||||||
|
@ -65,7 +66,7 @@
|
||||||
d:
|
d:
|
||||||
- e0: 0
|
- e0: 0
|
||||||
e1: ansible
|
e1: ansible
|
||||||
e2: True
|
e2: true
|
||||||
- e0: 1
|
- e0: 1
|
||||||
e1: redhat
|
e1: redhat
|
||||||
|
|
||||||
|
@ -99,7 +100,7 @@
|
||||||
result: "0"
|
result: "0"
|
||||||
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', True, 'e2', fail_on_missing=True) }}"
|
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', True, 'e2', fail_on_missing=True) }}"
|
||||||
result: "0"
|
result: "0"
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: Ensure the previous test failed
|
- name: Ensure the previous test failed
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
---
|
||||||
- name: Recursively find all test files
|
- name: Recursively find all test files
|
||||||
find:
|
find:
|
||||||
file_type: file
|
file_type: file
|
||||||
paths: "{{ role_path }}/tasks/include"
|
paths: "{{ role_path }}/tasks/include"
|
||||||
recurse: yes
|
recurse: true
|
||||||
use_regex: yes
|
use_regex: true
|
||||||
patterns:
|
patterns:
|
||||||
- '^(?!_).+$'
|
- '^(?!_).+$'
|
||||||
register: found
|
register: found
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- set_fact:
|
- set_fact:
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
|
@ -8,7 +9,7 @@
|
||||||
- name: Check argspec validation with lookup
|
- name: Check argspec validation with lookup
|
||||||
set_fact:
|
set_fact:
|
||||||
_result: "{{ a|ansible.utils.to_paths(wantlist=5) }}"
|
_result: "{{ a|ansible.utils.to_paths(wantlist=5) }}"
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- debug:
|
- debug:
|
||||||
|
@ -22,7 +23,7 @@
|
||||||
- name: Check argspec validation with lookup
|
- name: Check argspec validation with lookup
|
||||||
set_fact:
|
set_fact:
|
||||||
_result: "{{ lookup('ansible.utils.to_paths') }}"
|
_result: "{{ lookup('ansible.utils.to_paths') }}"
|
||||||
ignore_errors: True
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- debug:
|
- debug:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
---
|
||||||
#### Simple examples
|
#### Simple examples
|
||||||
|
|
||||||
- ansible.builtin.set_fact:
|
- ansible.builtin.set_fact:
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
|
@ -8,8 +8,8 @@
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
e:
|
e:
|
||||||
- True
|
- true
|
||||||
- False
|
- false
|
||||||
|
|
||||||
- ansible.builtin.set_fact:
|
- ansible.builtin.set_fact:
|
||||||
paths: "{{ a|ansible.utils.to_paths }}"
|
paths: "{{ a|ansible.utils.to_paths }}"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
---
|
||||||
#### Simple examples
|
#### Simple examples
|
||||||
|
|
||||||
- ansible.builtin.set_fact:
|
- ansible.builtin.set_fact:
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
|
@ -8,8 +8,8 @@
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
e:
|
e:
|
||||||
- True
|
- true
|
||||||
- False
|
- false
|
||||||
|
|
||||||
- ansible.builtin.set_fact:
|
- ansible.builtin.set_fact:
|
||||||
paths: "{{ lookup('ansible.utils.to_paths', a) }}"
|
paths: "{{ lookup('ansible.utils.to_paths', a) }}"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- set_fact:
|
- set_fact:
|
||||||
a:
|
a:
|
||||||
b:
|
b:
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
---
|
||||||
- name: Recursively find all test files
|
- name: Recursively find all test files
|
||||||
find:
|
find:
|
||||||
file_type: file
|
file_type: file
|
||||||
paths: "{{ role_path }}/tasks/include"
|
paths: "{{ role_path }}/tasks/include"
|
||||||
recurse: yes
|
recurse: true
|
||||||
use_regex: yes
|
use_regex: true
|
||||||
patterns:
|
patterns:
|
||||||
- '^(?!_).+$'
|
- '^(?!_).+$'
|
||||||
register: found
|
register: found
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
---
|
||||||
- name: Set a fact
|
- name: Set a fact
|
||||||
set_fact:
|
set_fact:
|
||||||
a:
|
a:
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
y:
|
y:
|
||||||
z:
|
z:
|
||||||
- 100
|
- 100
|
||||||
- True
|
- true
|
||||||
register: updated
|
register: updated
|
||||||
|
|
||||||
- assert:
|
- assert:
|
||||||
|
@ -46,7 +47,7 @@
|
||||||
y:
|
y:
|
||||||
z:
|
z:
|
||||||
- 100
|
- 100
|
||||||
- True
|
- true
|
||||||
|
|
||||||
- name: Update the fact
|
- name: Update the fact
|
||||||
ansible.utils.update_fact:
|
ansible.utils.update_fact:
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"in_crc_errors": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"enum": [true]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"rate": {
|
||||||
|
"properties": {
|
||||||
|
"in_rate": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"oper_status": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"GigabitEthernet0/0/0/0": {
|
||||||
|
"auto_negotiate": false,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 0,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "configured using Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": true,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "down",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
},
|
||||||
|
"GigabitEthernet0/0/0/1": {
|
||||||
|
"auto_negotiate": false,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 10,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "# interface is configures with Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": false,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "up",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
---
|
||||||
|
- name: validate data in json format using jsonschema (invalid data)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ show_interfaces|ansible.utils.validate([oper_status, enable_check, crc_error_check], engine='ansible.utils.jsonschema') }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "data_criteria_checks[0].data_path == 'GigabitEthernet0/0/0/0.oper_status'"
|
||||||
|
- "data_criteria_checks[1].data_path == 'GigabitEthernet0/0/0/1.enabled'"
|
||||||
|
- "data_criteria_checks[2].data_path == 'GigabitEthernet0/0/0/1.counters.in_crc_errors'"
|
||||||
|
|
||||||
|
- name: validate data in json format using jsonschema (valid data)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ show_interfaces|ansible.utils.validate(in_rate_check) }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "data_criteria_checks == []"
|
||||||
|
|
||||||
|
- name: test invalid plugin configuration option, passed within filter plugin
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ show_interfaces|ansible.utils.validate(in_rate_check, draft='draft0') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'value of draft must be one of: draft3, draft4, draft6, draft7, got: draft0' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid engine value
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ show_interfaces|ansible.utils.validate(in_rate_check, engine='ansible.utils.sample') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'errors' not in result"
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'For engine \\'ansible.utils.sample\\' error loading the corresponding validate plugin' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid data value
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ 'invalid data'|ansible.utils.validate(in_rate_check, engine='ansible.utils.jsonschema') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'\\'data\\' option value is invalid, value should of type dict or str format of dict' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid criteria value
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ show_interfaces|ansible.utils.validate('invalid criteria', engine='ansible.utils.jsonschema') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'\\'criteria\\' option value is invalid, value should of type dict or str format of dict' in result.msg"
|
||||||
|
|
||||||
|
- name: read data and criteria from file
|
||||||
|
set_fact:
|
||||||
|
data: "{{ lookup('file', 'data/show_interface.json') }}"
|
||||||
|
oper_status_up_criteria: "{{ lookup('file', 'criteria/oper_status_up.json') }}"
|
||||||
|
enabled_check_criteria: "{{ lookup('file', 'criteria/enabled_check.json') }}"
|
||||||
|
crc_error_check_criteria: "{{ lookup('file', 'criteria/crc_error_check.json') }}"
|
||||||
|
in_rate_check_criteria: "{{ lookup('file', 'criteria/in_rate_check.json') }}"
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (invalid data read from file)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ data|ansible.utils.validate([oper_status_up_criteria, enabled_check_criteria, crc_error_check_criteria]) }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "data_criteria_checks[0].data_path == 'GigabitEthernet0/0/0/0.oper_status'"
|
||||||
|
- "data_criteria_checks[1].data_path == 'GigabitEthernet0/0/0/1.enabled'"
|
||||||
|
- "data_criteria_checks[2].data_path == 'GigabitEthernet0/0/0/1.counters.in_crc_errors'"
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (valid data read from file)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ data|ansible.utils.validate(in_rate_check_criteria) }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "data_criteria_checks == []"
|
|
@ -0,0 +1,106 @@
|
||||||
|
---
|
||||||
|
- name: validate data in json format using jsonschema (invalid data)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', show_interfaces, [oper_status, enable_check, crc_error_check], engine='ansible.utils.jsonschema') }}"
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft7
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "data_criteria_checks[0].data_path == 'GigabitEthernet0/0/0/0.oper_status'"
|
||||||
|
- "data_criteria_checks[1].data_path == 'GigabitEthernet0/0/0/1.enabled'"
|
||||||
|
- "data_criteria_checks[2].data_path == 'GigabitEthernet0/0/0/1.counters.in_crc_errors'"
|
||||||
|
|
||||||
|
- name: validate data in json format using jsonschema (invalid data)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', show_interfaces, in_rate_check) }}"
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft7
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "data_criteria_checks == []"
|
||||||
|
|
||||||
|
- name: test invalid plugin configuration option, passed within lookup plugin
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', show_interfaces, in_rate_check, draft='draft0') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft7
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'value of draft must be one of: draft3, draft4, draft6, draft7, got: draft0' in result.msg"
|
||||||
|
|
||||||
|
- name: test invalid plugin configuration option, passed as task varaible
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', show_interfaces, in_rate_check) }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft0
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'value of draft must be one of: draft3, draft4, draft6, draft7, got: draft0' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid engine value
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', show_interfaces, in_rate_check, engine='ansible.utils.sample') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'errors' not in result"
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'For engine \\'ansible.utils.sample\\' error loading the corresponding validate plugin' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid data value
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', 'invalid data', in_rate_check, engine='ansible.utils.jsonschema') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'\\'data\\' option value is invalid, value should of type dict or str format of dict' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid criteria value
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', show_interfaces, 'invalid criteria', engine='ansible.utils.jsonschema') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'\\'criteria\\' option value is invalid, value should of type dict or str format of dict' in result.msg"
|
||||||
|
|
||||||
|
- name: read data and criteria from file
|
||||||
|
set_fact:
|
||||||
|
data: "{{ lookup('file', 'data/show_interface.json') }}"
|
||||||
|
oper_status_up_criteria: "{{ lookup('file', 'criteria/oper_status_up.json') }}"
|
||||||
|
enabled_check_criteria: "{{ lookup('file', 'criteria/enabled_check.json') }}"
|
||||||
|
crc_error_check_criteria: "{{ lookup('file', 'criteria/crc_error_check.json') }}"
|
||||||
|
in_rate_check_criteria: "{{ lookup('file', 'criteria/in_rate_check.json') }}"
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (invalid data read from file)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', data, [oper_status_up_criteria, enabled_check_criteria, crc_error_check_criteria], engine='ansible.utils.jsonschema') }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "data_criteria_checks[0].data_path == 'GigabitEthernet0/0/0/0.oper_status'"
|
||||||
|
- "data_criteria_checks[1].data_path == 'GigabitEthernet0/0/0/1.enabled'"
|
||||||
|
- "data_criteria_checks[2].data_path == 'GigabitEthernet0/0/0/1.counters.in_crc_errors'"
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (valid data read from file)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ lookup('ansible.utils.validate', data, in_rate_check_criteria, engine='ansible.utils.jsonschema') }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "data_criteria_checks == []"
|
|
@ -0,0 +1,134 @@
|
||||||
|
---
|
||||||
|
- name: validate data using jsonschema engine (invalid data)
|
||||||
|
ansible.utils.validate:
|
||||||
|
data: "{{ show_interfaces }}"
|
||||||
|
criteria:
|
||||||
|
- "{{ oper_status }}"
|
||||||
|
- "{{ enable_check }}"
|
||||||
|
- "{{ crc_error_check }}"
|
||||||
|
engine: ansible.utils.jsonschema
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft7
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'errors' in result"
|
||||||
|
- "result['errors'][0].data_path == 'GigabitEthernet0/0/0/0.oper_status'"
|
||||||
|
- "result['errors'][1].data_path == 'GigabitEthernet0/0/0/1.enabled'"
|
||||||
|
- "result['errors'][2].data_path == 'GigabitEthernet0/0/0/1.counters.in_crc_errors'"
|
||||||
|
- "'Validation errors were found' in result.msg"
|
||||||
|
- "'patternProperties.^.*.properties.oper_status.pattern' in result.msg"
|
||||||
|
- "'patternProperties.^.*.properties.enabled.enum' in result.msg"
|
||||||
|
- "'patternProperties.^.*.properties.counters.properties.in_crc_errors.maximum' in result.msg"
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (valid data)
|
||||||
|
ansible.utils.validate:
|
||||||
|
data: "{{ show_interfaces }}"
|
||||||
|
criteria: "{{ in_rate_check }}"
|
||||||
|
engine: ansible.utils.jsonschema
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft7
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'errors' not in result"
|
||||||
|
- "'all checks passed' in result.msg"
|
||||||
|
|
||||||
|
- name: test invalid plugin configuration option
|
||||||
|
ansible.utils.validate:
|
||||||
|
data: "{{ show_interfaces }}"
|
||||||
|
criteria: "{{ in_rate_check }}"
|
||||||
|
engine: ansible.utils.jsonschema
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft0
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'errors' not in result"
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'value of draft must be one of: draft3, draft4, draft6, draft7, got: draft0' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid engine value
|
||||||
|
ansible.utils.validate:
|
||||||
|
data: "{{ show_interfaces }}"
|
||||||
|
criteria: "{{ in_rate_check }}"
|
||||||
|
engine: ansible.utils.sample
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'errors' not in result"
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'For engine \\'ansible.utils.sample\\' error loading the corresponding validate plugin' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid data value
|
||||||
|
ansible.utils.validate:
|
||||||
|
data: "sample"
|
||||||
|
criteria: "{{ in_rate_check }}"
|
||||||
|
engine: ansible.utils.jsonschema
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'\\'data\\' option value is invalid, value should of type dict or str format of dict' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid criteria value
|
||||||
|
ansible.utils.validate:
|
||||||
|
data: "{{ show_interfaces }}"
|
||||||
|
criteria: "sample}"
|
||||||
|
engine: ansible.utils.jsonschema
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'\\'criteria\\' option value is invalid, value should of type dict or str format of dict' in result.msg"
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (invalid data read from file)
|
||||||
|
ansible.utils.validate:
|
||||||
|
data: "{{ lookup('file', 'data/show_interface.json') }}"
|
||||||
|
criteria:
|
||||||
|
- "{{ lookup('file', 'criteria/oper_status_up.json') }}"
|
||||||
|
- "{{ lookup('file', 'criteria/enabled_check.json') }}"
|
||||||
|
- "{{ lookup('file', 'criteria/crc_error_check.json') }}"
|
||||||
|
engine: ansible.utils.jsonschema
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft7
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'errors' in result"
|
||||||
|
- "result['errors'][0].data_path == 'GigabitEthernet0/0/0/0.oper_status'"
|
||||||
|
- "result['errors'][1].data_path == 'GigabitEthernet0/0/0/1.enabled'"
|
||||||
|
- "result['errors'][2].data_path == 'GigabitEthernet0/0/0/1.counters.in_crc_errors'"
|
||||||
|
- "'Validation errors were found' in result.msg"
|
||||||
|
- "'patternProperties.^.*.properties.oper_status.pattern' in result.msg"
|
||||||
|
- "'patternProperties.^.*.properties.enabled.enum' in result.msg"
|
||||||
|
- "'patternProperties.^.*.properties.counters.properties.in_crc_errors.maximum' in result.msg"
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (valid data read from file)
|
||||||
|
ansible.utils.validate:
|
||||||
|
data: "{{ lookup('file', 'data/show_interface.json') }}"
|
||||||
|
criteria: "{{ lookup('file', 'criteria/in_rate_check.json') }}"
|
||||||
|
engine: ansible.utils.jsonschema
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
vars:
|
||||||
|
ansible_validate_jsonschema_draft: draft7
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'errors' not in result"
|
||||||
|
- "'all checks passed' in result.msg"
|
|
@ -0,0 +1,89 @@
|
||||||
|
---
|
||||||
|
- name: validate data in json format using jsonschema (invalid data)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
is_data_valid: "{{ show_interfaces is ansible.utils.validate(engine='ansible.utils.jsonschema', criteria=[oper_status, enable_check, crc_error_check], draft='draft7') }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "is_data_valid == false"
|
||||||
|
|
||||||
|
- name: validate data in json format using jsonschema (valid data)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
is_data_valid: "{{ show_interfaces is ansible.utils.validate(criteria=in_rate_check) }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "is_data_valid == true"
|
||||||
|
|
||||||
|
- name: test invalid plugin configuration option, passed within filter plugin
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
is_data_valid: "{{ show_interfaces is ansible.utils.validate(criteria=in_rate_check, draft='draft0') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'value of draft must be one of: draft3, draft4, draft6, draft7, got: draft0' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid engine value
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ show_interfaces is ansible.utils.validate(criteria=in_rate_check, engine='ansible.utils.sample') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "'errors' not in result"
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'For engine \\'ansible.utils.sample\\' error loading the corresponding validate plugin' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid data value
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ 'invalid data' is ansible.utils.validate(criteria=in_rate_check) }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'\\'data\\' option value is invalid, value should of type dict or str format of dict' in result.msg"
|
||||||
|
|
||||||
|
- name: invalid criteria value
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ show_interfaces is ansible.utils.validate(criteria='invalid data') }}"
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "result['failed'] == true"
|
||||||
|
- "'\\'criteria\\' option value is invalid, value should of type dict or str format of dict' in result.msg"
|
||||||
|
|
||||||
|
- name: read data and criteria from file
|
||||||
|
set_fact:
|
||||||
|
data: "{{ lookup('file', 'data/show_interface.json') }}"
|
||||||
|
oper_status_up_criteria: "{{ lookup('file', 'criteria/oper_status_up.json') }}"
|
||||||
|
enabled_check_criteria: "{{ lookup('file', 'criteria/enabled_check.json') }}"
|
||||||
|
crc_error_check_criteria: "{{ lookup('file', 'criteria/crc_error_check.json') }}"
|
||||||
|
in_rate_check_criteria: "{{ lookup('file', 'criteria/in_rate_check.json') }}"
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (invalid data read from file)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
is_data_valid: "{{ show_interfaces is ansible.utils.validate(engine='ansible.utils.jsonschema', criteria=[oper_status_up_criteria, enabled_check_criteria, crc_error_check_criteria], draft='draft7') }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "is_data_valid == false"
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (invalid data read from file)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
is_data_valid: "{{ show_interfaces is ansible.utils.validate(criteria=in_rate_check_criteria) }}"
|
||||||
|
|
||||||
|
|
||||||
|
- name: validate data using jsonschema engine (valid data read from file)
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
data_criteria_checks: "{{ data|ansible.utils.validate(in_rate_check_criteria) }}"
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "is_data_valid == true"
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
- name: Recursively find all test files
|
||||||
|
find:
|
||||||
|
file_type: file
|
||||||
|
paths: "{{ role_path }}/tasks/include"
|
||||||
|
recurse: true
|
||||||
|
use_regex: true
|
||||||
|
patterns:
|
||||||
|
- '^(?!_).+$'
|
||||||
|
register: found
|
||||||
|
|
||||||
|
- include: "{{ item.path }}"
|
||||||
|
loop: "{{ found.files }}"
|
|
@ -0,0 +1,78 @@
|
||||||
|
---
|
||||||
|
show_interfaces:
|
||||||
|
GigabitEthernet0/0/0/0:
|
||||||
|
auto_negotiate: false
|
||||||
|
counters:
|
||||||
|
in_crc_errors: 0
|
||||||
|
in_errors: 0
|
||||||
|
rate:
|
||||||
|
in_rate: 0
|
||||||
|
out_rate: 0
|
||||||
|
description: configured using Ansible
|
||||||
|
duplex_mode: full
|
||||||
|
enabled: true
|
||||||
|
line_protocol: up
|
||||||
|
mtu: 1514
|
||||||
|
oper_status: down
|
||||||
|
type: GigabitEthernet
|
||||||
|
GigabitEthernet0/0/0/1:
|
||||||
|
auto_negotiate: false
|
||||||
|
counters:
|
||||||
|
in_crc_errors: 10
|
||||||
|
in_errors: 0
|
||||||
|
rate:
|
||||||
|
in_rate: 0
|
||||||
|
out_rate: 0
|
||||||
|
description: '# interface is configures with Ansible'
|
||||||
|
duplex_mode: full
|
||||||
|
enabled: false
|
||||||
|
line_protocol: up
|
||||||
|
mtu: 1514
|
||||||
|
oper_status: up
|
||||||
|
type: GigabitEthernet
|
||||||
|
|
||||||
|
oper_status:
|
||||||
|
type: object
|
||||||
|
patternProperties:
|
||||||
|
^.*:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
oper_status:
|
||||||
|
type: string
|
||||||
|
pattern: up
|
||||||
|
|
||||||
|
enable_check:
|
||||||
|
type: object
|
||||||
|
patternProperties:
|
||||||
|
^.*:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
enabled:
|
||||||
|
enum:
|
||||||
|
- true
|
||||||
|
|
||||||
|
crc_error_check:
|
||||||
|
type: object
|
||||||
|
patternProperties:
|
||||||
|
^.*:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
counters:
|
||||||
|
properties:
|
||||||
|
in_crc_errors:
|
||||||
|
type: number
|
||||||
|
maximum: 0
|
||||||
|
|
||||||
|
in_rate_check:
|
||||||
|
type: object
|
||||||
|
patternProperties:
|
||||||
|
^.*:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
counters:
|
||||||
|
properties:
|
||||||
|
rate:
|
||||||
|
properties:
|
||||||
|
in_rate:
|
||||||
|
type: number
|
||||||
|
maximum: 0
|
|
@ -8,9 +8,6 @@ from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import json
|
|
||||||
import heapq
|
|
||||||
import os
|
|
||||||
import unittest
|
import unittest
|
||||||
from ansible_collections.ansible.utils.plugins.module_utils.common.get_path import (
|
from ansible_collections.ansible.utils.plugins.module_utils.common.get_path import (
|
||||||
get_path,
|
get_path,
|
||||||
|
|
|
@ -7,7 +7,6 @@ from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
import copy
|
|
||||||
import re
|
import re
|
||||||
import unittest
|
import unittest
|
||||||
from mock import MagicMock
|
from mock import MagicMock
|
||||||
|
@ -43,10 +42,7 @@ class TestUpdate_Fact(unittest.TestCase):
|
||||||
self._plugin._task.args = {"before": True}
|
self._plugin._task.args = {"before": True}
|
||||||
result = self._plugin.run(task_vars=self._task_vars)
|
result = self._plugin.run(task_vars=self._task_vars)
|
||||||
self.assertTrue(result["failed"])
|
self.assertTrue(result["failed"])
|
||||||
self.assertIn(
|
self.assertIn("missing required arguments: after", result["msg"])
|
||||||
"missing required arguments: after",
|
|
||||||
result["msg"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_same(self):
|
def test_same(self):
|
||||||
"""Ensure two equal string don't create a diff"""
|
"""Ensure two equal string don't create a diff"""
|
||||||
|
@ -151,9 +147,15 @@ class TestUpdate_Fact(unittest.TestCase):
|
||||||
self._plugin._task.args = {"before": before, "after": after}
|
self._plugin._task.args = {"before": before, "after": after}
|
||||||
result = self._plugin.run(task_vars=self._task_vars)
|
result = self._plugin.run(task_vars=self._task_vars)
|
||||||
self.assertTrue(result["changed"])
|
self.assertTrue(result["changed"])
|
||||||
mlines = [l for l in result["diff_lines"] if re.match(r"^-\s+3$", l)]
|
mlines = [
|
||||||
|
line for line in result["diff_lines"] if re.match(r"^-\s+3$", line)
|
||||||
|
]
|
||||||
self.assertEqual(1, len(mlines))
|
self.assertEqual(1, len(mlines))
|
||||||
mlines = [l for l in result["diff_lines"] if re.match(r"^\+\s+4$", l)]
|
mlines = [
|
||||||
|
line
|
||||||
|
for line in result["diff_lines"]
|
||||||
|
if re.match(r"^\+\s+4$", line)
|
||||||
|
]
|
||||||
self.assertEqual(1, len(mlines))
|
self.assertEqual(1, len(mlines))
|
||||||
|
|
||||||
def test_invalid_diff_engine_not_collection(self):
|
def test_invalid_diff_engine_not_collection(self):
|
||||||
|
@ -179,10 +181,7 @@ class TestUpdate_Fact(unittest.TestCase):
|
||||||
}
|
}
|
||||||
result = self._plugin.run(task_vars=self._task_vars)
|
result = self._plugin.run(task_vars=self._task_vars)
|
||||||
self.assertTrue(result["failed"])
|
self.assertTrue(result["failed"])
|
||||||
self.assertIn(
|
self.assertIn("Error loading plugin 'a.b.c'", result["msg"])
|
||||||
"Error loading plugin 'a.b.c'",
|
|
||||||
result["msg"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_invalid_regex(self):
|
def test_invalid_regex(self):
|
||||||
"""Check with invalid regex"""
|
"""Check with invalid regex"""
|
||||||
|
@ -195,10 +194,7 @@ class TestUpdate_Fact(unittest.TestCase):
|
||||||
}
|
}
|
||||||
result = self._plugin.run(task_vars=self._task_vars)
|
result = self._plugin.run(task_vars=self._task_vars)
|
||||||
self.assertTrue(result["failed"])
|
self.assertTrue(result["failed"])
|
||||||
self.assertIn(
|
self.assertIn("The regex '+', is not valid", result["msg"])
|
||||||
"The regex '+', is not valid",
|
|
||||||
result["msg"],
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_fail_plugin(self):
|
def test_fail_plugin(self):
|
||||||
"""Simulate a diff plugin failure"""
|
"""Simulate a diff plugin failure"""
|
||||||
|
|
|
@ -97,8 +97,7 @@ class TestUpdate_Fact(unittest.TestCase):
|
||||||
with self.assertRaises(Exception) as error:
|
with self.assertRaises(Exception) as error:
|
||||||
self._plugin.run(task_vars=None)
|
self._plugin.run(task_vars=None)
|
||||||
self.assertIn(
|
self.assertIn(
|
||||||
"missing required arguments: updates",
|
"missing required arguments: updates", str(error.exception)
|
||||||
str(error.exception),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_argspec_none(self):
|
def test_argspec_none(self):
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import unittest
|
||||||
|
from jinja2 import Template, TemplateSyntaxError
|
||||||
|
from mock import MagicMock
|
||||||
|
from ansible.playbook.task import Task
|
||||||
|
from ansible.template import Templar
|
||||||
|
from ansible.errors import AnsibleActionFail
|
||||||
|
|
||||||
|
from ansible_collections.ansible.utils.plugins.action.validate import (
|
||||||
|
ActionModule,
|
||||||
|
)
|
||||||
|
|
||||||
|
DATA = {
|
||||||
|
"GigabitEthernet0/0/0/0": {
|
||||||
|
"auto_negotiate": False,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 0,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "configured using Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": True,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "down",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
},
|
||||||
|
"GigabitEthernet0/0/0/1": {
|
||||||
|
"auto_negotiate": False,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 10,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "# interface is configures with Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": False,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "up",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_CRC_ERROR_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"in_crc_errors": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_ENABLED_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"enum": [True]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_OPER_STATUS_UP_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"oper_status": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_IN_RATE_CHECK = {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"rate": {
|
||||||
|
"properties": {
|
||||||
|
"in_rate": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestValidate(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
task = MagicMock(Task)
|
||||||
|
play_context = MagicMock()
|
||||||
|
play_context.check_mode = False
|
||||||
|
connection = MagicMock()
|
||||||
|
fake_loader = {}
|
||||||
|
templar = Templar(loader=fake_loader)
|
||||||
|
self._plugin = ActionModule(
|
||||||
|
task=task,
|
||||||
|
connection=connection,
|
||||||
|
play_context=play_context,
|
||||||
|
loader=fake_loader,
|
||||||
|
templar=templar,
|
||||||
|
shared_loader_obj=None,
|
||||||
|
)
|
||||||
|
self._plugin._task.action = "validate"
|
||||||
|
|
||||||
|
def test_invalid_argspec(self):
|
||||||
|
"""Check passing invalid argspec"""
|
||||||
|
|
||||||
|
# missing required arguments
|
||||||
|
self._plugin._task.args = {"engine": "ansible.utils.jsonschema"}
|
||||||
|
result = self._plugin.run(task_vars=None)
|
||||||
|
self.assertIn("missing required arguments: criteria", result["errors"])
|
||||||
|
|
||||||
|
# invalid engine option value
|
||||||
|
self._plugin._task.args = {
|
||||||
|
"engine": "ansible.utils.sample",
|
||||||
|
"data": DATA,
|
||||||
|
"criteria": CRITERIA_OPER_STATUS_UP_CHECK
|
||||||
|
}
|
||||||
|
result = self._plugin.run(task_vars=None)
|
||||||
|
self.assertIn("For engine 'ansible.utils.sample' error loading the corresponding validate plugin", result["msg"])
|
||||||
|
|
||||||
|
# invalid data option value
|
||||||
|
self._plugin._task.args = {
|
||||||
|
"engine": "ansible.utils.jsonschema",
|
||||||
|
"data": "invalid data",
|
||||||
|
"criteria": CRITERIA_OPER_STATUS_UP_CHECK
|
||||||
|
}
|
||||||
|
|
||||||
|
with self.assertRaises(AnsibleActionFail) as error:
|
||||||
|
self._plugin.run(task_vars=None)
|
||||||
|
self.assertIn(
|
||||||
|
"'data' option value is invalid, value should of type dict or str format of dict", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
# invalid criteria option value
|
||||||
|
self._plugin._task.args = {
|
||||||
|
"engine": "ansible.utils.jsonschema",
|
||||||
|
"data": DATA,
|
||||||
|
"criteria": "invalid criteria"
|
||||||
|
}
|
||||||
|
|
||||||
|
with self.assertRaises(AnsibleActionFail) as error:
|
||||||
|
self._plugin.run(task_vars=None)
|
||||||
|
self.assertIn(
|
||||||
|
"'criteria' option value is invalid, value should of type dict or str format of dict", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invalid_validate_plugin_config_options(self):
|
||||||
|
"""Check passing invalid validate plugin options"""
|
||||||
|
|
||||||
|
self._plugin._task.args = {
|
||||||
|
"engine": "ansible.utils.jsonschema",
|
||||||
|
"data": DATA,
|
||||||
|
"criteria": CRITERIA_IN_RATE_CHECK
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self._plugin.run(task_vars={'ansible_validate_jsonschema_draft': 'draft0'})
|
||||||
|
self.assertIn("value of draft must be one of: draft3, draft4, draft6, draft7, got: draft0", result["msg"])
|
||||||
|
|
||||||
|
def test_invalid_data(self):
|
||||||
|
"""Check passing invalid data as per criteria"""
|
||||||
|
|
||||||
|
self._plugin._task.args = {
|
||||||
|
"engine": "ansible.utils.jsonschema",
|
||||||
|
"data": DATA,
|
||||||
|
"criteria": [CRITERIA_CRC_ERROR_CHECK, CRITERIA_ENABLED_CHECK, CRITERIA_OPER_STATUS_UP_CHECK]
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self._plugin.run(task_vars=None)
|
||||||
|
self.assertIn("patternProperties.^.*.properties.counters.properties.in_crc_errors.maximum", result["msg"])
|
||||||
|
self.assertIn("patternProperties.^.*.properties.enabled.enum", result["msg"])
|
||||||
|
self.assertIn("'patternProperties.^.*.properties.oper_status.pattern", result["msg"])
|
||||||
|
|
||||||
|
def test_valid_data(self):
|
||||||
|
"""Check passing valid data as per criteria"""
|
||||||
|
|
||||||
|
self._plugin._task.args = {
|
||||||
|
"engine": "ansible.utils.jsonschema",
|
||||||
|
"data": DATA,
|
||||||
|
"criteria": CRITERIA_IN_RATE_CHECK
|
||||||
|
}
|
||||||
|
|
||||||
|
result = self._plugin.run(task_vars=None)
|
||||||
|
self.assertIn("all checks passed", result["msg"])
|
|
@ -0,0 +1,190 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
from ansible_collections.ansible.utils.plugins.filter.validate import validate
|
||||||
|
|
||||||
|
DATA = {
|
||||||
|
"GigabitEthernet0/0/0/0": {
|
||||||
|
"auto_negotiate": False,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 0,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "configured using Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": True,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "down",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
},
|
||||||
|
"GigabitEthernet0/0/0/1": {
|
||||||
|
"auto_negotiate": False,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 10,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "# interface is configures with Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": False,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "up",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_CRC_ERROR_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"in_crc_errors": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_ENABLED_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"enum": [True]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_OPER_STATUS_UP_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"oper_status": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_IN_RATE_CHECK = {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"rate": {
|
||||||
|
"properties": {
|
||||||
|
"in_rate": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestValidate(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_invalid_argspec(self):
|
||||||
|
"""Check passing invalid argspec"""
|
||||||
|
|
||||||
|
# missing required arguments
|
||||||
|
args = [DATA]
|
||||||
|
kwargs = {}
|
||||||
|
with self.assertRaises(AnsibleFilterError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"Missing either 'data' or 'criteria' value in filter input, refer 'ansible.utils.validate' filter", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
# missing required arguments
|
||||||
|
with self.assertRaises(AnsibleFilterError) as error:
|
||||||
|
validate([DATA], {})
|
||||||
|
self.assertIn(
|
||||||
|
"Expected value of 'data' option is either dict or str, received type", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
args = [DATA, [CRITERIA_IN_RATE_CHECK]]
|
||||||
|
kwargs = {'engine': 'ansible.utils.sample'}
|
||||||
|
with self.assertRaises(AnsibleFilterError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"For engine 'ansible.utils.sample' error loading", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
args = ["invalid data", [CRITERIA_IN_RATE_CHECK]]
|
||||||
|
kwargs = {'engine': 'ansible.utils.jsonschema'}
|
||||||
|
with self.assertRaises(AnsibleFilterError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"'data' option value is invalid", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
args = [DATA, "invalid criteria"]
|
||||||
|
kwargs = {'engine': 'ansible.utils.jsonschema'}
|
||||||
|
with self.assertRaises(AnsibleFilterError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"'criteria' option value is invalid", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invalid_validate_plugin_config_options(self):
|
||||||
|
"""Check passing invalid validate plugin options"""
|
||||||
|
|
||||||
|
args = [DATA, [CRITERIA_CRC_ERROR_CHECK, CRITERIA_ENABLED_CHECK, CRITERIA_OPER_STATUS_UP_CHECK]]
|
||||||
|
kwargs = {'engine': 'ansible.utils.jsonschema', 'draft': 'draft0'}
|
||||||
|
with self.assertRaises(AnsibleFilterError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"value of draft must be one of: draft3, draft4, draft6, draft7, got: draft0", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_valid_data(self):
|
||||||
|
"""Check passing valid data as per criteria"""
|
||||||
|
|
||||||
|
args = [DATA, CRITERIA_IN_RATE_CHECK]
|
||||||
|
kwargs = {'engine': 'ansible.utils.jsonschema'}
|
||||||
|
result = validate(*args, **kwargs)
|
||||||
|
self.assertEqual(result, [])
|
|
@ -0,0 +1,186 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleLookupError
|
||||||
|
from ansible_collections.ansible.utils.plugins.lookup.validate import LookupModule
|
||||||
|
|
||||||
|
DATA = {
|
||||||
|
"GigabitEthernet0/0/0/0": {
|
||||||
|
"auto_negotiate": False,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 0,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "configured using Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": True,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "down",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
},
|
||||||
|
"GigabitEthernet0/0/0/1": {
|
||||||
|
"auto_negotiate": False,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 10,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "# interface is configures with Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": False,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "up",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_CRC_ERROR_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"in_crc_errors": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_ENABLED_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"enum": [True]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_OPER_STATUS_UP_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"oper_status": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_IN_RATE_CHECK = {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"rate": {
|
||||||
|
"properties": {
|
||||||
|
"in_rate": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestValidate(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self._lp = LookupModule()
|
||||||
|
|
||||||
|
def test_invalid_argspec(self):
|
||||||
|
"""Check passing invalid argspec"""
|
||||||
|
|
||||||
|
# missing required arguments
|
||||||
|
with self.assertRaises(AnsibleLookupError) as error:
|
||||||
|
self._lp.run([DATA], {})
|
||||||
|
self.assertIn(
|
||||||
|
"missing either 'data' or 'criteria' value", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
terms = [DATA, [CRITERIA_IN_RATE_CHECK]]
|
||||||
|
kwargs = {'engine': 'ansible.utils.sample'}
|
||||||
|
variables = {}
|
||||||
|
with self.assertRaises(AnsibleLookupError) as error:
|
||||||
|
self._lp.run(terms, variables, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"For engine 'ansible.utils.sample' error loading", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
terms = ["invalid data", [CRITERIA_IN_RATE_CHECK]]
|
||||||
|
kwargs = {'engine': 'ansible.utils.jsonschema'}
|
||||||
|
variables = {}
|
||||||
|
with self.assertRaises(AnsibleLookupError) as error:
|
||||||
|
self._lp.run(terms, variables, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"'data' option value is invalid", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
terms = [DATA, "invalid criteria"]
|
||||||
|
kwargs = {'engine': 'ansible.utils.jsonschema'}
|
||||||
|
variables = {}
|
||||||
|
with self.assertRaises(AnsibleLookupError) as error:
|
||||||
|
self._lp.run(terms, variables, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"'criteria' option value is invalid", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invalid_validate_plugin_config_options(self):
|
||||||
|
"""Check passing invalid validate plugin options"""
|
||||||
|
|
||||||
|
terms = [DATA, [CRITERIA_CRC_ERROR_CHECK, CRITERIA_ENABLED_CHECK, CRITERIA_OPER_STATUS_UP_CHECK]]
|
||||||
|
kwargs = {'engine': 'ansible.utils.jsonschema'}
|
||||||
|
variables = {}
|
||||||
|
result = self._lp.run(terms, variables, **kwargs)
|
||||||
|
self.assertIn("GigabitEthernet0/0/0/1.counters.in_crc_errors", result[0]['data_path'])
|
||||||
|
self.assertIn("GigabitEthernet0/0/0/1.enabled", result[1]['data_path'])
|
||||||
|
self.assertIn("GigabitEthernet0/0/0/0.oper_status", result[2]['data_path'])
|
||||||
|
|
||||||
|
def test_valid_data(self):
|
||||||
|
"""Check passing valid data as per criteria"""
|
||||||
|
|
||||||
|
terms = [DATA, CRITERIA_IN_RATE_CHECK]
|
||||||
|
kwargs = {'engine': 'ansible.utils.jsonschema'}
|
||||||
|
variables = {}
|
||||||
|
result = self._lp.run(terms, variables, **kwargs)
|
||||||
|
self.assertEqual(result, [])
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright 2020 Red Hat
|
||||||
|
# GNU General Public License v3.0+
|
||||||
|
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError
|
||||||
|
from ansible_collections.ansible.utils.plugins.test.validate import validate
|
||||||
|
|
||||||
|
DATA = {
|
||||||
|
"GigabitEthernet0/0/0/0": {
|
||||||
|
"auto_negotiate": False,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 0,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "configured using Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": True,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "down",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
},
|
||||||
|
"GigabitEthernet0/0/0/1": {
|
||||||
|
"auto_negotiate": False,
|
||||||
|
"counters": {
|
||||||
|
"in_crc_errors": 10,
|
||||||
|
"in_errors": 0,
|
||||||
|
"rate": {
|
||||||
|
"in_rate": 0,
|
||||||
|
"out_rate": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "# interface is configures with Ansible",
|
||||||
|
"duplex_mode": "full",
|
||||||
|
"enabled": False,
|
||||||
|
"line_protocol": "up",
|
||||||
|
"mtu": 1514,
|
||||||
|
"oper_status": "up",
|
||||||
|
"type": "GigabitEthernet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_CRC_ERROR_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"in_crc_errors": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_ENABLED_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"enum": [True]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_OPER_STATUS_UP_CHECK = {
|
||||||
|
"type" : "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"oper_status": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CRITERIA_IN_RATE_CHECK = {
|
||||||
|
"type": "object",
|
||||||
|
"patternProperties": {
|
||||||
|
"^.*": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"counters": {
|
||||||
|
"properties": {
|
||||||
|
"rate": {
|
||||||
|
"properties": {
|
||||||
|
"in_rate": {
|
||||||
|
"type": "number",
|
||||||
|
"maximum": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TestValidate(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_invalid_argspec(self):
|
||||||
|
"""Check passing invalid argspec"""
|
||||||
|
|
||||||
|
# missing required arguments
|
||||||
|
args = [DATA]
|
||||||
|
kwargs = {}
|
||||||
|
with self.assertRaises(AnsibleError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"missing required arguments: criteria", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs = {'criteria': CRITERIA_IN_RATE_CHECK, 'engine': 'ansible.utils.sample'}
|
||||||
|
with self.assertRaises(AnsibleError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"For engine 'ansible.utils.sample' error loading", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
args = ["invalid data"]
|
||||||
|
kwargs = {'criteria': [CRITERIA_IN_RATE_CHECK], 'engine': 'ansible.utils.jsonschema'}
|
||||||
|
with self.assertRaises(AnsibleError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"'data' option value is invalid", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
args = [DATA]
|
||||||
|
kwargs = {'criteria': 'invalid criteria', 'engine': 'ansible.utils.jsonschema'}
|
||||||
|
with self.assertRaises(AnsibleError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"'criteria' option value is invalid", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invalid_validate_plugin_config_options(self):
|
||||||
|
"""Check passing invalid validate plugin options"""
|
||||||
|
args = [DATA]
|
||||||
|
kwargs = {'criteria': 'invalid criteria', 'engine': 'ansible.utils.jsonschema', 'draft': 'draft0'}
|
||||||
|
|
||||||
|
with self.assertRaises(AnsibleError) as error:
|
||||||
|
validate(*args, **kwargs)
|
||||||
|
self.assertIn(
|
||||||
|
"value of draft must be one of: draft3, draft4, draft6, draft7, got: draft0", str(error.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invalid_data(self):
|
||||||
|
"""Check passing invalid data as per criteria"""
|
||||||
|
args = [DATA]
|
||||||
|
kwargs = {'criteria': [CRITERIA_ENABLED_CHECK, CRITERIA_OPER_STATUS_UP_CHECK, CRITERIA_CRC_ERROR_CHECK], 'engine': 'ansible.utils.jsonschema'}
|
||||||
|
result = validate(*args, **kwargs)
|
||||||
|
self.assertEqual(result, False)
|
||||||
|
|
||||||
|
def test_valid_data(self):
|
||||||
|
"""Check passing valid data as per criteria"""
|
||||||
|
args = [DATA]
|
||||||
|
kwargs = {'criteria': CRITERIA_IN_RATE_CHECK, 'engine': 'ansible.utils.jsonschema'}
|
||||||
|
result = validate(*args, **kwargs)
|
||||||
|
self.assertEqual(result, True)
|
|
@ -0,0 +1,31 @@
|
||||||
|
[tox]
|
||||||
|
minversion = 1.4.2
|
||||||
|
envlist = linters
|
||||||
|
skipsdist = True
|
||||||
|
|
||||||
|
[testenv]
|
||||||
|
deps = -r{toxinidir}/requirements.txt
|
||||||
|
-r{toxinidir}/test-requirements.txt
|
||||||
|
|
||||||
|
[testenv:black]
|
||||||
|
install_command = pip install {opts} {packages}
|
||||||
|
commands =
|
||||||
|
black -v -l79 {toxinidir}
|
||||||
|
|
||||||
|
[testenv:linters]
|
||||||
|
install_command = pip install {opts} {packages}
|
||||||
|
commands =
|
||||||
|
black -v -l79 --check {toxinidir}
|
||||||
|
flake8 {posargs}
|
||||||
|
|
||||||
|
[testenv:venv]
|
||||||
|
commands = {posargs}
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
# E123, E125 skipped as they are invalid PEP-8.
|
||||||
|
|
||||||
|
show-source = True
|
||||||
|
ignore = E123,E125,E402,W503
|
||||||
|
max-line-length = 160
|
||||||
|
builtins = _
|
||||||
|
exclude = .git,.tox,tests/unit/compat/
|
Loading…
Reference in New Issue