Download as pdf or txt
Download as pdf or txt
You are on page 1of 27

Extending Ansible

Ivan Pepelnjak (ip@ipSpace.net)


Network Architect

ipSpace.net AG

This material is copyrighted and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Do’s and Don’t’s of Ansible
Ansible is automation engine

Ansible is not
• Generic programming language
• Data manipulation tool
• Database

Don’t fight the tool – extend it

2 This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Extending Ansible
External programs
• Run a program (or script) with shell or command module
• Program might have side effects or print results
• Results can be registered into an Ansible variable (like *_command)
• Results in JSON format are automatically parsed
Extending Ansible
• Modules (code executed on remote nodes, prints JSON to stdout)
• Plugins (Python code that augments Ansible functionality)
Dynamic Inventory
• Create list of hosts and variables in an external script
Run Ansible playbook from your Python script

3 This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Ansible Plugins
• Filter: Jinja2 filters
• Test: Jinja2 tests
• Callback: hook into Ansible events (modify display or logging)

• Lookup: called by the lookup function or used in with_ looping


constructs
• Vars: inject additional variables into Ansible play

• Action: execute code before (or instead of) an Ansible module


• Cache: cache facts across plays
• Connection: communicate with the hosts
• Strategy: change Ansible play/task execution strategy

4 This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Dynamic Inventory
Execute an external script to create a list of hosts, groups, and variables
• Replace inventory file with an executable script
• Script will be called with --list to generate a list of groups and hosts, and
with --host name to return host variables for each host
• Use _meta key in the list of groups to supply all host variables in a single
script execution

Alternatives:
• Use Ansible Tower
• Use add_host action to create groups and hosts for later plays in the
same playbook

More @ http://docs.ansible.com/ansible/intro_dynamic_inventory.html
5 This material is copyrighted
© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Existing Dynamic Inventory Scripts
Most inventory scripts are third-party contributions
• CloudStack, Docker, LXC, Nova, OpenStack, OpenShift, OVirt
• Azure, Digital Ocean, EC2, GCE
• Consul
• MDT
• Nagios
• NSoT
• Vagrant
• VirtualBox, VMware

More @ https://github.com/ansible/ansible/tree/devel/contrib/inventory
6 This material is copyrighted
© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Dynamic Hosts
Use add_host module to add dynamic hosts to Ansible inventory
• Create new hosts
• Create a dynamic group
• Add Ansible variables (including ansible_host or ansible_user) to dynamic hosts

Alternative to dynamic inventory:


• Execute a command or REST request that returns JSON data
(or read data from YAML file)
• Create dynamic hosts with add_host

Dynamic hosts or groups can be used in subsequent plays


• They are treated like regular hosts
• Ansible goes through the same variable setup process
(including reading host_vars files if they exist)

More @ http://docs.ansible.com/ansible/add_host_module.html
7 This material is copyrighted
© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Example: Provision Service on a Subset of Nodes
transaction.yml
---
- hosts: localhost
name: Select network nodes participating in transaction
tasks:
- include: read-transaction-file.yml
- name: Build the list of participating network nodes
add_host:
name: "{{item.node}}"
groups: "deploy"
with_items: "{{transaction.ports}}"

- hosts: deploy
name: configure a single VLAN transaction
tasks:
- include: read-transaction-file.yml
- set_fact: data="{{lookup('template',…

8 This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Callback Plugins

This material is copyrighted and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Callback Plugins
Define the callback plugin to use in ansible.cfg
• callback_plugins: path for callback plugins
• callback_whitelist: list of enabled plugins
• stdout_callback: callback used to print to stdout

Use ANSIBLE_STDOUT_CALLBACK environment variable to specify the


output callback

Useful callbacks:
• json: display playbook results in JSON format
• dense: display the minimal amount of information
• mail: send emails on failed tasks/plays
• selective: print tasks that have been marked with tag print_action

10This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Dense callback

$ export ANSIBLE_STDOUT_CALLBACK=dense

11This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Selective callback

$ export ANSIBLE_STDOUT_CALLBACK=selective

12This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
JSON Callback

$ export ANSIBLE_STDOUT_CALLBACK=json

