Advertisement
bahman

merge variables matching list, regex

Sep 15th, 2023
1,029
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.81 KB | None | 0 0
  1. # From https://matrix.to/#/!czpAhMQgXXiRMvhOef:libera.chat/$Xjzm7Uw3HummdlPb1ZnqCNlePbVgPTASEM-p8_P5C-k?via=ansible.com&via=libera.chat&via=matrix.org
  2.  
  3. from __future__ import (absolute_import, division, print_function)
  4. __metaclass__ = type
  5.  
  6. # import os
  7. import re
  8.  
  9. from ansible.errors import AnsibleError
  10. from ansible.plugins.lookup import LookupBase
  11. # from ansible.utils.vars import merge_hash
  12. # from ansible.module_utils.six.moves import reduce
  13. # from ansible.plugins.filter.core import combine
  14. from ansible.plugins.filter.core import flatten
  15. from ansible.module_utils.six import string_types
  16.  
  17. # from ansible.module_utils._text import to_bytes, to_text
  18. # from ansible.template import generate_ansible_template_vars
  19.  
  20.  
  21. DOCUMENTATION = """
  22.    lookup: mergevars
  23.    author: Todd Lewis <todd_lewis@unc.edu>
  24.    version_added: "2.7.10"
  25.    short_description: merge variables matching list, regex
  26.    description:
  27.      - Listed variables and/or those matching the supplied regex will be
  28.        deep-merged and returned. Scalar variables (int and str) are treated
  29.        like single element lists. All variables must be either lists or dicts;
  30.        you can't mix them. Order is preserved for explicitly listed variables.
  31.        Variables matching var_regex are sorted and come after explicit variables.
  32.    options:
  33.      _terms:
  34.        description:
  35.          - list of variables to merge. These can be names of variables ("'foo'"), or
  36.            expressions ("foo[3]").
  37.        required: false
  38.      regex:
  39.        description: regular expression matching names of variables to merge
  40.        required: false
  41.      dedup:
  42.        discription: whether to deduplicate the resulting list
  43.        default: true
  44.        required: false
  45.        type: boolean
  46.      recursive:
  47.        description: a boolean to indicate whether to recursively merge dictionaries
  48.        default: true
  49.        required: false
  50.        type: boolean
  51. """
  52.  
  53. EXAMPLES = """
  54. - name: show merged variables
  55.  debug: msg="{{ lookup('mergevars', regex='^merge_these_.*') }}
  56.  
  57. - name: show merged variables
  58.  debug: msg="{{ lookup('mergevars', 'ansible_fqdn', 'ansible_mounts', regex='^merge_these_.*') }}
  59.  
  60. """
  61.  
  62. RETURN = """
  63. _raw:
  64.   description: list of merged variables
  65. """
  66.  
  67. try:
  68.     from __main__ import display
  69. except ImportError:
  70.     from ansible.utils.display import Display
  71.     display = Display()
  72.  
  73.  
  74. class LookupModule(LookupBase):
  75.  
  76.     def run(self, terms, variables, **kwargs):
  77.  
  78.         varpat = kwargs.get('regex', '')
  79.         dedup = kwargs.get('dedup', True)
  80.         recursive_dict_merge = kwargs.get('recursive', True)
  81.         keys = []
  82.         for key in terms:
  83.             if not isinstance(key, string_types):
  84.                 keys.append(key)
  85.             elif key not in keys and key in variables.keys():
  86.                 keys.append(key)
  87.  
  88.         if len(varpat) > 0:
  89.             for key in sorted(variables.keys()):
  90.                 if key not in keys and re.match(varpat, key):
  91.                     keys.append(key)
  92.  
  93.         display.v("Merging vars in this order: {}".format(list(map(type_or_str, keys))))
  94.  
  95.         # We need to render any jinja in the merged var now, because once it
  96.         # leaves this plugin, ansible will cleanse it by turning any jinja tags
  97.         # into comments.
  98.         # And we need it done before merging the variables,
  99.         # in case any structured data is specified with templates.
  100.         merge_vals = []
  101.         for key in keys:
  102.             if isinstance(key, string_types):
  103.                 val = self._templar.template(variables[key])
  104.             else:
  105.                 val = self._templar.template(key)
  106.             if isinstance(val, int) or isinstance(val, str):
  107.                 merge_vals.append([val])
  108.             else:
  109.                 merge_vals.append(val)
  110.  
  111.         # Dispatch based on type that we're merging
  112.         if len(merge_vals) == 0:
  113.             merged = []
  114.         elif isinstance(merge_vals[0], list):
  115.             merged = merge_list(merge_vals, dedup, recursive_dict_merge)
  116.         elif isinstance(merge_vals[0], dict):
  117.             merged = merge_dict(merge_vals, dedup, recursive_dict_merge)
  118.         else:
  119.             raise AnsibleError(
  120.                 "Don't know how to merge variables of type: {}".format(type(merge_vals[0]))
  121.             )
  122.  
  123.         return [merged]
  124.  
  125.  
  126. def type_or_str(term):
  127.     if isinstance(term, string_types):
  128.         return term
  129.     else:
  130.         return type(term)
  131.  
  132.  
  133. def merge_dict(merge_vals, dedup, recursive_dict_merge):
  134.     """
  135.    To merge dicts, just update one with the values of the next, etc.
  136.    """
  137.     check_type(merge_vals, dict)
  138.     merged = {}
  139.  
  140.     for val in merge_vals:
  141.         if not recursive_dict_merge:
  142.             merged.update(val)
  143.         else:
  144.             # Recursive merging of dictionaries with overlapping keys:
  145.             #   LISTS: merge with merge_list
  146.             #   DICTS: recursively merge with merge_dict
  147.             #   any other types: replace (same as usual behaviour)
  148.             for key in val.keys():
  149.                 if key not in merged:
  150.                     # first hit of the value - just assign
  151.                     merged[key] = val[key]
  152.                 elif isinstance(merged[key], list):
  153.                     merged[key] = merge_list([merged[key], val[key]], dedup, recursive_dict_merge)
  154.                 elif isinstance(merged[key], dict):
  155.                     merged[key] = merge_dict([merged[key], val[key]], dedup, recursive_dict_merge)
  156.                 else:
  157.                     merged[key] = val[key]
  158.     return merged
  159.  
  160.  
  161. def merge_list(merge_vals, dedup, recursive_dict_merge):
  162.     """ To merge lists, just concat them. Dedup if wanted. """
  163.     check_type(merge_vals, list)
  164.     flatten_levels = 0
  165.     if recursive_dict_merge:
  166.         flatten_levels = 999
  167.     merged = flatten(merge_vals, flatten_levels)
  168.     if dedup:
  169.         merged = deduplicate(merged)
  170.     return merged
  171.  
  172.  
  173. def check_type(mylist, _type):
  174.     """ Ensure that all members of mylist are of type _type. """
  175.     if not all([isinstance(item, _type) for item in mylist]):
  176.         raise AnsibleError("All values to merge must be of the same type, either dict or list")
  177.  
  178.  
  179. # def flatten(list_of_lists, levels=None):
  180. #     """
  181. #     Flattens a list of lists:
  182. #         >>> flatten([[1, 2] [3, 4]])
  183. #         [1, 2, 3, 4]
  184. #
  185. #     I wish Python had this in the standard lib :(
  186. #     """
  187. #     return list((x for y in list_of_lists for x in y))
  188.  
  189.  
  190. def deduplicate(mylist):
  191.     """
  192.    Just brute force it. This lets us keep order, and lets us dedup unhashable
  193.    things, like dicts. Hopefully you won't run into such big lists that
  194.    this will ever be a performance issue.
  195.    """
  196.     deduped = []
  197.     for item in mylist:
  198.         if item not in deduped:
  199.             deduped.append(item)
  200.     return deduped
  201.  
Tags: python ansible
Advertisement
Comments
Add Comment
Please, Sign In to add comment
Advertisement