-
Notifications
You must be signed in to change notification settings - Fork 100
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Runtime JSON Schema validation of settings file (#1099)
* Runtime json scheam validation * Move jsonschema dep to requirements * Test updates * Changelog * Replay empty * Delete ansible-navigator.yml * Test fix Co-authored-by: Ganesh Nalawade <ganesh634@gmail.com>
- Loading branch information
Showing
24 changed files
with
284 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Added settings file validation against the settings file JSON Schema during application | ||
initialization. | ||
|
||
-- by {user}`cidrblock` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
"""Functionality to perform json schema validation.""" | ||
|
||
import json | ||
|
||
from dataclasses import dataclass | ||
from typing import Any | ||
from typing import Deque | ||
from typing import Dict | ||
from typing import List | ||
from typing import Union | ||
|
||
from jsonschema import SchemaError | ||
from jsonschema import ValidationError | ||
from jsonschema.validators import validator_for | ||
|
||
from .functions import ExitMessage | ||
|
||
|
||
def to_path(schema_path: Deque[str]): | ||
"""Flatten a path to a dot delimited string. | ||
:param schema_path: The schema path | ||
:returns: The dot delimited path | ||
""" | ||
return ".".join(str(index) for index in schema_path) | ||
|
||
|
||
def json_path(absolute_path: Deque[str]): | ||
"""Flatten a data path to a dot delimited string. | ||
:param absolute_path: The path | ||
:returns: The dot delimited string | ||
""" | ||
path = "$" | ||
for elem in absolute_path: | ||
if isinstance(elem, int): | ||
path += "[" + str(elem) + "]" | ||
else: | ||
path += "." + elem | ||
return path | ||
|
||
|
||
@dataclass | ||
class JsonSchemaError: | ||
# pylint: disable=too-many-instance-attributes | ||
"""Data structure to hold a json schema validation error.""" | ||
|
||
message: str | ||
data_path: str | ||
json_path: str | ||
schema_path: str | ||
relative_schema: str | ||
expected: Union[bool, int, str] | ||
validator: str | ||
found: str | ||
|
||
def to_friendly(self): | ||
"""Provide a friendly explanation of the error. | ||
:returns: The error message | ||
""" | ||
return f"In '{self.data_path}': {self.message}." | ||
|
||
def to_exit_message(self): | ||
"""Provide an exit message for a schema validation failure. | ||
:returns: The exit message | ||
""" | ||
return ExitMessage(message=self.to_friendly()) | ||
|
||
|
||
def validate(schema: Union[str, Dict[str, Any]], data: Dict[str, Any]) -> List[JsonSchemaError]: | ||
"""Validate some data against a JSON schema. | ||
:param schema: the JSON schema to use for validation | ||
:param data: The data to validate | ||
:returns: Any errors encountered | ||
""" | ||
errors: List[JsonSchemaError] = [] | ||
|
||
if isinstance(schema, str): | ||
schema = json.loads(schema) | ||
validator = validator_for(schema) | ||
try: | ||
validator.check_schema(schema) | ||
except SchemaError as exc: | ||
error = JsonSchemaError( | ||
message=str(exc), | ||
data_path="schema sanity check", | ||
json_path="", | ||
schema_path="", | ||
relative_schema="", | ||
expected="", | ||
validator="", | ||
found="", | ||
) | ||
errors.append(error) | ||
return errors | ||
|
||
validation_errors = sorted(validator(schema).iter_errors(data), key=lambda e: e.path) | ||
|
||
if not validation_errors: | ||
return errors | ||
|
||
for validation_error in validation_errors: | ||
if isinstance(validation_error, ValidationError): | ||
error = JsonSchemaError( | ||
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, | ||
) | ||
errors.append(error) | ||
return errors |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,6 @@ ansible-core | |
darglint | ||
flake8-docstrings | ||
flake8-quotes | ||
jsonschema | ||
libtmux | ||
lxml | ||
pre-commit | ||
|
3 changes: 1 addition & 2 deletions
3
tests/fixtures/integration/actions/replay/ansible-navigator.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
--- | ||
ansible-navigator: | ||
empty: True | ||
ansible-navigator: {} |
3 changes: 1 addition & 2 deletions
3
tests/fixtures/integration/execution_environment/ansible-navigator_empty.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
--- | ||
ansible-navigator: | ||
empty: True | ||
ansible-navigator: {} |
3 changes: 1 addition & 2 deletions
3
tests/fixtures/integration/execution_environment_image/ansible-navigator_empty.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
--- | ||
ansible-navigator: | ||
empty: True | ||
ansible-navigator: {} |
3 changes: 1 addition & 2 deletions
3
tests/fixtures/integration/pass_environment_variable/ansible-navigator_empty.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
--- | ||
ansible-navigator: | ||
empty: True | ||
ansible-navigator: {} |
3 changes: 1 addition & 2 deletions
3
tests/fixtures/integration/set_environment_variable/ansible-navigator_empty.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
--- | ||
ansible-navigator: | ||
empty: True | ||
ansible-navigator: {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,20 @@ | ||
--- | ||
ansible-navigator: | ||
documentation: | ||
plugin: | ||
type: become | ||
editor: | ||
command: emacs -nw +{line_number} {filename} | ||
console: False | ||
doc-plugin-type: become | ||
execution-environment: | ||
container-engine: podman | ||
enable: False | ||
enabled: False | ||
image: quay.io/ansible/creator-ee:v0.2.0 | ||
inventory-columns: | ||
- ansible_network_os | ||
- ansible_network_cli_ssh_type | ||
- ansible_connection | ||
osc4: True | ||
color: | ||
osc4: True | ||
logging: | ||
level: critical |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
--- | ||
ansible-navigator: | ||
empty: True | ||
ansible-navigator: {} |
3 changes: 1 addition & 2 deletions
3
tests/fixtures/unit/configuration_subsystem/ansible-navigator_empty.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,2 @@ | ||
--- | ||
ansible-navigator: | ||
empty: True | ||
ansible-navigator: {} |
3 changes: 3 additions & 0 deletions
3
tests/fixtures/unit/configuration_subsystem/ansible-navigator_unknown_key.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
--- | ||
ansible-navigator: | ||
unknown: key |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Tests various scenarios from the CLI.""" |
99 changes: 99 additions & 0 deletions
99
tests/integration/settings_from_cli/test_json_schema_errors.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
"""Check exit messages for json schema validation.""" | ||
import os | ||
import subprocess | ||
|
||
from dataclasses import dataclass | ||
from pathlib import Path | ||
from typing import Any | ||
from typing import Tuple | ||
|
||
import pytest | ||
|
||
from ansible_navigator.utils.functions import shlex_join | ||
from ...defaults import FIXTURES_DIR | ||
|
||
|
||
TEST_FIXTURE_DIR = Path(FIXTURES_DIR) / "unit" / "configuration_subsystem" | ||
|
||
|
||
@dataclass | ||
class Scenario: | ||
"""Data for the tests.""" | ||
|
||
comment: str | ||
"""The comment for the test""" | ||
settings_file: Path | ||
"""The settings file path""" | ||
messages: Tuple[str, ...] | ||
"""Messages expected to be found""" | ||
|
||
command: Tuple[str, ...] = ("ansible-navigator", "-m", "stdout") | ||
"""The command to run""" | ||
|
||
def __str__(self): | ||
"""Provide a test id. | ||
:returns: The test id | ||
""" | ||
return self.comment | ||
|
||
|
||
test_data = ( | ||
Scenario( | ||
comment="Empty settings file", | ||
messages=("Settings file cannot be empty",), | ||
settings_file=TEST_FIXTURE_DIR / "ansible-navigator_broken.yml", | ||
), | ||
Scenario( | ||
comment="Unrecognized key", | ||
messages=("'unknown' was unexpected",), | ||
settings_file=TEST_FIXTURE_DIR / "ansible-navigator_unknown_key.yml", | ||
), | ||
Scenario( | ||
comment="Unrecognized app", | ||
messages=("'non_app' is not one of ['builder',",), | ||
settings_file=TEST_FIXTURE_DIR / "ansible-navigator_no_app.yml", | ||
), | ||
Scenario( | ||
comment="EE enabled is not a bool", | ||
messages=("5 is not one of [True, False]",), | ||
settings_file=TEST_FIXTURE_DIR / "ansible-navigator_not_bool.yml", | ||
), | ||
) | ||
|
||
|
||
@pytest.mark.parametrize("data", test_data, ids=str) | ||
def test(data: Scenario, subtests: Any, tmp_path: Path): | ||
"""Test for json schema errors. | ||
:param data: The test data | ||
:param tmp_path: The temporary path fixture | ||
:param subtests: The pytest subtest fixture | ||
:raises AssertionError: When tests fails | ||
""" | ||
assert data.settings_file.exists() | ||
venv_path = os.environ.get("VIRTUAL_ENV") | ||
if venv_path is None: | ||
raise AssertionError( | ||
"VIRTUAL_ENV environment variable was not set but tox should have set it.", | ||
) | ||
venv = Path(venv_path, "bin", "activate") | ||
log_file = tmp_path / "log.txt" | ||
|
||
command = list(data.command) + ["--lf", str(log_file)] | ||
|
||
bash_wrapped = f"/bin/bash -c 'source {venv!s} && {shlex_join(command)}'" | ||
env = {"ANSIBLE_NAVIGATOR_CONFIG": str(data.settings_file), "NO_COLOR": "true"} | ||
proc_out = subprocess.run( | ||
bash_wrapped, | ||
check=False, | ||
env=env, | ||
shell=True, | ||
stderr=subprocess.PIPE, | ||
stdout=subprocess.PIPE, | ||
universal_newlines=True, | ||
) | ||
for value in data.messages: | ||
with subtests.test(msg=value, value=value): | ||
assert value in proc_out.stdout |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.