Writing Ansible Libraries / Modules

I read this post by Adam the Automator and there was some talk on using win_shell for most of the tasks. I had a lot to say about the topic so instead of replying further in the twitter feed I thought I would share my thoughts in this blog . Just to address the tweet, underneath the hood of the playbooks, roles, tasks , Ansible is just executing powershell in an idempotent way. (which really is just powershell script written in a certain way. I’ll address this below). At first I had the same mindset of @Adbertram. Why use Ansible when Powershell can do it all? One of my good friends and colleague eventually got me to use it and now I can see why it is useful. The reason why I use Ansible is for organising my Powershell tasks and then orchestrating them together. For me I feel that writing Ansible playbooks, roles and modules easier than the DSC ecosubsystem. This is however is for another topic. Which brings me to when would we write ansible modules? The simple answer is when you find yourself writing blocks of tasks in shell script. The Win_Shell module is great for simple instructional tasks. So for example simple tasks Download a file from a ftp session. but when you find yourself writing task blocks of win_shell commands Red Hat’s best practice is to turn that into a module. Writing blocks of commands could be akin to using the DSC Script Resource for all tasks and creating specialised DSC resources is when you create an Ansible module. Most of the time you use DSC Script module until you can write a DSC Resource for that particular task. Writing an Ansible module for Windows can be a mystery at first but it is quite simple if you understand the core components as the Ansible team have written helper functions that make this process easy. To create an Ansible library, it is a simple matter of creating a folder called library in your role and putting your powershell scripts in there:

C:\Users\weiyentan\Documents\GitHub\AnsibleModules\roles\configure_win_failovercluster
├──defaults
│ └──main.yml
├──files
├──handlers
├──library
│ ├──win_failovercluster.ps1
│ └──win_failovercluster.py
├──meta
│ └──main.yml
├──tasks
│ └──main.yml
├──templates
└──vars

Here you can see that i have created a module called win_failovercluster.ps1. The file win_failovercluster.py is essentially the python help documentation on how to use it the module. Once I have this I can then reference this in my playbook like so:


– name: failover cluster to other nodes in Cluster
win_failovercluster:
name: ‘{{ item }}’
Clustertype: clustergroup
destination: "{{ win_sql_destination}}"
with_items:
– ‘SQL Server (MSSQL)’
– MSDTC

I won’t go into how to write Ansible playbooks the extra parameters such as Clustertype and destination are all customisable and you can define it in the ps1. Here is the anatomy of a ps1 file.

#Requires -Module Ansible.ModuleUtils.Legacy
$ErrorActionPreference = 'Stop'

$results = @{
changed = $false #mandatory key value pair
msg = '' #optional keyvalue pair
}

The first part is that you must have a hash table called results. This tells ansible whether it has changed it or not. You can control the hash table later based on your conditions. The second part is where you define your parameters:

$params = Parse-Args $args -supports_check_mode $false
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$Clustertype = Get-AnsibleParam -obj $params -name "Clustertype" -type "str" -ValidateSet "Clustergroup" , "Clusterresource" , "ClusterSharedVolume"
$destination = Get-AnsibleParam -obj $params -name "destination" -type "str"
$group = Get-AnsibleParam -obj $params -name "group" -type "str"

The Parse-Args function pulls the parameters from the yml and then puts it into the powershell script to process. Get-AnsibleParam then gets those values and then sorts them accordingly. For those that know Powershell it is very similar to powershell param block. Here is some of the examples with the powershell equivalent: -type: class. [int], [string] -failifempty: mandatory -ValidateSet The name parameter is what you call the parameter from the yml. You can create as many parameters as you like. Because of the way Ansible works, Write-Error, throw , Write-Output is not recognised by Ansible. Instead there are other ways to use these so that Ansible will behave correctly. The functions to use are: Fail-Json = Write-Error, throw Exit-Json = Write-Output Remember, when you use these functions the script will exit. I can manipulate the message that is given.

$results.msg ="The cluster group $name is already on $destination"
Exit-Json -obj $results
Fail-Json $results -message "The cluster group $name did not move successfully to $destination"

So how do we make this whole thing idempotent? The same way that DSC works but not having to know the Get, Set, Test. Consider the block of code for configuring the failover cluster. Consider the following block of script.

First we evaluate whether the cluster group exists through a helper function testclusterelement . If it doesnt exist, then we produce an error, with the error message back to Ansible. The ansible module will terminate.

Then on line 12, we do another get on a value of the current node. If the current node is the same as $destination, then we have no change , everything is set correctly, Exit the script with the message to say that the group is already on the correct destination. Once it reaches Exit-Json the script will exit.

Then it will move on, and try to move the cluster, because the assumption is in this case that we are ready to move. the cluster group. Again, we produce the message and Move-ClusterGroup executes in a try catch. We change the the results hashtable to reflect that change.
In the catch block we use Fail-Json to produce the correct errors.

The last part is where we have accounted for when the destination has -not- been specified and we just move the role to any destination.
I could have written this module better by having the exit-json output doing it only once.
For completeness I have provided the gist link below:

As you can see , Ansible doesnt replace powershell at all. It complements it. All tasks in Ansible are just Powershell scripts when it comes to Windows. It’s not a question of whether you should use Powershell or Ansible. Ansible will wrap around your existing workflows. While I do believe that I wouldnt use Ansible for day to day tasks that I interact with Windows. I would Ansible Tower/AWX to replace scheduled tasks and execution of Powershell ‘scripts’. Ever since I have started using Ansible, I have found my organisation of code to be better and my reusability of code far more proficient than I was with without.

Leave a Reply

Your email address will not be published. Required fields are marked *