Skip to content

Instantly share code, notes, and snippets.

@MrBasset
Last active October 6, 2016 09:57
Show Gist options
  • Save MrBasset/368aa587da5b3bc9c4c5 to your computer and use it in GitHub Desktop.
Save MrBasset/368aa587da5b3bc9c4c5 to your computer and use it in GitHub Desktop.
A very quick and dirty implementation of an xpath filter for ansible.
#/usr/bin/python
# By Dan Pool
# Custom filter for parsing XML variables using xpath
from functools import partial
from io import BytesIO
try:
from lxml import etree
import lxml
except:
etree = none
lxml = none
from ansible import errors
def xpath_match(value, xpath, nspace = ''):
if not xpath:
return False
encoded_value = BytesIO(value.encode('utf-8'))
# Try to parse the value as XML
try:
parser = etree.XMLParser(remove_blank_text=False)
xml = etree.parse(encoded_value, parser)
except etree.XMLSyntaxError as e:
raise errors.AnsibleFilterError("Error parsing value: {0}".format(e.msg))
try:
if not nspace:
match = xml.xpath(xpath, smart_strings=False)
else:
prefix,uri = nspace.split("=",1)
ns = {}
ns[prefix] = uri
match = xml.xpath(xpath, namespaces=ns, smart_strings=False)
except etree.XPathSyntaxError as e:
raise errors.AnsibleFilterError("Error with xpath syntax: {0}".format(e.error_log.last_error))
except etree.XPathEvalError as e:
raise errors.AnsibleFilterError("Error evalutating xpath: {0}".format(e.error_log.last_error))
match_xpaths = []
for m in match:
match_xpaths.append(m)
if len(match_xpaths) > 1:
return match_xpaths
elif len(match_xpaths) == 1:
return match_xpaths[0]
else:
return False
def _need_lxml(f_name, *args, **kwargs):
raise errors.AnsibleFilterError('The {0} filter requires python-lxml be'
' installed on the ansible controller'.format(f_name))
# ---- Ansible filters ----
class FilterModule(object):
filter_map = {
'xpath' : xpath_match
}
def filters(self):
if lxml:
return self.filter_map
else:
# Need to install python-lxml for these filters to work
return dict((f, partial(_need_lxml, f)) for f in self.filter_map)
@MrBasset
Copy link
Author

No doubt it could be cleaned up by anyone who's been doing python for more than one afternoon. Example usage:

- name: Info | Get some XML content                                                             
  environment: proxy_env                                                                        
  uri: method=GET url="{{ xml_href }}" return_content=yes                                       
  register: stuff                                                                             

- name: Set | Extract info from the content                                               
  set_fact:                                                                                     
    href: "{{ stuff.content | xpath("/x:foo/x:bar[@name="unique"]/@href", "x=http://www.example.com") }}"
    href_list: "{{ stuff.content | xpath("/x:foo/x:bar/@href", "x=http://www.example.com") }}"

- name: Info | Get new XML content
  environment: proxy_env                                                                        
  uri: method=GET url="{{ href }}" return_content=yes                                       
  register: new_stuff

- name: Info | Get lots of XML content
  environment: proxy_env                                                                        
  uri: method=GET url="{{ item }}" return_content=yes
  with_items: href_list.results
  register: loads_of_stuff

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment