diff --git a/.coveragerc b/.coveragerc index c8283bab61f..eb690592ac6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -23,3 +23,4 @@ omit = */python*/distutils/* */pyshared/* */pytest + */AnsiballZ_*.py diff --git a/changelogs/fragments/ansiballz_one_interpreter.yml b/changelogs/fragments/ansiballz_one_interpreter.yml new file mode 100644 index 00000000000..7dd6abed2aa --- /dev/null +++ b/changelogs/fragments/ansiballz_one_interpreter.yml @@ -0,0 +1,5 @@ +--- +minor_changes: +- Ansible-2.7 changes the Ansiballz strategy for running modules remotely so + that invoking a module only needs to invoke python once per module on the + remote machine instead of twice. diff --git a/changelogs/fragments/deprecated-__file__.yaml b/changelogs/fragments/deprecated-__file__.yaml new file mode 100644 index 00000000000..6ca23a66cbb --- /dev/null +++ b/changelogs/fragments/deprecated-__file__.yaml @@ -0,0 +1,5 @@ +--- +deprecated_features: +- Modules will no longer be able to rely on the __file__ attribute pointing to + a real file. If your third party module is using __file__ for something it + should be changed before 2.8. See the 2.7 porting guide for more information. diff --git a/docs/docsite/rst/porting_guides/porting_guide_2.7.rst b/docs/docsite/rst/porting_guides/porting_guide_2.7.rst index a6837d7faca..286e3331640 100644 --- a/docs/docsite/rst/porting_guides/porting_guide_2.7.rst +++ b/docs/docsite/rst/porting_guides/porting_guide_2.7.rst @@ -38,6 +38,26 @@ There is an important difference in the way that ``include_role`` (dynamic) will Deprecated ========== +Expedited Deprecation: Use of ``__file__`` in ``AnsibleModule`` +--------------------------------------------------------------- + +.. note:: The use of the ``__file__`` variable is deprecated in Ansible 2.7 and **will be eliminated in Ansible 2.8**. This is much quicker than our usual 4-release deprecation cycle. + +We are deprecating the use of the ``__file__`` variable to refer to the file containing the currently-running code. This common Python technique for finding a filesystem path does not always work (even in vanilla Python). Sometimes a Python module can be imported from a virtual location (like inside of a zip file). When this happens, the ``__file__`` variable will reference a virtual location pointing to inside of the zip file. This can cause problems if, for instance, the code was trying to use ``__file__`` to find the directory containing the python module to write some temporary information. + +Before the introduction of AnsiBallZ in Ansible 2.1, using ``__file__`` worked in ``AnsibleModule`` sometimes, but any module that used it would fail when pipelining was turned on (because the module would be piped into the python interpreter's standard input, so ``__file__`` wouldn't contain a file path). AnsiBallZ unintentionally made using ``__file__`` always work, by always creating a temporary file for ``AnsibleModule`` to reside in. + +Ansible 2.8 will no longer create a temporary file for ``AnsibleModule``; instead it will read the file out of a zip file. This change should speed up module execution, but it does mean that starting with Ansible 2.8, referencing ``__file__`` will always fail in ``AnsibleModule``. + +If you are the author of a third-party module which uses ``__file__`` with ``AnsibleModule``, please update your module(s) now, while the use of ``__file__`` is deprecated but still available. The most common use of ``__file__`` is to find a directory to write a temporary file. In Ansible 2.5 and above, you can use the ``tmpdir`` attribute on an ``AnsibleModule`` instance instead, as shown in this code from the :ref:`apt module `: + +.. code-block:: diff + + - tempdir = os.path.dirname(__file__) + - package = os.path.join(tempdir, to_native(deb.rsplit('/', 1)[1])) + + package = os.path.join(module.tmpdir, to_native(deb.rsplit('/', 1)[1])) + + Using a loop on a package module via squash_actions --------------------------------------------------- diff --git a/hacking/test-module b/hacking/test-module index c56b8b72166..b0764982a45 100755 --- a/hacking/test-module +++ b/hacking/test-module @@ -156,7 +156,7 @@ def boilerplate_module(modfile, args, interpreters, check, destfile): task_vars=task_vars ) - if module_style == 'new' and 'ANSIBALLZ_WRAPPER = True' in to_native(module_data): + if module_style == 'new' and '_ANSIBALLZ_WRAPPER = True' in to_native(module_data): module_style = 'ansiballz' modfile2_path = os.path.expanduser(destfile) @@ -192,7 +192,7 @@ def ansiballz_setup(modfile, modname, interpreters): debug_dir = lines[1].strip() argsfile = os.path.join(debug_dir, 'args') - modfile = os.path.join(debug_dir, 'ansible_module_%s.py' % modname) + modfile = os.path.join(debug_dir, '__main__.py') print("* ansiballz module detected; extracted module source to: %s" % debug_dir) return modfile, argsfile diff --git a/lib/ansible/executor/module_common.py b/lib/ansible/executor/module_common.py index 1c59af14ba4..8842b1e7657 100644 --- a/lib/ansible/executor/module_common.py +++ b/lib/ansible/executor/module_common.py @@ -70,7 +70,7 @@ _MODULE_UTILS_PATH = os.path.join(os.path.dirname(__file__), '..', 'module_utils ANSIBALLZ_TEMPLATE = u'''%(shebang)s %(coding)s -ANSIBALLZ_WRAPPER = True # For test-module script to tell this is a ANSIBALLZ_WRAPPER +_ANSIBALLZ_WRAPPER = True # For test-module script to tell this is a ANSIBALLZ_WRAPPER # This code is part of Ansible, but is an independent component. # The code in this particular templatable string, and this templatable string # only, is BSD licensed. Modules which end up using this snippet, which is @@ -98,201 +98,193 @@ ANSIBALLZ_WRAPPER = True # For test-module script to tell this is a ANSIBALLZ_WR # 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. -import os -import os.path -import sys -import __main__ +def _ansiballz_main(): + import os + import os.path + import sys + import __main__ -# For some distros and python versions we pick up this script in the temporary -# directory. This leads to problems when the ansible module masks a python -# library that another import needs. We have not figured out what about the -# specific distros and python versions causes this to behave differently. -# -# Tested distros: -# Fedora23 with python3.4 Works -# Ubuntu15.10 with python2.7 Works -# Ubuntu15.10 with python3.4 Fails without this -# Ubuntu16.04.1 with python3.5 Fails without this -# To test on another platform: -# * use the copy module (since this shadows the stdlib copy module) -# * Turn off pipelining -# * Make sure that the destination file does not exist -# * ansible ubuntu16-test -m copy -a 'src=/etc/motd dest=/var/tmp/m' -# This will traceback in shutil. Looking at the complete traceback will show -# that shutil is importing copy which finds the ansible module instead of the -# stdlib module -scriptdir = None -try: - scriptdir = os.path.dirname(os.path.realpath(__main__.__file__)) -except (AttributeError, OSError): - # Some platforms don't set __file__ when reading from stdin - # OSX raises OSError if using abspath() in a directory we don't have - # permission to read (realpath calls abspath) - pass -if scriptdir is not None: - sys.path = [p for p in sys.path if p != scriptdir] + # For some distros and python versions we pick up this script in the temporary + # directory. This leads to problems when the ansible module masks a python + # library that another import needs. We have not figured out what about the + # specific distros and python versions causes this to behave differently. + # + # Tested distros: + # Fedora23 with python3.4 Works + # Ubuntu15.10 with python2.7 Works + # Ubuntu15.10 with python3.4 Fails without this + # Ubuntu16.04.1 with python3.5 Fails without this + # To test on another platform: + # * use the copy module (since this shadows the stdlib copy module) + # * Turn off pipelining + # * Make sure that the destination file does not exist + # * ansible ubuntu16-test -m copy -a 'src=/etc/motd dest=/var/tmp/m' + # This will traceback in shutil. Looking at the complete traceback will show + # that shutil is importing copy which finds the ansible module instead of the + # stdlib module + scriptdir = None + try: + scriptdir = os.path.dirname(os.path.realpath(__main__.__file__)) + except (AttributeError, OSError): + # Some platforms don't set __file__ when reading from stdin + # OSX raises OSError if using abspath() in a directory we don't have + # permission to read (realpath calls abspath) + pass + if scriptdir is not None: + sys.path = [p for p in sys.path if p != scriptdir] -import base64 -import shutil -import zipfile -import tempfile -import subprocess + import base64 + import shutil + import tempfile + import zipimport + import zipfile -if sys.version_info < (3,): - bytes = str - PY3 = False -else: - unicode = str - PY3 = True - -ZIPDATA = """%(zipdata)s""" - -def invoke_module(module, modlib_path, json_params): - pythonpath = os.environ.get('PYTHONPATH') - if pythonpath: - os.environ['PYTHONPATH'] = ':'.join((modlib_path, pythonpath)) + if sys.version_info < (3,): + bytes = str + PY3 = False else: - os.environ['PYTHONPATH'] = modlib_path + unicode = str + PY3 = True - p = subprocess.Popen([%(interpreter)s, module], env=os.environ, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) - (stdout, stderr) = p.communicate(json_params) + ZIPDATA = """%(zipdata)s""" - if not isinstance(stderr, (bytes, unicode)): - stderr = stderr.read() - if not isinstance(stdout, (bytes, unicode)): - stdout = stdout.read() - if PY3: - sys.stderr.buffer.write(stderr) - sys.stdout.buffer.write(stdout) - else: - sys.stderr.write(stderr) - sys.stdout.write(stdout) - return p.returncode + def invoke_module(modlib_path, json_params): + # When installed via setuptools (including python setup.py install), + # ansible may be installed with an easy-install.pth file. That file + # may load the system-wide install of ansible rather than the one in + # the module. sitecustomize is the only way to override that setting. + z = zipfile.ZipFile(modlib_path, mode='a') -def debug(command, zipped_mod, json_params): - # The code here normally doesn't run. It's only used for debugging on the - # remote machine. - # - # The subcommands in this function make it easier to debug ansiballz - # modules. Here's the basic steps: - # - # Run ansible with the environment variable: ANSIBLE_KEEP_REMOTE_FILES=1 and -vvv - # to save the module file remotely:: - # $ ANSIBLE_KEEP_REMOTE_FILES=1 ansible host1 -m ping -a 'data=october' -vvv - # - # Part of the verbose output will tell you where on the remote machine the - # module was written to:: - # [...] - # SSH: EXEC ssh -C -q -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o - # PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o - # ControlPath=/home/badger/.ansible/cp/ansible-ssh-%%h-%%p-%%r -tt rhel7 '/bin/sh -c '"'"'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 - # LC_MESSAGES=en_US.UTF-8 /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping'"'"'' - # [...] - # - # Login to the remote machine and run the module file via from the previous - # step with the explode subcommand to extract the module payload into - # source files:: - # $ ssh host1 - # $ /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping explode - # Module expanded into: - # /home/badger/.ansible/tmp/ansible-tmp-1461173408.08-279692652635227/ansible - # - # You can now edit the source files to instrument the code or experiment with - # different parameter values. When you're ready to run the code you've modified - # (instead of the code from the actual zipped module), use the execute subcommand like this:: - # $ /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping execute + # py3: modlib_path will be text, py2: it's bytes. Need bytes at the end + sitecustomize = u'import sys\\nsys.path.insert(0,"%%s")\\n' %% modlib_path + sitecustomize = sitecustomize.encode('utf-8') + # Use a ZipInfo to work around zipfile limitation on hosts with + # clocks set to a pre-1980 year (for instance, Raspberry Pi) + zinfo = zipfile.ZipInfo() + zinfo.filename = 'sitecustomize.py' + zinfo.date_time = ( %(year)i, %(month)i, %(day)i, %(hour)i, %(minute)i, %(second)i) + z.writestr(zinfo, sitecustomize) + z.close() - # Okay to use __file__ here because we're running from a kept file - basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'debug_dir') - args_path = os.path.join(basedir, 'args') - script_path = os.path.join(basedir, 'ansible_module_%(ansible_module)s.py') + # Put the zipped up module_utils we got from the controller first in the python path so that we + # can monkeypatch the right basic + sys.path.insert(0, modlib_path) - if command == 'explode': - # transform the ZIPDATA into an exploded directory of code and then - # print the path to the code. This is an easy way for people to look - # at the code on the remote machine for debugging it in that - # environment - z = zipfile.ZipFile(zipped_mod) - for filename in z.namelist(): - if filename.startswith('/'): - raise Exception('Something wrong with this module zip file: should not contain absolute paths') + # Monkeypatch the parameters into basic + from ansible.module_utils import basic + basic._ANSIBLE_ARGS = json_params +%(coverage)s + # Run the module! By importing it as '__main__', it thinks it is executing as a script + importer = zipimport.zipimporter(modlib_path) + importer.load_module('__main__') - dest_filename = os.path.join(basedir, filename) - if dest_filename.endswith(os.path.sep) and not os.path.exists(dest_filename): - os.makedirs(dest_filename) - else: - directory = os.path.dirname(dest_filename) - if not os.path.exists(directory): - os.makedirs(directory) - f = open(dest_filename, 'wb') - f.write(z.read(filename)) - f.close() - - # write the args file - f = open(args_path, 'wb') - f.write(json_params) - f.close() - - print('Module expanded into:') - print('%%s' %% basedir) - exitcode = 0 - - elif command == 'execute': - # Execute the exploded code instead of executing the module from the - # embedded ZIPDATA. This allows people to easily run their modified - # code on the remote machine to see how changes will affect it. - # This differs slightly from default Ansible execution of Python modules - # as it passes the arguments to the module via a file instead of stdin. - - # Set pythonpath to the debug dir - pythonpath = os.environ.get('PYTHONPATH') - if pythonpath: - os.environ['PYTHONPATH'] = ':'.join((basedir, pythonpath)) - else: - os.environ['PYTHONPATH'] = basedir - - p = subprocess.Popen([%(interpreter)s, script_path, args_path], - env=os.environ, shell=False, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, stdin=subprocess.PIPE) - (stdout, stderr) = p.communicate() - - if not isinstance(stderr, (bytes, unicode)): - stderr = stderr.read() - if not isinstance(stdout, (bytes, unicode)): - stdout = stdout.read() - if PY3: - sys.stderr.buffer.write(stderr) - sys.stdout.buffer.write(stdout) - else: - sys.stderr.write(stderr) - sys.stdout.write(stdout) - return p.returncode - - elif command == 'excommunicate': - # This attempts to run the module in-process (by importing a main - # function and then calling it). It is not the way ansible generally - # invokes the module so it won't work in every case. It is here to - # aid certain debuggers which work better when the code doesn't change - # from one process to another but there may be problems that occur - # when using this that are only artifacts of how we're invoking here, - # not actual bugs (as they don't affect the real way that we invoke - # ansible modules) - - # stub the args and python path - sys.argv = ['%(ansible_module)s', args_path] - sys.path.insert(0, basedir) - - from ansible_module_%(ansible_module)s import main - main() - print('WARNING: Module returned to wrapper instead of exiting') + # Ansible modules must exit themselves + print('{"msg": "New-style module did not handle its own exit", "failed": true}') sys.exit(1) - else: - print('WARNING: Unknown debug command. Doing nothing.') - exitcode = 0 - return exitcode + def debug(command, zipped_mod, json_params): + # The code here normally doesn't run. It's only used for debugging on the + # remote machine. + # + # The subcommands in this function make it easier to debug ansiballz + # modules. Here's the basic steps: + # + # Run ansible with the environment variable: ANSIBLE_KEEP_REMOTE_FILES=1 and -vvv + # to save the module file remotely:: + # $ ANSIBLE_KEEP_REMOTE_FILES=1 ansible host1 -m ping -a 'data=october' -vvv + # + # Part of the verbose output will tell you where on the remote machine the + # module was written to:: + # [...] + # SSH: EXEC ssh -C -q -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o + # PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o + # ControlPath=/home/badger/.ansible/cp/ansible-ssh-%%h-%%p-%%r -tt rhel7 '/bin/sh -c '"'"'LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 + # LC_MESSAGES=en_US.UTF-8 /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping'"'"'' + # [...] + # + # Login to the remote machine and run the module file via from the previous + # step with the explode subcommand to extract the module payload into + # source files:: + # $ ssh host1 + # $ /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping explode + # Module expanded into: + # /home/badger/.ansible/tmp/ansible-tmp-1461173408.08-279692652635227/ansible + # + # You can now edit the source files to instrument the code or experiment with + # different parameter values. When you're ready to run the code you've modified + # (instead of the code from the actual zipped module), use the execute subcommand like this:: + # $ /usr/bin/python /home/badger/.ansible/tmp/ansible-tmp-1461173013.93-9076457629738/ping execute + + # Okay to use __file__ here because we're running from a kept file + basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'debug_dir') + args_path = os.path.join(basedir, 'args') + script_path = os.path.join(basedir, '__main__.py') + + if command == 'excommunicate': + print('The excommunicate debug command is deprecated and will be removed in 2.11. Use execute instead.') + command = 'execute' + + if command == 'explode': + # transform the ZIPDATA into an exploded directory of code and then + # print the path to the code. This is an easy way for people to look + # at the code on the remote machine for debugging it in that + # environment + z = zipfile.ZipFile(zipped_mod) + for filename in z.namelist(): + if filename.startswith('/'): + raise Exception('Something wrong with this module zip file: should not contain absolute paths') + + dest_filename = os.path.join(basedir, filename) + if dest_filename.endswith(os.path.sep) and not os.path.exists(dest_filename): + os.makedirs(dest_filename) + else: + directory = os.path.dirname(dest_filename) + if not os.path.exists(directory): + os.makedirs(directory) + f = open(dest_filename, 'wb') + f.write(z.read(filename)) + f.close() + + # write the args file + f = open(args_path, 'wb') + f.write(json_params) + f.close() + + print('Module expanded into:') + print('%%s' %% basedir) + exitcode = 0 + + elif command == 'execute': + # Execute the exploded code instead of executing the module from the + # embedded ZIPDATA. This allows people to easily run their modified + # code on the remote machine to see how changes will affect it. + + # Set pythonpath to the debug dir + sys.path.insert(0, basedir) + + # read in the args file which the user may have modified + with open(args_path, 'rb') as f: + json_params = f.read() + + # Monkeypatch the parameters into basic + from ansible.module_utils import basic + basic._ANSIBLE_ARGS = json_params + + # Run the module! By importing it as '__main__', it thinks it is executing as a script + import imp + with open(script_path, 'r') as f: + importer = imp.load_module('__main__', f, script_path, ('.py', 'r', imp.PY_SOURCE)) + + # Ansible modules must exit themselves + print('{"msg": "New-style module did not handle its own exit", "failed": true}') + sys.exit(1) + + else: + print('WARNING: Unknown debug command. Doing nothing.') + exitcode = 0 + + return exitcode -if __name__ == '__main__': # # See comments in the debug() method for information on debugging # @@ -306,38 +298,14 @@ if __name__ == '__main__': # store this in remote_tmpdir (use system tempdir instead) temp_path = tempfile.mkdtemp(prefix='ansible_') - zipped_mod = os.path.join(temp_path, 'ansible_modlib.zip') - modlib = open(zipped_mod, 'wb') - modlib.write(base64.b64decode(ZIPDATA)) - modlib.close() + zipped_mod = os.path.join(temp_path, 'ansible_%(ansible_module)s_payload.zip') + with open(zipped_mod, 'wb') as modlib: + modlib.write(base64.b64decode(ZIPDATA)) if len(sys.argv) == 2: exitcode = debug(sys.argv[1], zipped_mod, ANSIBALLZ_PARAMS) else: - z = zipfile.ZipFile(zipped_mod, mode='r') - module = os.path.join(temp_path, 'ansible_module_%(ansible_module)s.py') - f = open(module, 'wb') - f.write(z.read('ansible_module_%(ansible_module)s.py')) - f.close() - - # When installed via setuptools (including python setup.py install), - # ansible may be installed with an easy-install.pth file. That file - # may load the system-wide install of ansible rather than the one in - # the module. sitecustomize is the only way to override that setting. - z = zipfile.ZipFile(zipped_mod, mode='a') - - # py3: zipped_mod will be text, py2: it's bytes. Need bytes at the end - sitecustomize = u'import sys\\nsys.path.insert(0,"%%s")\\n' %% zipped_mod - sitecustomize = sitecustomize.encode('utf-8') - # Use a ZipInfo to work around zipfile limitation on hosts with - # clocks set to a pre-1980 year (for instance, Raspberry Pi) - zinfo = zipfile.ZipInfo() - zinfo.filename = 'sitecustomize.py' - zinfo.date_time = ( %(year)i, %(month)i, %(day)i, %(hour)i, %(minute)i, %(second)i) - z.writestr(zinfo, sitecustomize) - z.close() - - exitcode = invoke_module(module, zipped_mod, ANSIBALLZ_PARAMS) + invoke_module(zipped_mod, ANSIBALLZ_PARAMS) finally: try: shutil.rmtree(temp_path) @@ -345,6 +313,33 @@ if __name__ == '__main__': # tempdir creation probably failed pass sys.exit(exitcode) + +if __name__ == '__main__': + _ansiballz_main() +''' + +ANSIBALLZ_COVERAGE_TEMPLATE = ''' + # Access to the working directory is required by coverage. + # Some platforms, such as macOS, may not allow querying the working directory when using become to drop privileges. + try: + os.getcwd() + except OSError: + os.chdir('/') + + os.environ['COVERAGE_FILE'] = '%(coverage_output)s' + + import atexit + import coverage + + cov = coverage.Coverage(config_file='%(coverage_config)s') + + def atexit_coverage(): + cov.stop() + cov.save() + + atexit.register(atexit_coverage) + + cov.start() ''' @@ -759,7 +754,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas to_bytes(__author__) + b'"\n') zf.writestr('ansible/module_utils/__init__.py', b'from pkgutil import extend_path\n__path__=extend_path(__path__,__name__)\n') - zf.writestr('ansible_module_%s.py' % module_name, b_module_data) + zf.writestr('__main__.py', b_module_data) py_module_cache = {('__init__',): (b'', '[builtin]')} recursive_finder(module_name, b_module_data, py_module_names, py_module_cache, zf) @@ -805,6 +800,18 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas interpreter_parts = interpreter.split(u' ') interpreter = u"'{0}'".format(u"', '".join(interpreter_parts)) + coverage_config = os.environ.get('_ANSIBLE_COVERAGE_CONFIG') + + if coverage_config: + # Enable code coverage analysis of the module. + # This feature is for internal testing and may change without notice. + coverage = ANSIBALLZ_COVERAGE_TEMPLATE % dict( + coverage_config=coverage_config, + coverage_output=os.environ['_ANSIBLE_COVERAGE_OUTPUT'] + ) + else: + coverage = '' + now = datetime.datetime.utcnow() output.write(to_bytes(ACTIVE_ANSIBALLZ_TEMPLATE % dict( zipdata=zipdata, @@ -819,6 +826,7 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas hour=now.hour, minute=now.minute, second=now.second, + coverage=coverage, ))) b_module_data = output.getvalue() diff --git a/lib/ansible/module_utils/basic.py b/lib/ansible/module_utils/basic.py index 9dd3ee53090..913cdae6b6b 100644 --- a/lib/ansible/module_utils/basic.py +++ b/lib/ansible/module_utils/basic.py @@ -62,6 +62,7 @@ PASS_BOOLS = ('no_log', 'debug', 'diff') # The functions available here can be used to do many common tasks, # to simplify development of Python modules. +import __main__ import atexit import locale import os diff --git a/lib/ansible/plugins/action/__init__.py b/lib/ansible/plugins/action/__init__.py index 3460d5be392..72dd769686a 100644 --- a/lib/ansible/plugins/action/__init__.py +++ b/lib/ansible/plugins/action/__init__.py @@ -762,7 +762,7 @@ class ActionBase(with_metaclass(ABCMeta, object)): tmpdir = self._connection._shell.tmpdir remote_module_filename = self._connection._shell.get_remote_filename(module_path) - remote_module_path = self._connection._shell.join_path(tmpdir, remote_module_filename) + remote_module_path = self._connection._shell.join_path(tmpdir, 'AnsiballZ_%s' % remote_module_filename) args_file_path = None if module_style in ('old', 'non_native_want_json', 'binary'): diff --git a/test/runner/injector/injector.py b/test/runner/injector/injector.py index 761a7c51ff8..3bb9b9c22f3 100755 --- a/test/runner/injector/injector.py +++ b/test/runner/injector/injector.py @@ -103,36 +103,13 @@ def main(): logger.debug('Remote interpreter: %s', config.remote_interpreter) logger.debug('Coverage file: %s', config.coverage_file) - require_cwd = False - if os.path.basename(__file__) == 'injector.py': - if config.coverage_file: - args, env, require_cwd = cover() - else: - args, env = runner() + args, env = runner() # code coverage collection is baked into the AnsiballZ wrapper when needed else: args, env = injector() logger.debug('Run command: %s', ' '.join(pipes.quote(c) for c in args)) - altered_cwd = False - - try: - cwd = os.getcwd() - except OSError as ex: - # some platforms, such as macOS, may not allow querying the working directory when using become to drop privileges - if ex.errno != errno.EACCES: - raise - if require_cwd: - # make sure the program we execute can determine the working directory if it's required - cwd = '/' - os.chdir(cwd) - altered_cwd = True - else: - cwd = None - - logger.debug('Working directory: %s%s', cwd or '?', ' (altered)' if altered_cwd else '') - for key in sorted(env.keys()): logger.debug('%s=%s', key, env[key]) @@ -183,29 +160,6 @@ def runner(): return args, env -def cover(): - """ - :rtype: list[str], dict[str, str], bool - """ - if len(config.arguments) > 1: - executable = config.arguments[1] - else: - executable = '' - - require_cwd = False - - if os.path.basename(executable).startswith('ansible_module_'): - args, env = coverage_command() - # coverage requires knowing the working directory - require_cwd = True - else: - args, env = [config.python_interpreter], os.environ.copy() - - args += config.arguments[1:] - - return args, env, require_cwd - - def coverage_command(): """ :rtype: list[str], dict[str, str] diff --git a/test/runner/lib/cover.py b/test/runner/lib/cover.py index a1b131ee0b5..0b7dcaa70e9 100644 --- a/test/runner/lib/cover.py +++ b/test/runner/lib/cover.py @@ -88,10 +88,17 @@ def command_coverage_combine(args): continue if '/ansible_modlib.zip/ansible/' in filename: + # Rewrite the module_utils path from the remote host to match the controller. Ansible 2.6 and earlier. new_name = re.sub('^.*/ansible_modlib.zip/ansible/', ansible_path, filename) display.info('%s -> %s' % (filename, new_name), verbosity=3) filename = new_name + elif re.search(r'/ansible_[^/]+_payload\.zip/ansible/', filename): + # Rewrite the module_utils path from the remote host to match the controller. Ansible 2.7 and later. + new_name = re.sub(r'^.*/ansible_[^/]+_payload\.zip/ansible/', ansible_path, filename) + display.info('%s -> %s' % (filename, new_name), verbosity=3) + filename = new_name elif '/ansible_module_' in filename: + # Rewrite the module path from the remote host to match the controller. Ansible 2.6 and earlier. module_name = re.sub('^.*/ansible_module_(?P.*).py$', '\\g', filename) if module_name not in modules: display.warning('Skipping coverage of unknown module: %s' % module_name) @@ -99,7 +106,19 @@ def command_coverage_combine(args): new_name = os.path.abspath(modules[module_name]) display.info('%s -> %s' % (filename, new_name), verbosity=3) filename = new_name + elif re.search(r'/ansible_[^/]+_payload(_[^/]+|\.zip)/__main__\.py$', filename): + # Rewrite the module path from the remote host to match the controller. Ansible 2.7 and later. + # AnsiballZ versions using zipimporter will match the `.zip` portion of the regex. + # AnsiballZ versions not using zipimporter will match the `_[^/]+` portion of the regex. + module_name = re.sub(r'^.*/ansible_(?P[^/]+)_payload(_[^/]+|\.zip)/__main__\.py$', '\\g', filename).rstrip('_') + if module_name not in modules: + display.warning('Skipping coverage of unknown module: %s' % module_name) + continue + new_name = os.path.abspath(modules[module_name]) + display.info('%s -> %s' % (filename, new_name), verbosity=3) + filename = new_name elif re.search('^(/.*?)?/root/ansible/', filename): + # Rewrite the path of code running on a remote host or in a docker container as root. new_name = re.sub('^(/.*?)?/root/ansible/', root_path, filename) display.info('%s -> %s' % (filename, new_name), verbosity=3) filename = new_name diff --git a/test/runner/lib/util.py b/test/runner/lib/util.py index 2c60da6fe32..7770623cf99 100644 --- a/test/runner/lib/util.py +++ b/test/runner/lib/util.py @@ -169,6 +169,10 @@ def intercept_command(args, cmd, target_name, capture=False, env=None, data=None env['ANSIBLE_TEST_PYTHON_VERSION'] = version env['ANSIBLE_TEST_PYTHON_INTERPRETER'] = interpreter + if args.coverage: + env['_ANSIBLE_COVERAGE_CONFIG'] = os.path.join(inject_path, '.coveragerc') + env['_ANSIBLE_COVERAGE_OUTPUT'] = coverage_file + config = dict( python_interpreter=interpreter, coverage_file=coverage_file if args.coverage else None, diff --git a/test/sanity/validate-modules/utils.py b/test/sanity/validate-modules/utils.py index 07ae5f8353e..de60a4efc2d 100644 --- a/test/sanity/validate-modules/utils.py +++ b/test/sanity/validate-modules/utils.py @@ -140,4 +140,4 @@ class NoArgsAnsibleModule(AnsibleModule): methods within AnsibleModule without having to fake a bunch of data """ def _load_params(self): - self.params = {} + self.params = {'_ansible_selinux_special_fs': [], '_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False, '_ansible_check_mode': False} diff --git a/test/units/mock/procenv.py b/test/units/mock/procenv.py index a74b4f5c58c..e7326c09354 100644 --- a/test/units/mock/procenv.py +++ b/test/units/mock/procenv.py @@ -77,7 +77,7 @@ def swap_stdout(): class ModuleTestCase(unittest.TestCase): def setUp(self, module_args=None): if module_args is None: - module_args = {} + module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False} args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args)) diff --git a/test/units/module_utils/aws/test_aws_module.py b/test/units/module_utils/aws/test_aws_module.py index e17deba70f3..9539a2dc939 100644 --- a/test/units/module_utils/aws/test_aws_module.py +++ b/test/units/module_utils/aws/test_aws_module.py @@ -32,7 +32,7 @@ botocore = importorskip("botocore") class AWSModuleTestCase(unittest.TestCase): - basic._ANSIBLE_ARGS = to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {}})) + basic._ANSIBLE_ARGS = to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {'_ansible_tmpdir': '/tmp/ansible-abc'}})) def test_create_aws_module_should_set_up_params(self): m = AnsibleAWSModule(argument_spec=dict( @@ -59,7 +59,7 @@ class ErrorReportingTestcase(unittest.TestCase): def test_botocore_exception_reports_nicely_via_fail_json_aws(self): - basic._ANSIBLE_ARGS = to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {}})) + basic._ANSIBLE_ARGS = to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {'_ansible_tmpdir': '/tmp/ansible-abc'}})) module = AnsibleAWSModule(argument_spec=dict( fail_mode=dict(type='list', default=['success']) )) @@ -97,7 +97,7 @@ class ErrorReportingTestcase(unittest.TestCase): "Failed to find error/code; was: " + str(fail_json_double.mock_calls[0]) def test_botocore_exception_without_response_reports_nicely_via_fail_json_aws(self): - basic._ANSIBLE_ARGS = to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {}})) + basic._ANSIBLE_ARGS = to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': {'_ansible_tmpdir': '/tmp/ansible-abc'}})) module = AnsibleAWSModule(argument_spec=dict( fail_mode=dict(type='list', default=['success']) )) diff --git a/test/units/module_utils/basic/test_selinux.py b/test/units/module_utils/basic/test_selinux.py index c95d9acc97b..df6ec9d0e85 100644 --- a/test/units/module_utils/basic/test_selinux.py +++ b/test/units/module_utils/basic/test_selinux.py @@ -166,7 +166,9 @@ class TestSELinux(ModuleTestCase): def test_module_utils_basic_ansible_module_is_special_selinux_path(self): from ansible.module_utils import basic - args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'_ansible_selinux_special_fs': "nfs,nfsd,foos"})) + args = json.dumps(dict(ANSIBLE_MODULE_ARGS={'_ansible_selinux_special_fs': "nfs,nfsd,foos", + '_ansible_remote_tmp': "/tmp", + '_ansible_keep_remote_files': False})) with swap_stdin_and_argv(stdin_data=args): basic._ANSIBLE_ARGS = None diff --git a/test/units/module_utils/basic/test_tmpdir.py b/test/units/module_utils/basic/test_tmpdir.py index 2315313253e..20b529893a5 100644 --- a/test/units/module_utils/basic/test_tmpdir.py +++ b/test/units/module_utils/basic/test_tmpdir.py @@ -6,6 +6,7 @@ from __future__ import (absolute_import, division) __metaclass__ = type +import json import os import shutil import tempfile @@ -13,6 +14,9 @@ import tempfile import pytest from ansible.compat.tests.mock import patch, MagicMock +from ansible.module_utils._text import to_bytes + +from ansible.module_utils import basic class TestAnsibleModuleTmpDir: @@ -58,9 +62,8 @@ class TestAnsibleModuleTmpDir: # pylint bug: https://github.com/PyCQA/pylint/issues/511 # pylint: disable=undefined-variable - @pytest.mark.parametrize('stdin, expected, stat_exists', ((s, e, t) for s, t, e in DATA), - indirect=['stdin']) - def test_tmpdir_property(self, am, monkeypatch, expected, stat_exists): + @pytest.mark.parametrize('args, expected, stat_exists', ((s, e, t) for s, t, e in DATA)) + def test_tmpdir_property(self, monkeypatch, args, expected, stat_exists): makedirs = {'called': False} def mock_mkdtemp(prefix, dir): @@ -68,18 +71,20 @@ class TestAnsibleModuleTmpDir: def mock_makedirs(path, mode): makedirs['called'] = True - expected = os.path.expanduser(os.path.expandvars(am._remote_tmp)) - assert path == expected - assert mode == 0o700 + makedirs['path'] = path + makedirs['mode'] = mode return monkeypatch.setattr(tempfile, 'mkdtemp', mock_mkdtemp) monkeypatch.setattr(os.path, 'exists', lambda x: stat_exists) monkeypatch.setattr(os, 'makedirs', mock_makedirs) monkeypatch.setattr(shutil, 'rmtree', lambda x: None) + monkeypatch.setattr(basic, '_ANSIBLE_ARGS', to_bytes(json.dumps({'ANSIBLE_MODULE_ARGS': args}))) with patch('time.time', return_value=42): + am = basic.AnsibleModule(argument_spec={}) actual_tmpdir = am.tmpdir + assert actual_tmpdir == expected # verify subsequent calls always produces the same tmpdir @@ -87,6 +92,9 @@ class TestAnsibleModuleTmpDir: if not stat_exists: assert makedirs['called'] + expected = os.path.expanduser(os.path.expandvars(am._remote_tmp)) + assert makedirs['path'] == expected + assert makedirs['mode'] == 0o700 @pytest.mark.parametrize('stdin', ({"_ansible_tmpdir": None, "_ansible_remote_tmp": "$HOME/.test", diff --git a/test/units/module_utils/conftest.py b/test/units/module_utils/conftest.py index 8da7df81ec2..35912050bd1 100644 --- a/test/units/module_utils/conftest.py +++ b/test/units/module_utils/conftest.py @@ -25,6 +25,10 @@ def stdin(mocker, request): elif isinstance(request.param, MutableMapping): if 'ANSIBLE_MODULE_ARGS' not in request.param: request.param = {'ANSIBLE_MODULE_ARGS': request.param} + if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False args = json.dumps(request.param) else: raise Exception('Malformed data to the stdin pytest fixture') diff --git a/test/units/modules/cloud/amazon/test_aws_api_gateway.py b/test/units/modules/cloud/amazon/test_aws_api_gateway.py index d6f4c7e719f..9efa8460d0a 100644 --- a/test/units/modules/cloud/amazon/test_aws_api_gateway.py +++ b/test/units/modules/cloud/amazon/test_aws_api_gateway.py @@ -62,6 +62,7 @@ def test_upload_api(monkeypatch): "state": "present", "swagger_text": "the-swagger-text-is-fake", "region": 'mars-north-1', + "_ansible_tmpdir": "/tmp/ansibl-abcdef", }) with pytest.raises(SystemExit): agw.main() diff --git a/test/units/modules/conftest.py b/test/units/modules/conftest.py index 68dc0cbedf3..319ddb06dd8 100644 --- a/test/units/modules/conftest.py +++ b/test/units/modules/conftest.py @@ -17,6 +17,10 @@ def patch_ansible_module(request, mocker): elif isinstance(request.param, MutableMapping): if 'ANSIBLE_MODULE_ARGS' not in request.param: request.param = {'ANSIBLE_MODULE_ARGS': request.param} + if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = '/tmp' args = json.dumps(request.param) else: raise Exception('Malformed data to the patch_ansible_module pytest fixture') diff --git a/test/units/modules/network/cnos/cnos_module.py b/test/units/modules/network/cnos/cnos_module.py index 4b0af98bb4f..bbcf7b2ac2b 100644 --- a/test/units/modules/network/cnos/cnos_module.py +++ b/test/units/modules/network/cnos/cnos_module.py @@ -28,10 +28,6 @@ from ansible.module_utils import basic from ansible.module_utils._text import to_bytes -def set_module_args(args): - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) - fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') fixture_data = {} diff --git a/test/units/modules/network/cnos/test_cnos_config.py b/test/units/modules/network/cnos/test_cnos_config.py index 9caa35efeb1..35c9199c88f 100644 --- a/test/units/modules/network/cnos/test_cnos_config.py +++ b/test/units/modules/network/cnos/test_cnos_config.py @@ -24,7 +24,9 @@ import json from ansible.compat.tests.mock import patch from ansible.modules.network.cnos import cnos_config -from .cnos_module import TestCnosModule, load_fixture, set_module_args + +from .cnos_module import TestCnosModule, load_fixture +from units.modules.utils import set_module_args class TestCnosConfigModule(TestCnosModule): diff --git a/test/units/modules/network/enos/enos_module.py b/test/units/modules/network/enos/enos_module.py index 97464e2c259..66c59ca97fb 100644 --- a/test/units/modules/network/enos/enos_module.py +++ b/test/units/modules/network/enos/enos_module.py @@ -28,10 +28,6 @@ from ansible.module_utils import basic from ansible.module_utils._text import to_bytes -def set_module_args(args): - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) - fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') fixture_data = {} diff --git a/test/units/modules/network/enos/test_enos_config.py b/test/units/modules/network/enos/test_enos_config.py index c0a17fd9b82..1b5e4930d31 100644 --- a/test/units/modules/network/enos/test_enos_config.py +++ b/test/units/modules/network/enos/test_enos_config.py @@ -24,7 +24,9 @@ import json from ansible.compat.tests.mock import patch from ansible.modules.network.enos import enos_config -from .enos_module import TestEnosModule, load_fixture, set_module_args +from .enos_module import TestEnosModule, load_fixture + +from units.modules.utils import set_module_args class TestEnosConfigModule(TestEnosModule): diff --git a/test/units/modules/network/netact/test_netact_cm_command.py b/test/units/modules/network/netact/test_netact_cm_command.py index f5bc8724e88..b9344529976 100644 --- a/test/units/modules/network/netact/test_netact_cm_command.py +++ b/test/units/modules/network/netact/test_netact_cm_command.py @@ -36,14 +36,7 @@ from ansible.module_utils._text import to_bytes from ansible.modules.network.netact import netact_cm_command from ansible.compat.tests.mock import patch -from units.modules.utils import set_module_args as _set_module_args, \ - AnsibleExitJson, AnsibleFailJson, ModuleTestCase - - -def set_module_args(args): - """prepare arguments so that they will be picked up during module creation""" - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) +from units.modules.utils import set_module_args, AnsibleExitJson, AnsibleFailJson, ModuleTestCase class AnsibleExitJson(Exception): diff --git a/test/units/modules/network/nso/nso_module.py b/test/units/modules/network/nso/nso_module.py index 18566cd4ebf..8df42fe9967 100644 --- a/test/units/modules/network/nso/nso_module.py +++ b/test/units/modules/network/nso/nso_module.py @@ -30,11 +30,6 @@ fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') fixture_data = {} -def set_module_args(args): - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) - - def load_fixture(name): path = os.path.join(fixture_path, name) if path not in fixture_data: diff --git a/test/units/modules/network/nso/test_nso_action.py b/test/units/modules/network/nso/test_nso_action.py index b890b6bc1ea..661ad3a8696 100644 --- a/test/units/modules/network/nso/test_nso_action.py +++ b/test/units/modules/network/nso/test_nso_action.py @@ -25,6 +25,8 @@ from ansible.modules.network.nso import nso_action from . import nso_module from .nso_module import MockResponse +from units.modules.utils import set_module_args + class TestNsoAction(nso_module.TestNsoModule): module = nso_action @@ -42,7 +44,7 @@ class TestNsoAction(nso_module.TestNsoModule): ] open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'path': path, @@ -66,7 +68,7 @@ class TestNsoAction(nso_module.TestNsoModule): ] open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'path': path, @@ -92,7 +94,7 @@ class TestNsoAction(nso_module.TestNsoModule): ] open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'path': path, @@ -119,7 +121,7 @@ class TestNsoAction(nso_module.TestNsoModule): ] open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'path': path, @@ -147,7 +149,7 @@ class TestNsoAction(nso_module.TestNsoModule): ] open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'path': path, diff --git a/test/units/modules/network/nso/test_nso_query.py b/test/units/modules/network/nso/test_nso_query.py index 234a0cb76df..40060fe2333 100644 --- a/test/units/modules/network/nso/test_nso_query.py +++ b/test/units/modules/network/nso/test_nso_query.py @@ -23,6 +23,8 @@ from ansible.modules.network.nso import nso_query from . import nso_module from .nso_module import MockResponse +from units.modules.utils import set_module_args + class TestNsoQuery(nso_module.TestNsoModule): module = nso_query @@ -42,7 +44,7 @@ class TestNsoQuery(nso_module.TestNsoModule): ] open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'xpath': xpath, diff --git a/test/units/modules/network/nso/test_nso_show.py b/test/units/modules/network/nso/test_nso_show.py index 75e7014e02b..3d63716ccb0 100644 --- a/test/units/modules/network/nso/test_nso_show.py +++ b/test/units/modules/network/nso/test_nso_show.py @@ -25,6 +25,8 @@ from ansible.modules.network.nso import nso_show from . import nso_module from .nso_module import MockResponse +from units.modules.utils import set_module_args + class TestNsoShow(nso_module.TestNsoModule): module = nso_show @@ -43,7 +45,7 @@ class TestNsoShow(nso_module.TestNsoModule): ] open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'path': path @@ -64,7 +66,7 @@ class TestNsoShow(nso_module.TestNsoModule): ] open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'path': path, @@ -85,7 +87,7 @@ class TestNsoShow(nso_module.TestNsoModule): ] open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'path': path, diff --git a/test/units/modules/network/nso/test_nso_verify.py b/test/units/modules/network/nso/test_nso_verify.py index 34aa4530313..ac746b4a1fd 100644 --- a/test/units/modules/network/nso/test_nso_verify.py +++ b/test/units/modules/network/nso/test_nso_verify.py @@ -25,6 +25,8 @@ from ansible.modules.network.nso import nso_verify from . import nso_module from .nso_module import MockResponse +from units.modules.utils import set_module_args + class TestNsoVerify(nso_module.TestNsoModule): module = nso_verify @@ -39,7 +41,7 @@ class TestNsoVerify(nso_module.TestNsoModule): open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) data = {} - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'data': data }) @@ -68,7 +70,7 @@ class TestNsoVerify(nso_module.TestNsoModule): open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) data = nso_module.load_fixture('verify_violation_data.json') - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'data': data }) @@ -97,7 +99,7 @@ class TestNsoVerify(nso_module.TestNsoModule): open_url_mock.side_effect = lambda *args, **kwargs: nso_module.mock_call(calls, *args, **kwargs) data = nso_module.load_fixture('verify_violation_data.json') - nso_module.set_module_args({ + set_module_args({ 'username': 'user', 'password': 'password', 'url': 'http://localhost:8080/jsonrpc', 'data': data }) diff --git a/test/units/modules/source_control/test_gitlab_deploy_key.py b/test/units/modules/source_control/test_gitlab_deploy_key.py index d77f4033a19..162702e5208 100644 --- a/test/units/modules/source_control/test_gitlab_deploy_key.py +++ b/test/units/modules/source_control/test_gitlab_deploy_key.py @@ -10,6 +10,9 @@ from ansible.module_utils import basic import pytest import json +from units.modules.utils import set_module_args + + fake_server_state = [ { "id": 1, @@ -21,12 +24,6 @@ fake_server_state = [ ] -def set_module_args(args): - """prepare arguments so that they will be picked up during module creation""" - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) - - class FakeReader: def __init__(self, object): self.content = json.dumps(object, sort_keys=True) diff --git a/test/units/modules/source_control/test_gitlab_hooks.py b/test/units/modules/source_control/test_gitlab_hooks.py index 2487b963dc3..7acb8d66f60 100644 --- a/test/units/modules/source_control/test_gitlab_hooks.py +++ b/test/units/modules/source_control/test_gitlab_hooks.py @@ -10,6 +10,9 @@ from ansible.module_utils import basic import pytest import json +from units.modules.utils import set_module_args + + fake_server_state = [ { "id": 1, @@ -29,12 +32,6 @@ fake_server_state = [ ] -def set_module_args(args): - """prepare arguments so that they will be picked up during module creation""" - args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) - basic._ANSIBLE_ARGS = to_bytes(args) - - class FakeReader: def __init__(self, object): self.content = json.dumps(object, sort_keys=True) diff --git a/test/units/modules/utils.py b/test/units/modules/utils.py index a0e9351b127..91fb4c9522f 100644 --- a/test/units/modules/utils.py +++ b/test/units/modules/utils.py @@ -7,6 +7,11 @@ from ansible.module_utils._text import to_bytes def set_module_args(args): + if '_ansible_remote_tmp' not in args: + args['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in args: + args['_ansible_keep_remote_files'] = False + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) basic._ANSIBLE_ARGS = to_bytes(args) @@ -35,4 +40,5 @@ class ModuleTestCase(unittest.TestCase): def setUp(self): self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json) self.mock_module.start() + set_module_args({}) self.addCleanup(self.mock_module.stop)