reverse-diagrams 1.3.3__py3-none-any.whl → 1.3.5__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.
- reverse_diagrams-1.3.5.dist-info/METADATA +706 -0
- reverse_diagrams-1.3.5.dist-info/RECORD +35 -0
- {reverse_diagrams-1.3.3.dist-info → reverse_diagrams-1.3.5.dist-info}/WHEEL +1 -1
- src/aws/client_manager.py +217 -0
- src/aws/describe_identity_store.py +8 -0
- src/aws/describe_organization.py +324 -445
- src/aws/describe_sso.py +170 -142
- src/aws/exceptions.py +26 -0
- src/config.py +153 -0
- src/models.py +242 -0
- src/plugins/__init__.py +12 -0
- src/plugins/base.py +292 -0
- src/plugins/builtin/__init__.py +12 -0
- src/plugins/builtin/ec2_plugin.py +228 -0
- src/plugins/builtin/identity_center_plugin.py +496 -0
- src/plugins/builtin/organizations_plugin.py +376 -0
- src/plugins/registry.py +126 -0
- src/reports/console_view.py +57 -19
- src/reports/save_results.py +210 -15
- src/reverse_diagrams.py +332 -39
- src/utils/__init__.py +1 -0
- src/utils/cache.py +274 -0
- src/utils/concurrent.py +361 -0
- src/utils/progress.py +257 -0
- src/version.py +1 -1
- reverse_diagrams-1.3.3.dist-info/METADATA +0 -247
- reverse_diagrams-1.3.3.dist-info/RECORD +0 -21
- src/reports/tes.py +0 -366
- {reverse_diagrams-1.3.3.dist-info → reverse_diagrams-1.3.5.dist-info}/entry_points.txt +0 -0
- {reverse_diagrams-1.3.3.dist-info → reverse_diagrams-1.3.5.dist-info}/licenses/LICENSE +0 -0
src/reports/save_results.py
CHANGED
|
@@ -1,26 +1,221 @@
|
|
|
1
|
-
"""Save results."""
|
|
1
|
+
"""Save results with enhanced error handling and validation."""
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import Any, Union, Optional
|
|
6
|
+
import os
|
|
5
7
|
|
|
6
|
-
from
|
|
8
|
+
from ..config import get_config
|
|
9
|
+
from ..utils.progress import get_progress_tracker
|
|
7
10
|
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
|
|
14
|
+
def save_results(
|
|
15
|
+
results: Any,
|
|
16
|
+
filename: str,
|
|
17
|
+
directory_path: Union[str, Path] = ".",
|
|
18
|
+
indent: Optional[int] = None,
|
|
19
|
+
validate_json: bool = True
|
|
20
|
+
) -> bool:
|
|
10
21
|
"""
|
|
11
|
-
Save results to a file.
|
|
22
|
+
Save results to a JSON file with proper error handling.
|
|
12
23
|
|
|
13
|
-
:
|
|
14
|
-
|
|
15
|
-
|
|
24
|
+
Args:
|
|
25
|
+
results: Data to save
|
|
26
|
+
filename: Output filename
|
|
27
|
+
directory_path: Directory to save the file
|
|
28
|
+
indent: JSON indentation (uses config default if None)
|
|
29
|
+
validate_json: Whether to validate JSON before saving
|
|
16
30
|
|
|
17
|
-
:
|
|
31
|
+
Returns:
|
|
32
|
+
True if successful, False otherwise
|
|
18
33
|
"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
config = get_config()
|
|
35
|
+
progress = get_progress_tracker()
|
|
36
|
+
|
|
37
|
+
if indent is None:
|
|
38
|
+
indent = config.output.json_indent
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
# Ensure directory exists
|
|
42
|
+
directory = Path(directory_path)
|
|
43
|
+
if config.output.create_directories:
|
|
44
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
|
|
46
|
+
# Validate directory is writable
|
|
47
|
+
if not directory.exists():
|
|
48
|
+
raise FileNotFoundError(f"Directory {directory} does not exist")
|
|
49
|
+
|
|
50
|
+
if not os.access(directory, os.W_OK):
|
|
51
|
+
raise PermissionError(f"No write permission for directory {directory}")
|
|
52
|
+
|
|
53
|
+
# Prepare file path
|
|
54
|
+
file_path = directory / filename
|
|
55
|
+
|
|
56
|
+
# Validate JSON serialization if requested
|
|
57
|
+
if validate_json:
|
|
58
|
+
try:
|
|
59
|
+
json.dumps(results, indent=indent, default=str)
|
|
60
|
+
except (TypeError, ValueError) as e:
|
|
61
|
+
raise ValueError(f"Data is not JSON serializable: {e}")
|
|
62
|
+
|
|
63
|
+
# Write file
|
|
64
|
+
with file_path.open('w', encoding='utf-8') as f:
|
|
65
|
+
json.dump(results, f, indent=indent, default=str, ensure_ascii=False)
|
|
66
|
+
|
|
67
|
+
# Set file permissions
|
|
68
|
+
try:
|
|
69
|
+
os.chmod(file_path, config.output.file_permissions)
|
|
70
|
+
except OSError as e:
|
|
71
|
+
logger.warning(f"Could not set file permissions for {file_path}: {e}")
|
|
72
|
+
|
|
73
|
+
# Log success
|
|
74
|
+
file_size = file_path.stat().st_size
|
|
75
|
+
logger.debug(f"Saved {filename} ({file_size} bytes) to {directory}")
|
|
76
|
+
|
|
77
|
+
progress.show_success(
|
|
78
|
+
f"💾 Data saved successfully",
|
|
79
|
+
f"File: {file_path}\nSize: {file_size:,} bytes"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.error(f"Failed to save {filename}: {e}")
|
|
86
|
+
progress.show_error(
|
|
87
|
+
f"Failed to save {filename}",
|
|
88
|
+
f"Error: {e}\nDirectory: {directory_path}"
|
|
26
89
|
)
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def load_results(
|
|
94
|
+
filename: str,
|
|
95
|
+
directory_path: Union[str, Path] = ".",
|
|
96
|
+
validate_exists: bool = True
|
|
97
|
+
) -> Optional[Any]:
|
|
98
|
+
"""
|
|
99
|
+
Load results from a JSON file with error handling.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
filename: Input filename
|
|
103
|
+
directory_path: Directory containing the file
|
|
104
|
+
validate_exists: Whether to validate file exists
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Loaded data or None if failed
|
|
108
|
+
"""
|
|
109
|
+
progress = get_progress_tracker()
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
file_path = Path(directory_path) / filename
|
|
113
|
+
|
|
114
|
+
if validate_exists and not file_path.exists():
|
|
115
|
+
raise FileNotFoundError(f"File {file_path} does not exist")
|
|
116
|
+
|
|
117
|
+
if not os.access(file_path, os.R_OK):
|
|
118
|
+
raise PermissionError(f"No read permission for file {file_path}")
|
|
119
|
+
|
|
120
|
+
with file_path.open('r', encoding='utf-8') as f:
|
|
121
|
+
data = json.load(f)
|
|
122
|
+
|
|
123
|
+
file_size = file_path.stat().st_size
|
|
124
|
+
logger.info(f"Loaded {filename} ({file_size} bytes) from {directory_path}")
|
|
125
|
+
|
|
126
|
+
return data
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
logger.error(f"Failed to load {filename}: {e}")
|
|
130
|
+
progress.show_error(
|
|
131
|
+
f"Failed to load {filename}",
|
|
132
|
+
f"Error: {e}\nPath: {Path(directory_path) / filename}"
|
|
133
|
+
)
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def backup_file(file_path: Union[str, Path], max_backups: int = 5) -> bool:
|
|
138
|
+
"""
|
|
139
|
+
Create a backup of an existing file.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
file_path: Path to file to backup
|
|
143
|
+
max_backups: Maximum number of backups to keep
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
True if backup created successfully, False otherwise
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
file_path = Path(file_path)
|
|
150
|
+
|
|
151
|
+
if not file_path.exists():
|
|
152
|
+
return True # No file to backup
|
|
153
|
+
|
|
154
|
+
# Create backup filename
|
|
155
|
+
backup_path = file_path.with_suffix(f"{file_path.suffix}.backup")
|
|
156
|
+
|
|
157
|
+
# If backup already exists, rotate backups
|
|
158
|
+
if backup_path.exists():
|
|
159
|
+
for i in range(max_backups - 1, 0, -1):
|
|
160
|
+
old_backup = file_path.with_suffix(f"{file_path.suffix}.backup.{i}")
|
|
161
|
+
new_backup = file_path.with_suffix(f"{file_path.suffix}.backup.{i + 1}")
|
|
162
|
+
|
|
163
|
+
if old_backup.exists():
|
|
164
|
+
if new_backup.exists():
|
|
165
|
+
new_backup.unlink()
|
|
166
|
+
old_backup.rename(new_backup)
|
|
167
|
+
|
|
168
|
+
# Move current backup to .backup.1
|
|
169
|
+
backup_1 = file_path.with_suffix(f"{file_path.suffix}.backup.1")
|
|
170
|
+
if backup_1.exists():
|
|
171
|
+
backup_1.unlink()
|
|
172
|
+
backup_path.rename(backup_1)
|
|
173
|
+
|
|
174
|
+
# Create new backup
|
|
175
|
+
import shutil
|
|
176
|
+
shutil.copy2(file_path, backup_path)
|
|
177
|
+
|
|
178
|
+
logger.debug(f"Created backup: {backup_path}")
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
logger.warning(f"Failed to create backup for {file_path}: {e}")
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def ensure_directory_structure(base_path: Union[str, Path]) -> bool:
|
|
187
|
+
"""
|
|
188
|
+
Ensure the standard directory structure exists.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
base_path: Base directory path
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
True if structure created successfully, False otherwise
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
base_path = Path(base_path)
|
|
198
|
+
|
|
199
|
+
# Standard directories
|
|
200
|
+
directories = [
|
|
201
|
+
base_path,
|
|
202
|
+
base_path / "json",
|
|
203
|
+
base_path / "code",
|
|
204
|
+
base_path / "images"
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
for directory in directories:
|
|
208
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
209
|
+
|
|
210
|
+
# Set permissions
|
|
211
|
+
try:
|
|
212
|
+
os.chmod(directory, 0o755)
|
|
213
|
+
except OSError:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
logger.info(f"Directory structure ensured at {base_path}")
|
|
217
|
+
return True
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
logger.error(f"Failed to create directory structure at {base_path}: {e}")
|
|
221
|
+
return False
|
src/reverse_diagrams.py
CHANGED
|
@@ -1,23 +1,95 @@
|
|
|
1
1
|
"""Create graphs."""
|
|
2
2
|
import argparse
|
|
3
3
|
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
|
|
5
7
|
import argcomplete
|
|
6
|
-
from
|
|
8
|
+
from colorama import Fore
|
|
7
9
|
|
|
8
10
|
from .aws.describe_identity_store import graph_identity_center
|
|
9
11
|
from .aws.describe_organization import graph_organizations
|
|
12
|
+
from .aws.exceptions import AWSError, AWSCredentialsError, AWSPermissionError
|
|
10
13
|
from .banner.banner import get_version
|
|
14
|
+
from .config import get_config, Config
|
|
11
15
|
from .reports.console_view import watch_on_demand
|
|
12
16
|
from .version import __version__
|
|
17
|
+
from .utils.progress import get_progress_tracker
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def validate_arguments(args) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Validate command line arguments.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
args: Parsed command line arguments
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
ValueError: If arguments are invalid
|
|
29
|
+
"""
|
|
30
|
+
# Validate region format
|
|
31
|
+
if args.region and not args.region.replace('-', '').replace('_', '').isalnum():
|
|
32
|
+
raise ValueError(f"Invalid AWS region format: {args.region}")
|
|
33
|
+
|
|
34
|
+
# Validate output directory path
|
|
35
|
+
if args.output_dir_path:
|
|
36
|
+
try:
|
|
37
|
+
Path(args.output_dir_path).resolve()
|
|
38
|
+
except Exception as e:
|
|
39
|
+
raise ValueError(f"Invalid output directory path: {e}")
|
|
40
|
+
|
|
41
|
+
# Ensure at least one operation is selected
|
|
42
|
+
if not any([args.graph_organization, args.graph_identity, args.commands == "watch", args.list_plugins, args.plugins]):
|
|
43
|
+
if not args.version:
|
|
44
|
+
raise ValueError("Please specify at least one operation: -o, -i, or watch command")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def setup_logging_from_args(args, config: Config) -> None:
|
|
48
|
+
"""Setup logging based on command line arguments and configuration."""
|
|
49
|
+
if args.debug:
|
|
50
|
+
config.logging.level = "DEBUG"
|
|
51
|
+
config.setup_logging()
|
|
52
|
+
logging.info("Debug mode enabled")
|
|
53
|
+
else:
|
|
54
|
+
config.setup_logging()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def handle_aws_errors(func):
|
|
58
|
+
"""Decorator to handle AWS-specific errors gracefully."""
|
|
59
|
+
def wrapper(*args, **kwargs):
|
|
60
|
+
try:
|
|
61
|
+
return func(*args, **kwargs)
|
|
62
|
+
except AWSCredentialsError as e:
|
|
63
|
+
logging.error(f"{Fore.RED}❌ AWS Credentials Error: {e}{Fore.RESET}")
|
|
64
|
+
logging.error(f"{Fore.YELLOW}💡 Please check your AWS credentials and try again.{Fore.RESET}")
|
|
65
|
+
sys.exit(1)
|
|
66
|
+
except AWSPermissionError as e:
|
|
67
|
+
logging.error(f"{Fore.RED}❌ AWS Permission Error: {e}{Fore.RESET}")
|
|
68
|
+
logging.error(f"{Fore.YELLOW}💡 Please check your AWS permissions and try again.{Fore.RESET}")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
except AWSError as e:
|
|
71
|
+
logging.error(f"{Fore.RED}❌ AWS Error: {e}{Fore.RESET}")
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logging.error(f"{Fore.RED}❌ Unexpected error: {e}{Fore.RESET}")
|
|
75
|
+
if logging.getLogger().isEnabledFor(logging.DEBUG):
|
|
76
|
+
logging.exception("Full traceback:")
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
return wrapper
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@handle_aws_errors
|
|
13
82
|
|
|
14
83
|
|
|
15
84
|
def main() -> int:
|
|
16
85
|
"""
|
|
17
|
-
|
|
86
|
+
Create architecture diagram from your current state.
|
|
18
87
|
|
|
19
|
-
:return:
|
|
88
|
+
:return: Exit code
|
|
20
89
|
"""
|
|
90
|
+
# Load configuration
|
|
91
|
+
config = get_config()
|
|
92
|
+
|
|
21
93
|
# Initialize parser
|
|
22
94
|
parser = argparse.ArgumentParser(
|
|
23
95
|
prog="reverse_diagrams",
|
|
@@ -32,9 +104,9 @@ def main() -> int:
|
|
|
32
104
|
"-od",
|
|
33
105
|
"--output_dir_path",
|
|
34
106
|
help="Name of folder to save the diagrams python code files",
|
|
35
|
-
default=
|
|
107
|
+
default=config.output.default_output_dir,
|
|
36
108
|
)
|
|
37
|
-
parser.add_argument("-r", "--region", help="AWS region", default="us-east-
|
|
109
|
+
parser.add_argument("-r", "--region", help="AWS region", default="us-east-1")
|
|
38
110
|
parser.add_argument(
|
|
39
111
|
"-o",
|
|
40
112
|
"--graph_organization",
|
|
@@ -54,6 +126,24 @@ def main() -> int:
|
|
|
54
126
|
action="store_true",
|
|
55
127
|
default=True,
|
|
56
128
|
)
|
|
129
|
+
parser.add_argument(
|
|
130
|
+
"--plugin",
|
|
131
|
+
help="Use specific plugin for diagram generation (e.g., ec2, rds)",
|
|
132
|
+
action="append",
|
|
133
|
+
dest="plugins"
|
|
134
|
+
)
|
|
135
|
+
parser.add_argument(
|
|
136
|
+
"--list-plugins",
|
|
137
|
+
help="List available plugins",
|
|
138
|
+
action="store_true"
|
|
139
|
+
)
|
|
140
|
+
parser.add_argument(
|
|
141
|
+
"--concurrent",
|
|
142
|
+
help="Enable concurrent processing for better performance",
|
|
143
|
+
action="store_true",
|
|
144
|
+
default=True
|
|
145
|
+
)
|
|
146
|
+
|
|
57
147
|
# Create subparsers
|
|
58
148
|
subparsers = parser.add_subparsers(
|
|
59
149
|
dest="commands",
|
|
@@ -61,14 +151,16 @@ def main() -> int:
|
|
|
61
151
|
help="%(prog)s Commands",
|
|
62
152
|
description="Command and functionalities",
|
|
63
153
|
)
|
|
64
|
-
|
|
154
|
+
|
|
155
|
+
# Create watch subcommand options
|
|
65
156
|
watch_parser = subparsers.add_parser(
|
|
66
157
|
name="watch",
|
|
67
158
|
description="Create view of diagrams in console based on kind of diagram and json file.",
|
|
68
159
|
help="Create pretty console view: \n"
|
|
69
160
|
"For example: %(prog)s watch -wi diagrams/json/account_assignments.json ",
|
|
70
161
|
)
|
|
71
|
-
|
|
162
|
+
|
|
163
|
+
# Add watch options
|
|
72
164
|
watch_group = watch_parser.add_argument_group(
|
|
73
165
|
"Create view of diagrams in console based on kind of diagram and json file."
|
|
74
166
|
)
|
|
@@ -94,41 +186,242 @@ def main() -> int:
|
|
|
94
186
|
parser.add_argument("-v", "--version", help="Show version", action="store_true")
|
|
95
187
|
parser.add_argument("-d", "--debug", help="Debug Mode", action="store_true")
|
|
96
188
|
|
|
97
|
-
# Read arguments from command line
|
|
98
|
-
args = parser.parse_args()
|
|
99
189
|
# Add autocomplete
|
|
100
190
|
argcomplete.autocomplete(parser)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
191
|
+
|
|
192
|
+
# Read arguments from command line
|
|
193
|
+
args = parser.parse_args()
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
# Validate arguments
|
|
197
|
+
validate_arguments(args)
|
|
198
|
+
|
|
199
|
+
# Setup logging
|
|
200
|
+
setup_logging_from_args(args, config)
|
|
201
|
+
|
|
202
|
+
logging.debug(f"Starting reverse_diagrams with arguments: {vars(args)}")
|
|
203
|
+
|
|
204
|
+
# Handle list plugins request
|
|
205
|
+
if args.list_plugins:
|
|
206
|
+
from .plugins.registry import get_plugin_manager
|
|
207
|
+
manager = get_plugin_manager()
|
|
208
|
+
plugins = manager.list_available_plugins()
|
|
209
|
+
|
|
210
|
+
progress = get_progress_tracker()
|
|
211
|
+
if plugins:
|
|
212
|
+
progress.show_summary(
|
|
213
|
+
"Available Plugins",
|
|
214
|
+
[f"{p.name} v{p.version} - {p.description}" for p in plugins]
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
progress.show_warning("No plugins available", "Install plugins or check plugin directories")
|
|
218
|
+
return 0
|
|
219
|
+
|
|
220
|
+
# Handle plugin-based diagram generation
|
|
221
|
+
if args.plugins:
|
|
222
|
+
from .plugins.registry import get_plugin_manager
|
|
223
|
+
from .models import DiagramConfig
|
|
224
|
+
from .aws.client_manager import get_client_manager
|
|
225
|
+
|
|
226
|
+
manager = get_plugin_manager()
|
|
227
|
+
client_manager = get_client_manager(region=args.region, profile=args.profile)
|
|
228
|
+
progress = get_progress_tracker()
|
|
229
|
+
|
|
230
|
+
# Create output directories
|
|
231
|
+
output_path = Path(args.output_dir_path)
|
|
232
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
233
|
+
(output_path / "json").mkdir(exist_ok=True)
|
|
234
|
+
(output_path / "code").mkdir(exist_ok=True)
|
|
235
|
+
|
|
236
|
+
for plugin_name in args.plugins:
|
|
237
|
+
try:
|
|
238
|
+
plugin = manager.load_plugin(plugin_name, client_manager)
|
|
239
|
+
|
|
240
|
+
# Collect data
|
|
241
|
+
progress.show_success(f"🔌 Running {plugin_name} plugin")
|
|
242
|
+
data = plugin.collect_data(client_manager, args.region)
|
|
243
|
+
|
|
244
|
+
# Generate diagram
|
|
245
|
+
diagram_config = DiagramConfig(
|
|
246
|
+
title=f"{plugin_name.upper()} Infrastructure",
|
|
247
|
+
direction="TB",
|
|
248
|
+
output_format="png"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
diagram_code = plugin.generate_diagram_code(data, diagram_config)
|
|
252
|
+
|
|
253
|
+
# Save diagram code
|
|
254
|
+
plugin_file = output_path / "code" / f"graph_{plugin_name}.py"
|
|
255
|
+
plugin_file.write_text(diagram_code)
|
|
256
|
+
|
|
257
|
+
# Save data
|
|
258
|
+
data_file = output_path / "json" / f"{plugin_name}_data.json"
|
|
259
|
+
import json
|
|
260
|
+
data_file.write_text(json.dumps(data, indent=2, default=str))
|
|
261
|
+
|
|
262
|
+
if args.auto_create:
|
|
263
|
+
import os
|
|
264
|
+
command = os.system(f"cd {output_path / 'code'} && python3 graph_{plugin_name}.py")
|
|
265
|
+
if command == 0:
|
|
266
|
+
progress.show_success(f"✅ {plugin_name} diagram created successfully")
|
|
267
|
+
else:
|
|
268
|
+
progress.show_error(f"Failed to create {plugin_name} diagram", f"Exit code: {command}")
|
|
269
|
+
|
|
270
|
+
except Exception as e:
|
|
271
|
+
progress.show_error(f"Plugin {plugin_name} failed", str(e))
|
|
272
|
+
if not args.debug:
|
|
273
|
+
continue
|
|
274
|
+
raise
|
|
275
|
+
|
|
276
|
+
return 0
|
|
277
|
+
|
|
278
|
+
# Handle version request
|
|
279
|
+
if args.version:
|
|
280
|
+
get_version(version=__version__)
|
|
281
|
+
return 0
|
|
282
|
+
|
|
283
|
+
# Handle watch command
|
|
284
|
+
if args.commands == "watch":
|
|
285
|
+
watch_on_demand(args=args)
|
|
286
|
+
return 0
|
|
287
|
+
|
|
288
|
+
# Setup AWS client manager
|
|
289
|
+
from .aws.client_manager import get_client_manager
|
|
290
|
+
client_manager = get_client_manager(region=args.region, profile=args.profile)
|
|
291
|
+
|
|
292
|
+
diagrams_path = args.output_dir_path
|
|
293
|
+
region = args.region
|
|
294
|
+
|
|
295
|
+
# Create output directories
|
|
296
|
+
output_path = Path(diagrams_path)
|
|
297
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
298
|
+
(output_path / "json").mkdir(exist_ok=True)
|
|
299
|
+
(output_path / "code").mkdir(exist_ok=True)
|
|
300
|
+
|
|
301
|
+
logging.info(f"Output directory: {output_path.absolute()}")
|
|
302
|
+
|
|
303
|
+
# Execute requested operations using plugins
|
|
304
|
+
if args.graph_organization:
|
|
305
|
+
logging.info("Starting AWS Organizations diagram generation using plugin")
|
|
306
|
+
progress = get_progress_tracker()
|
|
307
|
+
try:
|
|
308
|
+
from .plugins.registry import get_plugin_manager
|
|
309
|
+
from .models import DiagramConfig
|
|
310
|
+
|
|
311
|
+
manager = get_plugin_manager()
|
|
312
|
+
plugin = manager.load_plugin("organizations", client_manager)
|
|
313
|
+
|
|
314
|
+
# Collect data
|
|
315
|
+
progress.show_success("🏢 Running Organizations plugin")
|
|
316
|
+
data = plugin.collect_data(client_manager, args.region)
|
|
317
|
+
|
|
318
|
+
# Generate diagram
|
|
319
|
+
diagram_config = DiagramConfig(
|
|
320
|
+
title="Organizations Structure",
|
|
321
|
+
direction="TB",
|
|
322
|
+
output_format="png"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
diagram_code = plugin.generate_diagram_code(data, diagram_config)
|
|
326
|
+
|
|
327
|
+
# Save diagram code
|
|
328
|
+
org_file = output_path / "code" / "graph_org.py"
|
|
329
|
+
org_file.write_text(diagram_code)
|
|
330
|
+
|
|
331
|
+
# Save data
|
|
332
|
+
from .reports.save_results import save_results
|
|
333
|
+
save_results(data["accounts"], "organizations.json", str(output_path / "json"))
|
|
334
|
+
save_results(data["organizations_complete"], "organizations_complete.json", str(output_path / "json"))
|
|
335
|
+
|
|
336
|
+
if args.auto_create:
|
|
337
|
+
import os
|
|
338
|
+
command = os.system(f"cd {output_path / 'code'} && python3 graph_org.py")
|
|
339
|
+
if command == 0:
|
|
340
|
+
progress.show_success("✅ Organizations diagram created successfully")
|
|
341
|
+
else:
|
|
342
|
+
progress.show_error("Failed to create Organizations diagram", f"Exit code: {command}")
|
|
343
|
+
|
|
344
|
+
logging.info("AWS Organizations diagram generation completed")
|
|
345
|
+
|
|
346
|
+
except Exception as e:
|
|
347
|
+
logging.error(f"Organizations plugin failed: {e}")
|
|
348
|
+
# Fallback to original implementation with same client manager
|
|
349
|
+
logging.info("Falling back to original Organizations implementation")
|
|
350
|
+
# Pass the region and profile to ensure credentials are used
|
|
351
|
+
import os
|
|
352
|
+
os.environ['AWS_PROFILE'] = args.profile if args.profile else ''
|
|
353
|
+
graph_organizations(
|
|
354
|
+
diagrams_path=diagrams_path, region=region, auto=args.auto_create
|
|
355
|
+
)
|
|
130
356
|
|
|
131
|
-
|
|
357
|
+
if args.graph_identity:
|
|
358
|
+
logging.info("Starting IAM Identity Center diagram generation using plugin")
|
|
359
|
+
progress = get_progress_tracker()
|
|
360
|
+
try:
|
|
361
|
+
from .plugins.registry import get_plugin_manager
|
|
362
|
+
from .models import DiagramConfig
|
|
363
|
+
|
|
364
|
+
manager = get_plugin_manager()
|
|
365
|
+
plugin = manager.load_plugin("identity-center", client_manager)
|
|
366
|
+
|
|
367
|
+
# Collect data
|
|
368
|
+
progress.show_success("🔐 Running Identity Center plugin")
|
|
369
|
+
data = plugin.collect_data(client_manager, args.region)
|
|
370
|
+
|
|
371
|
+
# Generate diagram
|
|
372
|
+
diagram_config = DiagramConfig(
|
|
373
|
+
title="IAM Identity Center",
|
|
374
|
+
direction="LR",
|
|
375
|
+
output_format="png"
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
diagram_code = plugin.generate_diagram_code(data, diagram_config)
|
|
379
|
+
|
|
380
|
+
# Save diagram code
|
|
381
|
+
sso_file = output_path / "code" / "graph_sso_complete.py"
|
|
382
|
+
sso_file.write_text(diagram_code)
|
|
383
|
+
|
|
384
|
+
# Save data
|
|
385
|
+
from .reports.save_results import save_results
|
|
386
|
+
save_results(data["final_account_assignments"], "account_assignments.json", str(output_path / "json"))
|
|
387
|
+
save_results(data["group_memberships"], "groups.json", str(output_path / "json"))
|
|
388
|
+
|
|
389
|
+
if args.auto_create:
|
|
390
|
+
import os
|
|
391
|
+
command = os.system(f"cd {output_path / 'code'} && python3 graph_sso_complete.py")
|
|
392
|
+
if command == 0:
|
|
393
|
+
progress.show_success("✅ Identity Center diagram created successfully")
|
|
394
|
+
else:
|
|
395
|
+
progress.show_error("Failed to create Identity Center diagram", f"Exit code: {command}")
|
|
396
|
+
|
|
397
|
+
logging.info("IAM Identity Center diagram generation completed")
|
|
398
|
+
|
|
399
|
+
except Exception as e:
|
|
400
|
+
logging.error(f"Identity Center plugin failed: {e}")
|
|
401
|
+
# Fallback to original implementation with same client manager
|
|
402
|
+
logging.info("Falling back to original Identity Center implementation")
|
|
403
|
+
# Pass the region and profile to ensure credentials are used
|
|
404
|
+
import os
|
|
405
|
+
os.environ['AWS_PROFILE'] = args.profile if args.profile else ''
|
|
406
|
+
graph_identity_center(
|
|
407
|
+
diagrams_path=diagrams_path, region=region, auto=args.auto_create
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
logging.info("All operations completed successfully")
|
|
411
|
+
return 0
|
|
412
|
+
|
|
413
|
+
except ValueError as e:
|
|
414
|
+
logging.error(f"{Fore.RED}❌ Invalid arguments: {e}{Fore.RESET}")
|
|
415
|
+
parser.print_help()
|
|
416
|
+
return 1
|
|
417
|
+
except KeyboardInterrupt:
|
|
418
|
+
logging.info(f"{Fore.YELLOW}⚠️ Operation cancelled by user{Fore.RESET}")
|
|
419
|
+
return 1
|
|
420
|
+
except Exception as e:
|
|
421
|
+
logging.error(f"{Fore.RED}❌ Unexpected error: {e}{Fore.RESET}")
|
|
422
|
+
if logging.getLogger().isEnabledFor(logging.DEBUG):
|
|
423
|
+
logging.exception("Full traceback:")
|
|
424
|
+
return 1
|
|
132
425
|
|
|
133
426
|
|
|
134
427
|
if __name__ == "__main__":
|
src/utils/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utility modules for Reverse Diagrams."""
|