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/__init__.py +29 -0
- ic/cli.py +769 -0
- ic/commands/__init__.py +10 -0
- ic/commands/config.py +699 -0
- ic/compat/__init__.py +241 -0
- ic/compat/cli.py +289 -0
- ic/compat/common.py +243 -0
- ic/config/__init__.py +10 -0
- ic/config/cleanup.py +382 -0
- ic/config/docs_organizer.py +587 -0
- ic/config/external.py +456 -0
- ic/config/manager.py +898 -0
- ic/config/migration.py +628 -0
- ic/config/schema.py +595 -0
- ic/config/secrets.py +437 -0
- ic/config/security.py +462 -0
- ic/core/__init__.py +16 -0
- ic/core/logging.py +311 -0
- ic/core/mcp_manager.py +856 -0
- ic/core/session.py +392 -0
- ic/core/silence_logging.py +67 -0
- ic_code-1.0.0.dist-info/METADATA +354 -0
- ic_code-1.0.0.dist-info/RECORD +27 -0
- ic_code-1.0.0.dist-info/WHEEL +5 -0
- ic_code-1.0.0.dist-info/entry_points.txt +2 -0
- ic_code-1.0.0.dist-info/licenses/LICENSE +21 -0
- ic_code-1.0.0.dist-info/top_level.txt +1 -0
ic/commands/config.py
ADDED
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management CLI commands.
|
|
3
|
+
|
|
4
|
+
This module provides CLI commands for configuration management, migration,
|
|
5
|
+
and validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import yaml
|
|
12
|
+
import json
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Dict, Any, Optional, List
|
|
15
|
+
from rich.console import Console
|
|
16
|
+
from rich.panel import Panel
|
|
17
|
+
from rich.table import Table
|
|
18
|
+
from rich.prompt import Confirm, Prompt
|
|
19
|
+
from rich.syntax import Syntax
|
|
20
|
+
|
|
21
|
+
from ..config.manager import ConfigManager
|
|
22
|
+
from ..config.security import SecurityManager
|
|
23
|
+
from ..config.migration import MigrationManager
|
|
24
|
+
from ..core.logging import ICLogger
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ConfigCommands:
|
|
28
|
+
"""Configuration management commands."""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.console = Console()
|
|
32
|
+
self.security_manager = SecurityManager()
|
|
33
|
+
self.config_manager = ConfigManager(security_manager=self.security_manager)
|
|
34
|
+
self.migration = MigrationManager()
|
|
35
|
+
|
|
36
|
+
def add_subparsers(self, parent_parser: argparse.ArgumentParser) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Add config subcommands to parent parser.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
parent_parser: Parent argument parser
|
|
42
|
+
"""
|
|
43
|
+
config_parser = parent_parser.add_parser(
|
|
44
|
+
"config",
|
|
45
|
+
help="Configuration management commands"
|
|
46
|
+
)
|
|
47
|
+
config_subparsers = config_parser.add_subparsers(
|
|
48
|
+
dest="config_command",
|
|
49
|
+
required=True,
|
|
50
|
+
help="Configuration management operations"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
# ic config init
|
|
54
|
+
init_parser = config_subparsers.add_parser(
|
|
55
|
+
"init",
|
|
56
|
+
help="Initialize secure configuration setup"
|
|
57
|
+
)
|
|
58
|
+
init_parser.add_argument(
|
|
59
|
+
"--output", "-o",
|
|
60
|
+
default="ic.yaml",
|
|
61
|
+
help="Output configuration file path (default: ic.yaml)"
|
|
62
|
+
)
|
|
63
|
+
init_parser.add_argument(
|
|
64
|
+
"--template", "-t",
|
|
65
|
+
choices=["minimal", "full", "aws", "azure", "gcp", "multi-cloud"],
|
|
66
|
+
default="minimal",
|
|
67
|
+
help="Configuration template to use (default: minimal)"
|
|
68
|
+
)
|
|
69
|
+
init_parser.add_argument(
|
|
70
|
+
"--force", "-f",
|
|
71
|
+
action="store_true",
|
|
72
|
+
help="Overwrite existing configuration file"
|
|
73
|
+
)
|
|
74
|
+
init_parser.set_defaults(func=self.init_config)
|
|
75
|
+
|
|
76
|
+
# ic config migrate
|
|
77
|
+
migrate_parser = config_subparsers.add_parser(
|
|
78
|
+
"migrate",
|
|
79
|
+
help="Migrate from .env to YAML configuration"
|
|
80
|
+
)
|
|
81
|
+
migrate_parser.add_argument(
|
|
82
|
+
"--env-file",
|
|
83
|
+
default=".env",
|
|
84
|
+
help="Source .env file path (default: .env)"
|
|
85
|
+
)
|
|
86
|
+
migrate_parser.add_argument(
|
|
87
|
+
"--output", "-o",
|
|
88
|
+
default="ic.yaml",
|
|
89
|
+
help="Output YAML configuration file (default: ic.yaml)"
|
|
90
|
+
)
|
|
91
|
+
migrate_parser.add_argument(
|
|
92
|
+
"--backup", "-b",
|
|
93
|
+
action="store_true",
|
|
94
|
+
default=True,
|
|
95
|
+
help="Create backup of existing files (default: True)"
|
|
96
|
+
)
|
|
97
|
+
migrate_parser.add_argument(
|
|
98
|
+
"--dry-run", "-n",
|
|
99
|
+
action="store_true",
|
|
100
|
+
help="Show what would be migrated without making changes"
|
|
101
|
+
)
|
|
102
|
+
migrate_parser.set_defaults(func=self.migrate_config)
|
|
103
|
+
|
|
104
|
+
# ic config validate
|
|
105
|
+
validate_parser = config_subparsers.add_parser(
|
|
106
|
+
"validate",
|
|
107
|
+
help="Validate configuration files"
|
|
108
|
+
)
|
|
109
|
+
validate_parser.add_argument(
|
|
110
|
+
"config_file",
|
|
111
|
+
nargs="?",
|
|
112
|
+
help="Configuration file to validate (default: auto-detect)"
|
|
113
|
+
)
|
|
114
|
+
validate_parser.add_argument(
|
|
115
|
+
"--security", "-s",
|
|
116
|
+
action="store_true",
|
|
117
|
+
help="Include security validation"
|
|
118
|
+
)
|
|
119
|
+
validate_parser.add_argument(
|
|
120
|
+
"--verbose", "-v",
|
|
121
|
+
action="store_true",
|
|
122
|
+
help="Show detailed validation results"
|
|
123
|
+
)
|
|
124
|
+
validate_parser.set_defaults(func=self.validate_config)
|
|
125
|
+
|
|
126
|
+
# ic config show
|
|
127
|
+
show_parser = config_subparsers.add_parser(
|
|
128
|
+
"show",
|
|
129
|
+
help="Show current configuration"
|
|
130
|
+
)
|
|
131
|
+
show_parser.add_argument(
|
|
132
|
+
"--sources", "-s",
|
|
133
|
+
action="store_true",
|
|
134
|
+
help="Show configuration sources"
|
|
135
|
+
)
|
|
136
|
+
show_parser.add_argument(
|
|
137
|
+
"--mask-sensitive", "-m",
|
|
138
|
+
action="store_true",
|
|
139
|
+
default=True,
|
|
140
|
+
help="Mask sensitive data in output (default: True)"
|
|
141
|
+
)
|
|
142
|
+
show_parser.add_argument(
|
|
143
|
+
"--format", "-f",
|
|
144
|
+
choices=["yaml", "json", "table"],
|
|
145
|
+
default="yaml",
|
|
146
|
+
help="Output format (default: yaml)"
|
|
147
|
+
)
|
|
148
|
+
show_parser.add_argument(
|
|
149
|
+
"key_path",
|
|
150
|
+
nargs="?",
|
|
151
|
+
help="Specific configuration key to show (dot notation, e.g., aws.regions)"
|
|
152
|
+
)
|
|
153
|
+
show_parser.set_defaults(func=self.show_config)
|
|
154
|
+
|
|
155
|
+
# ic config set
|
|
156
|
+
set_parser = config_subparsers.add_parser(
|
|
157
|
+
"set",
|
|
158
|
+
help="Set configuration value"
|
|
159
|
+
)
|
|
160
|
+
set_parser.add_argument(
|
|
161
|
+
"key_path",
|
|
162
|
+
help="Configuration key to set (dot notation, e.g., aws.regions)"
|
|
163
|
+
)
|
|
164
|
+
set_parser.add_argument(
|
|
165
|
+
"value",
|
|
166
|
+
help="Value to set (JSON format for complex values)"
|
|
167
|
+
)
|
|
168
|
+
set_parser.add_argument(
|
|
169
|
+
"--config-file", "-c",
|
|
170
|
+
default="ic.yaml",
|
|
171
|
+
help="Configuration file to update (default: ic.yaml)"
|
|
172
|
+
)
|
|
173
|
+
set_parser.add_argument(
|
|
174
|
+
"--create",
|
|
175
|
+
action="store_true",
|
|
176
|
+
help="Create configuration file if it doesn't exist"
|
|
177
|
+
)
|
|
178
|
+
set_parser.set_defaults(func=self.set_config)
|
|
179
|
+
|
|
180
|
+
# ic config get
|
|
181
|
+
get_parser = config_subparsers.add_parser(
|
|
182
|
+
"get",
|
|
183
|
+
help="Get configuration value"
|
|
184
|
+
)
|
|
185
|
+
get_parser.add_argument(
|
|
186
|
+
"key_path",
|
|
187
|
+
help="Configuration key to get (dot notation, e.g., aws.regions)"
|
|
188
|
+
)
|
|
189
|
+
get_parser.add_argument(
|
|
190
|
+
"--default", "-d",
|
|
191
|
+
help="Default value if key not found"
|
|
192
|
+
)
|
|
193
|
+
get_parser.add_argument(
|
|
194
|
+
"--format", "-f",
|
|
195
|
+
choices=["raw", "json", "yaml"],
|
|
196
|
+
default="raw",
|
|
197
|
+
help="Output format (default: raw)"
|
|
198
|
+
)
|
|
199
|
+
get_parser.set_defaults(func=self.get_config)
|
|
200
|
+
|
|
201
|
+
def init_config(self, args) -> None:
|
|
202
|
+
"""
|
|
203
|
+
Initialize secure configuration setup.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
args: Command line arguments
|
|
207
|
+
"""
|
|
208
|
+
output_path = Path(args.output)
|
|
209
|
+
|
|
210
|
+
# Check if file exists and not forcing
|
|
211
|
+
if output_path.exists() and not args.force:
|
|
212
|
+
if not Confirm.ask(f"Configuration file {output_path} already exists. Overwrite?"):
|
|
213
|
+
self.console.print("ā Configuration initialization cancelled.")
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
self.console.print(f"š Initializing IC configuration with template: {args.template}")
|
|
217
|
+
|
|
218
|
+
# Get template configuration
|
|
219
|
+
template_config = self._get_template_config(args.template)
|
|
220
|
+
|
|
221
|
+
# Interactive configuration if not minimal
|
|
222
|
+
if args.template != "minimal":
|
|
223
|
+
template_config = self._interactive_config_setup(template_config, args.template)
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
# Save configuration
|
|
227
|
+
self.config_manager.save_config(output_path, template_config)
|
|
228
|
+
|
|
229
|
+
# Create .env.example if it doesn't exist
|
|
230
|
+
env_example_path = Path(".env.example")
|
|
231
|
+
if not env_example_path.exists():
|
|
232
|
+
self._create_env_example(env_example_path, args.template)
|
|
233
|
+
|
|
234
|
+
# Update .gitignore
|
|
235
|
+
self._update_gitignore()
|
|
236
|
+
|
|
237
|
+
self.console.print(Panel(
|
|
238
|
+
f"ā
Configuration initialized successfully!\n\n"
|
|
239
|
+
f"š Configuration file: {output_path}\n"
|
|
240
|
+
f"š Environment example: .env.example\n"
|
|
241
|
+
f"š .gitignore updated for security\n\n"
|
|
242
|
+
f"Next steps:\n"
|
|
243
|
+
f"1. Review and customize {output_path}\n"
|
|
244
|
+
f"2. Set up environment variables (see .env.example)\n"
|
|
245
|
+
f"3. Run 'ic config validate' to verify setup",
|
|
246
|
+
title="Configuration Initialized",
|
|
247
|
+
border_style="green"
|
|
248
|
+
))
|
|
249
|
+
|
|
250
|
+
except Exception as e:
|
|
251
|
+
self.console.print(f"ā Failed to initialize configuration: {e}")
|
|
252
|
+
sys.exit(1)
|
|
253
|
+
|
|
254
|
+
def migrate_config(self, args) -> None:
|
|
255
|
+
"""
|
|
256
|
+
Migrate from .env to YAML configuration.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
args: Command line arguments
|
|
260
|
+
"""
|
|
261
|
+
env_file = Path(args.env_file)
|
|
262
|
+
output_file = Path(args.output)
|
|
263
|
+
|
|
264
|
+
if not env_file.exists():
|
|
265
|
+
self.console.print(f"ā Environment file {env_file} not found.")
|
|
266
|
+
sys.exit(1)
|
|
267
|
+
|
|
268
|
+
self.console.print(f"š Migrating configuration from {env_file} to {output_file}")
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
# Perform migration
|
|
272
|
+
if args.dry_run:
|
|
273
|
+
self.console.print("š Dry run - showing what would be migrated:")
|
|
274
|
+
# TODO: Implement dry run preview
|
|
275
|
+
result = {"success": True, "dry_run": True}
|
|
276
|
+
else:
|
|
277
|
+
success = self.migration.migrate_env_to_yaml(str(env_file), force=True)
|
|
278
|
+
result = {"success": success, "output_file": str(output_file)}
|
|
279
|
+
|
|
280
|
+
if args.dry_run:
|
|
281
|
+
self.console.print("š Dry run - showing what would be migrated:")
|
|
282
|
+
self._display_migration_preview(result)
|
|
283
|
+
else:
|
|
284
|
+
self._display_migration_result(result)
|
|
285
|
+
|
|
286
|
+
except Exception as e:
|
|
287
|
+
self.console.print(f"ā Migration failed: {e}")
|
|
288
|
+
sys.exit(1)
|
|
289
|
+
|
|
290
|
+
def validate_config(self, args) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Validate configuration files.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
args: Command line arguments
|
|
296
|
+
"""
|
|
297
|
+
if args.config_file:
|
|
298
|
+
config_file = Path(args.config_file)
|
|
299
|
+
if not config_file.exists():
|
|
300
|
+
self.console.print(f"ā Configuration file {config_file} not found.")
|
|
301
|
+
sys.exit(1)
|
|
302
|
+
config_files = [config_file]
|
|
303
|
+
else:
|
|
304
|
+
# Auto-detect configuration files
|
|
305
|
+
config_files = self._find_config_files()
|
|
306
|
+
|
|
307
|
+
if not config_files:
|
|
308
|
+
self.console.print("ā No configuration files found.")
|
|
309
|
+
sys.exit(1)
|
|
310
|
+
|
|
311
|
+
self.console.print("š Validating configuration files...")
|
|
312
|
+
|
|
313
|
+
all_valid = True
|
|
314
|
+
for config_file in config_files:
|
|
315
|
+
self.console.print(f"\nš Validating {config_file}:")
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
# Load and validate configuration
|
|
319
|
+
config_data = self.config_manager._load_config_file(config_file)
|
|
320
|
+
errors = self.config_manager.validate_config(config_data)
|
|
321
|
+
|
|
322
|
+
# Security validation if requested
|
|
323
|
+
security_warnings = []
|
|
324
|
+
if args.security:
|
|
325
|
+
security_warnings = self.security_manager.validate_config_security(config_data)
|
|
326
|
+
|
|
327
|
+
# Display results
|
|
328
|
+
if not errors and not security_warnings:
|
|
329
|
+
self.console.print(" ā
Configuration is valid")
|
|
330
|
+
else:
|
|
331
|
+
all_valid = False
|
|
332
|
+
|
|
333
|
+
if errors:
|
|
334
|
+
self.console.print(" ā Validation errors:")
|
|
335
|
+
for error in errors:
|
|
336
|
+
self.console.print(f" ⢠{error}")
|
|
337
|
+
|
|
338
|
+
if security_warnings:
|
|
339
|
+
self.console.print(" ā ļø Security warnings:")
|
|
340
|
+
for warning in security_warnings:
|
|
341
|
+
self.console.print(f" ⢠{warning}")
|
|
342
|
+
|
|
343
|
+
if args.verbose:
|
|
344
|
+
self._display_config_summary(config_data)
|
|
345
|
+
|
|
346
|
+
except Exception as e:
|
|
347
|
+
all_valid = False
|
|
348
|
+
self.console.print(f" ā Failed to validate: {e}")
|
|
349
|
+
|
|
350
|
+
if all_valid:
|
|
351
|
+
self.console.print("\nā
All configuration files are valid!")
|
|
352
|
+
else:
|
|
353
|
+
self.console.print("\nā Some configuration files have issues.")
|
|
354
|
+
sys.exit(1)
|
|
355
|
+
|
|
356
|
+
def show_config(self, args) -> None:
|
|
357
|
+
"""
|
|
358
|
+
Show current configuration.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
args: Command line arguments
|
|
362
|
+
"""
|
|
363
|
+
try:
|
|
364
|
+
# Load configuration
|
|
365
|
+
config = self.config_manager.load_config()
|
|
366
|
+
|
|
367
|
+
# Mask sensitive data if requested
|
|
368
|
+
if args.mask_sensitive:
|
|
369
|
+
config = self.security_manager.mask_sensitive_data(config)
|
|
370
|
+
|
|
371
|
+
# Show specific key if requested
|
|
372
|
+
if args.key_path:
|
|
373
|
+
value = self.config_manager.get_config_value(args.key_path)
|
|
374
|
+
if value is None:
|
|
375
|
+
self.console.print(f"ā Configuration key '{args.key_path}' not found.")
|
|
376
|
+
sys.exit(1)
|
|
377
|
+
config = {args.key_path: value}
|
|
378
|
+
|
|
379
|
+
# Display configuration
|
|
380
|
+
if args.format == "json":
|
|
381
|
+
self.console.print(json.dumps(config, indent=2))
|
|
382
|
+
elif args.format == "yaml":
|
|
383
|
+
yaml_output = yaml.dump(config, default_flow_style=False, indent=2)
|
|
384
|
+
syntax = Syntax(yaml_output, "yaml", theme="monokai", line_numbers=True)
|
|
385
|
+
self.console.print(syntax)
|
|
386
|
+
elif args.format == "table":
|
|
387
|
+
self._display_config_table(config)
|
|
388
|
+
|
|
389
|
+
# Show sources if requested
|
|
390
|
+
if args.sources:
|
|
391
|
+
sources = self.config_manager.get_config_sources()
|
|
392
|
+
self.console.print(f"\nš Configuration sources: {', '.join(sources)}")
|
|
393
|
+
|
|
394
|
+
except Exception as e:
|
|
395
|
+
self.console.print(f"ā Failed to show configuration: {e}")
|
|
396
|
+
sys.exit(1)
|
|
397
|
+
|
|
398
|
+
def set_config(self, args) -> None:
|
|
399
|
+
"""
|
|
400
|
+
Set configuration value.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
args: Command line arguments
|
|
404
|
+
"""
|
|
405
|
+
config_file = Path(args.config_file)
|
|
406
|
+
|
|
407
|
+
# Create config file if requested and doesn't exist
|
|
408
|
+
if not config_file.exists():
|
|
409
|
+
if args.create:
|
|
410
|
+
config_data = self.config_manager._get_default_config()
|
|
411
|
+
else:
|
|
412
|
+
self.console.print(f"ā Configuration file {config_file} not found. Use --create to create it.")
|
|
413
|
+
sys.exit(1)
|
|
414
|
+
else:
|
|
415
|
+
config_data = self.config_manager._load_config_file(config_file)
|
|
416
|
+
|
|
417
|
+
# Parse value (try JSON first, then string)
|
|
418
|
+
try:
|
|
419
|
+
value = json.loads(args.value)
|
|
420
|
+
except json.JSONDecodeError:
|
|
421
|
+
value = args.value
|
|
422
|
+
|
|
423
|
+
# Set the value
|
|
424
|
+
keys = args.key_path.split('.')
|
|
425
|
+
current = config_data
|
|
426
|
+
for key in keys[:-1]:
|
|
427
|
+
if key not in current:
|
|
428
|
+
current[key] = {}
|
|
429
|
+
current = current[key]
|
|
430
|
+
current[keys[-1]] = value
|
|
431
|
+
|
|
432
|
+
try:
|
|
433
|
+
# Save configuration
|
|
434
|
+
self.config_manager.safe_update_config(config_file, config_data)
|
|
435
|
+
self.console.print(f"ā
Configuration updated: {args.key_path} = {value}")
|
|
436
|
+
|
|
437
|
+
except Exception as e:
|
|
438
|
+
self.console.print(f"ā Failed to update configuration: {e}")
|
|
439
|
+
sys.exit(1)
|
|
440
|
+
|
|
441
|
+
def get_config(self, args) -> None:
|
|
442
|
+
"""
|
|
443
|
+
Get configuration value.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
args: Command line arguments
|
|
447
|
+
"""
|
|
448
|
+
try:
|
|
449
|
+
# Load configuration
|
|
450
|
+
self.config_manager.load_config()
|
|
451
|
+
|
|
452
|
+
# Get value
|
|
453
|
+
value = self.config_manager.get_config_value(args.key_path, args.default)
|
|
454
|
+
|
|
455
|
+
if value is None:
|
|
456
|
+
self.console.print(f"ā Configuration key '{args.key_path}' not found.")
|
|
457
|
+
sys.exit(1)
|
|
458
|
+
|
|
459
|
+
# Format output
|
|
460
|
+
if args.format == "json":
|
|
461
|
+
self.console.print(json.dumps(value, indent=2))
|
|
462
|
+
elif args.format == "yaml":
|
|
463
|
+
yaml_output = yaml.dump({args.key_path: value}, default_flow_style=False)
|
|
464
|
+
self.console.print(yaml_output.strip())
|
|
465
|
+
else:
|
|
466
|
+
self.console.print(str(value))
|
|
467
|
+
|
|
468
|
+
except Exception as e:
|
|
469
|
+
self.console.print(f"ā Failed to get configuration: {e}")
|
|
470
|
+
sys.exit(1)
|
|
471
|
+
|
|
472
|
+
def _get_template_config(self, template: str) -> Dict[str, Any]:
|
|
473
|
+
"""Get configuration template."""
|
|
474
|
+
base_config = self.config_manager._get_default_config()
|
|
475
|
+
|
|
476
|
+
if template == "minimal":
|
|
477
|
+
return {
|
|
478
|
+
"version": base_config["version"],
|
|
479
|
+
"logging": base_config["logging"],
|
|
480
|
+
"security": base_config["security"],
|
|
481
|
+
}
|
|
482
|
+
elif template == "aws":
|
|
483
|
+
return {
|
|
484
|
+
"version": base_config["version"],
|
|
485
|
+
"logging": base_config["logging"],
|
|
486
|
+
"aws": base_config["aws"],
|
|
487
|
+
"security": base_config["security"],
|
|
488
|
+
}
|
|
489
|
+
elif template == "azure":
|
|
490
|
+
return {
|
|
491
|
+
"version": base_config["version"],
|
|
492
|
+
"logging": base_config["logging"],
|
|
493
|
+
"azure": base_config["azure"],
|
|
494
|
+
"security": base_config["security"],
|
|
495
|
+
}
|
|
496
|
+
elif template == "gcp":
|
|
497
|
+
return {
|
|
498
|
+
"version": base_config["version"],
|
|
499
|
+
"logging": base_config["logging"],
|
|
500
|
+
"gcp": base_config["gcp"],
|
|
501
|
+
"security": base_config["security"],
|
|
502
|
+
}
|
|
503
|
+
elif template == "multi-cloud":
|
|
504
|
+
return base_config
|
|
505
|
+
else:
|
|
506
|
+
return base_config
|
|
507
|
+
|
|
508
|
+
def _interactive_config_setup(self, config: Dict[str, Any], template: str) -> Dict[str, Any]:
|
|
509
|
+
"""Interactive configuration setup."""
|
|
510
|
+
self.console.print(f"\nš§ Interactive setup for {template} template:")
|
|
511
|
+
|
|
512
|
+
if template in ["aws", "multi-cloud"]:
|
|
513
|
+
accounts = Prompt.ask("AWS Account IDs (comma-separated)", default="")
|
|
514
|
+
if accounts:
|
|
515
|
+
config["aws"]["accounts"] = [acc.strip() for acc in accounts.split(",")]
|
|
516
|
+
|
|
517
|
+
regions = Prompt.ask("AWS Regions (comma-separated)", default="ap-northeast-2")
|
|
518
|
+
config["aws"]["regions"] = [reg.strip() for reg in regions.split(",")]
|
|
519
|
+
|
|
520
|
+
if template in ["azure", "multi-cloud"]:
|
|
521
|
+
subscription_id = Prompt.ask("Azure Subscription ID", default="")
|
|
522
|
+
if subscription_id:
|
|
523
|
+
config["azure"]["subscription_id"] = subscription_id
|
|
524
|
+
|
|
525
|
+
if template in ["gcp", "multi-cloud"]:
|
|
526
|
+
project_id = Prompt.ask("GCP Project ID", default="")
|
|
527
|
+
if project_id:
|
|
528
|
+
config["gcp"]["project_id"] = project_id
|
|
529
|
+
|
|
530
|
+
return config
|
|
531
|
+
|
|
532
|
+
def _create_env_example(self, env_example_path: Path, template: str) -> None:
|
|
533
|
+
"""Create .env.example file."""
|
|
534
|
+
env_content = [
|
|
535
|
+
"# IC Configuration Environment Variables",
|
|
536
|
+
"# Copy this file to .env and fill in your actual values",
|
|
537
|
+
"# DO NOT commit .env to version control!",
|
|
538
|
+
"",
|
|
539
|
+
"# Logging Configuration",
|
|
540
|
+
"# IC_LOG_LEVEL=ERROR",
|
|
541
|
+
"# IC_LOG_FILE_LEVEL=INFO",
|
|
542
|
+
"",
|
|
543
|
+
]
|
|
544
|
+
|
|
545
|
+
if template in ["aws", "multi-cloud"]:
|
|
546
|
+
env_content.extend([
|
|
547
|
+
"# AWS Configuration",
|
|
548
|
+
"# AWS_PROFILE=your-profile-name",
|
|
549
|
+
"# AWS_ACCOUNTS=123456789012,987654321098",
|
|
550
|
+
"# AWS_REGIONS=ap-northeast-2,us-east-1",
|
|
551
|
+
"# AWS_CROSS_ACCOUNT_ROLE=OrganizationAccountAccessRole",
|
|
552
|
+
"",
|
|
553
|
+
])
|
|
554
|
+
|
|
555
|
+
if template in ["azure", "multi-cloud"]:
|
|
556
|
+
env_content.extend([
|
|
557
|
+
"# Azure Configuration",
|
|
558
|
+
"# AZURE_SUBSCRIPTION_ID=your-subscription-id",
|
|
559
|
+
"# AZURE_TENANT_ID=your-tenant-id",
|
|
560
|
+
"# AZURE_CLIENT_ID=your-client-id",
|
|
561
|
+
"# AZURE_CLIENT_SECRET=your-client-secret",
|
|
562
|
+
"",
|
|
563
|
+
])
|
|
564
|
+
|
|
565
|
+
if template in ["gcp", "multi-cloud"]:
|
|
566
|
+
env_content.extend([
|
|
567
|
+
"# GCP Configuration",
|
|
568
|
+
"# GCP_PROJECT_ID=your-project-id",
|
|
569
|
+
"# GCP_SERVICE_ACCOUNT_KEY_PATH=/path/to/service-account.json",
|
|
570
|
+
"# GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json",
|
|
571
|
+
"",
|
|
572
|
+
])
|
|
573
|
+
|
|
574
|
+
env_content.extend([
|
|
575
|
+
"# Optional: Slack Integration",
|
|
576
|
+
"# SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...",
|
|
577
|
+
"",
|
|
578
|
+
"# Optional: MCP GitHub Integration",
|
|
579
|
+
"# MCP_GITHUB_TOKEN=your-github-token",
|
|
580
|
+
])
|
|
581
|
+
|
|
582
|
+
with open(env_example_path, 'w') as f:
|
|
583
|
+
f.write('\n'.join(env_content))
|
|
584
|
+
|
|
585
|
+
def _update_gitignore(self) -> None:
|
|
586
|
+
"""Update .gitignore with security entries."""
|
|
587
|
+
gitignore_path = Path(".gitignore")
|
|
588
|
+
security_entries = self.security_manager.create_gitignore_entries()
|
|
589
|
+
|
|
590
|
+
existing_content = ""
|
|
591
|
+
if gitignore_path.exists():
|
|
592
|
+
with open(gitignore_path, 'r') as f:
|
|
593
|
+
existing_content = f.read()
|
|
594
|
+
|
|
595
|
+
# Add security entries if not already present
|
|
596
|
+
new_entries = []
|
|
597
|
+
for entry in security_entries:
|
|
598
|
+
if entry not in existing_content:
|
|
599
|
+
new_entries.append(entry)
|
|
600
|
+
|
|
601
|
+
if new_entries:
|
|
602
|
+
with open(gitignore_path, 'a') as f:
|
|
603
|
+
if existing_content and not existing_content.endswith('\n'):
|
|
604
|
+
f.write('\n')
|
|
605
|
+
f.write('\n'.join(new_entries) + '\n')
|
|
606
|
+
|
|
607
|
+
def _find_config_files(self) -> List[Path]:
|
|
608
|
+
"""Find configuration files in common locations."""
|
|
609
|
+
config_files = []
|
|
610
|
+
|
|
611
|
+
# Check common config file locations
|
|
612
|
+
possible_paths = [
|
|
613
|
+
Path("ic.yaml"),
|
|
614
|
+
Path("ic.yml"),
|
|
615
|
+
Path(".ic/config.yaml"),
|
|
616
|
+
Path(".ic/config.yml"),
|
|
617
|
+
Path("config/config.yaml"),
|
|
618
|
+
Path("config/config.yml"),
|
|
619
|
+
Path.home() / ".ic" / "config.yaml",
|
|
620
|
+
]
|
|
621
|
+
|
|
622
|
+
for path in possible_paths:
|
|
623
|
+
if path.exists():
|
|
624
|
+
config_files.append(path)
|
|
625
|
+
|
|
626
|
+
return config_files
|
|
627
|
+
|
|
628
|
+
def _display_migration_preview(self, result: Dict[str, Any]) -> None:
|
|
629
|
+
"""Display migration preview."""
|
|
630
|
+
if result.get("config_data"):
|
|
631
|
+
self.console.print("š Configuration that would be created:")
|
|
632
|
+
yaml_output = yaml.dump(result["config_data"], default_flow_style=False, indent=2)
|
|
633
|
+
syntax = Syntax(yaml_output, "yaml", theme="monokai")
|
|
634
|
+
self.console.print(syntax)
|
|
635
|
+
|
|
636
|
+
if result.get("warnings"):
|
|
637
|
+
self.console.print("\nā ļø Warnings:")
|
|
638
|
+
for warning in result["warnings"]:
|
|
639
|
+
self.console.print(f" ⢠{warning}")
|
|
640
|
+
|
|
641
|
+
def _display_migration_result(self, result: Dict[str, Any]) -> None:
|
|
642
|
+
"""Display migration result."""
|
|
643
|
+
if result.get("success"):
|
|
644
|
+
self.console.print(Panel(
|
|
645
|
+
f"ā
Migration completed successfully!\n\n"
|
|
646
|
+
f"š Configuration file: {result.get('output_file', 'ic.yaml')}\n"
|
|
647
|
+
f"š Backup created: {result.get('backup_file', 'N/A')}\n\n"
|
|
648
|
+
f"Next steps:\n"
|
|
649
|
+
f"1. Review the generated configuration file\n"
|
|
650
|
+
f"2. Remove sensitive data from the config file\n"
|
|
651
|
+
f"3. Set up environment variables for secrets\n"
|
|
652
|
+
f"4. Run 'ic config validate' to verify setup",
|
|
653
|
+
title="Migration Complete",
|
|
654
|
+
border_style="green"
|
|
655
|
+
))
|
|
656
|
+
else:
|
|
657
|
+
self.console.print(f"ā Migration failed: {result.get('error', 'Unknown error')}")
|
|
658
|
+
|
|
659
|
+
if result.get("warnings"):
|
|
660
|
+
self.console.print("\nā ļø Warnings:")
|
|
661
|
+
for warning in result["warnings"]:
|
|
662
|
+
self.console.print(f" ⢠{warning}")
|
|
663
|
+
|
|
664
|
+
def _display_config_summary(self, config_data: Dict[str, Any]) -> None:
|
|
665
|
+
"""Display configuration summary."""
|
|
666
|
+
table = Table(title="Configuration Summary")
|
|
667
|
+
table.add_column("Section", style="cyan")
|
|
668
|
+
table.add_column("Keys", style="green")
|
|
669
|
+
table.add_column("Status", style="yellow")
|
|
670
|
+
|
|
671
|
+
for section, data in config_data.items():
|
|
672
|
+
if isinstance(data, dict):
|
|
673
|
+
keys = list(data.keys())
|
|
674
|
+
status = "ā
Configured" if keys else "ā ļø Empty"
|
|
675
|
+
table.add_row(section, ", ".join(keys[:3]) + ("..." if len(keys) > 3 else ""), status)
|
|
676
|
+
|
|
677
|
+
self.console.print(table)
|
|
678
|
+
|
|
679
|
+
def _display_config_table(self, config: Dict[str, Any], prefix: str = "") -> None:
|
|
680
|
+
"""Display configuration as table."""
|
|
681
|
+
table = Table(title="Configuration")
|
|
682
|
+
table.add_column("Key", style="cyan")
|
|
683
|
+
table.add_column("Value", style="green")
|
|
684
|
+
table.add_column("Type", style="yellow")
|
|
685
|
+
|
|
686
|
+
def add_rows(data: Dict[str, Any], current_prefix: str = ""):
|
|
687
|
+
for key, value in data.items():
|
|
688
|
+
full_key = f"{current_prefix}.{key}" if current_prefix else key
|
|
689
|
+
|
|
690
|
+
if isinstance(value, dict):
|
|
691
|
+
table.add_row(full_key, "[dict]", "object")
|
|
692
|
+
add_rows(value, full_key)
|
|
693
|
+
elif isinstance(value, list):
|
|
694
|
+
table.add_row(full_key, f"[{len(value)} items]", "array")
|
|
695
|
+
else:
|
|
696
|
+
table.add_row(full_key, str(value), type(value).__name__)
|
|
697
|
+
|
|
698
|
+
add_rows(config)
|
|
699
|
+
self.console.print(table)
|