Skip to content

Commit

Permalink
feat: Add hierarchy retrieval info
Browse files Browse the repository at this point in the history
This feature is to help users understand the various pan-os-python
object hierarchies available for a given object.
  • Loading branch information
shinmog committed Jan 27, 2022
1 parent 0b47a3a commit ccdfbcb
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 10 deletions.
50 changes: 50 additions & 0 deletions panos/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,3 +459,53 @@ def node_color(module):
return nodecolor[module]
except KeyError:
return ""


def object_classes():
import inspect
from panos import errors
from panos import base

current_module = sys.modules[__name__]

omits = []
for pkg in (current_module, errors, base):
for name, the_cls in inspect.getmembers(pkg, inspect.isclass):
if not the_cls.__module__.startswith("panos"):
continue
if the_cls not in omits:
omits.append(the_cls)

from panos import device
from panos import firewall
from panos import ha
from panos import network
from panos import objects
from panos import panorama
from panos import policies
from panos import predefined

classes = {}
for pkg in (device, firewall, ha, network, objects, panorama, policies, predefined):
for name, the_cls in inspect.getmembers(pkg, inspect.isclass):
if not the_cls.__module__.startswith("panos"):
continue
if the_cls in omits:
continue
if getattr(the_cls, "IS_BASE_CLASS", False):
continue
classes[childtype_name(the_cls)] = the_cls

return classes


def childtype_name(cls):
return "{0}.{1}".format(cls.__module__.split(".")[1], cls.__name__)


def parents_for(cls, classes):
return [
x
for ctn, x in classes.items()
if childtype_name(cls) in getattr(x, "CHILDTYPES", [])
]
97 changes: 97 additions & 0 deletions panos/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2078,6 +2078,103 @@ def retrieve_panos_version(self):

return panos_version

def hierarchy_info(self):
"""This function returns hierarchical information about this object.
All objects in pan-os-python can be added as children to other objects,
so this function details what configurations are valid for this
particular object.
Returns:
dict: Hierarchy information about this object.
"""
from panos.firewall import Firewall
from panos.panorama import DeviceGroup
from panos.panorama import Panorama
from panos.panorama import Template
from panos.panorama import TemplateStack

ans = {
"configurations": [],
"valid": False,
}

classes = panos.object_classes()
configs = [
[self.__class__,],
]
updated_configs = []

# Find all possible config trees.
while True:
for num, chain in enumerate(configs):
parents = panos.parents_for(chain[-1], classes)
if parents:
configs.pop(num)
for p in parents:
configs.append(
chain + [p,]
)
break
else:
break

# Because Firewall objects can be children of Panorama objects,
# we need to do another pass to check for multi-PanDevice configs
# because Panorama is not strictly necessary.
for num in range(len(configs)):
chain = configs[num]
if Firewall in chain and Panorama in chain:
configs.append(chain[: chain.index(Firewall) + 1])

# Remove dupes.
updated_configs = []
for chain in configs:
if chain not in updated_configs:
updated_configs.append(chain)
configs = updated_configs

# Remove any DeviceGroup > Firewall hierarchies.
updated_configs = []
for chain in configs:
fw_index = -1
dg_index = -1
for num, x in enumerate(chain):
if x == Firewall:
fw_index = num
elif x == DeviceGroup:
dg_index = num
if fw_index == -1 or dg_index == -1 or fw_index + 1 != dg_index:
updated_configs.append(chain)
configs = updated_configs

# Remove Template / TemplateStack hierarchies if there is a DeviceGroup
# hierarchy.
for chain in configs:
if DeviceGroup in chain:
configs = [
x for x in configs if Template not in x and TemplateStack not in x
]
break

# Get the current config tree.
cur_tree = []
p = self
while p is not None:
cur_tree.append(p)
p = p.parent

# Reverse the trees to match reality.
for x in configs:
x.reverse()
cur_tree.reverse()

return {
"configurations": configs,
"current": cur_tree,
"valid": cur_tree in configs,
}


