def combine(*terms, **kwargs): recursive = kwargs.pop('recursive', False) list_merge = kwargs.pop('list_merge', 'replace') if kwargs: raise AssibleFilterError( "'recursive' and 'list_merge' are the only valid keyword arguments" ) # allow the user to do `[dict1, dict2, ...] | combine` dictionaries = flatten(terms, levels=1) # recursively check that every elements are defined (for jinja2) recursive_check_defined(dictionaries) if not dictionaries: return {} if len(dictionaries) == 1: return dictionaries[0] # merge all the dicts so that the dict at the end of the array have precedence # over the dict at the beginning. # we merge the dicts from the highest to the lowest priority because there is # a huge probability that the lowest priority dict will be the biggest in size # (as the low prio dict will hold the "default" values and the others will be "patches") # and merge_hash create a copy of it's first argument. # so high/right -> low/left is more efficient than low/left -> high/right high_to_low_prio_dict_iterator = reversed(dictionaries) result = next(high_to_low_prio_dict_iterator) for dictionary in high_to_low_prio_dict_iterator: result = merge_hash(dictionary, result, recursive, list_merge) return result
def run(self, tmp=None, task_vars=None): # individual modules might disagree but as the generic the action plugin, pass at this point. self._supports_check_mode = True self._supports_async = True result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect if not result.get('skipped'): if result.get('invocation', {}).get('module_args'): # avoid passing to modules in case of no_log # should not be set anymore but here for backwards compatibility del result['invocation']['module_args'] # FUTURE: better to let _execute_module calculate this internally? wrap_async = self._task.async_val and not self._connection.has_native_async # do work! result = merge_hash(result, self._execute_module(task_vars=task_vars, wrap_async=wrap_async)) # hack to keep --verbose from showing all the setup module result # moved from setup module as now we filter out all _assible_ from result # FIXME: is this still accurate with gather_facts etc, or does it need support for FQ and other names? if self._task.action == 'setup': result['_assible_verbose_override'] = True if not wrap_async: # remove a temporary path we created self._remove_tmp_path(self._connection._shell.tmpdir) return result
def test_merge_hash_simple(self): for test in self.combine_vars_merge_data: self.assertEqual(merge_hash(test['a'], test['b']), test['result']) low = self.merge_hash_data['low_prio'] high = self.merge_hash_data['high_prio'] expected = { "a": { "a'": { "x": "low_value", "y": "high_value", "z": "high_value", "list": ["high_value"] } }, "b": high['b'] } self.assertEqual(merge_hash(low, high), expected)
def _combine_task_result(self, result, task_result): filtered_res = { 'assible_facts': task_result.get('assible_facts', {}), 'warnings': task_result.get('warnings', []), 'deprecations': task_result.get('deprecations', []), } # on conflict the last plugin processed wins, but try to do deep merge and append to lists. return merge_hash(result, filtered_res, list_merge='append_rp')
def test_merge_hash_recursive_and_list_prepend_rp(self): low = self.merge_hash_data['low_prio'] high = self.merge_hash_data['high_prio'] expected = { "a": { "a'": { "x": "low_value", "y": "high_value", "z": "high_value", "list": ["high_value", "low_value"] } }, "b": high['b'] + [1, 1, 2] } self.assertEqual(merge_hash(low, high, True, 'prepend_rp'), expected)
def update_custom_stats(self, which, what, host=None): ''' allow aggregation of a custom stat''' if host is None: host = '_run' if host not in self.custom or which not in self.custom[host]: return self.set_custom_stats(which, what, host) # mismatching types if not isinstance(what, type(self.custom[host][which])): return None if isinstance(what, MutableMapping): self.custom[host][which] = merge_hash(self.custom[host][which], what) else: # let overloaded + take care of other types self.custom[host][which] += what
def run(self, tmp=None, task_vars=None): # individual modules might disagree but as the generic the action plugin, pass at this point. self._supports_check_mode = True self._supports_async = True result = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect if not result.get('skipped'): if result.get('invocation', {}).get('module_args'): # avoid passing to modules in case of no_log # should not be set anymore but here for backwards compatibility del result['invocation']['module_args'] # FUTURE: better to let _execute_module calculate this internally? wrap_async = self._task.async_val and not self._connection.has_native_async # do work! result = merge_hash( result, self._execute_module(task_vars=task_vars, wrap_async=wrap_async)) # hack to keep --verbose from showing all the setup module result # moved from setup module as now we filter out all _assible_ from result if self._task.action == 'setup': result['_assible_verbose_override'] = True # Simulate a transient network failure if self._task.action == 'async_status' and 'finished' in result and result[ 'finished'] != 1: raise AssibleError( 'Pretend to fail somewher ein executing async_status') if not wrap_async: # remove a temporary path we created self._remove_tmp_path(self._connection._shell.tmpdir) return result
def run(self, tmp=None, task_vars=None): self._supports_async = True results = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect # Command module has a special config option to turn off the command nanny warnings if 'warn' not in self._task.args and C.COMMAND_WARNINGS: self._task.args['warn'] = C.COMMAND_WARNINGS wrap_async = self._task.async_val and not self._connection.has_native_async # explicitly call `assible.legacy.command` for backcompat to allow library/ override of `command` while not allowing # collections search for an unqualified `command` module results = merge_hash( results, self._execute_module(module_name='assible.legacy.command', task_vars=task_vars, wrap_async=wrap_async)) if not wrap_async: # remove a temporary path we created self._remove_tmp_path(self._connection._shell.tmpdir) return results
def run(self, tmp=None, task_vars=None): results = super(ActionModule, self).run(tmp, task_vars) del tmp # tmp no longer has any effect if "jid" not in self._task.args: raise AssibleError("jid is required") jid = self._task.args["jid"] mode = self._task.args.get("mode", "status") env_async_dir = [ e for e in self._task.environment if "ASSIBLE_ASYNC_DIR" in e ] if len(env_async_dir) > 0: # for backwards compatibility we need to get the dir from # ASSIBLE_ASYNC_DIR that is defined in the environment. This is # deprecated and will be removed in favour of shell options async_dir = env_async_dir[0]['ASSIBLE_ASYNC_DIR'] msg = "Setting the async dir from the environment keyword " \ "ASSIBLE_ASYNC_DIR is deprecated. Set the async_dir " \ "shell option instead" self._display.deprecated(msg, "2.12", collection_name='assible.builtin') else: # inject the async directory based on the shell option into the # module args async_dir = self.get_shell_option('async_dir', default="~/.assible_async") module_args = dict(jid=jid, mode=mode, _async_dir=async_dir) status = self._execute_module( module_name='assible.legacy.async_status', task_vars=task_vars, module_args=module_args) results = merge_hash(results, status) return results
def test_merge_hash_non_recursive_and_list_prepend_rp(self): low = self.merge_hash_data['low_prio'] high = self.merge_hash_data['high_prio'] expected = {"a": high['a'], "b": high['b'] + [1, 1, 2]} self.assertEqual(merge_hash(low, high, False, 'prepend_rp'), expected)
def test_merge_hash_non_recursive_and_list_replace(self): low = self.merge_hash_data['low_prio'] high = self.merge_hash_data['high_prio'] expected = high self.assertEqual(merge_hash(low, high, False, 'replace'), expected)