PyPANRestV2 2.1.2__tar.gz → 2.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/PKG-INFO +2 -4
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/Panorama.py +242 -52
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pyproject.toml +1 -1
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/README.md +0 -0
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/ApplicationHelper.py +0 -0
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/Base.py +0 -0
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/Device.py +0 -0
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/Exceptions.py +0 -0
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/Network.py +0 -0
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/Objects.py +0 -0
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/Policies.py +0 -0
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/XDR.py +0 -0
- {pypanrestv2-2.1.2 → pypanrestv2-2.1.4}/pypanrestv2/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: PyPANRestV2
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4
|
|
4
4
|
Summary: Python tools for interacting with Palo Alto Networks REST API.
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Mark Rzepa
|
|
@@ -10,8 +10,6 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
15
13
|
Requires-Dist: dnspython (>=2.6.1)
|
|
16
14
|
Requires-Dist: icecream (>=2.1.3)
|
|
17
15
|
Requires-Dist: pycountry (>=23.12.11)
|
|
@@ -66,51 +66,216 @@ class Templates(PanoramaTab):
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
class TemplateStacks(PanoramaTab):
|
|
69
|
-
|
|
70
|
-
'as-number', 'qos-profiles', 'egress-max', 'link-tag']
|
|
69
|
+
"""Represents a Panorama template stack and its templates, devices, and variables.
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
self.templates: Dict = {'member': []}
|
|
75
|
-
self.devices: Dict = {'entry': []}
|
|
76
|
-
self.variable: Dict = {'entry': []}
|
|
71
|
+
This class models the JSON structure used by the Panorama REST API for
|
|
72
|
+
template stacks, including:
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
- Stack-level templates under ``templates.member``.
|
|
75
|
+
- Devices assigned to the stack under ``devices.entry``.
|
|
76
|
+
- Stack-level variable definitions under ``variable.entry``.
|
|
77
|
+
- Per-device variable assignments under
|
|
78
|
+
``devices.entry[*].variable.entry``.
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
Stack-level variables
|
|
81
|
+
---------------------
|
|
82
|
+
Stack-level variables describe which variables are available to the
|
|
83
|
+
template stack and what *type* they are. Each definition looks like::
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
name:
|
|
89
|
-
|
|
85
|
+
{
|
|
86
|
+
"@name": "$mgmt_ip",
|
|
87
|
+
"type": {"ip-netmask": "0.0.0.0/0"}
|
|
88
|
+
}
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
and is stored inside ``self.variable['entry']`` and serialized as the
|
|
91
|
+
``variable`` block on the template stack.
|
|
92
|
+
|
|
93
|
+
Per-device variables
|
|
94
|
+
--------------------
|
|
95
|
+
Devices are stored in ``self.devices['entry']``. Each device can contain
|
|
96
|
+
a per-device ``variable`` block that assigns values to the stack-level
|
|
97
|
+
variables for that device::
|
|
98
|
+
|
|
99
|
+
{
|
|
100
|
+
"@name": "0123456789", # device serial
|
|
101
|
+
"variable": {
|
|
102
|
+
"entry": [
|
|
103
|
+
{
|
|
104
|
+
"@name": "$mgmt_ip",
|
|
105
|
+
"type": {"ip-netmask": "10.0.0.1/32"}
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Typical usage
|
|
112
|
+
-------------
|
|
113
|
+
|
|
114
|
+
Create or load a template stack::
|
|
115
|
+
|
|
116
|
+
ts = TemplateStacks(panorama, name="Branch-Stack")
|
|
117
|
+
ts.refresh() # optional, to pull live data
|
|
118
|
+
|
|
119
|
+
Define variables at the stack level::
|
|
120
|
+
|
|
121
|
+
ts.update_variable("$mgmt_ip", "ip-netmask", "0.0.0.0/0")
|
|
122
|
+
ts.update_variable("$hostname", "hostname", "default-host")
|
|
123
|
+
ts.edit()
|
|
124
|
+
|
|
125
|
+
Add a device to the stack::
|
|
126
|
+
|
|
127
|
+
ts.add_device("0123456789")
|
|
128
|
+
ts.edit()
|
|
129
|
+
|
|
130
|
+
Set a device variable value (recommended entry point)::
|
|
131
|
+
|
|
132
|
+
ts.set_device_variable_value(
|
|
133
|
+
device_serial="0123456789",
|
|
134
|
+
variable_name="$mgmt_ip",
|
|
135
|
+
value="10.1.2.3/32",
|
|
136
|
+
)
|
|
137
|
+
ts.edit()
|
|
138
|
+
|
|
139
|
+
The :meth:`set_device_variable_value` helper only requires the device
|
|
140
|
+
serial, variable name, and value. It automatically infers the variable
|
|
141
|
+
type from the stack-level definition and creates the device entry if it
|
|
142
|
+
does not already exist (unless disabled via
|
|
143
|
+
``create_device_if_missing=False``).
|
|
144
|
+
"""
|
|
145
|
+
variable_types = ['ip-netmask', 'ip-range', 'hostname', 'ipv4-subnet', 'ipv6-subnet', 'pre-shared-key',
|
|
146
|
+
'fqdn', 'group-id', 'device-priority', 'device-id', 'interface',
|
|
147
|
+
'as-number', 'qos-profile', 'egress-max', 'link-tag']
|
|
148
|
+
|
|
149
|
+
def __init__(self, PANDevice, **kwargs):
|
|
150
|
+
super().__init__(PANDevice, max_description_length=255, max_name_length=63, **kwargs)
|
|
151
|
+
self.templates: Dict = kwargs.get('templates', {'member': []})
|
|
152
|
+
self.devices: Dict = kwargs.get('devices', {'entry': []})
|
|
153
|
+
self.variable: Dict = kwargs.get('variable', {'entry': []})
|
|
154
|
+
|
|
155
|
+
def _ensure_devices_container(self) -> None:
|
|
156
|
+
if not isinstance(self.devices, dict) or 'entry' not in self.devices:
|
|
157
|
+
self.devices = {'entry': []}
|
|
158
|
+
if 'devices' not in self.entry:
|
|
159
|
+
self.entry['devices'] = self.devices
|
|
160
|
+
|
|
161
|
+
def _ensure_device_variables_container(self, device_entry: Dict[str, Any]) -> None:
|
|
162
|
+
if 'variable' not in device_entry or not isinstance(device_entry['variable'], dict):
|
|
163
|
+
device_entry['variable'] = {'entry': []}
|
|
164
|
+
|
|
165
|
+
def add_device(self, name: str, variables: Optional[Dict] = None) -> bool:
|
|
166
|
+
self._ensure_devices_container()
|
|
167
|
+
|
|
168
|
+
if variables is not None:
|
|
169
|
+
# variables must be a variable dict: {'entry': [ ... ]}
|
|
170
|
+
if not self.validate_variable_structure(variables):
|
|
100
171
|
logger.debug(f"Invalid variable structure for device {name}. Not adding.")
|
|
101
|
-
logger.debug(f"Variables provided: {
|
|
172
|
+
logger.debug(f"Variables provided: {variables}")
|
|
102
173
|
return False
|
|
174
|
+
device_entry: Dict[str, Any] = {'@name': name, 'variable': variables}
|
|
103
175
|
else:
|
|
104
|
-
# Handle case where no variables are provided
|
|
105
|
-
logger.debug(f"Adding device {name} without variables to template stack {self.name}")
|
|
106
176
|
device_entry = {'@name': name}
|
|
107
177
|
|
|
108
|
-
# Add the device entry to the devices list
|
|
109
178
|
self.devices['entry'].append(device_entry)
|
|
110
179
|
self.entry['devices'] = self.devices
|
|
111
180
|
|
|
112
181
|
return True
|
|
113
182
|
|
|
183
|
+
def remove_device(self, name: str) -> bool:
|
|
184
|
+
self._ensure_devices_container()
|
|
185
|
+
for idx, device_entry in enumerate(self.devices['entry']):
|
|
186
|
+
if device_entry.get('@name') == name:
|
|
187
|
+
del self.devices['entry'][idx]
|
|
188
|
+
self.entry['devices'] = self.devices
|
|
189
|
+
return True
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
def set_device_variable_value(
|
|
193
|
+
self,
|
|
194
|
+
device_serial: str,
|
|
195
|
+
variable_name: str,
|
|
196
|
+
value: str,
|
|
197
|
+
create_device_if_missing: bool = True,
|
|
198
|
+
) -> None:
|
|
199
|
+
"""Set a per-device variable value using only serial, name, and value.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
device_serial:
|
|
204
|
+
Serial number of the device in the template stack.
|
|
205
|
+
variable_name:
|
|
206
|
+
Name of the variable (for example ``"$mgmt_ip"``). The variable
|
|
207
|
+
must already be defined at the stack level via
|
|
208
|
+
:meth:`update_variable`.
|
|
209
|
+
value:
|
|
210
|
+
Value to assign to this variable for the specified device.
|
|
211
|
+
create_device_if_missing:
|
|
212
|
+
If ``True`` (default), a new device entry is added to
|
|
213
|
+
``devices.entry`` if one with ``@name == device_serial`` does not
|
|
214
|
+
already exist. If ``False``, the method will only update existing
|
|
215
|
+
devices.
|
|
216
|
+
|
|
217
|
+
Notes
|
|
218
|
+
-----
|
|
219
|
+
The method looks up ``variable_name`` in the stack-level
|
|
220
|
+
``self.variable['entry']`` block to determine the correct variable
|
|
221
|
+
*type* (for example ``"ip-netmask"`` or ``"hostname"``). It then
|
|
222
|
+
delegates to :meth:`update_device_variable` to create or update the
|
|
223
|
+
per-device variable entry under
|
|
224
|
+
``devices.entry[*].variable.entry``.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
# Infer the variable type (from stack-level block or existing devices)
|
|
228
|
+
var_type_key = self._infer_variable_type(variable_name)
|
|
229
|
+
|
|
230
|
+
if not var_type_key:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"Variable definition {variable_name!r} not found for template stack {self.name}; "
|
|
233
|
+
"define it at the stack level or ensure another device has it assigned."
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Optionally create the device entry if it does not exist yet
|
|
237
|
+
device_exists = any(
|
|
238
|
+
isinstance(d, dict) and d.get('@name') == device_serial
|
|
239
|
+
for d in self.devices.get('entry', [])
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if not device_exists and create_device_if_missing:
|
|
243
|
+
self.add_device(device_serial)
|
|
244
|
+
|
|
245
|
+
# Delegate to the lower-level helper that knows about types
|
|
246
|
+
self.update_device_variable(device_serial, variable_name, var_type_key, value)
|
|
247
|
+
|
|
248
|
+
def _infer_variable_type(self, variable_name: str) -> Optional[str]:
|
|
249
|
+
"""Return the variable type key for a given variable name, if known.
|
|
250
|
+
|
|
251
|
+
The lookup order is:
|
|
252
|
+
|
|
253
|
+
1. Stack-level ``variable.entry`` definitions.
|
|
254
|
+
2. Existing device-level ``devices.entry[*].variable.entry`` blocks.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
# 1) Look at stack-level variable definitions, if present
|
|
258
|
+
if self.variable and isinstance(self.variable, dict) and 'entry' in self.variable:
|
|
259
|
+
for var_def in self.variable.get('entry', []):
|
|
260
|
+
if var_def.get('@name') == variable_name and isinstance(var_def.get('type'), dict):
|
|
261
|
+
if var_def['type']:
|
|
262
|
+
return next(iter(var_def['type']))
|
|
263
|
+
|
|
264
|
+
# 2) Fallback: inspect variables from existing devices in the stack
|
|
265
|
+
if self.devices and isinstance(self.devices, dict):
|
|
266
|
+
for dev in self.devices.get('entry', []):
|
|
267
|
+
if not isinstance(dev, dict):
|
|
268
|
+
continue
|
|
269
|
+
dev_var = dev.get('variable')
|
|
270
|
+
if not isinstance(dev_var, dict):
|
|
271
|
+
continue
|
|
272
|
+
for var in dev_var.get('entry', []):
|
|
273
|
+
if var.get('@name') == variable_name and isinstance(var.get('type'), dict):
|
|
274
|
+
if var['type']:
|
|
275
|
+
return next(iter(var['type']))
|
|
276
|
+
|
|
277
|
+
return None
|
|
278
|
+
|
|
114
279
|
def update_variable(self, name: str, variable_type: str, variable_value: str):
|
|
115
280
|
if variable_type in self.variable_types:
|
|
116
281
|
variable_entry = {'@name': name, 'type': {variable_type: variable_value}}
|
|
@@ -119,27 +284,52 @@ class TemplateStacks(PanoramaTab):
|
|
|
119
284
|
|
|
120
285
|
def update_device_variable(self, device_name: str, variable_name: str, variable_type: str,
|
|
121
286
|
variable_value: str) -> None:
|
|
122
|
-
if variable_type in self.variable_types:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
'@name': variable_name,
|
|
139
|
-
'type': {variable_type: variable_value}
|
|
140
|
-
})
|
|
287
|
+
if variable_type not in self.variable_types:
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
self._ensure_devices_container()
|
|
291
|
+
|
|
292
|
+
for device_entry in self.devices['entry']:
|
|
293
|
+
if device_entry.get('@name') != device_name:
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
self._ensure_device_variables_container(device_entry)
|
|
297
|
+
|
|
298
|
+
variable_found = False
|
|
299
|
+
for var_entry in device_entry['variable']['entry']:
|
|
300
|
+
if var_entry.get('@name') == variable_name:
|
|
301
|
+
var_entry['type'] = {variable_type: variable_value}
|
|
302
|
+
variable_found = True
|
|
141
303
|
break
|
|
142
304
|
|
|
305
|
+
if not variable_found:
|
|
306
|
+
device_entry['variable']['entry'].append({
|
|
307
|
+
'@name': variable_name,
|
|
308
|
+
'type': {variable_type: variable_value}
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
self.entry['devices'] = self.devices
|
|
312
|
+
break
|
|
313
|
+
|
|
314
|
+
def remove_device_variable(self, device_name: str, variable_name: str) -> bool:
|
|
315
|
+
self._ensure_devices_container()
|
|
316
|
+
|
|
317
|
+
for device_entry in self.devices['entry']:
|
|
318
|
+
if device_entry.get('@name') != device_name:
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
if 'variable' not in device_entry or 'entry' not in device_entry['variable']:
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
for idx, var_entry in enumerate(device_entry['variable']['entry']):
|
|
325
|
+
if var_entry.get('@name') == variable_name:
|
|
326
|
+
del device_entry['variable']['entry'][idx]
|
|
327
|
+
self.entry['devices'] = self.devices
|
|
328
|
+
return True
|
|
329
|
+
return False
|
|
330
|
+
|
|
331
|
+
return False
|
|
332
|
+
|
|
143
333
|
def add_template_member(self, member):
|
|
144
334
|
self.templates['member'].append(member)
|
|
145
335
|
self.entry['templates'] = self.templates
|
|
@@ -208,7 +398,7 @@ class TemplateStacks(PanoramaTab):
|
|
|
208
398
|
def variable(self, value: Dict):
|
|
209
399
|
if self.validate_variable_structure(value):
|
|
210
400
|
self._variable = value
|
|
211
|
-
self.entry['
|
|
401
|
+
self.entry['variable'] = value
|
|
212
402
|
else:
|
|
213
403
|
raise ValueError("Invalid variable structure")
|
|
214
404
|
|
|
@@ -251,9 +441,9 @@ class TemplateStacks(PanoramaTab):
|
|
|
251
441
|
if not isinstance(devices['entry'], list):
|
|
252
442
|
return False
|
|
253
443
|
for item in devices['entry']:
|
|
254
|
-
if not isinstance(item, dict) or '@name' not in item
|
|
444
|
+
if not isinstance(item, dict) or '@name' not in item:
|
|
255
445
|
return False
|
|
256
|
-
if not self.validate_variable_structure(item['variable']):
|
|
446
|
+
if 'variable' in item and not self.validate_variable_structure(item['variable']):
|
|
257
447
|
return False
|
|
258
448
|
return True
|
|
259
449
|
|
|
@@ -3,7 +3,7 @@ requires = ["poetry-core>=1.0.0"]
|
|
|
3
3
|
build-backend = "poetry.core.masonry.api"
|
|
4
4
|
[tool.poetry]
|
|
5
5
|
name = "PyPANRestV2"
|
|
6
|
-
version = "2.1.
|
|
6
|
+
version = "2.1.4"
|
|
7
7
|
description = "Python tools for interacting with Palo Alto Networks REST API."
|
|
8
8
|
authors = [
|
|
9
9
|
"Mark Rzepa <mark@rzepa.com>"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|