1""" 2A general map/reduce style salt runner for aggregating results 3returned by several different minions. 4 5.. versionadded:: 2014.7.0 6 7Aggregated results are sorted by the size of the minion pools which returned 8matching results. 9 10Useful for playing the game: *"some of these things are not like the others..."* 11when identifying discrepancies in a large infrastructure managed by salt. 12""" 13 14import salt.client 15from salt.exceptions import SaltClientError 16 17 18def hash(*args, **kwargs): 19 """ 20 Return the MATCHING minion pools from the aggregated and sorted results of 21 a salt command 22 23 .. versionadded:: 2014.7.0 24 25 This command is submitted via a salt runner using the 26 general form:: 27 28 salt-run survey.hash [survey_sort=up/down] <target> 29 <salt-execution-module> <salt-execution-module parameters> 30 31 Optionally accept a ``survey_sort=`` parameter. Default: ``survey_sort=down`` 32 33 CLI Example #1: (functionally equivalent to ``salt-run manage.up``) 34 35 .. code-block:: bash 36 37 salt-run survey.hash "*" test.ping 38 39 CLI Example #2: (find an "outlier" minion config file) 40 41 .. code-block:: bash 42 43 salt-run survey.hash "*" file.get_hash /etc/salt/minion survey_sort=up 44 """ 45 46 return _get_pool_results(*args, **kwargs) 47 48 49def diff(*args, **kwargs): 50 """ 51 Return the DIFFERENCE of the result sets returned by each matching minion 52 pool 53 54 .. versionadded:: 2014.7.0 55 56 These pools are determined from the aggregated and sorted results of 57 a salt command. 58 59 This command displays the "diffs" as a series of 2-way differences -- 60 namely the difference between the FIRST displayed minion pool 61 (according to sort order) and EACH SUBSEQUENT minion pool result set. 62 63 Differences are displayed according to the Python ``difflib.unified_diff()`` 64 as in the case of the salt execution module ``file.get_diff``. 65 66 This command is submitted via a salt runner using the general form:: 67 68 salt-run survey.diff [survey_sort=up/down] <target> 69 <salt-execution-module> <salt-execution-module parameters> 70 71 Optionally accept a ``survey_sort=`` parameter. Default: 72 ``survey_sort=down`` 73 74 CLI Example #1: (Example to display the "differences of files") 75 76 .. code-block:: bash 77 78 salt-run survey.diff survey_sort=up "*" cp.get_file_str file:///etc/hosts 79 """ 80 # TODO: The salt execution module "cp.get_file_str file:///..." is a 81 # non-obvious way to display the differences between files using 82 # survey.diff . A more obvious method needs to be found or developed. 83 84 import difflib 85 86 bulk_ret = _get_pool_results(*args, **kwargs) 87 88 is_first_time = True 89 for k in bulk_ret: 90 print("minion pool :\n------------") 91 print(k["pool"]) 92 print("pool size :\n----------") 93 print(" " + str(len(k["pool"]))) 94 if is_first_time: 95 is_first_time = False 96 print("pool result :\n------------") 97 print(" " + bulk_ret[0]["result"]) 98 print() 99 continue 100 101 outs = 'differences from "{}" results :'.format(bulk_ret[0]["pool"][0]) 102 print(outs) 103 print("-" * (len(outs) - 1)) 104 from_result = bulk_ret[0]["result"].splitlines() 105 for idx, _ in enumerate(from_result): 106 from_result[idx] += "\n" 107 to_result = k["result"].splitlines() 108 for idx, _ in enumerate(to_result): 109 to_result[idx] += "\n" 110 outs = "" 111 outs += "".join( 112 difflib.unified_diff( 113 from_result, 114 to_result, 115 fromfile=bulk_ret[0]["pool"][0], 116 tofile=k["pool"][0], 117 n=0, 118 ) 119 ) 120 print(outs) 121 print() 122 123 return bulk_ret 124 125 126def _get_pool_results(*args, **kwargs): 127 """ 128 A helper function which returns a dictionary of minion pools along with 129 their matching result sets. 130 Useful for developing other "survey style" functions. 131 Optionally accepts a "survey_sort=up" or "survey_sort=down" kwargs for 132 specifying sort order. 133 Because the kwargs namespace of the "salt" and "survey" command are shared, 134 the name "survey_sort" was chosen to help avoid option conflicts. 135 """ 136 # TODO: the option "survey.sort=" would be preferred for namespace 137 # separation but the kwargs parser for the salt-run command seems to 138 # improperly pass the options containing a "." in them for later modules to 139 # process. The "_" is used here instead. 140 141 import hashlib 142 143 tgt = args[0] 144 cmd = args[1] 145 ret = {} 146 147 sort = kwargs.pop("survey_sort", "down") 148 direction = sort != "up" 149 150 tgt_type = kwargs.pop("tgt_type", "compound") 151 if tgt_type not in ["compound", "pcre"]: 152 tgt_type = "compound" 153 154 kwargs_passthru = { 155 key: value for (key, value) in kwargs.items() if not key.startswith("_") 156 } 157 158 with salt.client.get_local_client(__opts__["conf_file"]) as client: 159 try: 160 minions = client.cmd( 161 tgt, 162 cmd, 163 args[2:], 164 timeout=__opts__["timeout"], 165 tgt_type=tgt_type, 166 kwarg=kwargs_passthru, 167 ) 168 except SaltClientError as client_error: 169 print(client_error) 170 return ret 171 172 # hash minion return values as a string 173 for minion in sorted(minions): 174 digest = hashlib.sha256( 175 str(minions[minion]).encode(__salt_system_encoding__) 176 ).hexdigest() 177 if digest not in ret: 178 ret[digest] = {} 179 ret[digest]["pool"] = [] 180 ret[digest]["result"] = str(minions[minion]) 181 182 ret[digest]["pool"].append(minion) 183 184 sorted_ret = [] 185 for k in sorted(ret, key=lambda k: len(ret[k]["pool"]), reverse=direction): 186 # return aggregated results, sorted by size of the hash pool 187 188 sorted_ret.append(ret[k]) 189 190 return sorted_ret 191