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
|
@ -95,10 +95,10 @@ jobs:
|
|||
- name: Install ansible-base (${{ matrix.ansible }})
|
||||
run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check
|
||||
|
||||
# OPTIONAL If your unit test requires Python libraries from other collections
|
||||
# Install them like this
|
||||
# - name: Install collection dependencies
|
||||
# run: ansible-galaxy collection install ansible.netcommon -p .
|
||||
# OPTIONAL If your unit test requires Python libraries from other collections
|
||||
# Install them like this
|
||||
- name: Install collection dependencies
|
||||
run: ansible-galaxy collection install ansible.utils -p .
|
||||
|
||||
# Run the unit tests
|
||||
- name: Run unit test
|
||||
|
@ -156,10 +156,16 @@ jobs:
|
|||
- name: Install ansible-base (${{ matrix.ansible }})
|
||||
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
|
||||
# Install them like this
|
||||
# - name: Install collection dependencies
|
||||
# run: ansible-galaxy collection install ansible.netcommon -p .
|
||||
- name: Install collection dependencies
|
||||
run: ansible-galaxy collection install ansible.utils -p .
|
||||
|
||||
# Run the integration tests
|
||||
- name: Run integration test
|
||||
|
@ -200,4 +206,4 @@ jobs:
|
|||
run: pip install black
|
||||
|
||||
- name: Run black --check
|
||||
run: black -l79 --diff --check ansible_collections/ansible/utils
|
||||
run: black -l79 --diff --check ansible_collections/ansible/utils
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
---
|
||||
name: Publish
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
- created
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
|
@ -11,7 +12,7 @@ jobs:
|
|||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
|
@ -31,5 +32,5 @@ jobs:
|
|||
# env:
|
||||
# ANSIBLE_GALAXY_API_KEY: ${{ secrets.ANSIBLE_GALAXY_API_KEY }}
|
||||
# run: |
|
||||
# ansible-galaxy collection build
|
||||
# ansible-galaxy collection build
|
||||
# ansible-galaxy collection publish *.tar.gz --api-key $ANSIBLE_GALAXY_API_KEY
|
||||
|
|
|
@ -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:
|
||||
- Add fact_diff module. Find the difference between text, files or facts
|
||||
major_changes:
|
||||
- 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
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
from importlib import import_module
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.errors import AnsibleActionFail
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible_collections.ansible.utils.plugins.modules.fact_diff import (
|
||||
DOCUMENTATION,
|
||||
|
|
|
@ -6,18 +6,17 @@
|
|||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
import ast
|
||||
import json
|
||||
import re
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.errors import AnsibleActionFail
|
||||
|
||||
from ansible.module_utils.common._collections_compat import (
|
||||
MutableMapping,
|
||||
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 ansible_collections.ansible.utils.plugins.modules.update_fact import (
|
||||
DOCUMENTATION,
|
||||
|
@ -40,9 +39,7 @@ class ActionModule(ActionBase):
|
|||
|
||||
def _check_argspec(self):
|
||||
aav = AnsibleArgSpecValidator(
|
||||
data=self._task.args,
|
||||
schema=DOCUMENTATION,
|
||||
name=self._task.action,
|
||||
data=self._task.args, schema=DOCUMENTATION, name=self._task.action
|
||||
)
|
||||
valid, errors, self._task.args = aav.validate()
|
||||
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._after = self._after.splitlines()
|
||||
self._before = [
|
||||
l
|
||||
for l in self._before
|
||||
if not any(regex.match(str(l)) for regex in self._skip_lines)
|
||||
line
|
||||
for line in self._before
|
||||
if not any(
|
||||
regex.match(str(line)) for regex in self._skip_lines
|
||||
)
|
||||
]
|
||||
self._after = [
|
||||
l
|
||||
for l in self._after
|
||||
if not any(regex.match(str(l)) for regex in self._skip_lines)
|
||||
line
|
||||
for line in self._after
|
||||
if not any(
|
||||
regex.match(str(line)) for regex in self._skip_lines
|
||||
)
|
||||
]
|
||||
if isinstance(self._before, list):
|
||||
self._debug("'before' is a list, joining with \n")
|
||||
|
|
|
@ -167,9 +167,7 @@ def _get_path(*args, **kwargs):
|
|||
data.update(kwargs)
|
||||
environment = data.pop("environment")
|
||||
aav = AnsibleArgSpecValidator(
|
||||
data=data,
|
||||
schema=DOCUMENTATION,
|
||||
name="get_path",
|
||||
data=data, schema=DOCUMENTATION, name="get_path"
|
||||
)
|
||||
valid, errors, updated_data = aav.validate()
|
||||
if not valid:
|
||||
|
|
|
@ -321,9 +321,7 @@ def _index_of(*args, **kwargs):
|
|||
data.update(kwargs)
|
||||
environment = data.pop("environment")
|
||||
aav = AnsibleArgSpecValidator(
|
||||
data=data,
|
||||
schema=DOCUMENTATION,
|
||||
name="index_of",
|
||||
data=data, schema=DOCUMENTATION, name="index_of"
|
||||
)
|
||||
valid, errors, updated_data = aav.validate()
|
||||
if not valid:
|
||||
|
|
|
@ -132,9 +132,7 @@ def _to_paths(*args, **kwargs):
|
|||
data = dict(zip(keys, args))
|
||||
data.update(kwargs)
|
||||
aav = AnsibleArgSpecValidator(
|
||||
data=data,
|
||||
schema=DOCUMENTATION,
|
||||
name="to_paths",
|
||||
data=data, schema=DOCUMENTATION, name="to_paths"
|
||||
)
|
||||
valid, errors, updated_data = aav.validate()
|
||||
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.update(kwargs)
|
||||
aav = AnsibleArgSpecValidator(
|
||||
data=terms,
|
||||
schema=DOCUMENTATION,
|
||||
name="get_path",
|
||||
data=terms, schema=DOCUMENTATION, name="get_path"
|
||||
)
|
||||
valid, errors, updated_data = aav.validate()
|
||||
if not valid:
|
||||
|
|
|
@ -338,9 +338,7 @@ class LookupModule(LookupBase):
|
|||
terms = dict(zip(keys, terms))
|
||||
terms.update(kwargs)
|
||||
aav = AnsibleArgSpecValidator(
|
||||
data=terms,
|
||||
schema=DOCUMENTATION,
|
||||
name="index_of",
|
||||
data=terms, schema=DOCUMENTATION, name="index_of"
|
||||
)
|
||||
valid, errors, updated_data = aav.validate()
|
||||
if not valid:
|
||||
|
|
|
@ -142,9 +142,7 @@ class LookupModule(LookupBase):
|
|||
terms = dict(zip(keys, terms))
|
||||
terms.update(kwargs)
|
||||
aav = AnsibleArgSpecValidator(
|
||||
data=terms,
|
||||
schema=DOCUMENTATION,
|
||||
name="to_paths",
|
||||
data=terms, schema=DOCUMENTATION, name="to_paths"
|
||||
)
|
||||
valid, errors, updated_data = aav.validate()
|
||||
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
|
||||
|
||||
import json
|
||||
import re
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.ansible.utils.plugins.module_utils.common.utils import (
|
||||
dict_merge,
|
||||
)
|
||||
from ansible.module_utils.six import iteritems, string_types
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible.module_utils.six import iteritems
|
||||
|
||||
try:
|
||||
import yaml
|
||||
|
@ -48,7 +46,9 @@ except ImportError:
|
|||
# ansible-base 2.11 should expose argspec validation outside of the
|
||||
# ansiblemodule class
|
||||
try:
|
||||
from ansible.module_utils.somefile import FutureBaseArgspecValidator
|
||||
from ansible.module_utils.somefile import ( # noqa: F401
|
||||
FutureBaseArgspecValidator,
|
||||
)
|
||||
|
||||
HAS_ANSIBLE_ARG_SPEC_VALIDATOR = True
|
||||
except ImportError:
|
||||
|
@ -107,9 +107,7 @@ class MonkeyModule(AnsibleModule):
|
|||
"""
|
||||
if self.name:
|
||||
msg = re.sub(
|
||||
r"\(basic\.pyc?\)",
|
||||
"'{name}'".format(name=self.name),
|
||||
msg,
|
||||
r"\(basic\.pyc?\)", "'{name}'".format(name=self.name), msg
|
||||
)
|
||||
self._valid = False
|
||||
self._errors = msg
|
||||
|
@ -243,3 +241,28 @@ class AnsibleArgSpecValidator:
|
|||
return self._validate()
|
||||
else:
|
||||
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
|
||||
|
||||
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._text import to_native
|
||||
|
||||
|
|
|
@ -8,10 +8,9 @@ from __future__ import absolute_import, division, print_function
|
|||
__metaclass__ = type
|
||||
|
||||
from copy import deepcopy
|
||||
from itertools import chain
|
||||
|
||||
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):
|
||||
|
@ -102,3 +101,12 @@ def dict_merge(base, other):
|
|||
combined[key] = other.get(key)
|
||||
|
||||
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,14 +1,15 @@
|
|||
- name: Check argspec validation
|
||||
---
|
||||
- name: Check argspec validation
|
||||
ansible.utils.fact_diff:
|
||||
ignore_errors: True
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ string in result.msg }}"
|
||||
loop:
|
||||
- "missing required arguments:"
|
||||
- before
|
||||
- after
|
||||
- "missing required arguments:"
|
||||
- before
|
||||
- after
|
||||
loop_control:
|
||||
loop_var: string
|
||||
|
||||
|
@ -19,9 +20,9 @@
|
|||
plugin:
|
||||
vars:
|
||||
skip_lines:
|
||||
a_dict: False
|
||||
ignore_errors: True
|
||||
a_dict: false
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ 'unable to convert to list' in result.msg }}"
|
||||
that: "{{ 'unable to convert to list' in result.msg }}"
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
---
|
||||
- set_fact:
|
||||
before:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 1
|
||||
after:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 2
|
||||
- 3
|
||||
- 2
|
||||
- 3
|
||||
|
||||
- name: Show the difference in json format
|
||||
ansible.utils.fact_diff:
|
||||
|
@ -111,7 +112,7 @@
|
|||
# ansible.utils.fact_diff:
|
||||
# before: "{{ pre.response|ansible.utils.to_paths }}"
|
||||
# after: "{{ post.response|ansible.utils.to_paths }}"
|
||||
|
||||
|
||||
# TASK [ansible.utils.fact_diff] *********************************************
|
||||
# --- before
|
||||
# +++ after
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
---
|
||||
- name: Check for graceful fail of invalid regex
|
||||
ansible.utils.fact_diff:
|
||||
before: [0, 1, 2]
|
||||
|
@ -5,11 +6,11 @@
|
|||
plugin:
|
||||
vars:
|
||||
skip_lines:
|
||||
- '+'
|
||||
ignore_errors: True
|
||||
- '+'
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ msg in result.msg }}"
|
||||
vars:
|
||||
msg: "The regex '+', is not valid"
|
||||
msg: "The regex '+', is not valid"
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
---
|
||||
- name: Recursively find all test files
|
||||
find:
|
||||
file_type: file
|
||||
paths: "{{ role_path }}/tasks/include"
|
||||
recurse: yes
|
||||
use_regex: yes
|
||||
recurse: true
|
||||
use_regex: true
|
||||
patterns:
|
||||
- '^(?!_).+$'
|
||||
register: found
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
---
|
||||
- set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 1
|
||||
|
||||
- 0
|
||||
- 1
|
||||
|
||||
- name: Check argspec validation with filter
|
||||
set_fact:
|
||||
_result: "{{ a|ansible.utils.get_path() }}"
|
||||
ignore_errors: True
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
|
@ -20,12 +21,12 @@
|
|||
- name: Check argspec validation with lookup
|
||||
set_fact:
|
||||
_result: "{{ lookup('ansible.utils.get_path') }}"
|
||||
ignore_errors: True
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ item in result.msg }}"
|
||||
loop:
|
||||
- "missing required arguments:"
|
||||
- path
|
||||
- var
|
||||
- "missing required arguments:"
|
||||
- path
|
||||
- var
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
---
|
||||
- ansible.builtin.set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 1
|
||||
e:
|
||||
- True
|
||||
- False
|
||||
- true
|
||||
- false
|
||||
|
||||
- name: Retrieve a value deep inside a using a path
|
||||
ansible.builtin.set_fact:
|
||||
|
@ -16,7 +17,7 @@
|
|||
path: b.c.d[0]
|
||||
|
||||
# TASK [Retrieve a value deep inside a using a path] ******************
|
||||
# ok: [localhost] => changed=false
|
||||
# ok: [localhost] => changed=false
|
||||
# ansible_facts:
|
||||
# value: '0'
|
||||
|
||||
|
@ -93,9 +94,9 @@
|
|||
|
||||
|
||||
# TASK [Get the description of several interfaces] ******************
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false
|
||||
# msg: Configured by ansible
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false
|
||||
# msg: CONFIGURED BY ANSIBLE
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false
|
||||
# msg: ''
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
---
|
||||
- ansible.builtin.set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 1
|
||||
e:
|
||||
- True
|
||||
- False
|
||||
- true
|
||||
- false
|
||||
|
||||
- name: Retrieve a value deep inside a using a path
|
||||
ansible.builtin.set_fact:
|
||||
|
@ -16,7 +17,7 @@
|
|||
path: b.c.d[0]
|
||||
|
||||
# TASK [Retrieve a value deep inside a using a path] ******************
|
||||
# ok: [localhost] => changed=false
|
||||
# ok: [localhost] => changed=false
|
||||
# ansible_facts:
|
||||
# value: '0'
|
||||
|
||||
|
@ -93,9 +94,9 @@
|
|||
|
||||
|
||||
# TASK [Get the description of several interfaces] ******************
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/1'].description) => changed=false
|
||||
# msg: Configured by ansible
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/2'].description|upper) => changed=false
|
||||
# msg: CONFIGURED BY ANSIBLE
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false
|
||||
# ok: [nxos101] => (item=by_name['Ethernet1/3'].description|default('')) => changed=false
|
||||
# msg: ''
|
||||
|
|
|
@ -1,58 +1,59 @@
|
|||
---
|
||||
- set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 1
|
||||
|
||||
- name: Simple test filter and lookup
|
||||
assert:
|
||||
that: "{{ item.result == item.expected }}"
|
||||
loop:
|
||||
- result: "{{ vars|ansible.utils.get_path('a') }}"
|
||||
expected: "{{ a }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b') }}"
|
||||
expected: "{{ a.b }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b.c') }}"
|
||||
expected: "{{ a.b.c }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b.c.d') }}"
|
||||
expected: "{{ a.b.c.d }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b.c.d[0]') }}"
|
||||
expected: "{{ a.b.c.d[0] }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b.c.d[1]') }}"
|
||||
expected: "{{ a.b.c.d[1] }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b[\"c\"]') }}"
|
||||
expected: "{{ a.b.c }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', vars, 'a') }}"
|
||||
expected: "{{ a }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b') }}"
|
||||
expected: "{{ a.b }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b.c') }}"
|
||||
expected: "{{ a.b.c }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b.c.d') }}"
|
||||
expected: "{{ a.b.c.d }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b.c.d[0]') }}"
|
||||
expected: "{{ a.b.c.d[0] }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b.c.d[1]') }}"
|
||||
expected: "{{ a.b.c.d[1] }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b[\"c\"]') }}"
|
||||
expected: "{{ a.b.c }}"
|
||||
- result: "{{ vars|ansible.utils.get_path('a') }}"
|
||||
expected: "{{ a }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b') }}"
|
||||
expected: "{{ a.b }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b.c') }}"
|
||||
expected: "{{ a.b.c }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b.c.d') }}"
|
||||
expected: "{{ a.b.c.d }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b.c.d[0]') }}"
|
||||
expected: "{{ a.b.c.d[0] }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b.c.d[1]') }}"
|
||||
expected: "{{ a.b.c.d[1] }}"
|
||||
- result: "{{ a|ansible.utils.get_path('b[\"c\"]') }}"
|
||||
expected: "{{ a.b.c }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', vars, 'a') }}"
|
||||
expected: "{{ a }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b') }}"
|
||||
expected: "{{ a.b }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b.c') }}"
|
||||
expected: "{{ a.b.c }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b.c.d') }}"
|
||||
expected: "{{ a.b.c.d }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b.c.d[0]') }}"
|
||||
expected: "{{ a.b.c.d[0] }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b.c.d[1]') }}"
|
||||
expected: "{{ a.b.c.d[1] }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', a, 'b[\"c\"]') }}"
|
||||
expected: "{{ a.b.c }}"
|
||||
|
||||
- set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 0
|
||||
|
||||
- name: Simple test filter and lookup w/ wantlist
|
||||
assert:
|
||||
that: "{{ item.result == item.expected }}"
|
||||
loop:
|
||||
- result: "{{ vars|ansible.utils.get_path('a.b.c.d[0]', wantlist=True) }}"
|
||||
expected:
|
||||
- "{{ a.b.c.d[0] }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', vars, 'a.b.c.d[0]', wantlist=True) }}"
|
||||
expected:
|
||||
- "{{ a.b.c.d[0] }}"
|
||||
- result: "{{ vars|ansible.utils.get_path('a.b.c.d[0]', wantlist=True) }}"
|
||||
expected:
|
||||
- "{{ a.b.c.d[0] }}"
|
||||
- result: "{{ lookup('ansible.utils.get_path', vars, 'a.b.c.d[0]', wantlist=True) }}"
|
||||
expected:
|
||||
- "{{ a.b.c.d[0] }}"
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
---
|
||||
- name: Recursively find all test files
|
||||
find:
|
||||
file_type: file
|
||||
paths: "{{ role_path }}/tasks/include"
|
||||
recurse: yes
|
||||
use_regex: yes
|
||||
recurse: true
|
||||
use_regex: true
|
||||
patterns:
|
||||
- '^(?!_).+$'
|
||||
register: found
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
---
|
||||
- set_fact:
|
||||
complex:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- e0: 0
|
||||
e1: ansible
|
||||
e2: True
|
||||
- e0: 1
|
||||
e1: redhat
|
||||
- e0: 0
|
||||
e1: ansible
|
||||
e2: true
|
||||
- e0: 1
|
||||
e1: redhat
|
||||
|
||||
- name: Check argspec validation with filter (not a list)
|
||||
set_fact:
|
||||
_result: "{{ complex|ansible.utils.index_of() }}"
|
||||
ignore_errors: True
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
|
@ -24,7 +25,7 @@
|
|||
- name: Check argspec validation with filter (missing params)
|
||||
set_fact:
|
||||
_result: "{{ complex.a.b.c.d|ansible.utils.index_of() }}"
|
||||
ignore_errors: True
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
|
@ -35,7 +36,7 @@
|
|||
- name: Check argspec validation with lookup (not a list)
|
||||
set_fact:
|
||||
_result: "{{ lookup('ansible.utils.index_of', complex) }}"
|
||||
ignore_errors: True
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
|
@ -46,12 +47,12 @@
|
|||
- name: Check argspec validation with lookup (missing params)
|
||||
set_fact:
|
||||
_result: "{{ lookup('ansible.utils.index_of') }}"
|
||||
ignore_errors: True
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "{{ item in result.msg }}"
|
||||
loop:
|
||||
- "missing required arguments:"
|
||||
- data
|
||||
- test
|
||||
- "missing required arguments:"
|
||||
- data
|
||||
- test
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
---
|
||||
#### Simple examples
|
||||
|
||||
- set_fact:
|
||||
data:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
- name: Find the index of 2
|
||||
set_fact:
|
||||
indices: "{{ data|ansible.utils.index_of('eq', 2) }}"
|
||||
|
||||
# TASK [Find the index of 2] *************************************************
|
||||
# ok: [nxos101] => changed=false
|
||||
# ok: [nxos101] => changed=false
|
||||
# ansible_facts:
|
||||
# indices: '1'
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
|||
indices: "{{ data|ansible.utils.index_of('eq', 2, wantlist=True) }}"
|
||||
|
||||
# TASK [Find the index of 2, ensure list is returned] ************************
|
||||
# ok: [nxos101] => changed=false
|
||||
# ok: [nxos101] => changed=false
|
||||
# ansible_facts:
|
||||
# indices:
|
||||
# - 1
|
||||
|
@ -32,7 +32,7 @@
|
|||
value: 3
|
||||
|
||||
# TASK [Find the index of 3 using the long format] ***************************
|
||||
# ok: [nxos101] => changed=false
|
||||
# ok: [nxos101] => changed=false
|
||||
# ansible_facts:
|
||||
# indices:
|
||||
# - 2
|
||||
|
@ -46,9 +46,9 @@
|
|||
value: 1
|
||||
|
||||
# TASK [Find numbers great than 1, using loop] *******************************
|
||||
# ok: [sw01] => (item=1) =>
|
||||
# ok: [sw01] => (item=1) =>
|
||||
# msg: 2 is > than 1
|
||||
# ok: [sw01] => (item=2) =>
|
||||
# ok: [sw01] => (item=2) =>
|
||||
# msg: 3 is > than 1
|
||||
|
||||
|
||||
|
@ -56,21 +56,21 @@
|
|||
|
||||
- set_fact:
|
||||
data:
|
||||
- name: sw01.example.lan
|
||||
type: switch
|
||||
- name: rtr01.example.lan
|
||||
type: router
|
||||
- name: fw01.example.corp
|
||||
type: firewall
|
||||
- name: fw02.example.corp
|
||||
type: firewall
|
||||
- name: sw01.example.lan
|
||||
type: switch
|
||||
- name: rtr01.example.lan
|
||||
type: router
|
||||
- name: fw01.example.corp
|
||||
type: firewall
|
||||
- name: fw02.example.corp
|
||||
type: firewall
|
||||
|
||||
- name: Find the index of all firewalls using the type key
|
||||
set_fact:
|
||||
firewalls: "{{ data|ansible.utils.index_of('eq', 'firewall', 'type') }}"
|
||||
|
||||
# TASK [Find the index of all firewalls using the type key] ******************
|
||||
# ok: [nxos101] => changed=false
|
||||
# ok: [nxos101] => changed=false
|
||||
# ansible_facts:
|
||||
# firewalls:
|
||||
# - 2
|
||||
|
@ -84,9 +84,9 @@
|
|||
device_type: firewall
|
||||
|
||||
# TASK [Find the index of all firewalls, use in a loop, as a filter] *********
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# msg: The type of firewall at index 2 has name fw01.example.corp.
|
||||
# ok: [nxos101] => (item=3) =>
|
||||
# ok: [nxos101] => (item=3) =>
|
||||
# msg: The type of firewall at index 3 has name fw02.example.corp.
|
||||
|
||||
- name: Find the index of all devices with a .corp name
|
||||
|
@ -94,12 +94,12 @@
|
|||
msg: "The device named {{ data[item].name }} is a {{ data[item].type }}"
|
||||
loop: "{{ data|ansible.utils.index_of('regex', expression, 'name') }}"
|
||||
vars:
|
||||
expression: '\.corp$' # ends with .corp
|
||||
expression: '\.corp$' # ends with .corp
|
||||
|
||||
# TASK [Find the index of all devices with a .corp name] *********************
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# msg: The device named fw01.example.corp is a firewall
|
||||
# ok: [nxos101] => (item=3) =>
|
||||
# ok: [nxos101] => (item=3) =>
|
||||
# msg: The device named fw02.example.corp is a firewall
|
||||
|
||||
|
||||
|
@ -110,8 +110,8 @@
|
|||
# state: gathered
|
||||
# register: current_l3
|
||||
|
||||
# TASK [Retrieve the current L3 interface configuration] *********************
|
||||
# ok: [sw01] => changed=false
|
||||
# TASK [Retrieve the current L3 interface configuration] *********************
|
||||
# ok: [sw01] => changed=false
|
||||
# gathered:
|
||||
# - name: Ethernet1/1
|
||||
# - name: Ethernet1/2
|
||||
|
@ -122,7 +122,7 @@
|
|||
# name: mgmt0
|
||||
|
||||
# - name: Find the indices interfaces with a 192.168.101.xx ip address
|
||||
# set_fact:
|
||||
# set_fact:
|
||||
# found: "{{ found + entry }}"
|
||||
# with_indexed_items: "{{ current_l3.gathered }}"
|
||||
# vars:
|
||||
|
@ -135,7 +135,7 @@
|
|||
# when: address
|
||||
|
||||
# TASK [debug] ***************************************************************
|
||||
# ok: [sw01] =>
|
||||
# ok: [sw01] =>
|
||||
# found:
|
||||
# - address_idxs:
|
||||
# - 0
|
||||
|
@ -150,7 +150,7 @@
|
|||
# address: "{{ interface.ipv4[item.1].address }}"
|
||||
|
||||
# TASK [Show all interfaces and their address] *******************************
|
||||
# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) =>
|
||||
# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) =>
|
||||
# msg: mgmt0 has ip 192.168.101.14/24
|
||||
|
||||
|
||||
|
@ -162,8 +162,8 @@
|
|||
interface:
|
||||
- config:
|
||||
description: configured by Ansible - 1
|
||||
enabled: True
|
||||
loopback-mode: False
|
||||
enabled: true
|
||||
loopback-mode: false
|
||||
mtu: 1024
|
||||
name: loopback0000
|
||||
type: eth
|
||||
|
@ -172,18 +172,18 @@
|
|||
subinterface:
|
||||
- config:
|
||||
description: subinterface configured by Ansible - 1
|
||||
enabled: True
|
||||
enabled: true
|
||||
index: 5
|
||||
index: 5
|
||||
- config:
|
||||
description: subinterface configured by Ansible - 2
|
||||
enabled: False
|
||||
enabled: false
|
||||
index: 2
|
||||
index: 2
|
||||
- config:
|
||||
description: configured by Ansible - 2
|
||||
enabled: False
|
||||
loopback-mode: False
|
||||
enabled: false
|
||||
loopback-mode: false
|
||||
mtu: 2048
|
||||
name: loopback1111
|
||||
type: virt
|
||||
|
@ -192,12 +192,12 @@
|
|||
subinterface:
|
||||
- config:
|
||||
description: subinterface configured by Ansible - 3
|
||||
enabled: True
|
||||
enabled: true
|
||||
index: 10
|
||||
index: 10
|
||||
- config:
|
||||
description: subinterface configured by Ansible - 4
|
||||
enabled: False
|
||||
enabled: false
|
||||
index: 3
|
||||
index: 3
|
||||
|
||||
|
@ -222,5 +222,5 @@
|
|||
ansible.utils.index_of('eq', sub_index, 'index') }}
|
||||
|
||||
# TASK [Find the description of loopback111, subinterface index 10] ************
|
||||
# ok: [sw01] =>
|
||||
# ok: [sw01] =>
|
||||
# msg: subinterface configured by Ansible - 3
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
---
|
||||
#### Simple examples
|
||||
|
||||
- set_fact:
|
||||
data:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
- name: Find the index of 2
|
||||
set_fact:
|
||||
indices: "{{ lookup('ansible.utils.index_of', data, 'eq', 2) }}"
|
||||
|
||||
# TASK [Find the index of 2] *************************************************
|
||||
# ok: [nxos101] => changed=false
|
||||
# ok: [nxos101] => changed=false
|
||||
# ansible_facts:
|
||||
# indices: '1'
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
|||
indices: "{{ lookup('ansible.utils.index_of', data, 'eq', 2, wantlist=True) }}"
|
||||
|
||||
# TASK [Find the index of 2, ensure list is returned] ************************
|
||||
# ok: [nxos101] => changed=false
|
||||
# ok: [nxos101] => changed=false
|
||||
# ansible_facts:
|
||||
# indices:
|
||||
# - 1
|
||||
|
@ -32,7 +32,7 @@
|
|||
value: 3
|
||||
|
||||
# TASK [Find the index of 3 using the long format] ***************************
|
||||
# ok: [nxos101] => changed=false
|
||||
# ok: [nxos101] => changed=false
|
||||
# ansible_facts:
|
||||
# indices:
|
||||
# - 2
|
||||
|
@ -46,9 +46,9 @@
|
|||
value: 1
|
||||
|
||||
# TASK [Find numbers great than 1, using loop] *******************************
|
||||
# ok: [sw01] => (item=1) =>
|
||||
# ok: [sw01] => (item=1) =>
|
||||
# msg: 2 is > than 1
|
||||
# ok: [sw01] => (item=2) =>
|
||||
# ok: [sw01] => (item=2) =>
|
||||
# msg: 3 is > than 1
|
||||
|
||||
- name: Find numbers greater than 1, using with
|
||||
|
@ -62,9 +62,9 @@
|
|||
value: 1
|
||||
|
||||
# TASK [Find numbers greater than 1, using with] *****************************
|
||||
# ok: [nxos101] => (item=1) =>
|
||||
# ok: [nxos101] => (item=1) =>
|
||||
# msg: 2 is > than 1
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# msg: 3 is > than 1
|
||||
|
||||
|
||||
|
@ -72,21 +72,21 @@
|
|||
|
||||
- set_fact:
|
||||
data:
|
||||
- name: sw01.example.lan
|
||||
type: switch
|
||||
- name: rtr01.example.lan
|
||||
type: router
|
||||
- name: fw01.example.corp
|
||||
type: firewall
|
||||
- name: fw02.example.corp
|
||||
type: firewall
|
||||
- name: sw01.example.lan
|
||||
type: switch
|
||||
- name: rtr01.example.lan
|
||||
type: router
|
||||
- name: fw01.example.corp
|
||||
type: firewall
|
||||
- name: fw02.example.corp
|
||||
type: firewall
|
||||
|
||||
- name: Find the index of all firewalls using the type key
|
||||
set_fact:
|
||||
firewalls: "{{ lookup('ansible.utils.index_of', data, 'eq', 'firewall', 'type') }}"
|
||||
|
||||
# TASK [Find the index of all firewalls using the type key] ******************
|
||||
# ok: [nxos101] => changed=false
|
||||
# ok: [nxos101] => changed=false
|
||||
# ansible_facts:
|
||||
# firewalls:
|
||||
# - 2
|
||||
|
@ -100,9 +100,9 @@
|
|||
device_type: firewall
|
||||
|
||||
# TASK [Find the index of all firewalls, use in a loop, as a filter] *********
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# msg: The type of firewall at index 2 has name fw01.example.corp.
|
||||
# ok: [nxos101] => (item=3) =>
|
||||
# ok: [nxos101] => (item=3) =>
|
||||
# msg: The type of firewall at index 3 has name fw02.example.corp.
|
||||
|
||||
- name: Find the index of all devices with a .corp name
|
||||
|
@ -110,12 +110,12 @@
|
|||
msg: "The device named {{ data[item].name }} is a {{ data[item].type }}"
|
||||
loop: "{{ lookup('ansible.utils.index_of', data, 'regex', expression, 'name') }}"
|
||||
vars:
|
||||
expression: '\.corp$' # ends with .corp
|
||||
expression: '\.corp$' # ends with .corp
|
||||
|
||||
# TASK [Find the index of all devices with a .corp name] *********************
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# ok: [nxos101] => (item=2) =>
|
||||
# msg: The device named fw01.example.corp is a firewall
|
||||
# ok: [nxos101] => (item=3) =>
|
||||
# ok: [nxos101] => (item=3) =>
|
||||
# msg: The device named fw02.example.corp is a firewall
|
||||
|
||||
|
||||
|
@ -126,8 +126,8 @@
|
|||
# state: gathered
|
||||
# register: current_l3
|
||||
|
||||
# TASK [Retrieve the current L3 interface configuration] *********************
|
||||
# ok: [sw01] => changed=false
|
||||
# TASK [Retrieve the current L3 interface configuration] *********************
|
||||
# ok: [sw01] => changed=false
|
||||
# gathered:
|
||||
# - name: Ethernet1/1
|
||||
# - name: Ethernet1/2
|
||||
|
@ -138,7 +138,7 @@
|
|||
# name: mgmt0
|
||||
|
||||
# - name: Find the indices interfaces with a 192.168.101.xx ip address
|
||||
# set_fact:
|
||||
# set_fact:
|
||||
# found: "{{ found + entry }}"
|
||||
# with_indexed_items: "{{ current_l3.gathered }}"
|
||||
# vars:
|
||||
|
@ -151,7 +151,7 @@
|
|||
# when: address
|
||||
|
||||
# TASK [debug] ***************************************************************
|
||||
# ok: [sw01] =>
|
||||
# ok: [sw01] =>
|
||||
# found:
|
||||
# - address_idxs:
|
||||
# - 0
|
||||
|
@ -166,7 +166,7 @@
|
|||
# address: "{{ interface.ipv4[item.1].address }}"
|
||||
|
||||
# TASK [Show all interfaces and their address] *******************************
|
||||
# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) =>
|
||||
# ok: [nxos101] => (item=[{'interface_idx': '128', 'address_idxs': [0]}, 0]) =>
|
||||
# msg: mgmt0 has ip 192.168.101.14/24
|
||||
|
||||
|
||||
|
@ -178,8 +178,8 @@
|
|||
interface:
|
||||
- config:
|
||||
description: configured by Ansible - 1
|
||||
enabled: True
|
||||
loopback-mode: False
|
||||
enabled: true
|
||||
loopback-mode: false
|
||||
mtu: 1024
|
||||
name: loopback0000
|
||||
type: eth
|
||||
|
@ -188,18 +188,18 @@
|
|||
subinterface:
|
||||
- config:
|
||||
description: subinterface configured by Ansible - 1
|
||||
enabled: True
|
||||
enabled: true
|
||||
index: 5
|
||||
index: 5
|
||||
- config:
|
||||
description: subinterface configured by Ansible - 2
|
||||
enabled: False
|
||||
enabled: false
|
||||
index: 2
|
||||
index: 2
|
||||
- config:
|
||||
description: configured by Ansible - 2
|
||||
enabled: False
|
||||
loopback-mode: False
|
||||
enabled: false
|
||||
loopback-mode: false
|
||||
mtu: 2048
|
||||
name: loopback1111
|
||||
type: virt
|
||||
|
@ -208,12 +208,12 @@
|
|||
subinterface:
|
||||
- config:
|
||||
description: subinterface configured by Ansible - 3
|
||||
enabled: True
|
||||
enabled: true
|
||||
index: 10
|
||||
index: 10
|
||||
- config:
|
||||
description: subinterface configured by Ansible - 4
|
||||
enabled: False
|
||||
enabled: false
|
||||
index: 3
|
||||
index: 3
|
||||
|
||||
|
@ -230,8 +230,8 @@
|
|||
sub_index: 10
|
||||
# retrieve the index in each nested list
|
||||
int_idx: |
|
||||
{{ lookup('ansible.utils.index_of',
|
||||
data.interfaces.interface,
|
||||
{{ lookup('ansible.utils.index_of',
|
||||
data.interfaces.interface,
|
||||
'eq', int_name, 'name') }}
|
||||
subint_idx: |
|
||||
{{ lookup('ansible.utils.index_of',
|
||||
|
@ -239,5 +239,5 @@
|
|||
'eq', sub_index, 'index') }}
|
||||
|
||||
# TASK [Find the description of loopback111, subinterface index 10] ************
|
||||
# ok: [sw01] =>
|
||||
# ok: [sw01] =>
|
||||
# msg: subinterface configured by Ansible - 3
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
---
|
||||
- set_fact:
|
||||
complex:
|
||||
a:
|
||||
- True
|
||||
- True
|
||||
- False
|
||||
- true
|
||||
- true
|
||||
- false
|
||||
- 5
|
||||
|
||||
b:
|
||||
|
@ -13,9 +14,9 @@
|
|||
b2: 4
|
||||
c:
|
||||
c1:
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
- a
|
||||
- b
|
||||
- c
|
||||
d:
|
||||
- Abcd
|
||||
- abcd
|
||||
|
@ -26,35 +27,35 @@
|
|||
assert:
|
||||
that: "{{ item.test == item.result }}"
|
||||
loop:
|
||||
- test: "{{ complex.a|ansible.utils.index_of('eq', True) }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a, 'eq', True) }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ complex.a|ansible.utils.index_of('in', [True, False]) }}"
|
||||
result: [0, 1, 2]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a, 'in', [True, False]) }}"
|
||||
result: [0, 1, 2]
|
||||
# These are commented out due to jinja < 2.11 w/ 2.9, 'integer' not avaialable
|
||||
# can be enabled at a later date
|
||||
# - test: "{{ complex.a|ansible.utils.index_of('integer') }}"
|
||||
# result: "3"
|
||||
# - test: "{{ lookup('ansible.utils.index_of', complex.a, 'integer') }}"
|
||||
# result: "3"
|
||||
- test: "{{ complex.a|ansible.utils.index_of('eq', True) }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a, 'eq', True) }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ complex.a|ansible.utils.index_of('in', [True, False]) }}"
|
||||
result: [0, 1, 2]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a, 'in', [True, False]) }}"
|
||||
result: [0, 1, 2]
|
||||
# These are commented out due to jinja < 2.11 w/ 2.9, 'integer' not avaialable
|
||||
# can be enabled at a later date
|
||||
# - test: "{{ complex.a|ansible.utils.index_of('integer') }}"
|
||||
# result: "3"
|
||||
# - test: "{{ lookup('ansible.utils.index_of', complex.a, 'integer') }}"
|
||||
# result: "3"
|
||||
|
||||
- test: "{{ complex.b|ansible.utils.index_of('==', 1, 'b1') }}"
|
||||
result: "0"
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.b, '==', 1, 'b1') }}"
|
||||
result: "0"
|
||||
|
||||
- test: "{{ complex.c.c1|ansible.utils.index_of('!=', 'c') }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.c.c1, '!=', 'c') }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ complex.b|ansible.utils.index_of('==', 1, 'b1') }}"
|
||||
result: "0"
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.b, '==', 1, 'b1') }}"
|
||||
result: "0"
|
||||
|
||||
- test: "{{ complex.d|ansible.utils.index_of('match', '.*d$') }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.d, 'match', '.*d$') }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ complex.c.c1|ansible.utils.index_of('!=', 'c') }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.c.c1, '!=', 'c') }}"
|
||||
result: [0, 1]
|
||||
|
||||
- test: "{{ complex.d|ansible.utils.index_of('match', '.*d$') }}"
|
||||
result: [0, 1]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.d, 'match', '.*d$') }}"
|
||||
result: [0, 1]
|
||||
|
||||
|
||||
- set_fact:
|
||||
|
@ -63,43 +64,43 @@
|
|||
b:
|
||||
c:
|
||||
d:
|
||||
- e0: 0
|
||||
e1: ansible
|
||||
e2: True
|
||||
- e0: 1
|
||||
e1: redhat
|
||||
- e0: 0
|
||||
e1: ansible
|
||||
e2: true
|
||||
- e0: 1
|
||||
e1: redhat
|
||||
|
||||
- name: Find index in list of dictionaries
|
||||
assert:
|
||||
that: "{{ item.test == item.result }}"
|
||||
loop:
|
||||
- test: "{{ complex.a.b.c.d|ansible.utils.index_of('eq', 'ansible', 'e1') }}"
|
||||
result: "0"
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', 'ansible', 'e1') }}"
|
||||
result: "0"
|
||||
- test: "{{ complex.a.b.c.d|ansible.utils.index_of('eq', 'ansible', 'e1', wantlist=True) }}"
|
||||
result: [0]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', 'ansible', 'e1', wantlist=True) }}"
|
||||
result: [0]
|
||||
- test: "{{ complex.a.b.c.d|ansible.utils.index_of('eq', 'ansible', 'e1') }}"
|
||||
result: "0"
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', 'ansible', 'e1') }}"
|
||||
result: "0"
|
||||
- test: "{{ complex.a.b.c.d|ansible.utils.index_of('eq', 'ansible', 'e1', wantlist=True) }}"
|
||||
result: [0]
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', 'ansible', 'e1', wantlist=True) }}"
|
||||
result: [0]
|
||||
|
||||
- name: Test a missing key in the list of dictionaries
|
||||
assert:
|
||||
that: "{{ item.test == item.result }}"
|
||||
loop:
|
||||
- test: "{{ complex.a.b.c.d|ansible.utils.index_of('eq', True, 'e2') }}"
|
||||
result: "0"
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', True, 'e2') }}"
|
||||
result: "0"
|
||||
- test: "{{ complex.a.b.c.d|ansible.utils.index_of('eq', True, 'e2') }}"
|
||||
result: "0"
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', True, 'e2') }}"
|
||||
result: "0"
|
||||
|
||||
- name: Test a missing key in the list of dictionaries, fail on missing
|
||||
assert:
|
||||
that: "{{ item.test == item.result }}"
|
||||
loop:
|
||||
- test: "{{ complex.a.b.c.d|ansible.utils.index_of('eq', True, 'e2', fail_on_missing=True) }}"
|
||||
result: "0"
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', True, 'e2', fail_on_missing=True) }}"
|
||||
result: "0"
|
||||
ignore_errors: True
|
||||
- test: "{{ complex.a.b.c.d|ansible.utils.index_of('eq', True, 'e2', fail_on_missing=True) }}"
|
||||
result: "0"
|
||||
- test: "{{ lookup('ansible.utils.index_of', complex.a.b.c.d, 'eq', True, 'e2', fail_on_missing=True) }}"
|
||||
result: "0"
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- name: Ensure the previous test failed
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
---
|
||||
- name: Recursively find all test files
|
||||
find:
|
||||
file_type: file
|
||||
paths: "{{ role_path }}/tasks/include"
|
||||
recurse: yes
|
||||
use_regex: yes
|
||||
recurse: true
|
||||
use_regex: true
|
||||
patterns:
|
||||
- '^(?!_).+$'
|
||||
register: found
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
---
|
||||
- set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 0
|
||||
|
||||
- name: Check argspec validation with lookup
|
||||
set_fact:
|
||||
_result: "{{ a|ansible.utils.to_paths(wantlist=5) }}"
|
||||
ignore_errors: True
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- debug:
|
||||
|
@ -22,7 +23,7 @@
|
|||
- name: Check argspec validation with lookup
|
||||
set_fact:
|
||||
_result: "{{ lookup('ansible.utils.to_paths') }}"
|
||||
ignore_errors: True
|
||||
ignore_errors: true
|
||||
register: result
|
||||
|
||||
- debug:
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
#### Simple examples
|
||||
|
||||
- ansible.builtin.set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 1
|
||||
e:
|
||||
- True
|
||||
- False
|
||||
- true
|
||||
- false
|
||||
|
||||
- ansible.builtin.set_fact:
|
||||
paths: "{{ a|ansible.utils.to_paths }}"
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
---
|
||||
#### Simple examples
|
||||
|
||||
- ansible.builtin.set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 1
|
||||
e:
|
||||
- True
|
||||
- False
|
||||
- true
|
||||
- false
|
||||
|
||||
- ansible.builtin.set_fact:
|
||||
paths: "{{ lookup('ansible.utils.to_paths', a) }}"
|
||||
|
|
|
@ -1,52 +1,53 @@
|
|||
---
|
||||
- set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 1
|
||||
|
||||
- name: Test filter and lookup plugin, simple and prepend
|
||||
assert:
|
||||
that: "{{ item.result == item.expected }}"
|
||||
loop:
|
||||
- result: "{{ a|ansible.utils.to_paths }}"
|
||||
expected:
|
||||
b.c.d[0]: 0
|
||||
b.c.d[1]: 1
|
||||
- result: "{{ lookup('ansible.utils.to_paths', a) }}"
|
||||
expected:
|
||||
b.c.d[0]: 0
|
||||
b.c.d[1]: 1
|
||||
- result: "{{ a|ansible.utils.to_paths(prepend='a') }}"
|
||||
expected:
|
||||
a.b.c.d[0]: 0
|
||||
a.b.c.d[1]: 1
|
||||
- result: "{{ lookup('ansible.utils.to_paths', a, prepend='a') }}"
|
||||
expected:
|
||||
a.b.c.d[0]: 0
|
||||
a.b.c.d[1]: 1
|
||||
- result: "{{ a|ansible.utils.to_paths }}"
|
||||
expected:
|
||||
b.c.d[0]: 0
|
||||
b.c.d[1]: 1
|
||||
- result: "{{ lookup('ansible.utils.to_paths', a) }}"
|
||||
expected:
|
||||
b.c.d[0]: 0
|
||||
b.c.d[1]: 1
|
||||
- result: "{{ a|ansible.utils.to_paths(prepend='a') }}"
|
||||
expected:
|
||||
a.b.c.d[0]: 0
|
||||
a.b.c.d[1]: 1
|
||||
- result: "{{ lookup('ansible.utils.to_paths', a, prepend='a') }}"
|
||||
expected:
|
||||
a.b.c.d[0]: 0
|
||||
a.b.c.d[1]: 1
|
||||
|
||||
- set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
d:
|
||||
- 0
|
||||
|
||||
- 0
|
||||
|
||||
- name: Test filter and lookup plugin, wantlist and prepend
|
||||
assert:
|
||||
that: "{{ item.result == item.expected }}"
|
||||
loop:
|
||||
- result: "{{ a|ansible.utils.to_paths(wantlist=True) }}"
|
||||
expected:
|
||||
- b.c.d[0]: 0
|
||||
- result: "{{ lookup('ansible.utils.to_paths', a, wantlist=True) }}"
|
||||
expected:
|
||||
- b.c.d[0]: 0
|
||||
- result: "{{ a|ansible.utils.to_paths(wantlist=True, prepend='a') }}"
|
||||
expected:
|
||||
- a.b.c.d[0]: 0
|
||||
- result: "{{ lookup('ansible.utils.to_paths', a, wantlist=True, prepend='a') }}"
|
||||
expected:
|
||||
- a.b.c.d[0]: 0
|
||||
- result: "{{ a|ansible.utils.to_paths(wantlist=True) }}"
|
||||
expected:
|
||||
- b.c.d[0]: 0
|
||||
- result: "{{ lookup('ansible.utils.to_paths', a, wantlist=True) }}"
|
||||
expected:
|
||||
- b.c.d[0]: 0
|
||||
- result: "{{ a|ansible.utils.to_paths(wantlist=True, prepend='a') }}"
|
||||
expected:
|
||||
- a.b.c.d[0]: 0
|
||||
- result: "{{ lookup('ansible.utils.to_paths', a, wantlist=True, prepend='a') }}"
|
||||
expected:
|
||||
- a.b.c.d[0]: 0
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
---
|
||||
- name: Recursively find all test files
|
||||
find:
|
||||
file_type: file
|
||||
paths: "{{ role_path }}/tasks/include"
|
||||
recurse: yes
|
||||
use_regex: yes
|
||||
recurse: true
|
||||
use_regex: true
|
||||
patterns:
|
||||
- '^(?!_).+$'
|
||||
register: found
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
---
|
||||
- name: Set a fact
|
||||
set_fact:
|
||||
a:
|
||||
b:
|
||||
c:
|
||||
- 1
|
||||
- 2
|
||||
- 1
|
||||
- 2
|
||||
|
||||
- name: Update the fact
|
||||
ansible.utils.update_fact:
|
||||
updates:
|
||||
- path: a.b.c.0
|
||||
value: 10
|
||||
- path: "a['b']['c'][1]"
|
||||
value: 20
|
||||
- path: a.b.c.0
|
||||
value: 10
|
||||
- path: "a['b']['c'][1]"
|
||||
value: 20
|
||||
register: updated
|
||||
|
||||
- assert:
|
||||
|
@ -22,19 +23,19 @@
|
|||
a:
|
||||
b:
|
||||
c:
|
||||
- 10
|
||||
- 20
|
||||
- 10
|
||||
- 20
|
||||
|
||||
- name: Update the fact
|
||||
ansible.utils.update_fact:
|
||||
updates:
|
||||
- path: a
|
||||
value:
|
||||
x:
|
||||
y:
|
||||
z:
|
||||
- 100
|
||||
- True
|
||||
- path: a
|
||||
value:
|
||||
x:
|
||||
y:
|
||||
z:
|
||||
- 100
|
||||
- true
|
||||
register: updated
|
||||
|
||||
- assert:
|
||||
|
@ -45,14 +46,14 @@
|
|||
x:
|
||||
y:
|
||||
z:
|
||||
- 100
|
||||
- True
|
||||
- 100
|
||||
- true
|
||||
|
||||
- name: Update the fact
|
||||
ansible.utils.update_fact:
|
||||
updates:
|
||||
- path: "a.b.c[{{ index }}]"
|
||||
value: 20
|
||||
- path: "a.b.c[{{ index }}]"
|
||||
value: 20
|
||||
vars:
|
||||
index: "{{ a.b.c|ansible.utils.index_of('eq', 2) }}"
|
||||
register: updated
|
||||
|
@ -64,5 +65,5 @@
|
|||
a:
|
||||
b:
|
||||
c:
|
||||
- 1
|
||||
- 20
|
||||
- 1
|
||||
- 20
|
||||
|
|
|
@ -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
|
||||
|
||||
import json
|
||||
import heapq
|
||||
import os
|
||||
import unittest
|
||||
from ansible_collections.ansible.utils.plugins.module_utils.common.get_path import (
|
||||
get_path,
|
||||
|
|
|
@ -7,7 +7,6 @@ from __future__ import absolute_import, division, print_function
|
|||
|
||||
__metaclass__ = type
|
||||
|
||||
import copy
|
||||
import re
|
||||
import unittest
|
||||
from mock import MagicMock
|
||||
|
@ -43,10 +42,7 @@ class TestUpdate_Fact(unittest.TestCase):
|
|||
self._plugin._task.args = {"before": True}
|
||||
result = self._plugin.run(task_vars=self._task_vars)
|
||||
self.assertTrue(result["failed"])
|
||||
self.assertIn(
|
||||
"missing required arguments: after",
|
||||
result["msg"],
|
||||
)
|
||||
self.assertIn("missing required arguments: after", result["msg"])
|
||||
|
||||
def test_same(self):
|
||||
"""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}
|
||||
result = self._plugin.run(task_vars=self._task_vars)
|
||||
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))
|
||||
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))
|
||||
|
||||
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)
|
||||
self.assertTrue(result["failed"])
|
||||
self.assertIn(
|
||||
"Error loading plugin 'a.b.c'",
|
||||
result["msg"],
|
||||
)
|
||||
self.assertIn("Error loading plugin 'a.b.c'", result["msg"])
|
||||
|
||||
def test_invalid_regex(self):
|
||||
"""Check with invalid regex"""
|
||||
|
@ -195,10 +194,7 @@ class TestUpdate_Fact(unittest.TestCase):
|
|||
}
|
||||
result = self._plugin.run(task_vars=self._task_vars)
|
||||
self.assertTrue(result["failed"])
|
||||
self.assertIn(
|
||||
"The regex '+', is not valid",
|
||||
result["msg"],
|
||||
)
|
||||
self.assertIn("The regex '+', is not valid", result["msg"])
|
||||
|
||||
def test_fail_plugin(self):
|
||||
"""Simulate a diff plugin failure"""
|
||||
|
|
|
@ -97,8 +97,7 @@ class TestUpdate_Fact(unittest.TestCase):
|
|||
with self.assertRaises(Exception) as error:
|
||||
self._plugin.run(task_vars=None)
|
||||
self.assertIn(
|
||||
"missing required arguments: updates",
|
||||
str(error.exception),
|
||||
"missing required arguments: updates", str(error.exception)
|
||||
)
|
||||
|
||||
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