Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Business logic validator plugin implementation #84

Merged
merged 42 commits into from
Apr 5, 2021
Merged

Business logic validator plugin implementation #84

merged 42 commits into from
Apr 5, 2021

Conversation

chipn
Copy link
Contributor

@chipn chipn commented Feb 8, 2021

Resolves #24

@itdependsnetworks
Copy link
Contributor

Can you update the docs on how to use it?

@chipn
Copy link
Contributor Author

chipn commented Feb 8, 2021

Can you update the docs on how to use it?

I figured it was worth waiting for agreement on the approach before adding docs, just in case we decide not to use this particular implementation.

@itdependsnetworks
Copy link
Contributor

itdependsnetworks commented Feb 8, 2021

Just enough for me to understand, I don't actually know how it is working.

schema_enforcer/config.py Outdated Show resolved Hide resolved
schema_enforcer/schemas/manager.py Outdated Show resolved Hide resolved
schema_enforcer/schemas/manager.py Outdated Show resolved Hide resolved
schema_enforcer/schemas/validator.py Outdated Show resolved Hide resolved
schema_enforcer/schemas/validator.py Outdated Show resolved Hide resolved
peer_match = data[peer]["interfaces"][peer_int]["peer"] == normal_hostname(host)
peer_int_match = data[peer]["interfaces"][peer_int]["peer_int"] == interface
if not (peer_match and peer_int_match):
raise ValidationError
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we should return a list of ValidationResult here instead of expecting an exception to be thrown. It allows the validators to flag more than 1 error at a time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking out loud, would be useful to provide something in the ModelValidation class to generate/store ValidationResult easily.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't updated the check_peer examples to return ValidationResults since cross-host correlation isn't currently supported. The check_interface example is what is supported and it returns a ValidationResult.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of helper functions for generating/storing ValidationResult. I'll have to think about a good way to implement that.

@dgarros
Copy link
Contributor

dgarros commented Feb 8, 2021

@chipn I forgot to mention it but it's great to see so much progress already and it's exciting to add this capabilities to the schema-enforcer 👍
Impressive work so far

@itdependsnetworks
Copy link
Contributor

Yes, agreed!! Really great work with little guidance

docs/custom_validators.md Outdated Show resolved Hide resolved
id = "CheckInterface"
left = "interfaces.*[@.type=='core'][] | length([?@])"
right = 2
operator = "eq"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to support specifying left/right/operator as a single combined string, such as "interfaces.*[@.type=='core'][] | length([?@]) eq 2"? That seems like it'd be more user-friendly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to support this format, but I couldn't think of a good way to do so since jmespath expressions can be complex and contain spaces/pipes. Any thoughts on how to implement it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably fine to defer to a later PR, but off the top of my head can we regex around the supported operator strings (plus surrounding whitespace)? Something like r"(.*) (eq|lt|gt|lte|gte|ne) (.*)"? Or can these operators potentially be used within left/right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that is likely a valid use case. The optimization doesn’t seem like too much to me, would prefer to do something similar in a json/yaml file outside of python if we are going to spend efforts on it.

docs/custom_validators.md Show resolved Hide resolved
schema_enforcer/schemas/validator.py Outdated Show resolved Hide resolved
"eq": lambda r, v: r == v,
"lt": lambda r, v: int(r) < int(v),
"lte": lambda r, v: int(r) <= int(v),
"contains": lambda r, v: v in r,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we support the actual operators (">", ">=", etc.) as well? Or is that not useful/desirable in a jmespath context?

Copy link
Contributor Author

@chipn chipn Feb 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can support the actual operators. However, I wonder if that may be confusing. "gt" is obviously a string, but someone may try to use > (unquoted). Easy to correct by adding quotes, but maybe not always obvious?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah agreed; I think we'd only want to do this if we're able to combine the "left", "right", and "operator" parameters into a single string as discussed above.

schema_enforcer/schemas/validator.py Outdated Show resolved Hide resolved
tests/fixtures/test_validators/validators/check_peers.py Outdated Show resolved Hide resolved
tests/fixtures/test_validators/validators/check_peers.py Outdated Show resolved Hide resolved
Copy link
Contributor

