Merge pull request #15562 from abadger/ziploader-minor

Quite a few individually minor changes to ziploader
pull/4420/head
Toshio Kuratomi 2016-04-25 06:59:22 -07:00
commit 87d2345cf5
10 changed files with 380 additions and 129 deletions

View File

@ -3,25 +3,28 @@ Developing Modules
.. contents:: Topics .. contents:: Topics
Ansible modules are reusable units of magic that can be used by the Ansible API, Ansible modules are reusable, standalone scripts that can be used by the Ansible API,
or by the `ansible` or `ansible-playbook` programs. or by the :command:`ansible` or :command:`ansible-playbook` programs. They
return information to ansible by printing a JSON string to stdout before
exiting. They take arguments in in one of several ways which we'll go into
as we work through this tutorial.
See :doc:`modules` for a list of various ones developed in core. See :doc:`modules` for a list of various ones developed in core.
Modules can be written in any language and are found in the path specified Modules can be written in any language and are found in the path specified
by `ANSIBLE_LIBRARY` or the ``--module-path`` command line option. by :envvar:`ANSIBLE_LIBRARY` or the ``--module-path`` command line option.
By default, everything that ships with ansible is pulled from its source tree, but By default, everything that ships with Ansible is pulled from its source tree, but
additional paths can be added. additional paths can be added.
The directory "./library", alongside your top level playbooks, is also automatically The directory i:file:`./library`, alongside your top level :term:`playbooks`, is also automatically
added as a search directory. added as a search directory.
Should you develop an interesting Ansible module, consider sending a pull request to the Should you develop an interesting Ansible module, consider sending a pull request to the
`modules-extras project <https://github.com/ansible/ansible-modules-extras>`_. There's also a core `modules-extras project <https://github.com/ansible/ansible-modules-extras>`_. There's also a core
repo for more established and widely used modules. "Extras" modules may be promoted to core periodically, repo for more established and widely used modules. "Extras" modules may be promoted to core periodically,
but there's no fundamental difference in the end - both ship with ansible, all in one package, regardless but there's no fundamental difference in the end - both ship with Ansible, all in one package, regardless
of how you acquire ansible. of how you acquire Ansible.
.. _module_dev_tutorial: .. _module_dev_tutorial:
@ -41,14 +44,14 @@ written in any language OTHER than Python are going to have to do exactly this.
way later. way later.
So, here's an example. You would never really need to build a module to set the system time, So, here's an example. You would never really need to build a module to set the system time,
the 'command' module could already be used to do this. Though we're going to make one. the 'command' module could already be used to do this.
Reading the modules that come with ansible (linked above) is a great way to learn how to write Reading the modules that come with Ansible (linked above) is a great way to learn how to write
modules. Keep in mind, though, that some modules in ansible's source tree are internalisms, modules. Keep in mind, though, that some modules in Ansible's source tree are internalisms,
so look at `service` or `yum`, and don't stare too close into things like `async_wrapper` or so look at :ref:`service` or :ref:`yum`, and don't stare too close into things like :ref:`async_wrapper` or
you'll turn to stone. Nobody ever executes async_wrapper directly. you'll turn to stone. Nobody ever executes :ref:`async_wrapper` directly.
Ok, let's get going with an example. We'll use Python. For starters, save this as a file named `timetest.py`:: Ok, let's get going with an example. We'll use Python. For starters, save this as a file named :file:`timetest.py`::
#!/usr/bin/python #!/usr/bin/python
@ -65,13 +68,12 @@ Ok, let's get going with an example. We'll use Python. For starters, save this
Testing Modules Testing Modules
```````````````` ````````````````
There's a useful test script in the source checkout for ansible:: There's a useful test script in the source checkout for Ansible::
git clone git://github.com/ansible/ansible.git --recursive git clone git://github.com/ansible/ansible.git --recursive
source ansible/hacking/env-setup source ansible/hacking/env-setup
chmod +x ansible/hacking/test-module
For instructions on setting up ansible from source, please see For instructions on setting up Ansible from source, please see
:doc:`intro_installation`. :doc:`intro_installation`.
Let's run the script you just wrote with that:: Let's run the script you just wrote with that::
@ -80,7 +82,7 @@ Let's run the script you just wrote with that::
You should see output that looks something like this:: You should see output that looks something like this::
{u'time': u'2012-03-14 22:13:48.539183'} {'time': '2012-03-14 22:13:48.539183'}
If you did not, you might have a typo in your module, so recheck it and try again. If you did not, you might have a typo in your module, so recheck it and try again.
@ -105,7 +107,7 @@ If no time parameter is set, we'll just leave the time as is and return the curr
.. note:: .. note::
This is obviously an unrealistic idea for a module. You'd most likely just This is obviously an unrealistic idea for a module. You'd most likely just
use the shell module. However, it probably makes a decent tutorial. use the command module. However, it makes for a decent tutorial.
Let's look at the code. Read the comments as we'll explain as we go. Note that this Let's look at the code. Read the comments as we'll explain as we go. Note that this
is highly verbose because it's intended as an educational example. You can write modules is highly verbose because it's intended as an educational example. You can write modules
@ -126,10 +128,12 @@ a lot shorter than this::
args_file = sys.argv[1] args_file = sys.argv[1]
args_data = file(args_file).read() args_data = file(args_file).read()
# for this module, we're going to do key=value style arguments # For this module, we're going to do key=value style arguments.
# this is up to each module to decide what it wants, but all # Modules can choose to receive json instead by adding the string:
# core modules besides 'command' and 'shell' take key=value # WANT_JSON
# so this is highly recommended # Somewhere in the file.
# Modules can also take free-form arguments instead of key-value or json
# but this is not recommended.
arguments = shlex.split(args_data) arguments = shlex.split(args_data)
for arg in arguments: for arg in arguments:
@ -205,7 +209,7 @@ This should return something like::
Module Provided 'Facts' Module Provided 'Facts'
```````````````````````` ````````````````````````
The 'setup' module that ships with Ansible provides many variables about a system that can be used in playbooks The :ref:`setup` module that ships with Ansible provides many variables about a system that can be used in playbooks
and templates. However, it's possible to also add your own facts without modifying the system module. To do and templates. However, it's possible to also add your own facts without modifying the system module. To do
this, just have the module return a `ansible_facts` key, like so, along with other return data:: this, just have the module return a `ansible_facts` key, like so, along with other return data::
@ -238,43 +242,52 @@ Rather than mention these here, the best way to learn is to read some of the `so
The 'group' and 'user' modules are reasonably non-trivial and showcase what this looks like. The 'group' and 'user' modules are reasonably non-trivial and showcase what this looks like.
Key parts include always ending the module file with:: Key parts include always importing the boilerplate code from
:mod:`ansible.module_utils.basic` like this::
from ansible.module_utils.basic import * from ansible.module_utils.basic import AnsibleModule
if __name__ == '__main__': if __name__ == '__main__':
main() main()
.. note::
Prior to Ansible-2.1.0, importing only what you used from
:mod:`ansible.module_utils.basic` did not work. You needed to use
a wildcard import like this::
from ansible.module_utils.basic import *
And instantiating the module class like:: And instantiating the module class like::
module = AnsibleModule( def main():
argument_spec = dict( module = AnsibleModule(
state = dict(default='present', choices=['present', 'absent']), argument_spec = dict(
name = dict(required=True), state = dict(default='present', choices=['present', 'absent']),
enabled = dict(required=True, type='bool'), name = dict(required=True),
something = dict(aliases=['whatever']) enabled = dict(required=True, type='bool'),
something = dict(aliases=['whatever'])
)
) )
)
The AnsibleModule provides lots of common code for handling returns, parses your arguments The :class:`AnsibleModule` provides lots of common code for handling returns, parses your arguments
for you, and allows you to check inputs. for you, and allows you to check inputs.
Successful returns are made like this:: Successful returns are made like this::
module.exit_json(changed=True, something_else=12345) module.exit_json(changed=True, something_else=12345)
And failures are just as simple (where 'msg' is a required parameter to explain the error):: And failures are just as simple (where `msg` is a required parameter to explain the error)::
module.fail_json(msg="Something fatal happened") module.fail_json(msg="Something fatal happened")
There are also other useful functions in the module class, such as module.sha1(path). See There are also other useful functions in the module class, such as :func:`module.sha1(path)`. See
lib/ansible/module_utils/basic.py in the source checkout for implementation details. :file:`lib/ansible/module_utils/basic.py` in the source checkout for implementation details.
Again, modules developed this way are best tested with the hacking/test-module script in the git Again, modules developed this way are best tested with the :file:`hacking/test-module` script in the git
source checkout. Because of the magic involved, this is really the only way the scripts source checkout. Because of the magic involved, this is really the only way the scripts
can function outside of Ansible. can function outside of Ansible.
If submitting a module to ansible's core code, which we encourage, use of the AnsibleModule If submitting a module to Ansible's core code, which we encourage, use of
class is required. :class:`AnsibleModule` is required.
.. _developing_for_check_mode: .. _developing_for_check_mode:
@ -449,13 +462,126 @@ built and appear in the 'docsite/' directory.
You can set the environment variable ANSIBLE_KEEP_REMOTE_FILES=1 on the controlling host to prevent ansible from You can set the environment variable ANSIBLE_KEEP_REMOTE_FILES=1 on the controlling host to prevent ansible from
deleting the remote files so you can debug your module. deleting the remote files so you can debug your module.
.. _module_contribution: .. _debugging_ansiblemodule_based_modules:
Debugging AnsibleModule-based modules
`````````````````````````````````````
.. tip::
If you're using the :file:`hacking/test-module` script then most of this
is taken care of for you. If you need to do some debugging of the module
on the remote machine that the module will actually run on or when the
module is used in a playbook then you may need to use this information
instead of relying on test-module.
Starting with Ansible-2.1.0, AnsibleModule-based modules are put together as
a zip file consisting of the module file and the various python module
boilerplate inside of a wrapper script instead of as a single file with all of
the code concatenated together. Without some help, this can be harder to
debug as the file needs to be extracted from the wrapper in order to see
what's actually going on in the module. Luckily the wrapper script provides
some helper methods to do just that.
If you are using Ansible with the :envvar:`ANSIBLE_KEEP_REMOTE_FILES`
environment variables to keep the remote module file, here's a sample of how
your debugging session will start::
$ ANSIBLE_KEEP_REMOTE_FILES=1 ansible localhost -m ping -a 'data=debugging_session' -vvv
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: badger
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1461434734.35-235318071810595 `" && echo "` echo $HOME/.ansible/tmp/ansible-tmp-1461434734.35-235318071810595 `" )'
<127.0.0.1> PUT /var/tmp/tmpjdbJ1w TO /home/badger/.ansible/tmp/ansible-tmp-1461434734.35-235318071810595/ping
<127.0.0.1> EXEC /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-1461434734.35-235318071810595/ping'
localhost | SUCCESS => {
"changed": false,
"invocation": {
"module_args": {
"data": "debugging_session"
},
"module_name": "ping"
},
"ping": "debugging_session"
}
Setting :envvar:`ANSIBLE_KEEP_REMOTE_FILE` to ``1`` tells Ansible to keep the
remote module files instead of deleting them after the module finishes
executing. Giving Ansible the ``-vvv`` optin makes Ansible more verbose.
That way it prints the file name of the temporary module file for you to see.
If you want to examine the wrapper file you can. It will show a small python
script with a large, base64 encoded string. The string contains the module
that is going to be executed. Run the wrapper's explode command to turn the
string into some python files that you can work with::
$ python /home/badger/.ansible/tmp/ansible-tmp-1461434734.35-235318071810595/ping explode
Module expanded into:
/home/badger/.ansible/tmp/ansible-tmp-1461434734.35-235318071810595/debug_dir
When you look into the debug_dir you'll see a directory structure like this::
├── ansible_module_ping.py
├── args
└── ansible
├── __init__.py
└── module_utils
├── basic.py
└── __init__.py
* :file:`ansible_module_ping.py` is the code for the module itself. The name
is based on the name of the module with a prefix so that we don't clash with
any other python module names. You can modify this code to see what effect
it would have on your module.
* The :file:`args` file contains a JSON string. The string is a dictionary
containing the module arguments and other variables that Ansible passes into
the module to change it's behaviour. If you want to modify the parameters
that are passed to the module, this is the file to do it in.
* The :file:`ansible` directory contains code from
:module:`ansible.module_utils` that is used by the module. Ansible includes
files for any :`module:`ansible.module_utils` imports in the module but not
no files from any other module. So if your module uses
:module:`ansible.module_utils.url` Ansible will include it for you, but if
your module includes :module:`requests` then you'll have to make sure that
the python requests library is installed on the system before running the
module. You can modify files in this directory if you suspect that the
module is having a problem in some of this boilerplate code rather than in
the module code you have written.
Once you edit the code or arguments in the exploded tree you need some way to
run it. There's a separate wrapper subcommand for this::
$ python /home/badger/.ansible/tmp/ansible-tmp-1461434734.35-235318071810595/ping execute
{"invocation": {"module_args": {"data": "debugging_session"}}, "changed": false, "ping": "debugging_session"}
This subcommand takes care of setting the PYTHONPATH to use the exploded
:file:`debug_dir/ansible/module_utils` directory and invoking the script using
the arguments in the :file:`args` file. You can continue to run it like this
until you understand the problem. Then you can copy it back into your real
module file and test that the real module works via :command:`ansible` or
:command:`ansible-playbook`.
.. note::
The wrapper provides one more subcommand, ``excommunicate``. This
subcommand is very similar to ``execute`` in that it invokes the exploded
module on the arguments in the :file:`args`. The way it does this is
different, however. ``excommunicate`` imports the :function:`main`
function from the module and then calls that. This makes excommunicate
execute the module in the wrapper's process. This may be useful for
running the module under some graphical debuggers but it is very different
from the way the module is executed by Ansible itself. Some modules may
not work with ``excommunicate`` or may behave differently than when used
with Ansible normally. Those are not bugs in the module; they're
limitations of ``excommunicate``. Use at your own risk.
.. _module_paths
Module Paths Module Paths
```````````` ````````````
If you are having trouble getting your module "found" by ansible, be If you are having trouble getting your module "found" by ansible, be
sure it is in the ``ANSIBLE_LIBRARY`` environment variable. sure it is in the :envvar:`ANSIBLE_LIBRARY` environment variable.
If you have a fork of one of the ansible module projects, do something like this:: If you have a fork of one of the ansible module projects, do something like this::
@ -468,6 +594,8 @@ To be safe, if you're working on a variant on something in Ansible's normal dist
a bad idea to give it a new name while you are working on it, to be sure you know you're pulling a bad idea to give it a new name while you are working on it, to be sure you know you're pulling
your version. your version.
.. _module_contribution:
Getting Your Module Into Ansible Getting Your Module Into Ansible
```````````````````````````````` ````````````````````````````````
@ -548,7 +676,7 @@ The following checklist items are important guidelines for people who want to c
* Are module actions idempotent? If not document in the descriptions or the notes. * Are module actions idempotent? If not document in the descriptions or the notes.
* Import module snippets `from ansible.module_utils.basic import *` at the bottom, conserves line numbers for debugging. * Import module snippets `from ansible.module_utils.basic import *` at the bottom, conserves line numbers for debugging.
* Call your :func:`main` from a conditional so that it would be possible to * Call your :func:`main` from a conditional so that it would be possible to
test them in the future example:: import them into unittests in the future example::
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -286,9 +286,20 @@ imports of things in module_utils instead of merely preprocessing the module.
It does this by constructing a zipfile--which includes the module file, files It does this by constructing a zipfile--which includes the module file, files
in :file:`ansible/module_utils` that are imported by the module, and some in :file:`ansible/module_utils` that are imported by the module, and some
boilerplate to pass in the constants. The zipfile is then Base64 encoded and boilerplate to pass in the constants. The zipfile is then Base64 encoded and
wrapped in a small Python script which unzips the file on the managed node and wrapped in a small Python script which decodes the Base64 encoding and places
then invokes Python on the file. (Ansible wraps the zipfile in the Python the zipfile into a temp direcrtory on the managed node. It then extracts just
script so that pipelining will work.) the ansible module script from the zip file and places that in the temporary
directory as well. Then it sets the PYTHONPATH to find python modules inside
of the zip file and invokes :command:`python` on the extracted ansible module.
.. note::
Ansible wraps the zipfile in the Python script for two reasons:
* for compatibility with Python-2.4 and Python-2.6 which have less
featureful versions of Python's ``-m`` command line switch.
* so that pipelining will function properly. Pipelining needs to pipe the
Python module into the Python interpreter on the remote node. Python
understands scripts on stdin but does not understand zip files.
In ziploader, any imports of Python modules from the ``ansible.module_utils`` In ziploader, any imports of Python modules from the ``ansible.module_utils``
package trigger inclusion of that Python file into the zipfile. Instances of package trigger inclusion of that Python file into the zipfile. Instances of
@ -299,16 +310,10 @@ that are included from module_utils are themselves scanned for imports of other
Python modules from module_utils to be included in the zipfile as well. Python modules from module_utils to be included in the zipfile as well.
.. warning:: .. warning::
At present, there are two caveats to how ziploader determines other files At present, Ziploader cannot determine whether an import should be
to import: included if it is a relative import. Always use an absolute import that
has ``ansible.module_utils`` in it to allow ziploader to determine that
* Ziploader cannot determine whether an import should be included if it is the file should be included.
a relative import. Always use an absolute import that has
``ansible.module_utils`` in it to allow ziploader to determine that the
file should be included.
* Ziploader does not include Python packages (directories with
:file:`__init__.py`` in them). Ziploader only works on :file:`*.py`
files that are directly in the :file:`ansible/module_utils` directory.
.. _flow_passing_module_args: .. _flow_passing_module_args:
@ -317,13 +322,11 @@ Passing args
In :ref:`module_replacer`, module arguments are turned into a JSON-ified In :ref:`module_replacer`, module arguments are turned into a JSON-ified
string and substituted into the combined module file. In :ref:`ziploader`, string and substituted into the combined module file. In :ref:`ziploader`,
the JSON-ified string is placed in the the :envvar:`ANSIBLE_MODULE_ARGS` the JSON-ified string is passed into the module via stdin. When
environment variable. When :code:`ansible.module_utils.basic` is imported, a :class:`ansible.module_utils.basic.AnsibleModule` is instantiated,
it places this string in the global variable it parses this string and places the args into
``ansible.module_utils.basic.MODULE_COMPLEX_ARGS`` and removes it from the :attribute:`AnsibleModule.params` where it can be accessed by the module's
environment. Modules should not access this variable directly. Instead, they other code.
should instantiate an :class:`AnsibleModule()` and use
:meth:`AnsibleModule.params` to access the parsed version of the arguments.
.. _flow_passing_module_constants: .. _flow_passing_module_constants:
@ -351,21 +354,17 @@ For now, :code:`ANSIBLE_VERSION` is also available at its old location inside of
``ansible.module_utils.basic``, but that will eventually be removed. ``ansible.module_utils.basic``, but that will eventually be removed.
``SELINUX_SPECIAL_FS`` and ``SYSLOG_FACILITY`` have changed much more. ``SELINUX_SPECIAL_FS`` and ``SYSLOG_FACILITY`` have changed much more.
:ref:`ziploader` passes these as another JSON-ified string inside of the :ref:`ziploader` passes these as part of the JSON-ified argument string via stdin.
:envvar:`ANSIBLE_MODULE_CONSTANTS` environment variable. When When
``ansible.module_utils.basic`` is imported, it places this string in the global :class:`ansible.module_utils.basic.AnsibleModule` is instantiated, it parses this
variable :code:`ansible.module_utils.basic.MODULE_CONSTANTS` and removes it from string and places the constants into :attribute:`AnsibleModule.constants`
the environment. The constants are parsed when an :class:`AnsibleModule` is where other code can access it.
instantiated. Modules shouldn't access any of those directly. Instead, they
should instantiate an :class:`AnsibleModule` and use
:attr:`AnsibleModule.constants` to access the parsed version of these values.
Unlike the ``ANSIBLE_ARGS`` and ``ANSIBLE_VERSION``, where some efforts were Unlike the ``ANSIBLE_VERSION``, where some efforts were made to keep the old
made to keep the old backwards compatible globals available, these two backwards compatible globals available, these two constants are not available
constants are not available at their old names. This is a combination of the at their old names. This is a combination of the degree to which these are
degree to which these are internal to the needs of ``module_utils.basic`` and, internal to the needs of ``module_utils.basic`` and, in the case of
in the case of ``SYSLOG_FACILITY``, how hacky and unsafe the previous ``SYSLOG_FACILITY``, how hacky and unsafe the previous implementation was.
implementation was.
Porting code from the :ref:`module_replacer` method of getting Porting code from the :ref:`module_replacer` method of getting
``SYSLOG_FACILITY`` to the new one is a little more tricky than the other ``SYSLOG_FACILITY`` to the new one is a little more tricky than the other

View File

@ -35,6 +35,7 @@ import os
import subprocess import subprocess
import sys import sys
import traceback import traceback
import shutil
import ansible.utils.vars as utils_vars import ansible.utils.vars as utils_vars
from ansible.parsing.dataloader import DataLoader from ansible.parsing.dataloader import DataLoader
@ -141,18 +142,43 @@ def boilerplate_module(modfile, args, interpreter, check, destfile):
task_vars=task_vars task_vars=task_vars
) )
if module_style == 'new' and 'ZIPLOADER_WRAPPER = True' in module_data:
module_style = 'ziploader'
modfile2_path = os.path.expanduser(destfile) modfile2_path = os.path.expanduser(destfile)
print("* including generated source, if any, saving to: %s" % modfile2_path) print("* including generated source, if any, saving to: %s" % modfile2_path)
print("* this may offset any line numbers in tracebacks/debuggers!") if module_style not in ('ziploader', 'old'):
print("* this may offset any line numbers in tracebacks/debuggers!")
modfile2 = open(modfile2_path, 'w') modfile2 = open(modfile2_path, 'w')
modfile2.write(module_data) modfile2.write(module_data)
modfile2.close() modfile2.close()
modfile = modfile2_path modfile = modfile2_path
return (modfile2_path, module_style) return (modfile2_path, modname, module_style)
def runtest( modfile, argspath): def ziploader_setup(modfile, modname):
os.system("chmod +x %s" % modfile)
cmd = subprocess.Popen([modfile, 'explode'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmd.communicate()
lines = out.splitlines()
if len(lines) != 2 or 'Module expanded into' not in lines[0]:
print("*" * 35)
print("INVALID OUTPUT FROM ZIPLOADER MODULE WRAPPER")
print(out)
sys.exit(1)
debug_dir = lines[1].strip()
argsfile = os.path.join(debug_dir, 'args')
modfile = os.path.join(debug_dir, 'ansible_module_%s.py' % modname)
print("* ziploader module detected; extracted module source to: %s" % debug_dir)
return modfile, argsfile
def runtest(modstyle, modfile, argspath, modname, module_style):
"""Test run a module, piping it's output for reporting.""" """Test run a module, piping it's output for reporting."""
if module_style == 'ziploader':
modfile, argspath = ziploader_setup(modfile, modname)
os.system("chmod +x %s" % modfile) os.system("chmod +x %s" % modfile)
@ -164,25 +190,28 @@ def runtest( modfile, argspath):
(out, err) = cmd.communicate() (out, err) = cmd.communicate()
try: try:
print("***********************************") print("*" * 35)
print("RAW OUTPUT") print("RAW OUTPUT")
print(out) print(out)
print(err) print(err)
results = json.loads(out) results = json.loads(out)
except: except:
print("***********************************") print("*" * 35)
print("INVALID OUTPUT FORMAT") print("INVALID OUTPUT FORMAT")
print(out) print(out)
traceback.print_exc() traceback.print_exc()
sys.exit(1) sys.exit(1)
print("***********************************") print("*" * 35)
print("PARSED OUTPUT") print("PARSED OUTPUT")
print(jsonify(results,format=True)) print(jsonify(results,format=True))
def rundebug(debugger, modfile, argspath): def rundebug(debugger, modfile, argspath, modname, module_style):
"""Run interactively with console debugger.""" """Run interactively with console debugger."""
if module_style == 'ziploader':
modfile, argspath = ziploader_setup(modfile, modname)
if argspath is not None: if argspath is not None:
subprocess.call("%s %s %s" % (debugger, modfile, argspath), shell=True) subprocess.call("%s %s %s" % (debugger, modfile, argspath), shell=True)
else: else:
@ -191,10 +220,10 @@ def rundebug(debugger, modfile, argspath):
def main(): def main():
options, args = parse() options, args = parse()
(modfile, module_style) = boilerplate_module(options.module_path, options.module_args, options.interpreter, options.check, options.filename) (modfile, modname, module_style) = boilerplate_module(options.module_path, options.module_args, options.interpreter, options.check, options.filename)
argspath = None argspath = None
if module_style != 'new': if module_style not in ('new', 'ziploader'):
if module_style == 'non_native_want_json': if module_style == 'non_native_want_json':
argspath = write_argsfile(options.module_args, json=True) argspath = write_argsfile(options.module_args, json=True)
elif module_style == 'old': elif module_style == 'old':
@ -203,10 +232,13 @@ def main():
raise Exception("internal error, unexpected module style: %s" % module_style) raise Exception("internal error, unexpected module style: %s" % module_style)
if options.execute: if options.execute:
if options.debugger: if options.debugger:
rundebug(options.debugger, modfile, argspath) rundebug(options.debugger, modfile, argspath, modname, module_style)
else: else:
runtest(modfile, argspath) runtest(modfile, argspath, modname, module_style)
if __name__ == "__main__": if __name__ == "__main__":
main() try:
main()
finally:
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)

View File

@ -60,6 +60,7 @@ _SNIPPET_PATH = os.path.join(os.path.dirname(__file__), '..', 'module_utils')
ZIPLOADER_TEMPLATE = u'''%(shebang)s ZIPLOADER_TEMPLATE = u'''%(shebang)s
%(coding)s %(coding)s
ZIPLOADER_WRAPPER = True # For test-module script to tell this is a ZIPLOADER_WRAPPER
# This code is part of Ansible, but is an independent component. # This code is part of Ansible, but is an independent component.
# The code in this particular templatable string, and this templatable string # The code in this particular templatable string, and this templatable string
# only, is BSD licensed. Modules which end up using this snippet, which is # only, is BSD licensed. Modules which end up using this snippet, which is
@ -134,16 +135,42 @@ def invoke_module(module, modlib_path, json_params):
def debug(command, zipped_mod, json_params): def debug(command, zipped_mod, json_params):
# The code here normally doesn't run. It's only used for debugging on the # The code here normally doesn't run. It's only used for debugging on the
# remote machine. Run with ANSIBLE_KEEP_REMOTE_FILES=1 envvar and -vvv # remote machine.
# to save the module file remotely. Login to the remote machine and use #
# /path/to/module explode to extract the ZIPDATA payload into source # The subcommands in this function make it easier to debug ziploader
# files. Edit the source files to instrument the code or experiment with # modules. Here's the basic steps:
# different values. Then use /path/to/module execute to run the extracted #
# files you've edited instead of the actual zipped module. # 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::
# [...]
# <host1> 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 # Okay to use __file__ here because we're running from a kept file
basedir = os.path.abspath(os.path.dirname(__file__)) basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'debug_dir')
args_path = os.path.join(basedir, 'args') args_path = os.path.join(basedir, 'args')
script_path = os.path.join(basedir, 'ansible_module_%(ansible_module)s.py')
if command == 'explode': if command == 'explode':
# transform the ZIPDATA into an exploded directory of code and then # 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 # print the path to the code. This is an easy way for people to look
@ -171,7 +198,7 @@ def debug(command, zipped_mod, json_params):
f.close() f.close()
print('Module expanded into:') print('Module expanded into:')
print('%%s' %% os.path.join(basedir, 'ansible')) print('%%s' %% basedir)
exitcode = 0 exitcode = 0
elif command == 'execute': elif command == 'execute':
@ -181,13 +208,14 @@ def debug(command, zipped_mod, json_params):
# This differs slightly from default Ansible execution of Python modules # This differs slightly from default Ansible execution of Python modules
# as it passes the arguments to the module via a file instead of stdin. # 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') pythonpath = os.environ.get('PYTHONPATH')
if pythonpath: if pythonpath:
os.environ['PYTHONPATH'] = ':'.join((basedir, pythonpath)) os.environ['PYTHONPATH'] = ':'.join((basedir, pythonpath))
else: else:
os.environ['PYTHONPATH'] = basedir os.environ['PYTHONPATH'] = basedir
p = subprocess.Popen(['%(interpreter)s', 'ansible_module_%(ansible_module)s.py', args_path], env=os.environ, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) 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() (stdout, stderr) = p.communicate()
if not isinstance(stderr, (bytes, unicode)): if not isinstance(stderr, (bytes, unicode)):
@ -212,8 +240,10 @@ def debug(command, zipped_mod, json_params):
# not actual bugs (as they don't affect the real way that we invoke # not actual bugs (as they don't affect the real way that we invoke
# ansible modules) # ansible modules)
# stub the # stub the args and python path
sys.argv = ['%(ansible_module)s', args_path] sys.argv = ['%(ansible_module)s', args_path]
sys.path.insert(0, basedir)
from ansible_module_%(ansible_module)s import main from ansible_module_%(ansible_module)s import main
main() main()
print('WARNING: Module returned to wrapper instead of exiting') print('WARNING: Module returned to wrapper instead of exiting')
@ -225,10 +255,17 @@ def debug(command, zipped_mod, json_params):
return exitcode return exitcode
if __name__ == '__main__': if __name__ == '__main__':
#
# See comments in the debug() method for information on debugging
#
ZIPLOADER_PARAMS = %(params)s ZIPLOADER_PARAMS = %(params)s
if PY3: if PY3:
ZIPLOADER_PARAMS = ZIPLOADER_PARAMS.encode('utf-8') ZIPLOADER_PARAMS = ZIPLOADER_PARAMS.encode('utf-8')
try: try:
# There's a race condition with the controller removing the
# remote_tmpdir and this module executing under async. So we cannot
# store this in remote_tmpdir (use system tempdir instead)
temp_path = tempfile.mkdtemp(prefix='ansible_') temp_path = tempfile.mkdtemp(prefix='ansible_')
zipped_mod = os.path.join(temp_path, 'ansible_modlib.zip') zipped_mod = os.path.join(temp_path, 'ansible_modlib.zip')
modlib = open(zipped_mod, 'wb') modlib = open(zipped_mod, 'wb')
@ -252,6 +289,24 @@ if __name__ == '__main__':
sys.exit(exitcode) sys.exit(exitcode)
''' '''
def _strip_comments(source):
# Strip comments and blank lines from the wrapper
buf = []
for line in source.splitlines():
l = line.strip()
if not l or l.startswith(u'#'):
continue
buf.append(line)
return u'\n'.join(buf)
if C.DEFAULT_KEEP_REMOTE_FILES:
# Keep comments when KEEP_REMOTE_FILES is set. That way users will see
# the comments with some nice usage instructions
ACTIVE_ZIPLOADER_TEMPLATE = ZIPLOADER_TEMPLATE
else:
# ZIPLOADER_TEMPLATE stripped of comments for smaller over the wire size
ACTIVE_ZIPLOADER_TEMPLATE = _strip_comments(ZIPLOADER_TEMPLATE)
class ModuleDepFinder(ast.NodeVisitor): class ModuleDepFinder(ast.NodeVisitor):
# Caveats: # Caveats:
# This code currently does not handle: # This code currently does not handle:
@ -301,19 +356,6 @@ class ModuleDepFinder(ast.NodeVisitor):
self.generic_visit(node) self.generic_visit(node)
def _strip_comments(source):
# Strip comments and blank lines from the wrapper
buf = []
for line in source.splitlines():
l = line.strip()
if not l or l.startswith(u'#'):
continue
buf.append(line)
return u'\n'.join(buf)
# ZIPLOADER_TEMPLATE stripped of comments for smaller over the wire size
STRIPPED_ZIPLOADER_TEMPLATE = _strip_comments(ZIPLOADER_TEMPLATE)
def _slurp(path): def _slurp(path):
if not os.path.exists(path): if not os.path.exists(path):
raise AnsibleError("imported module support code does not exist at %s" % os.path.abspath(path)) raise AnsibleError("imported module support code does not exist at %s" % os.path.abspath(path))
@ -555,10 +597,11 @@ def _find_snippet_imports(module_name, module_data, module_path, module_args, ta
raise AnsibleError('A different worker process failed to create module file. Look at traceback for that process for debugging information.') raise AnsibleError('A different worker process failed to create module file. Look at traceback for that process for debugging information.')
# Fool the check later... I think we should just remove the check # Fool the check later... I think we should just remove the check
py_module_names.add(('basic',)) py_module_names.add(('basic',))
shebang, interpreter = _get_shebang(u'/usr/bin/python', task_vars) shebang, interpreter = _get_shebang(u'/usr/bin/python', task_vars)
if shebang is None: if shebang is None:
shebang = u'#!/usr/bin/python' shebang = u'#!/usr/bin/python'
output.write(to_bytes(STRIPPED_ZIPLOADER_TEMPLATE % dict( output.write(to_bytes(ACTIVE_ZIPLOADER_TEMPLATE % dict(
zipdata=zipdata, zipdata=zipdata,
ansible_module=module_name, ansible_module=module_name,
params=python_repred_params, params=python_repred_params,

View File

@ -223,6 +223,12 @@ from ansible import __version__
# Backwards compat. New code should just import and use __version__ # Backwards compat. New code should just import and use __version__
ANSIBLE_VERSION = __version__ ANSIBLE_VERSION = __version__
# Internal global holding passed in params and constants. This is consulted
# in case multiple AnsibleModules are created. Otherwise each AnsibleModule
# would attempt to read from stdin. Other code should not use this directly
# as it is an internal implementation detail
_ANSIBLE_ARGS = None
FILE_COMMON_ARGUMENTS=dict( FILE_COMMON_ARGUMENTS=dict(
src = dict(), src = dict(),
mode = dict(type='raw'), mode = dict(type='raw'),
@ -1457,23 +1463,28 @@ class AnsibleModule(object):
''' read the input and set the params attribute. Sets the constants as well.''' ''' read the input and set the params attribute. Sets the constants as well.'''
# debug overrides to read args from file or cmdline # debug overrides to read args from file or cmdline
# Avoid tracebacks when locale is non-utf8 global _ANSIBLE_ARGS
# We control the args and we pass them as utf8 if _ANSIBLE_ARGS is not None:
if len(sys.argv) > 1: buffer = _ANSIBLE_ARGS
if os.path.isfile(sys.argv[1]):
fd = open(sys.argv[1], 'rb')
buffer = fd.read()
fd.close()
else:
buffer = sys.argv[1]
if sys.version_info >= (3,):
buffer = buffer.encode('utf-8', errors='surrogateescape')
# default case, read from stdin
else: else:
if sys.version_info < (3,): # Avoid tracebacks when locale is non-utf8
buffer = sys.stdin.read() # We control the args and we pass them as utf8
if len(sys.argv) > 1:
if os.path.isfile(sys.argv[1]):
fd = open(sys.argv[1], 'rb')
buffer = fd.read()
fd.close()
else:
buffer = sys.argv[1]
if sys.version_info >= (3,):
buffer = buffer.encode('utf-8', errors='surrogateescape')
# default case, read from stdin
else: else:
buffer = sys.stdin.buffer.read() if sys.version_info < (3,):
buffer = sys.stdin.read()
else:
buffer = sys.stdin.buffer.read()
_ANSIBLE_ARGS = buffer
try: try:
params = json.loads(buffer.decode('utf-8')) params = json.loads(buffer.decode('utf-8'))

View File

@ -45,6 +45,7 @@ class TestAnsibleModuleExitJson(unittest.TestCase):
self.stdout_swap_ctx = swap_stdout() self.stdout_swap_ctx = swap_stdout()
self.fake_stream = self.stdout_swap_ctx.__enter__() self.fake_stream = self.stdout_swap_ctx.__enter__()
reload(basic)
self.module = basic.AnsibleModule(argument_spec=dict()) self.module = basic.AnsibleModule(argument_spec=dict())
def tearDown(self): def tearDown(self):
@ -125,6 +126,7 @@ class TestAnsibleModuleExitValuesRemoved(unittest.TestCase):
params = json.dumps(params) params = json.dumps(params)
with swap_stdin_and_argv(stdin_data=params): with swap_stdin_and_argv(stdin_data=params):
reload(basic)
with swap_stdout(): with swap_stdout():
module = basic.AnsibleModule( module = basic.AnsibleModule(
argument_spec = dict( argument_spec = dict(
@ -146,6 +148,7 @@ class TestAnsibleModuleExitValuesRemoved(unittest.TestCase):
params = dict(ANSIBLE_MODULE_ARGS=args, ANSIBLE_MODULE_CONSTANTS={}) params = dict(ANSIBLE_MODULE_ARGS=args, ANSIBLE_MODULE_CONSTANTS={})
params = json.dumps(params) params = json.dumps(params)
with swap_stdin_and_argv(stdin_data=params): with swap_stdin_and_argv(stdin_data=params):
reload(basic)
with swap_stdout(): with swap_stdout():
module = basic.AnsibleModule( module = basic.AnsibleModule(
argument_spec = dict( argument_spec = dict(

View File

@ -49,6 +49,7 @@ class TestAnsibleModuleSysLogSmokeTest(unittest.TestCase):
self.stdin_swap = swap_stdin_and_argv(stdin_data=args) self.stdin_swap = swap_stdin_and_argv(stdin_data=args)
self.stdin_swap.__enter__() self.stdin_swap.__enter__()
reload(basic)
self.am = basic.AnsibleModule( self.am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
) )
@ -85,6 +86,7 @@ class TestAnsibleModuleJournaldSmokeTest(unittest.TestCase):
self.stdin_swap = swap_stdin_and_argv(stdin_data=args) self.stdin_swap = swap_stdin_and_argv(stdin_data=args)
self.stdin_swap.__enter__() self.stdin_swap.__enter__()
reload(basic)
self.am = basic.AnsibleModule( self.am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
) )
@ -132,6 +134,7 @@ class TestAnsibleModuleLogSyslog(unittest.TestCase):
self.stdin_swap = swap_stdin_and_argv(stdin_data=args) self.stdin_swap = swap_stdin_and_argv(stdin_data=args)
self.stdin_swap.__enter__() self.stdin_swap.__enter__()
reload(basic)
self.am = basic.AnsibleModule( self.am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
) )
@ -192,6 +195,7 @@ class TestAnsibleModuleLogJournal(unittest.TestCase):
self.stdin_swap = swap_stdin_and_argv(stdin_data=args) self.stdin_swap = swap_stdin_and_argv(stdin_data=args)
self.stdin_swap.__enter__() self.stdin_swap.__enter__()
reload(basic)
self.am = basic.AnsibleModule( self.am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
) )

View File

@ -67,6 +67,7 @@ class TestAnsibleModuleRunCommand(unittest.TestCase):
self.stdin_swap = swap_stdin_and_argv(stdin_data=args) self.stdin_swap = swap_stdin_and_argv(stdin_data=args)
self.stdin_swap.__enter__() self.stdin_swap.__enter__()
reload(basic)
self.module = AnsibleModule(argument_spec=dict()) self.module = AnsibleModule(argument_spec=dict())
self.module.fail_json = MagicMock(side_effect=SystemExit) self.module.fail_json = MagicMock(side_effect=SystemExit)

View File

@ -26,6 +26,12 @@ import json
from ansible.compat.tests import unittest from ansible.compat.tests import unittest
from units.mock.procenv import swap_stdin_and_argv from units.mock.procenv import swap_stdin_and_argv
try:
from importlib import reload
except:
# Py2 has reload as a builtin
pass
class TestAnsibleModuleExitJson(unittest.TestCase): class TestAnsibleModuleExitJson(unittest.TestCase):
def test_module_utils_basic_safe_eval(self): def test_module_utils_basic_safe_eval(self):
@ -34,6 +40,7 @@ class TestAnsibleModuleExitJson(unittest.TestCase):
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={}, ANSIBLE_MODULE_CONSTANTS={})) args = json.dumps(dict(ANSIBLE_MODULE_ARGS={}, ANSIBLE_MODULE_CONSTANTS={}))
with swap_stdin_and_argv(stdin_data=args): with swap_stdin_and_argv(stdin_data=args):
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec=dict(), argument_spec=dict(),
) )

View File

@ -31,6 +31,12 @@ try:
except ImportError: except ImportError:
import __builtin__ as builtins import __builtin__ as builtins
try:
from importlib import reload
except:
# Py2 has reload as a builtin
pass
from units.mock.procenv import swap_stdin_and_argv from units.mock.procenv import swap_stdin_and_argv
from ansible.compat.tests import unittest from ansible.compat.tests import unittest
@ -291,6 +297,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={"foo": "hello"}, ANSIBLE_MODULE_CONSTANTS={})) args = json.dumps(dict(ANSIBLE_MODULE_ARGS={"foo": "hello"}, ANSIBLE_MODULE_CONSTANTS={}))
with swap_stdin_and_argv(stdin_data=args): with swap_stdin_and_argv(stdin_data=args):
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = arg_spec, argument_spec = arg_spec,
mutually_exclusive = mut_ex, mutually_exclusive = mut_ex,
@ -307,6 +314,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={}, ANSIBLE_MODULE_CONSTANTS={})) args = json.dumps(dict(ANSIBLE_MODULE_ARGS={}, ANSIBLE_MODULE_CONSTANTS={}))
with swap_stdin_and_argv(stdin_data=args): with swap_stdin_and_argv(stdin_data=args):
reload(basic)
self.assertRaises( self.assertRaises(
SystemExit, SystemExit,
basic.AnsibleModule, basic.AnsibleModule,
@ -353,6 +361,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_load_file_common_arguments(self): def test_module_utils_basic_ansible_module_load_file_common_arguments(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -401,6 +410,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_selinux_mls_enabled(self): def test_module_utils_basic_ansible_module_selinux_mls_enabled(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -420,6 +430,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_selinux_initial_context(self): def test_module_utils_basic_ansible_module_selinux_initial_context(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -433,6 +444,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_selinux_enabled(self): def test_module_utils_basic_ansible_module_selinux_enabled(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -464,6 +476,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_selinux_default_context(self): def test_module_utils_basic_ansible_module_selinux_default_context(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -499,6 +512,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_selinux_context(self): def test_module_utils_basic_ansible_module_selinux_context(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -540,6 +554,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_is_special_selinux_path(self): def test_module_utils_basic_ansible_module_is_special_selinux_path(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
args = json.dumps(dict(ANSIBLE_MODULE_ARGS={}, ANSIBLE_MODULE_CONSTANTS={"SELINUX_SPECIAL_FS": "nfs,nfsd,foos"})) args = json.dumps(dict(ANSIBLE_MODULE_ARGS={}, ANSIBLE_MODULE_CONSTANTS={"SELINUX_SPECIAL_FS": "nfs,nfsd,foos"}))
@ -584,6 +599,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_to_filesystem_str(self): def test_module_utils_basic_ansible_module_to_filesystem_str(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -608,6 +624,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_find_mount_point(self): def test_module_utils_basic_ansible_module_find_mount_point(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -631,18 +648,19 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_set_context_if_different(self): def test_module_utils_basic_ansible_module_set_context_if_different(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
) )
basic.HAS_SELINUX = False basic.HAVE_SELINUX = False
am.selinux_enabled = MagicMock(return_value=False) am.selinux_enabled = MagicMock(return_value=False)
self.assertEqual(am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], True), True) self.assertEqual(am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], True), True)
self.assertEqual(am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], False), False) self.assertEqual(am.set_context_if_different('/path/to/file', ['foo_u', 'foo_r', 'foo_t', 's0'], False), False)
basic.HAS_SELINUX = True basic.HAVE_SELINUX = True
am.selinux_enabled = MagicMock(return_value=True) am.selinux_enabled = MagicMock(return_value=True)
am.selinux_context = MagicMock(return_value=['bar_u', 'bar_r', None, None]) am.selinux_context = MagicMock(return_value=['bar_u', 'bar_r', None, None])
@ -675,6 +693,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_set_owner_if_different(self): def test_module_utils_basic_ansible_module_set_owner_if_different(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -713,6 +732,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_set_group_if_different(self): def test_module_utils_basic_ansible_module_set_group_if_different(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -751,6 +771,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module_set_mode_if_different(self): def test_module_utils_basic_ansible_module_set_mode_if_different(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -838,6 +859,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
): ):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),
@ -1015,6 +1037,7 @@ class TestModuleUtilsBasic(unittest.TestCase):
def test_module_utils_basic_ansible_module__symbolic_mode_to_octal(self): def test_module_utils_basic_ansible_module__symbolic_mode_to_octal(self):
from ansible.module_utils import basic from ansible.module_utils import basic
reload(basic)
am = basic.AnsibleModule( am = basic.AnsibleModule(
argument_spec = dict(), argument_spec = dict(),