catocli 2.0.2__py3-none-any.whl → 2.0.3__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 +4 -1
- catocli/__init__.py +1 -1
- catocli/parsers/custom/__init__.py +4 -3
- catocli/parsers/custom/customLib.py +239 -1
- catocli/parsers/custom/export_rules/export_rules.py +1 -1
- catocli/parsers/custom/export_sites/export_sites.py +186 -54
- catocli/parsers/custom/import_rules_to_tf/__init__.py +1 -1
- catocli/parsers/custom/import_rules_to_tf/import_rules_to_tf.py +1 -137
- catocli/parsers/custom/import_sites_to_tf/__init__.py +45 -0
- catocli/parsers/custom/import_sites_to_tf/import_sites_to_tf.py +891 -0
- catocli/parsers/mutation_accountManagement/__init__.py +6 -6
- catocli/parsers/mutation_admin/__init__.py +6 -6
- catocli/parsers/mutation_container/__init__.py +2 -2
- catocli/parsers/mutation_hardware/__init__.py +2 -2
- catocli/parsers/mutation_policy/__init__.py +192 -192
- catocli/parsers/mutation_sandbox/__init__.py +4 -4
- catocli/parsers/mutation_site/__init__.py +56 -56
- catocli/parsers/mutation_sites/__init__.py +56 -56
- catocli/parsers/mutation_xdr/__init__.py +6 -6
- catocli/parsers/parserApiClient.py +36 -11
- catocli/parsers/query_accountBySubdomain/__init__.py +2 -2
- catocli/parsers/query_accountManagement/__init__.py +2 -2
- catocli/parsers/query_accountMetrics/__init__.py +2 -2
- catocli/parsers/query_accountRoles/__init__.py +2 -2
- catocli/parsers/query_accountSnapshot/__init__.py +2 -2
- catocli/parsers/query_admin/__init__.py +2 -2
- catocli/parsers/query_admins/__init__.py +2 -2
- catocli/parsers/query_appStats/__init__.py +2 -2
- catocli/parsers/query_appStatsTimeSeries/__init__.py +2 -2
- catocli/parsers/query_auditFeed/__init__.py +2 -2
- catocli/parsers/query_catalogs/__init__.py +2 -2
- catocli/parsers/query_container/__init__.py +2 -2
- catocli/parsers/query_devices/__init__.py +2 -2
- catocli/parsers/query_entityLookup/__init__.py +2 -2
- catocli/parsers/query_events/__init__.py +2 -2
- catocli/parsers/query_eventsFeed/__init__.py +2 -2
- catocli/parsers/query_eventsTimeSeries/__init__.py +2 -2
- catocli/parsers/query_hardware/__init__.py +2 -2
- catocli/parsers/query_hardwareManagement/__init__.py +2 -2
- catocli/parsers/query_licensing/__init__.py +2 -2
- catocli/parsers/query_policy/__init__.py +2 -2
- catocli/parsers/query_sandbox/__init__.py +2 -2
- catocli/parsers/query_site/__init__.py +2 -2
- catocli/parsers/query_siteLocation/__init__.py +2 -2
- catocli/parsers/query_subDomains/__init__.py +2 -2
- catocli/parsers/query_xdr/__init__.py +4 -4
- catocli/parsers/raw/README.md +4 -0
- catocli/parsers/raw/__init__.py +3 -2
- {catocli-2.0.2.dist-info → catocli-2.0.3.dist-info}/METADATA +1 -1
- {catocli-2.0.2.dist-info → catocli-2.0.3.dist-info}/RECORD +55 -53
- schema/catolib.py +14 -9
- {catocli-2.0.2.dist-info → catocli-2.0.3.dist-info}/LICENSE +0 -0
- {catocli-2.0.2.dist-info → catocli-2.0.3.dist-info}/WHEEL +0 -0
- {catocli-2.0.2.dist-info → catocli-2.0.3.dist-info}/entry_points.txt +0 -0
- {catocli-2.0.2.dist-info → catocli-2.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Direct Terraform Import Script using Python
|
|
4
|
+
Imports socket sites, WAN interfaces, and network ranges directly using subprocess calls to terraform import
|
|
5
|
+
Reads from JSON structure exported from Cato API
|
|
6
|
+
Adapted from scripts/import_if_rules_to_tfstate.py for CLI usage
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
import re
|
|
13
|
+
import time
|
|
14
|
+
import glob
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from ..customLib import validate_terraform_environment
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_json_data(json_file):
|
|
20
|
+
"""Load socket sites data from JSON file"""
|
|
21
|
+
try:
|
|
22
|
+
with open(json_file, 'r') as f:
|
|
23
|
+
data = json.load(f)
|
|
24
|
+
return data['sites']
|
|
25
|
+
except FileNotFoundError:
|
|
26
|
+
print(f"Error: JSON file '{json_file}' not found")
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
except json.JSONDecodeError as e:
|
|
29
|
+
print(f"Error: Invalid JSON in '{json_file}': {e}")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
except KeyError as e:
|
|
32
|
+
print(f"Error: Expected JSON structure not found in '{json_file}': {e}")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def sanitize_name_for_terraform(name):
|
|
37
|
+
"""Sanitize rule/section name to create valid Terraform resource key"""
|
|
38
|
+
# Replace spaces and special characters with underscores
|
|
39
|
+
sanitized = re.sub(r'[^a-zA-Z0-9_-]', '_', name)
|
|
40
|
+
# Remove multiple consecutive underscores
|
|
41
|
+
sanitized = re.sub(r'_+', '_', sanitized)
|
|
42
|
+
# Remove leading/trailing underscores
|
|
43
|
+
sanitized = sanitized.strip('_')
|
|
44
|
+
return sanitized
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def extract_socket_sites_data(sites_data):
|
|
48
|
+
"""Extract socket sites, WAN interfaces, and network ranges from the sites data"""
|
|
49
|
+
sites = []
|
|
50
|
+
wan_interfaces = []
|
|
51
|
+
network_ranges = []
|
|
52
|
+
|
|
53
|
+
for site in sites_data:
|
|
54
|
+
if site.get('id') and site.get('name'):
|
|
55
|
+
# Transform site_location to match provider expectations
|
|
56
|
+
site_location = site.get('site_location', {})
|
|
57
|
+
transformed_location = {
|
|
58
|
+
'country_code': site_location.get('countryCode', site_location.get('country_code', '')),
|
|
59
|
+
'state_code': site.get('stateCode', site_location.get('state_code', '')),
|
|
60
|
+
'timezone': site_location.get('timezone', ''),
|
|
61
|
+
'city': site_location.get('city', ''),
|
|
62
|
+
'address': site_location.get('address', '')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# Transform native_range data
|
|
66
|
+
native_range = {
|
|
67
|
+
'native_network_range': site.get('native_network_range', ''),
|
|
68
|
+
'local_ip': site.get('local_ip', ''),
|
|
69
|
+
'translated_subnet': site.get('translated_subnet', ''),
|
|
70
|
+
'native_network_range_id': site.get('native_network_range_id', '')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Handle dhcp_settings - ensure it's a proper object with required fields
|
|
74
|
+
dhcp_settings = site.get('dhcp_settings')
|
|
75
|
+
if dhcp_settings and isinstance(dhcp_settings, dict) and dhcp_settings.get('dhcp_type'):
|
|
76
|
+
native_range['dhcp_settings'] = {
|
|
77
|
+
'dhcp_type': dhcp_settings.get('dhcp_type', ''),
|
|
78
|
+
'ip_range': dhcp_settings.get('ip_range', ''),
|
|
79
|
+
'relay_group_id': dhcp_settings.get('relay_group_id', '')
|
|
80
|
+
}
|
|
81
|
+
else:
|
|
82
|
+
native_range['dhcp_settings'] = None
|
|
83
|
+
|
|
84
|
+
sites.append({
|
|
85
|
+
'id': site['id'],
|
|
86
|
+
'name': site['name'],
|
|
87
|
+
'description': site.get('description', ''),
|
|
88
|
+
'connection_type': site.get('connectionType', ''),
|
|
89
|
+
'site_type': site.get('type', ''),
|
|
90
|
+
'site_location': transformed_location,
|
|
91
|
+
'native_range': native_range
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
# Extract WAN interfaces for this site
|
|
95
|
+
for wan_interface in site.get('wan_interfaces', []):
|
|
96
|
+
if wan_interface.get('id') and wan_interface.get('name'):
|
|
97
|
+
wan_interfaces.append({
|
|
98
|
+
'site_id': site['id'],
|
|
99
|
+
'site_name': site['name'],
|
|
100
|
+
'interface_id': wan_interface['id'],
|
|
101
|
+
'name': wan_interface['name'],
|
|
102
|
+
'upstream_bandwidth': wan_interface.get('upstreamBandwidth', 25),
|
|
103
|
+
'downstream_bandwidth': wan_interface.get('downstreamBandwidth', 25),
|
|
104
|
+
'dest_type': wan_interface.get('destType', 'CATO'),
|
|
105
|
+
'role': wan_interface.get('id', 'wan_1'), # Use interface ID as role
|
|
106
|
+
'precedence': 'ACTIVE'
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
# Extract network ranges for this site
|
|
110
|
+
for lan_interface in site.get('lan_interfaces', []):
|
|
111
|
+
interface_id = lan_interface.get('id', '')
|
|
112
|
+
interface_name = lan_interface.get('name', '')
|
|
113
|
+
|
|
114
|
+
for network_range in lan_interface.get('network_ranges', []):
|
|
115
|
+
if network_range.get('id') and network_range.get('subnet'):
|
|
116
|
+
network_ranges.append({
|
|
117
|
+
'site_id': site['id'],
|
|
118
|
+
'site_name': site['name'],
|
|
119
|
+
'interface_id': interface_id,
|
|
120
|
+
'interface_name': interface_name,
|
|
121
|
+
'network_range_id': network_range['id'],
|
|
122
|
+
'name': network_range.get('rangeName', ''),
|
|
123
|
+
'subnet': network_range['subnet'],
|
|
124
|
+
'vlan_tag': network_range.get('vlanTag', ''),
|
|
125
|
+
'range_type': 'VLAN' if network_range.get('vlanTag') and network_range.get('vlanTag') != '' else 'Native',
|
|
126
|
+
'microsegmentation': network_range.get('microsegmentation', False)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
return sites, wan_interfaces, network_ranges
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def run_terraform_import(resource_address, resource_id, timeout=60, verbose=False):
|
|
133
|
+
"""
|
|
134
|
+
Run a single terraform import command
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
resource_address: The terraform resource address
|
|
138
|
+
resource_id: The actual resource ID to import
|
|
139
|
+
timeout: Command timeout in seconds
|
|
140
|
+
verbose: Whether to show verbose output
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
tuple: (success: bool, output: str, error: str)
|
|
144
|
+
"""
|
|
145
|
+
cmd = ['terraform', 'import', resource_address, resource_id]
|
|
146
|
+
if verbose:
|
|
147
|
+
print(f"Command: {' '.join(cmd)}")
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
print(f"Importing: {resource_address} <- {resource_id}")
|
|
151
|
+
|
|
152
|
+
result = subprocess.run(
|
|
153
|
+
cmd,
|
|
154
|
+
capture_output=True,
|
|
155
|
+
text=True,
|
|
156
|
+
timeout=timeout,
|
|
157
|
+
cwd=Path.cwd()
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
if result.returncode == 0:
|
|
161
|
+
print(f"Success: {resource_address}")
|
|
162
|
+
return True, result.stdout, result.stderr
|
|
163
|
+
else:
|
|
164
|
+
print(f"Failed: {resource_address}")
|
|
165
|
+
print(f"Error: {result.stderr}")
|
|
166
|
+
return False, result.stdout, result.stderr
|
|
167
|
+
|
|
168
|
+
except subprocess.TimeoutExpired:
|
|
169
|
+
print(f"Timeout: {resource_address} (exceeded {timeout}s)")
|
|
170
|
+
return False, "", f"Command timed out after {timeout} seconds"
|
|
171
|
+
except Exception as e:
|
|
172
|
+
print(f"Unexpected error for {resource_address}: {e}")
|
|
173
|
+
return False, "", str(e)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def find_rule_index(rules, rule_name):
|
|
177
|
+
"""Find rule index by name."""
|
|
178
|
+
for index, rule in enumerate(rules):
|
|
179
|
+
if rule['name'] == rule_name:
|
|
180
|
+
return index
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def import_sections(sections, module_name, resource_type,
|
|
185
|
+
resource_name="sections", verbose=False):
|
|
186
|
+
"""Import all sections"""
|
|
187
|
+
print("\nStarting section imports...")
|
|
188
|
+
total_sections = len(sections)
|
|
189
|
+
successful_imports = 0
|
|
190
|
+
failed_imports = 0
|
|
191
|
+
|
|
192
|
+
for i, section in enumerate(sections):
|
|
193
|
+
section_id = section['section_id']
|
|
194
|
+
section_name = section['section_name']
|
|
195
|
+
section_index = section['section_index']
|
|
196
|
+
resource_address = f'{module_name}.{resource_type}.{resource_name}["{section_name}"]'
|
|
197
|
+
print(f"\n[{i+1}/{total_sections}] Section: {section_name} (index: {section_index})")
|
|
198
|
+
|
|
199
|
+
# For sections, we use the section name as the ID since that's how Cato identifies them
|
|
200
|
+
success, stdout, stderr = run_terraform_import(resource_address, section_id, verbose=verbose)
|
|
201
|
+
|
|
202
|
+
if success:
|
|
203
|
+
successful_imports += 1
|
|
204
|
+
else:
|
|
205
|
+
failed_imports += 1
|
|
206
|
+
|
|
207
|
+
print(f"\nSection Import Summary: {successful_imports} successful, {failed_imports} failed")
|
|
208
|
+
return successful_imports, failed_imports
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def import_rules(rules, module_name, verbose=False,
|
|
212
|
+
resource_type="cato_if_rule", resource_name="rules",
|
|
213
|
+
batch_size=10, delay_between_batches=2, auto_approve=False):
|
|
214
|
+
"""Import all rules in batches"""
|
|
215
|
+
print("\nStarting rule imports...")
|
|
216
|
+
successful_imports = 0
|
|
217
|
+
failed_imports = 0
|
|
218
|
+
total_rules = len(rules)
|
|
219
|
+
|
|
220
|
+
for i, rule in enumerate(rules):
|
|
221
|
+
rule_id = rule['id']
|
|
222
|
+
rule_name = rule['name']
|
|
223
|
+
rule_index = find_rule_index(rules, rule_name)
|
|
224
|
+
terraform_key = sanitize_name_for_terraform(rule_name)
|
|
225
|
+
|
|
226
|
+
# Use array index syntax instead of rule ID
|
|
227
|
+
resource_address = f'{module_name}.{resource_type}.{resource_name}["{str(rule_name)}"]'
|
|
228
|
+
print(f"\n[{i+1}/{total_rules}] Rule: {rule_name} (index: {rule_index})")
|
|
229
|
+
|
|
230
|
+
success, stdout, stderr = run_terraform_import(resource_address, rule_id, verbose=verbose)
|
|
231
|
+
|
|
232
|
+
if success:
|
|
233
|
+
successful_imports += 1
|
|
234
|
+
else:
|
|
235
|
+
failed_imports += 1
|
|
236
|
+
|
|
237
|
+
# Ask user if they want to continue on failure (unless auto-approved)
|
|
238
|
+
if failed_imports <= 3 and not auto_approve: # Only prompt for first few failures
|
|
239
|
+
response = input(f"\nContinue with remaining imports? (y/n): ").lower()
|
|
240
|
+
if response == 'n':
|
|
241
|
+
print("Import process stopped by user.")
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
# Delay between batches
|
|
245
|
+
if (i + 1) % batch_size == 0 and i < total_rules - 1:
|
|
246
|
+
print(f"\n Batch complete. Waiting {delay_between_batches}s before next batch...")
|
|
247
|
+
time.sleep(delay_between_batches)
|
|
248
|
+
|
|
249
|
+
print(f"\n Rule Import Summary: {successful_imports} successful, {failed_imports} failed")
|
|
250
|
+
return successful_imports, failed_imports
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def import_if_rules_to_tf(args, configuration):
|
|
254
|
+
"""Main function to orchestrate the import process"""
|
|
255
|
+
try:
|
|
256
|
+
print(" Terraform Import Tool - Cato IFW Rules & Sections")
|
|
257
|
+
print("=" * 60)
|
|
258
|
+
|
|
259
|
+
# Load data
|
|
260
|
+
print(f" Loading data from {args.json_file}...")
|
|
261
|
+
policy_data = load_json_data(args.json_file)
|
|
262
|
+
|
|
263
|
+
# Extract rules and sections
|
|
264
|
+
rules, sections = extract_rules_and_sections(policy_data)
|
|
265
|
+
|
|
266
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
267
|
+
print(f"section_ids: {json.dumps(policy_data.get('section_ids', {}), indent=2)}")
|
|
268
|
+
|
|
269
|
+
print(f" Found {len(rules)} rules")
|
|
270
|
+
print(f" Found {len(sections)} sections")
|
|
271
|
+
|
|
272
|
+
if not rules and not sections:
|
|
273
|
+
print(" No rules or sections found. Exiting.")
|
|
274
|
+
return [{"success": False, "error": "No rules or sections found"}]
|
|
275
|
+
|
|
276
|
+
# Validate Terraform environment before proceeding
|
|
277
|
+
validate_terraform_environment(args.module_name, verbose=args.verbose)
|
|
278
|
+
|
|
279
|
+
# Ask for confirmation (unless auto-approved)
|
|
280
|
+
if not args.rules_only and not args.sections_only:
|
|
281
|
+
print(f"\n Ready to import {len(sections)} sections and {len(rules)} rules.")
|
|
282
|
+
elif args.rules_only:
|
|
283
|
+
print(f"\n Ready to import {len(rules)} rules only.")
|
|
284
|
+
elif args.sections_only:
|
|
285
|
+
print(f"\n Ready to import {len(sections)} sections only.")
|
|
286
|
+
|
|
287
|
+
if hasattr(args, 'auto_approve') and args.auto_approve:
|
|
288
|
+
print("\nAuto-approve enabled, proceeding with import...")
|
|
289
|
+
else:
|
|
290
|
+
confirm = input(f"\nProceed with import? (y/n): ").lower()
|
|
291
|
+
if confirm != 'y':
|
|
292
|
+
print("Import cancelled.")
|
|
293
|
+
return [{"success": False, "error": "Import cancelled by user"}]
|
|
294
|
+
|
|
295
|
+
total_successful = 0
|
|
296
|
+
total_failed = 0
|
|
297
|
+
|
|
298
|
+
# Import sections first (if not skipped)
|
|
299
|
+
if not args.rules_only and sections:
|
|
300
|
+
successful, failed = import_sections(sections, module_name=args.module_name, resource_type="cato_if_section", verbose=args.verbose)
|
|
301
|
+
total_successful += successful
|
|
302
|
+
total_failed += failed
|
|
303
|
+
|
|
304
|
+
# Import rules (if not skipped)
|
|
305
|
+
if not args.sections_only and rules:
|
|
306
|
+
successful, failed = import_rules(rules, module_name=args.module_name,
|
|
307
|
+
verbose=args.verbose, batch_size=args.batch_size,
|
|
308
|
+
delay_between_batches=args.delay,
|
|
309
|
+
auto_approve=getattr(args, 'auto_approve', False))
|
|
310
|
+
total_successful += successful
|
|
311
|
+
total_failed += failed
|
|
312
|
+
|
|
313
|
+
# Final summary
|
|
314
|
+
print("\n" + "=" * 60)
|
|
315
|
+
print(" FINAL IMPORT SUMMARY")
|
|
316
|
+
print("=" * 60)
|
|
317
|
+
print(f" Total successful imports: {total_successful}")
|
|
318
|
+
print(f" Total failed imports: {total_failed}")
|
|
319
|
+
print(f" Overall success rate: {(total_successful / (total_successful + total_failed) * 100):.1f}%" if (total_successful + total_failed) > 0 else "N/A")
|
|
320
|
+
print("\n Import process completed!")
|
|
321
|
+
|
|
322
|
+
return [{
|
|
323
|
+
"success": True,
|
|
324
|
+
"total_successful": total_successful,
|
|
325
|
+
"total_failed": total_failed,
|
|
326
|
+
"module_name": args.module_name
|
|
327
|
+
}]
|
|
328
|
+
|
|
329
|
+
except Exception as e:
|
|
330
|
+
print(f"ERROR: {str(e)}")
|
|
331
|
+
return [{"success": False, "error": str(e)}]
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def load_wf_json_data(json_file):
|
|
335
|
+
"""Load WAN Firewall data from JSON file"""
|
|
336
|
+
try:
|
|
337
|
+
with open(json_file, 'r') as f:
|
|
338
|
+
data = json.load(f)
|
|
339
|
+
return data['data']['policy']['wanFirewall']['policy']
|
|
340
|
+
except FileNotFoundError:
|
|
341
|
+
print(f"Error: JSON file '{json_file}' not found")
|
|
342
|
+
sys.exit(1)
|
|
343
|
+
except json.JSONDecodeError as e:
|
|
344
|
+
print(f"Error: Invalid JSON in '{json_file}': {e}")
|
|
345
|
+
sys.exit(1)
|
|
346
|
+
except KeyError as e:
|
|
347
|
+
print(f"Error: Expected JSON structure not found in '{json_file}': {e}")
|
|
348
|
+
sys.exit(1)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def import_wf_sections(sections, module_name, verbose=False,
|
|
352
|
+
resource_type="cato_wf_section", resource_name="sections"):
|
|
353
|
+
"""Import all WAN Firewall sections"""
|
|
354
|
+
print("\nStarting WAN Firewall section imports...")
|
|
355
|
+
total_sections = len(sections)
|
|
356
|
+
successful_imports = 0
|
|
357
|
+
failed_imports = 0
|
|
358
|
+
|
|
359
|
+
for i, section in enumerate(sections):
|
|
360
|
+
section_id = section['section_id']
|
|
361
|
+
section_name = section['section_name']
|
|
362
|
+
section_index = section['section_index']
|
|
363
|
+
# Add module. prefix if not present
|
|
364
|
+
if not module_name.startswith('module.'):
|
|
365
|
+
module_name = f'module.{module_name}'
|
|
366
|
+
resource_address = f'{module_name}.{resource_type}.{resource_name}["{section_name}"]'
|
|
367
|
+
print(f"\n[{i+1}/{total_sections}] Section: {section_name} (index: {section_index})")
|
|
368
|
+
|
|
369
|
+
# For sections, we use the section name as the ID since that's how Cato identifies them
|
|
370
|
+
success, stdout, stderr = run_terraform_import(resource_address, section_id, verbose=verbose)
|
|
371
|
+
|
|
372
|
+
if success:
|
|
373
|
+
successful_imports += 1
|
|
374
|
+
else:
|
|
375
|
+
failed_imports += 1
|
|
376
|
+
|
|
377
|
+
print(f"\nWAN Firewall Section Import Summary: {successful_imports} successful, {failed_imports} failed")
|
|
378
|
+
return successful_imports, failed_imports
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def import_wf_rules(rules, module_name, verbose=False,
|
|
382
|
+
resource_type="cato_wf_rule", resource_name="rules",
|
|
383
|
+
batch_size=10, delay_between_batches=2, auto_approve=False):
|
|
384
|
+
"""Import all WAN Firewall rules in batches"""
|
|
385
|
+
print("\nStarting WAN Firewall rule imports...")
|
|
386
|
+
successful_imports = 0
|
|
387
|
+
failed_imports = 0
|
|
388
|
+
total_rules = len(rules)
|
|
389
|
+
|
|
390
|
+
for i, rule in enumerate(rules):
|
|
391
|
+
rule_id = rule['id']
|
|
392
|
+
rule_name = rule['name']
|
|
393
|
+
rule_index = find_rule_index(rules, rule_name)
|
|
394
|
+
terraform_key = sanitize_name_for_terraform(rule_name)
|
|
395
|
+
|
|
396
|
+
# Add module. prefix if not present
|
|
397
|
+
if not module_name.startswith('module.'):
|
|
398
|
+
module_name = f'module.{module_name}'
|
|
399
|
+
|
|
400
|
+
# Use array index syntax instead of rule ID
|
|
401
|
+
resource_address = f'{module_name}.{resource_type}.{resource_name}["{str(rule_name)}"]'
|
|
402
|
+
print(f"\n[{i+1}/{total_rules}] Rule: {rule_name} (index: {rule_index})")
|
|
403
|
+
|
|
404
|
+
success, stdout, stderr = run_terraform_import(resource_address, rule_id, verbose=verbose)
|
|
405
|
+
|
|
406
|
+
if success:
|
|
407
|
+
successful_imports += 1
|
|
408
|
+
else:
|
|
409
|
+
failed_imports += 1
|
|
410
|
+
|
|
411
|
+
# Ask user if they want to continue on failure (unless auto-approved)
|
|
412
|
+
if failed_imports <= 3 and not auto_approve: # Only prompt for first few failures
|
|
413
|
+
response = input(f"\nContinue with remaining imports? (y/n): ").lower()
|
|
414
|
+
if response == 'n':
|
|
415
|
+
print("Import process stopped by user.")
|
|
416
|
+
break
|
|
417
|
+
|
|
418
|
+
# Delay between batches
|
|
419
|
+
if (i + 1) % batch_size == 0 and i < total_rules - 1:
|
|
420
|
+
print(f"\n Batch complete. Waiting {delay_between_batches}s before next batch...")
|
|
421
|
+
time.sleep(delay_between_batches)
|
|
422
|
+
|
|
423
|
+
print(f"\nWAN Firewall Rule Import Summary: {successful_imports} successful, {failed_imports} failed")
|
|
424
|
+
return successful_imports, failed_imports
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def import_wf_rules_to_tf(args, configuration):
|
|
428
|
+
"""Main function to orchestrate the WAN Firewall import process"""
|
|
429
|
+
try:
|
|
430
|
+
print(" Terraform Import Tool - Cato WF Rules & Sections")
|
|
431
|
+
print("=" * 60)
|
|
432
|
+
|
|
433
|
+
# Load data
|
|
434
|
+
print(f" Loading data from {args.json_file}...")
|
|
435
|
+
policy_data = load_wf_json_data(args.json_file)
|
|
436
|
+
|
|
437
|
+
# Extract rules and sections
|
|
438
|
+
rules, sections = extract_rules_and_sections(policy_data)
|
|
439
|
+
|
|
440
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
441
|
+
print(f"section_ids: {json.dumps(policy_data.get('section_ids', {}), indent=2)}")
|
|
442
|
+
|
|
443
|
+
print(f" Found {len(rules)} rules")
|
|
444
|
+
print(f" Found {len(sections)} sections")
|
|
445
|
+
|
|
446
|
+
if not rules and not sections:
|
|
447
|
+
print(" No rules or sections found. Exiting.")
|
|
448
|
+
return [{"success": False, "error": "No rules or sections found"}]
|
|
449
|
+
|
|
450
|
+
# Add module. prefix if not present
|
|
451
|
+
module_name = args.module_name
|
|
452
|
+
if not module_name.startswith('module.'):
|
|
453
|
+
module_name = f'module.{module_name}'
|
|
454
|
+
# Validate Terraform environment before proceeding
|
|
455
|
+
validate_terraform_environment(module_name, verbose=args.verbose)
|
|
456
|
+
|
|
457
|
+
# Ask for confirmation (unless auto-approved)
|
|
458
|
+
if not args.rules_only and not args.sections_only:
|
|
459
|
+
print(f"\n Ready to import {len(sections)} sections and {len(rules)} rules.")
|
|
460
|
+
elif args.rules_only:
|
|
461
|
+
print(f"\n Ready to import {len(rules)} rules only.")
|
|
462
|
+
elif args.sections_only:
|
|
463
|
+
print(f"\n Ready to import {len(sections)} sections only.")
|
|
464
|
+
|
|
465
|
+
if hasattr(args, 'auto_approve') and args.auto_approve:
|
|
466
|
+
print("\nAuto-approve enabled, proceeding with import...")
|
|
467
|
+
else:
|
|
468
|
+
confirm = input(f"\nProceed with import? (y/n): ").lower()
|
|
469
|
+
if confirm != 'y':
|
|
470
|
+
print("Import cancelled.")
|
|
471
|
+
return [{"success": False, "error": "Import cancelled by user"}]
|
|
472
|
+
|
|
473
|
+
total_successful = 0
|
|
474
|
+
total_failed = 0
|
|
475
|
+
|
|
476
|
+
# Import sections first (if not skipped)
|
|
477
|
+
if not args.rules_only and sections:
|
|
478
|
+
successful, failed = import_wf_sections(sections, module_name=args.module_name, verbose=args.verbose)
|
|
479
|
+
total_successful += successful
|
|
480
|
+
total_failed += failed
|
|
481
|
+
|
|
482
|
+
# Import rules (if not skipped)
|
|
483
|
+
if not args.sections_only and rules:
|
|
484
|
+
successful, failed = import_wf_rules(rules, module_name=args.module_name,
|
|
485
|
+
verbose=args.verbose, batch_size=args.batch_size,
|
|
486
|
+
delay_between_batches=args.delay,
|
|
487
|
+
auto_approve=getattr(args, 'auto_approve', False))
|
|
488
|
+
total_successful += successful
|
|
489
|
+
total_failed += failed
|
|
490
|
+
|
|
491
|
+
# Final summary
|
|
492
|
+
print("\n" + "=" * 60)
|
|
493
|
+
print(" FINAL IMPORT SUMMARY")
|
|
494
|
+
print("=" * 60)
|
|
495
|
+
print(f" Total successful imports: {total_successful}")
|
|
496
|
+
print(f" Total failed imports: {total_failed}")
|
|
497
|
+
print(f" Overall success rate: {(total_successful / (total_successful + total_failed) * 100):.1f}%" if (total_successful + total_failed) > 0 else "N/A")
|
|
498
|
+
print("\n Import process completed!")
|
|
499
|
+
|
|
500
|
+
return [{
|
|
501
|
+
"success": True,
|
|
502
|
+
"total_successful": total_successful,
|
|
503
|
+
"total_failed": total_failed,
|
|
504
|
+
"module_name": args.module_name
|
|
505
|
+
}]
|
|
506
|
+
|
|
507
|
+
except Exception as e:
|
|
508
|
+
print(f"ERROR: {str(e)}")
|
|
509
|
+
return [{"success": False, "error": str(e)}]
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def import_socket_sites(sites, module_name, verbose=False,
|
|
513
|
+
resource_type="cato_socket_site", resource_name="socket-site",
|
|
514
|
+
batch_size=10, delay_between_batches=2, auto_approve=False):
|
|
515
|
+
"""Import all socket sites in batches"""
|
|
516
|
+
print("\nStarting socket site imports...")
|
|
517
|
+
successful_imports = 0
|
|
518
|
+
failed_imports = 0
|
|
519
|
+
total_sites = len(sites)
|
|
520
|
+
|
|
521
|
+
for i, site in enumerate(sites):
|
|
522
|
+
site_id = site['id']
|
|
523
|
+
site_name = site['name']
|
|
524
|
+
terraform_key = sanitize_name_for_terraform(site_name)
|
|
525
|
+
|
|
526
|
+
# Add module. prefix if not present
|
|
527
|
+
if not module_name.startswith('module.'):
|
|
528
|
+
module_name = f'module.{module_name}'
|
|
529
|
+
|
|
530
|
+
# Use correct resource addressing for nested module
|
|
531
|
+
resource_address = f'{module_name}.module.socket-site["{site_name}"].cato_socket_site.site'
|
|
532
|
+
print(f"\n[{i+1}/{total_sites}] Site: {site_name} (ID: {site_id})")
|
|
533
|
+
|
|
534
|
+
success, stdout, stderr = run_terraform_import(resource_address, site_id, verbose=verbose)
|
|
535
|
+
|
|
536
|
+
if success:
|
|
537
|
+
successful_imports += 1
|
|
538
|
+
else:
|
|
539
|
+
failed_imports += 1
|
|
540
|
+
|
|
541
|
+
# Ask user if they want to continue on failure (unless auto-approved)
|
|
542
|
+
if failed_imports <= 3 and not auto_approve: # Only prompt for first few failures
|
|
543
|
+
response = input(f"\nContinue with remaining imports? (y/n): ").lower()
|
|
544
|
+
if response == 'n':
|
|
545
|
+
print("Import process stopped by user.")
|
|
546
|
+
break
|
|
547
|
+
|
|
548
|
+
# Delay between batches
|
|
549
|
+
if (i + 1) % batch_size == 0 and i < total_sites - 1:
|
|
550
|
+
print(f"\n Batch complete. Waiting {delay_between_batches}s before next batch...")
|
|
551
|
+
time.sleep(delay_between_batches)
|
|
552
|
+
|
|
553
|
+
print(f"\nSocket Site Import Summary: {successful_imports} successful, {failed_imports} failed")
|
|
554
|
+
return successful_imports, failed_imports
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def import_wan_interfaces(wan_interfaces, module_name, verbose=False,
|
|
558
|
+
resource_type="cato_wan_interface", resource_name="wan_interfaces",
|
|
559
|
+
batch_size=10, delay_between_batches=2, auto_approve=False):
|
|
560
|
+
"""Import all WAN interfaces in batches"""
|
|
561
|
+
print("\nStarting WAN interface imports...")
|
|
562
|
+
successful_imports = 0
|
|
563
|
+
failed_imports = 0
|
|
564
|
+
total_interfaces = len(wan_interfaces)
|
|
565
|
+
|
|
566
|
+
for i, interface in enumerate(wan_interfaces):
|
|
567
|
+
site_id = interface['site_id']
|
|
568
|
+
interface_id = interface['interface_id']
|
|
569
|
+
interface_name = interface['name']
|
|
570
|
+
site_name = interface['site_name']
|
|
571
|
+
|
|
572
|
+
# Add module. prefix if not present
|
|
573
|
+
if not module_name.startswith('module.'):
|
|
574
|
+
module_name = f'module.{module_name}'
|
|
575
|
+
|
|
576
|
+
# Use correct resource addressing for WAN interfaces
|
|
577
|
+
# WAN interfaces use the interface role as the key
|
|
578
|
+
wan_role = interface['role']
|
|
579
|
+
resource_address = f'{module_name}.module.socket-site["{site_name}"].cato_wan_interface.wan["{wan_role}"]'
|
|
580
|
+
|
|
581
|
+
# WAN interface import needs site_id:interface_id format
|
|
582
|
+
# Check if interface_id is already in the correct format (contains ':')
|
|
583
|
+
if ':' in interface_id:
|
|
584
|
+
import_id = interface_id # Already in correct format
|
|
585
|
+
else:
|
|
586
|
+
import_id = f"{site_id}:{interface_id}" # Need to add site_id
|
|
587
|
+
print(f"\n[{i+1}/{total_interfaces}] WAN Interface: {interface_name} on {site_name} (ID: {import_id})")
|
|
588
|
+
|
|
589
|
+
success, stdout, stderr = run_terraform_import(resource_address, import_id, verbose=verbose)
|
|
590
|
+
|
|
591
|
+
if success:
|
|
592
|
+
successful_imports += 1
|
|
593
|
+
else:
|
|
594
|
+
failed_imports += 1
|
|
595
|
+
|
|
596
|
+
# Ask user if they want to continue on failure (unless auto-approved)
|
|
597
|
+
if failed_imports <= 3 and not auto_approve: # Only prompt for first few failures
|
|
598
|
+
response = input(f"\nContinue with remaining imports? (y/n): ").lower()
|
|
599
|
+
if response == 'n':
|
|
600
|
+
print("Import process stopped by user.")
|
|
601
|
+
break
|
|
602
|
+
|
|
603
|
+
# Delay between batches
|
|
604
|
+
if (i + 1) % batch_size == 0 and i < total_interfaces - 1:
|
|
605
|
+
print(f"\n Batch complete. Waiting {delay_between_batches}s before next batch...")
|
|
606
|
+
time.sleep(delay_between_batches)
|
|
607
|
+
|
|
608
|
+
print(f"\nWAN Interface Import Summary: {successful_imports} successful, {failed_imports} failed")
|
|
609
|
+
return successful_imports, failed_imports
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def import_network_ranges(network_ranges, module_name, verbose=False,
|
|
613
|
+
resource_type="cato_network_range", resource_name="network_ranges",
|
|
614
|
+
batch_size=10, delay_between_batches=2, auto_approve=False):
|
|
615
|
+
"""Import all network ranges in batches"""
|
|
616
|
+
print("\nStarting network range imports...")
|
|
617
|
+
successful_imports = 0
|
|
618
|
+
failed_imports = 0
|
|
619
|
+
total_ranges = len(network_ranges)
|
|
620
|
+
|
|
621
|
+
for i, network_range in enumerate(network_ranges):
|
|
622
|
+
network_range_id = network_range['network_range_id']
|
|
623
|
+
range_name = network_range['name']
|
|
624
|
+
site_name = network_range['site_name']
|
|
625
|
+
subnet = network_range['subnet']
|
|
626
|
+
|
|
627
|
+
# Add module. prefix if not present
|
|
628
|
+
if not module_name.startswith('module.'):
|
|
629
|
+
module_name = f'module.{module_name}'
|
|
630
|
+
|
|
631
|
+
# Use correct resource addressing for network ranges
|
|
632
|
+
# Based on terraform plan output, network ranges are structured as:
|
|
633
|
+
# module.sites.module.socket-site["site_name"].module.lan_interfaces["interface_id"].module.network_range["subnet"].cato_network_range.no_dhcp[0]
|
|
634
|
+
interface_id = network_range['interface_id']
|
|
635
|
+
# Convert interface_id to the format used in terraform (e.g., "161570" -> "INT_5")
|
|
636
|
+
if interface_id.isdigit():
|
|
637
|
+
# This is a numeric interface ID, need to convert to INT_X format
|
|
638
|
+
# For now, we'll use the interface_id as is and let terraform handle the mapping
|
|
639
|
+
terraform_interface_id = interface_id
|
|
640
|
+
else:
|
|
641
|
+
terraform_interface_id = interface_id
|
|
642
|
+
|
|
643
|
+
resource_address = f'{module_name}.module.socket-site["{site_name}"].module.lan_interfaces["{terraform_interface_id}"].module.network_range["{subnet}"].cato_network_range.no_dhcp[0]'
|
|
644
|
+
|
|
645
|
+
print(f"\n[{i+1}/{total_ranges}] Network Range: {range_name} ({subnet}) on {site_name} (ID: {network_range_id})")
|
|
646
|
+
|
|
647
|
+
success, stdout, stderr = run_terraform_import(resource_address, network_range_id, verbose=verbose)
|
|
648
|
+
|
|
649
|
+
if success:
|
|
650
|
+
successful_imports += 1
|
|
651
|
+
else:
|
|
652
|
+
failed_imports += 1
|
|
653
|
+
|
|
654
|
+
# Ask user if they want to continue on failure (unless auto-approved)
|
|
655
|
+
if failed_imports <= 3 and not auto_approve: # Only prompt for first few failures
|
|
656
|
+
response = input(f"\nContinue with remaining imports? (y/n): ").lower()
|
|
657
|
+
if response == 'n':
|
|
658
|
+
print("Import process stopped by user.")
|
|
659
|
+
break
|
|
660
|
+
|
|
661
|
+
# Delay between batches
|
|
662
|
+
if (i + 1) % batch_size == 0 and i < total_ranges - 1:
|
|
663
|
+
print(f"\n Batch complete. Waiting {delay_between_batches}s before next batch...")
|
|
664
|
+
time.sleep(delay_between_batches)
|
|
665
|
+
|
|
666
|
+
print(f"\nNetwork Range Import Summary: {successful_imports} successful, {failed_imports} failed")
|
|
667
|
+
return successful_imports, failed_imports
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def generate_terraform_import_files(sites, output_dir="./imported_sites"):
|
|
671
|
+
"""Generate Terraform configuration files for imported sites"""
|
|
672
|
+
import os
|
|
673
|
+
|
|
674
|
+
# Create output directory if it doesn't exist
|
|
675
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
676
|
+
|
|
677
|
+
# Generate main.tf with socket site resources
|
|
678
|
+
main_tf_content = []
|
|
679
|
+
|
|
680
|
+
for site in sites:
|
|
681
|
+
site_name = sanitize_name_for_terraform(site['name'])
|
|
682
|
+
site_location = site['site_location']
|
|
683
|
+
native_range = site['native_range']
|
|
684
|
+
|
|
685
|
+
# Build site_location block
|
|
686
|
+
location_attrs = []
|
|
687
|
+
if site_location.get('country_code'):
|
|
688
|
+
location_attrs.append(f' country_code = "{site_location["country_code"]}"')
|
|
689
|
+
if site_location.get('state_code'):
|
|
690
|
+
location_attrs.append(f' state_code = "{site_location["state_code"]}"')
|
|
691
|
+
if site_location.get('timezone'):
|
|
692
|
+
location_attrs.append(f' timezone = "{site_location["timezone"]}"')
|
|
693
|
+
if site_location.get('city'):
|
|
694
|
+
location_attrs.append(f' city = "{site_location["city"]}"')
|
|
695
|
+
if site_location.get('address'):
|
|
696
|
+
location_attrs.append(f' address = "{site_location["address"]}"')
|
|
697
|
+
|
|
698
|
+
# Build native_range block - these are required fields
|
|
699
|
+
native_range_attrs = []
|
|
700
|
+
# Always include required fields, even if empty
|
|
701
|
+
native_range_attrs.append(f' native_network_range = "{native_range.get("native_network_range", "")}"')
|
|
702
|
+
native_range_attrs.append(f' local_ip = "{native_range.get("local_ip", "")}"')
|
|
703
|
+
if native_range.get('translated_subnet'):
|
|
704
|
+
native_range_attrs.append(f' translated_subnet = "{native_range["translated_subnet"]}"')
|
|
705
|
+
|
|
706
|
+
# Add dhcp_settings if present
|
|
707
|
+
if native_range.get('dhcp_settings'):
|
|
708
|
+
dhcp_settings = native_range['dhcp_settings']
|
|
709
|
+
dhcp_attrs = []
|
|
710
|
+
if dhcp_settings.get('dhcp_type'):
|
|
711
|
+
dhcp_attrs.append(f' dhcp_type = "{dhcp_settings["dhcp_type"]}"')
|
|
712
|
+
if dhcp_settings.get('ip_range'):
|
|
713
|
+
dhcp_attrs.append(f' ip_range = "{dhcp_settings["ip_range"]}"')
|
|
714
|
+
if dhcp_settings.get('relay_group_id'):
|
|
715
|
+
dhcp_attrs.append(f' relay_group_id = "{dhcp_settings["relay_group_id"]}"')
|
|
716
|
+
|
|
717
|
+
if dhcp_attrs:
|
|
718
|
+
native_range_attrs.append(' dhcp_settings = {')
|
|
719
|
+
native_range_attrs.extend(dhcp_attrs)
|
|
720
|
+
native_range_attrs.append(' }')
|
|
721
|
+
|
|
722
|
+
# Generate resource block
|
|
723
|
+
resource_block = f"""resource "cato_socket_site" "{site_name}" {{
|
|
724
|
+
name = "{site['name']}"
|
|
725
|
+
description = "{site.get('description', '')}"
|
|
726
|
+
site_type = "{site.get('site_type', '')}"
|
|
727
|
+
connection_type = "{site.get('connection_type', '')}"
|
|
728
|
+
|
|
729
|
+
site_location = {{
|
|
730
|
+
{chr(10).join(location_attrs)}
|
|
731
|
+
}}
|
|
732
|
+
|
|
733
|
+
native_range = {{
|
|
734
|
+
{chr(10).join(native_range_attrs)}
|
|
735
|
+
}}
|
|
736
|
+
}}
|
|
737
|
+
"""
|
|
738
|
+
|
|
739
|
+
main_tf_content.append(resource_block)
|
|
740
|
+
|
|
741
|
+
# Write main.tf
|
|
742
|
+
with open(os.path.join(output_dir, "main.tf"), "w") as f:
|
|
743
|
+
f.write(chr(10).join(main_tf_content))
|
|
744
|
+
|
|
745
|
+
# Generate import.tf with import blocks
|
|
746
|
+
import_tf_content = []
|
|
747
|
+
|
|
748
|
+
for site in sites:
|
|
749
|
+
site_name = sanitize_name_for_terraform(site['name'])
|
|
750
|
+
import_block = f"""import {{
|
|
751
|
+
to = cato_socket_site.{site_name}
|
|
752
|
+
id = "{site['id']}"
|
|
753
|
+
}}
|
|
754
|
+
"""
|
|
755
|
+
import_tf_content.append(import_block)
|
|
756
|
+
|
|
757
|
+
# Write import.tf
|
|
758
|
+
with open(os.path.join(output_dir, "import.tf"), "w") as f:
|
|
759
|
+
f.write(chr(10).join(import_tf_content))
|
|
760
|
+
|
|
761
|
+
print(f"\nGenerated Terraform configuration files in {output_dir}:")
|
|
762
|
+
print(f" - main.tf: {len(sites)} socket site resources")
|
|
763
|
+
print(f" - import.tf: {len(sites)} import blocks")
|
|
764
|
+
|
|
765
|
+
return output_dir
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
def import_socket_sites_to_tf(args, configuration):
|
|
769
|
+
"""Main function to orchestrate the socket sites import process"""
|
|
770
|
+
try:
|
|
771
|
+
print(" Terraform Import Tool - Cato Socket Sites, WAN Interfaces & Network Ranges")
|
|
772
|
+
print("=" * 80)
|
|
773
|
+
|
|
774
|
+
# Load data
|
|
775
|
+
print(f" Loading data from {args.json_file}...")
|
|
776
|
+
sites_data = load_json_data(args.json_file)
|
|
777
|
+
|
|
778
|
+
# Extract sites, WAN interfaces, and network ranges
|
|
779
|
+
sites, wan_interfaces, network_ranges = extract_socket_sites_data(sites_data)
|
|
780
|
+
|
|
781
|
+
if hasattr(args, 'verbose') and args.verbose:
|
|
782
|
+
print(f"\nExtracted data summary:")
|
|
783
|
+
print(f" Sites: {len(sites)}")
|
|
784
|
+
print(f" WAN Interfaces: {len(wan_interfaces)}")
|
|
785
|
+
print(f" Network Ranges: {len(network_ranges)}")
|
|
786
|
+
|
|
787
|
+
print(f" Found {len(sites)} sites")
|
|
788
|
+
print(f" Found {len(wan_interfaces)} WAN interfaces")
|
|
789
|
+
print(f" Found {len(network_ranges)} network ranges")
|
|
790
|
+
|
|
791
|
+
if not sites and not wan_interfaces and not network_ranges:
|
|
792
|
+
print(" No sites, interfaces, or network ranges found. Exiting.")
|
|
793
|
+
return [{"success": False, "error": "No data found to import"}]
|
|
794
|
+
|
|
795
|
+
# Add module. prefix if not present
|
|
796
|
+
module_name = args.module_name
|
|
797
|
+
if not module_name.startswith('module.'):
|
|
798
|
+
module_name = f'module.{module_name}'
|
|
799
|
+
|
|
800
|
+
# Generate Terraform configuration files if requested
|
|
801
|
+
if hasattr(args, 'generate_only') and args.generate_only:
|
|
802
|
+
print("\nGenerating Terraform configuration files...")
|
|
803
|
+
output_dir = generate_terraform_import_files(sites, output_dir=getattr(args, 'output_dir', './imported_sites'))
|
|
804
|
+
print(f"\nTerraform configuration files generated successfully in {output_dir}")
|
|
805
|
+
print("\nNext steps:")
|
|
806
|
+
print(f" 1. Copy the generated files to your Terraform project directory")
|
|
807
|
+
print(f" 2. Run 'terraform init' to initialize")
|
|
808
|
+
print(f" 3. Run 'terraform plan -generate-config-out=generated.tf' to generate configuration")
|
|
809
|
+
print(f" 4. Run 'terraform apply' to import the resources")
|
|
810
|
+
|
|
811
|
+
return [{
|
|
812
|
+
"success": True,
|
|
813
|
+
"total_generated": len(sites),
|
|
814
|
+
"output_dir": output_dir
|
|
815
|
+
}]
|
|
816
|
+
|
|
817
|
+
# Validate Terraform environment before proceeding
|
|
818
|
+
validate_terraform_environment(module_name, verbose=args.verbose)
|
|
819
|
+
|
|
820
|
+
# Ask for confirmation (unless auto-approved)
|
|
821
|
+
import_summary = []
|
|
822
|
+
if not args.sites_only and not args.interfaces_only and not args.network_ranges_only:
|
|
823
|
+
import_summary.append(f"{len(sites)} sites")
|
|
824
|
+
import_summary.append(f"{len(wan_interfaces)} WAN interfaces")
|
|
825
|
+
import_summary.append(f"{len(network_ranges)} network ranges")
|
|
826
|
+
elif args.sites_only:
|
|
827
|
+
import_summary.append(f"{len(sites)} sites only")
|
|
828
|
+
elif args.interfaces_only:
|
|
829
|
+
import_summary.append(f"{len(wan_interfaces)} WAN interfaces only")
|
|
830
|
+
elif args.network_ranges_only:
|
|
831
|
+
import_summary.append(f"{len(network_ranges)} network ranges only")
|
|
832
|
+
|
|
833
|
+
print(f"\n Ready to import {', '.join(import_summary)}.")
|
|
834
|
+
|
|
835
|
+
if hasattr(args, 'auto_approve') and args.auto_approve:
|
|
836
|
+
print("\nAuto-approve enabled, proceeding with import...")
|
|
837
|
+
else:
|
|
838
|
+
confirm = input(f"\nProceed with import? (y/n): ").lower()
|
|
839
|
+
if confirm != 'y':
|
|
840
|
+
print("Import cancelled.")
|
|
841
|
+
return [{"success": False, "error": "Import cancelled by user"}]
|
|
842
|
+
|
|
843
|
+
total_successful = 0
|
|
844
|
+
total_failed = 0
|
|
845
|
+
|
|
846
|
+
# Import sites first (if not skipped)
|
|
847
|
+
if not args.interfaces_only and not args.network_ranges_only and sites:
|
|
848
|
+
successful, failed = import_socket_sites(sites, module_name=args.module_name,
|
|
849
|
+
verbose=args.verbose, batch_size=args.batch_size,
|
|
850
|
+
delay_between_batches=args.delay,
|
|
851
|
+
auto_approve=getattr(args, 'auto_approve', False))
|
|
852
|
+
total_successful += successful
|
|
853
|
+
total_failed += failed
|
|
854
|
+
|
|
855
|
+
# Import WAN interfaces (if not skipped)
|
|
856
|
+
if not args.sites_only and not args.network_ranges_only and wan_interfaces:
|
|
857
|
+
successful, failed = import_wan_interfaces(wan_interfaces, module_name=args.module_name,
|
|
858
|
+
verbose=args.verbose, batch_size=args.batch_size,
|
|
859
|
+
delay_between_batches=args.delay,
|
|
860
|
+
auto_approve=getattr(args, 'auto_approve', False))
|
|
861
|
+
total_successful += successful
|
|
862
|
+
total_failed += failed
|
|
863
|
+
|
|
864
|
+
# Import network ranges (if not skipped)
|
|
865
|
+
if not args.sites_only and not args.interfaces_only and network_ranges:
|
|
866
|
+
successful, failed = import_network_ranges(network_ranges, module_name=args.module_name,
|
|
867
|
+
verbose=args.verbose, batch_size=args.batch_size,
|
|
868
|
+
delay_between_batches=args.delay,
|
|
869
|
+
auto_approve=getattr(args, 'auto_approve', False))
|
|
870
|
+
total_successful += successful
|
|
871
|
+
total_failed += failed
|
|
872
|
+
|
|
873
|
+
# Final summary
|
|
874
|
+
print("\n" + "=" * 80)
|
|
875
|
+
print(" FINAL IMPORT SUMMARY")
|
|
876
|
+
print("=" * 80)
|
|
877
|
+
print(f" Total successful imports: {total_successful}")
|
|
878
|
+
print(f" Total failed imports: {total_failed}")
|
|
879
|
+
print(f" Overall success rate: {(total_successful / (total_successful + total_failed) * 100):.1f}%" if (total_successful + total_failed) > 0 else "N/A")
|
|
880
|
+
print("\n Import process completed!")
|
|
881
|
+
|
|
882
|
+
return [{
|
|
883
|
+
"success": True,
|
|
884
|
+
"total_successful": total_successful,
|
|
885
|
+
"total_failed": total_failed,
|
|
886
|
+
"module_name": args.module_name
|
|
887
|
+
}]
|
|
888
|
+
|
|
889
|
+
except Exception as e:
|
|
890
|
+
print(f"ERROR: {str(e)}")
|
|
891
|
+
return [{"success": False, "error": str(e)}]
|