|
| 1 | +# Temporary diff helpers for ansible-freeipa modules. |
| 2 | +# Remove once https://github.com/freeipa/ansible-freeipa/pull/1415 |
| 3 | +# is merged and released. |
| 4 | + |
| 5 | +from __future__ import (absolute_import, division, print_function) |
| 6 | + |
| 7 | +__metaclass__ = type |
| 8 | + |
| 9 | +from ansible.module_utils._text import to_text |
| 10 | + |
| 11 | + |
| 12 | +def _compare_key(arg, ipa_arg): |
| 13 | + """Compare a single key's value using compare_args_ipa logic.""" |
| 14 | + if isinstance(ipa_arg, (list, tuple)): |
| 15 | + if not isinstance(arg, list): |
| 16 | + arg = [arg] |
| 17 | + if len(ipa_arg) != len(arg): |
| 18 | + return False |
| 19 | + if ipa_arg and arg and not ( |
| 20 | + isinstance(ipa_arg[0], type(arg[0])) |
| 21 | + or isinstance(arg[0], type(ipa_arg[0])) |
| 22 | + ): |
| 23 | + arg = [to_text(_a) for _a in arg] |
| 24 | + try: |
| 25 | + return set(arg) == set(ipa_arg) |
| 26 | + except TypeError: |
| 27 | + return arg == ipa_arg |
| 28 | + return arg == ipa_arg |
| 29 | + |
| 30 | + |
| 31 | +class IPADiffTracker: |
| 32 | + """Track before/after state for Ansible --diff output.""" |
| 33 | + |
| 34 | + def __init__(self): |
| 35 | + self._diffs = [] |
| 36 | + |
| 37 | + def build_diff(self): |
| 38 | + """Return kwargs for exit_json (empty dict if no changes).""" |
| 39 | + if not self._diffs: |
| 40 | + return {} |
| 41 | + return {"diff": self._diffs} |
| 42 | + |
| 43 | + def add_entry_diff(self, name, before, after): |
| 44 | + """Record a diff entry for one IPA object.""" |
| 45 | + if before == after: |
| 46 | + return |
| 47 | + self._diffs.append({ |
| 48 | + "before_header": name, |
| 49 | + "after_header": name, |
| 50 | + "before": before, |
| 51 | + "after": after, |
| 52 | + }) |
| 53 | + |
| 54 | + |
| 55 | +def gen_args_diff(args, res_find, ignore=None): |
| 56 | + """Extract only changed keys from args vs res_find for diff output. |
| 57 | +
|
| 58 | + Returns (before_dict, after_dict) containing only keys that differ. |
| 59 | + Uses the same comparison logic as compare_args_ipa for consistency. |
| 60 | + Single-element IPA lists are normalized to scalars for readability. |
| 61 | + """ |
| 62 | + if not args: |
| 63 | + return {}, {} |
| 64 | + before = {} |
| 65 | + after = {} |
| 66 | + if ignore is None: |
| 67 | + ignore = [] |
| 68 | + for key in args: |
| 69 | + if key in ignore: |
| 70 | + continue |
| 71 | + arg = args[key] |
| 72 | + ipa_arg = res_find.get(key, [""]) |
| 73 | + if not _compare_key(arg, ipa_arg): |
| 74 | + # Normalize for display |
| 75 | + _ipa = ipa_arg[0] if isinstance(ipa_arg, (list, tuple)) \ |
| 76 | + and len(ipa_arg) == 1 else ipa_arg |
| 77 | + _arg = arg[0] if isinstance(arg, (list, tuple)) \ |
| 78 | + and len(arg) == 1 else arg |
| 79 | + before[key] = _ipa |
| 80 | + after[key] = _arg |
| 81 | + return before, after |
| 82 | + |
| 83 | + |
| 84 | +def gen_member_diff(member_key, add_list, del_list, current_list): |
| 85 | + """Compute before/after for one member category. |
| 86 | +
|
| 87 | + Returns (before_dict, after_dict) with member_key as key and sorted |
| 88 | + lists as values. Returns ({}, {}) if no changes. |
| 89 | + """ |
| 90 | + if not add_list and not del_list: |
| 91 | + return {}, {} |
| 92 | + current = sorted(current_list or []) |
| 93 | + desired = sorted( |
| 94 | + [x for x in current if x not in (del_list or [])] |
| 95 | + + (add_list or []) |
| 96 | + ) |
| 97 | + return {member_key: current}, {member_key: desired} |
| 98 | + |
| 99 | + |
| 100 | +def merge_diffs(*diff_pairs): |
| 101 | + """Merge multiple (before, after) tuples into a single pair.""" |
| 102 | + merged_before = {} |
| 103 | + merged_after = {} |
| 104 | + for _before, _after in diff_pairs: |
| 105 | + merged_before.update(_before) |
| 106 | + merged_after.update(_after) |
| 107 | + return merged_before, merged_after |
0 commit comments