diff --git a/ansible_mitogen/runner.py b/ansible_mitogen/runner.py index 44780aa2..45bb5f0b 100644 --- a/ansible_mitogen/runner.py +++ b/ansible_mitogen/runner.py @@ -38,6 +38,7 @@ how to build arguments for it, preseed related data, etc. from __future__ import absolute_import from __future__ import unicode_literals +import atexit import ctypes import errno import imp @@ -757,9 +758,25 @@ class NewStyleRunner(ScriptRunner): if klass and isinstance(exc, klass): mod.module.fail_json(**exc.results) - def _run(self): - code = self._get_code() + def _run_code(self, code, mod): + try: + if mitogen.core.PY3: + exec(code, vars(mod)) + else: + exec('exec code in vars(mod)') + except Exception as e: + self._handle_magic_exception(mod, e) + raise + def _run_atexit_funcs(self): + """ + Newer Ansibles use atexit.register() to trigger tmpdir cleanup, when + AnsibleModule.tmpdir is responsible for creating its own temporary + directory. + """ + atexit._run_exitfuncs() + + def _run(self): mod = types.ModuleType(self.main_module_name) mod.__package__ = None # Some Ansible modules use __file__ to find the Ansiballz temporary @@ -771,16 +788,13 @@ class NewStyleRunner(ScriptRunner): 'ansible_module_' + os.path.basename(self.path), ) + code = self._get_code() exc = None try: try: - if mitogen.core.PY3: - exec(code, vars(mod)) - else: - exec('exec code in vars(mod)') - except Exception as e: - self._handle_magic_exception(mod, e) - raise + self._run_code(code, mod) + finally: + self._run_atexit_funcs() except SystemExit as e: exc = e diff --git a/tests/ansible/integration/runner/all.yml b/tests/ansible/integration/runner/all.yml index 69c22edb..9dd209d7 100644 --- a/tests/ansible/integration/runner/all.yml +++ b/tests/ansible/integration/runner/all.yml @@ -1,3 +1,4 @@ +- import_playbook: atexit.yml - import_playbook: builtin_command_module.yml - import_playbook: custom_bash_hashbang_argument.yml - import_playbook: custom_bash_old_style_module.yml @@ -15,6 +16,6 @@ - import_playbook: environment_isolation.yml - import_playbook: etc_environment.yml - import_playbook: forking_active.yml -- import_playbook: forking_inactive.yml - import_playbook: forking_correct_parent.yml +- import_playbook: forking_inactive.yml - import_playbook: missing_module.yml diff --git a/tests/ansible/integration/runner/atexit.yml b/tests/ansible/integration/runner/atexit.yml new file mode 100644 index 00000000..872cdd57 --- /dev/null +++ b/tests/ansible/integration/runner/atexit.yml @@ -0,0 +1,31 @@ +# issue #397: newer Ansibles rely on atexit to cleanup their temporary +# directories. Ensure atexit handlers run during runner completion. + +- name: integration/runner/atexit.yml + hosts: test-targets + gather_facts: false + any_errors_fatal: false + tasks: + + # + # Verify a run with a healthy atexit handler. Broken handlers cause an + # exception to be raised. + # + + - custom_python_run_script: + script: | + import atexit + atexit.register(lambda: + open('/tmp/atexit-was-triggered', 'w').write('yep')) + + - slurp: + path: /tmp/atexit-was-triggered + register: out + + - assert: + that: + - out.content|b64decode == "yep" + + - file: + path: /tmp/atexit-was-triggered + state: absent