PaloAltoNetworks / pan-os-python

The PAN-OS SDK for Python is a package to help interact with Palo Alto Networks devices (including physical and virtualized Next-generation Firewalls and Panorama). The pan-os-python SDK is object oriented and mimics the traditional interaction with the device via the GUI or CLI/API.
https://pan-os-python.readthedocs.io
ISC License
341 stars 169 forks source link

op method returns a successfull ElementTree but without any content result #423

Closed ak-empiak closed 2 years ago

ak-empiak commented 2 years ago

Describe the bug

op method returns an successfull ElementTree without the content result.

Expected behavior

the text "attribute" of the xml.etree.ElementTree.Element result should contain the output of the command.

Current behavior

It is a NoneType type with None value (empty) Using the xml=True option works and returns the full output. It that normal that this last method returns a bytes object ? I tried to convert this bytes object into a ElementTree, but this new ElementTree has the exact same structure and content than the

Possible solution

i don't know.

Steps to reproduce

from panos.panorama import Panorama session = Panorama(hostname='192.168.103.10', api_key='**') result_et = session.op('show system info') ### successfull, but empty. result_xml = session.op('show system info', xml=True) ### successfull and returns output. import xml.etree.ElementTree as et result_xml_converted_to_et = et.fromstring(result_xml.decode()) ### result_xml is strictly identical as result_xml_converted_to_et

Screenshots

palo_panospython_op_command

Context

I want to use the ElementTree object, which is simpler to get values, instead of the bytes object.

Your Environment

python 3.9.6 pan-python 0.16.0 panorama 10.1.3-h1

welcome-to-palo-alto-networks[bot] commented 2 years ago

:tada: Thanks for opening your first issue here! Welcome to the community!

shinmog commented 2 years ago

op() seems to be behaving properly:

In [1]: from panos.panorama import Panorama

In [2]: pano = Panorama(.......)

In [3]: pano.refresh_system_info()
Out[3]: SystemInfo(version='10.1.3-h1', platform='Panorama', serial='unknown')

In [4]: ans = pano.op('show system info')

In [5]: ans.attrib
Out[5]: {'status': 'success'}

In [6]: ans.text

In [7]: type(ans)
Out[7]: xml.etree.ElementTree.Element

In [8]: ans.tag
Out[8]: 'response'

So when you run op() you get an xml.etree.ElementTree back, you can get the tag of a given XML node, the attributes of a given node, and you can iterate over the children of the node by doing:

for subtag in ans:
    print('tag:{0} attrib:{1}'.format(subtag.tag, subtag.attrib))
ak-empiak commented 2 years ago

Hello, yes I'm sorry, I forgot to close this issue, you are right, ElementTree objects are still a bit a mystery to me : data are not visible through pycharm debugger, I had no idea how to get them. If it can help someone, here is a full working example for a "show devices summary" on a Panorama :

    dict_summary = {}
    session = Panorama(hostname=xxxxxxxxx, api_key=yyyyyyyyyyyyy)
    command = f'show devices summary'
    result = session.op(command)

    xelement = result.find('result').find('device-summary')
    dict_summary['connected'] = xelement.find('connected').text
    dict_summary['dis-connected'] = xelement.find('dis-connected').text
    dict_summary['sw-version'] = [{entry.attrib.get('name'): entry.find('count').text} for entry in
                                  xelement.find('sw-version').findall('entry')]
    dict_summary['app-version'] = [{entry.attrib.get('name'): entry.find('count').text} for entry in
                                   xelement.find('app-version').findall('entry')]
    dict_summary['dg-status'] = {'in-sync': xelement.find('dg-status').find('in-sync').text,
                                 'out-sync': xelement.find('dg-status').find('out-sync').text}
    dict_summary['tpl-status'] = {'in-sync': xelement.find('tpl-status').find('in-sync').text,
                                  'out-sync': xelement.find('tpl-status').find('out-sync').text}
    return dict_summary

And you have a beautiful dict.

rebelfish commented 2 years ago

Another option I use to navigate xml responses:

'''
find('./'element1/element2) = absolute path and must be specific to the xml tree from the root
find('.//'element1/element2) = goes to any depth in the tree to find the element
findall('.//devices/entry') = returns a list of all entries beneath devices
A trailing '/' in the find or findall string will find the child elements as opposed to the element last mentioned in the string
find('.//element1/element2') will find element2
find('.//element1/element2/' will find the child elements of element2
'''

from panos.panorama import Panorama
pano = (hn, un, pw)
result = pano.op('show devices connected')

# Search for a specific attribute such as a name:
hn = result.find('.//entry[@name="012345678901"]/hostname').text
# or using a variable
sn = '012345678901'
hn = result.find(f'.//entry[@name="{sn}"]/hostname').text

# Find the parent of an entry using a trailing '/..' which can be stacked out to go further up the tree '/../..'
sn = result.find('.//hostname/..').attrib['name']

# Find a specific text result from an element
sn = result.find('.//entry/[hostname="myHostname"]').attrib['name']
# or using a varialbe
hn = 'myHostname'
sn = result.find(f'.//entry/[hostname="{hn}"]').attrib['name']

# Search for child elements within the found element
ip = result.find(f'.//entry/[hostname="{hn}"].find('ip-address').text
st = result.find(f'.//entry/[hostname="{hn}"]/ha/state').text

# Getting fancy
for host in result.findall('.//ha/[state="active"]/../hostname'):
    hn = host.text

It took a lot of research and help to pull these simple examples together and maybe it can help save someone else some time and effort.