catocli 2.0.2__py3-none-any.whl → 2.0.4__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.

Files changed (57) hide show
  1. catocli/Utils/clidriver.py +4 -1
  2. catocli/__init__.py +1 -1
  3. catocli/parsers/custom/__init__.py +4 -3
  4. catocli/parsers/custom/customLib.py +239 -1
  5. catocli/parsers/custom/export_rules/__init__.py +2 -0
  6. catocli/parsers/custom/export_rules/export_rules.py +30 -6
  7. catocli/parsers/custom/export_sites/__init__.py +1 -0
  8. catocli/parsers/custom/export_sites/export_sites.py +194 -55
  9. catocli/parsers/custom/import_rules_to_tf/__init__.py +1 -1
  10. catocli/parsers/custom/import_rules_to_tf/import_rules_to_tf.py +1 -137
  11. catocli/parsers/custom/import_sites_to_tf/__init__.py +45 -0
  12. catocli/parsers/custom/import_sites_to_tf/import_sites_to_tf.py +891 -0
  13. catocli/parsers/mutation_accountManagement/__init__.py +6 -6
  14. catocli/parsers/mutation_admin/__init__.py +6 -6
  15. catocli/parsers/mutation_container/__init__.py +2 -2
  16. catocli/parsers/mutation_hardware/__init__.py +2 -2
  17. catocli/parsers/mutation_policy/__init__.py +192 -192
  18. catocli/parsers/mutation_sandbox/__init__.py +4 -4
  19. catocli/parsers/mutation_site/__init__.py +56 -56
  20. catocli/parsers/mutation_sites/__init__.py +56 -56
  21. catocli/parsers/mutation_xdr/__init__.py +6 -6
  22. catocli/parsers/parserApiClient.py +199 -12
  23. catocli/parsers/query_accountBySubdomain/__init__.py +2 -2
  24. catocli/parsers/query_accountManagement/__init__.py +2 -2
  25. catocli/parsers/query_accountMetrics/__init__.py +2 -2
  26. catocli/parsers/query_accountRoles/__init__.py +2 -2
  27. catocli/parsers/query_accountSnapshot/__init__.py +2 -2
  28. catocli/parsers/query_admin/__init__.py +2 -2
  29. catocli/parsers/query_admins/__init__.py +2 -2
  30. catocli/parsers/query_appStats/__init__.py +2 -2
  31. catocli/parsers/query_appStatsTimeSeries/__init__.py +2 -2
  32. catocli/parsers/query_auditFeed/__init__.py +2 -2
  33. catocli/parsers/query_catalogs/__init__.py +2 -2
  34. catocli/parsers/query_container/__init__.py +2 -2
  35. catocli/parsers/query_devices/__init__.py +2 -2
  36. catocli/parsers/query_entityLookup/__init__.py +2 -2
  37. catocli/parsers/query_events/__init__.py +2 -2
  38. catocli/parsers/query_eventsFeed/__init__.py +2 -2
  39. catocli/parsers/query_eventsTimeSeries/__init__.py +2 -2
  40. catocli/parsers/query_hardware/__init__.py +2 -2
  41. catocli/parsers/query_hardwareManagement/__init__.py +2 -2
  42. catocli/parsers/query_licensing/__init__.py +2 -2
  43. catocli/parsers/query_policy/__init__.py +2 -2
  44. catocli/parsers/query_sandbox/__init__.py +2 -2
  45. catocli/parsers/query_site/__init__.py +2 -2
  46. catocli/parsers/query_siteLocation/__init__.py +2 -2
  47. catocli/parsers/query_subDomains/__init__.py +2 -2
  48. catocli/parsers/query_xdr/__init__.py +4 -4
  49. catocli/parsers/raw/README.md +18 -0
  50. catocli/parsers/raw/__init__.py +5 -2
  51. {catocli-2.0.2.dist-info → catocli-2.0.4.dist-info}/METADATA +1 -1
  52. {catocli-2.0.2.dist-info → catocli-2.0.4.dist-info}/RECORD +57 -55
  53. schema/catolib.py +14 -9
  54. {catocli-2.0.2.dist-info → catocli-2.0.4.dist-info}/LICENSE +0 -0
  55. {catocli-2.0.2.dist-info → catocli-2.0.4.dist-info}/WHEEL +0 -0
  56. {catocli-2.0.2.dist-info → catocli-2.0.4.dist-info}/entry_points.txt +0 -0
  57. {catocli-2.0.2.dist-info → catocli-2.0.4.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,8 @@
1
1
  import os
2
2
  import json
3
+ import traceback
4
+ import sys
5
+ from datetime import datetime
3
6
  from graphql_client.api.call_api import ApiClient, CallApi
4
7
  from graphql_client.api_client import ApiException
5
8
  from ..customLib import writeDataToFile, makeCall, getAccountID
@@ -8,9 +11,19 @@ def export_socket_site_to_json(args, configuration):
8
11
  """
9
12
  Export consolidated site and socket data to JSON format
10
13
  """
11
- processed_data = {'sites':{}}
14
+ processed_data = {'sites':[]}
15
+ warning_stats = {
16
+ 'missing_sites': 0,
17
+ 'missing_interfaces': 0,
18
+ 'missing_data': 0,
19
+ 'missing_interface_details': []
20
+ }
12
21
 
13
22
  try:
23
+ settings = {}
24
+ with open(os.path.join(os.path.dirname(__file__), '../../../../settings.json'), 'r', encoding='utf-8') as f:
25
+ settings = json.load(f)
26
+
14
27
  account_id = getAccountID(args, configuration)
15
28
  # Get account snapshot with siteIDs if provided
16
29
  # Get siteIDs from args if provided (comma-separated string)
@@ -32,82 +45,207 @@ def export_socket_site_to_json(args, configuration):
32
45
  ##################################################################
33
46
  ## Create processed_data object indexed by siteId with location ##
34
47
  ##################################################################
35
- for site_data in snapshot_sites:
48
+ for snapshot_site in snapshot_sites['data']['accountSnapshot']['sites']:
36
49
  cur_site = {
37
- 'wan_interfaces': {},
38
- 'lan_interfaces': {},
50
+ 'wan_interfaces': [],
51
+ 'lan_interfaces': [],
39
52
  }
40
- site_id = site_data.get('id')
41
- cur_site['id'] = site_id
42
- cur_site['name'] = site_data.get('infoSiteSnapshot', {}).get('name')
43
- cur_site['description'] = site_data.get('infoSiteSnapshot', {}).get('description')
44
- cur_site['connectionType'] = site_data.get('infoSiteSnapshot', {}).get('connType')
45
- cur_site['type'] = site_data.get('infoSiteSnapshot', {}).get('type')
46
- cur_site = populateSiteLocationData(args, site_data, cur_site)
47
-
48
- site_interfaces = site_data.get('infoSiteSnapshot', {}).get('interfaces', [])
49
- for wan_ni in site_interfaces:
50
- cur_wan_interface = {}
51
- id = wan_ni.get('wanRoleInterfaceInfo', "")
52
- if id[0:3] == "wan":
53
- cur_wan_interface['id'] = id
54
- cur_wan_interface['name'] = wan_ni.get('name', "")
55
- cur_wan_interface['upstreamBandwidth'] = wan_ni.get('upstreamBandwidth', 0)
56
- cur_wan_interface['downstreamBandwidth'] = wan_ni.get('downstreamBandwidth', 0)
57
- cur_wan_interface['destType'] = wan_ni.get('destType', "")
58
- cur_site['wan_interfaces'][id] = cur_wan_interface
59
-
60
- if site_id:
61
- processed_data['sites'][site_id] = cur_site
53
+ site_id = snapshot_site.get('id')
54
+ connectionType = snapshot_site.get('infoSiteSnapshot', {}).get('connType', "")
55
+ if connectionType not in settings["ignore_export_by_socket_type"]:
56
+ cur_site['id'] = site_id
57
+ cur_site['name'] = snapshot_site.get('infoSiteSnapshot', {}).get('name')
58
+ cur_site['description'] = snapshot_site.get('infoSiteSnapshot', {}).get('description')
59
+ cur_site['connectionType'] = connectionType
60
+ cur_site['type'] = snapshot_site.get('infoSiteSnapshot', {}).get('type')
61
+ cur_site = populateSiteLocationData(args, snapshot_site, cur_site)
62
+
63
+ site_interfaces = snapshot_site.get('infoSiteSnapshot', {}).get('interfaces', [])
64
+ for wan_ni in site_interfaces:
65
+ cur_wan_interface = {}
66
+ role = wan_ni.get('wanRoleInterfaceInfo', "")
67
+ if role is not None and role[0:3] == "wan":
68
+ if connectionType == "SOCKET_X1500":
69
+ cur_wan_interface['id'] = site_id+":"+ wan_ni.get('id', "")
70
+ else:
71
+ cur_wan_interface['id'] = site_id+":INT_"+ wan_ni.get('id', "")
72
+ cur_wan_interface['id'] = site_id+":INT_"+ wan_ni.get('id', "")
73
+ cur_wan_interface['name'] = wan_ni.get('name', "")
74
+ cur_wan_interface['upstreamBandwidth'] = wan_ni.get('upstreamBandwidth', 0)
75
+ cur_wan_interface['downstreamBandwidth'] = wan_ni.get('downstreamBandwidth', 0)
76
+ cur_wan_interface['destType'] = wan_ni.get('destType', "")
77
+ cur_wan_interface['role'] = role
78
+ cur_site['wan_interfaces'].append(cur_wan_interface)
79
+
80
+ if site_id:
81
+ processed_data['sites'].append(cur_site)
62
82
 
63
83
  ##################################################################################
64
84
  ## Process entity lookup LAN network interfaces adding to site object by site_id##
65
85
  ##################################################################################
86
+ interface_map = {}
66
87
  for lan_ni in entity_network_interfaces:
67
88
  cur_lan_interface = {
68
- 'network_ranges': {},
89
+ 'network_ranges': [],
69
90
  }
70
- site_id = lan_ni.get("helperFields","").get('siteId', "")
71
- id = lan_ni.get('entity', "").get('id', "")
72
- interfaceName = lan_ni.get('entity', "").get('interfaceName', "")
91
+ site_id = str(lan_ni.get("helperFields","").get('siteId', ""))
92
+ id = str(lan_ni.get('entity', "").get('id', ""))
93
+ interfaceName = lan_ni.get('helperFields', "").get('interfaceName', "")
73
94
  cur_lan_interface['id'] = id
74
95
  cur_lan_interface['name'] = interfaceName
96
+ # Split interfaceName on " \ " and take the last element
75
97
  cur_lan_interface['index'] = lan_ni.get("helperFields","").get('interfaceId', "")
76
- cur_lan_interface['upstreamBandwidth'] = lan_ni.get('upstreamBandwidth', 0)
77
- cur_lan_interface['downstreamBandwidth'] = lan_ni.get('downstreamBandwidth', 0)
78
- cur_lan_interface['destType'] = lan_ni.get('destType', "")
79
- processed_data['sites'][site_id]['lan_interfaces'][interfaceName] = cur_lan_interface
98
+ cur_lan_interface['destType'] = lan_ni.get("helperFields","").get('destType', "")
99
+
100
+ # Create a composite key for interface mapping that includes site_id
101
+ interface_key = f"{site_id}_{interfaceName}"
102
+ interface_map[interface_key] = id
103
+
104
+ # Only add interface if the site exists in processed_data
105
+ site_entry = next((site for site in processed_data['sites'] if site['id'] == site_id), None)
106
+ if site_entry:
107
+ site_entry['lan_interfaces'].append(cur_lan_interface)
108
+ else:
109
+ if hasattr(args, 'verbose') and args.verbose:
110
+ print(f"WARNING: Site {site_id} not found in snapshot data, skipping interface {interfaceName} ({id})")
80
111
 
81
112
  #############################################################################
82
113
  ## Process entity lookup network ranges populating by network interface id ##
83
114
  #############################################################################
84
115
  for range in entity_network_ranges:
116
+ if hasattr(args, 'verbose') and args.verbose:
117
+ print(f"Processing network range: {type(range)} - {range}")
85
118
  cur_range = {}
86
- site_id = lan_ni.get("helperFields","").get('siteId', "")
87
- id = lan_ni.get('entity', "").get('id', "")
88
- interface_name = lan_ni.get('entity', "").get('interfaceName', "")
89
- cur_lan_interface['id'] = id
90
- cur_lan_interface['subnet'] = lan_ni.get("helperFields","").get('subnet', "")
91
- cur_lan_interface['vlanTag'] = lan_ni.get("helperFields","").get('vlanTag', "")
92
- cur_lan_interface['microsegmentation'] = lan_ni.get("helperFields","").get('microsegmentation', "")
119
+ helper_fields = range.get("helperFields", {})
120
+ entity_data = range.get('entity', {})
93
121
 
94
- processed_data['sites'][site_id]['lan_interfaces'][interface_name] = cur_range
95
-
122
+ if hasattr(args, 'verbose') and args.verbose:
123
+ print(f" helperFields type: {type(helper_fields)}, value: {helper_fields}")
124
+ print(f" entity type: {type(entity_data)}, value: {entity_data}")
125
+
126
+ range_id = entity_data.get('id', "")
127
+ site_id = str(helper_fields.get('siteId', ""))
128
+ interface_name = str(helper_fields.get('interfaceName', ""))
129
+ # Use the composite key to lookup interface_id
130
+ interface_key = f"{site_id}_{interface_name}"
131
+ interface_id = str(interface_map.get(interface_key, ""))
132
+ cur_range['id'] = range_id
133
+ range_name = entity_data.get('name', "")
134
+ if range_name and " \\ " in range_name:
135
+ cur_range['rangeName'] = range_name.split(" \\ ").pop()
136
+ else:
137
+ cur_range['rangeName'] = range_name
138
+ cur_range['name'] = range_name
139
+ cur_range['subnet'] = helper_fields.get('subnet', "")
140
+ cur_range['vlanTag'] = helper_fields.get('vlanTag', "")
141
+ cur_range['microsegmentation'] = helper_fields.get('microsegmentation', "")
142
+
143
+ # Safely add to processed_data with existence checks
144
+ if site_id and interface_id and range_id:
145
+ site_entry = next((site for site in processed_data['sites'] if site['id'] == site_id), None)
146
+ if not site_entry:
147
+ # print(f"WARNING: Site ID {site_id} not found in processed_data")
148
+ warning_stats['missing_sites'] += 1
149
+ continue
150
+
151
+ # Find the interface in the lan_interfaces array
152
+ interface_entry = next((iface for iface in site_entry['lan_interfaces'] if iface['id'] == interface_id), None)
153
+ if not interface_entry:
154
+ print(f"WARNING: Interface {interface_id} (name: {interface_name}) not found in site {site_id}. Range {range_id} will be skipped.")
155
+ warning_stats['missing_interfaces'] += 1
156
+ warning_stats['missing_interface_details'].append({
157
+ 'interface_id': interface_id,
158
+ 'interface_name': interface_name,
159
+ 'site_id': site_id,
160
+ 'range_id': range_id
161
+ })
162
+ if hasattr(args, 'verbose') and args.verbose:
163
+ available_interfaces = [iface['id'] for iface in site_entry['lan_interfaces']]
164
+ print(f" Available interfaces in site {site_id}: {available_interfaces}")
165
+ print(f" Looked up interface with key: {interface_key}")
166
+ continue
167
+ interface_entry['network_ranges'].append(cur_range)
168
+ if hasattr(args, 'verbose') and args.verbose:
169
+ print(f" Successfully added range {range_id} to site {site_id}, interface_name {interface_name} with interface_id {interface_id}")
170
+ else:
171
+ if not interface_id:
172
+ print(f"WARNING: Interface lookup failed for range {range_id}. Site: {site_id}, Interface name: {interface_name}, Lookup key: {interface_key}")
173
+ if hasattr(args, 'verbose') and args.verbose:
174
+ print(f" Available interface keys: {list(interface_map.keys())[:10]}...") # Show first 10 keys
175
+ else:
176
+ print(f"WARNING: Missing required data for range: site_id={site_id}, interface_id={interface_id}, range_id={range_id}")
177
+ warning_stats['missing_data'] += 1
96
178
 
179
+ # Print warning summary
180
+ total_warnings = warning_stats['missing_sites'] + warning_stats['missing_interfaces'] + warning_stats['missing_data']
181
+ if total_warnings > 0:
182
+ print(f"\n=== WARNING SUMMARY ===")
183
+ print(f"Total warnings: {total_warnings}")
184
+ print(f"- Missing sites: {warning_stats['missing_sites']}")
185
+ print(f"- Missing interfaces: {warning_stats['missing_interfaces']}")
186
+ print(f"- Missing data: {warning_stats['missing_data']}")
187
+
188
+ if warning_stats['missing_interfaces'] > 0:
189
+ print(f"\nMissing interface details:")
190
+ unique_interfaces = {}
191
+ for detail in warning_stats['missing_interface_details']:
192
+ key = f"{detail['interface_id']} ({detail['interface_name']})"
193
+ if key not in unique_interfaces:
194
+ unique_interfaces[key] = []
195
+ unique_interfaces[key].append(detail['site_id'])
196
+
197
+ for interface, sites in unique_interfaces.items():
198
+ print(f" - Interface {interface} missing in sites: {', '.join(sites)}")
199
+
200
+ print(f"\nThese warnings indicate network ranges that reference interfaces that don't exist in the site data.")
201
+ print(f"This is usually caused by data inconsistencies and can be safely ignored if the export completes successfully.")
202
+ print(f"=========================\n")
203
+
204
+ # Handle timestamp in filename if requested
205
+ filename_template = "socket_sites_{account_id}.json"
206
+ if hasattr(args, 'append_timestamp') and args.append_timestamp:
207
+ timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
208
+ filename_template = "socket_sites_{account_id}_" + timestamp + ".json"
209
+
97
210
  # Write the processed data to file using the general-purpose function
98
211
  output_file = writeDataToFile(
99
212
  data=processed_data,
100
213
  args=args,
101
214
  account_id=account_id,
102
- default_filename_template="socket_sites_{account_id}.json",
215
+ default_filename_template=filename_template,
103
216
  default_directory="config_data"
104
217
  )
105
218
 
106
219
  return [{"success": True, "output_file": output_file, "account_id": account_id}]
107
220
 
108
221
  except Exception as e:
109
- print(f"ERROR: {str(e)}")
110
- return [{"success": False, "error": str(e)}]
222
+ # Get the current exception info
223
+ exc_type, exc_value, exc_traceback = sys.exc_info()
224
+
225
+ # Get the line number where the error occurred
226
+ line_number = exc_traceback.tb_lineno
227
+ filename = exc_traceback.tb_frame.f_code.co_filename
228
+ function_name = exc_traceback.tb_frame.f_code.co_name
229
+
230
+ # Get the full traceback as a string
231
+ full_traceback = traceback.format_exc()
232
+
233
+ # Create detailed error message
234
+ error_details = {
235
+ "error_type": exc_type.__name__,
236
+ "error_message": str(exc_value),
237
+ "line_number": line_number,
238
+ "function_name": function_name,
239
+ "filename": os.path.basename(filename),
240
+ "full_traceback": full_traceback
241
+ }
242
+
243
+ # Print detailed error information
244
+ print(f"ERROR: {exc_type.__name__}: {str(exc_value)}")
245
+ print(f"Location: {os.path.basename(filename)}:{line_number} in {function_name}()")
246
+ print(f"Full traceback:\n{full_traceback}")
247
+
248
+ return [{"success": False, "error": str(e), "error_details": error_details}]
111
249
 
112
250
 
113
251
  ##########################################################################
@@ -135,16 +273,17 @@ def populateSiteLocationData(args, site_data, cur_site):
135
273
  print(f"Warning: Could not load site location data: {e}")
136
274
 
137
275
  ## siteLocation attributes
138
- cur_site['address'] = site_data.get('infoSiteSnapshot', {}).get('address')
139
- cur_site['city'] = site_data.get('infoSiteSnapshot', {}).get('cityName')
140
- cur_site['stateName'] = site_data.get('infoSiteSnapshot', {}).get('countryStateName')
141
- cur_site['countryCode'] = site_data.get('infoSiteSnapshot', {}).get('countryCode')
142
- cur_site['countryName'] = site_data.get('infoSiteSnapshot', {}).get('countryName')
276
+ cur_site['site_location'] = {}
277
+ cur_site['site_location']['address'] = site_data.get('infoSiteSnapshot', {}).get('address')
278
+ cur_site['site_location']['city'] = site_data.get('infoSiteSnapshot', {}).get('cityName')
279
+ cur_site['site_location']['stateName'] = site_data.get('infoSiteSnapshot', {}).get('countryStateName')
280
+ cur_site['site_location']['countryCode'] = site_data.get('infoSiteSnapshot', {}).get('countryCode')
281
+ cur_site['site_location']['countryName'] = site_data.get('infoSiteSnapshot', {}).get('countryName')
143
282
 
144
283
  # Look up timezone and state code from location data
145
- country_name = cur_site['countryName']
146
- state_name = cur_site['stateName']
147
- city = cur_site['city']
284
+ country_name = cur_site['site_location']['countryName']
285
+ state_name = cur_site['site_location']['stateName']
286
+ city = cur_site['site_location']['city']
148
287
 
149
288
  # Create lookup key based on available data
150
289
  if state_name:
@@ -173,7 +312,7 @@ def populateSiteLocationData(args, site_data, cur_site):
173
312
 
174
313
  # Get timezone - always use the 0 element in the timezones array
175
314
  timezones = location_data.get('timezone', [])
176
- cur_site['timezone'] = timezones[0] if timezones else None
315
+ cur_site['site_location']['timezone'] = timezones[0] if timezones else None
177
316
  return cur_site
178
317
 
179
318
  def getEntityLookup(args, configuration, account_id, entity_type):
@@ -1,6 +1,6 @@
1
1
  import catocli.parsers.custom.import_rules_to_tf.import_rules_to_tf as import_rules_to_tf
2
2
 
3
- def import_parse(subparsers):
3
+ def rule_import_parse(subparsers):
4
4
  """Create import command parsers"""
5
5
 
6
6
  # Create the main import parser
@@ -13,6 +13,7 @@ import re
13
13
  import time
14
14
  import glob
15
15
  from pathlib import Path
16
+ from ..customLib import validate_terraform_environment
16
17
 
17
18
 
18
19
  def load_json_data(json_file):
@@ -191,143 +192,6 @@ def import_rules(rules, module_name, verbose=False,
191
192
  return successful_imports, failed_imports
192
193
 
193
194
 
194
- def check_terraform_binary():
195
- """Check if terraform binary is available"""
196
- try:
197
- result = subprocess.run(['terraform', '--version'], capture_output=True, text=True)
198
- if result.returncode == 0:
199
- return True, result.stdout.strip().split('\n')[0]
200
- else:
201
- return False, "Terraform binary not found or not working"
202
- except FileNotFoundError:
203
- return False, "Terraform binary not found in PATH"
204
- except Exception as e:
205
- return False, f"Error checking terraform binary: {e}"
206
-
207
-
208
- def check_terraform_config_files():
209
- """Check if Terraform configuration files exist in current directory"""
210
- tf_files = glob.glob('*.tf') + glob.glob('*.tf.json')
211
- if tf_files:
212
- return True, tf_files
213
- else:
214
- return False, []
215
-
216
-
217
- def check_terraform_init():
218
- """Check if Terraform has been initialized"""
219
- terraform_dir = Path('.terraform')
220
- if terraform_dir.exists() and terraform_dir.is_dir():
221
- # Check for providers
222
- providers_dir = terraform_dir / 'providers'
223
- if providers_dir.exists():
224
- return True, "Terraform is initialized"
225
- else:
226
- return False, "Terraform directory exists but no providers found"
227
- else:
228
- return False, "Terraform not initialized (.terraform directory not found)"
229
-
230
-
231
- def check_module_exists(module_name):
232
- """Check if the specified module exists in Terraform configuration"""
233
- try:
234
- # Remove 'module.' prefix if present
235
- clean_module_name = module_name.replace('module.', '')
236
-
237
- # Method 1: Check .tf files directly for module definitions
238
- tf_files = glob.glob('*.tf') + glob.glob('*.tf.json')
239
- for tf_file in tf_files:
240
- try:
241
- with open(tf_file, 'r') as f:
242
- content = f.read()
243
- # Look for module "module_name" blocks
244
- if f'module "{clean_module_name}"' in content or f"module '{clean_module_name}'" in content:
245
- return True, f"Module '{clean_module_name}' found in {tf_file}"
246
- except Exception as e:
247
- print(f"Warning: Could not read {tf_file}: {e}")
248
- continue
249
-
250
- # Method 2: Try terraform show -json as fallback
251
- try:
252
- result = subprocess.run(
253
- ['terraform', 'show', '-json'],
254
- capture_output=True,
255
- text=True,
256
- cwd=Path.cwd()
257
- )
258
-
259
- if result.returncode == 0:
260
- state_data = json.loads(result.stdout)
261
-
262
- # Check if module exists in configuration
263
- if 'configuration' in state_data and state_data['configuration']:
264
- modules = state_data.get('configuration', {}).get('root_module', {}).get('module_calls', {})
265
- if clean_module_name in modules:
266
- return True, f"Module '{clean_module_name}' found in Terraform state"
267
-
268
- # Also check in planned_values for modules
269
- if 'planned_values' in state_data and state_data['planned_values']:
270
- modules = state_data.get('planned_values', {}).get('root_module', {}).get('child_modules', [])
271
- for module in modules:
272
- module_addr = module.get('address', '')
273
- if clean_module_name in module_addr:
274
- return True, f"Module '{clean_module_name}' found in planned values"
275
- except (subprocess.SubprocessError, json.JSONDecodeError) as e:
276
- print(f"Warning: Could not check terraform state: {e}")
277
-
278
- return False, f"Module '{clean_module_name}' not found in Terraform configuration files"
279
-
280
- except Exception as e:
281
- return False, f"Error checking module existence: {e}"
282
-
283
-
284
- def validate_terraform_environment(module_name, verbose=False):
285
- """Validate the complete Terraform environment"""
286
- print("\n Validating Terraform environment...")
287
-
288
- # 1. Check terraform binary
289
- print("\n Checking Terraform binary...")
290
- has_terraform, terraform_msg = check_terraform_binary()
291
- if not has_terraform:
292
- raise Exception(f" Terraform not available: {terraform_msg}")
293
- if verbose:
294
- print(f" {terraform_msg}")
295
- else:
296
- print(" Terraform binary found")
297
-
298
- # 2. Check for configuration files
299
- print("\n Checking Terraform configuration files...")
300
- has_config, config_files = check_terraform_config_files()
301
- if not has_config:
302
- raise Exception(" No Terraform configuration files (.tf or .tf.json) found in current directory")
303
- if verbose:
304
- print(f" Found {len(config_files)} configuration files: {', '.join(config_files)}")
305
- else:
306
- print(f" Found {len(config_files)} Terraform configuration files")
307
-
308
- # 3. Check if terraform is initialized
309
- print("\n Checking Terraform initialization...")
310
- is_initialized, init_msg = check_terraform_init()
311
- if not is_initialized:
312
- raise Exception(f" {init_msg}. Run 'terraform init' first.")
313
- if verbose:
314
- print(f" {init_msg}")
315
- else:
316
- print(" Terraform is initialized")
317
-
318
- # 4. Check if the specified module exists
319
- print(f"\n Checking if module '{module_name}' exists...")
320
- module_exists, module_msg = check_module_exists(module_name)
321
- if not module_exists:
322
- raise Exception(f" {module_msg}. Please add the module to your Terraform configuration first.")
323
- if verbose:
324
- print(f" {module_msg}")
325
- else:
326
- print(f" Module '{module_name}' found")
327
-
328
- print("\n All Terraform environment checks passed!")
329
-
330
-
331
195
  def import_if_rules_to_tf(args, configuration):
332
196
  """Main function to orchestrate the import process"""
333
197
  try:
@@ -0,0 +1,45 @@
1
+ import catocli.parsers.custom.import_sites_to_tf.import_sites_to_tf as import_sites_to_tf
2
+
3
+ def site_import_parse(subparsers, import_parser):
4
+ """Add socket sites import command to existing import parser"""
5
+
6
+ if import_parser is None:
7
+ raise ValueError("Import parser not found. Make sure rule_import_parse is called before site_import_parse.")
8
+
9
+ # Get the existing subparsers from the import parser
10
+ import_subparsers = None
11
+ for action in import_parser._subparsers._group_actions:
12
+ if hasattr(action, 'choices'):
13
+ import_subparsers = action
14
+ break
15
+
16
+ if import_subparsers is None:
17
+ raise ValueError("Import subparsers not found in existing import parser.")
18
+
19
+ # Add socket_sites_to_tf command
20
+ socket_sites_parser = import_subparsers.add_parser(
21
+ 'socket_sites_to_tf',
22
+ help='Import socket sites to Terraform state',
23
+ usage='catocli import socket_sites_to_tf <json_file> --module-name <module_name> [options]\n\nexample: catocli import socket_sites_to_tf config_data/socket_sites_11484.json --module-name module.sites'
24
+ )
25
+
26
+ socket_sites_parser.add_argument('json_file', help='Path to the JSON file containing socket sites data')
27
+ socket_sites_parser.add_argument('--module-name', required=True,
28
+ help='Terraform module name to import resources into')
29
+ socket_sites_parser.add_argument('-accountID', help='Account ID (required by CLI framework but not used for import)', required=False)
30
+ socket_sites_parser.add_argument('--batch-size', type=int, default=10,
31
+ help='Number of imports per batch (default: 10)')
32
+ socket_sites_parser.add_argument('--delay', type=int, default=2,
33
+ help='Delay between batches in seconds (default: 2)')
34
+ socket_sites_parser.add_argument('--sites-only', action='store_true',
35
+ help='Import only sites, skip interfaces and network ranges')
36
+ socket_sites_parser.add_argument('--interfaces-only', action='store_true',
37
+ help='Import only WAN interfaces, skip sites and network ranges')
38
+ socket_sites_parser.add_argument('--network-ranges-only', action='store_true',
39
+ help='Import only network ranges, skip sites and interfaces')
40
+ socket_sites_parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
41
+ socket_sites_parser.add_argument('--auto-approve', action='store_true', help='Skip confirmation prompt and proceed automatically')
42
+
43
+ socket_sites_parser.set_defaults(func=import_sites_to_tf.import_socket_sites_to_tf)
44
+
45
+ return import_parser