1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Bug fixes and enhancements for panos connection (#52306)

This commit is contained in:
Garfield Lee Freeman 2019-02-17 19:49:26 -08:00 committed by Ganesh Nalawade
parent 97ebc95784
commit 6b8f679ad1

View file

@ -29,8 +29,10 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
_MIN_VERSION_ERROR = '{0} version ({1}) < minimum version ({2})'
HAS_PANDEVICE = True HAS_PANDEVICE = True
try: try:
import pandevice
from pandevice.base import PanDevice from pandevice.base import PanDevice
from pandevice.panorama import DeviceGroup, Template, TemplateStack from pandevice.panorama import DeviceGroup, Template, TemplateStack
from pandevice.policies import PreRulebase, PostRulebase, Rulebase from pandevice.policies import PreRulebase, PostRulebase, Rulebase
@ -40,8 +42,13 @@ except ImportError:
HAS_PANDEVICE = False HAS_PANDEVICE = False
def _vstr(val):
return '{0}.{1}.{2}'.format(*val)
class ConnectionHelper(object): class ConnectionHelper(object):
def __init__(self, panorama_error, firewall_error): def __init__(self, min_pandevice_version, min_panos_version,
panorama_error, firewall_error):
"""Performs connection initialization and determines params.""" """Performs connection initialization and determines params."""
# Params for AnsibleModule. # Params for AnsibleModule.
self.argument_spec = {} self.argument_spec = {}
@ -55,6 +62,8 @@ class ConnectionHelper(object):
self.template = None self.template = None
self.template_stack = None self.template_stack = None
self.vsys_importable = None self.vsys_importable = None
self.min_pandevice_version = min_pandevice_version
self.min_panos_version = min_panos_version
self.panorama_error = panorama_error self.panorama_error = panorama_error
self.firewall_error = firewall_error self.firewall_error = firewall_error
@ -78,6 +87,14 @@ class ConnectionHelper(object):
if not HAS_PANDEVICE: if not HAS_PANDEVICE:
module.fail_json(msg='Missing required library "pandevice".') module.fail_json(msg='Missing required library "pandevice".')
# Verify pandevice minimum version.
if self.min_pandevice_version is not None:
pdv = tuple(int(x) for x in pandevice.__version__.split('.'))
if pdv < self.min_pandevice_version:
module.fail_json(msg=_MIN_VERSION_ERROR.format(
'pandevice', pandevice.__version__,
_vstr(self.min_pandevice_version)))
d, host_arg = None, None d, host_arg = None, None
if module.params['provider'] and module.params['provider']['host']: if module.params['provider'] and module.params['provider']['host']:
d = module.params['provider'] d = module.params['provider']
@ -91,12 +108,22 @@ class ConnectionHelper(object):
# Create the connection object. # Create the connection object.
try: try:
self.device = PanDevice.create_from_device( self.device = PanDevice.create_from_device(
d[host_arg], d['username'], d['password'], d['api_key']) d[host_arg], d['username'], d['password'],
d['api_key'], d['port'])
except PanDeviceError as e: except PanDeviceError as e:
module.fail_json(msg='Failed connection: {0}'.format(e)) module.fail_json(msg='Failed connection: {0}'.format(e))
# Verify PAN-OS minimum version.
if self.min_panos_version is not None:
if self.device._version_info < self.min_panos_version:
module.fail_json(msg=_MIN_VERSION_ERROR.format(
'PAN-OS', _vstr(self.device._version_info),
_vstr(self.min_panos_version)))
parent = self.device parent = self.device
not_found = '{0} "{1}" is not present.' not_found = '{0} "{1}" is not present.'
pano_mia_param = 'Param "{0}" is required for Panorama but not specified.'
ts_error = 'Specify either the template or the template stack{0}.'
if hasattr(self.device, 'refresh_devices'): if hasattr(self.device, 'refresh_devices'):
# Panorama connection. # Panorama connection.
# Error if Panorama is not supported. # Error if Panorama is not supported.
@ -104,22 +131,33 @@ class ConnectionHelper(object):
module.fail_json(msg=self.panorama_error) module.fail_json(msg=self.panorama_error)
# Spec: template stack. # Spec: template stack.
tmpl_required = False
added_template = False
if self.template_stack is not None: if self.template_stack is not None:
name = module.params[self.template_stack] name = module.params[self.template_stack]
stacks = TemplateStack.refreshall(parent) if name is not None:
stacks = TemplateStack.refreshall(parent, name_only=True)
for ts in stacks: for ts in stacks:
if ts.name == name: if ts.name == name:
parent = ts parent = ts
added_template = True
break break
else: else:
module.fail_json(msg=not_found.format( module.fail_json(msg=not_found.format(
'Template stack', name, 'Template stack', name,
)) ))
elif self.template is not None:
tmpl_required = True
else:
module.fail_json(msg=pano_mia_param.format(self.template_stack))
# Spec: template. # Spec: template.
if self.template is not None: if self.template is not None:
name = module.params[self.template] name = module.params[self.template]
templates = Template.refreshall(parent) if name is not None:
if added_template:
module.fail_json(msg=ts_error.format(', not both'))
templates = Template.refreshall(parent, name_only=True)
for t in templates: for t in templates:
if t.name == name: if t.name == name:
parent = t parent = t
@ -128,11 +166,16 @@ class ConnectionHelper(object):
module.fail_json(msg=not_found.format( module.fail_json(msg=not_found.format(
'Template', name, 'Template', name,
)) ))
elif tmpl_required:
module.fail_json(msg=ts_error.format(''))
else:
module.fail_json(msg=pano_mia_param.format(self.template))
# Spec: vsys importable. # Spec: vsys importable.
if self.vsys_importable is not None: vsys_name = self.vsys_importable or self.vsys
name = module.params[self.vsys_importable] if vsys_name is not None:
if name is not None: name = module.params[vsys_name]
if name not in (None, 'shared'):
vo = Vsys(name) vo = Vsys(name)
parent.add(vo) parent.add(vo)
parent = vo parent = vo
@ -142,7 +185,7 @@ class ConnectionHelper(object):
if dg_name is not None: if dg_name is not None:
name = module.params[dg_name] name = module.params[dg_name]
if name not in (None, 'shared'): if name not in (None, 'shared'):
groups = DeviceGroup.refreshall(parent) groups = DeviceGroup.refreshall(parent, name_only=True)
for dg in groups: for dg in groups:
if dg.name == name: if dg.name == name:
parent = dg parent = dg
@ -158,6 +201,10 @@ class ConnectionHelper(object):
rb = PreRulebase() rb = PreRulebase()
parent.add(rb) parent.add(rb)
parent = rb parent = rb
elif module.params[self.rulebase] == 'rulebase':
rb = Rulebase()
parent.add(rb)
parent = rb
elif module.params[self.rulebase] == 'post-rulebase': elif module.params[self.rulebase] == 'post-rulebase':
rb = PostRulebase() rb = PostRulebase()
parent.add(rb) parent.add(rb)
@ -174,7 +221,7 @@ class ConnectionHelper(object):
# Spec: vsys or vsys_dg or vsys_importable. # Spec: vsys or vsys_dg or vsys_importable.
vsys_name = self.vsys_dg or self.vsys or self.vsys_importable vsys_name = self.vsys_dg or self.vsys or self.vsys_importable
if vsys_name is not None: if vsys_name is not None:
self.con.vsys = module.params[vsys_name] self.device.vsys = module.params[vsys_name]
# Spec: rulebase. # Spec: rulebase.
if self.rulebase is not None: if self.rulebase is not None:
@ -189,20 +236,30 @@ class ConnectionHelper(object):
def get_connection(vsys=None, device_group=None, def get_connection(vsys=None, device_group=None,
vsys_dg=None, vsys_importable=None, vsys_dg=None, vsys_importable=None,
rulebase=None, template=None, template_stack=None, rulebase=None, template=None, template_stack=None,
classic_provider_spec=False, with_classic_provider_spec=False, with_state=True,
argument_spec=None, required_one_of=None,
min_pandevice_version=None, min_panos_version=None,
panorama_error=None, firewall_error=None): panorama_error=None, firewall_error=None):
"""Returns a helper object that handles pandevice object tree init. """Returns a helper object that handles pandevice object tree init.
All arguments to this function (except panorama_error, firewall_error, The `vsys`, `device_group`, `vsys_dg`, `vsys_importable`, `rulebase`,
and classic_provider_spec) can be any of the following types: `template`, and `template_stack` params can be any of the following types:
* None - do not include this in the spec * None - do not include this in the spec
* True - use the default param name * True - use the default param name
* string - use this string for the param name * string - use this string for the param name
The `min_pandevice_version` and `min_panos_version` args expect a 3 element
tuple of ints. For example, `(0, 6, 0)` or `(8, 1, 0)`.
If you are including template support (by defining either `template` and/or
`template_stack`), and the thing the module is enabling the management of is
an "importable", you should define either `vsys_importable` (whose default
value is None) or `vsys` (whose default value is 'vsys1').
Arguments: Arguments:
vsys: Firewall only - The vsys. vsys: The vsys (default: 'vsys1').
device_group: Panorama only - The device group. device_group: Panorama only - The device group (default: 'shared').
vsys_dg: The param name if vsys and device_group are a shared param. vsys_dg: The param name if vsys and device_group are a shared param.
vsys_importable: Either this or `vsys` should be specified. For: vsys_importable: Either this or `vsys` should be specified. For:
- Interfaces - Interfaces
@ -212,16 +269,24 @@ def get_connection(vsys=None, device_group=None,
rulebase: This is a policy of some sort. rulebase: This is a policy of some sort.
template: Panorama - The template name. template: Panorama - The template name.
template_stack: Panorama - The template stack name. template_stack: Panorama - The template stack name.
classic_provider_spec(bool): Include the ip_address, username, with_classic_provider_spec(bool): Include the ip_address, username,
password, api_key params in the base spec, and make the password, api_key, and port params in the base spec, and make the
"provider" param optional. "provider" param optional.
with_state(bool): Include the standard 'state' param.
argument_spec(dict): The argument spec to mixin with the
generated spec based on the given parameters.
required_one_of(list): List of lists to extend into required_one_of.
min_pandevice_version(tuple): Minimum pandevice version allowed.
min_panos_version(tuple): Minimum PAN-OS version allowed.
panorama_error(str): The error message if the device is Panorama. panorama_error(str): The error message if the device is Panorama.
firewall_error(str): The error message if the device is a firewall. firewall_error(str): The error message if the device is a firewall.
Returns: Returns:
ConnectionHelper ConnectionHelper
""" """
helper = ConnectionHelper(panorama_error, firewall_error) helper = ConnectionHelper(
min_pandevice_version, min_panos_version,
panorama_error, firewall_error)
req = [] req = []
spec = { spec = {
'provider': { 'provider': {
@ -233,11 +298,12 @@ def get_connection(vsys=None, device_group=None,
'username': {'default': 'admin'}, 'username': {'default': 'admin'},
'password': {'no_log': True}, 'password': {'no_log': True},
'api_key': {'no_log': True}, 'api_key': {'no_log': True},
'port': {'default': 443, 'type': 'int'},
}, },
}, },
} }
if classic_provider_spec: if with_classic_provider_spec:
spec['provider']['required'] = False spec['provider']['required'] = False
spec['provider']['options']['host']['required'] = False spec['provider']['options']['host']['required'] = False
del(spec['provider']['required_one_of']) del(spec['provider']['required_one_of'])
@ -246,12 +312,19 @@ def get_connection(vsys=None, device_group=None,
'username': {'default': 'admin'}, 'username': {'default': 'admin'},
'password': {'no_log': True}, 'password': {'no_log': True},
'api_key': {'no_log': True}, 'api_key': {'no_log': True},
'port': {'default': 443, 'type': 'int'},
}) })
req.extend([ req.extend([
['provider', 'ip_address'], ['provider', 'ip_address'],
['provider', 'password', 'api_key'], ['provider', 'password', 'api_key'],
]) ])
if with_state:
spec['state'] = {
'default': 'present',
'choices': ['present', 'absent'],
}
if vsys_dg is not None: if vsys_dg is not None:
if isinstance(vsys_dg, bool): if isinstance(vsys_dg, bool):
param = 'vsys_dg' param = 'vsys_dg'
@ -275,6 +348,8 @@ def get_connection(vsys=None, device_group=None,
spec[param] = {'default': 'shared'} spec[param] = {'default': 'shared'}
helper.device_group = param helper.device_group = param
if vsys_importable is not None: if vsys_importable is not None:
if vsys is not None:
raise KeyError('Define "vsys" or "vsys_importable", not both.')
if isinstance(vsys_importable, bool): if isinstance(vsys_importable, bool):
param = 'vsys' param = 'vsys'
else: else:
@ -288,8 +363,8 @@ def get_connection(vsys=None, device_group=None,
else: else:
param = rulebase param = rulebase
spec[param] = { spec[param] = {
'default': 'pre-rulebase', 'default': None,
'choices': ['pre-rulebase', 'post-rulebase'], 'choices': ['pre-rulebase', 'rulebase', 'post-rulebase'],
} }
helper.rulebase = param helper.rulebase = param
@ -309,6 +384,15 @@ def get_connection(vsys=None, device_group=None,
spec[param] = {} spec[param] = {}
helper.template_stack = param helper.template_stack = param
if argument_spec is not None:
for k in argument_spec.keys():
if k in spec:
raise KeyError('{0}: key used by connection helper.'.format(k))
spec[k] = argument_spec[k]
if required_one_of is not None:
req.extend(required_one_of)
# Done. # Done.
helper.argument_spec = spec helper.argument_spec = spec
helper.required_one_of = req helper.required_one_of = req