1""" 2Utility functions for state functions 3 4.. versionadded:: 2018.3.0 5""" 6 7 8import copy 9 10import salt.state 11from salt.exceptions import CommandExecutionError 12 13_empty = object() 14 15 16def gen_tag(low): 17 """ 18 Generate the running dict tag string from the low data structure 19 """ 20 return "{0[state]}_|-{0[__id__]}_|-{0[name]}_|-{0[fun]}".format(low) 21 22 23def search_onfail_requisites(sid, highstate): 24 """ 25 For a particular low chunk, search relevant onfail related states 26 """ 27 onfails = [] 28 if "_|-" in sid: 29 st = salt.state.split_low_tag(sid) 30 else: 31 st = {"__id__": sid} 32 for fstate, fchunks in highstate.items(): 33 if fstate == st["__id__"]: 34 continue 35 else: 36 for mod_, fchunk in fchunks.items(): 37 if not isinstance(mod_, str) or mod_.startswith("__"): 38 continue 39 else: 40 if not isinstance(fchunk, list): 41 continue 42 else: 43 # bydefault onfail will fail, but you can 44 # set onfail_stop: False to prevent the highstate 45 # to stop if you handle it 46 onfail_handled = False 47 for fdata in fchunk: 48 if not isinstance(fdata, dict): 49 continue 50 onfail_handled = fdata.get("onfail_stop", True) is False 51 if onfail_handled: 52 break 53 if not onfail_handled: 54 continue 55 for fdata in fchunk: 56 if not isinstance(fdata, dict): 57 continue 58 for knob, fvalue in fdata.items(): 59 if knob != "onfail": 60 continue 61 for freqs in fvalue: 62 for fmod, fid in freqs.items(): 63 if not ( 64 fid == st["__id__"] 65 and fmod == st.get("state", fmod) 66 ): 67 continue 68 onfails.append((fstate, mod_, fchunk)) 69 return onfails 70 71 72def check_onfail_requisites(state_id, state_result, running, highstate): 73 """ 74 When a state fail and is part of a highstate, check 75 if there is onfail requisites. 76 When we find onfail requisites, we will consider the state failed 77 only if at least one of those onfail requisites also failed 78 79 Returns: 80 81 True: if onfail handlers succeeded 82 False: if one on those handler failed 83 None: if the state does not have onfail requisites 84 85 """ 86 nret = None 87 if state_id and state_result and highstate and isinstance(highstate, dict): 88 onfails = search_onfail_requisites(state_id, highstate) 89 if onfails: 90 for handler in onfails: 91 fstate, mod_, fchunk = handler 92 for rstateid, rstate in running.items(): 93 if "_|-" in rstateid: 94 st = salt.state.split_low_tag(rstateid) 95 # in case of simple state, try to guess 96 else: 97 id_ = rstate.get("__id__", rstateid) 98 if not id_: 99 raise ValueError("no state id") 100 st = {"__id__": id_, "state": mod_} 101 if mod_ == st["state"] and fstate == st["__id__"]: 102 ofresult = rstate.get("result", _empty) 103 if ofresult in [False, True]: 104 nret = ofresult 105 if ofresult is False: 106 # as soon as we find an errored onfail, we stop 107 break 108 # consider that if we parsed onfailes without changing 109 # the ret, that we have failed 110 if nret is None: 111 nret = False 112 return nret 113 114 115def check_result(running, recurse=False, highstate=None): 116 """ 117 Check the total return value of the run and determine if the running 118 dict has any issues 119 """ 120 if not isinstance(running, dict): 121 return False 122 123 if not running: 124 return False 125 126 ret = True 127 for state_id, state_result in running.items(): 128 expected_type = dict 129 # The __extend__ state is a list 130 if "__extend__" == state_id: 131 expected_type = list 132 if not recurse and not isinstance(state_result, expected_type): 133 ret = False 134 if ret and isinstance(state_result, dict): 135 result = state_result.get("result", _empty) 136 if result is False: 137 ret = False 138 # only override return value if we are not already failed 139 elif result is _empty and isinstance(state_result, dict) and ret: 140 ret = check_result(state_result, recurse=True, highstate=highstate) 141 # if we detect a fail, check for onfail requisites 142 if not ret: 143 # ret can be None in case of no onfail reqs, recast it to bool 144 ret = bool( 145 check_onfail_requisites(state_id, state_result, running, highstate) 146 ) 147 # return as soon as we got a failure 148 if not ret: 149 break 150 return ret 151 152 153def merge_subreturn(original_return, sub_return, subkey=None): 154 """ 155 Update an existing state return (`original_return`) in place 156 with another state return (`sub_return`), i.e. for a subresource. 157 158 Returns: 159 dict: The updated state return. 160 161 The existing state return does not need to have all the required fields, 162 as this is meant to be called from the internals of a state function, 163 but any existing data will be kept and respected. 164 165 It is important after using this function to check the return value 166 to see if it is False, in which case the main state should return. 167 Prefer to check `_ret['result']` instead of `ret['result']`, 168 as the latter field may not yet be populated. 169 170 Code Example: 171 172 .. code-block:: python 173 174 def state_func(name, config, alarm=None): 175 ret = {'name': name, 'comment': '', 'changes': {}} 176 if alarm: 177 _ret = __states__['subresource.managed'](alarm) 178 __utils__['state.merge_subreturn'](ret, _ret) 179 if _ret['result'] is False: 180 return ret 181 """ 182 if not subkey: 183 subkey = sub_return["name"] 184 185 if sub_return["result"] is False: 186 # True or None stay the same 187 original_return["result"] = sub_return["result"] 188 189 sub_comment = sub_return["comment"] 190 if not isinstance(sub_comment, list): 191 sub_comment = [sub_comment] 192 original_return.setdefault("comment", []) 193 if isinstance(original_return["comment"], list): 194 original_return["comment"].extend(sub_comment) 195 else: 196 if original_return["comment"]: 197 # Skip for empty original comments 198 original_return["comment"] += "\n" 199 original_return["comment"] += "\n".join(sub_comment) 200 201 if sub_return["changes"]: # changes always exists 202 original_return.setdefault("changes", {}) 203 original_return["changes"][subkey] = sub_return["changes"] 204 205 return original_return 206 207 208def get_sls_opts(opts, **kwargs): 209 """ 210 Return a copy of the opts for use, optionally load a local config on top 211 """ 212 opts = copy.deepcopy(opts) 213 214 if "localconfig" in kwargs: 215 return salt.config.minion_config(kwargs["localconfig"], defaults=opts) 216 217 if "saltenv" in kwargs: 218 saltenv = kwargs["saltenv"] 219 if saltenv is not None: 220 if not isinstance(saltenv, str): 221 saltenv = str(saltenv) 222 if opts["lock_saltenv"] and saltenv != opts["saltenv"]: 223 raise CommandExecutionError( 224 "lock_saltenv is enabled, saltenv cannot be changed" 225 ) 226 opts["saltenv"] = kwargs["saltenv"] 227 228 if "pillarenv" in kwargs or opts.get("pillarenv_from_saltenv", False): 229 pillarenv = kwargs.get("pillarenv") or kwargs.get("saltenv") 230 if pillarenv is not None and not isinstance(pillarenv, str): 231 opts["pillarenv"] = str(pillarenv) 232 else: 233 opts["pillarenv"] = pillarenv 234 235 return opts 236