Skip to content

Commit

Permalink
Fix hashing
Browse files Browse the repository at this point in the history
  • Loading branch information
AlanCoding committed Jan 29, 2025
1 parent 489cfcd commit 6d3aed1
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 12 deletions.
26 changes: 14 additions & 12 deletions awx/main/tasks/host_indirect.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,35 +18,37 @@ class UnhashableFacts(RuntimeError):
pass


def get_hashable_form(python_dict: Union[dict, list, int, float, str, bool]) -> Tuple[Union[Tuple, dict, int, float]]:
def get_hashable_form(input_data: Union[dict, list, int, float, str, bool]) -> Tuple[Union[Tuple, dict, int, float]]:
"Given a dictionary of JSON types, return something that can be hashed and is the same data"
if isinstance(python_dict, (int, float, str, bool)):
return python_dict # return scalars as-is
if isinstance(python_dict, dict):
# Can't hash? Make it a tuple. Can't hash the tuples in the tuple? We'll make tuples out of them too.
return tuple(sorted(((get_hashable_form(k), get_hashable_form(v)) for k, v in python_dict.items())))
elif isinstance(python_dict, (list, tuple)):
return tuple(python_dict)
raise UnhashableFacts(f'Cannonical facts contains a {type(python_dict)} type which can not be hashed.')
if isinstance(input_data, (int, float, str, bool)):
return input_data # return scalars as-is

Check warning on line 24 in awx/main/tasks/host_indirect.py

View check run for this annotation

Codecov / codecov/patch

awx/main/tasks/host_indirect.py#L24

Added line #L24 was not covered by tests
if isinstance(input_data, dict):
# Can't hash because we got a dict? Make the dict a tuple of tuples.
# Can't hash the data in the tuple in the tuple? We'll make tuples out of them too.
return tuple(sorted(((get_hashable_form(k), get_hashable_form(v)) for k, v in input_data.items())))

Check warning on line 28 in awx/main/tasks/host_indirect.py

View check run for this annotation

Codecov / codecov/patch

awx/main/tasks/host_indirect.py#L28

Added line #L28 was not covered by tests
elif isinstance(input_data, (list, tuple)):
# Nested list data might not be hashable, and lists were never hashable in the first place
return tuple(get_hashable_form(item) for item in input_data)
raise UnhashableFacts(f'Cannonical facts contains a {type(input_data)} type which can not be hashed.')

Check warning on line 32 in awx/main/tasks/host_indirect.py

View check run for this annotation

Codecov / codecov/patch

awx/main/tasks/host_indirect.py#L31-L32

Added lines #L31 - L32 were not covered by tests


def build_indirect_host_data(job, job_event_queries: dict[str, str]) -> list[IndirectManagedNodeAudit]:
results = {}

Check warning on line 36 in awx/main/tasks/host_indirect.py

View check run for this annotation

Codecov / codecov/patch

awx/main/tasks/host_indirect.py#L36

Added line #L36 was not covered by tests
compiled_jq_expressions = {} # Cache for compiled jq expressions
facts_missing_logged = False
unhashable_facts_logged = False

Check warning on line 39 in awx/main/tasks/host_indirect.py

View check run for this annotation

Codecov / codecov/patch

awx/main/tasks/host_indirect.py#L39

Added line #L39 was not covered by tests
print(f'using event queries {job_event_queries}')
for event in job.job_events.filter(task__in=job_event_queries.keys()).iterator():
print(f'inspecting event {event}')
if 'res' not in event.event_data:
continue

# Recall from cache, or process the jq expression, and loop over the jq results
jq_str_for_event = job_event_queries[event.task]
if jq_str_for_event not in compiled_jq_expressions:
compiled_jq_expressions[event.task] = jq.compile(jq_str_for_event)
compiled_jq = compiled_jq_expressions[event.task]
for data in compiled_jq.input(event.event_data['res']).all():

# From the JQ result, get index information about this record
# From this jq result (specific to a single Ansible module), get index information about this host record
if not data.get('canonical_facts'):
if not facts_missing_logged:
logger.error(f'jq output missing canonical_facts for module {event.task} on event {event.id} using jq:{jq_str_for_event}')
Expand Down
10 changes: 10 additions & 0 deletions awx/main/tests/functional/tasks/test_host_indirect.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def test_nested_lists_different(self):
def test_nested_lists_same(self):
assert get_hashable_form({'a': ['b', 'c']}) == get_hashable_form({'a': ['b', 'c']})
assert get_hashable_form({'a': ('b', 'c')}) == get_hashable_form({'a': ('b', 'c')})
assert hash(get_hashable_form({'a': ['b', 'c']})) == hash(get_hashable_form({'a': ['b', 'c']}))

Check warning on line 182 in awx/main/tests/functional/tasks/test_host_indirect.py

View check run for this annotation

Codecov / codecov/patch

awx/main/tests/functional/tasks/test_host_indirect.py#L180-L182

Added lines #L180 - L182 were not covered by tests

def test_list_nested_lists_different(self):
assert get_hashable_form(['a', ['b', 'c']]) != get_hashable_form(['a', ['b', 'd']])
Expand All @@ -187,3 +188,12 @@ def test_list_nested_lists_different(self):
def test_list_nested_lists_same(self):
assert get_hashable_form(['a', ['b', 'c']]) == get_hashable_form(['a', ['b', 'c']])
assert get_hashable_form(['a', ('b', 'c')]) == get_hashable_form(['a', ('b', 'c')])
assert hash(get_hashable_form(['a', ('b', 'c')])) == hash(get_hashable_form(['a', ('b', 'c')]))

Check warning on line 191 in awx/main/tests/functional/tasks/test_host_indirect.py

View check run for this annotation

Codecov / codecov/patch

awx/main/tests/functional/tasks/test_host_indirect.py#L189-L191

Added lines #L189 - L191 were not covered by tests

def test_list_nested_dicts_different(self):
assert get_hashable_form(['a', {'b': 'c'}]) != get_hashable_form(['a', {'b': 'd'}])
assert hash(get_hashable_form(['a', {'b': 'c'}])) != hash(get_hashable_form(['a', {'b': 'd'}]))

Check warning on line 195 in awx/main/tests/functional/tasks/test_host_indirect.py

View check run for this annotation

Codecov / codecov/patch

awx/main/tests/functional/tasks/test_host_indirect.py#L194-L195

Added lines #L194 - L195 were not covered by tests

def test_list_nested_dicts_same(self):
assert get_hashable_form(['a', {'b': 'c'}]) == get_hashable_form(['a', {'b': 'c'}])
assert hash(get_hashable_form(['a', {'b': 'c'}])) == hash(get_hashable_form(['a', {'b': 'c'}]))

Check warning on line 199 in awx/main/tests/functional/tasks/test_host_indirect.py

View check run for this annotation

Codecov / codecov/patch

awx/main/tests/functional/tasks/test_host_indirect.py#L198-L199

Added lines #L198 - L199 were not covered by tests

0 comments on commit 6d3aed1

Please sign in to comment.