reverse-diagrams 1.3.4__py3-none-any.whl → 2.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.
@@ -0,0 +1,35 @@
1
+ src/__init__.py,sha256=lOE8TAJF_WdxgxEBxVz9t8dC3s3t2c21VWYPrzfznkE,17
2
+ src/config.py,sha256=7KinPO7ybCc5IQKmz1OcT-oXF1TUQWlaIyNC2eW86bI,4980
3
+ src/models.py,sha256=vFDPdKGxjxwdBN3O_-bnnF9TIqM3I3KTK7n2n9PaktY,7608
4
+ src/reverse_diagrams.py,sha256=XFF3f1MoNDjtCYolj1SiDOYWlV5djD4Hq30NJw5gvbQ,16929
5
+ src/version.py,sha256=tJk7e8Z_LpxGMdTvSeAHQ45G-1AEWVWBBqAqDg_KC9w,48
6
+ src/aws/__init__.py,sha256=lOE8TAJF_WdxgxEBxVz9t8dC3s3t2c21VWYPrzfznkE,17
7
+ src/aws/client_manager.py,sha256=rmDoGDsxZj7q0tYf9xot5pxOrN5cBnHBkRgbiLrerqU,8456
8
+ src/aws/describe_identity_store.py,sha256=AA3GVz6UyKepImm6VL2nqkbyH13X8f40qn-zNhICEkw,15821
9
+ src/aws/describe_organization.py,sha256=tqImKyLtftk5upgVQmordFmBO7M1NeIQg3-KwwiQhOA,12743
10
+ src/aws/describe_sso.py,sha256=_wcyHr0xFLXicC6IJs7uaj26E6TXywiiwUjteStCI_M,6771
11
+ src/aws/exceptions.py,sha256=QJqO3i5Q1BiwnX15RDkUWQoDA-6MnYsNP5d8Q6SKqbE,562
12
+ src/banner/__init__.py,sha256=lOE8TAJF_WdxgxEBxVz9t8dC3s3t2c21VWYPrzfznkE,17
13
+ src/banner/banner.py,sha256=QOhJ_qI0_0MWI5wo-IMFv_-nrPN4RuJRJSqPUq_uPF0,2101
14
+ src/dgms/__init__.py,sha256=lOE8TAJF_WdxgxEBxVz9t8dC3s3t2c21VWYPrzfznkE,17
15
+ src/dgms/graph_mapper.py,sha256=xutxzxLCIGSBfHCAF-BnWXN21bjzusA43ZhKtXUir2E,7714
16
+ src/dgms/graph_template.py,sha256=IOEmTk9A0xqEhtdNnaFrVDJlBF0ggW0WpxosW4nbcQo,1217
17
+ src/plugins/__init__.py,sha256=Oq17hXr9-BOlY60ZcbKbQaeMf2cabqJc6APdtqJ7Ylg,312
18
+ src/plugins/base.py,sha256=wuLqqNibsAoJJacKI3pQwUFdzmhbXBm__U60_xyRDKA,9533
19
+ src/plugins/registry.py,sha256=R4IpEnis9wr5GUREAb3Rqi2X-4OBY41kvynGxg0doSo,3957
20
+ src/plugins/builtin/__init__.py,sha256=u06O_XAREbZ1BxvYp1d-2stD3iELpA_pTll3j5-b5Jw,360
21
+ src/plugins/builtin/ec2_plugin.py,sha256=BgLANn7sTN0m9Rs0gHnA-5mtPJL1V-tQmr7JnUtac38,8958
22
+ src/plugins/builtin/identity_center_plugin.py,sha256=9IKTEFAaluuePoED2jhwtLAL0_fjbcP72TEmRQCXpJ8,22141
23
+ src/plugins/builtin/organizations_plugin.py,sha256=4dYGCL4kvJ_ZAa9D_-6fRS6u0mqUr8hzalr-sRna1CE,15319
24
+ src/reports/__init__.py,sha256=lOE8TAJF_WdxgxEBxVz9t8dC3s3t2c21VWYPrzfznkE,17
25
+ src/reports/console_view.py,sha256=-zR8fd7h2lLwqjIIs8TMzl0r8Q1k3ZWt5xrtEhN9KNc,7997
26
+ src/reports/save_results.py,sha256=35KOP6R3t5DBWQDPdQy5agEODPQBTPnwe22M7-HfOJA,7004
27
+ src/utils/__init__.py,sha256=2qVZjBvUSXJngCvc68FnhAOJTA2RiW-AWc6HhxQX-ak,43
28
+ src/utils/cache.py,sha256=fZ5b0rgeETNeaJW19hHClkQwEiohF4hvLr1nxuPSqXQ,8711
29
+ src/utils/concurrent.py,sha256=6haOaH7IGeCLHyGlxkMrOYEuaA471k2ncHr3VtNXhNU,12230
30
+ src/utils/progress.py,sha256=j9e6-axDloIn8C5UL1CLVqlw4nQbKe7gK1Mjd_e0Wdk,7996
31
+ reverse_diagrams-2.0.0.dist-info/METADATA,sha256=L0RTnQsLw-WlLlEnHHjrS3FuZXvormP9aqwCr2yrxvg,23889
32
+ reverse_diagrams-2.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
33
+ reverse_diagrams-2.0.0.dist-info/entry_points.txt,sha256=VZNkrc7qUDbddTCH3pGd83EhUT3PHTx9MzpAk6bb6qc,63
34
+ reverse_diagrams-2.0.0.dist-info/licenses/LICENSE,sha256=o6nDaQ7M9xbR0L7HSJ3A-1JbBPoZro_zhVPO4-M5CAQ,571
35
+ reverse_diagrams-2.0.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.25.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,217 @@
1
+ """AWS Client Manager with proper error handling and validation."""
2
+ import logging
3
+ from functools import wraps
4
+ from typing import Optional, Dict, Any
5
+ import time
6
+
7
+ import boto3
8
+ from botocore.exceptions import (
9
+ ClientError,
10
+ NoCredentialsError,
11
+ PartialCredentialsError,
12
+ BotoCoreError
13
+ )
14
+
15
+ from .exceptions import (
16
+ AWSCredentialsError,
17
+ AWSPermissionError,
18
+ AWSServiceError,
19
+ AWSResourceNotFoundError
20
+ )
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ def retry_with_backoff(max_retries: int = 3, backoff_factor: float = 2.0):
26
+ """Decorator to retry AWS API calls with exponential backoff."""
27
+ def decorator(func):
28
+ @wraps(func)
29
+ def wrapper(*args, **kwargs):
30
+ for attempt in range(max_retries):
31
+ try:
32
+ return func(*args, **kwargs)
33
+ except ClientError as e:
34
+ error_code = e.response.get('Error', {}).get('Code', '')
35
+
36
+ # Retry on throttling errors
37
+ if error_code in ['Throttling', 'TooManyRequestsException', 'RequestLimitExceeded']:
38
+ if attempt < max_retries - 1:
39
+ sleep_time = backoff_factor ** attempt
40
+ logger.warning(f"AWS API throttled, retrying in {sleep_time}s (attempt {attempt + 1}/{max_retries})")
41
+ time.sleep(sleep_time)
42
+ continue
43
+
44
+ # Don't retry on other errors
45
+ raise AWSServiceError(f"AWS API error: {e}")
46
+ except BotoCoreError as e:
47
+ raise AWSServiceError(f"AWS service error: {e}")
48
+
49
+ # Final attempt without retry
50
+ return func(*args, **kwargs)
51
+ return wrapper
52
+ return decorator
53
+
54
+
55
+ class AWSClientManager:
56
+ """Manages AWS clients with proper error handling and validation."""
57
+
58
+ def __init__(self, region: str, profile: Optional[str] = None):
59
+ """
60
+ Initialize AWS client manager.
61
+
62
+ Args:
63
+ region: AWS region name
64
+ profile: AWS CLI profile name (optional)
65
+ """
66
+ self.region = region
67
+ self.profile = profile
68
+ self._session = None
69
+ self._clients: Dict[str, Any] = {}
70
+
71
+ self._setup_session()
72
+ self._validate_credentials()
73
+
74
+ def _setup_session(self):
75
+ """Setup boto3 session with profile if provided."""
76
+ try:
77
+ if self.profile:
78
+ self._session = boto3.Session(profile_name=self.profile)
79
+ logger.info(f"Using AWS profile: {self.profile}")
80
+ else:
81
+ self._session = boto3.Session()
82
+ logger.info("Using default AWS credentials")
83
+ except Exception as e:
84
+ raise AWSCredentialsError(f"Failed to setup AWS session: {e}")
85
+
86
+ def _validate_credentials(self):
87
+ """Validate AWS credentials by making a test call."""
88
+ try:
89
+ sts_client = self.get_client('sts')
90
+ identity = sts_client.get_caller_identity()
91
+ logger.info(f"AWS credentials validated for account: {identity.get('Account')}")
92
+ except NoCredentialsError:
93
+ raise AWSCredentialsError("No AWS credentials found. Please configure AWS CLI or set environment variables.")
94
+ except PartialCredentialsError:
95
+ raise AWSCredentialsError("Incomplete AWS credentials. Please check your configuration.")
96
+ except ClientError as e:
97
+ error_code = e.response.get('Error', {}).get('Code', '')
98
+ if error_code in ['AccessDenied', 'UnauthorizedOperation']:
99
+ raise AWSPermissionError(f"Insufficient AWS permissions: {e}")
100
+ raise AWSCredentialsError(f"AWS credential validation failed: {e}")
101
+
102
+ def get_client(self, service_name: str, **kwargs):
103
+ """
104
+ Get AWS client for specified service with caching.
105
+
106
+ Args:
107
+ service_name: AWS service name (e.g., 'organizations', 'sso-admin')
108
+ **kwargs: Additional client configuration
109
+
110
+ Returns:
111
+ Boto3 client instance
112
+ """
113
+ client_key = f"{service_name}_{self.region}"
114
+
115
+ if client_key not in self._clients:
116
+ try:
117
+ self._clients[client_key] = self._session.client(
118
+ service_name,
119
+ region_name=self.region,
120
+ **kwargs
121
+ )
122
+ logger.debug(f"Created {service_name} client for region {self.region}")
123
+ except Exception as e:
124
+ raise AWSServiceError(f"Failed to create {service_name} client: {e}")
125
+
126
+ return self._clients[client_key]
127
+
128
+ @retry_with_backoff()
129
+ def call_api(self, service_name: str, method_name: str, **kwargs):
130
+ """
131
+ Make AWS API call with error handling and retry logic.
132
+
133
+ Args:
134
+ service_name: AWS service name
135
+ method_name: API method name
136
+ **kwargs: Method parameters
137
+
138
+ Returns:
139
+ API response
140
+ """
141
+ try:
142
+ client = self.get_client(service_name)
143
+ method = getattr(client, method_name)
144
+ response = method(**kwargs)
145
+ logger.debug(f"Successfully called {service_name}.{method_name}")
146
+ return response
147
+ except AttributeError:
148
+ raise AWSServiceError(f"Method {method_name} not found for service {service_name}")
149
+ except ClientError as e:
150
+ error_code = e.response.get('Error', {}).get('Code', '')
151
+
152
+ if error_code in ['ResourceNotFoundException', 'NoSuchEntity']:
153
+ raise AWSResourceNotFoundError(f"AWS resource not found: {e}")
154
+ elif error_code in ['AccessDenied', 'UnauthorizedOperation']:
155
+ raise AWSPermissionError(f"Insufficient permissions for {service_name}.{method_name}: {e}")
156
+ else:
157
+ raise AWSServiceError(f"AWS API call failed {service_name}.{method_name}: {e}")
158
+
159
+ def paginate_api_call(self, service_name: str, method_name: str, result_key: str, **kwargs):
160
+ """
161
+ Handle paginated AWS API calls.
162
+
163
+ Args:
164
+ service_name: AWS service name
165
+ method_name: API method name
166
+ result_key: Key in response containing the results
167
+ **kwargs: Method parameters
168
+
169
+ Returns:
170
+ List of all results from paginated calls
171
+ """
172
+ try:
173
+ client = self.get_client(service_name)
174
+ paginator = client.get_paginator(method_name)
175
+
176
+ all_results = []
177
+ for page in paginator.paginate(**kwargs):
178
+ if result_key in page:
179
+ all_results.extend(page[result_key])
180
+
181
+ logger.info(f"Retrieved {len(all_results)} items from paginated {service_name}.{method_name}")
182
+ return all_results
183
+
184
+ except ClientError as e:
185
+ raise AWSServiceError(f"Paginated API call failed {service_name}.{method_name}: {e}")
186
+ except Exception as e:
187
+ raise AWSServiceError(f"Pagination error for {service_name}.{method_name}: {e}")
188
+
189
+
190
+ # Global client manager instance
191
+ _client_manager: Optional[AWSClientManager] = None
192
+
193
+
194
+ def get_client_manager(region: str, profile: Optional[str] = None) -> AWSClientManager:
195
+ """Get or create global client manager instance."""
196
+ global _client_manager
197
+
198
+ if _client_manager is None or _client_manager.region != region or _client_manager.profile != profile:
199
+ _client_manager = AWSClientManager(region=region, profile=profile)
200
+
201
+ return _client_manager
202
+
203
+
204
+ def client(service_name: str, region_name: str, profile: Optional[str] = None):
205
+ """
206
+ Backward compatibility function for existing code.
207
+
208
+ Args:
209
+ service_name: AWS service name
210
+ region_name: AWS region
211
+ profile: AWS profile (optional)
212
+
213
+ Returns:
214
+ Boto3 client instance
215
+ """
216
+ manager = get_client_manager(region=region_name, profile=profile)
217
+ return manager.get_client(service_name)
@@ -406,6 +406,14 @@ def graph_identity_center(diagrams_path, region, auto):
406
406
  + emoji.emojize(":sparkle: Getting Identity store instance info" + Fore.RESET)
407
407
  )
408
408
  logging.debug(store_instances)
409
+
410
+ # Check if IAM Identity Center is enabled
411
+ if not store_instances or len(store_instances) == 0:
412
+ error_msg = "No IAM Identity Center (SSO) instances found. Please enable IAM Identity Center in your AWS account."
413
+ print(Fore.RED + f"❌ {error_msg}" + Fore.RESET)
414
+ logging.error(error_msg)
415
+ raise ValueError(error_msg)
416
+
409
417
  store_id = store_instances[0]["IdentityStoreId"]
410
418
  store_arn = store_instances[0]["InstanceArn"]
411
419