1.. _developing_plugins:
2.. _plugin_guidelines:
3
4******************
5Developing plugins
6******************
7
8.. contents::
9   :local:
10
11Plugins augment Ansible's core functionality with logic and features that are accessible to all modules. Ansible collections include a number of handy plugins, and you can easily write your own. All plugins must:
12
13* be written in Python
14* raise errors
15* return strings in unicode
16* conform to Ansible's configuration and documentation standards
17
18Once you've reviewed these general guidelines, you can skip to the particular type of plugin you want to develop.
19
20Writing plugins in Python
21=========================
22
23You must write your plugin in Python so it can be loaded by the ``PluginLoader`` and returned as a Python object that any module can use. Since your plugin will execute on the controller, you must write it in a :ref:`compatible version of Python <control_node_requirements>`.
24
25Raising errors
26==============
27
28You should return errors encountered during plugin execution by raising ``AnsibleError()`` or a similar class with a message describing the error. When wrapping other exceptions into error messages, you should always use the ``to_native`` Ansible function to ensure proper string compatibility across Python versions:
29
30.. code-block:: python
31
32    from ansible.module_utils._text import to_native
33
34    try:
35        cause_an_exception()
36    except Exception as e:
37        raise AnsibleError('Something happened, this was original exception: %s' % to_native(e))
38
39Check the different `AnsibleError objects <https://github.com/ansible/ansible/blob/devel/lib/ansible/errors/__init__.py>`_ and see which one applies best to your situation.
40
41String encoding
42===============
43
44You must convert any strings returned by your plugin into Python's unicode type. Converting to unicode ensures that these strings can run through Jinja2. To convert strings:
45
46.. code-block:: python
47
48    from ansible.module_utils._text import to_text
49    result_string = to_text(result_string)
50
51Plugin configuration & documentation standards
52==============================================
53
54To define configurable options for your plugin, describe them in the ``DOCUMENTATION`` section of the python file. Callback and connection plugins have declared configuration requirements this way since Ansible version 2.4; most plugin types now do the same. This approach ensures that the documentation of your plugin's options will always be correct and up-to-date. To add a configurable option to your plugin, define it in this format:
55
56.. code-block:: yaml
57
58    options:
59      option_name:
60        description: describe this config option
61        default: default value for this config option
62        env:
63          - name: NAME_OF_ENV_VAR
64        ini:
65          - section: section_of_ansible.cfg_where_this_config_option_is_defined
66            key: key_used_in_ansible.cfg
67        required: True/False
68        type: boolean/float/integer/list/none/path/pathlist/pathspec/string/tmppath
69        version_added: X.x
70
71To access the configuration settings in your plugin, use ``self.get_option(<option_name>)``. For most plugin types, the controller pre-populates the settings. If you need to populate settings explicitly, use a ``self.set_options()`` call.
72
73Plugins that support embedded documentation (see :ref:`ansible-doc` for the list) should include well-formed doc strings. If you inherit from a plugin, you must document the options it takes, either via a documentation fragment or as a copy. See :ref:`module_documenting` for more information on correct documentation. Thorough documentation is a good idea even if you're developing a plugin for local use.
74
75Developing particular plugin types
76==================================
77
78.. _developing_actions:
79
80Action plugins
81--------------
82
83Action plugins let you integrate local processing and local data with module functionality.
84
85To create an action plugin, create a new class with the Base(ActionBase) class as the parent:
86
87.. code-block:: python
88
89    from ansible.plugins.action import ActionBase
90
91    class ActionModule(ActionBase):
92        pass
93
94From there, execute the module using the ``_execute_module`` method to call the original module.
95After successful execution of the module, you can modify the module return data.
96
97.. code-block:: python
98
99    module_return = self._execute_module(module_name='<NAME_OF_MODULE>',
100                                         module_args=module_args,
101                                         task_vars=task_vars, tmp=tmp)
102
103
104For example, if you wanted to check the time difference between your Ansible controller and your target machine(s), you could write an action plugin to check the local time and compare it to the return data from Ansible's ``setup`` module:
105
106.. code-block:: python
107
108    #!/usr/bin/python
109    # Make coding more python3-ish, this is required for contributions to Ansible
110    from __future__ import (absolute_import, division, print_function)
111    __metaclass__ = type
112
113    from ansible.plugins.action import ActionBase
114    from datetime import datetime
115
116
117    class ActionModule(ActionBase):
118        def run(self, tmp=None, task_vars=None):
119            super(ActionModule, self).run(tmp, task_vars)
120            module_args = self._task.args.copy()
121            module_return = self._execute_module(module_name='setup',
122                                                 module_args=module_args,
123                                                 task_vars=task_vars, tmp=tmp)
124            ret = dict()
125            remote_date = None
126            if not module_return.get('failed'):
127                for key, value in module_return['ansible_facts'].items():
128                    if key == 'ansible_date_time':
129                        remote_date = value['iso8601']
130
131            if remote_date:
132                remote_date_obj = datetime.strptime(remote_date, '%Y-%m-%dT%H:%M:%SZ')
133                time_delta = datetime.now() - remote_date_obj
134                ret['delta_seconds'] = time_delta.seconds
135                ret['delta_days'] = time_delta.days
136                ret['delta_microseconds'] = time_delta.microseconds
137
138            return dict(ansible_facts=dict(ret))
139
140
141This code checks the time on the controller, captures the date and time for the remote machine using the ``setup`` module, and calculates the difference between the captured time and
142the local time, returning the time delta in days, seconds and microseconds.
143
144For practical examples of action plugins,
145see the source code for the `action plugins included with Ansible Core <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/action>`_
146
147.. _developing_cache_plugins:
148
149Cache plugins
150-------------
151
152Cache plugins store gathered facts and data retrieved by inventory plugins.
153
154Import cache plugins using the cache_loader so you can use ``self.set_options()`` and ``self.get_option(<option_name>)``. If you import a cache plugin directly in the code base, you can only access options via ``ansible.constants``, and you break the cache plugin's ability to be used by an inventory plugin.
155
156.. code-block:: python
157
158    from ansible.plugins.loader import cache_loader
159    [...]
160    plugin = cache_loader.get('custom_cache', **cache_kwargs)
161
162There are two base classes for cache plugins, ``BaseCacheModule`` for database-backed caches, and ``BaseCacheFileModule`` for file-backed caches.
163
164To create a cache plugin, start by creating a new ``CacheModule`` class with the appropriate base class. If you're creating a plugin using an ``__init__`` method you should initialize the base class with any provided args and kwargs to be compatible with inventory plugin cache options. The base class calls ``self.set_options(direct=kwargs)``. After the base class ``__init__`` method is called ``self.get_option(<option_name>)`` should be used to access cache options.
165
166New cache plugins should take the options ``_uri``, ``_prefix``, and ``_timeout`` to be consistent with existing cache plugins.
167
168.. code-block:: python
169
170    from ansible.plugins.cache import BaseCacheModule
171
172    class CacheModule(BaseCacheModule):
173        def __init__(self, *args, **kwargs):
174            super(CacheModule, self).__init__(*args, **kwargs)
175            self._connection = self.get_option('_uri')
176            self._prefix = self.get_option('_prefix')
177            self._timeout = self.get_option('_timeout')
178
179If you use the ``BaseCacheModule``, you must implement the methods ``get``, ``contains``, ``keys``, ``set``, ``delete``, ``flush``, and ``copy``. The ``contains`` method should return a boolean that indicates if the key exists and has not expired. Unlike file-based caches, the ``get`` method does not raise a KeyError if the cache has expired.
180
181If you use the ``BaseFileCacheModule``, you must implement ``_load`` and ``_dump`` methods that will be called from the base class methods ``get`` and ``set``.
182
183If your cache plugin stores JSON, use ``AnsibleJSONEncoder`` in the ``_dump`` or ``set`` method  and ``AnsibleJSONDecoder`` in the ``_load`` or ``get`` method.
184
185For example cache plugins, see the source code for the `cache plugins included with Ansible Core <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/cache>`_.
186
187.. _developing_callbacks:
188
189Callback plugins
190----------------
191
192Callback plugins add new behaviors to Ansible when responding to events. By default, callback plugins control most of the output you see when running the command line programs.
193
194To create a callback plugin, create a new class with the Base(Callbacks) class as the parent:
195
196.. code-block:: python
197
198  from ansible.plugins.callback import CallbackBase
199
200  class CallbackModule(CallbackBase):
201      pass
202
203From there, override the specific methods from the CallbackBase that you want to provide a callback for.
204For plugins intended for use with Ansible version 2.0 and later, you should only override methods that start with ``v2``.
205For a complete list of methods that you can override, please see ``__init__.py`` in the
206`lib/ansible/plugins/callback <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/callback>`_ directory.
207
208The following is a modified example of how Ansible's timer plugin is implemented,
209but with an extra option so you can see how configuration works in Ansible version 2.4 and later:
210
211.. code-block:: python
212
213  # Make coding more python3-ish, this is required for contributions to Ansible
214  from __future__ import (absolute_import, division, print_function)
215  __metaclass__ = type
216
217  # not only visible to ansible-doc, it also 'declares' the options the plugin requires and how to configure them.
218  DOCUMENTATION = '''
219    callback: timer
220    callback_type: aggregate
221    requirements:
222      - whitelist in configuration
223    short_description: Adds time to play stats
224    version_added: "2.0"  # for collections, use the collection version, not the Ansible version
225    description:
226        - This callback just adds total play duration to the play stats.
227    options:
228      format_string:
229        description: format of the string shown to user at play end
230        ini:
231          - section: callback_timer
232            key: format_string
233        env:
234          - name: ANSIBLE_CALLBACK_TIMER_FORMAT
235        default: "Playbook run took %s days, %s hours, %s minutes, %s seconds"
236  '''
237  from datetime import datetime
238
239  from ansible.plugins.callback import CallbackBase
240
241
242  class CallbackModule(CallbackBase):
243      """
244      This callback module tells you how long your plays ran for.
245      """
246      CALLBACK_VERSION = 2.0
247      CALLBACK_TYPE = 'aggregate'
248      CALLBACK_NAME = 'namespace.collection_name.timer'
249
250      # only needed if you ship it and don't want to enable by default
251      CALLBACK_NEEDS_WHITELIST = True
252
253      def __init__(self):
254
255          # make sure the expected objects are present, calling the base's __init__
256          super(CallbackModule, self).__init__()
257
258          # start the timer when the plugin is loaded, the first play should start a few milliseconds after.
259          self.start_time = datetime.now()
260
261      def _days_hours_minutes_seconds(self, runtime):
262          ''' internal helper method for this callback '''
263          minutes = (runtime.seconds // 60) % 60
264          r_seconds = runtime.seconds - (minutes * 60)
265          return runtime.days, runtime.seconds // 3600, minutes, r_seconds
266
267      # this is only event we care about for display, when the play shows its summary stats; the rest are ignored by the base class
268      def v2_playbook_on_stats(self, stats):
269          end_time = datetime.now()
270          runtime = end_time - self.start_time
271
272          # Shows the usage of a config option declared in the DOCUMENTATION variable. Ansible will have set it when it loads the plugin.
273          # Also note the use of the display object to print to screen. This is available to all callbacks, and you should use this over printing yourself
274          self._display.display(self._plugin_options['format_string'] % (self._days_hours_minutes_seconds(runtime)))
275
276Note that the ``CALLBACK_VERSION`` and ``CALLBACK_NAME`` definitions are required for properly functioning plugins for Ansible version 2.0 and later. ``CALLBACK_TYPE`` is mostly needed to distinguish 'stdout' plugins from the rest, since you can only load one plugin that writes to stdout.
277
278For example callback plugins, see the source code for the `callback plugins included with Ansible Core <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/callback>`_
279
280.. _developing_connection_plugins:
281
282Connection plugins
283------------------
284
285Connection plugins allow Ansible to connect to the target hosts so it can execute tasks on them. Ansible ships with many connection plugins, but only one can be used per host at a time. The most commonly used connection plugins are the ``paramiko`` SSH, native ssh (just called ``ssh``), and ``local`` connection types.  All of these can be used in playbooks and with ``/usr/bin/ansible`` to connect to remote machines.
286
287Ansible version 2.1 introduced the ``smart`` connection plugin. The ``smart`` connection type allows Ansible to automatically select either the ``paramiko`` or ``openssh`` connection plugin based on system capabilities, or the ``ssh`` connection plugin if OpenSSH supports ControlPersist.
288
289To create a new connection plugin (for example, to support SNMP, Message bus, or other transports), copy the format of one of the existing connection plugins and drop it into ``connection`` directory on your :ref:`local plugin path <local_plugins>`.
290
291Connection plugins can support common options (such as the ``--timeout`` flag) by defining an entry in the documentation for the attribute name (in this case ``timeout``). If the common option has a non-null default, the plugin should define the same default since a different default would be ignored.
292
293For example connection plugins, see the source code for the `connection plugins included with Ansible Core <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/connection>`_.
294
295.. _developing_filter_plugins:
296
297Filter plugins
298--------------
299
300Filter plugins manipulate data. They are a feature of Jinja2 and are also available in Jinja2 templates used by the ``template`` module. As with all plugins, they can be easily extended, but instead of having a file for each one you can have several per file. Most of the filter plugins shipped with Ansible reside in a ``core.py``.
301
302Filter plugins do not use the standard configuration and documentation system described above.
303
304For example filter plugins, see the source code for the `filter plugins included with Ansible Core <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/filter>`_.
305
306.. _developing_inventory_plugins:
307
308Inventory plugins
309-----------------
310
311Inventory plugins parse inventory sources and form an in-memory representation of the inventory. Inventory plugins were added in Ansible version 2.4.
312
313You can see the details for inventory plugins in the :ref:`developing_inventory` page.
314
315.. _developing_lookup_plugins:
316
317Lookup plugins
318--------------
319
320Lookup plugins pull in data from external data stores. Lookup plugins can be used within playbooks both for looping --- playbook language constructs like ``with_fileglob`` and ``with_items`` are implemented via lookup plugins --- and to return values into a variable or parameter.
321
322Lookup plugins are very flexible, allowing you to retrieve and return any type of data. When writing lookup plugins, always return data of a consistent type that can be easily consumed in a playbook. Avoid parameters that change the returned data type. If there is a need to return a single value sometimes and a complex dictionary other times, write two different lookup plugins.
323
324Ansible includes many :ref:`filters <playbooks_filters>` which can be used to manipulate the data returned by a lookup plugin. Sometimes it makes sense to do the filtering inside the lookup plugin, other times it is better to return results that can be filtered in the playbook. Keep in mind how the data will be referenced when determining the appropriate level of filtering to be done inside the lookup plugin.
325
326Here's a simple lookup plugin implementation --- this lookup returns the contents of a text file as a variable:
327
328.. code-block:: python
329
330  # python 3 headers, required if submitting to Ansible
331  from __future__ import (absolute_import, division, print_function)
332  __metaclass__ = type
333
334  DOCUMENTATION = """
335          lookup: file
336          author: Daniel Hokka Zakrisson <daniel@hozac.com>
337          version_added: "0.9"  # for collections, use the collection version, not the Ansible version
338          short_description: read file contents
339          description:
340              - This lookup returns the contents from a file on the Ansible controller's file system.
341          options:
342            _terms:
343              description: path(s) of files to read
344              required: True
345          notes:
346            - if read in variable context, the file can be interpreted as YAML if the content is valid to the parser.
347            - this lookup does not understand globing --- use the fileglob lookup instead.
348  """
349  from ansible.errors import AnsibleError, AnsibleParserError
350  from ansible.plugins.lookup import LookupBase
351  from ansible.utils.display import Display
352
353  display = Display()
354
355
356  class LookupModule(LookupBase):
357
358      def run(self, terms, variables=None, **kwargs):
359
360
361          # lookups in general are expected to both take a list as input and output a list
362          # this is done so they work with the looping construct 'with_'.
363          ret = []
364          for term in terms:
365              display.debug("File lookup term: %s" % term)
366
367              # Find the file in the expected search path, using a class method
368              # that implements the 'expected' search path for Ansible plugins.
369              lookupfile = self.find_file_in_search_path(variables, 'files', term)
370
371              # Don't use print or your own logging, the display class
372              # takes care of it in a unified way.
373              display.vvvv(u"File lookup using %s as file" % lookupfile)
374              try:
375                  if lookupfile:
376                      contents, show_data = self._loader._get_file_contents(lookupfile)
377                      ret.append(contents.rstrip())
378                  else:
379                      # Always use ansible error classes to throw 'final' exceptions,
380                      # so the Ansible engine will know how to deal with them.
381                      # The Parser error indicates invalid options passed
382                      raise AnsibleParserError()
383              except AnsibleParserError:
384                  raise AnsibleError("could not locate file in lookup: %s" % term)
385
386          return ret
387
388The following is an example of how this lookup is called::
389
390  ---
391  - hosts: all
392    vars:
393       contents: "{{ lookup('namespace.collection_name.file', '/etc/foo.txt') }}"
394
395    tasks:
396
397       - debug:
398           msg: the value of foo.txt is {{ contents }} as seen today {{ lookup('pipe', 'date +"%Y-%m-%d"') }}
399
400For example lookup plugins, see the source code for the `lookup plugins included with Ansible Core <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/lookup>`_.
401
402For more usage examples of lookup plugins, see :ref:`Using Lookups<playbooks_lookups>`.
403
404.. _developing_test_plugins:
405
406Test plugins
407------------
408
409Test plugins verify data. They are a feature of Jinja2 and are also available in Jinja2 templates used by the ``template`` module. As with all plugins, they can be easily extended, but instead of having a file for each one you can have several per file. Most of the test plugins shipped with Ansible reside in a ``core.py``. These are specially useful in conjunction with some filter plugins like ``map`` and ``select``; they are also available for conditional directives like ``when:``.
410
411Test plugins do not use the standard configuration and documentation system described above.
412
413For example test plugins, see the source code for the `test plugins included with Ansible Core <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/test>`_.
414
415.. _developing_vars_plugins:
416
417Vars plugins
418------------
419
420Vars plugins inject additional variable data into Ansible runs that did not come from an inventory source, playbook, or command line. Playbook constructs like 'host_vars' and 'group_vars' work using vars plugins.
421
422Vars plugins were partially implemented in Ansible 2.0 and rewritten to be fully implemented starting with Ansible 2.4. Vars plugins are unsupported by collections.
423
424Older plugins used a ``run`` method as their main body/work:
425
426.. code-block:: python
427
428    def run(self, name, vault_password=None):
429        pass # your code goes here
430
431
432Ansible 2.0 did not pass passwords to older plugins, so vaults were unavailable.
433Most of the work now  happens in the ``get_vars`` method which is called from the VariableManager when needed.
434
435.. code-block:: python
436
437    def get_vars(self, loader, path, entities):
438        pass # your code goes here
439
440The parameters are:
441
442 * loader: Ansible's DataLoader. The DataLoader can read files, auto-load JSON/YAML and decrypt vaulted data, and cache read files.
443 * path: this is 'directory data' for every inventory source and the current play's playbook directory, so they can search for data in reference to them. ``get_vars`` will be called at least once per available path.
444 * entities: these are host or group names that are pertinent to the variables needed. The plugin will get called once for hosts and again for groups.
445
446This ``get_vars`` method just needs to return a dictionary structure with the variables.
447
448Since Ansible version 2.4, vars plugins only execute as needed when preparing to execute a task. This avoids the costly 'always execute' behavior that occurred during inventory construction in older versions of Ansible. Since Ansible version 2.10, vars plugin execution can be toggled by the user to run when preparing to execute a task or after importing an inventory source.
449
450Since Ansible 2.10, vars plugins can require whitelisting. Vars plugins that don't require whitelisting will run by default. To require whitelisting for your plugin set the class variable ``REQUIRES_WHITELIST``:
451
452.. code-block:: python
453
454    class VarsModule(BaseVarsPlugin):
455        REQUIRES_WHITELIST = True
456
457Include the ``vars_plugin_staging`` documentation fragment to allow users to determine when vars plugins run.
458
459.. code-block:: python
460
461    DOCUMENTATION = '''
462        vars: custom_hostvars
463        version_added: "2.10"  # for collections, use the collection version, not the Ansible version
464        short_description: Load custom host vars
465        description: Load custom host vars
466        options:
467          stage:
468            ini:
469              - key: stage
470                section: vars_custom_hostvars
471            env:
472              - name: ANSIBLE_VARS_PLUGIN_STAGE
473        extends_documentation_fragment:
474          - vars_plugin_staging
475    '''
476
477Also since Ansible 2.10, vars plugins can reside in collections. Vars plugins in collections must require whitelisting to be functional.
478
479For example vars plugins, see the source code for the `vars plugins included with Ansible Core
480<https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/vars>`_.
481
482.. seealso::
483
484   :ref:`list_of_collections`
485       Browse existing collections, modules, and plugins
486   :ref:`developing_api`
487       Learn about the Python API for task execution
488   :ref:`developing_inventory`
489       Learn about how to develop dynamic inventory sources
490   :ref:`developing_modules_general`
491       Learn about how to write Ansible modules
492   `Mailing List <https://groups.google.com/group/ansible-devel>`_
493       The development mailing list
494   `irc.libera.chat <https://libera.chat/>`_
495       #ansible IRC chat channel
496