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