mirror of https://github.com/ansible/ansible.git
(cherry picked from commit 9a76441c02
)
pull/45369/head
parent
e7e345da7e
commit
1301d4edc8
@ -0,0 +1,149 @@
|
||||
.. _debugging:
|
||||
|
||||
*****************
|
||||
Debugging modules
|
||||
*****************
|
||||
|
||||
Debugging (local)
|
||||
=================
|
||||
|
||||
To break into a module running on ``localhost`` and step through with the debugger:
|
||||
|
||||
- Set a breakpoint in the module: ``import pdb; pdb.set_trace()``
|
||||
- Run the module on the local machine: ``$ python -m pdb ./my_new_test_module.py ./args.json``
|
||||
|
||||
Debugging (remote)
|
||||
==================
|
||||
|
||||
To debug a module running on a remote target (i.e. not ``localhost``):
|
||||
|
||||
#. On your controller machine (running Ansible) set ``ANSIBLE_KEEP_REMOTE_FILES=1`` to tell Ansible to retain the modules it sends to the remote machine instead of removing them after you playbook runs.
|
||||
#. Run your playbook targeting the remote machine and specify ``-vvvv`` (verbose) to display the remote location Ansible is using for the modules (among many other things).
|
||||
#. Take note of the directory Ansible used to store modules on the remote host. This directory is usually under the home directory of your ``ansible_ssh_user``, in the form ``~/.ansible/tmp/ansible-tmp-...``.
|
||||
#. SSH into the remote target after the playbook runs.
|
||||
#. Navigate to the directory you noted in step 3.
|
||||
#. Extract the module you want to debug from the zipped file that Ansible sent to the remote host: ``$ python my_test_module.py explode``. Ansible will expand the module into ``./debug-dir``. You can optionally run the zipped file by specifying ``python my_test_module.py``.
|
||||
#. Navigate to the debug directory: ``$ cd debug-dir``.
|
||||
#. Modify or set a breakpoint in ``ansible_module_my_test_module.py``.
|
||||
#. Ensure that the unzipped module is executable: ``$ chmod 755 ansible_module_my_test_module.py``.
|
||||
#. Run the unzipped module directly, passing the ``args`` file that contains the params that were originally passed: ``$ ./ansible_module_my_test_module.py args``. This approach is good for reproducing behavior as well as modifying the parameters for debugging.
|
||||
|
||||
|
||||
.. _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, 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:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ 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_FILES` to ``1`` tells Ansible to keep the
|
||||
remote module files instead of deleting them after the module finishes
|
||||
executing. Giving Ansible the ``-vvv`` option 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:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ 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 its 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
|
||||
:mod:`ansible.module_utils` that is used by the module. Ansible includes
|
||||
files for any :`module:`ansible.module_utils` imports in the module but not
|
||||
any files from any other module. So if your module uses
|
||||
:mod:`ansible.module_utils.url` Ansible will include it for you, but if
|
||||
your module includes :mod:`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:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ 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 :func:`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.
|
@ -0,0 +1,95 @@
|
||||
.. _using_local_modules_and_plugins:
|
||||
.. _developing_locally:
|
||||
|
||||
**********************************
|
||||
Adding modules and plugins locally
|
||||
**********************************
|
||||
|
||||
.. contents:: Topics
|
||||
:local:
|
||||
|
||||
The easiest, quickest, and most popular way to extend Ansible is to copy or write a module or a plugin for local use. You can store local modules and plugins on your Ansible control node for use within your team or organization. You can also share a local plugin or module by embedding it in a role and publishing it on Ansible Galaxy. If you've been using roles off Galaxy, you may have been using local modules and plugins without even realizing it. If you're using a local module or plugin that already exists, this page is all you need.
|
||||
|
||||
Extending Ansible with local modules and plugins offers lots of shortcuts:
|
||||
|
||||
* You can copy other people's modules and plugins.
|
||||
* If you're writing a new module, you can choose any programming language you like.
|
||||
* You don't have to clone the main Ansible repo.
|
||||
* You don't have to open a pull request.
|
||||
* You don't have to add tests (though we recommend that you do!).
|
||||
|
||||
To save a local module or plugin so Ansible can find and use it, drop the module or plugin in the correct "magic" directory. For local modules, use the name of the file as the module name: for example, if the module file is ``~/.ansible/plugins/modules/local_users.py``, use ``local_users`` as the module name.
|
||||
|
||||
.. _modules_vs_plugins:
|
||||
|
||||
Modules and plugins: what's the difference?
|
||||
===========================================
|
||||
If you're looking to add local functionality to Ansible, you may be wondering whether you need a module or a plugin. Here's a quick overview of the differences:
|
||||
|
||||
* Modules are reusable, standalone scripts that can be used by the Ansible API, the :command:`ansible` command, or the :command:`ansible-playbook` command. Modules provide a defined interface, accepting arguments and returning information to Ansible by printing a JSON string to stdout before exiting.
|
||||
* Plugins are shared code that can be used by any module. They provide abilities like cacheing information or copying files that are useful for many modules.
|
||||
|
||||
.. _local_modules:
|
||||
|
||||
Adding a module locally
|
||||
=======================
|
||||
Ansible automatically loads all executable files found in certain directories as modules, so you can create or add a local module in any of these locations:
|
||||
|
||||
* any directory added to the ``ANSIBLE_LIBRARY`` environment variable (``$ANSIBLE_LIBRARY`` takes a colon-separated list like ``$PATH``)
|
||||
* ``~/.ansible/plugins/modules/``
|
||||
* ``/usr/share/ansible/plugins/modules/``
|
||||
|
||||
Once you save your module file in one of these locations, Ansible will load it and you can use it in any local task, playbook, or role.
|
||||
|
||||
To confirm that ``my_custom_module`` is available:
|
||||
|
||||
* type ``ansible-doc -t module my_custom_module``. You should see the documentation for that module.
|
||||
|
||||
To use a local module only in certain playbooks:
|
||||
|
||||
* store it in a sub-directory called ``library`` in the directory that contains the playbook(s)
|
||||
|
||||
To use a local module only in a single role:
|
||||
|
||||
* store it in a sub-directory called ``library`` within that role
|
||||
|
||||
.. _distributing_plugins:
|
||||
.. _local_plugins:
|
||||
|
||||
Adding a plugin locally
|
||||
=======================
|
||||
Ansible loads plugins automatically too, loading each type of plugin separately from a directory named for the type of plugin. Here's the full list of plugin directory names:
|
||||
|
||||
* action_plugins*
|
||||
* cache_plugins
|
||||
* callback_plugins
|
||||
* connection_plugins
|
||||
* filter_plugins*
|
||||
* inventory_plugins
|
||||
* lookup_plugins
|
||||
* shell_plugins
|
||||
* strategy_plugins
|
||||
* test_plugins*
|
||||
* vars_plugins
|
||||
|
||||
You can create or add a local plugin in any of these locations:
|
||||
|
||||
* any directory added to the relevant ``ANSIBLE_plugin_type_PLUGINS`` environment variable (these variables, such as ``$ANSIBLE_INVENTORY_PLUGINS`` and ``$ANSIBLE_VARS_PLUGINS`` take colon-separated lists like ``$PATH``)
|
||||
* the directory named for the correct ``plugin_type`` within ``~/.ansible/plugins/`` - for example, ``~/.ansible/plugins/callback_plugins``
|
||||
* the directory named for the correct ``plugin_type`` within ``/usr/share/ansible/plugins/`` - for example, ``/usr/share/ansible/plugins/plugin_type/action_plugins``
|
||||
|
||||
Once your plugin file is in one of these locations, Ansible will load it and you can use it in a any local module, task, playbook, or role.
|
||||
|
||||
To confirm that ``plugins/plugin_type/my_custom_plugin`` is available:
|
||||
|
||||
* type ``ansible-doc -t <plugin_type> my_custom_lookup_plugin``. For example, ``ansible-doc -t lookup my_custom_lookup_plugin``. You should see the documentation for that plugin. This works for all plugin types except the ones marked with ``*`` in the list above - see :ref:`ansible-doc` for more details.
|
||||
|
||||
To use your local plugin only in certain playbooks:
|
||||
|
||||
* store it in a sub-directory for the correct ``plugin_type`` (for example, ``callback_plugins`` or ``inventory_plugins``) in the directory that contains the playbook(s)
|
||||
|
||||
To use your local plugin only in a single role:
|
||||
|
||||
* store it in a sub-directory for the correct ``plugin_type`` (for example, ``cache_plugins`` or ``strategy_plugins``) within that role
|
||||
|
||||
When shipped as part of a role, the plugin will be available as soon as the role is called in the play.
|
@ -1,197 +1,143 @@
|
||||
.. _developing_modules_best_practices:
|
||||
.. _module_dev_conventions:
|
||||
|
||||
Conventions, Best Practices, and Pitfalls
|
||||
`````````````````````````````````````````
|
||||
*******************************
|
||||
Conventions, tips, and pitfalls
|
||||
*******************************
|
||||
|
||||
As a reminder from the example code above, here are some basic conventions
|
||||
and guidelines:
|
||||
.. contents:: Topics
|
||||
:local:
|
||||
|
||||
* If the module is addressing an object, the parameter for that object should be called 'name' whenever possible, or accept 'name' as an alias.
|
||||
As you design and develop modules, follow these basic conventions and tips for clean, usable code:
|
||||
|
||||
* If you have a company module that returns facts specific to your installations, a good name for this module is `site_facts`.
|
||||
Scoping your module(s)
|
||||
======================
|
||||
|
||||
* Modules accepting boolean status should generally accept 'yes', 'no', 'true', 'false', or anything else a user may likely throw at them. The AnsibleModule common code supports this with "type='bool'".
|
||||
Especially if you want to contribute your module(s) back to Ansible Core, make sure each module includes enough logic and functionality, but not too much. If you're finding these guidelines tricky, consider :ref:`whether you really need to write a module <module_dev_should_you>` at all.
|
||||
|
||||
* Include a minimum of dependencies if possible. If there are dependencies, document them at the top of the module file, and have the module raise JSON error messages when the import fails.
|
||||
* Each module should have a concise and well-defined functionality. Basically, follow the UNIX philosophy of doing one thing well.
|
||||
* Do not add ``list`` or ``info`` state options to an existing module - create a new ``_facts`` module.
|
||||
* Modules should not require that a user know all the underlying options of an API/tool to be used. For instance, if the legal values for a required module parameter cannot be documented, the module does not belong in Ansible Core.
|
||||
* Modules should encompass much of the logic for interacting with a resource. A lightweight wrapper around a complex API forces users to offload too much logic into their playbooks. If you want to connect Ansible to a complex API, :ref:`create multiple modules <developing_modules_in_groups>` that interact with smaller individual pieces of the API.
|
||||
* Avoid creating a module that does the work of other modules; this leads to code duplication and divergence, and makes things less uniform, unpredictable and harder to maintain. Modules should be the building blocks. If you are asking 'how can I have a module execute other modules' ... you want to write a role.
|
||||
|
||||
* Modules must be self-contained in one file to be auto-transferred by ansible.
|
||||
Designing module interfaces
|
||||
===========================
|
||||
|
||||
* If packaging modules in an RPM, they only need to be installed on the control machine and should be dropped into /usr/share/ansible. This is entirely optional and up to you.
|
||||
* If your module is addressing an object, the parameter for that object should be called ``name`` whenever possible, or accept ``name`` as an alias.
|
||||
* Modules accepting boolean status should accept ``yes``, ``no``, ``true``, ``false``, or anything else a user may likely throw at them. The AnsibleModule common code supports this with ``type='bool'``.
|
||||
* Avoid ``action``/``command``, they are imperative and not declarative, there are other ways to express the same thing.
|
||||
|
||||
* Modules must output valid JSON only. The top level return type must be a hash (dictionary) although they can be nested. Lists or simple scalar values are not supported, though they can be trivially contained inside a dictionary.
|
||||
General guidelines & tips
|
||||
=========================
|
||||
|
||||
* In the event of failure, a key of 'failed' should be included, along with a string explanation in 'msg'. Modules that raise tracebacks (stacktraces) are generally considered 'poor' modules, though Ansible can deal with these returns and will automatically convert anything unparseable into a failed result. If you are using the AnsibleModule common Python code, the 'failed' element will be included for you automatically when you call 'fail_json'.
|
||||
* Each module should be self-contained in one file, so it can be be auto-transferred by Ansible.
|
||||
* Always use the ``hacking/test-module`` script when developing modules - it will warn you about common pitfalls.
|
||||
* If you have a local module that returns facts specific to your installations, a good name for this module is ``site_facts``.
|
||||
* Eliminate or minimize dependencies. If your module has dependencies, document them at the top of the module file and raise JSON error messages when dependency import fails.
|
||||
* Don't write to files directly; use a temporary file and then use the ``atomic_move`` function from ``ansible.module_utils.basic`` to move the updated temporary file into place. This prevents data corruption and ensures that the correct context for the file is kept.
|
||||
* Avoid creating caches. Ansible is designed without a central server or authority, so you cannot guarantee it will not run with different permissions, options or locations. If you need a central authority, have it on top of Ansible (for example, using bastion/cm/ci server or tower); do not try to build it into modules.
|
||||
* If you package your module(s) in an RPM, install the modules on the control machine in ``/usr/share/ansible``. Packaging modules in RPMs is optional.
|
||||
|
||||
* Return codes from modules are used if 'failed' is missing, 0=success and non-zero=failure.
|
||||
Python tips
|
||||
===========
|
||||
|
||||
* As results from many hosts will be aggregated at once, modules should return only relevant output. Returning the entire contents of a log file is generally bad form.
|
||||
* When fetching URLs, use ``fetch_url`` or ``open_url`` from ``ansible.module_utils.urls``. Do not use ``urllib2``, which does not natively verify TLS certificates and so is insecure for https.
|
||||
* Include a ``main`` function that wraps the normal execution.
|
||||
* Call your :func:`main` from a conditional so you can import it into unit tests - for example:
|
||||
|
||||
* Modules should have a concise and well defined functionality. Basically, follow the UNIX philosophy of doing one thing well.
|
||||
.. code-block:: python
|
||||
|
||||
* Modules should not require that a user know all the underlying options of an api/tool to be used. For instance, if the legal values for a required module parameter cannot be documented, that's a sign that the module would be rejected.
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
* Modules should typically encompass much of the logic for interacting with a resource. A lightweight wrapper around an API that does not contain much logic would likely cause users to offload too much logic into a playbook, and for this reason the module would be rejected. Instead try creating multiple modules for interacting with smaller individual pieces of the API.
|
||||
.. _shared_code:
|
||||
|
||||
.. _debugging_ansiblemodule_based_modules:
|
||||
Importing and using shared code
|
||||
===============================
|
||||
|
||||
Debugging AnsibleModule-based modules
|
||||
`````````````````````````````````````
|
||||
* Use shared code whenever possible - don't reinvent the wheel. Ansible offers the ``AnsibleModule`` common Python code, plus :ref:`utilities <appendix_module_utilities>` for many common use cases and patterns.
|
||||
* Import ``ansible.module_utils`` code in the same place as you import other libraries.
|
||||
* Do NOT use wildcards (*) for importing other python modules; instead, list the function(s) you are importing (for example, ``from some.other_python_module.basic import otherFunction``).
|
||||
* Import custom packages in ``try``/``except`` and handle them with ``fail_json()`` in ``main()``. For example:
|
||||
|
||||
.. tip::
|
||||
.. code-block:: python
|
||||
|
||||
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.
|
||||
try:
|
||||
import foo
|
||||
HAS_LIB=True
|
||||
except:
|
||||
HAS_LIB=False
|
||||
|
||||
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.
|
||||
Then in main(), just after the argspec, do
|
||||
|
||||
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:
|
||||
.. code-block:: python
|
||||
|
||||
.. code-block:: shell-session
|
||||
if not HAS_LIB:
|
||||
module.fail_json(msg='The foo Python module is required')
|
||||
|
||||
$ 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"
|
||||
}
|
||||
And document the dependency in the ``requirements`` section of your module's :ref:`documentation_block`.
|
||||
|
||||
Setting :envvar:`ANSIBLE_KEEP_REMOTE_FILES` to ``1`` tells Ansible to keep the
|
||||
remote module files instead of deleting them after the module finishes
|
||||
executing. Giving Ansible the ``-vvv`` option makes Ansible more verbose.
|
||||
That way it prints the file name of the temporary module file for you to see.
|
||||
.. _module_failures:
|
||||
|
||||
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:
|
||||
Handling module failures
|
||||
========================
|
||||
|
||||
.. code-block:: shell-session
|
||||
When your module fails, help users understand what went wrong. If you are using the ``AnsibleModule`` common Python code, the ``failed`` element will be included for you automatically when you call ``fail_json``. For polite module failure behavior:
|
||||
|
||||
$ 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
|
||||
* Include a key of ``failed`` along with a string explanation in ``msg``. If you don't do this, Ansible will use standard return codes: 0=success and non-zero=failure.
|
||||
* Don't raise a traceback (stacktrace). Ansible can deal with stacktraces and automatically converts anything unparseable into a failed result, but raising a stacktrace on module failure is not user-friendly.
|
||||
* Do not use ``sys.exit()``. Use ``fail_json()`` from the module object.
|
||||
|
||||
When you look into the debug_dir you'll see a directory structure like this::
|
||||
Handling exceptions (bugs) gracefully
|
||||
=====================================
|
||||
|
||||
├── ansible_module_ping.py
|
||||
├── args
|
||||
└── ansible
|
||||
├── __init__.py
|
||||
└── module_utils
|
||||
├── basic.py
|
||||
└── __init__.py
|
||||
* Validate upfront--fail fast and return useful and clear error messages.
|
||||
* Use defensive programming--use a simple design for your module, handle errors gracefully, and avoid direct stacktraces.
|
||||
* Fail predictably--if we must fail, do it in a way that is the most expected. Either mimic the underlying tool or the general way the system works.
|
||||
* Give out a useful message on what you were doing and add exception messages to that.
|
||||
* Avoid catchall exceptions, they are not very useful unless the underlying API gives very good error messages pertaining the attempted action.
|
||||
|
||||
* :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.
|
||||
.. _module_output:
|
||||
|
||||
* 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 its behaviour. If you want to modify the parameters
|
||||
that are passed to the module, this is the file to do it in.
|
||||
Creating correct and informative module output
|
||||
==============================================
|
||||
|
||||
* The :file:`ansible` directory contains code from
|
||||
:mod:`ansible.module_utils` that is used by the module. Ansible includes
|
||||
files for any :`module:`ansible.module_utils` imports in the module but not
|
||||
any files from any other module. So if your module uses
|
||||
:mod:`ansible.module_utils.url` Ansible will include it for you, but if
|
||||
your module includes :mod:`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.
|
||||
Modules must output valid JSON only. Follow these guidelines for creating correct, useful module output:
|
||||
|
||||
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:
|
||||
* Make your top-level return type a hash (dictionary).
|
||||
* Nest complex return values within the top-level hash.
|
||||
* Incorporate any lists or simple scalar values within the top-level return hash.
|
||||
* Do not send module output to standard error, because the system will merge standard out with standard error and prevent the JSON from parsing.
|
||||
* Capture standard error and return it as a variable in the JSON on standard out. This is how the command module is implemented.
|
||||
* Never do ``print("some status message")`` in a module, because it will not produce valid JSON output.
|
||||
* Always return useful data, even when there is no change.
|
||||
* Be consistent about returns (some modules are too random), unless it is detrimental to the state/action.
|
||||
* Make returns reusable--most of the time you don't want to read it, but you do want to process it and re-purpose it.
|
||||
* Return diff if in diff mode. This is not required for all modules, as it won't make sense for certain ones, but please include it when applicable.
|
||||
* Enable your return values to be serialized as JSON with Python's standard `JSON encoder and decoder <https://docs.python.org/3/library/json.html>`_ library. Basic python types (strings, int, dicts, lists, etc) are serializable.
|
||||
* Do not return an object via exit_json(). Instead, convert the fields you need from the object into the fields of a dictionary and return the dictionary.
|
||||
* Results from many hosts will be aggregated at once, so your module should return only relevant output. Returning the entire contents of a log file is generally bad form.
|
||||
|
||||
.. code-block:: shell-session
|
||||
If a module returns stderr or otherwise fails to produce valid JSON, the actual output will still be shown in Ansible, but the command will not succeed.
|
||||
|
||||
$ python /home/badger/.ansible/tmp/ansible-tmp-1461434734.35-235318071810595/ping execute
|
||||
{"invocation": {"module_args": {"data": "debugging_session"}}, "changed": false, "ping": "debugging_session"}
|
||||
.. _module_conventions:
|
||||
|
||||
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`.
|
||||
Following Ansible conventions
|
||||
=============================
|
||||
|
||||
.. 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 :func:`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.
|
||||
Ansible conventions offer a predictable user interface across all modules, playbooks, and roles. To follow Ansible conventions in your module development:
|
||||
|
||||
|
||||
Module Paths
|
||||
````````````
|
||||
|
||||
If you are having trouble getting your module "found" by ansible, be
|
||||
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::
|
||||
|
||||
ANSIBLE_LIBRARY=~/ansible-modules-core
|
||||
|
||||
And this will make the items in your fork be loaded ahead of what ships with Ansible. Just be sure
|
||||
to make sure you're not reporting bugs on versions from your fork!
|
||||
|
||||
To be safe, if you're working on a variant on something in Ansible's normal distribution, it's not
|
||||
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.
|
||||
|
||||
Common Pitfalls
|
||||
```````````````
|
||||
|
||||
You should never do this in a module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
print("some status message")
|
||||
|
||||
Because the output is supposed to be valid JSON.
|
||||
|
||||
Modules must not output anything on standard error, because the system will merge
|
||||
standard out with standard error and prevent the JSON from parsing. Capturing standard
|
||||
error and returning it as a variable in the JSON on standard out is fine, and is, in fact,
|
||||
how the command module is implemented.
|
||||
|
||||
If a module returns stderr or otherwise fails to produce valid JSON, the actual output
|
||||
will still be shown in Ansible, but the command will not succeed.
|
||||
|
||||
Don't write to files directly; use a temporary file and then use the `atomic_move` function from `ansible.module_utils.basic` to move the updated temporary file into place. This prevents data corruption and ensures that the correct context for the file is kept.
|
||||
|
||||
Avoid creating a module that does the work of other modules; this leads to code duplication and divergence, and makes things less uniform, unpredictable and harder to maintain. Modules should be the building blocks. Instead of creating a module that does the work of other modules, use Plays and Roles instead.
|
||||
|
||||
Avoid creating 'caches'. Ansible is designed without a central server or authority, so you cannot guarantee it will not run with different permissions, options or locations. If you need a central authority, have it on top of Ansible (for example, using bastion/cm/ci server or tower); do not try to build it into modules.
|
||||
|
||||
Always use the hacking/test-module script when developing modules and it will warn you about these kind of things.
|
||||
* Use consistent names across modules (yes, we have many legacy deviations - don't make the problem worse!).
|
||||
* Use consistent parameters (arguments) within your module(s).
|
||||
* Normalize parameters with other modules - if Ansible and the API your module connects to use different names for the same parameter, add aliases to your parameters so the user can choose which names to use in tasks and playbooks.
|
||||
* Return facts from ``*_facts`` modules in the ``ansible_facts`` field of the :ref:`result dictionary<common_return_values>` so other modules can access them.
|
||||
* Implement ``check_mode`` in all ``*_facts`` modules. Playbooks which conditionalize based on fact information will only conditionalize correctly in ``check_mode`` if the facts are returned in ``check_mode``. Usually you can add ``check_mode=True`` when instantiating ``AnsibleModule``.
|
||||
* Use module-specific environment variables. For example, if you use the helpers in ``module_utils.api`` for basic authentication with ``module_utils.urls.fetch_url()`` and you fall back on environment variables for default values, use a module-specific environment variable like :code:`API_<MODULENAME>_USERNAME` to avoid conflict between modules.
|
||||
* Keep module options simple and focused - if you're loading a lot of choices/states on an existing option, consider adding a new, simple option instead.
|
||||
* Keep options small when possible. Passing a large data structure to an option might save us a few tasks, but it adds a complex requirement that we cannot easily validate before passing on to the module.
|
||||
* If you want to pass complex data to an option, write an expert module that allows this, along with several smaller modules that provide a more 'atomic' operation against the underlying APIs and services. Complex operations require complex data. Let the user choose whether to reflect that complexity in tasks and plays or in vars files.
|
||||
* Implement declarative operations (not CRUD) so the user can ignore existing state and focus on final state. For example, use ``started/stopped``, ``present/absent``.
|
||||
* Strive for a consistent final state (aka idempotency). If running your module twice in a row against the same system would result in two different states, see if you can redesign or rewrite to achieve consistent final state. If you can't, document the behavior and the reasons for it.
|
||||
* Provide consistent return values within the standard Ansible return structure, even if NA/None are used for keys normally returned under other options.
|
||||
* Follow additional guidelines that apply to families of modules if applicable. For example, AWS modules should follow `the Amazon guidelines <https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/GUIDELINES.md>`_
|
||||
|
@ -1,168 +1,36 @@
|
||||
.. _developing_modules_checklist:
|
||||
.. _module_contribution:
|
||||
|
||||
===================================
|
||||
Contributing Your Module to Ansible
|
||||
===================================
|
||||
***********************************
|
||||
Contributing your module to Ansible
|
||||
***********************************
|
||||
|
||||
High-quality modules with minimal dependencies
|
||||
can be included in Ansible, but modules (just due to the programming
|
||||
preferences of the developers) will need to be implemented in Python and use
|
||||
the AnsibleModule common code, and should generally use consistent arguments with the rest of
|
||||
the program. Stop by the mailing list to inquire about requirements if you like, and submit
|
||||
a github pull request to the `ansible <https://github.com/ansible/ansible>`_ project.
|
||||
Included modules will ship with ansible, and also have a chance to be promoted to 'core' status, which
|
||||
gives them slightly higher development priority (though they'll work in exactly the same way).
|
||||
If you want to contribute a module to Ansible, you must meet our objective and subjective requirements. Modules accepted into the `main project repo <https://github.com/ansible/ansible>`_ ship with every Ansible installation. However, contributing to the main project isn't the only way to distribute a module - you can embed modules in roles on Galaxy or simply share copies of your module code for :ref:`local use <developing_locally>`.
|
||||
|
||||
.. formerly marked with _module_dev_testing:
|
||||
Contributing to Ansible: objective requirements
|
||||
===============================================
|
||||
|
||||
------------------------------
|
||||
Contributing Modules Checklist
|
||||
------------------------------
|
||||
To contribute a module to Ansible, you must:
|
||||
|
||||
The following checklist items are important guidelines for people who want to contribute to the development of modules to Ansible on GitHub. Please read the guidelines before you submit your PR/proposal.
|
||||
* write your module in either Python or Powershell for Windows
|
||||
* use the ``AnsibleModule`` common code
|
||||
* support Python 2.7 and Python 3.5 - if your module cannot support Python 2.7, explain the required minimum Python version and rationale in the requirements section in ``DOCUMENTATION``
|
||||
* use proper :ref:`Python 3 syntax <developing_python_3>`
|
||||
* follow `PEP 8 <https://www.python.org/dev/peps/pep-0008/>`_ Python style conventions - see :ref:`testing_pep8` for more information
|
||||
* license your module with GPL 3
|
||||
* conform to Ansible's :ref:`formatting and documentation <developing_modules_documenting>` standards
|
||||
* include comprehensive :ref:`tests <developing_testing>` for your module
|
||||
* minimize module dependencies
|
||||
* support :ref:`check_mode <check_mode_dry>` if possible
|
||||
|
||||
* The shebang must always be ``#!/usr/bin/python``. This allows ``ansible_python_interpreter`` to work
|
||||
* Modules must be written to support Python 2.6. If this is not possible, required minimum Python version and rationale should be explained in the requirements section in ``DOCUMENTATION``. In Ansible-2.3 the minimum requirement for modules was Python-2.4.
|
||||
* Modules must be written to use proper Python-3 syntax. At some point in the future we'll come up with rules for running on Python-3 but we're not there yet. See :doc:`developing_python_3` for help on how to do this.
|
||||
* Modules must have a metadata section. For the vast majority of new modules,
|
||||
the metadata should look exactly like this:
|
||||
Please make sure your module meets these requirements before you submit your PR/proposal. If you have questions, reach out via `Ansible's IRC chat channel <http://irc.freenode.net>`_ or the `Ansible development mailing list <https://groups.google.com/group/ansible-devel>`_.
|
||||
|
||||
.. code-block:: python
|
||||
Contributing to Ansible: subjective requirements
|
||||
================================================
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'metadata_version': '1.1'}
|
||||
|
||||
Read the complete :ref:`module metadata specification <ansible_metadata_block>` for more information.
|
||||
|
||||
* Documentation: Make sure it exists
|
||||
* Module documentation should briefly and accurately define what each module and option does, and how it works with others in the underlying system. Documentation should be written for broad audience--readable both by experts and non-experts. This documentation is not meant to teach a total novice, but it also should not be reserved for the Illuminati (hard balance).
|
||||
* Descriptions should always start with a capital letter and end with a full stop. Consistency always helps.
|
||||
* The `required` setting is only required when true, otherwise it is assumed to be false.
|
||||
* If `required` is false/missing, `default` may be specified (assumed 'null' if missing). Ensure that the default parameter in docs matches default parameter in code.
|
||||
* Documenting `default` is not needed for `required: true`.
|
||||
* Remove unnecessary doc like `aliases: []` or `choices: []`.
|
||||
* Do not use Boolean values in a choice list . For example, in the list `choices: ['no', 'verify', 'always]`, 'no' will be interpreted as a Boolean value (you can check basic.py for BOOLEANS_* constants to see the full list of Boolean keywords). If your option actually is a boolean, just use `type=bool`; there is no need to populate 'choices'.
|
||||
* For new modules or options in a module add version_added. The version should match the value of the current development version and is a string (not a float), so be sure to enclose it in quotes.
|
||||
* Verify that arguments in doc and module spec dict are identical.
|
||||
* For password / secret arguments no_log=True should be set.
|
||||
* Requirements should be documented, using the `requirements=[]` field.
|
||||
* Author should be set, with their name and their github id, at the least.
|
||||
* Ensure that you make use of `U()` for URLs, `I()` for option names, `C()` for files and option values, `M()` for module names.
|
||||
* If an optional parameter is sometimes required this need to be reflected in the documentation, e.g. "Required when C(state=present)."
|
||||
* Verify that a GPL 3 License header is included.
|
||||
* Does module use check_mode? Could it be modified to use it? Document it. Documentation is everyone's friend.
|
||||
* Examples--include them whenever possible and make sure they are reproducible.
|
||||
* Document the return structure of the module. Refer to :ref:`common_return_values` and :ref:`module_documenting` for additional information.
|
||||
* Predictable user interface: This is a particularly important section as it is also an area where we need significant improvements.
|
||||
* Name consistency across modules (we've gotten better at this, but we still have many deviations).
|
||||
* Declarative operation (not CRUD)--this makes it easy for a user not to care what the existing state is, just about the final state. ``started/stopped``, ``present/absent``--don't overload options too much. It is preferable to add a new, simple option than to add choices/states that don't fit with existing ones.
|
||||
* Keep options small, having them take large data structures might save us a few tasks, but adds a complex requirement that we cannot easily validate before passing on to the module.
|
||||
* Allow an "expert mode". This may sound like the absolute opposite of the previous one, but it is always best to let expert users deal with complex data. This requires different modules in some cases, so that you end up having one (1) expert module and several 'piecemeal' ones (ec2_vpc_net?). The reason for this is not, as many users express, because it allows a single task and keeps plays small (which just moves the data complexity into vars files, leaving you with a slightly different structure in another YAML file). It does, however, allow for a more 'atomic' operation against the underlying APIs and services.
|
||||
* Informative responses: Please note, that for >= 2.0, it is required that return data to be documented.
|
||||
* Always return useful data, even when there is no change.
|
||||
* Be consistent about returns (some modules are too random), unless it is detrimental to the state/action.
|
||||
* Make returns reusable--most of the time you don't want to read it, but you do want to process it and re-purpose it.
|
||||
* Return diff if in diff mode. This is not required for all modules, as it won't make sense for certain ones, but please attempt to include this when applicable).
|
||||
* Code: This applies to all code in general, but often seems to be missing from modules, so please keep the following in mind as you work.
|
||||
* Validate upfront--fail fast and return useful and clear error messages.
|
||||
* Defensive programming--modules should be designed simply enough that this should be easy. Modules should always handle errors gracefully and avoid direct stacktraces. Ansible deals with this better in 2.0 and returns them in the results.
|
||||
* Fail predictably--if we must fail, do it in a way that is the most expected. Either mimic the underlying tool or the general way the system works.
|
||||
* Modules should not do the job of other modules, that is what roles are for. Less magic is more.
|
||||
* Don't reinvent the wheel. Part of the problem is that code sharing is not that easy nor documented, we also need to expand our base functions to provide common patterns (retry, throttling, etc).
|
||||
* Support check mode. This is not required for all modules, as it won't make sense for certain ones, but please attempt to include this when applicable). For more information, refer to :ref:`check_mode_drift` and :ref:`check_mode_dry`.
|
||||
* Exceptions: The module must handle them. (exceptions are bugs)
|
||||
* Give out useful messages on what you were doing and you can add the exception message to that.
|
||||
* Avoid catchall exceptions, they are not very useful unless the underlying API gives very good error messages pertaining the attempted action.
|
||||
* Module-dependent guidelines: Additional module guidelines may exist for certain families of modules.
|
||||
* Be sure to check out the modules themselves for additional information.
|
||||
* `Amazon <https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/GUIDELINES.md>`_
|
||||
* Modules should make use of the "extends_documentation_fragment" to ensure documentation available. For example, the AWS module should include::
|
||||
|
||||
extends_documentation_fragment:
|
||||
- aws
|
||||
- ec2
|
||||
|
||||
* The module must not use sys.exit() --> use fail_json() from the module object.
|
||||
* Import custom packages in try/except and handled with fail_json() in main() e.g.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
try:
|
||||
import foo
|
||||
HAS_LIB=True
|
||||
except:
|
||||
HAS_LIB=False
|
||||
|
||||
* The return structure should be consistent, even if NA/None are used for keys normally returned under other options.
|
||||
* Are module actions idempotent? If not document in the descriptions or the notes.
|
||||
* Import ``ansible.module_utils`` code in the same place as you import other libraries. In older code, this was done at the bottom of the file but that's no longer needed.
|
||||
* Do not use wildcards for importing other python modules (ex: ``from ansible.module_utils.basic import *``). This used to be required for code imported from ``ansible.module_utils`` but, from Ansible-2.1 onwards, it's just an outdated and bad practice.
|
||||
* The module must have a `main` function that wraps the normal execution.
|
||||
* Call your :func:`main` from a conditional so that it would be possible to
|
||||
import them into unit tests in the future example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
* Try to normalize parameters with other modules, you can have aliases for when user is more familiar with underlying API name for the option
|
||||
* Being `PEP 8 <https://www.python.org/dev/peps/pep-0008/>`_ compliant is a requirement. See :doc:`testing_pep8` for more information.
|
||||
* Avoid '`action`/`command`', they are imperative and not declarative, there are other ways to express the same thing
|
||||
* Do not add `list` or `info` state options to an existing module - create a new `_facts` module.
|
||||
* If you are asking 'how can I have a module execute other modules' ... you want to write a role
|
||||
* Return values must be able to be serialized as json via the python stdlib
|
||||
json library. basic python types (strings, int, dicts, lists, etc) are
|
||||
serializable. A common pitfall is to try returning an object via
|
||||
exit_json(). Instead, convert the fields you need from the object into the
|
||||
fields of a dictionary and return the dictionary.
|
||||
* When fetching URLs, please use either fetch_url or open_url from ansible.module_utils.urls
|
||||
rather than urllib2; urllib2 does not natively verify TLS certificates and so is insecure for https.
|
||||
* facts modules must return facts in the ansible_facts field of the :ref:`result
|
||||
dictionary<common_return_values>`.
|
||||
* modules that are purely about fact gathering need to implement check_mode.
|
||||
they should not cause any changes anyway so it should be as simple as adding
|
||||
check_mode=True when instantiating AnsibleModule. (The reason is that
|
||||
playbooks which conditionalize based on fact information will only
|
||||
conditionalize correctly in check_mode if the facts are returned in
|
||||
check_mode).
|
||||
* Basic auth: module_utils.api has some helpers for doing basic auth with
|
||||
module_utils.urls.fetch_url(). If you use those you may find you also want
|
||||
to fallback on environment variables for default values. If you do that,
|
||||
be sure to use non-generic environment variables (like
|
||||
:code:`API_<MODULENAME>_USERNAME`). Using generic environment variables
|
||||
like :code:`API_USERNAME` would conflict between modules.
|
||||
If your module meets our objective requirements, we'll review your code to see if we think it's clear, concise, secure, and maintainable. We'll consider whether your module provides a good user experience, helpful error messages, reasonable defaults, and more. This process is subjective, and we can't list exact standards for acceptance. For the best chance of getting your module accepted into the Ansible repo, follow our :ref:`tips for module development <developing_modules_best_practices>`.
|
||||
|
||||
Windows modules checklist
|
||||
=========================
|
||||
|
||||
For a checklist and details on how to write Windows modules please see :doc:`developing_modules_general_windows`
|
||||
|
||||
|
||||
Deprecating and making module aliases
|
||||
======================================
|
||||
|
||||
Starting in 1.8, you can deprecate modules by renaming them with a preceding ``_``, i.e. ``old_cloud.py`` to
|
||||
``_old_cloud.py``. This keeps the module available, but hides it from the primary docs and listing.
|
||||
|
||||
When deprecating a module:
|
||||
|
||||
1) Set the ``ANSIBLE_METADATA`` `status` to `deprecated`.
|
||||
2) In the ``DOCUMENTATION`` section, add a `deprecated` field along the lines of::
|
||||
|
||||
deprecated: Deprecated in 2.3. Use M(whatmoduletouseinstead) instead.
|
||||
|
||||
3) Add the deprecation to CHANGELOG.md under the ``###Deprecations:`` section.
|
||||
|
||||
Alias module names
|
||||
------------------
|
||||
|
||||
You can also rename modules and keep an alias to the old name by using a symlink that starts with _.
|
||||
This example allows the stat module to be called with fileinfo, making the following examples equivalent::
|
||||
|
||||
EXAMPLES = '''
|
||||
ln -s stat.py _fileinfo.py
|
||||
ansible -m stat -a "path=/tmp" localhost
|
||||
ansible -m fileinfo -a "path=/tmp" localhost
|
||||
'''
|
||||
For a checklist and details on how to write Windows modules please see :ref:`developing_modules_general_windows`
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,46 @@
|
||||
.. _module_lifecycle:
|
||||
|
||||
**********************************
|
||||
The lifecycle of an Ansible module
|
||||
**********************************
|
||||
|
||||
Modules in the main Ansible repo have a defined life cycle, from first introduction to final removal. The module life cycle is tied to the `Ansible release cycle <release_cycle>` and reflected in the :ref:`ansible_metadata_block`. A module may move through these four states:
|
||||
|
||||
1. When a module is first accepted into Ansible, we consider it in tech preview and mark it ``preview``. Modules in ``preview`` are not stable. You may change the parameters or dependencies, expand or reduce the functionality of ``preview`` modules. Many modules remain ``preview`` for years.
|
||||
|
||||
2. If a module matures, we may mark it ``stableinterface`` and commit to maintaining its parameters, dependencies, and functionality. We support (though we cannot guarantee) backwards compatibility for ``stableinterface`` modules, which means their parameters should be maintained with stable meanings.
|
||||
|
||||
3. If a module's target API changes radically, or if someone creates a better implementation of its functionality, we may mark it ``deprecated``. Modules that are ``deprecated`` are still available but they are reaching the end of their life cycle. We retain deprecated modules for 4 release cycles with deprecation warnings to help users update playbooks and roles that use them.
|
||||
|
||||
4. When a module has been deprecated for four release cycles, we remove the code and mark the stub file ``removed``. Modules that are ``removed`` are no longer shipped with Ansible. The stub file helps users find alternative modules.
|
||||
|
||||
.. _deprecating_modules:
|
||||
|
||||
Deprecating modules
|
||||
===================
|
||||
|
||||
To deprecate a module, you must:
|
||||
|
||||
1. Rename the file so it starts with an ``_``, for example, rename ``old_cloud.py`` to ``_old_cloud.py``. This keeps the module available and marks it as deprecated on the module index pages.
|
||||
2. Mention the deprecation in the relevant ``CHANGELOG``.
|
||||
3. Reference the deprecation in the relevant ``porting_guide_x.y.rst``.
|
||||
4. Update ``ANSIBLE_METADATA`` to contain ``status: ['deprecated']``.
|
||||
5. Add ``deprecated:`` to the documentation with the following sub-values:
|
||||
|
||||
:removed_in: A ``string``, such as ``"2.9"``; the version of Ansible where the module will be replaced with a docs-only module stub. Usually current release +4.
|
||||
:why: Optional string that used to detail why this has been removed.
|
||||
:alternative: Inform users they should do instead, i.e. ``Use M(whatmoduletouseinstead) instead.``.
|
||||
|
||||
* For an example of documenting deprecation, see this `PR that deprecates multiple modules <https://github.com/ansible/ansible/pull/43781/files>`_.
|
||||
|
||||
Changing a module name
|
||||
======================
|
||||
|
||||
You can also rename a module and keep an alias to the old name by using a symlink that starts with _.
|
||||
This example allows the ``stat`` module to be called with ``fileinfo``, making the following examples equivalent::
|
||||
|
||||
EXAMPLES = '''
|
||||
ln -s stat.py _fileinfo.py
|
||||
ansible -m stat -a "path=/tmp" localhost
|
||||
ansible -m fileinfo -a "path=/tmp" localhost
|
||||
'''
|
@ -1,29 +0,0 @@
|
||||
==========
|
||||
Repo Merge
|
||||
==========
|
||||
|
||||
Background
|
||||
----------
|
||||
On Tuesday 6th December 2016, the Ansible Core Team re-merged the module repositories back into `ansible/ansible <https://github.com/ansible/ansible/>`_ in GitHub. The two module repos will be essentially locked, though they will be kept in place for the existing 2.1 and 2.2 dependencies. Once 2.2 moves out of official support (early 2018), these repositories will be fully readonly for all branches. Until then, any issues/PRs opened there will be auto-closed with a note to open it on `ansible/ansible <https://github.com/ansible/ansible/>`_.
|
||||
|
||||
Why Are We Doing This (Again...)?
|
||||
-----------------------------------
|
||||
|
||||
For those who've been using Ansible long enough, you know that originally we started with a single repository. The original intention of the core vs. extras split was that core would be better supported/tested/etc. Extras would have been a bit more of a "wild-west" for modules, to allow new modules to make it into the distribution more quickly. Unfortunately this never really worked out, as well as the following:
|
||||
|
||||
1. Many modules in the core repo were also essentially "grand-fathered" in, despite not having a good set of tests or dedicated maintainers from the community.
|
||||
2. The time in queue for modules to be merged into extras was not really any different from the time to merge modules into core.
|
||||
3. The split introduced a few other problems for contributors such as having to submit multiple related PRs for modules with tests, or for those which rely on action plugins.
|
||||
4. git submodules are notoriously complicated, even for contributors with decent git experience. The constant need to update git submodule pointers for devel and each stable branch can lead to testing surprises and really buys us nothing in terms of flexibility.
|
||||
5. Users can already be confused about where to open issues, especially when the problem appears to be with a module but is actually an action plugin (ie. template) or something more fundamental like includes. Having everything back in one repo makes it easier to link issues, and you're always sure to open a bug report in the right place.
|
||||
|
||||
Metadata - Support/Ownership and Module Status
|
||||
----------------------------------------------------------------------
|
||||
|
||||
As part of this move, we will be introducing module metadata, which will contain a couple of pieces of information regarding modules:
|
||||
|
||||
1. Support Status: This field indicates who supports the module, whether it's the core team, the community, the person who wrote it, or if it is an abandoned module which is not receiving regular updates. The Ansible team has gone through the list of modules and we have marked about 100 of them as "Core Supported", meaning a member of the Ansible core team should be actively fixing bugs on those modules. The vast majority of the rest will be community supported. This is not really a change from the status quo, this just makes it clearer.
|
||||
2. Module Status: This field indicates how well supported that module may be. This generally applies to the maturity of the module's parameters, however, not necessarily its bug status.
|
||||
|
||||
|
||||
The documentation pages for modules will be updated to reflect the above information as well, so that users can evaluate the status of a module before committing to using it in playbooks and roles.
|
@ -0,0 +1,8 @@
|
||||
.. note::
|
||||
**LICENSING REQUIREMENTS** Ansible enforces the following licensing requirements:
|
||||
|
||||
* Utilities (files in ``lib/ansible/module_utils/``) may have one of two licenses:
|
||||
* A ``module_util`` used **only** for a specific vendor's hardware, provider, or service may be licensed under GPLv3+.
|
||||
* All other ``module_utils`` must be licensed under BSD, so GPL-licensed third-party and Galaxy modules can use them.
|
||||
* If there's doubt about the appropriate license for a ``module_util``, the Ansible Core Team will decide during an Ansible Core Community Meeting.
|
||||
* All other files shipped with Ansible, including all modules, must be licensed under the GPL license (GPLv3 or later).
|
@ -0,0 +1,24 @@
|
||||
.. _testing_documentation:
|
||||
|
||||
*********************
|
||||
Testing documentation
|
||||
*********************
|
||||
|
||||
Before you submit a module for inclusion in the main Ansible repo, you must test your documentation for correct HTML rendering and to ensure that the argspec matches the documentation.
|
||||
|
||||
To check the HTML output of your module documentation:
|
||||
|
||||
#. Save your completed module file into the correct directory: ``lib/ansible/modules/$CATEGORY/my_code.py``.
|
||||
#. Move to the docsite directory: ``cd /path/to/ansible/docs/docsite/``.
|
||||
#. Run the command to build the docs for your module: ``MODULES=my_code make webdocs``.
|
||||
#. View the HTML page at ``file:///path/to/ansible/docs/docsite/_build/html/my_code_module.html``.
|
||||
|
||||
To build the HTML documentation for multiple modules, use a comma-separated list of module names: ``MODULES=my_code,my_other_code make webdocs``.
|
||||
|
||||
To ensure that your documentation matches your ``argument_spec``, run the ``validate-modules`` test. Note that this option isn't currently enabled in Shippable due to the time it takes to run.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# If you don't already, ensure you are using your local checkout
|
||||
source hacking/env-setup
|
||||
./test/sanity/validate-modules/validate-modules --arg-spec --warnings lib/ansible/modules/$CATEGORY/my_code.py
|
@ -1,105 +0,0 @@
|
||||
*******************************************
|
||||
Testing using the Legacy Integration system
|
||||
*******************************************
|
||||
|
||||
.. contents:: Topics
|
||||
|
||||
This page details how to run the integration tests that haven't been ported to the new ``ansible-test`` framework.
|
||||
|
||||
The following areas are still tested using the legacy ``make tests`` command:
|
||||
|
||||
* amazon (some)
|
||||
* azure
|
||||
* cloudflare
|
||||
* cloudscale
|
||||
* cloudstack
|
||||
* consul
|
||||
* exoscale
|
||||
* gce
|
||||
* jenkins
|
||||
* rackspace
|
||||
|
||||
Over time the above list will be reduced as tests are ported to the ``ansible-test`` framework.
|
||||
|
||||
|
||||
Running Cloud Tests
|
||||
====================
|
||||
|
||||
Cloud tests exercise capabilities of cloud modules (e.g. ec2_key). These are
|
||||
not 'tests run in the cloud' so much as tests that leverage the cloud modules
|
||||
and are organized by cloud provider.
|
||||
|
||||
Some AWS tests may use environment variables. It is recommended to either unset any AWS environment variables( such as ``AWS_DEFAULT_PROFILE``, ``AWS_SECRET_ACCESS_KEY``, etc) or be sure that the environment variables match the credentials provided in ``credentials.yml`` to ensure the tests run with consistency to their full capability on the expected account. See `AWS CLI docs <https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html>`_ for information on creating a profile.
|
||||
|
||||
Subsets of tests may be run by ``#commenting`` out unnecessary roles in the appropriate playbook, such as ``test/integration/amazon.yml``.
|
||||
|
||||
In order to run cloud tests, you must provide access credentials in a file
|
||||
named ``credentials.yml``. A sample credentials file named
|
||||
``credentials.template`` is available for syntax help.
|
||||
|
||||
Provide cloud credentials::
|
||||
|
||||
cp credentials.template credentials.yml
|
||||
${EDITOR:-vi} credentials.yml
|
||||
|
||||
|
||||
Other configuration
|
||||
===================
|
||||
|
||||
In order to run some tests, you must provide access credentials in a file named
|
||||
``credentials.yml``. A sample credentials file named ``credentials.template`` is available
|
||||
for syntax help.
|
||||
|
||||
IAM policies for AWS
|
||||
====================
|
||||
|
||||
In order to run the tests in an AWS account ansible needs fairly wide ranging powers which
|
||||
can be provided to a dedicated user or temporary credentials using a specific policy
|
||||
configured in the AWS account.
|
||||
|
||||
testing-iam-policy.json.j2
|
||||
--------------------------
|
||||
|
||||
The testing-iam-policy.json.j2 file contains a policy which can be given to the user
|
||||
running the tests to give close to minimum rights required to run the tests. Please note
|
||||
that this does not fully restrict the user; The user has wide privileges for viewing
|
||||
account definitions and is also able to manage some resources that are not related to
|
||||
testing (e.g. AWS lambdas with different names) primarily due to the limitations of the
|
||||
Amazon ARN notation. At the very least the policy limits the user to one region, however
|
||||
tests should not be run in a primary production account in any case.
|
||||
|
||||
Other Definitions required
|
||||
--------------------------
|
||||
|
||||
Apart from installing the policy and giving it to the user identity running
|
||||
the tests, a lambda role `ansible_integration_tests` has to be created which
|
||||
has lambda basic execution privileges.
|
||||
|
||||
|
||||
Running Tests
|
||||
=============
|
||||
|
||||
The tests are invoked via a ``Makefile``.
|
||||
|
||||
If you haven't already got Ansible available use the local checkout by doing::
|
||||
|
||||
source hacking/env-setup
|
||||
|
||||
Run the tests by doing::
|
||||
|
||||
cd test/integration/
|
||||
# TARGET is the name of the test from the list at the top of this page
|
||||
#make TARGET
|
||||
# e.g.
|
||||
make amazon
|
||||
# To run all cloud tests you can do:
|
||||
make cloud
|
||||
|
||||
.. warning:: Possible cost of running cloud tests
|
||||
|
||||
Running cloud integration tests will create and destroy cloud
|
||||
resources. Running these tests may result in additional fees associated with
|
||||
your cloud account. Care is taken to ensure that created resources are
|
||||
removed. However, it is advisable to inspect your AWS console to ensure no
|
||||
unexpected resources are running.
|
||||
|
@ -0,0 +1,69 @@
|
||||
.. _ansible.module_utils:
|
||||
.. _module_utils:
|
||||
|
||||
***************************************************************
|
||||
Ansible Reference: Module Utilities
|
||||
***************************************************************
|
||||
|
||||
This page documents the available utilities called with ``ansible.module_utils.util_name``.
|
||||
|
||||
Generic
|
||||
--------
|
||||
.. glossary::
|
||||
|
||||
.. _ansible.module_utils.debug
|
||||
debug
|
||||
.. _ansible.module_utils.url
|
||||
url
|
||||
.. _ansible.module_utils.log
|
||||
log
|
||||
.. _ansible.module_utils.no_log
|
||||
no_log
|
||||
.. _Ansible.get_bin_path
|
||||
Ansible.get_bin_path
|
||||
|
||||
|
||||
.. _AnsibleModule:
|
||||
.. _ansible.module_utils.basic.AnsibleModule:
|
||||
|
||||
AnsibleModule
|
||||
--------------
|
||||
.. glossary::
|
||||
|
||||
ansible.module_utils.basic.AnsibleModule
|
||||
Utilities in module_utils.basic.AnsibleModule apply to all module types
|
||||
|
||||
.. _AnsibleModule.debug:
|
||||
AnsibleModule.debug
|
||||
.. _AnsibleModule._debug:
|
||||
AnsibleModule._debug
|
||||
.. _AnsibleModule._diff:
|
||||
AnsibleModule._diff
|
||||
.. _AnsibleModule.log:
|
||||
AnsibleModule.log
|
||||
.. _AnsibleModule.no_log:
|
||||
AnsibleModule.no_log
|
||||
.. _AnsibleModule.params:
|
||||
AnsibleModule.params
|
||||
.. _AnsibleModule.run_command:
|
||||
AnsibleModule.run_command
|
||||
.. _ansible.module_utils.basic.AnsibleModule._selinux_special_fs:
|
||||
ansible.module_utils.basic.AnsibleModule._selinux_special_fs
|
||||
(formerly ansible.module_utils.basic.SELINUX_SPECIAL_FS)
|
||||
|
||||
|
||||
.. _ansible.module_utils.basic:
|
||||
|
||||
Basic
|
||||
------
|
||||
.. glossary::
|
||||
|
||||
ansible.module_utils.basic
|
||||
Utilities in module_utils.basic apply to all module types.
|
||||
|
||||
.. _ansible.module_utils.basic.SELINUX_SPECIAL_FS:
|
||||
ansible.module_utils.basic.SELINUX_SPECIAL_FS
|
||||
*deprecated* replaced by :term:`ansible.module_utils.basic.AnsibleModule._selinux_special_fs`
|
||||
|
||||
.. _ansible.module_utils.basic._load_params:
|
||||
load_params
|
Loading…
Reference in New Issue