Creating and sharing a module is a great way of contributing to Ansible.
This guide will show you step-by-step how to create and test one.
When you later create useful modules doing real work, see the Ansible collection creator path link at the end of the topic to learn how to share them with others and even get them included in the Ansible package!
Tested on Fedora 39.
Prepare your environment
1. Install podman
$ sudo dnf install podman
2. Install ansible-core (if you already have the ansible
package installed, it’ll also work)
$ pip install ansible-core
3. Create the following directories in your home directory
$ mkdir -p ~/ansible_collections/my_namespace/my_collection/plugins/modules
$ cd ~/ansible_collections/my_namespace/my_collection/
Add a module
4. Start a new module file with your text editor
$ vim plugins/modules/dummy.py
5. Add the following content to the module
#!/usr/bin/python
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
# What you define in this section will appear on Galaxy
# and on docs.ansible.com as module documentation
# if it gets included in the Ansible package.
DOCUMENTATION = r'''
---
module: dummy
short_description: A dummy module
description:
- Divides one number by another one and returns a quotient.
version_added: '0.1.0'
author:
- Your name (@YourGitHubAccount)
options:
dividend:
description:
- Dividend.
type: int
required: true
divisor:
description:
- Divisor.
type: int
required: true
'''
EXAMPLES = r'''
- name: Divide a by b
register: result
my_namespace.my_collection.dummy:
dividend: 4
divisor: 2
- name: Print the result
ansible.builtin.debug:
var: result.quotient
'''
RETURN = r'''
quotient:
description:
- The result of division.
returned: on success
sample: 2
type: int
'''
from ansible.module_utils.basic import AnsibleModule
def main():
# The module accepts arguments declared here.
argument_spec = {}
argument_spec.update(
dividend=dict(type='int', required=True),
divisor=dict(type='int', required=True),
)
# Instantiate an object of the AnsibleModule class
# provided by the Ansible Core.
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
# Assign passed options to variables
dividend = module.params['dividend']
divisor = module.params['divisor']
# The work starts here.
# All interactions with the user happen through
# interfaces provided by the module object of
# the AnsibleModule class of Ansible Core.
# Let's fail when the divisor is zero using the fail_json() method.
if divisor == 0:
module.fail_json("Division by zero is not allowed!")
# Do something.
quotient = dividend // divisor
# Exit the module.
# Users will get the result in its JSON output after execution.
module.exit_json(changed=False, quotient=quotient)
if __name__ == '__main__':
main()
5. Let’s check that the module has a proper format, satisfies the code standards, etc. with running sanity tests
$ ansible-test sanity plugins/modules/dummy.py --docker
6. Create directories for integration tests
$ mkdir -p tests/integration/targets/dummy/tasks
7. Let’s check if the module works as expected by adding and running integration tests
$ vim tests/integration/targets/dummy/tasks/main.yml
- name: Divide by zero
register: result
ignore_errors: true
my_namespace.my_collection.dummy:
dividend: 4
divisor: 0
- name: Assert the result
ansible.builtin.assert:
that:
- result is failed
- result.msg == "Division by zero is not allowed!"
- name: Divide a by b
register: result
my_namespace.my_collection.dummy:
dividend: 4
divisor: 2
- name: Assert the result
ansible.builtin.assert:
that:
- result is succeeded
- result.quotient == 2
8. Run the integration tests in a podman container
$ ansible-test integration dummy --docker -vvv
In the output you should see:
TASK [dummy : Devide a by b] ***
...
ok: [testhost] => {
"changed": false,
"invocation": {
"module_args": {
"dividend": 4,
"divisor": 2
}
},
"quotient": 2
}
TASK [dummy : Assert the result] ***
...
ok: [testhost] => {
"changed": false,
"msg": "All assertions passed"
}
What you could do next:
1. Create a simple info module that returns some system or service information, for example, a kernel version as a single row:
6.10.4-100.fc39.x86_64
2. When it works, make it return the version as a dictionary like:
kernel: 6
major: 10
minor: 4
3. If there are no decent collections with modules on Galaxy for the services you use at work/as hobby, create one yourself. You could start with writing an _info
module that fetches useful information about the service and returns it to a user.
4. Add other modules that changes the service state in an idempotent way.
5. Read the next topic How to set up a repository for an Ansible collection step by step.
5. Want to contribute to existing modules? Read the How to submit your first pull request to Ansible step-by-step post.
Documentation:
- Developing modules: modules development well-explained including
_fact/_info
modules and how to test them. - Ansible collection creator path: a consistent overview of the Ansible collection creator journey from an idea for the first module/role to having your collection included in the Ansible community package.
- Ansible developer journey