class VersioningSupport(object):
"""A class that supports getting version specific values of something.
Expand Down
2 changes: 2 additions & 0 deletions panos/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ class Vsys(VersionedPanObject):
"network.Vlan",
"network.VirtualRouter",
"network.VirtualWire",
"network.Layer2Subinterface",
"network.Layer3Subinterface",
"network.Zone",
)

Expand Down
2 changes: 2 additions & 0 deletions panos/firewall.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ class Firewall(PanDevice):
"network.LoopbackInterface",
"network.TunnelInterface",
"network.VlanInterface",
"network.Layer2Subinterface",
"network.Layer3Subinterface",
"network.Vlan",
"network.VirtualRouter",
"network.ManagementProfile",
Expand Down
28 changes: 28 additions & 0 deletions panos/panorama.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class Template(VersionedPanObject):
SUFFIX = ENTRY
CHILDTYPES = (
"device.Vsys",
"device.VsysResources",
"device.SystemSettings",
"device.LogSettingsSystem",
"device.LogSettingsConfig",
Expand All @@ -197,6 +198,11 @@ class Template(VersionedPanObject):
"network.IpsecCryptoProfile",
"network.IkeCryptoProfile",
"network.GreTunnel",
"network.Zone",
"network.IpsecTunnelIpv4ProxyId",
"network.IpsecTunnelIpv6ProxyId",
"network.Layer2Subinterface",
"network.Layer3Subinterface",
"panorama.TemplateVariable",
)

Expand Down Expand Up @@ -280,6 +286,11 @@ class TemplateStack(VersionedPanObject):
"network.IpsecCryptoProfile",
"network.IkeCryptoProfile",
"network.GreTunnel",
"network.Zone",
"network.IpsecTunnelIpv4ProxyId",
"network.IpsecTunnelIpv6ProxyId",
"network.Layer2Subinterface",
"network.Layer3Subinterface",
"panorama.TemplateVariable",
)

Expand Down Expand Up @@ -392,11 +403,28 @@ class Panorama(base.PanDevice):
"device.HttpServerProfile",
"device.CertificateProfile",
"device.SslDecrypt",
"objects.AddressObject",
"objects.AddressGroup",
"objects.ServiceObject",
"objects.ServiceGroup",
"objects.Tag",
"objects.ApplicationObject",
"objects.ApplicationGroup",
"objects.ApplicationFilter",
"objects.ApplicationContainer",
"objects.ScheduleObject",
"objects.SecurityProfileGroup",
"objects.CustomUrlCategory",
"objects.LogForwardingProfile",
"objects.DynamicUserGroup",
"objects.Region",
"objects.Edl",
"firewall.Firewall",
"panorama.DeviceGroup",
"panorama.Template",
"panorama.TemplateStack",
"plugins.CloudServicesPlugin",
"policies.Rulebase",
)

def __init__(
Expand Down
6 changes: 6 additions & 0 deletions panos/predefined.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ class Predefined(object):
XPATH = "/config/predefined"
SINGLE_ENTRY_XPATH = "/entry[@name='{0}']"
ALL_ENTRIES_XPATH = "/entry"
CHILDTYPES = (
"objects.ApplicationContainer",
"objects.ApplicationObject",
"objects.ServiceObject",
"objects.Tag",
)

def __init__(self, device=None, *args, **kwargs):
# Create a class logger
Expand Down
51 changes: 41 additions & 10 deletions tests/test_standards.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,6 @@ def inst(cls):
return cls()


def childtype_string(cls):
"""Get this class as a string suitable for a PanObject.CHILDTYPES entry."""
return "{0}.{1}".format(cls.__module__.split(".")[1], cls.__name__)


def versions():
val = MIN_PANOS_VERSION
while True:
Expand Down Expand Up @@ -295,7 +290,7 @@ def test_firewall_object_childtypes(panobj):
if panobj.__module__ == "panos.panorama":
pytest.skip("Skipping panorama specific classes for firewall test")

cts = childtype_string(panobj)
cts = panos.childtype_name(panobj)

if cts in PANORAMA_OBJECTS:
pytest.skip("Skipping Panoroama-only objects")
Expand All @@ -315,7 +310,7 @@ def test_object_with_vsys_root_is_in_vsys_childtypes(panobj):
if panobj.__module__ == "panos.panorama":
pytest.skip("Skipping panorama specific classes for firewall test")

cts = childtype_string(panobj)
cts = panos.childtype_name(panobj)

if panobj.ROOT != base.Root.VSYS:
pytest.skip("Not a vsys object")
Expand All @@ -331,7 +326,7 @@ def test_object_with_vsys_root_is_in_firewall_childtypes(panobj):
if panobj.__module__ == "panos.panorama":
pytest.skip("Skipping panorama specific classes for firewall test")

cts = childtype_string(panobj)
cts = panos.childtype_name(panobj)

if panobj.ROOT != base.Root.VSYS:
pytest.skip("Not a vsys object")
Expand All @@ -347,7 +342,7 @@ def test_object_with_non_vsys_root_is_not_in_vsys_childtypes(panobj):
if panobj.__module__ == "panos.panorama":
pytest.skip("Skipping panorama specific classes for firewall test")

cts = childtype_string(panobj)
cts = panos.childtype_name(panobj)

if hasattr(panobj, "ALWAYS_IMPORT"):
pytest.skip("Skipping importable object")
Expand All @@ -363,7 +358,7 @@ def test_vsys_importable_childtypes(panobj):
if panobj.__module__ == "panos.panorama":
pytest.skip("Skipping panorama specific classes for firewall test")

cts = childtype_string(panobj)
cts = panos.childtype_name(panobj)

if not hasattr(panobj, "ALWAYS_IMPORT"):
pytest.skip("Skipping standard object")
Expand Down Expand Up @@ -460,3 +455,39 @@ def test_policy_rule_is_in_all_rulebase_childtypes(policy_rule):

for cls in [policies.Rulebase, policies.PreRulebase, policies.PostRulebase]:
assert cts in cls.CHILDTYPES


def test_parent_aware_children_show_in_parent_childtypes(versioned_object):
obj = inst(versioned_object)

classes = set([])
for combo in obj._xpaths.settings:
cls_str = combo[0]
if cls_str is None:
continue

cls = None
for x in (
device,
firewall,
ha,
network,
objects,
panorama,
policies,
predefined,
):
if hasattr(x, cls_str):
cls = getattr(x, cls_str)
break
else:
assert False, "Could not find class {0}".format(cls_str)

if cls is not None:
classes.add(cls)

msg = "Child {0} has parent {1}, but does not show in parent's CHILDTYPES"
for cls in classes:
assert panos.childtype_name(versioned_object) in cls.CHILDTYPES, msg.format(
versioned_object, cls
)

0 comments on commit ccdfbcb

Please sign in to comment.