Skip to content

Commit

Permalink
Module utils default path (#20913)
Browse files Browse the repository at this point in the history
* Make the module_utils path configurable
* Add a config value to define the path site module_utils files
* Handle module_utils that do not have source as an error
* Make an integration test for module_utils envvar working
* Add documentation for the ANSIBLE_MODULE_UTILS config option/envvar
* Add it to the sample ansible.cfg
* Add it to intro_configuration.
* Also modify intro_configuration to place envvars on equal footing with
  the config options (will need to document the envvar names in the
  future)
* Also add the ANSIBLE_LIBRARY use case from
  #15432 so we can close out
  that bug.
  • Loading branch information
abadger committed Feb 3, 2017
1 parent 6580fb2 commit 1df7d95
Show file tree
Hide file tree
Showing 22 changed files with 145 additions and 24 deletions.
77 changes: 58 additions & 19 deletions docs/docsite/rst/intro_configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ You may wish to consult the `ansible.cfg in source control <https://raw.github.c
Environmental configuration
```````````````````````````

Ansible also allows configuration of settings via environment variables. If these environment variables are set, they will
override any setting loaded from the configuration file. These variables are for brevity not defined here, but look in `constants.py <https://github.com/ansible/ansible/blob/devel/lib/ansible/constants.py>`_ in the source tree if you want to use these. They are mostly considered to be a legacy system as compared to the config file, but are equally valid.
Ansible also allows configuration of settings via environment variables. If
these environment variables are set, they will override any setting loaded
from the configuration file. These variables are defined in `constants.py <https://github.com/ansible/ansible/blob/devel/lib/ansible/constants.py>`_.

.. _config_values_by_section:

Expand Down Expand Up @@ -521,8 +522,25 @@ This is the default location Ansible looks to find modules::

library = /usr/share/ansible

Ansible knows how to look in multiple locations if you feed it a colon separated path, and it also will look for modules in the
"./library" directory alongside a playbook.
Ansible can look in multiple locations if you feed it a colon
separated path, and it also will look for modules in the :file:`./library`
directory alongside a playbook.

This can be used to manage modules pulled from several different locations.
For instance, a site wishing to checkout modules from several different git
repositories might handle it like this:

.. code-block:: shell-session
$ mkdir -p /srv/modules
$ cd /srv/modules
$ git checkout https://vendor_modules .
$ git checkout ssh://custom_modules .
$ export ANSIBLE_LIBRARY=/srv/modules/custom_modules:/srv/modules/vendor_modules
$ ansible [...]
In case of modules with the same name, the library paths are searched in order
and the first module found with that name is used.

.. _local_tmp:

Expand Down Expand Up @@ -586,23 +604,8 @@ together. The same holds true for --skip-tags.
default value will be True. After 2.4, the option is going away.
Multiple --tags and multiple --skip-tags will always be merged together.

.. _module_set_locale:

module_set_locale
=================

This boolean value controls whether or not Ansible will prepend locale-specific environment variables (as specified
via the :ref:`module_lang` configuration option). If enabled, it results in the LANG, LC_MESSAGES, and LC_ALL
being set when the module is executed on the given remote system. By default this is disabled.

.. note::

The module_set_locale option was added in Ansible-2.1 and defaulted to
True. The default was changed to False in Ansible-2.2

.. _module_lang:


module_lang
===========

Expand All @@ -611,6 +614,10 @@ By default, the value is value `LANG` on the controller or, if unset, `en_US.UTF

module_lang = en_US.UTF-8

.. note::

This is only used if :ref:`module_set_locale` is set to True.

.. _module_name:

module_name
Expand All @@ -622,6 +629,38 @@ it to 'shell'::

module_name = command

.. _module_set_locale:

module_set_locale
=================

This boolean value controls whether or not Ansible will prepend locale-specific environment variables (as specified
via the :ref:`module_lang` configuration option). If enabled, it results in the LANG, LC_MESSAGES, and LC_ALL
being set when the module is executed on the given remote system. By default this is disabled.

.. note::

The module_set_locale option was added in Ansible-2.1 and defaulted to
True. The default was changed to False in Ansible-2.2

.. _module_utils:

module_utils
============

This is the default location Ansible looks to find module_utils::

module_utils = /usr/share/ansible/my_module_utils

module_utils are python modules that Ansible is able to combine with Ansible
modules when sending them to the remote machine. Having custom module_utils
is useful for extracting common code when developing a set of site-specific
modules.

Ansible can look in multiple locations if you feed it a colon
separated path, and it also will look for modules in the
:file:`./module_utils` directory alongside a playbook.

.. _nocolor:

nocolor
Expand Down
1 change: 1 addition & 0 deletions examples/ansible.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#inventory = /etc/ansible/hosts
#library = /usr/share/my_modules/
#module_utils = /usr/share/my_module_utils/
#remote_tmp = ~/.ansible/tmp
#local_tmp = ~/.ansible/tmp
#forks = 5
Expand Down
5 changes: 4 additions & 1 deletion lib/ansible/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ def load_config_file():
#### GENERALLY CONFIGURABLE THINGS ####
DEFAULT_DEBUG = get_config(p, DEFAULTS, 'debug', 'ANSIBLE_DEBUG', False, value_type='boolean')
DEFAULT_HOST_LIST = get_config(p, DEFAULTS,'inventory', 'ANSIBLE_INVENTORY', DEPRECATED_HOST_LIST, value_type='path')
DEFAULT_MODULE_PATH = get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', None, value_type='pathlist')
DEFAULT_ROLES_PATH = get_config(p, DEFAULTS, 'roles_path', 'ANSIBLE_ROLES_PATH', '/etc/ansible/roles', value_type='pathlist', expand_relative_paths=True)
DEFAULT_REMOTE_TMP = get_config(p, DEFAULTS, 'remote_tmp', 'ANSIBLE_REMOTE_TEMP', '~/.ansible/tmp')
DEFAULT_LOCAL_TMP = get_config(p, DEFAULTS, 'local_tmp', 'ANSIBLE_LOCAL_TEMP', '~/.ansible/tmp', value_type='tmppath')
Expand Down Expand Up @@ -285,16 +284,20 @@ def load_config_file():
# (mapping of param: squash field)
DEFAULT_SQUASH_ACTIONS = get_config(p, DEFAULTS, 'squash_actions', 'ANSIBLE_SQUASH_ACTIONS', "apk, apt, dnf, homebrew, openbsd_pkg, pacman, pkgng, yum, zypper", value_type='list')
# paths

DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '~/.ansible/plugins/action:/usr/share/ansible/plugins/action', value_type='pathlist')
DEFAULT_CACHE_PLUGIN_PATH = get_config(p, DEFAULTS, 'cache_plugins', 'ANSIBLE_CACHE_PLUGINS', '~/.ansible/plugins/cache:/usr/share/ansible/plugins/cache', value_type='pathlist')
DEFAULT_CALLBACK_PLUGIN_PATH = get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '~/.ansible/plugins/callback:/usr/share/ansible/plugins/callback', value_type='pathlist')
DEFAULT_CONNECTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'connection_plugins', 'ANSIBLE_CONNECTION_PLUGINS', '~/.ansible/plugins/connection:/usr/share/ansible/plugins/connection', value_type='pathlist')
DEFAULT_LOOKUP_PLUGIN_PATH = get_config(p, DEFAULTS, 'lookup_plugins', 'ANSIBLE_LOOKUP_PLUGINS', '~/.ansible/plugins/lookup:/usr/share/ansible/plugins/lookup', value_type='pathlist')
DEFAULT_MODULE_PATH = get_config(p, DEFAULTS, 'library', 'ANSIBLE_LIBRARY', None, value_type='pathlist')
DEFAULT_MODULE_UTILS_PATH = get_config(p, DEFAULTS, 'module_utils', 'ANSIBLE_MODULE_UTILS', None, value_type='pathlist')
DEFAULT_INVENTORY_PLUGIN_PATH = get_config(p, DEFAULTS, 'inventory_plugins', 'ANSIBLE_INVENTORY_PLUGINS', '~/.ansible/plugins/inventory:/usr/share/ansible/plugins/inventory', value_type='pathlist')
DEFAULT_VARS_PLUGIN_PATH = get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', '~/.ansible/plugins/vars:/usr/share/ansible/plugins/vars', value_type='pathlist')
DEFAULT_FILTER_PLUGIN_PATH = get_config(p, DEFAULTS, 'filter_plugins', 'ANSIBLE_FILTER_PLUGINS', '~/.ansible/plugins/filter:/usr/share/ansible/plugins/filter', value_type='pathlist')
DEFAULT_TEST_PLUGIN_PATH = get_config(p, DEFAULTS, 'test_plugins', 'ANSIBLE_TEST_PLUGINS', '~/.ansible/plugins/test:/usr/share/ansible/plugins/test', value_type='pathlist')
DEFAULT_STRATEGY_PLUGIN_PATH = get_config(p, DEFAULTS, 'strategy_plugins', 'ANSIBLE_STRATEGY_PLUGINS', '~/.ansible/plugins/strategy:/usr/share/ansible/plugins/strategy', value_type='pathlist')

DEFAULT_STRATEGY = get_config(p, DEFAULTS, 'strategy', 'ANSIBLE_STRATEGY', 'linear')
DEFAULT_STDOUT_CALLBACK = get_config(p, DEFAULTS, 'stdout_callback', 'ANSIBLE_STDOUT_CALLBACK', 'default')
# cache
Expand Down
16 changes: 14 additions & 2 deletions lib/ansible/executor/module_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,20 @@ def recursive_finder(name, data, py_module_names, py_module_cache, zf):
if module_info is None:
msg = ['Could not find imported module support code for %s. Looked for' % name]
if idx == 2:
msg.append('either %s or %s' % (py_module_name[-1], py_module_name[-2]))
msg.append('either %s.py or %s.py' % (py_module_name[-1], py_module_name[-2]))
else:
msg.append(py_module_name[-1])
raise AnsibleError(' '.join(msg))

# Found a byte compiled file rather than source. We cannot send byte
# compiled over the wire as the python version might be different.
# imp.find_module seems to prefer to return source packages so we just
# error out if imp.find_module returns byte compiled files (This is
# fragile as it depends on undocumented imp.find_module behaviour)
if module_info[2][2] not in (imp.PY_SOURCE, imp.PKG_DIRECTORY):
msg = ['Could not find python source for imported module support code for %s. Looked for' % name]
if idx == 2:
msg.append('either %s.py or %s.py' % (py_module_name[-1], py_module_name[-2]))
else:
msg.append(py_module_name[-1])
raise AnsibleError(' '.join(msg))
Expand Down Expand Up @@ -571,7 +584,6 @@ def _find_module_utils(module_name, b_module_data, module_path, module_args, tas
Given the source of the module, convert it to a Jinja2 template to insert
module code and return whether it's a new or old style module.
"""

module_substyle = module_style = 'old'

# module_style is something important to calling code (ActionBase). It
Expand Down
2 changes: 1 addition & 1 deletion lib/ansible/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ def all(self, *args, **kwargs):
module_utils_loader = PluginLoader(
'',
'ansible.module_utils',
'module_utils',
C.DEFAULT_MODULE_UTILS_PATH,
'module_utils',
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/python
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.json_utils import data
from ansible.module_utils.mork import data as mork_data

results = {"json_utils": data, "mork": mork_data}

AnsibleModule(argument_spec=dict()).exit_json(**results)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sysv_is_enabled = 'sysv_is_enabled'
51 changes: 51 additions & 0 deletions test/integration/targets/module_utils/module_utils_envvar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
- hosts: localhost
gather_facts: no
tasks:
- name: Use a specially crafted module to see if things were imported correctly
test:
register: result

- name: Check that these are all loaded from playbook dir's module_utils
assert:
that:
- 'result["abcdefgh"] == "abcdefgh"'
- 'result["bar0"] == "bar0"'
- 'result["bar1"] == "bar1"'
- 'result["bar2"] == "bar2"'
- 'result["baz1"] == "baz1"'
- 'result["baz2"] == "baz2"'
- 'result["foo0"] == "foo0"'
- 'result["foo1"] == "foo1"'
- 'result["foo2"] == "foo2"'
- 'result["qux1"] == "qux1"'
- 'result["qux2"] == ["qux2:quux", "qux2:quuz"]'
- 'result["spam1"] == "spam1"'
- 'result["spam2"] == "spam2"'
- 'result["spam3"] == "spam3"'
- 'result["spam4"] == "spam4"'
- 'result["spam5"] == ["spam5:bacon", "spam5:eggs"]'
- 'result["spam6"] == ["spam6:bacon", "spam6:eggs"]'
- 'result["spam7"] == ["spam7:bacon", "spam7:eggs"]'
- 'result["spam8"] == ["spam8:bacon", "spam8:eggs"]'

# Test that overriding something in module_utils with something in the local library works
- name: Test that playbook dir's module_utils overrides facts.py
test_override:
register: result

- name: Make sure the we used the local facts.py, not the one shipped with ansible
assert:
that:
- 'result["data"] == "overridden facts.py"'

- name: Test that importing something from the module_utils in the env_vars works
test_env_override:
register: result

- name: Make sure we used the module_utils from the env_var for these
assert:
that:
# Override of shipped module_utils
- 'result["json_utils"] == "overridden json_utils"'
# Only i nthe env vars directory
- 'result["mork"] == "mork"'
3 changes: 2 additions & 1 deletion test/integration/targets/module_utils/module_utils_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@
ignore_errors: True
register: result

- debug: var=result
- name: Make sure we failed in AnsiBallZ
assert:
that:
- 'result["failed"] == True'
- '"Could not find imported module support code for test_failure. Looked for either foo or zebra" == result["msg"]'
- '"Could not find imported module support code for test_failure. Looked for either foo.py or zebra.py" == result["msg"]'
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data = 'should not be visible abcdefgh'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data = 'should not be visible facts.py'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data = 'overridden json_utils'
1 change: 1 addition & 0 deletions test/integration/targets/module_utils/other_mu_dir/mork.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data = 'mork'
1 change: 1 addition & 0 deletions test/integration/targets/module_utils/runme.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
set -eux

ansible-playbook module_utils_test.yml -i ../../inventory -v "$@"
ANSIBLE_MODULE_UTILS=$(pwd)/other_mu_dir ansible-playbook module_utils_envvar.yml -i ../../inventory -v "$@"

0 comments on commit 1df7d95

Please sign in to comment.