2018-03-30 10:10:38 +00:00
|
|
|
# Copyright 2017, David Wilson
|
|
|
|
#
|
|
|
|
# Redistribution and use in source and binary forms, with or without
|
|
|
|
# modification, are permitted provided that the following conditions are met:
|
|
|
|
#
|
|
|
|
# 1. Redistributions of source code must retain the above copyright notice,
|
|
|
|
# this list of conditions and the following disclaimer.
|
|
|
|
#
|
|
|
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
# this list of conditions and the following disclaimer in the documentation
|
|
|
|
# and/or other materials provided with the distribution.
|
|
|
|
#
|
|
|
|
# 3. Neither the name of the copyright holder nor the names of its contributors
|
|
|
|
# may be used to endorse or promote products derived from this software without
|
|
|
|
# specific prior written permission.
|
|
|
|
#
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
|
2018-04-01 10:29:52 +00:00
|
|
|
|
|
|
|
"""
|
|
|
|
These classes implement execution for each style of Ansible module. They are
|
2018-04-06 16:22:45 +00:00
|
|
|
instantiated in the target context by way of target.py::run_module().
|
2018-04-01 10:29:52 +00:00
|
|
|
|
|
|
|
Each class in here has a corresponding Planner class in planners.py that knows
|
|
|
|
how to build arguments for it, preseed related data, etc.
|
|
|
|
"""
|
|
|
|
|
2018-03-30 10:10:38 +00:00
|
|
|
from __future__ import absolute_import
|
2018-04-17 16:40:45 +00:00
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2018-05-07 10:46:24 +00:00
|
|
|
import ctypes
|
2018-06-10 14:25:11 +00:00
|
|
|
import errno
|
2018-05-13 00:47:11 +00:00
|
|
|
import imp
|
2018-03-30 10:10:38 +00:00
|
|
|
import json
|
2018-04-01 17:19:34 +00:00
|
|
|
import logging
|
2018-03-30 10:10:38 +00:00
|
|
|
import os
|
2018-08-10 09:06:57 +00:00
|
|
|
import shlex
|
2018-04-01 20:10:04 +00:00
|
|
|
import sys
|
2018-03-30 10:10:38 +00:00
|
|
|
import tempfile
|
2018-04-01 20:10:04 +00:00
|
|
|
import types
|
2018-03-30 10:10:38 +00:00
|
|
|
|
2018-06-07 15:48:42 +00:00
|
|
|
import mitogen.core
|
2018-04-06 16:22:45 +00:00
|
|
|
import ansible_mitogen.target # TODO: circular import
|
2018-03-30 10:10:38 +00:00
|
|
|
|
2018-04-17 16:40:45 +00:00
|
|
|
try:
|
2018-07-04 23:28:25 +00:00
|
|
|
# Cannot use cStringIO as it does not support Unicode.
|
|
|
|
from StringIO import StringIO
|
2018-04-17 16:40:45 +00:00
|
|
|
except ImportError:
|
|
|
|
from io import StringIO
|
|
|
|
|
2018-03-30 10:10:38 +00:00
|
|
|
try:
|
|
|
|
from shlex import quote as shlex_quote
|
|
|
|
except ImportError:
|
|
|
|
from pipes import quote as shlex_quote
|
|
|
|
|
|
|
|
# Prevent accidental import of an Ansible module from hanging on stdin read.
|
|
|
|
import ansible.module_utils.basic
|
|
|
|
ansible.module_utils.basic._ANSIBLE_ARGS = '{}'
|
|
|
|
|
2018-05-07 10:46:24 +00:00
|
|
|
# For tasks that modify /etc/resolv.conf, non-Debian derivative glibcs cache
|
|
|
|
# resolv.conf at startup and never implicitly reload it. Cope with that via an
|
|
|
|
# explicit call to res_init() on each task invocation. BSD-alikes export it
|
|
|
|
# directly, Linux #defines it as "__res_init".
|
|
|
|
libc = ctypes.CDLL(None)
|
|
|
|
libc__res_init = None
|
|
|
|
for symbol in 'res_init', '__res_init':
|
|
|
|
try:
|
|
|
|
libc__res_init = getattr(libc, symbol)
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
2018-03-30 10:10:38 +00:00
|
|
|
|
2018-08-10 09:06:57 +00:00
|
|
|
# For tasks running on Linux machines, with vanilla Ansible, edits to
|
|
|
|
# /etc/environment and ~/.pam_environment are reflected if become:true, due to
|
|
|
|
# sudo reinvoking pam_env. If multiplexing is disabled, then edits are also
|
|
|
|
# reflected with become:false. Rather than emulate existing semantics, simply
|
|
|
|
# always ensure edits are reflects for the next task.
|
|
|
|
try:
|
|
|
|
etc_env_st = os.stat('/etc/environment')
|
|
|
|
except OSError:
|
|
|
|
etc_env_st = None
|
|
|
|
|
|
|
|
try:
|
|
|
|
pam_env_st = os.stat(os.path.expanduser('~/.pam_environment'))
|
|
|
|
except OSError:
|
|
|
|
pam_env_st = None
|
|
|
|
|
|
|
|
|
2018-04-17 16:40:45 +00:00
|
|
|
iteritems = getattr(dict, 'iteritems', dict.items)
|
2018-04-01 17:19:34 +00:00
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2018-04-20 19:47:07 +00:00
|
|
|
def utf8(s):
|
|
|
|
"""
|
|
|
|
Coerce an object to bytes if it is Unicode.
|
|
|
|
"""
|
2018-04-17 16:40:45 +00:00
|
|
|
if isinstance(s, mitogen.core.UnicodeType):
|
2018-04-20 19:47:07 +00:00
|
|
|
s = s.encode('utf-8')
|
|
|
|
return s
|
|
|
|
|
|
|
|
|
2018-04-02 07:59:36 +00:00
|
|
|
def reopen_readonly(fp):
|
|
|
|
"""
|
|
|
|
Replace the file descriptor belonging to the file object `fp` with one
|
|
|
|
open on the same file (`fp.name`), but opened with :py:data:`os.O_RDONLY`.
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
This enables temporary files to be executed on Linux, which usually throws
|
2018-04-02 07:59:36 +00:00
|
|
|
``ETXTBUSY`` if any writeable handle exists pointing to a file passed to
|
|
|
|
`execve()`.
|
|
|
|
"""
|
|
|
|
fd = os.open(fp.name, os.O_RDONLY)
|
|
|
|
os.dup2(fd, fp.fileno())
|
|
|
|
os.close(fd)
|
|
|
|
|
|
|
|
|
2018-08-10 09:06:57 +00:00
|
|
|
def parse_env(fp):
|
|
|
|
"""
|
|
|
|
Parse /etc/environ using roughly the same syntax as pam_env.
|
|
|
|
"""
|
|
|
|
# https://github.com/linux-pam/linux-pam/blob/v1.3.1/modules/pam_env/pam_env.c#L207
|
|
|
|
for line in fp:
|
|
|
|
# ' #export foo=some var ' -> ['#export', 'foo=some var ']
|
|
|
|
bits = shlex.split(line, comments=True)
|
|
|
|
if not bits:
|
|
|
|
continue
|
|
|
|
|
|
|
|
if bits[0] == 'export':
|
|
|
|
bits.pop(0)
|
|
|
|
|
|
|
|
key, sep, value = (' '.join(bits)).partition('=')
|
|
|
|
if sep:
|
|
|
|
os.environ[key] = value
|
|
|
|
|
|
|
|
|
|
|
|
def reload_env(old_st, path):
|
|
|
|
"""
|
|
|
|
Compare the :func:`os.stat` for the pam_env style environmnt file `path`
|
|
|
|
with the previous result `old_st`, which may be :data:`None` if the
|
|
|
|
previous stat attempt failed. Reload its contents if the file has changed
|
|
|
|
or appeared since last attempt.
|
|
|
|
|
|
|
|
:returns:
|
|
|
|
New :func:`os.stat` result. The new call to :func:`reload_env` should
|
|
|
|
pass it as the value of `old_st`.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
path = os.path.expanduser(path)
|
|
|
|
st = os.stat(path)
|
|
|
|
except OSError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if old_st == st:
|
|
|
|
return old_st
|
|
|
|
if st is None:
|
|
|
|
LOG.debug('reload_env(%r): file has disappeared', path)
|
|
|
|
return st
|
|
|
|
|
|
|
|
LOG.debug('reload_env(%r): file has changed or appeared, reloading', path)
|
|
|
|
with open(path) as fp:
|
|
|
|
parse_env(fp)
|
|
|
|
return st
|
|
|
|
|
|
|
|
|
2018-03-31 04:37:11 +00:00
|
|
|
class Runner(object):
|
|
|
|
"""
|
|
|
|
Ansible module runner. After instantiation (with kwargs supplied by the
|
|
|
|
corresponding Planner), `.run()` is invoked, upon which `setup()`,
|
|
|
|
`_run()`, and `revert()` are invoked, with the return value of `_run()`
|
|
|
|
returned by `run()`.
|
|
|
|
|
|
|
|
Subclasses may override `_run`()` and extend `setup()` and `revert()`.
|
2018-05-04 15:50:38 +00:00
|
|
|
|
|
|
|
:param str module:
|
|
|
|
Name of the module to execute, e.g. "shell"
|
|
|
|
:param mitogen.core.Context service_context:
|
|
|
|
Context to which we should direct FileService calls. For now, always
|
|
|
|
the connection multiplexer process on the controller.
|
2018-04-17 16:40:45 +00:00
|
|
|
:param str json_args:
|
2018-06-09 21:11:26 +00:00
|
|
|
Ansible module arguments. A mixture of user and internal keys created
|
|
|
|
by :meth:`ansible.plugins.action.ActionBase._execute_module`.
|
2018-04-17 16:40:45 +00:00
|
|
|
|
|
|
|
This is passed as a string rather than a dict in order to mimic the
|
|
|
|
implicit bytes/str conversion behaviour of a 2.x controller running
|
|
|
|
against a 3.x target.
|
2018-05-04 15:50:38 +00:00
|
|
|
:param dict env:
|
2018-07-10 16:56:05 +00:00
|
|
|
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.
|
2018-06-09 21:11:26 +00:00
|
|
|
:param mitogen.core.ExternalContext econtext:
|
|
|
|
When `detach` is :data:`True`, a reference to the ExternalContext the
|
|
|
|
runner is executing in.
|
|
|
|
:param bool detach:
|
|
|
|
When :data:`True`, indicate the runner should detach the context from
|
|
|
|
its parent after setup has completed successfully.
|
2018-03-31 04:37:11 +00:00
|
|
|
"""
|
2018-07-10 17:29:49 +00:00
|
|
|
def __init__(self, module, service_context, json_args, extra_env=None,
|
|
|
|
cwd=None, env=None, econtext=None, detach=False):
|
2018-04-17 16:40:45 +00:00
|
|
|
self.module = module
|
2018-04-08 22:50:57 +00:00
|
|
|
self.service_context = service_context
|
2018-06-07 15:31:01 +00:00
|
|
|
self.econtext = econtext
|
|
|
|
self.detach = detach
|
2018-04-17 16:40:45 +00:00
|
|
|
self.args = json.loads(json_args)
|
2018-07-10 17:29:49 +00:00
|
|
|
self.extra_env = extra_env
|
2018-03-31 04:37:11 +00:00
|
|
|
self.env = env
|
2018-07-10 16:56:05 +00:00
|
|
|
self.cwd = cwd
|
2018-03-31 04:37:11 +00:00
|
|
|
|
|
|
|
def setup(self):
|
|
|
|
"""
|
2018-05-04 16:55:23 +00:00
|
|
|
Prepare for running a module, including fetching necessary dependencies
|
|
|
|
from the parent, as :meth:`run` may detach prior to beginning
|
|
|
|
execution. The base implementation simply prepares the environment.
|
2018-03-31 04:37:11 +00:00
|
|
|
"""
|
2018-07-10 18:28:12 +00:00
|
|
|
if self.cwd:
|
|
|
|
# For situations like sudo to another non-privileged account, the
|
|
|
|
# CWD could be $HOME of the old account, which could have mode go=,
|
|
|
|
# which means it is impossible to restore the old directory, so
|
|
|
|
# don't even bother.
|
|
|
|
os.chdir(self.cwd)
|
2018-07-10 17:29:49 +00:00
|
|
|
env = dict(self.extra_env or {})
|
|
|
|
if self.env:
|
|
|
|
env.update(self.env)
|
2018-08-10 09:06:57 +00:00
|
|
|
self._setup_environ()
|
2018-07-10 17:29:49 +00:00
|
|
|
self._env = TemporaryEnvironment(env)
|
2018-03-31 04:37:11 +00:00
|
|
|
|
2018-08-10 09:06:57 +00:00
|
|
|
def _setup_environ(self):
|
|
|
|
"""
|
|
|
|
Ensure /etc/environment and ~/.pam_environment are reloaded if their
|
|
|
|
content appears to differ since execution of the previous task. This
|
|
|
|
must happen before TemporaryEnvironment is installed, to ensure changes
|
|
|
|
persist across tasks.
|
|
|
|
"""
|
|
|
|
global etc_env_st
|
|
|
|
etc_env_st = reload_env(etc_env_st, '/etc/environment')
|
|
|
|
|
|
|
|
global pam_env_st
|
|
|
|
pam_env_st = reload_env(pam_env_st, '~/.pam_environment')
|
|
|
|
|
2018-03-31 04:37:11 +00:00
|
|
|
def revert(self):
|
|
|
|
"""
|
|
|
|
Revert any changes made to the process after running a module. The base
|
|
|
|
implementation simply restores the original environment.
|
|
|
|
"""
|
|
|
|
self._env.revert()
|
2018-06-10 14:25:11 +00:00
|
|
|
self._try_cleanup_temp()
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
|
|
|
|
def _cleanup_temp(self):
|
2018-05-04 15:50:38 +00:00
|
|
|
"""
|
|
|
|
Empty temp_dir in time for the next module invocation.
|
|
|
|
"""
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
for name in os.listdir(ansible_mitogen.target.temp_dir):
|
|
|
|
if name in ('.', '..'):
|
|
|
|
continue
|
|
|
|
|
|
|
|
path = os.path.join(ansible_mitogen.target.temp_dir, name)
|
|
|
|
LOG.debug('Deleting %r', path)
|
|
|
|
ansible_mitogen.target.prune_tree(path)
|
2018-03-31 04:37:11 +00:00
|
|
|
|
2018-06-10 14:25:11 +00:00
|
|
|
def _try_cleanup_temp(self):
|
|
|
|
"""
|
|
|
|
During broker shutdown triggered by async task timeout or loss of
|
|
|
|
connection to the parent, it is possible for prune_tree() in
|
|
|
|
target.py::_on_broker_shutdown() to run before _cleanup_temp(), so skip
|
|
|
|
cleanup if the directory or a file disappears from beneath us.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
self._cleanup_temp()
|
|
|
|
except (IOError, OSError) as e:
|
|
|
|
if e.args[0] == errno.ENOENT:
|
|
|
|
return
|
|
|
|
raise
|
|
|
|
|
2018-03-31 04:37:11 +00:00
|
|
|
def _run(self):
|
2018-04-01 11:56:53 +00:00
|
|
|
"""
|
|
|
|
The _run() method is expected to return a dictionary in the form of
|
|
|
|
ActionBase._low_level_execute_command() output, i.e. having::
|
|
|
|
|
|
|
|
{
|
|
|
|
"rc": int,
|
|
|
|
"stdout": "stdout data",
|
|
|
|
"stderr": "stderr data"
|
|
|
|
}
|
|
|
|
"""
|
2018-03-31 04:37:11 +00:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
"""
|
|
|
|
Set up the process environment in preparation for running an Ansible
|
|
|
|
module. This monkey-patches the Ansible libraries in various places to
|
|
|
|
prevent it from trying to kill the process on completion, and to
|
|
|
|
prevent it from reading sys.stdin.
|
|
|
|
|
|
|
|
:returns:
|
|
|
|
Module result dictionary.
|
|
|
|
"""
|
|
|
|
self.setup()
|
2018-06-07 15:31:01 +00:00
|
|
|
if self.detach:
|
|
|
|
self.econtext.detach()
|
|
|
|
|
2018-03-31 04:37:11 +00:00
|
|
|
try:
|
2018-04-20 13:20:05 +00:00
|
|
|
return self._run()
|
2018-03-31 04:37:11 +00:00
|
|
|
finally:
|
|
|
|
self.revert()
|
|
|
|
|
|
|
|
|
2018-05-13 00:47:11 +00:00
|
|
|
class ModuleUtilsImporter(object):
|
|
|
|
"""
|
|
|
|
:param list module_utils:
|
|
|
|
List of `(fullname, path, is_pkg)` tuples.
|
|
|
|
"""
|
|
|
|
def __init__(self, context, module_utils):
|
|
|
|
self._context = context
|
2018-06-19 17:46:30 +00:00
|
|
|
self._by_fullname = dict(
|
|
|
|
(fullname, (path, is_pkg))
|
2018-05-13 00:47:11 +00:00
|
|
|
for fullname, path, is_pkg in module_utils
|
2018-06-19 17:46:30 +00:00
|
|
|
)
|
2018-05-13 00:47:11 +00:00
|
|
|
self._loaded = set()
|
|
|
|
sys.meta_path.insert(0, self)
|
|
|
|
|
|
|
|
def revert(self):
|
|
|
|
sys.meta_path.remove(self)
|
|
|
|
for fullname in self._loaded:
|
|
|
|
sys.modules.pop(fullname, None)
|
|
|
|
|
|
|
|
def find_module(self, fullname, path=None):
|
|
|
|
if fullname in self._by_fullname:
|
|
|
|
return self
|
|
|
|
|
|
|
|
def load_module(self, fullname):
|
|
|
|
path, is_pkg = self._by_fullname[fullname]
|
2018-06-07 15:48:42 +00:00
|
|
|
source = ansible_mitogen.target.get_small_file(self._context, path)
|
2018-04-17 16:40:45 +00:00
|
|
|
code = compile(source, path, 'exec', 0, 1)
|
2018-05-13 00:47:11 +00:00
|
|
|
mod = sys.modules.setdefault(fullname, imp.new_module(fullname))
|
|
|
|
mod.__file__ = "master:%s" % (path,)
|
|
|
|
mod.__loader__ = self
|
|
|
|
if is_pkg:
|
|
|
|
mod.__path__ = []
|
2018-07-28 21:09:21 +00:00
|
|
|
mod.__package__ = str(fullname)
|
2018-05-13 00:47:11 +00:00
|
|
|
else:
|
2018-07-28 21:09:21 +00:00
|
|
|
mod.__package__ = str(fullname.rpartition('.')[0])
|
2018-05-13 00:47:11 +00:00
|
|
|
exec(code, mod.__dict__)
|
|
|
|
self._loaded.add(fullname)
|
|
|
|
return mod
|
|
|
|
|
|
|
|
|
2018-03-30 10:10:38 +00:00
|
|
|
class TemporaryEnvironment(object):
|
2018-07-10 17:29:49 +00:00
|
|
|
"""
|
|
|
|
Apply environment changes from `env` until :meth:`revert` is called. Values
|
|
|
|
in the dict may be :data:`None` to indicate the relevant key should be
|
|
|
|
deleted.
|
|
|
|
"""
|
2018-03-30 10:10:38 +00:00
|
|
|
def __init__(self, env=None):
|
2018-07-10 17:29:49 +00:00
|
|
|
self.original = dict(os.environ)
|
2018-03-30 10:10:38 +00:00
|
|
|
self.env = env or {}
|
2018-07-10 17:29:49 +00:00
|
|
|
for key, value in iteritems(self.env):
|
|
|
|
if value is None:
|
|
|
|
os.environ.pop(key, None)
|
|
|
|
else:
|
|
|
|
os.environ[key] = str(value)
|
2018-03-30 10:10:38 +00:00
|
|
|
|
|
|
|
def revert(self):
|
2018-07-19 14:58:38 +00:00
|
|
|
"""
|
|
|
|
Revert changes made by the module to the process environment. This must
|
|
|
|
always run, as some modules (e.g. git.py) set variables like GIT_SSH
|
|
|
|
that must be cleared out between runs.
|
|
|
|
"""
|
|
|
|
os.environ.clear()
|
|
|
|
os.environ.update(self.original)
|
2018-03-30 10:10:38 +00:00
|
|
|
|
|
|
|
|
2018-04-01 20:10:04 +00:00
|
|
|
class TemporaryArgv(object):
|
|
|
|
def __init__(self, argv):
|
|
|
|
self.original = sys.argv[:]
|
2018-05-04 00:50:48 +00:00
|
|
|
sys.argv[:] = map(str, argv)
|
2018-03-30 10:10:38 +00:00
|
|
|
|
|
|
|
def revert(self):
|
2018-04-01 20:10:04 +00:00
|
|
|
sys.argv[:] = self.original
|
2018-03-30 10:10:38 +00:00
|
|
|
|
|
|
|
|
2018-04-01 20:10:04 +00:00
|
|
|
class NewStyleStdio(object):
|
2018-03-30 10:10:38 +00:00
|
|
|
"""
|
2018-03-30 17:20:17 +00:00
|
|
|
Patch ansible.module_utils.basic argument globals.
|
2018-03-30 10:10:38 +00:00
|
|
|
"""
|
|
|
|
def __init__(self, args):
|
2018-04-01 20:10:04 +00:00
|
|
|
self.original_stdout = sys.stdout
|
|
|
|
self.original_stderr = sys.stderr
|
|
|
|
self.original_stdin = sys.stdin
|
2018-04-17 16:40:45 +00:00
|
|
|
sys.stdout = StringIO()
|
|
|
|
sys.stderr = StringIO()
|
|
|
|
encoded = json.dumps({'ANSIBLE_MODULE_ARGS': args})
|
|
|
|
ansible.module_utils.basic._ANSIBLE_ARGS = utf8(encoded)
|
|
|
|
sys.stdin = StringIO(mitogen.core.to_text(encoded))
|
2018-03-30 10:10:38 +00:00
|
|
|
|
|
|
|
def revert(self):
|
2018-04-01 20:10:04 +00:00
|
|
|
sys.stdout = self.original_stdout
|
|
|
|
sys.stderr = self.original_stderr
|
|
|
|
sys.stdin = self.original_stdin
|
|
|
|
ansible.module_utils.basic._ANSIBLE_ARGS = '{}'
|
2018-03-30 10:10:38 +00:00
|
|
|
|
|
|
|
|
2018-04-01 17:19:34 +00:00
|
|
|
class ProgramRunner(Runner):
|
2018-05-04 15:50:38 +00:00
|
|
|
"""
|
|
|
|
Base class for runners that run external programs.
|
|
|
|
|
|
|
|
:param str path:
|
|
|
|
Absolute path to the program file on the master, as it can be retrieved
|
2018-05-29 16:07:58 +00:00
|
|
|
via :class:`mitogen.service.FileService`.
|
2018-05-04 15:50:38 +00:00
|
|
|
:param bool emulate_tty:
|
|
|
|
If :data:`True`, execute the program with `stdout` and `stderr` merged
|
|
|
|
into a single pipe, emulating Ansible behaviour when an SSH TTY is in
|
|
|
|
use.
|
|
|
|
"""
|
|
|
|
def __init__(self, path, emulate_tty=None, **kwargs):
|
2018-04-01 17:19:34 +00:00
|
|
|
super(ProgramRunner, self).__init__(**kwargs)
|
2018-05-04 15:50:38 +00:00
|
|
|
self.emulate_tty = emulate_tty
|
2018-04-17 16:40:45 +00:00
|
|
|
self.path = path
|
2018-03-30 17:20:17 +00:00
|
|
|
|
2018-03-30 10:10:38 +00:00
|
|
|
def setup(self):
|
2018-04-01 17:19:34 +00:00
|
|
|
super(ProgramRunner, self).setup()
|
2018-04-01 10:32:45 +00:00
|
|
|
self._setup_program()
|
2018-03-30 10:10:38 +00:00
|
|
|
|
2018-05-04 16:55:23 +00:00
|
|
|
def _get_program_filename(self):
|
|
|
|
"""
|
|
|
|
Return the filename used for program on disk. Ansible uses the original
|
|
|
|
filename for non-Ansiballz runs, and "ansible_module_+filename for
|
|
|
|
Ansiballz runs.
|
|
|
|
"""
|
|
|
|
return os.path.basename(self.path)
|
|
|
|
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
program_fp = None
|
|
|
|
|
2018-03-30 10:10:38 +00:00
|
|
|
def _setup_program(self):
|
|
|
|
"""
|
|
|
|
Create a temporary file containing the program code. The code is
|
2018-04-01 10:32:45 +00:00
|
|
|
fetched via :meth:`_get_program`.
|
2018-03-30 10:10:38 +00:00
|
|
|
"""
|
2018-05-04 16:55:23 +00:00
|
|
|
filename = self._get_program_filename()
|
|
|
|
path = os.path.join(ansible_mitogen.target.temp_dir, filename)
|
2018-05-04 00:50:48 +00:00
|
|
|
self.program_fp = open(path, 'wb')
|
2018-04-01 17:19:34 +00:00
|
|
|
self.program_fp.write(self._get_program())
|
|
|
|
self.program_fp.flush()
|
|
|
|
os.chmod(self.program_fp.name, int('0700', 8))
|
2018-04-02 07:59:36 +00:00
|
|
|
reopen_readonly(self.program_fp)
|
2018-03-30 10:10:38 +00:00
|
|
|
|
2018-04-01 17:19:34 +00:00
|
|
|
def _get_program(self):
|
2018-03-30 10:10:38 +00:00
|
|
|
"""
|
2018-04-01 17:19:34 +00:00
|
|
|
Fetch the module binary from the master if necessary.
|
2018-03-30 10:10:38 +00:00
|
|
|
"""
|
2018-06-07 15:48:42 +00:00
|
|
|
return ansible_mitogen.target.get_small_file(
|
2018-04-01 17:19:34 +00:00
|
|
|
context=self.service_context,
|
|
|
|
path=self.path,
|
2018-03-30 10:10:38 +00:00
|
|
|
)
|
2018-04-01 17:19:34 +00:00
|
|
|
|
|
|
|
def _get_program_args(self):
|
2018-07-24 00:40:17 +00:00
|
|
|
"""
|
|
|
|
Return any arguments to pass to the program.
|
|
|
|
"""
|
|
|
|
return []
|
2018-03-30 10:10:38 +00:00
|
|
|
|
|
|
|
def revert(self):
|
|
|
|
"""
|
2018-04-01 17:19:34 +00:00
|
|
|
Delete the temporary program file.
|
2018-03-30 10:10:38 +00:00
|
|
|
"""
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
if self.program_fp:
|
|
|
|
self.program_fp.close()
|
2018-04-04 13:05:57 +00:00
|
|
|
super(ProgramRunner, self).revert()
|
2018-03-30 10:10:38 +00:00
|
|
|
|
2018-07-24 00:40:17 +00:00
|
|
|
def _get_argv(self):
|
|
|
|
"""
|
|
|
|
Return the final argument vector used to execute the program.
|
|
|
|
"""
|
2018-07-24 04:41:34 +00:00
|
|
|
return [
|
|
|
|
self.args['_ansible_shell_executable'],
|
|
|
|
'-c',
|
|
|
|
self._get_shell_fragment(),
|
|
|
|
]
|
|
|
|
|
|
|
|
def _get_shell_fragment(self):
|
|
|
|
return "%s %s" % (
|
|
|
|
shlex_quote(self.program_fp.name),
|
|
|
|
' '.join(map(shlex_quote, self._get_program_args())),
|
|
|
|
)
|
2018-07-24 00:40:17 +00:00
|
|
|
|
2018-03-30 10:10:38 +00:00
|
|
|
def _run(self):
|
2018-03-30 17:20:17 +00:00
|
|
|
try:
|
2018-04-06 16:22:45 +00:00
|
|
|
rc, stdout, stderr = ansible_mitogen.target.exec_args(
|
2018-07-24 00:40:17 +00:00
|
|
|
args=self._get_argv(),
|
2018-04-20 13:20:05 +00:00
|
|
|
emulate_tty=self.emulate_tty,
|
2018-03-30 17:20:17 +00:00
|
|
|
)
|
2018-04-17 16:40:45 +00:00
|
|
|
except Exception as e:
|
2018-07-24 00:40:17 +00:00
|
|
|
LOG.exception('While running %s', self._get_argv())
|
2018-03-31 04:37:11 +00:00
|
|
|
return {
|
2018-04-01 11:56:53 +00:00
|
|
|
'rc': 1,
|
|
|
|
'stdout': '',
|
|
|
|
'stderr': '%s: %s' % (type(e), e),
|
2018-03-31 04:37:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
'rc': rc,
|
2018-04-17 16:40:45 +00:00
|
|
|
'stdout': mitogen.core.to_text(stdout),
|
|
|
|
'stderr': mitogen.core.to_text(stderr),
|
2018-03-31 04:37:11 +00:00
|
|
|
}
|
2018-03-30 10:10:38 +00:00
|
|
|
|
|
|
|
|
2018-04-01 17:19:34 +00:00
|
|
|
class ArgsFileRunner(Runner):
|
|
|
|
def setup(self):
|
|
|
|
super(ArgsFileRunner, self).setup()
|
|
|
|
self._setup_args()
|
|
|
|
|
|
|
|
def _setup_args(self):
|
|
|
|
"""
|
|
|
|
Create a temporary file containing the module's arguments. The
|
|
|
|
arguments are formatted via :meth:`_get_args`.
|
|
|
|
"""
|
|
|
|
self.args_fp = tempfile.NamedTemporaryFile(
|
|
|
|
prefix='ansible_mitogen',
|
|
|
|
suffix='-args',
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
dir=ansible_mitogen.target.temp_dir,
|
2018-04-01 17:19:34 +00:00
|
|
|
)
|
2018-04-17 16:40:45 +00:00
|
|
|
self.args_fp.write(utf8(self._get_args_contents()))
|
2018-04-01 17:19:34 +00:00
|
|
|
self.args_fp.flush()
|
2018-04-02 07:59:36 +00:00
|
|
|
reopen_readonly(self.program_fp)
|
2018-04-01 17:19:34 +00:00
|
|
|
|
|
|
|
def _get_args_contents(self):
|
|
|
|
"""
|
|
|
|
Return the module arguments formatted as JSON.
|
|
|
|
"""
|
|
|
|
return json.dumps(self.args)
|
|
|
|
|
|
|
|
def _get_program_args(self):
|
2018-07-24 00:40:17 +00:00
|
|
|
return [self.args_fp.name]
|
2018-04-01 17:19:34 +00:00
|
|
|
|
|
|
|
def revert(self):
|
|
|
|
"""
|
|
|
|
Delete the temporary argument file.
|
|
|
|
"""
|
|
|
|
self.args_fp.close()
|
2018-04-04 13:05:57 +00:00
|
|
|
super(ArgsFileRunner, self).revert()
|
2018-04-01 17:19:34 +00:00
|
|
|
|
|
|
|
|
|
|
|
class BinaryRunner(ArgsFileRunner, ProgramRunner):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ScriptRunner(ProgramRunner):
|
2018-07-24 00:40:17 +00:00
|
|
|
def __init__(self, interpreter_fragment, is_python, **kwargs):
|
2018-04-01 17:19:34 +00:00
|
|
|
super(ScriptRunner, self).__init__(**kwargs)
|
2018-07-24 00:40:17 +00:00
|
|
|
self.interpreter_fragment = interpreter_fragment
|
|
|
|
self.is_python = is_python
|
2018-04-01 17:19:34 +00:00
|
|
|
|
|
|
|
b_ENCODING_STRING = b'# -*- coding: utf-8 -*-'
|
|
|
|
|
2018-04-01 10:32:45 +00:00
|
|
|
def _get_program(self):
|
2018-04-01 17:19:34 +00:00
|
|
|
return self._rewrite_source(
|
|
|
|
super(ScriptRunner, self)._get_program()
|
|
|
|
)
|
|
|
|
|
2018-07-24 00:40:17 +00:00
|
|
|
def _get_argv(self):
|
|
|
|
return [
|
|
|
|
self.args['_ansible_shell_executable'],
|
|
|
|
'-c',
|
|
|
|
self._get_shell_fragment(),
|
|
|
|
]
|
|
|
|
|
|
|
|
def _get_shell_fragment(self):
|
|
|
|
"""
|
|
|
|
Scripts are eligible for having their hashbang line rewritten, and to
|
|
|
|
be executed via /bin/sh using the ansible_*_interpreter value used as a
|
|
|
|
shell fragment prefixing to the invocation.
|
|
|
|
"""
|
|
|
|
return "%s %s %s" % (
|
|
|
|
self.interpreter_fragment,
|
|
|
|
shlex_quote(self.program_fp.name),
|
|
|
|
' '.join(map(shlex_quote, self._get_program_args())),
|
|
|
|
)
|
|
|
|
|
2018-04-01 17:19:34 +00:00
|
|
|
def _rewrite_source(self, s):
|
|
|
|
"""
|
|
|
|
Mutate the source according to the per-task parameters.
|
|
|
|
"""
|
2018-07-24 00:40:17 +00:00
|
|
|
# While Ansible rewrites the #! using ansible_*_interpreter, it is
|
|
|
|
# never actually used to execute the script, instead it is a shell
|
|
|
|
# fragment consumed by shell/__init__.py::build_module_command().
|
|
|
|
new = [b'#!' + utf8(self.interpreter_fragment)]
|
|
|
|
if self.is_python:
|
2018-04-01 17:19:34 +00:00
|
|
|
new.append(self.b_ENCODING_STRING)
|
|
|
|
|
2018-04-17 16:40:45 +00:00
|
|
|
_, _, rest = s.partition(b'\n')
|
2018-04-01 17:19:34 +00:00
|
|
|
new.append(rest)
|
2018-04-17 16:40:45 +00:00
|
|
|
return b'\n'.join(new)
|
2018-04-01 17:19:34 +00:00
|
|
|
|
|
|
|
|
2018-04-01 20:10:04 +00:00
|
|
|
class NewStyleRunner(ScriptRunner):
|
|
|
|
"""
|
|
|
|
Execute a new-style Ansible module, where Module Replacer-related tricks
|
|
|
|
aren't required.
|
|
|
|
"""
|
2018-04-01 20:40:06 +00:00
|
|
|
#: path => new-style module bytecode.
|
|
|
|
_code_by_path = {}
|
|
|
|
|
2018-06-07 15:48:42 +00:00
|
|
|
def __init__(self, module_map, **kwargs):
|
2018-05-12 13:14:35 +00:00
|
|
|
super(NewStyleRunner, self).__init__(**kwargs)
|
2018-06-07 15:48:42 +00:00
|
|
|
self.module_map = module_map
|
|
|
|
|
|
|
|
def _setup_imports(self):
|
|
|
|
"""
|
2018-06-09 23:05:24 +00:00
|
|
|
Ensure the local importer and PushFileService has everything for the
|
|
|
|
Ansible module before setup() completes, but before detach() is called
|
|
|
|
in an asynchronous task.
|
2018-06-07 15:48:42 +00:00
|
|
|
|
|
|
|
The master automatically streams modules towards us concurrent to the
|
|
|
|
runner invocation, however there is no public API to synchronize on the
|
|
|
|
completion of those preloads. Instead simply reuse the importer's
|
|
|
|
synchronization mechanism by importing everything the module will need
|
|
|
|
prior to detaching.
|
|
|
|
"""
|
|
|
|
for fullname, _, _ in self.module_map['custom']:
|
|
|
|
mitogen.core.import_module(fullname)
|
|
|
|
for fullname in self.module_map['builtin']:
|
|
|
|
mitogen.core.import_module(fullname)
|
2018-05-12 13:14:35 +00:00
|
|
|
|
2018-04-01 20:10:04 +00:00
|
|
|
def setup(self):
|
|
|
|
super(NewStyleRunner, self).setup()
|
2018-06-07 15:48:42 +00:00
|
|
|
|
2018-04-01 20:10:04 +00:00
|
|
|
self._stdio = NewStyleStdio(self.args)
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
# It is possible that not supplying the script filename will break some
|
|
|
|
# module, but this has never been a bug report. Instead act like an
|
|
|
|
# interpreter that had its script piped on stdin.
|
|
|
|
self._argv = TemporaryArgv([''])
|
2018-05-13 00:47:11 +00:00
|
|
|
self._importer = ModuleUtilsImporter(
|
|
|
|
context=self.service_context,
|
2018-06-07 15:48:42 +00:00
|
|
|
module_utils=self.module_map['custom'],
|
2018-05-13 00:47:11 +00:00
|
|
|
)
|
2018-06-07 15:48:42 +00:00
|
|
|
self._setup_imports()
|
2018-05-07 10:46:24 +00:00
|
|
|
if libc__res_init:
|
|
|
|
libc__res_init()
|
2018-04-01 20:10:04 +00:00
|
|
|
|
|
|
|
def revert(self):
|
2018-04-01 20:40:06 +00:00
|
|
|
self._argv.revert()
|
2018-04-01 20:10:04 +00:00
|
|
|
self._stdio.revert()
|
2018-04-01 20:40:06 +00:00
|
|
|
super(NewStyleRunner, self).revert()
|
2018-04-01 20:10:04 +00:00
|
|
|
|
2018-05-04 16:55:23 +00:00
|
|
|
def _get_program_filename(self):
|
|
|
|
"""
|
|
|
|
See ProgramRunner._get_program_filename().
|
|
|
|
"""
|
|
|
|
return 'ansible_module_' + os.path.basename(self.path)
|
|
|
|
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
def _setup_args(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def _setup_program(self):
|
2018-06-09 23:59:24 +00:00
|
|
|
self.source = ansible_mitogen.target.get_small_file(
|
|
|
|
context=self.service_context,
|
|
|
|
path=self.path,
|
|
|
|
)
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
|
2018-04-01 20:40:06 +00:00
|
|
|
def _get_code(self):
|
|
|
|
try:
|
|
|
|
return self._code_by_path[self.path]
|
|
|
|
except KeyError:
|
|
|
|
return self._code_by_path.setdefault(self.path, compile(
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
source=self.source,
|
2018-04-17 16:40:45 +00:00
|
|
|
filename="master:" + self.path,
|
2018-04-01 20:40:06 +00:00
|
|
|
mode='exec',
|
|
|
|
dont_inherit=True,
|
|
|
|
))
|
2018-04-01 20:10:04 +00:00
|
|
|
|
2018-04-17 16:40:45 +00:00
|
|
|
if mitogen.core.PY3:
|
|
|
|
main_module_name = '__main__'
|
|
|
|
else:
|
|
|
|
main_module_name = b'__main__'
|
|
|
|
|
2018-04-01 20:10:04 +00:00
|
|
|
def _run(self):
|
2018-04-01 20:40:06 +00:00
|
|
|
code = self._get_code()
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
|
2018-04-17 16:40:45 +00:00
|
|
|
mod = types.ModuleType(self.main_module_name)
|
2018-04-22 12:27:41 +00:00
|
|
|
mod.__package__ = None
|
issue #199: ansible: stop writing temp files for new style modules
While adding support for non-new style module types, NewStyleRunner
began writing modules to a temporary file, and sys.argv was patched to
actually include the script filename. The argv change was never required
to fix any particular bug, and a search of the standard modules reveals
no argv users. Update argv[0] to be '', like an interactive interpreter
would have.
While fixing #210, new style runner began setting __file__ to the
temporary file path in order to allow apt.py to discover the Ansiballz
temporary directory. 5 out of 1,516 standard modules follow this
pattern, but in each case, none actually attempt to access __file__,
they just call dirname on it. Therefore do not write the contents of
file, simply set it to the path as it would exist, within a real
temporary directory.
Finally move temporary directory creation out of runner and into target.
Now a single directory exists for the duration of a run, and is emptied
by runner.py as necessary after each task invocation.
This could be further extended to stop rewriting non-new-style modules
in a with_items loop, but that's another step.
Finally the last bullet point in the documentation almost isn't a lie
again.
2018-05-04 05:16:25 +00:00
|
|
|
# Some Ansible modules use __file__ to find the Ansiballz temporary
|
|
|
|
# directory. We must provide some temporary path in __file__, but we
|
|
|
|
# don't want to pointlessly write the module to disk when it never
|
|
|
|
# actually needs to exist. So just pass the filename as it would exist.
|
|
|
|
mod.__file__ = os.path.join(
|
|
|
|
ansible_mitogen.target.temp_dir,
|
|
|
|
'ansible_module_' + os.path.basename(self.path),
|
|
|
|
)
|
2018-04-01 20:10:04 +00:00
|
|
|
|
2018-04-17 16:40:45 +00:00
|
|
|
exc = None
|
2018-04-01 20:10:04 +00:00
|
|
|
try:
|
2018-04-17 16:40:45 +00:00
|
|
|
if mitogen.core.PY3:
|
|
|
|
exec(code, vars(mod))
|
|
|
|
else:
|
|
|
|
exec('exec code in vars(mod)')
|
|
|
|
except SystemExit as e:
|
|
|
|
exc = e
|
2018-04-01 20:10:04 +00:00
|
|
|
|
|
|
|
return {
|
2018-04-17 16:40:45 +00:00
|
|
|
'rc': exc.args[0] if exc else 2,
|
|
|
|
'stdout': mitogen.core.to_text(sys.stdout.getvalue()),
|
|
|
|
'stderr': mitogen.core.to_text(sys.stderr.getvalue()),
|
2018-04-01 20:10:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-04-01 20:20:58 +00:00
|
|
|
class JsonArgsRunner(ScriptRunner):
|
2018-04-17 16:40:45 +00:00
|
|
|
JSON_ARGS = b'<<INCLUDE_ANSIBLE_MODULE_JSON_ARGS>>'
|
2018-04-01 17:19:34 +00:00
|
|
|
|
|
|
|
def _get_args_contents(self):
|
2018-04-17 16:40:45 +00:00
|
|
|
return json.dumps(self.args).encode()
|
2018-04-01 17:19:34 +00:00
|
|
|
|
|
|
|
def _rewrite_source(self, s):
|
|
|
|
return (
|
2018-04-01 20:20:58 +00:00
|
|
|
super(JsonArgsRunner, self)._rewrite_source(s)
|
2018-04-01 17:19:34 +00:00
|
|
|
.replace(self.JSON_ARGS, self._get_args_contents())
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
class WantJsonRunner(ArgsFileRunner, ScriptRunner):
|
|
|
|
pass
|
|
|
|
|
2018-03-30 10:10:38 +00:00
|
|
|
|
2018-04-01 18:01:18 +00:00
|
|
|
class OldStyleRunner(ArgsFileRunner, ScriptRunner):
|
2018-04-01 17:19:34 +00:00
|
|
|
def _get_args_contents(self):
|
2018-03-30 10:10:38 +00:00
|
|
|
"""
|
|
|
|
Mimic the argument formatting behaviour of
|
|
|
|
ActionBase._execute_module().
|
|
|
|
"""
|
|
|
|
return ' '.join(
|
|
|
|
'%s=%s' % (key, shlex_quote(str(self.args[key])))
|
|
|
|
for key in self.args
|
2018-04-01 20:10:04 +00:00
|
|
|
) + ' ' # Bug-for-bug :(
|