@glennmatthews glennmatthews left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great to me!

examples/ansible3/validators/check_interfaces.py Outdated Show resolved Hide resolved
docs/custom_validators.md Show resolved Hide resolved
tests/test_schemas_validator.py Outdated Show resolved Hide resolved
tests/test_schemas_validator.py Show resolved Hide resolved
tests/test_schemas_validator.py Outdated Show resolved Hide resolved
@@ -43,6 +44,11 @@ def __init__(self, config):
schema = self.create_schema_from_file(root, filename)
self.schemas[schema.get_id()] = schema

# Load validators
full_validator_dir = f"{config.validator_directory}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should remove the fstring since it's not used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't our tests catching this?

schema_enforcer/schemas/validator.py Outdated Show resolved Hide resolved
Copy link
Contributor

@dgarros dgarros left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Chip, looks great

schema_enforcer/schemas/validator.py Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved

* `top_level_properties`: Field for mapping of validator to data
* `id`: Schema ID to use for reporting purposes (optional - defaults to class name)
* `left`: Jmespath expression to query your host data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am the one who indicated left/right, but I did so out of lack of imagination. Just want to make sure we are all good with the name here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is lhs (left-hand side) and rhs (right-hand side).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left / right sounds good to me

tests/fixtures/test_validators/validators/check_peers.py Outdated Show resolved Hide resolved
schema_enforcer/schemas/validator.py Outdated Show resolved Hide resolved
schema_enforcer/schemas/validator.py Outdated Show resolved Hide resolved
@@ -303,8 +303,9 @@ def ansible(
data = hostvars

# Validate host vars against schema
for result in schema_obj.validate(data=data, strict=strict):
schema_obj.validate(data=data, strict=strict)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can you make the same changes to the validate command , otherwise validators won't work for this command

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validate CLI command calls validate on the InstanceFile object. The InstanceFile validate method was refactored for the new approach:

for schema_id, schema in schema_manager.iter_schemas():
if schema_id not in self.matches:
continue
schema.validate(self.get_content(), strict)
results = schema.get_results()
errs = itertools.chain(errs, results)
schema.clear_results()


* `top_level_properties`: Field for mapping of validator to data
* `id`: Schema ID to use for reporting purposes (optional - defaults to class name)
* `left`: Jmespath expression to query your host data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left / right sounds good to me

Copy link
Contributor

@PhillSimonds PhillSimonds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! LGTM. A few comments in line.

@@ -0,0 +1,158 @@
# Implementing custom validators

With custom validators, you can implement business logic in Python. Schema-enforcer will automatically
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An intro of what's meant by "business logic" might be valuable here to set proper context?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pinging myself to review


class CheckInterface(JmesPathModelValidation): # pylint: disable=too-few-public-methods
"""Check that each device has more than one core uplink."""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think pydocstyle requires no newline here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No newline after the comment? I vaguely remember that pydocstyle complained when it isn't in a single line or you miss the period at the end. This validator is also in the test directory and pydocstyle is passing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack. That's interesting -- I've seen pydocstyle flag when a function has space between the docstring and the beginning of the logic executed by the function. It may just be on multiline docstrings though.. IDK. If pydocstyle isn't complaining I'm good :).

def get_results(self) -> list[ValidationResult]:
"""Return all validation results for this validator."""
if not self._results:
self._results.append(ValidationResult(result="PASS", schema_id=self.id))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat adjacent to #46, If there are no validation results, do we want to print a log message indicating that nothing was validated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May defer to @dgarros here. I assumed the intent was to support the use case of no failure is equal to a pass, but that doesn't really fit the current runtime model.

@chipn chipn changed the title [WIP] Business logic validator plugin implementation Business logic validator plugin implementation Mar 18, 2021
@dgarros dgarros merged commit d165177 into develop Apr 5, 2021
@dgarros dgarros deleted the plugin branch December 21, 2021 19:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Business Logic Validation
5 participants