docs/docsite/rst/dev_guide/developing_modules_best_practices.rst
.. _developing_modules_best_practices: .. _module_dev_conventions:
Conventions, tips, and pitfalls
.. contents:: Topics :local:
As you design and develop modules, follow these basic conventions and tips for clean, usable code:
Especially if you want to contribute your module(s) to an existing Ansible Collection, make sure each module includes enough logic and functionality, but not too much. If these guidelines seem confusing, consider :ref:whether you really need to write a module <module_dev_should_you> at all.
get, list or info state options to an existing module - create a new _info or _facts module.create multiple modules <developing_modules_in_groups> that interact with smaller individual pieces of the API.name whenever possible, or accept name as an alias.yes, no, true, false, or anything else a user may likely throw at them. The AnsibleModule common code supports this with type='bool'.action/command, they are imperative and not declarative, there are other ways to express the same thing.ansible-core.ansible-core from importing your module.hacking/test-module.py script when developing modules - it will warn you about common pitfalls.site_info.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./usr/share/ansible. Packaging modules in RPMs is optional.my_function_name.main function that wraps the normal execution.main function from a conditional so you can import it into unit tests - for example:.. code-block:: python
if __name__ == '__main__':
main()
.. _shared_code:
AnsibleModule common Python code, plus :ref:utilities <developing_module_utilities> for many common use cases and patterns. You can also create documentation fragments for docs that apply to multiple modules.ansible.module_utils code in the same place as you import other libraries.from some.other_python_module.basic import otherFunction).try/except, capture any import errors, and handle them with fail_json() in main(). For example:.. code-block:: python
import traceback
from ansible.module_utils.basic import missing_required_lib
LIB_IMP_ERR = None
try:
import foo
HAS_LIB = True
except:
HAS_LIB = False
LIB_IMP_ERR = traceback.format_exc()
Then in main(), just after the argspec, do
.. code-block:: python
if not HAS_LIB:
module.fail_json(msg=missing_required_lib("foo"),
exception=LIB_IMP_ERR)
And document the dependency in the requirements section of your module's :ref:documentation_block.
.. _module_failures:
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:
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.sys.exit(). Use fail_json() from the module object... _module_output:
Modules must output valid JSON only. Follow these guidelines for creating correct, useful module output:
errors='replace' to replace non UTF-8 characters making the return values lossy.print("some status message") in a module, because it will not produce valid JSON output.JSON encoder and decoder <https://docs.python.org/3/library/json.html>_ library. Basic python types (strings, int, dicts, lists, and so on) are serializable.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.
.. _module_conventions:
Ansible conventions offer a predictable user interface across all modules, playbooks, and roles. To follow Ansible conventions in your module development:
*_facts modules in the ansible_facts field of the :ref:result dictionary<common_return_values> so other modules can access them.check_mode in all *_info and *_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 supports_check_mode=True when instantiating AnsibleModule.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 conflicts between modules.started/stopped, present/absent.module.run_command, not subprocess or Popen or os.system.use_unsafe_shell=True to module.run_command.use_unsafe_shell=True, you must wrap them with pipes.quote(x).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.no_log=True will automatically have that value stripped from module return values. If your module could return these sensitive values as part of a dictionary key name, you should call the ansible.module_utils.basic.sanitize_keys() function to strip the values from the keys. See the uri module for an example.