inventory plugin docs (#42022)

* inventory plugin docs
* added set options
* minor wording and formatting fixes
* changed headers to std as per #35520, also added to main readme
* unified inventory plugin devel, referenced from generic plugin dev
* fixed typos and update as per feedback
pull/42394/head
Brian Coca 6 years ago committed by GitHub
parent 133b70ff45
commit 475abc0af7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,3 +19,33 @@ To install sphinx and the required theme, install pip and then "pip install sphi
[file issues]: https://github.com/ansible/ansible/issues
[module-docs]: https://docs.ansible.com/developing_modules.html#documenting-your-module
HEADERS
=======
RST allows for arbitrary hierchy for the headers, it will 'learn on the fly' but we want a standard so all our documents can follow:
```
##########################
# with overline, for parts
##########################
*****************************
* with overline, for chapters
*****************************
=, for sections
===============
-, for subsections
------------------
^, for sub-subsections
^^^^^^^^^^^^^^^^^^^^^
", for paragraphs
"""""""""""""""""
```
We do have pages littered with ```````` headers, but those should be removed for one of the above.

@ -1,21 +1,236 @@
.. _developing_inventory:
Developing Dynamic Inventory Sources
====================================
Developing Dynamic Inventory
============================
.. contents:: Topics
:local:
As described in :ref:`dynamic_inventory`, Ansible can pull inventory information from dynamic sources, including cloud sources. You can also create a new dynamic inventory provider by creating a script or program that can output JSON in the correct format when invoked with the proper arguments. There is no restriction on the language used for creating a dynamic inventory provider.
As described in :ref:`dynamic_inventory`, Ansible can pull inventory information from dynamic sources,
including cloud sources, using the supplied :ref:`inventory plugins <inventory_plugins>`.
If the source you want is not currently covered by existing plugins, you can create your own as with any other plugin type.
In previous versions you had to create a script or program that can output JSON in the correct format when invoked with the proper arguments.
You can still use and write inventory scripts, as we ensured backwards compatiblity via the :ref:`script inventory plugin <script_inventory>`
and there is no restriction on the programming language used.
If you choose to write a script, however, you will need to implement some features youself.
i.e caching, configuration management, dynamic variable and group composition, etc.
While with :ref:`inventory plugins <inventory_plugins>` you can leverage the Ansible codebase to add these common features.
.. _inventory_sources:
Inventory sources
-----------------
Inventory sources are strings (i.e what you pass to ``-i`` in the command line),
they can represent a path to a file/script or just be the raw data for the plugin to use.
Here are some plugins and the type of source they use:
+--------------------------------------------+--------------------------------------+
| Plugin | Source |
+--------------------------------------------+--------------------------------------+
| :ref:`host list <host_list_inventory>` | A comma separated list of hosts |
+--------------------------------------------+--------------------------------------+
| :ref:`yaml <yaml_inventory>` | Path to a YAML format data file |
+--------------------------------------------+--------------------------------------+
| :ref:`constructed <constructed_inventory>` | Path to a YAML configuration file |
+--------------------------------------------+--------------------------------------+
| :ref:`ini <ini_inventory>` | Path to An ini formated data file |
+--------------------------------------------+--------------------------------------+
| :ref:`virtualbox <virtualbox_inventory>` | Path to a YAML configuration file |
+--------------------------------------------+--------------------------------------+
| :ref:`script plugin <script_inventory>` | Path to an executable outputing JSON |
+--------------------------------------------+--------------------------------------+
.. _developing_inventory_inventory_plugins:
Inventory Plugins
-----------------
Like most plugin types (except modules) they must be developed in Python, since they execute on the controller they should match the same requirements :ref:`control_machine_requirements`.
Most of the documentation in :ref:`developing_plugins` also applies here, so as to not repeat ourselves, you should read that document first and we'll include inventory plugin specifics next.
Inventory plugins normally only execute at the start of a run, before playbooks/plays and roles are loaded,
but they can be 're-executed' via the ``meta: refresh_inventory`` task, which will clear out the existing inventory and rebuild it.
When using the 'persistent' cache, inventory plugins can also use the configured cache plugin to store and retrieve data to avoid costly external calls.
.. _developing_an_inventory_plugin:
Developing an Inventory Plugin
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The first thing you want to do is use the base class:
.. code-block:: python
from ansible.plugins.inventory import BaseInventoryPlugin
class InventoryModule(BaseInventoryPlugin):
NAME = 'myplugin' # used internally by Ansible, it should match the file name but not required
This class has a couple of methods each plugin should implement and a few helpers for parsing the inventory source and updating the inventory.
After you have the basic plugin working you might want to to incorporate other features by adding more base classes:
.. code-block:: python
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
NAME = 'myplugin'
For the bulk of the work in the plugin, We mostly want to deal with 2 methods ``verify_file`` and ``parse``.
.. _inventory_plugin_verify_file:
verify_file
"""""""""""
This method is used by Ansible to make a quick determination if the inventory source is usable by the plugin. It does not need to be 100% accurate as there might be overlap in what plugins can handle and Ansible will try the enabled plugins (in order) by default.
.. code-block:: python
def verify_file(self, path):
''' return true/false if this is possibly a valid file for this plugin to consume '''
valid = False
if super(InventoryModule, self).verify_file(path):
# base class verifies that file exists and is readable by current user
if path.endswith(('.vbox.yaml', '.vbox.yml')):
valid = True
return valid
In this case, from the :ref:`virtualbox inventory plugin <virtualbox_inventory>`, we screen for specific file name patterns to avoid attempting to consume any valid yaml file. You can add any type of condition here, but the most common one is 'extension matching'
Another example that actually does not use a 'file' but the inventory source string itself,
from the :ref:`host list <host_list_inventory>` plugin:
.. code-block:: python
def verify_file(self, path):
''' don't call base class as we don't expect a path, but a host list '''
host_list = path
valid = False
b_path = to_bytes(host_list, errors='surrogate_or_strict')
if not os.path.exists(b_path) and ',' in host_list:
# the path does NOT exist and there is a comma to indicate this is a 'host list'
valid = True
return valid
This method is just to expedite the inventory process and avoid uneccessary parsing of sources that are easy to filter out before causing a parse error.
.. _inventory_plugin_parse:
parse
"""""
This method does the bulk of the work in the plugin.
It takes the following paramters:
* inventory: inventory object with existing data and the methods to add hosts/groups/variables to inventory
* loader: Ansible's DataLoader. The DataLoader can read files, auto load JSON/YAML and decrypt vaulted data, and cache read files.
* path: string with inventory source (this is usually a path, but is not required)
* cache: indicates whether the plugin should use or avoid caches (cache plugin and/or loader)
The base class does some minimal assignment for reuse in other methods.
.. code-block:: python
def parse(self, inventory, loader, path, cache=True):
self.loader = loader
self.inventory = inventory
self.templar = Templar(loader=loader)
It is up to the plugin now to deal with the inventory source provided and translate that into the Ansible inventory.
To facilitate this there are a few of helper functions used in the example below:
.. code-block:: python
NAME = 'myplugin'
def parse(self, inventory, loader, path, cache=True):
# call base method to ensure properties are available for use with other helper methods
super(InventoryModule, self).parse(inventory, loader, path, cache)
# this method will parse 'common format' inventory sources and
# update any options declared in DOCUMENTATION as needed
config = self._read_config_data(self, path)
# if NOT using _read_config_data you should call set_options directly,
# to process any defined configuration for this plugin,
# if you dont define any options you can skip
#self.set_options()
# example consuming options from inventory source
mysession = apilib.session(user=self.get_option('api_user'),
password=self.get_option('api_pass'),
server=self.get_option('api_server')
)
# make requests to get data to feed into inventorya
mydata = myselss.getitall()
#parse data and create inventory objects:
for colo in mydata:
for server in mydata[colo]['servers']:
self.inventory.add_host(server['name'])
self.inventory.set_varaible('ansible_host', server['external_ip'])
The specifics will vary depending on API and structure returned. But one thing to keep in mind, if the inventory source or any other issue crops up you should ``raise AnsibleParserError`` to let Ansible know that the source was invalid or the process failed.
For examples on how to implement an inventory plug in, see the source code here:
`lib/ansible/plugins/inventory <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/inventory>`_.
.. _inventory_source_common_format:
inventory source common format
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To simplify development, most plugins use a mostly standard configuration file as the inventory source, YAML based and with just one required field ``plugin`` which should contain the name of the plugin that is expected to consume the file.
Depending on other common features used, other fields might be needed, but each plugin can also add it's own custom options as needed.
For example, if you use the integrated caching, ``cache_plugin``, ``cache_timeout`` and other cache related fields could be present.
.. _inventory_development_auto:
The 'auto' plugin
^^^^^^^^^^^^^^^^^
Since Ansible 2.5, we include the :ref:`auto inventory plugin <auto_inventory>` enabled by default, which itself just loads other plugins if they use the common YAML configuration format that specifies a ``plugin`` field that matches an inventory plugin name, this makes it easier to use your plugin w/o having to update configurations.
.. _inventory_scripts:
.. _developing_inventory_scripts:
Inventory Scripts
-----------------
Even though we now have inventory plugins, we still support inventory scripts, not only for backwards compatibility but also to allow users to leverage other programming languages.
.. _inventory_script_conventions:
Script Conventions
``````````````````
Inventory script conventions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Inventory scripts must accept the ``--list`` and ``--host <hostname>`` arguments, other arguments are allowed but Ansible will not use them.
They might still be useful for when executing the scripts directly.
Dynamic inventory providers must accept the ``--list`` and ``--host <hostname>`` arguments.
When the script is called with the single argument ``--list``, the script must output to stdout a JSON-encoded hash or
dictionary containing all of the groups to be managed.
Each group's value should be either a hash or dictionary containing a list of each host, any child groups,
and potential group variables, or simply a list of hosts::
When the dynamic inventory provider is called with the single argument ``--list``, the script must output to stdout a JSON-encoded hash or dictionary containing all of the groups to be managed. Each group's value should be either a hash or dictionary containing a list of each host, any child groups, and potential group variables, or simply a list of hosts::
{
"group001": {
@ -45,24 +260,24 @@ When called with the argument ``--host <hostname>`` (where <hostname> is a host
"VAR002": "VALUE",
}
Printing variables is optional. If the inventory provider does not do this, it should print an empty hash or dictionary.
Printing variables is optional. If the script does not do this, it should print an empty hash or dictionary.
.. _inventory_script_tuning:
Tuning the External Inventory Script
````````````````````````````````````
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 1.3
The stock inventory script system detailed above works for all versions of
Ansible, but calling ``--host`` for every host can be rather inefficient,
especially if it involves API calls to a remote subsystem. In Ansible 1.3 or
later, if the inventory script returns a top level element called "_meta", it
is possible to return all of the host variables in one inventory provider call.
When this meta element contains a value for "hostvars", the inventory script
will not be invoked with ``--host`` for each host. This results in a
significant performance increase for large numbers of hosts, and also makes
client-side caching easier to implement for the inventory provider.
The stock inventory script system detailed above works for all versions of Ansible,
but calling ``--host`` for every host can be rather inefficient,
especially if it involves API calls to a remote subsystem.
To avoid this inefficiency, if the inventory script returns a top level element called "_meta",
it is possible to return all of the host variables in one script execution.
When this meta element contains a value for "hostvars",
the inventory script will not be invoked with ``--host`` for each host.
This results in a significant performance increase for large numbers of hosts.
The data to be added to the top level JSON dictionary looks like this::
@ -83,7 +298,8 @@ The data to be added to the top level JSON dictionary looks like this::
}
}
To satisfy the requirements of using ``_meta``, to prevent ansible from calling your inventory with ``--host`` you must at least populate ``_meta`` with an empty ``hostvars`` dictionary. For example::
To satisfy the requirements of using ``_meta``, to prevent ansible from calling your inventory with ``--host`` you must at least populate ``_meta`` with an empty ``hostvars`` dictionary.
For example::
{
@ -98,24 +314,26 @@ To satisfy the requirements of using ``_meta``, to prevent ansible from calling
.. _replacing_inventory_ini_with_dynamic_provider:
If you intend to replace an existing inventory ini file with a dynamic provider,
If you intend to replace an existing static inventory file with an inventory script,
it must return a JSON object which contains an 'all' group that includes every
host in the inventory as a member and every group in the inventory as a child.
It should also include an 'ungrouped' group which contains all hosts which are not members of
any other group. A skeleton example of this JSON object is::
It should also include an 'ungrouped' group which contains all hosts which are not members of any other group.
A skeleton example of this JSON object is::
{
"_meta": {
"hostvars": {}
},
},
"all": {
"children": [
"ungrouped"
]
},
},
"ungrouped": {}
}
An easy way to see how this should look is using :ref:`ansible-inventory`, which also supports ``--list`` and ``--host`` parameters like an inventory script would.
.. seealso::
:doc:`developing_api`

