catocli 2.1.4__py3-none-any.whl → 2.1.8__py3-none-any.whl
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.
Potentially problematic release.
This version of catocli might be problematic. Click here for more details.
- catocli/Utils/clidriver.py +10 -8
- catocli/Utils/cliutils.py +48 -5
- catocli/__init__.py +2 -2
- catocli/clisettings.json +35 -0
- catocli/parsers/custom/export_sites/__init__.py +14 -3
- catocli/parsers/custom/export_sites/export_sites.py +644 -12
- catocli/parsers/custom/import_sites_to_tf/__init__.py +44 -16
- catocli/parsers/custom/import_sites_to_tf/import_sites_to_tf.py +732 -395
- catocli/parsers/customParserApiClient.py +14 -7
- catocli/parsers/mutation_licensing/__init__.py +24 -0
- catocli/parsers/mutation_licensing_updateCommercialLicense/README.md +19 -0
- catocli/parsers/mutation_policy/__init__.py +509 -509
- catocli/parsers/query_devices/README.md +1 -1
- catocli/parsers/query_eventsFeed/README.md +1 -1
- catocli/parsers/query_policy/__init__.py +55 -55
- {catocli-2.1.4.dist-info → catocli-2.1.8.dist-info}/METADATA +1 -1
- {catocli-2.1.4.dist-info → catocli-2.1.8.dist-info}/RECORD +27 -23
- models/mutation.licensing.updateCommercialLicense.json +931 -0
- models/query.devices.json +199 -60
- models/query.events.json +216 -0
- models/query.eventsFeed.json +48 -0
- models/query.eventsTimeSeries.json +144 -0
- schema/catolib.py +3 -3
- {catocli-2.1.4.dist-info → catocli-2.1.8.dist-info}/WHEEL +0 -0
- {catocli-2.1.4.dist-info → catocli-2.1.8.dist-info}/entry_points.txt +0 -0
- {catocli-2.1.4.dist-info → catocli-2.1.8.dist-info}/licenses/LICENSE +0 -0
- {catocli-2.1.4.dist-info → catocli-2.1.8.dist-info}/top_level.txt +0 -0
|
@@ -3,6 +3,7 @@ import json
|
|
|
3
3
|
import traceback
|
|
4
4
|
import sys
|
|
5
5
|
import ipaddress
|
|
6
|
+
import csv
|
|
6
7
|
from datetime import datetime
|
|
7
8
|
from graphql_client.api.call_api import ApiClient, CallApi
|
|
8
9
|
from graphql_client.api_client import ApiException
|
|
@@ -40,6 +41,18 @@ def calculateLocalIp(subnet):
|
|
|
40
41
|
# Invalid subnet format
|
|
41
42
|
return None
|
|
42
43
|
|
|
44
|
+
def export_socket_sites_dispatcher(args, configuration):
|
|
45
|
+
"""
|
|
46
|
+
Dispatcher function that routes to JSON or CSV export based on format argument
|
|
47
|
+
"""
|
|
48
|
+
export_format = getattr(args, 'export_format', 'json')
|
|
49
|
+
|
|
50
|
+
if export_format == 'csv':
|
|
51
|
+
return export_socket_site_to_csv(args, configuration)
|
|
52
|
+
else:
|
|
53
|
+
return export_socket_site_to_json(args, configuration)
|
|
54
|
+
|
|
55
|
+
|
|
43
56
|
def export_socket_site_to_json(args, configuration):
|
|
44
57
|
"""
|
|
45
58
|
Export consolidated site and socket data to JSON format
|
|
@@ -55,8 +68,7 @@ def export_socket_site_to_json(args, configuration):
|
|
|
55
68
|
try:
|
|
56
69
|
# Load CLI settings using the robust function
|
|
57
70
|
settings = load_cli_settings()
|
|
58
|
-
if
|
|
59
|
-
raise ValueError("Unable to load clisettings.json. Cannot proceed with export.")
|
|
71
|
+
# Note: load_cli_settings() now returns embedded defaults if file cannot be loaded
|
|
60
72
|
|
|
61
73
|
account_id = getAccountID(args, configuration)
|
|
62
74
|
# Get account snapshot with siteIDs if provided
|
|
@@ -136,7 +148,12 @@ def export_socket_site_to_json(args, configuration):
|
|
|
136
148
|
cur_wan_interface['id'] = site_id+":"+ wan_ni.get('id', "")
|
|
137
149
|
else:
|
|
138
150
|
cur_wan_interface['id'] = site_id+":INT_"+ wan_ni.get('id', "")
|
|
139
|
-
|
|
151
|
+
# Format WAN interface index: INT_X for numeric values, keep as-is for non-numeric
|
|
152
|
+
wan_interface_id = wan_ni.get('id', "")
|
|
153
|
+
if isinstance(wan_interface_id, (int, str)) and str(wan_interface_id).isdigit():
|
|
154
|
+
cur_wan_interface['index'] = f"INT_{wan_interface_id}"
|
|
155
|
+
else:
|
|
156
|
+
cur_wan_interface['index'] = wan_interface_id
|
|
140
157
|
cur_wan_interface['name'] = wan_ni.get('name', "")
|
|
141
158
|
cur_wan_interface['upstream_bandwidth'] = wan_ni.get('upstreamBandwidth', 0)
|
|
142
159
|
cur_wan_interface['downstream_bandwidth'] = wan_ni.get('downstreamBandwidth', 0)
|
|
@@ -174,7 +191,7 @@ def export_socket_site_to_json(args, configuration):
|
|
|
174
191
|
cur_site_entry["native_range"]["subnet"] = lan_ni_subnet
|
|
175
192
|
cur_site_entry["native_range"]["index"] = ni_index
|
|
176
193
|
# Add entry to lan interfaces for default_lan
|
|
177
|
-
cur_site_entry['lan_interfaces'].append({"network_ranges": [],"default_lan":True})
|
|
194
|
+
cur_site_entry['lan_interfaces'].append({"network_ranges": [],"default_lan":True})
|
|
178
195
|
else:
|
|
179
196
|
cur_lan_interface['id'] = ni_interface_id
|
|
180
197
|
cur_lan_interface['name'] = ni_interface_name
|
|
@@ -185,7 +202,9 @@ def export_socket_site_to_json(args, configuration):
|
|
|
185
202
|
cur_site_entry['lan_interfaces'].append(cur_lan_interface)
|
|
186
203
|
else:
|
|
187
204
|
if hasattr(args, 'verbose') and args.verbose:
|
|
188
|
-
|
|
205
|
+
ni_interface_name = lan_ni_helper_fields.get('interfaceName', "")
|
|
206
|
+
ni_interface_id = lan_ni_entity_data.get('id', "")
|
|
207
|
+
print(f"WARNING: Site {lan_ni_site_id} not found in snapshot data, skipping interface {ni_interface_name} ({ni_interface_id})")
|
|
189
208
|
|
|
190
209
|
#############################################################################
|
|
191
210
|
## Process entity lookup network ranges populating by network interface id ##
|
|
@@ -218,7 +237,7 @@ def export_socket_site_to_json(args, configuration):
|
|
|
218
237
|
nr_relay_group_name = nr_helper_fields.get('XXXXX', None)
|
|
219
238
|
nr_gateway = nr_helper_fields.get('XXXXX', None)
|
|
220
239
|
nr_translated_subnet = nr_helper_fields.get('XXXXX', None)
|
|
221
|
-
|
|
240
|
+
nr_internet_only = nr_helper_fields.get('XXXXX', None) # Default to None for JSON
|
|
222
241
|
nr_local_ip = nr_helper_fields.get('XXXXX', None)
|
|
223
242
|
nr_range_type = nr_helper_fields.get('XXXXX', None)
|
|
224
243
|
# Adding logic to pre-populate with default value
|
|
@@ -252,8 +271,7 @@ def export_socket_site_to_json(args, configuration):
|
|
|
252
271
|
cur_range['gateway'] = nr_gateway
|
|
253
272
|
cur_range['range_type'] = nr_range_type
|
|
254
273
|
cur_range['translated_subnet'] = nr_translated_subnet
|
|
255
|
-
|
|
256
|
-
# cur_range['internet_only'] = nr_internet_only
|
|
274
|
+
cur_range['internet_only'] = nr_internet_only
|
|
257
275
|
cur_range['local_ip'] = nr_local_ip # Use the calculated or original value
|
|
258
276
|
cur_range['dhcp_settings'] = {
|
|
259
277
|
'dhcp_type': nr_dhcp_type,
|
|
@@ -272,8 +290,7 @@ def export_socket_site_to_json(args, configuration):
|
|
|
272
290
|
site_native_range['gateway'] = nr_gateway
|
|
273
291
|
site_native_range['range_type'] = nr_range_type
|
|
274
292
|
site_native_range['translated_subnet'] = nr_translated_subnet
|
|
275
|
-
|
|
276
|
-
# site_native_range['internet_only'] = nr_internet_only
|
|
293
|
+
site_native_range['internet_only'] = nr_internet_only
|
|
277
294
|
site_native_range['local_ip'] = nr_local_ip
|
|
278
295
|
site_native_range['dhcp_settings'] = {
|
|
279
296
|
'dhcp_type': nr_dhcp_type,
|
|
@@ -295,8 +312,7 @@ def export_socket_site_to_json(args, configuration):
|
|
|
295
312
|
cur_range['gateway'] = nr_gateway
|
|
296
313
|
cur_range['range_type'] = nr_range_type
|
|
297
314
|
cur_range['translated_subnet'] = nr_translated_subnet
|
|
298
|
-
|
|
299
|
-
# cur_range['internet_only'] = nr_internet_only
|
|
315
|
+
cur_range['internet_only'] = nr_internet_only
|
|
300
316
|
cur_range['local_ip'] = nr_local_ip # Use the calculated or original value
|
|
301
317
|
cur_range['dhcp_settings'] = {
|
|
302
318
|
'dhcp_type': nr_dhcp_type,
|
|
@@ -390,6 +406,622 @@ def export_socket_site_to_json(args, configuration):
|
|
|
390
406
|
return [{"success": False, "error": str(e), "error_details": error_details}]
|
|
391
407
|
|
|
392
408
|
|
|
409
|
+
def export_socket_site_to_csv(args, configuration):
|
|
410
|
+
"""
|
|
411
|
+
Export consolidated site and socket data to CSV format
|
|
412
|
+
Creates main sites CSV and individual network ranges CSV files
|
|
413
|
+
"""
|
|
414
|
+
try:
|
|
415
|
+
# First get the JSON data using existing function
|
|
416
|
+
json_result = export_socket_site_to_json(args, configuration)
|
|
417
|
+
|
|
418
|
+
if not json_result or not json_result[0].get('success'):
|
|
419
|
+
return json_result
|
|
420
|
+
|
|
421
|
+
# Load the processed data from the JSON function
|
|
422
|
+
# We'll re-use the data processing logic but output to CSV instead
|
|
423
|
+
processed_data = get_processed_site_data(args, configuration)
|
|
424
|
+
|
|
425
|
+
if not processed_data or not processed_data.get('sites'):
|
|
426
|
+
return [{"success": False, "error": "No sites data found to export"}]
|
|
427
|
+
|
|
428
|
+
account_id = getAccountID(args, configuration)
|
|
429
|
+
output_files = []
|
|
430
|
+
|
|
431
|
+
# Export main sites CSV
|
|
432
|
+
sites_csv_file = export_sites_to_csv(processed_data['sites'], args, account_id)
|
|
433
|
+
output_files.append(sites_csv_file)
|
|
434
|
+
|
|
435
|
+
# Export individual network ranges CSV files for each site
|
|
436
|
+
for site in processed_data['sites']:
|
|
437
|
+
ranges_csv_file = export_network_ranges_to_csv(site, args, account_id)
|
|
438
|
+
if ranges_csv_file:
|
|
439
|
+
output_files.append(ranges_csv_file)
|
|
440
|
+
|
|
441
|
+
return [{"success": True, "output_files": output_files, "account_id": account_id}]
|
|
442
|
+
|
|
443
|
+
except Exception as e:
|
|
444
|
+
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
445
|
+
line_number = exc_traceback.tb_lineno
|
|
446
|
+
filename = exc_traceback.tb_frame.f_code.co_filename
|
|
447
|
+
function_name = exc_traceback.tb_frame.f_code.co_name
|
|
448
|
+
full_traceback = traceback.format_exc()
|
|
449
|
+
|
|
450
|
+
error_details = {
|
|
451
|
+
"error_type": exc_type.__name__,
|
|
452
|
+
"error_message": str(exc_value),
|
|
453
|
+
"line_number": line_number,
|
|
454
|
+
"function_name": function_name,
|
|
455
|
+
"filename": os.path.basename(filename),
|
|
456
|
+
"full_traceback": full_traceback
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
print(f"ERROR: {exc_type.__name__}: {str(exc_value)}")
|
|
460
|
+
print(f"Location: {os.path.basename(filename)}:{line_number} in {function_name}()")
|
|
461
|
+
print(f"Full traceback:\n{full_traceback}")
|
|
462
|
+
|
|
463
|
+
return [{"success": False, "error": str(e), "error_details": error_details}]
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def get_processed_site_data(args, configuration):
|
|
467
|
+
"""
|
|
468
|
+
Get processed site data without writing to JSON file
|
|
469
|
+
Reuses the logic from export_socket_site_to_json but returns data instead
|
|
470
|
+
"""
|
|
471
|
+
processed_data = {'sites': []}
|
|
472
|
+
|
|
473
|
+
# Load CLI settings
|
|
474
|
+
settings = load_cli_settings()
|
|
475
|
+
# Note: load_cli_settings() now returns embedded defaults if file cannot be loaded
|
|
476
|
+
|
|
477
|
+
account_id = getAccountID(args, configuration)
|
|
478
|
+
|
|
479
|
+
# Get siteIDs from args if provided
|
|
480
|
+
site_ids = []
|
|
481
|
+
if hasattr(args, 'siteIDs') and args.siteIDs:
|
|
482
|
+
site_ids = [site_id.strip() for site_id in args.siteIDs.split(',') if site_id.strip()]
|
|
483
|
+
|
|
484
|
+
# Call APIs to retrieve sites, interface and network ranges
|
|
485
|
+
snapshot_sites = getAccountSnapshot(args, configuration, account_id, site_ids)
|
|
486
|
+
sites_list = snapshot_sites['data']['accountSnapshot']['sites']
|
|
487
|
+
|
|
488
|
+
if not sites_list or len(sites_list) == 0:
|
|
489
|
+
return processed_data
|
|
490
|
+
|
|
491
|
+
entity_network_interfaces = getEntityLookup(args, configuration, account_id, "networkInterface")
|
|
492
|
+
entity_network_ranges = getEntityLookup(args, configuration, account_id, "siteRange")
|
|
493
|
+
entity_sites = getEntityLookup(args, configuration, account_id, "site")
|
|
494
|
+
|
|
495
|
+
# Process sites (reuse existing logic)
|
|
496
|
+
for snapshot_site in snapshot_sites['data']['accountSnapshot']['sites']:
|
|
497
|
+
site_id = snapshot_site.get('id')
|
|
498
|
+
connectionType = snapshot_site.get('infoSiteSnapshot', {}).get('connType', "")
|
|
499
|
+
|
|
500
|
+
cur_site = {
|
|
501
|
+
'wan_interfaces': [],
|
|
502
|
+
'lan_interfaces': [],
|
|
503
|
+
'native_range': {}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if connectionType in settings["export_by_socket_type"]:
|
|
507
|
+
cur_site['id'] = site_id
|
|
508
|
+
cur_site['name'] = snapshot_site.get('infoSiteSnapshot', {}).get('name')
|
|
509
|
+
cur_site['description'] = snapshot_site.get('infoSiteSnapshot', {}).get('description')
|
|
510
|
+
cur_site['connection_type'] = connectionType
|
|
511
|
+
cur_site['type'] = snapshot_site.get('infoSiteSnapshot', {}).get('type')
|
|
512
|
+
cur_site = populateSiteLocationData(args, snapshot_site, cur_site)
|
|
513
|
+
|
|
514
|
+
# Process WAN interfaces
|
|
515
|
+
site_interfaces = snapshot_site.get('infoSiteSnapshot', {}).get('interfaces', [])
|
|
516
|
+
for wan_ni in site_interfaces:
|
|
517
|
+
cur_wan_interface = {}
|
|
518
|
+
role = wan_ni.get('wanRoleInterfaceInfo', "")
|
|
519
|
+
interfaceName = wan_ni.get('id', "")
|
|
520
|
+
if role is not None and role[0:3] == "wan":
|
|
521
|
+
if interfaceName[0:3] in ("WAN", "USB", "LTE"):
|
|
522
|
+
cur_wan_interface['id'] = site_id+":"+ wan_ni.get('id', "")
|
|
523
|
+
else:
|
|
524
|
+
cur_wan_interface['id'] = site_id+":INT_"+ wan_ni.get('id', "")
|
|
525
|
+
# Format WAN interface index: INT_X for numeric values, keep as-is for non-numeric
|
|
526
|
+
wan_interface_id = wan_ni.get('id', "")
|
|
527
|
+
if isinstance(wan_interface_id, (int, str)) and str(wan_interface_id).isdigit():
|
|
528
|
+
cur_wan_interface['index'] = f"INT_{wan_interface_id}"
|
|
529
|
+
else:
|
|
530
|
+
cur_wan_interface['index'] = wan_interface_id
|
|
531
|
+
cur_wan_interface['name'] = wan_ni.get('name', "")
|
|
532
|
+
cur_wan_interface['upstream_bandwidth'] = wan_ni.get('upstreamBandwidth', 0)
|
|
533
|
+
cur_wan_interface['downstream_bandwidth'] = wan_ni.get('downstreamBandwidth', 0)
|
|
534
|
+
cur_wan_interface['dest_type'] = wan_ni.get('destType', "")
|
|
535
|
+
cur_wan_interface['role'] = role
|
|
536
|
+
cur_wan_interface['precedence'] = "ACTIVE"
|
|
537
|
+
cur_site['wan_interfaces'].append(cur_wan_interface)
|
|
538
|
+
|
|
539
|
+
if site_id:
|
|
540
|
+
processed_data['sites'].append(cur_site)
|
|
541
|
+
|
|
542
|
+
# Process LAN interfaces (reuse existing logic)
|
|
543
|
+
for lan_ni in entity_network_interfaces:
|
|
544
|
+
lan_ni_helper_fields = lan_ni.get("helperFields", {})
|
|
545
|
+
lan_ni_entity_data = lan_ni.get('entity', {})
|
|
546
|
+
lan_ni_site_id = str(lan_ni_helper_fields.get('siteId', ""))
|
|
547
|
+
cur_site_entry = next((site for site in processed_data['sites'] if site['id'] == lan_ni_site_id), None)
|
|
548
|
+
if cur_site_entry:
|
|
549
|
+
cur_lan_interface = {'network_ranges': []}
|
|
550
|
+
ni_interface_id = lan_ni_entity_data.get('id', "")
|
|
551
|
+
ni_interface_name = lan_ni_helper_fields.get('interfaceName', "")
|
|
552
|
+
lan_ni_subnet = str(lan_ni_helper_fields.get('subnet', ""))
|
|
553
|
+
ni_index = lan_ni_helper_fields.get('interfaceId', "")
|
|
554
|
+
ni_index = f"INT_{ni_index}" if isinstance(ni_index, (int, str)) and str(ni_index).isdigit() else ni_index
|
|
555
|
+
|
|
556
|
+
if cur_site_entry["connection_type"] in settings["default_socket_interface_map"] and ni_index in settings["default_socket_interface_map"][cur_site_entry["connection_type"]]:
|
|
557
|
+
cur_site_entry["native_range"]["interface_id"] = ni_interface_id
|
|
558
|
+
cur_site_entry["native_range"]["interface_name"] = ni_interface_name
|
|
559
|
+
cur_site_entry["native_range"]["subnet"] = lan_ni_subnet
|
|
560
|
+
cur_site_entry["native_range"]["index"] = ni_index
|
|
561
|
+
cur_site_entry['lan_interfaces'].append({"network_ranges": [], "default_lan": True})
|
|
562
|
+
else:
|
|
563
|
+
cur_lan_interface['id'] = ni_interface_id
|
|
564
|
+
cur_lan_interface['name'] = ni_interface_name
|
|
565
|
+
cur_lan_interface['index'] = ni_index
|
|
566
|
+
cur_lan_interface['dest_type'] = lan_ni_helper_fields.get('destType', "")
|
|
567
|
+
cur_lan_interface['subnet'] = lan_ni_subnet
|
|
568
|
+
cur_site_entry['lan_interfaces'].append(cur_lan_interface)
|
|
569
|
+
|
|
570
|
+
# Process network ranges (reuse existing logic)
|
|
571
|
+
for range in entity_network_ranges:
|
|
572
|
+
nr_helper_fields = range.get("helperFields", {})
|
|
573
|
+
nr_entity_data = range.get('entity', {})
|
|
574
|
+
nr_interface_name = str(nr_helper_fields.get('interfaceName', ""))
|
|
575
|
+
nr_site_id = str(nr_helper_fields.get('siteId', ""))
|
|
576
|
+
range_id = nr_entity_data.get('id', "")
|
|
577
|
+
|
|
578
|
+
nr_site_entry = next((site for site in processed_data['sites'] if site['id'] == nr_site_id), None)
|
|
579
|
+
if nr_site_entry:
|
|
580
|
+
nr_subnet = nr_helper_fields.get('subnet', None)
|
|
581
|
+
nr_vlan = nr_helper_fields.get('vlanTag', None)
|
|
582
|
+
nr_mdns_reflector = nr_helper_fields.get('mdnsReflector', False)
|
|
583
|
+
nr_dhcp_microsegmentation = nr_helper_fields.get('microsegmentation', False)
|
|
584
|
+
range_name = nr_entity_data.get('name', nr_interface_name)
|
|
585
|
+
if range_name and " \\ " in range_name:
|
|
586
|
+
range_name = range_name.split(" \\ ").pop()
|
|
587
|
+
|
|
588
|
+
# Set defaults for missing fields
|
|
589
|
+
nr_dhcp_type = "DHCP_DISABLED"
|
|
590
|
+
nr_ip_range = None
|
|
591
|
+
nr_relay_group_name = None
|
|
592
|
+
nr_gateway = None
|
|
593
|
+
nr_translated_subnet = None
|
|
594
|
+
nr_internet_only = None # Default to None for JSON
|
|
595
|
+
nr_local_ip = None
|
|
596
|
+
nr_range_type = "VLAN" if nr_vlan != None else "Direct"
|
|
597
|
+
|
|
598
|
+
# Calculate local IP if requested
|
|
599
|
+
if hasattr(args, 'calculate_local_ip') and args.calculate_local_ip and nr_subnet:
|
|
600
|
+
calculated_ip = calculateLocalIp(nr_subnet)
|
|
601
|
+
if calculated_ip:
|
|
602
|
+
nr_local_ip = calculated_ip
|
|
603
|
+
|
|
604
|
+
site_native_range = nr_site_entry.get('native_range', {})
|
|
605
|
+
|
|
606
|
+
if site_native_range.get("interface_name", "") == nr_interface_name:
|
|
607
|
+
if range_name != "Native Range":
|
|
608
|
+
nr_lan_interface_entry = next((lan_nic for lan_nic in nr_site_entry["lan_interfaces"] if 'default_lan' in lan_nic and lan_nic['default_lan']), None)
|
|
609
|
+
if nr_lan_interface_entry:
|
|
610
|
+
cur_range = {
|
|
611
|
+
'id': range_id, 'name': range_name, 'subnet': nr_subnet, 'vlan': nr_vlan,
|
|
612
|
+
'mdns_reflector': nr_mdns_reflector, 'gateway': nr_gateway, 'range_type': nr_range_type,
|
|
613
|
+
'translated_subnet': nr_translated_subnet, 'internet_only': nr_internet_only, 'local_ip': nr_local_ip,
|
|
614
|
+
'dhcp_settings': {
|
|
615
|
+
'dhcp_type': nr_dhcp_type, 'ip_range': nr_ip_range, 'relay_group_id': None,
|
|
616
|
+
'relay_group_name': nr_relay_group_name, 'dhcp_microsegmentation': nr_dhcp_microsegmentation
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
nr_lan_interface_entry["network_ranges"].append(cur_range)
|
|
620
|
+
else:
|
|
621
|
+
site_native_range['range_name'] = range_name
|
|
622
|
+
site_native_range['range_id'] = range_id
|
|
623
|
+
site_native_range['vlan'] = nr_vlan
|
|
624
|
+
site_native_range['mdns_reflector'] = nr_mdns_reflector
|
|
625
|
+
site_native_range['gateway'] = nr_gateway
|
|
626
|
+
site_native_range['range_type'] = nr_range_type
|
|
627
|
+
site_native_range['translated_subnet'] = nr_translated_subnet
|
|
628
|
+
site_native_range['internet_only'] = nr_internet_only
|
|
629
|
+
site_native_range['local_ip'] = nr_local_ip
|
|
630
|
+
site_native_range['dhcp_settings'] = {
|
|
631
|
+
'dhcp_type': nr_dhcp_type, 'ip_range': nr_ip_range, 'relay_group_id': None,
|
|
632
|
+
'relay_group_name': nr_relay_group_name, 'dhcp_microsegmentation': nr_dhcp_microsegmentation
|
|
633
|
+
}
|
|
634
|
+
else:
|
|
635
|
+
nr_lan_interface_entry = next((lan_nic for lan_nic in nr_site_entry["lan_interfaces"] if ('default_lan' not in lan_nic or not lan_nic['default_lan']) and lan_nic['name'] == nr_interface_name), None)
|
|
636
|
+
if nr_lan_interface_entry:
|
|
637
|
+
cur_range = {
|
|
638
|
+
'id': range_id, 'name': range_name, 'subnet': nr_subnet, 'vlan': nr_vlan,
|
|
639
|
+
'mdns_reflector': nr_mdns_reflector, 'gateway': nr_gateway, 'range_type': nr_range_type,
|
|
640
|
+
'translated_subnet': nr_translated_subnet, 'internet_only': nr_internet_only, 'local_ip': nr_local_ip,
|
|
641
|
+
'dhcp_settings': {
|
|
642
|
+
'dhcp_type': nr_dhcp_type, 'ip_range': nr_ip_range, 'relay_group_id': None,
|
|
643
|
+
'relay_group_name': nr_relay_group_name, 'dhcp_microsegmentation': nr_dhcp_microsegmentation
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if "subnet" in nr_lan_interface_entry and nr_subnet == nr_lan_interface_entry["subnet"]:
|
|
648
|
+
cur_range['native_range'] = True
|
|
649
|
+
del nr_lan_interface_entry["subnet"]
|
|
650
|
+
|
|
651
|
+
nr_lan_interface_entry["network_ranges"].append(cur_range)
|
|
652
|
+
|
|
653
|
+
return processed_data
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
def export_sites_to_csv(sites, args, account_id):
|
|
657
|
+
"""
|
|
658
|
+
Export main sites data to CSV file in bulk-sites format
|
|
659
|
+
One row per WAN interface, site attributes only on first row per site
|
|
660
|
+
"""
|
|
661
|
+
# Handle custom filename and timestamp
|
|
662
|
+
if hasattr(args, 'csv_filename') and args.csv_filename:
|
|
663
|
+
base_filename = args.csv_filename
|
|
664
|
+
if base_filename.endswith('.csv'):
|
|
665
|
+
base_filename = base_filename[:-4]
|
|
666
|
+
|
|
667
|
+
if hasattr(args, 'append_timestamp') and args.append_timestamp:
|
|
668
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
669
|
+
filename_template = f"{base_filename}_{timestamp}.csv"
|
|
670
|
+
else:
|
|
671
|
+
filename_template = f"{base_filename}.csv"
|
|
672
|
+
else:
|
|
673
|
+
if hasattr(args, 'append_timestamp') and args.append_timestamp:
|
|
674
|
+
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
675
|
+
filename_template = f"socket_sites_{{account_id}}_{timestamp}.csv"
|
|
676
|
+
else:
|
|
677
|
+
filename_template = "socket_sites_{account_id}.csv"
|
|
678
|
+
|
|
679
|
+
# Replace account_id placeholder
|
|
680
|
+
filename = filename_template.format(account_id=account_id)
|
|
681
|
+
|
|
682
|
+
# Determine output directory
|
|
683
|
+
output_dir = getattr(args, 'output_directory', None)
|
|
684
|
+
if not output_dir:
|
|
685
|
+
output_dir = 'config_data'
|
|
686
|
+
if not os.path.isabs(output_dir):
|
|
687
|
+
output_dir = os.path.join(os.getcwd(), output_dir)
|
|
688
|
+
|
|
689
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
690
|
+
filepath = os.path.join(output_dir, filename)
|
|
691
|
+
|
|
692
|
+
# Define CSV headers matching bulk-sites.csv format
|
|
693
|
+
headers = [
|
|
694
|
+
'site_id',
|
|
695
|
+
'site_name',
|
|
696
|
+
'wan_interface_id',
|
|
697
|
+
'wan_interface_index',
|
|
698
|
+
'wan_interface_name',
|
|
699
|
+
'wan_upstream_bw',
|
|
700
|
+
'wan_downstream_bw',
|
|
701
|
+
'wan_role',
|
|
702
|
+
'wan_precedence',
|
|
703
|
+
'site_description',
|
|
704
|
+
'native_range_subnet',
|
|
705
|
+
'native_range_name',
|
|
706
|
+
'native_range_id',
|
|
707
|
+
'native_range_vlan',
|
|
708
|
+
'native_range_mdns_reflector',
|
|
709
|
+
'native_range_gateway',
|
|
710
|
+
'native_range_type',
|
|
711
|
+
'native_range_translated_subnet',
|
|
712
|
+
'native_range_interface_id',
|
|
713
|
+
'native_range_interface_index',
|
|
714
|
+
'native_range_interface_name',
|
|
715
|
+
'native_range_dhcp_type',
|
|
716
|
+
'native_range_dhcp_ip_range',
|
|
717
|
+
'native_range_dhcp_relay_group_id',
|
|
718
|
+
'native_range_dhcp_relay_group_name',
|
|
719
|
+
'native_range_dhcp_microsegmentation',
|
|
720
|
+
'native_range_local_ip',
|
|
721
|
+
'site_type',
|
|
722
|
+
'connection_type',
|
|
723
|
+
'site_location_address',
|
|
724
|
+
'site_location_city',
|
|
725
|
+
'site_location_country_code',
|
|
726
|
+
'site_location_state_code',
|
|
727
|
+
'site_location_timezone'
|
|
728
|
+
]
|
|
729
|
+
|
|
730
|
+
rows = []
|
|
731
|
+
total_interfaces = 0
|
|
732
|
+
|
|
733
|
+
for site in sites:
|
|
734
|
+
site_name = site.get('name', '')
|
|
735
|
+
wan_interfaces = site.get('wan_interfaces', [])
|
|
736
|
+
|
|
737
|
+
# Sort WAN interfaces to ensure 'wan_1' role appears first
|
|
738
|
+
# This organizes the CSV so wan_1 interfaces are on the first line for each site
|
|
739
|
+
def wan_interface_sort_key(interface):
|
|
740
|
+
role = interface.get('role', '').lower()
|
|
741
|
+
if role == 'wan_1':
|
|
742
|
+
return 0 # wan_1 comes first
|
|
743
|
+
elif role == 'wan_2':
|
|
744
|
+
return 1
|
|
745
|
+
elif role == 'wan_3':
|
|
746
|
+
return 2
|
|
747
|
+
elif role == 'wan_4':
|
|
748
|
+
return 3
|
|
749
|
+
else:
|
|
750
|
+
return 9 # Unknown/other roles come last
|
|
751
|
+
|
|
752
|
+
wan_interfaces.sort(key=wan_interface_sort_key)
|
|
753
|
+
|
|
754
|
+
# If no WAN interfaces, create one empty row for the site
|
|
755
|
+
if not wan_interfaces:
|
|
756
|
+
wan_interfaces = [{}] # Empty interface to ensure site is included
|
|
757
|
+
|
|
758
|
+
for idx, wan_interface in enumerate(wan_interfaces):
|
|
759
|
+
# Site-specific attributes only on first row per site
|
|
760
|
+
is_first_interface = (idx == 0)
|
|
761
|
+
|
|
762
|
+
# Calculate local IP from native range subnet if available
|
|
763
|
+
native_range = site.get('native_range', {})
|
|
764
|
+
native_subnet = native_range.get('subnet', '')
|
|
765
|
+
local_ip = ''
|
|
766
|
+
if native_subnet and hasattr(args, 'calculate_local_ip') and args.calculate_local_ip:
|
|
767
|
+
calculated_ip = calculateLocalIp(native_subnet)
|
|
768
|
+
if calculated_ip:
|
|
769
|
+
local_ip = calculated_ip
|
|
770
|
+
elif native_range.get('local_ip'):
|
|
771
|
+
local_ip = native_range.get('local_ip', '')
|
|
772
|
+
|
|
773
|
+
row = {
|
|
774
|
+
'site_id': site.get('id', '') if is_first_interface else '',
|
|
775
|
+
'site_name': site_name,
|
|
776
|
+
'wan_interface_id': wan_interface.get('id', '') if wan_interface else '',
|
|
777
|
+
'wan_interface_index': wan_interface.get('index', '') if wan_interface else '',
|
|
778
|
+
'wan_interface_name': wan_interface.get('name', '') if wan_interface else '',
|
|
779
|
+
'wan_upstream_bw': wan_interface.get('upstream_bandwidth', '') if wan_interface else '',
|
|
780
|
+
'wan_downstream_bw': wan_interface.get('downstream_bandwidth', '') if wan_interface else '',
|
|
781
|
+
'wan_role': wan_interface.get('role', '') if wan_interface else '',
|
|
782
|
+
'wan_precedence': wan_interface.get('precedence', '') if wan_interface else '',
|
|
783
|
+
|
|
784
|
+
# Site attributes - only populate on first interface row
|
|
785
|
+
'site_description': site.get('description', '') if is_first_interface else '',
|
|
786
|
+
'native_range_subnet': native_subnet if is_first_interface else '',
|
|
787
|
+
'native_range_name': native_range.get('range_name', '') if is_first_interface else '',
|
|
788
|
+
'native_range_id': native_range.get('range_id', '') if is_first_interface else '',
|
|
789
|
+
'native_range_vlan': native_range.get('vlan', '') if is_first_interface else '',
|
|
790
|
+
'native_range_mdns_reflector': str(native_range.get('mdns_reflector', '')).upper() if is_first_interface and native_range.get('mdns_reflector') != '' else '' if is_first_interface else '',
|
|
791
|
+
'native_range_gateway': native_range.get('gateway', '') if is_first_interface else '',
|
|
792
|
+
'native_range_type': native_range.get('range_type', '') if is_first_interface else '',
|
|
793
|
+
'native_range_translated_subnet': native_range.get('translated_subnet', '') if is_first_interface else '',
|
|
794
|
+
'native_range_interface_id': native_range.get('interface_id', '') if is_first_interface else '',
|
|
795
|
+
'native_range_interface_index': native_range.get('index', '') if is_first_interface else '',
|
|
796
|
+
'native_range_interface_name': native_range.get('interface_name', '') if is_first_interface else '',
|
|
797
|
+
'native_range_dhcp_type': native_range.get('dhcp_settings', {}).get('dhcp_type', '') if is_first_interface else '',
|
|
798
|
+
'native_range_dhcp_ip_range': native_range.get('dhcp_settings', {}).get('ip_range', '') if is_first_interface else '',
|
|
799
|
+
'native_range_dhcp_relay_group_id': native_range.get('dhcp_settings', {}).get('relay_group_id', '') if is_first_interface else '',
|
|
800
|
+
'native_range_dhcp_relay_group_name': native_range.get('dhcp_settings', {}).get('relay_group_name', '') if is_first_interface else '',
|
|
801
|
+
'native_range_dhcp_microsegmentation': str(native_range.get('dhcp_settings', {}).get('dhcp_microsegmentation', '')).upper() if is_first_interface and native_range.get('dhcp_settings', {}).get('dhcp_microsegmentation') != '' else '' if is_first_interface else '',
|
|
802
|
+
'native_range_local_ip': local_ip if is_first_interface else '',
|
|
803
|
+
'site_type': site.get('type', '') if is_first_interface else '',
|
|
804
|
+
'connection_type': site.get('connection_type', '') if is_first_interface else '',
|
|
805
|
+
'site_location_address': site.get('site_location', {}).get('address', '') if is_first_interface else '',
|
|
806
|
+
'site_location_city': site.get('site_location', {}).get('city', '') if is_first_interface else '',
|
|
807
|
+
'site_location_country_code': site.get('site_location', {}).get('countryCode', '') if is_first_interface else '',
|
|
808
|
+
'site_location_state_code': site.get('site_location', {}).get('stateCode', '') if is_first_interface else '',
|
|
809
|
+
'site_location_timezone': site.get('site_location', {}).get('timezone', '') if is_first_interface else ''
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
rows.append(row)
|
|
813
|
+
total_interfaces += 1 if wan_interface else 0
|
|
814
|
+
|
|
815
|
+
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
|
816
|
+
writer = csv.DictWriter(csvfile, fieldnames=headers)
|
|
817
|
+
writer.writeheader()
|
|
818
|
+
writer.writerows(rows)
|
|
819
|
+
|
|
820
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
821
|
+
print(f"Exported {len(sites)} sites with {total_interfaces} WAN interfaces to {filepath}")
|
|
822
|
+
|
|
823
|
+
return filepath
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
def export_network_ranges_to_csv(site, args, account_id):
|
|
827
|
+
"""
|
|
828
|
+
Export network ranges for a single site to CSV file
|
|
829
|
+
Structure: LAN interface as parent with network ranges as children
|
|
830
|
+
First row per interface contains interface details, subsequent rows contain only network range details
|
|
831
|
+
"""
|
|
832
|
+
site_name = site.get('name', '')
|
|
833
|
+
if not site_name:
|
|
834
|
+
return None
|
|
835
|
+
|
|
836
|
+
# Sanitize site name for filename
|
|
837
|
+
safe_site_name = "".join(c for c in site_name if c.isalnum() or c in ('-', '_')).rstrip()
|
|
838
|
+
|
|
839
|
+
# Determine output directory
|
|
840
|
+
output_dir = getattr(args, 'output_directory', None)
|
|
841
|
+
if not output_dir:
|
|
842
|
+
output_dir = 'config_data'
|
|
843
|
+
if not os.path.isabs(output_dir):
|
|
844
|
+
output_dir = os.path.join(os.getcwd(), output_dir)
|
|
845
|
+
|
|
846
|
+
# Create sites_config subdirectory with account ID
|
|
847
|
+
sites_config_dir = os.path.join(output_dir, f'sites_config_{account_id}')
|
|
848
|
+
os.makedirs(sites_config_dir, exist_ok=True)
|
|
849
|
+
|
|
850
|
+
filename = f"{safe_site_name}_network_ranges.csv"
|
|
851
|
+
filepath = os.path.join(sites_config_dir, filename)
|
|
852
|
+
|
|
853
|
+
# Define CSV headers - Reordered LAN interface columns first, then network range columns
|
|
854
|
+
headers = [
|
|
855
|
+
# LAN Interface columns (first 3 columns, is_native_range 4th, lan_interface_index 5th)
|
|
856
|
+
'lan_interface_id', 'lan_interface_name', 'lan_interface_dest_type', 'is_native_range', 'lan_interface_index',
|
|
857
|
+
# Network Range columns (populated on all rows)
|
|
858
|
+
'network_range_id', 'network_range_name', 'subnet', 'vlan', 'mdns_reflector',
|
|
859
|
+
'gateway', 'range_type', 'translated_subnet', 'internet_only', 'local_ip',
|
|
860
|
+
'dhcp_type', 'dhcp_ip_range', 'dhcp_relay_group_id', 'dhcp_relay_group_name', 'dhcp_microsegmentation'
|
|
861
|
+
]
|
|
862
|
+
|
|
863
|
+
rows = []
|
|
864
|
+
|
|
865
|
+
# Get the native range subnet from the site to exclude it from detailed CSV
|
|
866
|
+
native_range_subnet = site.get('native_range', {}).get('subnet', '')
|
|
867
|
+
|
|
868
|
+
# Process default LAN interface (from native_range) - ONLY if it has additional networks
|
|
869
|
+
native_range = site.get('native_range', {})
|
|
870
|
+
native_range_index = native_range.get('index', '') # The default interface index from parent CSV
|
|
871
|
+
default_lan_interfaces = [lan_nic for lan_nic in site.get('lan_interfaces', []) if lan_nic.get('default_lan', False)]
|
|
872
|
+
|
|
873
|
+
if default_lan_interfaces:
|
|
874
|
+
for default_lan_interface in default_lan_interfaces:
|
|
875
|
+
interface_id = native_range.get('interface_id', '')
|
|
876
|
+
interface_name = native_range.get('interface_name', '')
|
|
877
|
+
interface_index = native_range.get('index', '') # Use actual interface index like INT_5
|
|
878
|
+
interface_dest_type = 'LAN'
|
|
879
|
+
|
|
880
|
+
network_ranges = default_lan_interface.get('network_ranges', [])
|
|
881
|
+
|
|
882
|
+
# Filter out the network range that matches the parent CSV native_range_subnet
|
|
883
|
+
filtered_ranges = [nr for nr in network_ranges if nr.get('subnet', '') != native_range_subnet]
|
|
884
|
+
|
|
885
|
+
# For default_lan interfaces, ONLY create entries if there are additional ranges
|
|
886
|
+
# Do NOT create an entry with is_native_range=TRUE since native range is managed at parent level
|
|
887
|
+
if filtered_ranges:
|
|
888
|
+
# Process filtered ranges - all marked as additional (non-native) ranges
|
|
889
|
+
for idx, network_range in enumerate(filtered_ranges):
|
|
890
|
+
# First row for this interface includes interface details
|
|
891
|
+
is_first_range = (idx == 0)
|
|
892
|
+
|
|
893
|
+
# For default_lan interfaces, all ranges are additional (is_native_range=FALSE)
|
|
894
|
+
# because the native range is already defined in the parent CSV
|
|
895
|
+
|
|
896
|
+
row = {
|
|
897
|
+
# LAN Interface details - for default LAN interfaces, don't populate interface ID, name, type since managed at parent level
|
|
898
|
+
'lan_interface_id': '', # Empty for default LAN interfaces (managed at parent level)
|
|
899
|
+
'lan_interface_name': '', # Empty for default LAN interfaces (managed at parent level)
|
|
900
|
+
'lan_interface_dest_type': '', # Empty for default LAN interfaces (managed at parent level)
|
|
901
|
+
'is_native_range': '', # Always empty for default LAN interfaces
|
|
902
|
+
'lan_interface_index': interface_index, # Populated for every row
|
|
903
|
+
|
|
904
|
+
# Network Range details (on all rows)
|
|
905
|
+
'network_range_id': network_range.get('id', ''),
|
|
906
|
+
'network_range_name': network_range.get('name', ''),
|
|
907
|
+
'subnet': network_range.get('subnet', ''),
|
|
908
|
+
'vlan': network_range.get('vlan', ''),
|
|
909
|
+
'mdns_reflector': str(network_range.get('mdns_reflector', False)).upper(),
|
|
910
|
+
'gateway': network_range.get('gateway', ''),
|
|
911
|
+
'range_type': network_range.get('range_type', ''),
|
|
912
|
+
'translated_subnet': network_range.get('translated_subnet', ''),
|
|
913
|
+
'internet_only': network_range.get('internet_only', ''),
|
|
914
|
+
'local_ip': network_range.get('local_ip', ''),
|
|
915
|
+
'dhcp_type': network_range.get('dhcp_settings', {}).get('dhcp_type', ''),
|
|
916
|
+
'dhcp_ip_range': network_range.get('dhcp_settings', {}).get('ip_range', ''),
|
|
917
|
+
'dhcp_relay_group_id': network_range.get('dhcp_settings', {}).get('relay_group_id', ''),
|
|
918
|
+
'dhcp_relay_group_name': network_range.get('dhcp_settings', {}).get('relay_group_name', ''),
|
|
919
|
+
'dhcp_microsegmentation': str(network_range.get('dhcp_settings', {}).get('dhcp_microsegmentation', False)).upper()
|
|
920
|
+
}
|
|
921
|
+
rows.append(row)
|
|
922
|
+
|
|
923
|
+
# Process regular LAN interfaces and their network ranges
|
|
924
|
+
for lan_interface in site.get('lan_interfaces', []):
|
|
925
|
+
is_default_lan = lan_interface.get('default_lan', False)
|
|
926
|
+
|
|
927
|
+
# Skip default_lan interfaces (already processed above)
|
|
928
|
+
if is_default_lan:
|
|
929
|
+
continue
|
|
930
|
+
|
|
931
|
+
interface_id = lan_interface.get('id', '')
|
|
932
|
+
interface_name = lan_interface.get('name', '')
|
|
933
|
+
interface_index = lan_interface.get('index', '')
|
|
934
|
+
interface_dest_type = lan_interface.get('dest_type', '')
|
|
935
|
+
|
|
936
|
+
network_ranges = lan_interface.get('network_ranges', [])
|
|
937
|
+
|
|
938
|
+
# Filter out the network range that matches the parent CSV native_range_subnet
|
|
939
|
+
filtered_ranges = [nr for nr in network_ranges if nr.get('subnet', '') != native_range_subnet]
|
|
940
|
+
|
|
941
|
+
# If no filtered ranges, create at least one row with just the LAN interface info
|
|
942
|
+
# For regular LAN interfaces, mark as native range if this is the first/only interface
|
|
943
|
+
if not filtered_ranges:
|
|
944
|
+
row = {
|
|
945
|
+
# LAN Interface details - first 3 columns reordered, is_native_range 4th, lan_interface_index 5th
|
|
946
|
+
'lan_interface_id': interface_id,
|
|
947
|
+
'lan_interface_name': interface_name,
|
|
948
|
+
'lan_interface_dest_type': interface_dest_type,
|
|
949
|
+
'is_native_range': 'TRUE', # Mark as native range for non-default LAN interfaces with no additional ranges
|
|
950
|
+
'lan_interface_index': interface_index,
|
|
951
|
+
|
|
952
|
+
# Network Range details (empty since no additional ranges)
|
|
953
|
+
'network_range_id': '',
|
|
954
|
+
'network_range_name': '',
|
|
955
|
+
'subnet': '',
|
|
956
|
+
'vlan': '',
|
|
957
|
+
'mdns_reflector': '',
|
|
958
|
+
'gateway': '',
|
|
959
|
+
'range_type': '',
|
|
960
|
+
'translated_subnet': '',
|
|
961
|
+
'internet_only': '',
|
|
962
|
+
'local_ip': '',
|
|
963
|
+
'dhcp_type': '',
|
|
964
|
+
'dhcp_ip_range': '',
|
|
965
|
+
'dhcp_relay_group_id': '',
|
|
966
|
+
'dhcp_relay_group_name': '',
|
|
967
|
+
'dhcp_microsegmentation': ''
|
|
968
|
+
}
|
|
969
|
+
rows.append(row)
|
|
970
|
+
else:
|
|
971
|
+
# Process filtered ranges as before
|
|
972
|
+
for idx, network_range in enumerate(filtered_ranges):
|
|
973
|
+
# First row for this interface includes interface details
|
|
974
|
+
is_first_range = (idx == 0)
|
|
975
|
+
|
|
976
|
+
# For non-default_lan interfaces, first range should be marked as is_native_range
|
|
977
|
+
is_native_range = is_first_range
|
|
978
|
+
|
|
979
|
+
row = {
|
|
980
|
+
# LAN Interface details - first 3 columns reordered, is_native_range 4th, lan_interface_index 5th
|
|
981
|
+
'lan_interface_id': interface_id if is_first_range else '',
|
|
982
|
+
'lan_interface_name': interface_name if is_first_range else '',
|
|
983
|
+
'lan_interface_dest_type': interface_dest_type if is_first_range else '',
|
|
984
|
+
'is_native_range': 'TRUE' if is_native_range else '',
|
|
985
|
+
'lan_interface_index': interface_index, # Populated for every row
|
|
986
|
+
|
|
987
|
+
# Network Range details (on all rows)
|
|
988
|
+
'network_range_id': network_range.get('id', ''),
|
|
989
|
+
'network_range_name': network_range.get('name', ''),
|
|
990
|
+
'subnet': network_range.get('subnet', ''),
|
|
991
|
+
'vlan': network_range.get('vlan', ''),
|
|
992
|
+
'mdns_reflector': str(network_range.get('mdns_reflector', False)).upper(),
|
|
993
|
+
'gateway': network_range.get('gateway', ''),
|
|
994
|
+
'range_type': network_range.get('range_type', ''),
|
|
995
|
+
'translated_subnet': network_range.get('translated_subnet', ''),
|
|
996
|
+
'internet_only': network_range.get('internet_only', ''),
|
|
997
|
+
'local_ip': network_range.get('local_ip', ''),
|
|
998
|
+
'dhcp_type': network_range.get('dhcp_settings', {}).get('dhcp_type', ''),
|
|
999
|
+
'dhcp_ip_range': network_range.get('dhcp_settings', {}).get('ip_range', ''),
|
|
1000
|
+
'dhcp_relay_group_id': network_range.get('dhcp_settings', {}).get('relay_group_id', ''),
|
|
1001
|
+
'dhcp_relay_group_name': network_range.get('dhcp_settings', {}).get('relay_group_name', ''),
|
|
1002
|
+
'dhcp_microsegmentation': str(network_range.get('dhcp_settings', {}).get('dhcp_microsegmentation', False)).upper()
|
|
1003
|
+
}
|
|
1004
|
+
rows.append(row)
|
|
1005
|
+
|
|
1006
|
+
# If still no rows, it means the site only has the default LAN interface (managed at parent level)
|
|
1007
|
+
# In this case, create an empty CSV file - no entries needed since default interface is handled in parent CSV
|
|
1008
|
+
# This is correct behavior: sites with only default LAN interfaces should have empty site-level CSV files
|
|
1009
|
+
|
|
1010
|
+
# Always create file now (removed the early return)
|
|
1011
|
+
# if not rows:
|
|
1012
|
+
# return None
|
|
1013
|
+
|
|
1014
|
+
with open(filepath, 'w', newline='', encoding='utf-8') as csvfile:
|
|
1015
|
+
writer = csv.DictWriter(csvfile, fieldnames=headers)
|
|
1016
|
+
writer.writeheader()
|
|
1017
|
+
writer.writerows(rows)
|
|
1018
|
+
|
|
1019
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
1020
|
+
print(f"Exported {len(rows)} network ranges for site '{site_name}' to {filepath}")
|
|
1021
|
+
|
|
1022
|
+
return filepath
|
|
1023
|
+
|
|
1024
|
+
|
|
393
1025
|
##########################################################################
|
|
394
1026
|
########################### Helper functions #############################
|
|
395
1027
|
##########################################################################
|