Sample results
{
"play": {
"id": "5e379943-5d6c-42aa-9b83-76bfd94c358e",
"name": "configure VLAN service"
},
"tasks": [
{
"hosts": {
"leaf-1": {
"_ansible_no_log": false,
"ansible_facts": {
"services": {
"ACME": {

13This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
JSON Callback – Summary

$ export ANSIBLE_STDOUT_CALLBACK=json

Sample results
"stats": {
"leaf-1": {
"changed": 2,
"failures": 1,
"ok": 12,
"skipped": 7,
"unreachable": 0
},
"leaf-2": {
"changed": 2,
"failures": 1,
"ok": 10,
"skipped": 6,
"unreachable": 0
}}

14This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Jinja2 Filters and
Tests

This material is copyrighted and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Extending Ansible with Jinja2 Filters and Tests
Jinja2 plugins are the easiest way to extend Ansible
• Simple interface
• No asynchronous/distributed code
• Easy to test – you can test them in Python

A few ideas
• Get data from database/IPAM
• Transform data
• Complex data formatting
• Perform complex checks

16This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Simple Jinja2 Filter: Append to List
List.py (global methods)
def list_append(l,*argv):
for element in argv:
l.append(element)
return l

class FilterModule(object):

def filters(self):
return { 'append': list_append }

Define filter method Define FilterModule class


• First argument is filtered variable • filters method returns a dictionary of filters
• Other arguments are additional arguments defined in this module
specified in the filter
• Returns value (scalar, list, dictionary)

Challenge: pollution of global namespace  move filter methods into the class
17This material is copyrighted
© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Simple Jinja2 Filter: Append to List
List.py (simple)
class FilterModule(object):

def list_append(self,l,*argv):
for element in argv:
l.append(element)
return l

def filters(self):
return { 'append': self.list_append }

Jinja2 filter moved into the class (no longer visible from the outside)
• Filter method has to specify an extra argument (self = object reference)
• Filter method reference has to use self (method within a class)

Based on http://www.dasblinkenlichten.com/creating-ansible-filter-plugins/
18This material is copyrighted
© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Simple Jinja2 Filter: Error Handling
List.py
from jinja2 import TemplateError

class FilterModule(object):

def list_append(self,l,*argv):
if type(l) is not list:
raise TemplateError("First argument must be a list")
for element in argv:
l.append(element)
return l

def filters(self):
return { 'append': self.list_append }

19This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Simple Jinja2 Filter: Accept Scalars or Lists
List.py
from jinja2 import TemplateError

class FilterModule(object):

def list_append(self,l,*argv):
if type(l) is not list:
raise TemplateError("First argument must be a list")
for element in argv:
if type(element) is list:
l.extend(element)
else:
l.append(element)
return l

def filters(self):
return { 'append': self.list_append }

20This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Test Everything: Append Filter Unit Text
test-list-plugin.yml
---
- name: Test list plugin
hosts: localhost
connection: local
tasks:
- assert:
that: "[1,2,3]|append(4) is equalto([1,2,3,4])"
msg: Append to list failed
- assert:
that: "[1,2,3]|append(4,5) is equalto([1,2,3,4,5])"
msg: Append multiple elements to list failed
- assert:
that: "[]|append(4) is equalto([4])"
msg: Append to empty list failed
- assert:
that: "[1,2]|append([3,4]) is equalto([1,2,3,4])"
msg: Appending a list to a list should result in a single list

21This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Example: Set Device OS Based on Show Version
net-helper.py
class FilterModule(object):

def device_type(self,show_ver,*deftype):
if "Cisco IOS" in show_ver: return "ios"
if "NX-OS" in show_ver: return "nx-os"

if len(deftype) > 0: return deftype[0]

raise TemplateError("Unknown device type")

def filters(self):
return {
'device_type': self.device_type
}

22This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Set Device OS: Unit Tests
test-device-type.py
---
- hosts: localhost
tasks:
- assert:
that: "'Cisco IOS'|device_type == 'ios'"
msg: Failed to recognize Cisco IOS
- assert:
that: "'NX-OS'|device_type == 'nx-os'"
msg: Failed to recognize Cisco Nexus OS
- assert:
that: "'abcd'|device_type('unknown') == 'unknown'"
msg: failed to process default device type
- set_fact: x="{{'aaa'|device_type}}"
ignore_errors: true
register: result
- assert:
that: result|failed
msg: Failed to crash on unknown device type with no default

23This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Using Automatic Device Type Filter
device-type.py
---
- name: Set network device type
hosts: all
tasks:
- ios_command:
commands: show version
provider: '{{cli}}'
register: result
- set_fact:
ansible_os: "{{result.stdout[0]|device_type}}"
- debug:
msg: |
Device type for {{inventory_hostname}}
set to {{ansible_os}}

24This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Connecting to
External Databases

This material is copyrighted and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Connecting to External Databases
Ansible has no database connection module

Reading database data:


• Dynamic inventory
• External program generating JSON output
• Lookup or filter plugins

Writing database data:


• External program (worst case: SQL statements)
• Filter plugin

26This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars
Questions?

Send them to ip@ipSpace.net or @ioshints

27This material is copyrighted


© ipSpace.net 2017 and licensed for the sole use by Mikel Maeso (mikel.maeso@gmail.com
Extending Ansible [85.87.178.33]). More information at http://www.ipSpace.net/Webinars

You might also like