12.3 Creating a Local Module
Before we dive further into the bells and whistles of collections and other ansible dev tools, let’s start with a simple module. Most likely you will encounter use-cases that will not justify the overhead of maintaining a collection. In those cases it is still possible to create a local module and use it in your playbooks in a much easier and more appropriate way.
Setup
To do so, we need to configure Ansible to look for our custom modules at a specified location.
We can tell Ansible where to look for custom modules by either setting the ANSIBLE_LIBRARY
environment variable or by setting the library option in the defaults section of the ansible.cfg file.
Let’s use the ansible.cfg approach for this and set the library option to a local folder ./library.
Solution Task 1
The ansible.cfg file should look like this:
| |
Do not forget to create the library folder:
| |
Ansible Module Structure
Let’s have a look at the example module of the official documentation.
From the code of the my_test.py module we can identify the key elements required to write a module:
1. Some boilerplate code
Ansible modules must start with this boilerplate code.
| |
This ensures that the module can be executed as a standalone module using python 2 and 3.
2. The DOCUMENTATION block
This block contains information about the module and its arguments.
Ansible tooling uses this information to generate documentation for the ansible-doc output and the antsibull-doc HTML pages.
For this reason this block must adhere to a specific format, which is thoroughly documented in the official documentation.
3. The EXAMPLES block
This block contains examples of how to use the module. Just like the DOCUMENTATION block, this block must adhere to a specific format.
The specification is documented in the official documentation as well.
4. The RETURN block
Finally, the RETURN block contains information about the return values of the module. Again, this block must adhere to a specific format.
The specification is documented in the official documentation.
5. The main function
The main function contains the code of the module. There are only a few rules to keep in mind:
- in the main function you must instantiate an
AnsibleModuleobject with an argument specification. - you can access the Ansible task arguments using the
module.paramsdictionary and use them to implement your logic. - the main function exits when either
module.fail_json(in case of failure) ormodule.exit_json(in case of success) is called.
So given these rules, the main function signature will probably look like this:
| |
6. The main guard
This is the last part and is the entry point of the module. It contains the code call that is executed when the module is called, either by Ansible when it executes the module on the target host or when we want to test the module locally. This part usually looks like this:
| |
Now that we know how a module is structured, let’s try to implement a simple module ourselves.
Task 2 - Schrödingers Cat - Module specification
Let’s say for some reason we need a module schroedingers_cat which can determine the state of a cat on our hosts.
We can implement it in the previously created directory as ./library/schroedingers_cat.py.
By default, the module must return dead and alive as the state of the cat.
However, if the force_box_open argument is set to true, the module should choose randomly between alive or dead.
Given the above requirements, try to implement the DOCUMENTATION, EXAMPLES and RETURN of the schroedingers_cat module.
Solution Task 1
First create the module file:
| |
Now, add these blocks and don’t forget the boilerplate code:
| |
Task 2 - Schrödingers Cat - Implementing the module
Now let’s implement the main function of the schroedingers_cat module.
Keep in mind the rules for the main function mentioned above and don’t forget to call the function in the main guard.
Solution Task 2
First we must import the required python dependencies:
| |
Now we can start the main function by creating the argument specification and instantiating an AnsibleModule object:
| |
The next step now would be to access the arguments using the module.params dictionary to implement the logic of the module.
| |
Finally, we can return the result using the module.exit_json function:
| |
At the very last of the file we need to add the main guard where we call the main function:
| |
Task 3 - Schrödingers Cat - Verifying the module
Now that we have the logic in place let’s verify the module. First we can test using ansible-doc if the module is detected correctly by ansible.
If so we can then proceed with testing our module with an Ansible adhoc command.
Solution Task 3
First check using ansible-doc:
| |
If the module is detected correctly, we can then proceed with testing our module with Ansible adhoc commands:
| |
or with the force_box_open argument:
| |
Running the module with the force_box_open argument should result in a random alive or dead state.
All done?
- Can you verify your module in any other way? see Verifying your module