Powershell to write Ansible modules for NSX-v
About a year ago Ansible was still pretty new to me. At some point I got a question if I could automate parts of the NSX-v Distributed Firewall, prefferably with Ansible. So I started roaming the modules and found nothing. Then I roamed the internet and found little. Except for some Python tools, I found not so much. Then someone pointed out to me that you can actually write Ansible modules with Powershell. Which is actually pretty cool, is it not?
Use Case
There are several reasons to why you should do or not do this. In my case there was a strong wish from the customer to use Ansible. The NSX-v scripting is part of a larger script that does have regular Ansible modules available. So it would be super nice if I could have this fit and make it uniform. Since I am not great in Python, the Powershell option seemed to be a real good way to go. And so I did.
Requirements
Actually there are very few requirements to get this to work. There is a very good description of how to make Ansible Powershell modules here. This is pretty comprehensive and back then i needed a job to be done. So the example scripts here are simple. What I want to demonstrate is the ease you can integrate Powershell into Ansible. What you need:
- windows jumphost accesbile with Ansible, for e.g. WinRM
- installed powershell and nsx-v modules on jumphost
- automation user is recommended
- folder library in your Ansible repository
Especially setting up the jumphost might require some effort. When you need assistance feel free to leave me a message.
How it works
It is really quite simple. You need to add requirements at the top of the script, as you will see in the example. This enables you to use the Get-AnsibleParam
command. Now you can define variables for powershell retrieved from the Ansible task script. For example the variable $ssouser
in Powershell retieves content from sso_user
variable in Ansible. You are free to create and name those variables yourself. It is recommended to keep naming aligned to avoid confusion.
When you have defined all the variables you want, you should create a $result
array. This array is used to keep track of the changed
variable. This variable is used by Ansible for idempotency. This means, the script should only run when something is changed. If nothing has changed the script should do nothing and Ansible should give a green result.
Then write the scripts as you would like to and keep it simple. Also think about constructs to ensure idempotency. Send any relevant information to the $result
array and don’t forget to set $result.changed = $true
if anything changed.
When done save the script in your Ansible structure in a folder called ‘library’. Ansible knows to look in this folder for custom modules. Then just make your Ansible task just like any other Ansible task. You probably will have to run it with a ‘delegate_to’ command, so Ansible knows it should use the jumphost.
Goals
Create Distributed Firewall Rules in an existing section and add a Security Group to the source and destination for each rule. The Security Group must have dynamic inclusion for VM’s based on naming convention. The naming convention is als the basis for the name of the rule. The goal is to achieve this by using an Ansible Playbook.
Keep in mind
When creating multiple rules I have experienced that using a jumphost might be slow compared to native (Python) Ansible modules. The main reason for this is that it seeks and makes a new connection with vCenter and NSX every time it iterates over the script. As of today I have not found a way to circumvent this. The other thing to think about when writing Powershell modules for Ansible is to keep scripts as efficient as possible. Try to limit the module to just doing one task.
Layout
To achieve this I created four files. Two module files, placed in the ‘library’ folder. Then there is a file that holds the vars for the Distributed Firewall in the ‘vars’ folder under ‘inventory’. The fourth file is the actual tasks files, which for this purpose is placed under ‘playbooks\devops’.
Code used for the script
Powershell Module – create Security Groups
#!powershell
# Copyright: Davy van de Laar
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.Legacy
$ErrorActionPreference = "Stop"
# input parameters
$params = Parse-Args $args -supports_check_mode $true
$securitygroup = Get-AnsibleParam -obj $params -name "security_group" -type "str" -failifempty $true
$ssouser = Get-AnsibleParam -obj $params -name "sso_user" -type "str" -failifempty $true
$ssopassword = Get-AnsibleParam -obj $params -name "sso_password" -type "str" -failifempty $true
$vcenter = Get-AnsibleParam -obj $params -name "vcenter" -type "str" -failifempty $true
$dynamicmemberid = Get-AnsibleParam -obj $params -name "dynamic_memberid" -type "str" -failifempty $true
# result status
$result = @{
changed = $false
securitygroup = $securitygroup
}
# make connection with nsx and vcenter
try {
Connect-NsxServer -vCenterServer $vcenter -Username $ssouser -Password $ssopassword
}
catch {
Fail-Json -obj $result "check credentials or contact administrator"
}
# check if securitygroup exists, if not exist create new securitygroup
if (Get-NsxSecurityGroup -Name $securitygroup ){
$result.securitygroup = "security group $securitygroup already exists"
}
else {
try{
$criteria = New-NsxDynamicCriteriaSpec -Key VmName -Condition starts_with -Value $dynamicmemberid
New-NsxSecurityGroup -Name $securitygroup
Get-NsxSecurityGroup -Name $securitygroup | Add-NsxDynamicMemberSet -SetOperator AND -CriteriaOperator ANY -DynamicCriteriaSpec $criteria
} catch { Fail-Json -obj $result "unknown error creating rule $securitygroup" }
$result.changed = $true
}
Exit-Json -obj $result
Powershell Module – create firewall rules
#!powershell
# Copyright: Davy van de Laar
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Basic
#Requires -Module Ansible.ModuleUtils.Legacy
$ErrorActionPreference = "Stop"
# input parameters
$params = Parse-Args $args -supports_check_mode $true
$firewallsection = Get-AnsibleParam -obj $params -name "firewall_section" -type "str" -failifempty $true
$firewallrule = Get-AnsibleParam -obj $params -name "firewall_rule" -type "str" -failifempty $true
$securitygroup = Get-AnsibleParam -obj $params -name "security_group" -type "str" -failifempty $true
$ssouser = Get-AnsibleParam -obj $params -name "sso_user" -type "str" -failifempty $true
$ssopassword = Get-AnsibleParam -obj $params -name "sso_password" -type "str" -failifempty $true
$vcenter = Get-AnsibleParam -obj $params -name "vcenter" -type "str" -failifempty $true
# result status
$result = @{
changed = $false
firewallrule = $firewallrule
firewallsection = $firewallsection
securitygroup = $securitygroup
}
# make connection with nsx and vcenter
try {
Connect-NsxServer -vCenterServer $vcenter -Username $ssouser -Password $ssopassword
}
catch {
Fail-Json -obj $result "check credentials or contact administrator"
}
# check if firewallsection exists, this is a requirement to continue
# check if rule exists in section, if not exists create rule within section at bottom of section, with allow rule
if (Get-NsxFirewallSection -Name $firewallsection){
$result.firewallsection = "section $firewallsection exists"
}
else { Fail-Json -obj $result.firewallsection "section $firewallsection does not exist, check firewall_section or contact administrator" }
If (Get-NsxFirewallSection -Name $firewallsection | Get-NsxFirewallRule -Name $firewallrule){
$result.firewallrule = "firewall rule $firewallrule already exists in $firewallsection"
}
else {
try{
Get-NsxFirewallSection $firewallsection | New-NsxFirewallRule -Name $firewallrule -source (Get-NsxSecurityGroup $securitygroup) -Destination (Get-NsxSecurityGroup $securitygroup) -Action Allow -Position Bottom -EnableLogging
} catch { Fail-Json -obj $result "unknown error creating rule $firewallrule" }
$result.changed = $true
}
Exit-Json -obj $result
Yaml vars file – naming to be used (I used numbers)
script_naming:
'001':
section: 'NSX DFW - Existing Segment'
'002':
section: 'NSX DFW - Existing Segment'
'003':
section: 'NSX DFW - Existing Segment'
Yaml tasks file – the actual Ansible Playbook script
---
- name: create firewall rules with security groups
hosts: all
gather_facts: no
vars_files:
- "{{ inventory_dir }}/vars/script_naming.yml"
vars:
ansible_user: "{{ windows_ansible_user }}" # winrm user
ansible_password: "{{ windows_ansible_password }}"
ansible_connection: winrm
ansible_shell_type: powershell
ansible_winrm_transport: credssp
ansible_winrm_server_cert_validation: ignore
tasks:
- name: create dfw securitygroup
create_dfw_sec_group:
vcenter: vc01
sso_user: your-vc-sso-user
sso_password: your-vc-sso-password
security_group: "sg_{{ item.key }}"
dynamic_memberid: "VM{{ item.key }}"
loop: "{{ query('dict',script_naming) }}"
delegate_to: yourjumphost.somedomain.com
- name: create dfw firewall rule
create_dfw_rule:
vcenter: vc01
sso_user: your-vc-sso-user
sso_password: your-vc-sso-password
firewall_rule: "fw_rule_{{ item.key }}"
firewall_section: "{{ item.value.section }}"
security_group: "sg_{{ item.key }}"
loop: "{{ query('dict',script_naming) }}"
delegate_to: yourjumphost.somedomain.com
Conclusion
If you need to automate with Ansible and you want to implement some custom tasks based on Powershell, this works really fine. The problem I am having though is the Jumphost, this really slows things down. Using Powershell Core on the Ansible host would be a solution, however I did not get that to work. So in all honesty, I moved away in using this method for NSX. Nowadays I am using the Ansible uri modules to invoke RestAPI-calls. I might post about this in the future. Also I am trying to get more familiar with Python to write custom modules for various tasks. That being said, I still have some Powershell modules that I use today and I find it great that it’s possible. Especially when native or community Ansible modules are not available for your specific task.
Thanks for reading and feel free to reach out to me !
github: https://github.com/davyvandelaar/devops
[…] If you want to read more about Ansible and Powershel modules, please read: https://www.codecrusaders.nl/vmware/powershell-to-write-ansible-modules-for-nsx-v/ […]
[…] to delete Horizon Pools. The other one is a NSV-v Rule deletion role. Much like I described in this post. Except this role will much faster because it is written on the RestAPI, so no need for a […]
[…] Ansible is that you can create your own modules with Powershell. I wrote a blogpost about it here https://www.codecrusaders.nl/vmware/powershell-to-write-ansible-modules-for-nsx-v/ . It is recommended to keep your Powershell modules compact and short for simplicity and clarity. […]