ic-code 1.0.0__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.
ic/config/external.py ADDED
@@ -0,0 +1,456 @@
1
+ """
2
+ External configuration loader module for IC.
3
+
4
+ This module provides loading of external configuration files from various cloud providers.
5
+ """
6
+
7
+ import configparser
8
+ from pathlib import Path
9
+ from typing import Dict, Any, Optional, List
10
+ import logging
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ class ExternalConfigLoader:
16
+ """
17
+ Loads external configuration files from various sources.
18
+ """
19
+
20
+ def __init__(self, config_manager=None):
21
+ """
22
+ Initialize ExternalConfigLoader.
23
+
24
+ Args:
25
+ config_manager: Reference to ConfigManager instance
26
+ """
27
+ self.config_manager = config_manager
28
+ self.external_configs: Dict[str, Any] = {}
29
+
30
+ def load_all_external_configs(self) -> Dict[str, Any]:
31
+ """
32
+ Load all external configuration files.
33
+
34
+ Returns:
35
+ Dictionary containing all external configurations
36
+ """
37
+ external_configs = {}
38
+
39
+ try:
40
+ # Load AWS configuration
41
+ aws_config = self.load_aws_config()
42
+ if aws_config:
43
+ external_configs['aws'] = aws_config
44
+
45
+ # Load OCI configuration
46
+ oci_config = self.load_oci_config()
47
+ if oci_config:
48
+ external_configs['oci'] = oci_config
49
+
50
+ # Load SSH configuration
51
+ ssh_config = self.load_ssh_config()
52
+ if ssh_config:
53
+ external_configs['ssh'] = ssh_config
54
+
55
+ # Load CloudFlare configuration
56
+ cf_config = self.load_cloudflare_config()
57
+ if cf_config:
58
+ external_configs['cloudflare'] = cf_config
59
+
60
+ except Exception as e:
61
+ logger.warning(f"Failed to load some external configurations: {e}")
62
+
63
+ self.external_configs = external_configs
64
+ return external_configs
65
+
66
+ def load_aws_config(self) -> Dict[str, Any]:
67
+ """
68
+ Load AWS configuration from ~/.aws/config and ~/.aws/credentials.
69
+
70
+ Returns:
71
+ Dictionary containing AWS configuration
72
+ """
73
+ aws_config = {}
74
+
75
+ # Load AWS config file
76
+ aws_config_path = Path.home() / ".aws" / "config"
77
+ if aws_config_path.exists():
78
+ try:
79
+ config = configparser.ConfigParser()
80
+ config.read(aws_config_path)
81
+
82
+ profiles = {}
83
+ for section_name in config.sections():
84
+ if section_name.startswith('profile '):
85
+ profile_name = section_name.split('profile ')[1]
86
+ profiles[profile_name] = dict(config[section_name])
87
+ elif section_name == 'default':
88
+ profiles['default'] = dict(config[section_name])
89
+
90
+ if profiles:
91
+ aws_config['profiles'] = profiles
92
+ logger.debug(f"Loaded {len(profiles)} AWS profiles from config")
93
+
94
+ except Exception as e:
95
+ logger.warning(f"Failed to load AWS config: {e}")
96
+ else:
97
+ logger.debug("AWS config file not found at ~/.aws/config")
98
+
99
+ # Load AWS credentials file
100
+ aws_creds_path = Path.home() / ".aws" / "credentials"
101
+ if aws_creds_path.exists():
102
+ try:
103
+ config = configparser.ConfigParser()
104
+ config.read(aws_creds_path)
105
+
106
+ credentials = {}
107
+ for section_name in config.sections():
108
+ # Don't store actual credentials, just metadata
109
+ credentials[section_name] = {
110
+ 'has_access_key': 'aws_access_key_id' in config[section_name],
111
+ 'has_secret_key': 'aws_secret_access_key' in config[section_name],
112
+ 'has_session_token': 'aws_session_token' in config[section_name]
113
+ }
114
+
115
+ if credentials:
116
+ aws_config['credentials_profiles'] = credentials
117
+ logger.debug(f"Found {len(credentials)} AWS credential profiles")
118
+
119
+ except Exception as e:
120
+ logger.warning(f"Failed to load AWS credentials metadata: {e}")
121
+ else:
122
+ logger.debug("AWS credentials file not found at ~/.aws/credentials")
123
+
124
+ return aws_config
125
+
126
+ def load_oci_config(self) -> Dict[str, Any]:
127
+ """
128
+ Load OCI configuration from ~/.oci/config.
129
+
130
+ Returns:
131
+ Dictionary containing OCI configuration
132
+ """
133
+ oci_config = {}
134
+
135
+ oci_config_path = Path.home() / ".oci" / "config"
136
+ if oci_config_path.exists():
137
+ try:
138
+ config = configparser.ConfigParser()
139
+ config.read(oci_config_path)
140
+
141
+ profiles = {}
142
+ for section_name in config.sections():
143
+ # Store non-sensitive configuration only
144
+ profile_config = {}
145
+ for key, value in config[section_name].items():
146
+ # Skip sensitive keys like private keys
147
+ if 'key' not in key.lower() or key.lower() in ['key_file', 'key_path']:
148
+ if key.lower() in ['key_file', 'key_path']:
149
+ # Just indicate that key file exists, don't store path
150
+ profile_config[key] = "***KEY_FILE_CONFIGURED***" if Path(value).exists() else "***KEY_FILE_MISSING***"
151
+ else:
152
+ profile_config[key] = value
153
+
154
+ profiles[section_name] = profile_config
155
+
156
+ if profiles:
157
+ oci_config['profiles'] = profiles
158
+ logger.debug(f"Loaded {len(profiles)} OCI profiles from config")
159
+
160
+ except Exception as e:
161
+ logger.warning(f"Failed to load OCI config: {e}")
162
+ else:
163
+ logger.debug("OCI config file not found at ~/.oci/config")
164
+
165
+ return oci_config
166
+
167
+ def load_ssh_config(self) -> Dict[str, Any]:
168
+ """
169
+ Load SSH configuration from ~/.ssh/config.
170
+
171
+ Returns:
172
+ Dictionary containing SSH configuration
173
+ """
174
+ ssh_config = {}
175
+
176
+ ssh_config_path = Path.home() / ".ssh" / "config"
177
+ if ssh_config_path.exists():
178
+ try:
179
+ hosts = {}
180
+ current_host = None
181
+
182
+ with open(ssh_config_path, 'r') as f:
183
+ for line in f:
184
+ line = line.strip()
185
+ if not line or line.startswith('#'):
186
+ continue
187
+
188
+ if line.lower().startswith('host '):
189
+ current_host = line.split(' ', 1)[1]
190
+ hosts[current_host] = {}
191
+ elif current_host and ' ' in line:
192
+ key, value = line.split(' ', 1)
193
+ key_lower = key.lower()
194
+
195
+ # Store configuration but mask sensitive information
196
+ if 'identityfile' in key_lower:
197
+ # Check if identity file exists
198
+ identity_path = Path(value).expanduser()
199
+ hosts[current_host][key_lower] = "***IDENTITY_FILE_CONFIGURED***" if identity_path.exists() else "***IDENTITY_FILE_MISSING***"
200
+ else:
201
+ hosts[current_host][key_lower] = value
202
+
203
+ if hosts:
204
+ ssh_config['hosts'] = hosts
205
+ logger.debug(f"Loaded {len(hosts)} SSH host configurations")
206
+
207
+ except Exception as e:
208
+ logger.warning(f"Failed to load SSH config: {e}")
209
+ else:
210
+ logger.debug("SSH config file not found at ~/.ssh/config")
211
+
212
+ return ssh_config
213
+
214
+ def load_cloudflare_config(self) -> Dict[str, Any]:
215
+ """
216
+ Load CloudFlare configuration from various possible locations.
217
+
218
+ Returns:
219
+ Dictionary containing CloudFlare configuration
220
+ """
221
+ cf_config = {}
222
+
223
+ # Check for CloudFlare config in various locations
224
+ possible_paths = [
225
+ Path.home() / ".cloudflare" / "config",
226
+ Path.home() / ".cloudflare" / "config.yaml",
227
+ Path.home() / ".cloudflare" / "config.yml",
228
+ Path("config") / "cloudflare.yaml",
229
+ Path("config") / "cloudflare.yml"
230
+ ]
231
+
232
+ for cf_path in possible_paths:
233
+ if cf_path.exists():
234
+ try:
235
+ if cf_path.suffix.lower() in ['.yaml', '.yml']:
236
+ # Load YAML format
237
+ import yaml
238
+ with open(cf_path, 'r') as f:
239
+ cf_data = yaml.safe_load(f) or {}
240
+
241
+ # Mask sensitive information
242
+ cf_config = self._mask_cloudflare_secrets(cf_data)
243
+
244
+ else:
245
+ # Try to parse as simple key=value format
246
+ with open(cf_path, 'r') as f:
247
+ for line in f:
248
+ line = line.strip()
249
+ if '=' in line and not line.startswith('#'):
250
+ key, value = line.split('=', 1)
251
+ key = key.strip()
252
+ value = value.strip()
253
+
254
+ # Mask sensitive values
255
+ if any(sensitive in key.lower() for sensitive in ['token', 'key', 'secret', 'password']):
256
+ cf_config[key] = "***MASKED***"
257
+ else:
258
+ cf_config[key] = value
259
+
260
+ if cf_config:
261
+ logger.debug(f"Loaded CloudFlare config from {cf_path}")
262
+ break
263
+
264
+ except Exception as e:
265
+ logger.warning(f"Failed to load CloudFlare config from {cf_path}: {e}")
266
+
267
+ if not cf_config:
268
+ logger.debug("No CloudFlare config file found")
269
+
270
+ return cf_config
271
+
272
+ def _mask_cloudflare_secrets(self, cf_data: Dict[str, Any]) -> Dict[str, Any]:
273
+ """
274
+ Mask sensitive information in CloudFlare configuration.
275
+
276
+ Args:
277
+ cf_data: CloudFlare configuration data
278
+
279
+ Returns:
280
+ Configuration with sensitive data masked
281
+ """
282
+ masked_data = {}
283
+
284
+ for key, value in cf_data.items():
285
+ if isinstance(value, dict):
286
+ masked_data[key] = self._mask_cloudflare_secrets(value)
287
+ elif isinstance(value, str) and any(sensitive in key.lower() for sensitive in ['token', 'key', 'secret', 'password']):
288
+ if value:
289
+ masked_data[key] = "***MASKED***"
290
+ else:
291
+ masked_data[key] = value
292
+ else:
293
+ masked_data[key] = value
294
+
295
+ return masked_data
296
+
297
+ def get_external_config_value(self, service: str, key_path: str, default: Any = None) -> Any:
298
+ """
299
+ Get a value from external configuration using dot notation.
300
+
301
+ Args:
302
+ service: Service name (aws, oci, ssh, cloudflare)
303
+ key_path: Dot-separated path to configuration value
304
+ default: Default value if key is not found
305
+
306
+ Returns:
307
+ Configuration value or default
308
+ """
309
+ if service not in self.external_configs:
310
+ return default
311
+
312
+ keys = key_path.split('.')
313
+ current = self.external_configs[service]
314
+
315
+ try:
316
+ for key in keys:
317
+ current = current[key]
318
+ return current
319
+ except (KeyError, TypeError):
320
+ return default
321
+
322
+ def has_external_config(self, service: str) -> bool:
323
+ """
324
+ Check if external configuration exists for a service.
325
+
326
+ Args:
327
+ service: Service name to check
328
+
329
+ Returns:
330
+ True if external configuration exists
331
+ """
332
+ return service in self.external_configs and bool(self.external_configs[service])
333
+
334
+ def get_aws_profile_names(self) -> list:
335
+ """
336
+ Get list of available AWS profile names.
337
+
338
+ Returns:
339
+ List of AWS profile names
340
+ """
341
+ aws_config = self.external_configs.get('aws', {})
342
+ profiles = aws_config.get('profiles', {})
343
+ return list(profiles.keys())
344
+
345
+ def get_oci_profile_names(self) -> list:
346
+ """
347
+ Get list of available OCI profile names.
348
+
349
+ Returns:
350
+ List of OCI profile names
351
+ """
352
+ oci_config = self.external_configs.get('oci', {})
353
+ profiles = oci_config.get('profiles', {})
354
+ return list(profiles.keys())
355
+
356
+ def get_ssh_host_names(self) -> list:
357
+ """
358
+ Get list of configured SSH host names.
359
+
360
+ Returns:
361
+ List of SSH host names
362
+ """
363
+ ssh_config = self.external_configs.get('ssh', {})
364
+ hosts = ssh_config.get('hosts', {})
365
+ return list(hosts.keys())
366
+
367
+ def validate_external_configs(self) -> Dict[str, List[str]]:
368
+ """
369
+ Validate external configurations and return any issues found.
370
+
371
+ Returns:
372
+ Dictionary mapping service names to lists of validation issues
373
+ """
374
+ issues = {}
375
+
376
+ # Validate AWS configuration
377
+ aws_issues = self._validate_aws_config()
378
+ if aws_issues:
379
+ issues['aws'] = aws_issues
380
+
381
+ # Validate OCI configuration
382
+ oci_issues = self._validate_oci_config()
383
+ if oci_issues:
384
+ issues['oci'] = oci_issues
385
+
386
+ # Validate SSH configuration
387
+ ssh_issues = self._validate_ssh_config()
388
+ if ssh_issues:
389
+ issues['ssh'] = ssh_issues
390
+
391
+ return issues
392
+
393
+ def _validate_aws_config(self) -> List[str]:
394
+ """Validate AWS configuration."""
395
+ issues = []
396
+ aws_config = self.external_configs.get('aws', {})
397
+
398
+ if not aws_config:
399
+ issues.append("No AWS configuration found")
400
+ return issues
401
+
402
+ profiles = aws_config.get('profiles', {})
403
+ credentials = aws_config.get('credentials_profiles', {})
404
+
405
+ if not profiles and not credentials:
406
+ issues.append("No AWS profiles or credentials found")
407
+
408
+ # Check for profiles without corresponding credentials
409
+ for profile_name in profiles.keys():
410
+ if profile_name not in credentials:
411
+ issues.append(f"AWS profile '{profile_name}' has no corresponding credentials")
412
+
413
+ return issues
414
+
415
+ def _validate_oci_config(self) -> List[str]:
416
+ """Validate OCI configuration."""
417
+ issues = []
418
+ oci_config = self.external_configs.get('oci', {})
419
+
420
+ if not oci_config:
421
+ issues.append("No OCI configuration found")
422
+ return issues
423
+
424
+ profiles = oci_config.get('profiles', {})
425
+
426
+ for profile_name, profile_config in profiles.items():
427
+ required_fields = ['user', 'fingerprint', 'tenancy', 'region']
428
+ for field in required_fields:
429
+ if field not in profile_config:
430
+ issues.append(f"OCI profile '{profile_name}' missing required field '{field}'")
431
+
432
+ # Check if key file is configured and exists
433
+ key_file_status = profile_config.get('key_file', '')
434
+ if 'MISSING' in key_file_status:
435
+ issues.append(f"OCI profile '{profile_name}' key file is missing")
436
+
437
+ return issues
438
+
439
+ def _validate_ssh_config(self) -> List[str]:
440
+ """Validate SSH configuration."""
441
+ issues = []
442
+ ssh_config = self.external_configs.get('ssh', {})
443
+
444
+ if not ssh_config:
445
+ issues.append("No SSH configuration found")
446
+ return issues
447
+
448
+ hosts = ssh_config.get('hosts', {})
449
+
450
+ for host_name, host_config in hosts.items():
451
+ # Check if identity file exists
452
+ identity_status = host_config.get('identityfile', '')
453
+ if 'MISSING' in identity_status:
454
+ issues.append(f"SSH host '{host_name}' identity file is missing")
455
+
456
+ return issues