2018-09-07 15:57:36 +02:00
.. _developing_modules_general:
2017-01-27 23:03:26 +01:00
.. _module_dev_tutorial_sample:
2018-09-07 15:57:36 +02:00
***** ***** ***** ***** ***** ***** ***** ***** ***
Ansible module development: getting started
***** ***** ***** ***** ***** ***** ***** ***** ***
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
A module is a reusable, standalone script that Ansible runs on your behalf, either locally or remotely. Modules interact with your local machine, an API, or a remote system to perform specific tasks like changing a database password or spinning up a cloud instance. Each module can be used by the Ansible API, or by the :command: `ansible` or :command: `ansible-playbook` programs. A module provides a defined interface, accepting arguments and returning information to Ansible by printing a JSON string to stdout before exiting. Ansible ships with thousands of modules, and you can easily write your own. If you're writing a module for local use, you can choose any programming language and follow your own rules. This tutorial illustrates how to get started developing an Ansible module in Python.
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
.. contents :: Topics
:local:
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
.. _environment_setup:
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
Environment setup
=================
2018-09-07 15:57:36 +02:00
Prerequisites via apt (Ubuntu)
------------------------------
2017-11-27 20:58:13 +01:00
Due to dependencies (for example ansible -> paramiko -> pynacl -> libffi):
2017-01-27 23:03:26 +01:00
2017-11-27 20:58:13 +01:00
.. code :: bash
sudo apt update
sudo apt install build-essential libssl-dev libffi-dev python-dev
2018-09-07 15:57:36 +02:00
Common environment setup
------------------------------
2017-07-14 04:06:01 +02:00
1. Clone the Ansible repository:
`` $ git clone https://github.com/ansible/ansible.git ``
2. Change directory into the repository root dir: `` $ cd ansible ``
3. Create a virtual environment: `` $ python3 -m venv venv `` (or for
Python 2 `` $ virtualenv venv `` . Note, this requires you to install
the virtualenv package: `` $ pip install virtualenv `` )
4. Activate the virtual environment: `` $ . venv/bin/activate ``
5. Install development requirements:
`` $ pip install -r requirements.txt ``
6. Run the environment setup script for each new dev shell process:
`` $ . hacking/env-setup ``
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
.. note :: After the initial setup above, every time you are ready to start
developing Ansible you should be able to just run the following from the
root of the Ansible repo:
`` $ . venv/bin/activate && . hacking/env-setup ``
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
Starting a new module
=====================
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
To create a new module:
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
1. Navigate to the correct directory for your new module: `` $ cd lib/ansible/modules/cloud/azure/ ``
2019-07-22 21:58:13 +02:00
2. Create your new module file: `` $ touch my_test.py ``
2018-09-07 15:57:36 +02:00
3. Paste the content below into your new module file. It includes the :ref: `required Ansible format and documentation <developing_modules_documenting>` and some example code.
4. Modify and extend the code to do what you want your new module to do. See the :ref: `programming tips <developing_modules_best_practices>` and :ref: `Python 3 compatibility <developing_python_3>` pages for pointers on writing clean, concise module code.
2017-08-17 02:56:53 +02:00
2019-01-24 23:09:41 +01:00
.. code-block :: python
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
#!/usr/bin/python
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
# Copyright: (c) 2018, Terry Jones <terry.jones@example.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
2017-07-14 04:06:01 +02:00
ANSIBLE_METADATA = {
2017-08-05 20:28:21 +02:00
'metadata_version': '1.1',
2017-07-14 04:06:01 +02:00
'status': ['preview'],
2017-08-05 20:28:21 +02:00
'supported_by': 'community'
2017-07-14 04:06:01 +02:00
}
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
DOCUMENTATION = '''
---
2019-07-22 21:58:13 +02:00
module: my_test
2017-07-14 04:06:01 +02:00
2019-07-22 21:58:13 +02:00
short_description: This is my test module
2017-07-14 04:06:01 +02:00
version_added: "2.4"
description:
2019-07-22 21:58:13 +02:00
- "This is my longer description explaining my test module"
2017-07-14 04:06:01 +02:00
options:
name:
description:
2019-07-22 21:58:13 +02:00
- This is the message to send to the test module
2017-07-14 04:06:01 +02:00
required: true
new:
description:
- Control to demo if the result of this module is changed or not
required: false
2017-08-11 23:35:14 +02:00
extends_documentation_fragment:
2017-07-14 04:06:01 +02:00
- azure
author:
- Your Name (@yourhandle)
'''
EXAMPLES = '''
# Pass in a message
- name: Test with a message
2019-07-22 21:58:13 +02:00
my_test:
2017-07-14 04:06:01 +02:00
name: hello world
# pass in a message and have changed true
- name: Test with a message and changed output
2019-07-22 21:58:13 +02:00
my_test:
2017-07-14 04:06:01 +02:00
name: hello world
new: true
# fail the module
- name: Test failure of the module
2019-07-22 21:58:13 +02:00
my_test:
2017-07-14 04:06:01 +02:00
name: fail me
'''
RETURN = '''
original_message:
description: The original name param that was passed in
type: str
2019-03-20 17:22:49 +01:00
returned: always
2017-07-14 04:06:01 +02:00
message:
2019-07-22 21:58:13 +02:00
description: The output message that the test module generates
2019-03-20 17:22:49 +01:00
type: str
returned: always
2017-07-14 04:06:01 +02:00
'''
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
from ansible.module_utils.basic import AnsibleModule
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
def run_module():
2018-09-07 15:57:36 +02:00
# define available arguments/parameters a user can pass to the module
2017-07-14 04:06:01 +02:00
module_args = dict(
name=dict(type='str', required=True),
new=dict(type='bool', required=False, default=False)
)
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
# seed the result dict in the object
# we primarily care about changed and state
# change is if this module effectively modified the target
# state will include any data that you want your module to pass back
# for consumption, for example, in a subsequent task
result = dict(
changed=False,
original_message='',
message=''
)
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
# the AnsibleModule object will be our abstraction working with Ansible
# this includes instantiation, a couple of common attr would be the
# args/params passed to the execution, as well as if the module
# supports check mode
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
# if the user is working with this module in only check mode we do not
# want to make any changes to the environment, just return the current
# state with no modifications
if module.check_mode:
2019-01-18 18:03:59 +01:00
module.exit_json(**result)
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
# manipulate or modify the state as needed (this is going to be the
# part where your module will do what it needs to do)
result['original_message'] = module.params['name']
result['message'] = 'goodbye'
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
# use whatever logic you need to determine whether or not this module
# made any modifications to your target
if module.params['new']:
result['changed'] = True
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
# during the execution of the module, if there is an exception or a
# conditional state that effectively causes a failure, run
# AnsibleModule.fail_json() to pass in the message and the result
if module.params['name'] == 'fail me':
module.fail_json(msg='You requested this to fail', **result)
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
# in the event of a successful module execution, you will want to
# simple AnsibleModule.exit_json(), passing the key/value results
module.exit_json(**result)
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
def main():
run_module()
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
if __name__ == '__main__':
main()
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
Exercising your module code
2017-07-14 04:06:01 +02:00
===========================
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
Once you've modified the sample code above to do what you want, you can try out your module.
Our :ref: `debugging tips <debugging>` will help if you run into bugs as you exercise your module code.
Exercising module code locally
------------------------------
2018-10-12 16:59:08 +02:00
If your module does not need to target a remote host, you can quickly and easily exercise your code locally like this:
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
- Create an arguments file, a basic JSON config file that passes parameters to your module so you can run it. Name the arguments file `` /tmp/args.json `` and add the following content:
2017-01-27 23:03:26 +01:00
2017-08-17 02:56:53 +02:00
.. code :: json
2017-01-27 23:03:26 +01:00
2017-08-17 02:56:53 +02:00
{
"ANSIBLE_MODULE_ARGS": {
"name": "hello",
"new": true
}
}
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
- If you are using a virtual environment (highly recommended for
development) activate it: `` $ . venv/bin/activate ``
- Setup the environment for development: `` $ . hacking/env-setup ``
- Run your test module locally and directly:
2019-07-22 21:58:13 +02:00
`` $ python -m ansible.modules.cloud.azure.my_test /tmp/args.json ``
2017-01-27 23:03:26 +01:00
2018-10-12 16:59:08 +02:00
This should return output like this:
2017-01-27 23:03:26 +01:00
2017-08-17 02:56:53 +02:00
.. code :: json
2017-05-08 18:08:55 +02:00
2017-07-14 04:06:01 +02:00
{"changed": true, "state": {"original_message": "hello", "new_message": "goodbye"}, "invocation": {"module_args": {"name": "hello", "new": true}}}
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
Exercising module code in a playbook
------------------------------------
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
The next step in testing your new module is to consume it with an Ansible playbook.
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
- Create a playbook in any directory: `` $ touch testmod.yml ``
2017-08-14 02:09:19 +02:00
- Add the following to the new playbook file::
2017-01-27 23:03:26 +01:00
2017-08-14 02:09:19 +02:00
- name: test my new module
hosts: localhost
tasks:
- name: run the new module
2019-07-22 21:58:13 +02:00
my_test:
2017-08-14 02:09:19 +02:00
name: 'hello'
new: true
register: testout
- name: dump test output
debug:
msg: '{{ testout }}'
- Run the playbook and analyze the output: `` $ ansible-playbook ./testmod.yml ``
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
Testing basics
====================
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
These two examples will get you started with testing your module code. Please review our :ref: `testing <developing_testing>` section for more detailed
2018-10-24 17:14:01 +02:00
information, including instructions for :ref: `testing module documentation <testing_module_documentation>` , adding :ref: `integration tests <testing_integration>` , and more.
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
Sanity tests
------------
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
You can run through Ansible's sanity checks in a container:
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
`` $ ansible-test sanity -v --docker --python 2.7 MODULE_NAME ``
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
Note that this example requires Docker to be installed and running. If you'd rather not use a
2019-09-26 20:28:06 +02:00
container for this, you can choose to use `` --venv `` instead of `` --docker `` .
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
Unit tests
----------
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
You can add unit tests for your module in `` ./test/units/modules `` . You must first setup your testing environment. In this example, we're using Python 3.5.
2017-01-27 23:03:26 +01:00
2019-09-03 16:49:15 +02:00
- Install the requirements (outside of your virtual environment): `` $ pip3 install -r ./test/lib/ansible_test/_data/requirements/units.txt ``
2017-07-14 04:06:01 +02:00
- To run all tests do the following: `` $ ansible-test units --python 3.5 `` (you must run `` . hacking/env-setup `` prior to this)
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
.. note :: Ansible uses pytest for unit testing.
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
To run pytest against a single test module, you can do the following (provide the path to the test module appropriately):
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
`` $ pytest -r a --cov=. --cov-report=html --fulltrace --color yes
2019-07-22 21:58:13 +02:00
test/units/modules/.../test/my_test.py``
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
Contributing back to Ansible
============================
2017-01-27 23:03:26 +01:00
2018-02-11 09:32:22 +01:00
If you would like to contribute to the main Ansible repository
by adding a new feature or fixing a bug, `create a fork <https://help.github.com/articles/fork-a-repo/> `_
of the Ansible repository and develop against a new feature
branch using the `` devel `` branch as a starting point.
2018-09-07 15:57:36 +02:00
When you you have a good working code change, you can
2018-02-11 09:32:22 +01:00
submit a pull request to the Ansible repository by selecting
your feature branch as a source and the Ansible devel branch as
a target.
2017-01-27 23:03:26 +01:00
2018-09-07 15:57:36 +02:00
If you want to contribute your module back to the upstream Ansible repo,
review our :ref: `submission checklist <developing_modules_checklist>` , :ref: `programming tips <developing_modules_best_practices>` ,
and :ref: `strategy for maintaining Python 2 and Python 3 compatibility <developing_python_3>` , as well as
information about :ref: `testing <developing_testing>` before you open a pull request.
The :ref: `Community Guide <ansible_community_guide>` covers how to open a pull request and what happens next.
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
Communication and development support
=====================================
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
Join the IRC channel `` #ansible-devel `` on freenode for discussions
surrounding Ansible development.
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
For questions and discussions pertaining to using the Ansible product,
use the `` #ansible `` channel.
2017-01-27 23:03:26 +01:00
2017-07-14 04:06:01 +02:00
Credit
======
2017-01-27 23:03:26 +01:00
2019-04-22 16:37:00 +02:00
Thank you to Thomas Stringer (`@trstringer <https://github.com/trstringer> `_ ) for contributing source
2017-07-14 04:06:01 +02:00
material for this topic.