mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-26 20:31:27 -07:00
Fix all RST errors for docs/docsite/rst (#20005)
* developing_modules.rst is now in dev_guide, sync changes and delete the old version * Cleaner RST & formatted code * Tidyup roadmaps * Link to repomerge * Pull in abadger's fixes From https://github.com/ansible/ansible/compare/docs-code-block-fixes?expand=1 * Clean docsite/rst (apart from ROADMAP
This commit is contained in:
parent
cba66dfedc
commit
7df31aaca1
38 changed files with 464 additions and 1096 deletions
|
@ -9,7 +9,7 @@ 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 existing modules.
|
||||
|
||||
Modules can be written in any language and are found in the path specified
|
||||
by :envvar:`ANSIBLE_LIBRARY` or the ``--module-path`` command line option.
|
||||
|
@ -51,7 +51,9 @@ modules. Keep in mind, though, that some modules in Ansible's source tree are
|
|||
so look at :ref:`service` or :ref:`yum`, and don't stare too close into things like ``async_wrapper`` or
|
||||
you'll turn to stone. Nobody ever executes ``async_wrapper`` directly.
|
||||
|
||||
Ok, let's get going with an example. We'll use Python. For starters, save this as a file named :file:`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`
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/python
|
||||
|
||||
|
@ -68,21 +70,27 @@ Ok, let's get going with an example. We'll use Python. For starters, save this
|
|||
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:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
git clone git://github.com/ansible/ansible.git --recursive
|
||||
source ansible/hacking/env-setup
|
||||
|
||||
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:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
ansible/hacking/test-module -m ./timetest.py
|
||||
|
||||
You should see output that looks something like this::
|
||||
You should see output that looks something like this:
|
||||
|
||||
{'time': '2012-03-14 22:13:48.539183'}
|
||||
.. code-block:: json
|
||||
|
||||
{"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.
|
||||
|
||||
|
@ -111,7 +119,9 @@ If no time parameter is set, we'll just leave the time as is and return the curr
|
|||
|
||||
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
|
||||
a lot shorter than this::
|
||||
a lot shorter than this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/python
|
||||
|
||||
|
@ -211,7 +221,9 @@ Binary Modules Input
|
|||
Support for binary modules was added in Ansible 2.2. When Ansible detects a binary module, it will proceed to
|
||||
supply the argument input as a file on ``argv[1]`` that is formatted as JSON. The JSON contents of that file
|
||||
would resemble something similar to the following payload for a module accepting the same arguments as the
|
||||
``ping`` module::
|
||||
``ping`` module:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"data": "pong",
|
||||
|
@ -229,10 +241,12 @@ Module Provided 'Facts'
|
|||
|
||||
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
|
||||
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:
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"changed" : True,
|
||||
"changed" : true,
|
||||
"rc" : 5,
|
||||
"ansible_facts" : {
|
||||
"leptons" : 5000,
|
||||
|
@ -265,7 +279,9 @@ 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.
|
||||
|
||||
Key parts include always importing the boilerplate code from
|
||||
:mod:`ansible.module_utils.basic` like this::
|
||||
:mod:`ansible.module_utils.basic` like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
if __name__ == '__main__':
|
||||
|
@ -274,11 +290,15 @@ Key parts include always importing the boilerplate code from
|
|||
.. 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::
|
||||
a wildcard import like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
And instantiating the module class like::
|
||||
And instantiating the module class like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
|
@ -293,11 +313,15 @@ And instantiating the module class like::
|
|||
The :class:`AnsibleModule` provides lots of common code for handling returns, parses your arguments
|
||||
for you, and allows you to check inputs.
|
||||
|
||||
Successful returns are made like this::
|
||||
Successful returns are made like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
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):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
module.fail_json(msg="Something fatal happened")
|
||||
|
||||
|
@ -322,7 +346,9 @@ mode, the module should try to predict whether changes will occur.
|
|||
|
||||
For your module to support check mode, you must pass ``supports_check_mode=True``
|
||||
when instantiating the AnsibleModule object. The AnsibleModule.check_mode attribute
|
||||
will evaluate to True when check mode is enabled. For example::
|
||||
will evaluate to True when check mode is enabled. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(...),
|
||||
|
@ -344,7 +370,9 @@ mode, your module will simply be skipped.
|
|||
Common Pitfalls
|
||||
```````````````
|
||||
|
||||
You should also never do this in a module::
|
||||
You should also NEVER do this in a module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
print "some status message"
|
||||
|
||||
|
@ -358,6 +386,12 @@ 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 `ansibile.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.
|
||||
|
||||
|
@ -407,7 +441,9 @@ Example
|
|||
|
||||
See an example documentation string in the checkout under `examples/DOCUMENTATION.yml <https://github.com/ansible/ansible/blob/devel/examples/DOCUMENTATION.yml>`_.
|
||||
|
||||
Include it in your module file like this::
|
||||
Include it in your module file like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
#!/usr/bin/python
|
||||
# Copyright header....
|
||||
|
@ -419,8 +455,6 @@ Include it in your module file like this::
|
|||
# ... snip ...
|
||||
'''
|
||||
|
||||
If an argument takes both C(True)/C(False) and C(Yes)/C(No), the documentation should use C(True) and C(False).
|
||||
|
||||
The ``description``, and ``notes`` fields
|
||||
support formatting with some special macros.
|
||||
|
||||
|
@ -451,17 +485,17 @@ the ``copy`` module::
|
|||
description: destination file/path
|
||||
returned: success
|
||||
type: string
|
||||
sample: "/path/to/file.txt"
|
||||
sample: /path/to/file.txt
|
||||
src:
|
||||
description: source file used for the copy on the target machine
|
||||
returned: changed
|
||||
type: string
|
||||
sample: "/home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source"
|
||||
sample: /home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source
|
||||
md5sum:
|
||||
description: md5 checksum of the file after running copy
|
||||
returned: when supported
|
||||
type: string
|
||||
sample: "2a5aeecc61dc98c4d780b14b330e3282"
|
||||
sample: 2a5aeecc61dc98c4d780b14b330e3282
|
||||
...
|
||||
'''
|
||||
|
||||
|
@ -507,7 +541,9 @@ 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::
|
||||
your debugging session will start:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
$ ANSIBLE_KEEP_REMOTE_FILES=1 ansible localhost -m ping -a 'data=debugging_session' -vvv
|
||||
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: badger
|
||||
|
@ -515,13 +551,13 @@ your debugging session will start::
|
|||
<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,
|
||||
"changed": false,
|
||||
"invocation": {
|
||||
"module_args": {
|
||||
"data": "debugging_session"
|
||||
},
|
||||
},
|
||||
"module_name": "ping"
|
||||
},
|
||||
},
|
||||
"ping": "debugging_session"
|
||||
}
|
||||
|
||||
|
@ -533,7 +569,9 @@ 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::
|
||||
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:
|
||||
|
@ -571,7 +609,9 @@ When you look into the debug_dir you'll see a directory structure like this::
|
|||
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::
|
||||
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"}
|
||||
|
@ -639,16 +679,18 @@ The following checklist items are important guidelines for people who want to c
|
|||
* Modules must be written to support Python 2.4. If this is not possible, required minimum python version and rationale should be explained in the requirements section in ``DOCUMENTATION``. This minimum requirement will be advanced to Python-2.6 in Ansible-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_modules_python3` 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::
|
||||
the metadata should look exactly like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['preview'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
The complete module metadata specification is here: https://github.com/ansible/proposals/issues/30
|
||||
The complete module metadata specification is here: https://github.com/ansible/proposals/issues/30
|
||||
|
||||
* 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).
|
||||
* If an argument takes both C(True)/C(False) and C(Yes)/C(No), the documentation should use C(True) and C(False).
|
||||
* 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.
|
||||
|
@ -682,7 +724,7 @@ The following checklist items are important guidelines for people who want to c
|
|||
* 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`.
|
||||
* 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.
|
||||
|
@ -696,7 +738,9 @@ The following checklist items are important guidelines for people who want to c
|
|||
- 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.::
|
||||
* Import custom packages in try/except and handled with fail_json() in main() e.g.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
try:
|
||||
import foo
|
||||
|
@ -710,7 +754,9 @@ The following checklist items are important guidelines for people who want to c
|
|||
* 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 unittests in the future example::
|
||||
import them into unittests in the future example
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -727,53 +773,80 @@ The following checklist items are important guidelines for people who want to c
|
|||
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 result
|
||||
dictionary. :ref:`module_provided_facts`
|
||||
* 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
|
||||
:envvar:`API_<MODULENAME>_USERNAME`). Using generic environment variables
|
||||
like :envvar:`API_USERNAME` would conflict between modules.
|
||||
|
||||
Windows modules checklist
|
||||
`````````````````````````
|
||||
* Favour native powershell and .net ways of doing things over calls to COM libraries or calls to native executables which may or may not be present in all versions of windows
|
||||
* Favour native powershell and .net ways of doing things over calls to COM libraries or calls to native executables which may or may not be present in all versions of Windows
|
||||
* modules are in powershell (.ps1 files) but the docs reside in same name python file (.py)
|
||||
* look at ansible/lib/ansible/module_utils/powershell.ps1 for common code, avoid duplication
|
||||
* Ansible uses strictmode version 2.0 so be sure to test with that enabled
|
||||
* start with::
|
||||
|
||||
All powershell modules must start:
|
||||
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
#!powershell
|
||||
|
||||
then::
|
||||
|
||||
<GPL header>
|
||||
|
||||
then::
|
||||
|
||||
# WANT_JSON
|
||||
# POWERSHELL_COMMON
|
||||
|
||||
then, to parse all arguments into a variable modules generally use::
|
||||
|
||||
To parse all arguments into a variable modules generally use:
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
$params = Parse-Args $args
|
||||
|
||||
* Arguments:
|
||||
* Try and use state present and state absent like other modules
|
||||
* You need to check that all your mandatory args are present. You can do this using the builtin Get-AnsibleParam function.
|
||||
* Required arguments::
|
||||
Arguments
|
||||
+++++++++
|
||||
|
||||
* Try and use state present and state absent like other modules
|
||||
* You need to check that all your mandatory args are present. You can do this using the builtin Get-AnsibleParam function.
|
||||
* Required arguments:
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
$package = Get-AnsibleParam -obj $params -name name -failifempty $true
|
||||
|
||||
* Required arguments with name validation::
|
||||
Required arguments with name validation:
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
$state = Get-AnsibleParam -obj $params -name "State" -ValidateSet "Present","Absent" -resultobj $resultobj -failifempty $true
|
||||
|
||||
* Optional arguments with name validation::
|
||||
Optional arguments with name validation
|
||||
+++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
$state = Get-AnsibleParam -obj $params -name "State" -default "Present" -ValidateSet "Present","Absent"
|
||||
|
||||
* the If "FailIfEmpty" is true, the resultobj parameter is used to specify the object returned to fail-json. You can also override the default message
|
||||
using $emptyattributefailmessage (for missing required attributes) and $ValidateSetErrorMessage (for attribute validation errors)
|
||||
* Look at existing modules for more examples of argument checking.
|
||||
* If the "FailIfEmpty" is true, the resultobj parameter is used to specify the object returned to fail-json. You can also override the default message
|
||||
using $emptyattributefailmessage (for missing required attributes) and $ValidateSetErrorMessage (for attribute validation errors)
|
||||
* Look at existing modules for more examples of argument checking.
|
||||
|
||||
* Results
|
||||
* The result object should always contain an attribute called changed set to either $true or $false
|
||||
* Create your result object like this::
|
||||
Results
|
||||
+++++++
|
||||
* The result object should always contain an attribute called changed set to either $true or $false
|
||||
* Create your result object like this
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
$result = New-Object psobject @{
|
||||
changed = $false
|
||||
|
@ -783,10 +856,10 @@ Windows modules checklist
|
|||
If all is well, exit with a
|
||||
Exit-Json $result
|
||||
|
||||
* Ensure anything you return, including errors can be converted to json.
|
||||
* Be aware that because exception messages could contain almost anything.
|
||||
* ConvertTo-Json will fail if it encounters a trailing \ in a string.
|
||||
* If all is not well use Fail-Json to exit.
|
||||
* Ensure anything you return, including errors can be converted to json.
|
||||
* Be aware that because exception messages could contain almost anything.
|
||||
* ConvertTo-Json will fail if it encounters a trailing \ in a string.
|
||||
* If all is not well use Fail-Json to exit.
|
||||
|
||||
* Have you tested for powershell 3.0 and 4.0 compliance?
|
||||
|
||||
|
@ -808,7 +881,7 @@ This example allows the stat module to be called with fileinfo, making the follo
|
|||
|
||||
.. seealso::
|
||||
|
||||
:doc:`modules`
|
||||
:doc:`../modules`
|
||||
Learn about available modules
|
||||
:doc:`developing_plugins`
|
||||
Learn about developing plugins
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue