Nebulaworks Insight Content Card Background - Pontus wellgraf tile metal
I love Ansible. It has so many features that make my work easier, automated, and highly readable as a Cloud Engineer at
Nebulaworks. But what about when the OOTB modules are not cutting it? What about when I find my playbook with 10+
module calls and each play is 12 lines of code; idempotence, readability have flown out the window! No more, I say! In
this post, I’m going to provide you an easily repeatable framework for creating custom Ansible modules. I think you will
find it simple to do, fun, and can improve playbook quality significantly. I take into account the major benefits of
Ansible and we’ll ensure these benefits:
- Support of check mode
- Implement key word
Disclaimer: consider whether or not you should make a module before doing so
Supporting check mode
First a quick explanation on why you should support check mode and how it dictates the design of this framework.
One of the major arguments for even using a tool like Ansible is the ability to declare state but not necessarily take action. This is the basic idea of idempotence. Check mode takes this idea up one level and allows you to simply test whether or not any changes WOULD be made. This can be useful in many ways, for example, have your playbooks run continuously in check mode against prod infrastructure and alert if a change WOULD take place.
In order to allow for check mode, all we need to do is separate making changes from checking if changes are required. I typically do this by implementing a few common variables:
changed: starts as
falseand is set to
trueonce we’ve determined a change needs to be made according to the play
exists: set to
trueif the declared state item “already exists”
current_state: used to hold the content of the declared item state (if it already exists)
requested_state: used to hold the desired content of declared item state (parameters from your play)
For example code, you can reference Ansible Module 101
Anatomy of a module
- AnsibleModule class instantiation
- Comparison of the current and desired state
- Taking Action
1. AnsibleModule class instantiation
Ansible provides the
AnsibleModule class for easy extension of the tool. We start off any module by instantiating this
class and passing the modules variables from our play.
def main(): module = AnsibleModule( argument_spec=dict( message=dict(type='str'), state=dict(type='str', choices=['present','absent'], default='present') ), required_if=([('state', 'present', ['message'])]), supports_check_mode=True )
For a list of the accepted parameters to AnsibleModule, check the source here
When the above python file is called by an Ansible play, it will start by creating a module instance. With the returned
module we can now perform the rest of our logic and end by returning
changed: True /
False. We will
actually use a variable to track so
2. Exploration of current state
In order to be idempotent, we must next examine the current state of the object we’re attempting to manage. I also take this time to make checks on the environment to ensure and requisites are already present. Whatever object is attempting to be managed, perform checks:
Does it exist?
exists = os.path.exists(motd_file)
If it exists and is desired state is ‘present’ check current content vs desired
if exists and module.params['state'] == 'present': # get current message from file with open(motd_file, 'r') as fd: current_message = fd.read().strip() # check current content vs desired if current_message != message: changed = True # file exists but we don't want it to elif exists and module.params['state'] == 'absent': changed = True # file doesn't exist but we want it to else: if module.params['state'] == 'present': changed = True
3. Taking Action
Now we have all the information we need to make idempotent respectful changes. Last we’ll account for check mode and skip over taking action if directed.
# only perform changes when a required change has been detected and if check mode is off if changed and not module.check_mode: if module.params['state'] == 'present': with open(motd_file, 'w') as fd: fd.write(module.params['message']) else: os.remove(motd_file)
Finally, we can report back to Ansible on what we’ve done. The goal of check mode is to state changed = True if we WOULD have completed a change but we do not complete it. With the code design in our [taking action](### Taking Action) section, we have accounted for that requirement.
- If you’re considering writing a custom module here is some help on how to test while writing
- Here’s a more robust explanation on how to write an Ansible module Ansible Module 101
Now you know how to create a repeatable framework for Ansible modules! For all things automation and Devops, Nebulaworks provides expert guidance, training, and services. Hit us up if you’re looking for help!