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

refactor(RHINENG-5484): refactor operating_system filter #2141

Open
wants to merge 14 commits into
base: master
Choose a base branch
from

Conversation

FabriciaDinizRH
Copy link
Contributor

@FabriciaDinizRH FabriciaDinizRH commented Dec 18, 2024

Overview

This PR is being created to address RHINENG-5484.

PR Checklist

  • Keep PR title short, ideally under 72 characters
  • Descriptive comments provided in complex code blocks
  • Include raw query examples in the PR description, if adding/modifying SQL query
  • Tests: validate optimal/expected output
  • Tests: validate exceptions and failure scenarios
  • Tests: edge cases
  • Recovers or fails gracefully during potential resource outages (e.g. DB, Kafka)
  • Uses type hinting, if convenient
  • Documentation, if this PR changes the way other services interact with host inventory
  • Links to related PRs

Secure Coding Practices Documentation Reference

You can find documentation on this checklist here.

Secure Coding Checklist

  • Input Validation
  • Output Encoding
  • Authentication and Password Management
  • Session Management
  • Access Control
  • Cryptographic Practices
  • Error Handling and Logging
  • Data Protection
  • Communication Security
  • System Configuration
  • Database Security
  • File Management
  • Memory Management
  • General Coding Practices

@FabriciaDinizRH
Copy link
Contributor Author

I added some commits I was not supposed to, I'm fixing it

Comment on lines 119 to 120
for os_name in filter_param.keys(): # this doesn't account for "os_name" instead of os names
if os_name not in (os_names := _get_valid_os_names()):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd recommend checking for the key "name" between these 2 lines and doing the custom logic there:

Suggested change
for os_name in filter_param.keys(): # this doesn't account for "os_name" instead of os names
if os_name not in (os_names := _get_valid_os_names()):
for os_name in filter_param.keys():
if os_name == "name":
# Return an OsComparison object that implies all hosts with a matching OS name.
# Maybe if OsComparison.comparator is "eq" or "neq" and the major version is None?
elif os_name not in (os_names := _get_valid_os_names()):

@@ -158,6 +169,7 @@ def build_operating_system_filter(filter_param: dict) -> tuple:
os_filter_list.append(os_field.astext.operate(comparator, None))

