issue #297: local actions must execute with fixed directory.

Local actions must execute in the the parent directory of the playbook
that defines the action.
pull/298/head
David Wilson 6 years ago
parent 26ba3e4d83
commit 012745efea

@ -347,6 +347,9 @@ class Connection(ansible.plugins.connection.ConnectionBase):
#: Set to 'hostvars' by on_action_run()
host_vars = None
#: Set to '_loader.get_basedir()' by on_action_run().
loader_basedir = None
#: Set after connection to the target context's home directory.
home_dir = None
@ -366,7 +369,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
# https://github.com/dw/mitogen/issues/140
self.close()
def on_action_run(self, task_vars):
def on_action_run(self, task_vars, loader_basedir):
"""
Invoked by ActionModuleMixin to indicate a new task is about to start
executing. We use the opportunity to grab relevant bits from the
@ -384,6 +387,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
self.mitogen_ssh_debug_level = task_vars.get('mitogen_ssh_debug_level')
self.inventory_hostname = task_vars['inventory_hostname']
self.host_vars = task_vars['hostvars']
self.loader_basedir = loader_basedir
self.close(new_task=True)
@property
@ -543,6 +547,13 @@ class Connection(ansible.plugins.connection.ConnectionBase):
"""
return self.call(ansible_mitogen.target.create_fork_child)
def get_default_cwd(self):
"""
Overridden by connections/mitogen_local.py to emulate behaviour of CWD
inherited from WorkerProcess.
"""
return None
def exec_command(self, cmd, in_data='', sudoable=True, mitogen_chdir=None):
"""
Implement exec_command() by calling the corresponding
@ -560,7 +571,7 @@ class Connection(ansible.plugins.connection.ConnectionBase):
ansible_mitogen.target.exec_command,
cmd=mitogen.utils.cast(cmd),
in_data=mitogen.utils.cast(in_data),
chdir=mitogen_chdir,
chdir=mitogen_chdir or self.get_default_cwd(),
emulate_tty=emulate_tty,
)

@ -108,7 +108,10 @@ class ActionModuleMixin(ansible.plugins.action.ActionBase):
Override run() to notify Connection of task-specific data, so it has a
chance to know e.g. the Python interpreter in use.
"""
self._connection.on_action_run(task_vars)
self._connection.on_action_run(
task_vars=task_vars,
loader_basedir=self._loader.get_basedir(),
)
return super(ActionModuleMixin, self).run(tmp, task_vars)
def call(self, func, *args, **kwargs):

@ -175,6 +175,7 @@ class Planner(object):
"""
new = dict((mitogen.core.UnicodeType(k), kwargs[k])
for k in kwargs)
new.setdefault('cwd', self._inv.connection.get_default_cwd())
new.setdefault('emulate_tty', True)
new.setdefault('service_context', self._inv.connection.parent)
return new

@ -41,3 +41,7 @@ import ansible_mitogen.connection
class Connection(ansible_mitogen.connection.Connection):
transport = 'local'
def get_default_cwd(self):
# https://github.com/ansible/ansible/issues/14489
return self.loader_basedir

@ -126,8 +126,10 @@ class Runner(object):
implicit bytes/str conversion behaviour of a 2.x controller running
against a 3.x target.
:param dict env:
Additional environment variables to set during the run.
Additional environment variables to set during the run. Keys with
:data:`None` are unset if present.
:param str cwd:
If not :data:`None`, change to this directory before executing.
:param mitogen.core.ExternalContext econtext:
When `detach` is :data:`True`, a reference to the ExternalContext the
runner is executing in.
@ -135,7 +137,7 @@ class Runner(object):
When :data:`True`, indicate the runner should detach the context from
its parent after setup has completed successfully.
"""
def __init__(self, module, service_context, json_args, env=None,
def __init__(self, module, service_context, json_args, cwd=None, env=None,
econtext=None, detach=False):
self.module = module
self.service_context = service_context
@ -143,6 +145,7 @@ class Runner(object):
self.detach = detach
self.args = json.loads(json_args)
self.env = env
self.cwd = cwd
def setup(self):
"""
@ -150,6 +153,7 @@ class Runner(object):
from the parent, as :meth:`run` may detach prior to beginning
execution. The base implementation simply prepares the environment.
"""
self._cwd = TemporaryCwd(self.cwd)
self._env = TemporaryEnvironment(self.env)
def revert(self):
@ -158,6 +162,7 @@ class Runner(object):
implementation simply restores the original environment.
"""
self._env.revert()
self._cwd.revert()
self._try_cleanup_temp()
def _cleanup_temp(self):
@ -259,6 +264,16 @@ class ModuleUtilsImporter(object):
return mod
class TemporaryCwd(object):
def __init__(self, cwd):
self.original = os.getcwd()
if cwd:
os.chdir(cwd)
def revert(self):
os.chdir(self.original)
class TemporaryEnvironment(object):
def __init__(self, env=None):
self.original = os.environ.copy()

@ -8,6 +8,7 @@
- import_playbook: become/all.yml
- import_playbook: connection_loader/all.yml
- import_playbook: context_service/all.yml
- import_playbook: local/all.yml
#- import_playbook: module_utils/all.yml
- import_playbook: playbook_semantics/all.yml
- import_playbook: remote_tmp/all.yml

@ -0,0 +1,4 @@
- import_playbook: cwd_preserved.yml
#- import_playbook: env_preserved.yml

@ -0,0 +1,22 @@
# Current working directory should be that of WorkerProcess -- which is the
# same directory as the currently executing playbook, i.e. integration/local/
#
# https://github.com/ansible/ansible/issues/14489
- name: integration/local/cwd_preserved.yml
any_errors_fatal: true
hosts: test-targets
tasks:
- connection: local
command: pwd
register: pwd
- connection: local
stat:
path: "{{pwd.stdout}}/cwd_preserved.yml"
register: stat
- assert:
that: stat.stat.exists
Loading…
Cancel
Save