def test_action_base__compute_environment_string(self): fake_loader = DictDataLoader({}) # create our fake task mock_task = MagicMock() mock_task.action = "copy" mock_task.args = dict(a=1) # create a mock connection, so we don't actually try and connect to things def env_prefix(**args): return ' '.join([ '%s=%s' % (k, shlex_quote(text_type(v))) for k, v in args.items() ]) mock_connection = MagicMock() mock_connection._shell.env_prefix.side_effect = env_prefix # we're using a real play context here play_context = PlayContext() # and we're using a real templar here too templar = Templar(loader=fake_loader) # our test class action_base = DerivedActionBase( task=mock_task, connection=mock_connection, play_context=play_context, loader=fake_loader, templar=templar, shared_loader_obj=None, ) # test standard environment setup mock_task.environment = [dict(FOO='foo'), None] env_string = action_base._compute_environment_string() self.assertEqual(env_string, "FOO=foo") # test where environment is not a list mock_task.environment = dict(FOO='foo') env_string = action_base._compute_environment_string() self.assertEqual(env_string, "FOO=foo") # test environment with a variable in it templar.available_variables = dict(the_var='bar') mock_task.environment = [dict(FOO='{{the_var}}')] env_string = action_base._compute_environment_string() self.assertEqual(env_string, "FOO=bar") # test with a bad environment set mock_task.environment = dict(FOO='foo') mock_task.environment = ['hi there'] self.assertRaises(AssibleError, action_base._compute_environment_string)
def _get_delegated_vars(self, play, task, existing_variables): # This method has a lot of code copied from ``TaskExecutor._get_loop_items`` # if this is failing, and ``TaskExecutor._get_loop_items`` is not # then more will have to be copied here. # TODO: dedupe code here and with ``TaskExecutor._get_loop_items`` # this may be possible once we move pre-processing pre fork if not hasattr(task, 'loop'): # This "task" is not a Task, so we need to skip it return {}, None # we unfortunately need to template the delegate_to field here, # as we're fetching vars before post_validate has been called on # the task that has been passed in vars_copy = existing_variables.copy() # get search path for this task to pass to lookup plugins vars_copy['assible_search_path'] = task.get_search_path() # ensure basedir is always in (dwim already searches here but we need to display it) if self._loader.get_basedir() not in vars_copy['assible_search_path']: vars_copy['assible_search_path'].append(self._loader.get_basedir()) templar = Templar(loader=self._loader, variables=vars_copy) items = [] has_loop = True if task.loop_with is not None: if task.loop_with in lookup_loader: fail = True if task.loop_with == 'first_found': # first_found loops are special. If the item is undefined then we want to fall through to the next fail = False try: loop_terms = listify_lookup_plugin_terms( terms=task.loop, templar=templar, loader=self._loader, fail_on_undefined=fail, convert_bare=False) if not fail: loop_terms = [ t for t in loop_terms if not templar.is_template(t) ] mylookup = lookup_loader.get(task.loop_with, loader=self._loader, templar=templar) # give lookup task 'context' for subdir (mostly needed for first_found) for subdir in ['template', 'var', 'file']: # TODO: move this to constants? if subdir in task.action: break setattr(mylookup, '_subdir', subdir + 's') items = wrap_var( mylookup.run(terms=loop_terms, variables=vars_copy)) except AssibleTemplateError: # This task will be skipped later due to this, so we just setup # a dummy array for the later code so it doesn't fail items = [None] else: raise AssibleError( "Failed to find the lookup named '%s' in the available lookup plugins" % task.loop_with) elif task.loop is not None: try: items = templar.template(task.loop) except AssibleTemplateError: # This task will be skipped later due to this, so we just setup # a dummy array for the later code so it doesn't fail items = [None] else: has_loop = False items = [None] # since host can change per loop, we keep dict per host name resolved delegated_host_vars = dict() item_var = getattr(task.loop_control, 'loop_var', 'item') cache_items = False for item in items: # update the variables with the item value for templating, in case we need it if item is not None: vars_copy[item_var] = item templar.available_variables = vars_copy delegated_host_name = templar.template(task.delegate_to, fail_on_undefined=False) if delegated_host_name != task.delegate_to: cache_items = True if delegated_host_name is None: raise AssibleError( message="Undefined delegate_to host for task:", obj=task._ds) if not isinstance(delegated_host_name, string_types): raise AssibleError( message= "the field 'delegate_to' has an invalid type (%s), and could not be" " converted to a string type." % type(delegated_host_name), obj=task._ds) if delegated_host_name in delegated_host_vars: # no need to repeat ourselves, as the delegate_to value # does not appear to be tied to the loop item variable continue # now try to find the delegated-to host in inventory, or failing that, # create a new host on the fly so we can fetch variables for it delegated_host = None if self._inventory is not None: delegated_host = self._inventory.get_host(delegated_host_name) # try looking it up based on the address field, and finally # fall back to creating a host on the fly to use for the var lookup if delegated_host is None: for h in self._inventory.get_hosts( ignore_limits=True, ignore_restrictions=True): # check if the address matches, or if both the delegated_to host # and the current host are in the list of localhost aliases if h.address == delegated_host_name: delegated_host = h break else: delegated_host = Host(name=delegated_host_name) else: delegated_host = Host(name=delegated_host_name) # now we go fetch the vars for the delegated-to host and save them in our # master dictionary of variables to be used later in the TaskExecutor/PlayContext delegated_host_vars[delegated_host_name] = self.get_vars( play=play, host=delegated_host, task=task, include_delegate_to=False, include_hostvars=True, ) delegated_host_vars[delegated_host_name][ 'inventory_hostname'] = vars_copy.get('inventory_hostname') _assible_loop_cache = None if has_loop and cache_items: # delegate_to templating produced a change, so we will cache the templated items # in a special private hostvar # this ensures that delegate_to+loop doesn't produce different results than TaskExecutor # which may reprocess the loop _assible_loop_cache = items return delegated_host_vars, _assible_loop_cache