elif comparison.comparator == "eq":
print("~~~~~~~~~~~~~~~~~~~``", os_field["name"].astext == comparison.name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Around here is where you'd convert the OsComparison to an actual DB operation. If we went with the logic I posted above, this next section could be something like this:

Suggested change
print("~~~~~~~~~~~~~~~~~~~``", os_field["name"].astext == comparison.name)
os_filters = [
os_field["name"].astext == comparison.name,
]
if comparison.major is not None:
os_filters.append(os_field["major"].astext.cast(Integer) == comparison.major)
if comparison.minor:
os_filters.append(os_field["minor"].astext.cast(Integer) == comparison.minor)
os_filter_list.append(and_(*os_filters))

Of course, you'll need to handle "neq" as well :)

@FabriciaDinizRH FabriciaDinizRH requested a review from kruai January 6, 2025 20:43
@FabriciaDinizRH FabriciaDinizRH marked this pull request as ready for review January 6, 2025 20:44
@FabriciaDinizRH FabriciaDinizRH requested a review from a team as a code owner January 6, 2025 20:44
Copy link
Collaborator

@kruai kruai left a comment

Choose a reason for hiding this comment

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

Mostly looking good, got a few suggestions. Also, reminder that we'll need to update the documentation as well so people know how to use the filter in this way.

# case insensitive
if real_os_name.lower() not in (os_names := [name.lower() for name in _get_valid_os_names()]):
raise ValidationException(f"operating_system filter only supports these OS names: {os_names}.")
return [OsComparison(real_os_name, comparator, None)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Adding a return statement here will make it so other provided OS filters are ignored. For instance, if the request happens to be ?filter[system_profile][operating_system][name][eq]=RHEL&filter[system_profile][operating_system][RHEL][version][gte]=8, it will no longer filter on the major version. Instead, the comparison should be appended to os_filter_list.

Copy link
Contributor Author

@FabriciaDinizRH FabriciaDinizRH Jan 8, 2025

Choose a reason for hiding this comment

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

@kruai I guess I need to adjust the logic a little bit or maybe ?filter[system_profile][operating_system][name][eq]=RHEL&filter[system_profile][operating_system][RHEL][version][gte]=8 doesn't make sense. I'm saying this because the query came out like this:

AND (lower(((hbi.hosts.system_profile_facts["operating_system"]) ->> "name")) = "rhel"
       AND CAST(((hbi.hosts.system_profile_facts["operating_system"]) ->> "major") AS INTEGER) = "8"
       OR lower(((hbi.hosts.system_profile_facts["operating_system"]) ->> "name")) = "rhel")

If someone is looking for RHEL, and they want to specify a version, it's unnecessary to add filter[system_profile][operating_system][name][eq]=RHEL

this is with the return statement as it is. If I just append to the filter list the request returns 500

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, it's for sure redundant and they shouldn't provide a query like that. But if they do, we should be able to handle it imo.
HTTP 500 just means there was an unhandled exception on our end; what does the exception say in the logs?

Copy link
Contributor Author

@FabriciaDinizRH FabriciaDinizRH Jan 8, 2025

Choose a reason for hiding this comment

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

File "/home/fabriciadiniz/dev/insights-host-inventory/api/filtering/db_custom_filters.py", line 131, in separate_operating_system_filters
    if not isinstance(version_node := filter_param[os_name]["version"], dict):
KeyError: 'version'

Copy link
Collaborator

Choose a reason for hiding this comment

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

Makes sense - once you add the filter to the list, you don't want to run the rest of the code that generates the usual OS filter. You could just put the rest in an else, but I feel like we can also simplify it a bit. I'll play around with it a bit

Copy link
Collaborator

Choose a reason for hiding this comment

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

@FabriciaDinizRH Suggestion for the code flow: FabriciaDinizRH#1
(Feel free to reject it or tweak as necessary ofc)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you!

Comment on lines 125 to 126
if real_os_name.lower() not in (os_names := [name.lower() for name in _get_valid_os_names()]):
raise ValidationException(f"operating_system filter only supports these OS names: {os_names}.")
Copy link
Collaborator

Choose a reason for hiding this comment

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

DRY with the code directly below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

for this I can either use pass instead of raising the validation in the first if and it will be caught in the second if (because "name" is not a supported OS name), or I can extract the raise ValidationException into a function and call it in both ifs. wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

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

If using raise..., should there be a test asserting for this exception?

@@ -117,7 +118,15 @@ def separate_operating_system_filters(filter_param) -> list[OsComparison]:

# filter_param is a dict
for os_name in filter_param.keys():
if os_name not in (os_names := _get_valid_os_names()):
# [operating_system][name][eq]==real_os_name
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# [operating_system][name][eq]==real_os_name

Comment on lines +1401 to +1406
("[name][eq]=CentOS", True),
("[name][eq]=centos", True),
("[name][eq]=CENTOS", True),
("[name][eq]=centos&filter[system_profile][operating_system][RHEL][version][eq][]=8", True),
("[name][neq]=CENTOS", False),
("[name][neq]=CentOS", False),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This could use another test to validate the scenario I mentioned above. For example, ?filter[system_profile][operating_system][name][eq]=RHEL&filter[system_profile][operating_system][RHEL][version][eq]=9 should not match either of the host data in this test

@FabriciaDinizRH FabriciaDinizRH requested a review from kruai January 8, 2025 13:42
@@ -117,7 +118,15 @@ def separate_operating_system_filters(filter_param) -> list[OsComparison]:

# filter_param is a dict
for os_name in filter_param.keys():
if os_name not in (os_names := _get_valid_os_names()):
# [operating_system][name][eq]==real_os_name
if os_name == "name":
Copy link
Contributor

Choose a reason for hiding this comment

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

rename the variable os_name to key, which is what is being checked. A name like os_name causes confusion when a key is being checked NOT its value.

Comment on lines 125 to 126
if real_os_name.lower() not in (os_names := [name.lower() for name in _get_valid_os_names()]):
raise ValidationException(f"operating_system filter only supports these OS names: {os_names}.")
Copy link
Contributor

Choose a reason for hiding this comment

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

If using raise..., should there be a test asserting for this exception?

kruai
kruai previously approved these changes Jan 9, 2025
@kruai
Copy link
Collaborator

kruai commented Jan 9, 2025

/retest

Copy link
Contributor

@thearifismail thearifismail left a comment

Choose a reason for hiding this comment

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

LGTM

@FabriciaDinizRH FabriciaDinizRH requested a review from kruai January 13, 2025 11:42
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.

3 participants