@ -15,7 +15,7 @@ General Guidelines
This section lists some things that should apply to any type of plugin you develop.
Raising Errors
``````````````
^^^^^^^^^^^^^^
In general, errors encountered during execution should be returned by raising AnsibleError() or similar class with a message describing the error. When wrapping other exceptions into error messages, you should always use the ``to_text`` Ansible function to ensure proper string compatibility across Python versions:
@ -31,7 +31,7 @@ In general, errors encountered during execution should be returned by raising An
Check the different AnsibleError objects and see which one applies the best to your situation.
String Encoding
```````````````
^^^^^^^^^^^^^^^
Any strings returned by your plugin that could ever contain non-ASCII characters must be converted into Python's unicode type because the strings will be run through jinja2. To do this, you can use:
.. code-block:: python
@ -40,16 +40,20 @@ Any strings returned by your plugin that could ever contain non-ASCII characters
result_string = to_text(result_string)
Plugin Configuration
````````````````````
^^^^^^^^^^^^^^^^^^^^
Starting with Ansible version 2.4, we are unifying how each plugin type is configured and how they get those settings. Plugins will be able to declare their requirements and have Ansible provide them with a resolved'configuration. Starting with Ansible 2.4 both callback and connection type plugins can use this system.
Most plugins will be able to use ``self._options[<optionname>]`` to access the settings, except callbacks that use ``self._plugin_options[<optionname>]``.
Most plugins will be able to use ``self.get_option(<optionname>)`` to access the settings.
These are pre-populated by a ``self.set_options()`` call, for most plugin types this is done by the controller,
but for some types you might need to do this explicitly.
Of course, if you don't have any configurable options, you can ignore this.
Plugins that support embedded documentation (see `ansible-doc` for the list) are now required to provide well-formed doc strings to be considered for merge into the Ansible repo.
Plugins that support embedded documentation (see :ref:`ansible-doc` for the list) must now include well-formed doc strings to be considered for merge into the Ansible repo. This documentation also doubles as 'configuration definition' so they will never be out of sync.
If you inherit from a plugin, you must document the options it takes, either via a documentation fragment or as a copy.
.. _developing_callbacks:
Callback Plugins
@ -167,32 +171,7 @@ Inventory Plugins
Inventory plugins were added in Ansible version 2.4. Inventory plugins parse inventory sources and form an in memory representation of the inventory.
Inventory plugins are invoked via the InventoryManager and are given access to any existing inventory data. They are given an 'inventory source' as supplied to Ansible (via config/options/defaults/etc), which they can either ignore
by returning false from the ``verify_file`` method, or attempting to parse (with the ``parse`` method) and return an ``AnsibleParserError`` on failure.
.. code-block:: python
def parse(self, inventory, loader, path, cache=True):
pass # your code goes here
Inventory plugins take the following parameters:
* inventory: inventory object with existing data and the methods to add hosts/groups/variables to inventory
* loader: Ansible's DataLoader. The DataLoader can read files, auto load JSON/YAML and decrypt vaulted data, and cache read files.
* path: string with inventory source (this is usually a path, but is not required)
* cache: indicates whether the plugin should use or avoid caches (cache plugin and/or loader)
Inventory sources are strings. They usually correspond to a file path, but they can also be a comma separated list,
a URI, or anything your plugin can use as input.
The 'inventory source' provided can be either a string (``host_list`` plugin), a data file (like consumed by the ``yaml`` and ``ini`` plugins), a configuration file (see ``virtualbox`` and ``constructed``) or even a script or executable (the ``script`` uses those).
When using the 'persistent' cache, inventory plugins can also use the configured cache plugin to store and retrieve data to avoid costly external calls.
Inventory plugins normally only execute at the start of a run, before playbooks/plays and roles are found,
but they can be 're-executed' via the ``meta: refresh_inventory`` task, which will clear out the existing inventory and rebuild it.
For examples on how to implement an inventory plug in, see the source code here:
`lib/ansible/plugins/inventory <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/inventory>`_.
You can see the details for inventory plugins in the :ref:`developing_inventory` page.
.. _developing_lookup_plugins:

@ -1,5 +1,6 @@
.. contents:: Topics
.. _inventory_plugins:
Inventory Plugins
-----------------

@ -146,14 +146,30 @@ class BaseInventoryPlugin(AnsiblePlugin):
self.cache = None
def parse(self, inventory, loader, path, cache=True):
''' Populates self.groups from the given data. Raises an error on any parse failure. '''
''' Populates inventory from the given data. Raises an error on any parse failure
:arg inventory: a copy of the previously accumulated inventory data,
to be updated with any new data this plugin provides.
The inventory can be empty if no other source/plugin ran successfully.
:arg loader: a reference to the DataLoader, which can read in YAML and JSON files,
it also has Vault support to automatically decrypt files.
:arg path: the string that represents the 'inventory source',
normally a path to a configuration file for this inventory,
but it can also be a raw string for this plugin to consume
:arg cache: a boolean that indicates if the plugin should use the cache or not
you can ignore if this plugin does not implement caching.
'''
self.loader = loader
self.inventory = inventory
self.templar = Templar(loader=loader)
def verify_file(self, path):
''' Verify if file is usable by this plugin, base does minimal accessability check '''
''' Verify if file is usable by this plugin, base does minimal accessability check
:arg path: a string that was passed as an inventory source,
it normally is a path to a config file, but this is not a requirement,
it can also be parsed itself as the inventory data to process.
So only call this base class if you expect it to be a file.
'''
b_path = to_bytes(path, errors='surrogate_or_strict')
return (os.path.exists(b_path) and os.access(b_path, os.R_OK))
@ -168,7 +184,9 @@ class BaseInventoryPlugin(AnsiblePlugin):
self.inventory.set_variable(host, k, variables[k])
def _read_config_data(self, path):
''' validate config and set options as appropriate '''
''' validate config and set options as appropriate
:arg path: path to common yaml format config file for this plugin
'''
config = {}
try:
@ -200,7 +218,10 @@ class BaseInventoryPlugin(AnsiblePlugin):
cache_dir=options.get('cache_connection'))
def _consume_options(self, data):
''' update existing options from file data'''
''' update existing options from alternate configuration sources not normally used by Ansible.
Many API libraries already have existing configuration sources, this allows plugin author to leverage them.
:arg data: key/value pairs that correspond to configuration options for this plugin
'''
for k in self._options:
if k in data:

Loading…
Cancel
Save