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
AnsibleModule
object with an argument specification. - you can access the Ansible task arguments using the
module.params
dictionary 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