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/aws/describe_organization.py
CHANGED
|
@@ -1,480 +1,359 @@
|
|
|
1
1
|
"""Describe Organizations."""
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
from typing import List, Dict, Any, Optional
|
|
5
|
+
from pathlib import Path
|
|
4
6
|
|
|
5
7
|
import emoji
|
|
6
8
|
from colorama import Fore
|
|
7
9
|
|
|
10
|
+
from .client_manager import get_client_manager
|
|
11
|
+
from .exceptions import AWSServiceError
|
|
8
12
|
from ..dgms.graph_mapper import create_file, create_mapper, find_ou_name
|
|
9
13
|
from ..dgms.graph_template import graph_template
|
|
10
14
|
from ..reports.save_results import save_results
|
|
11
|
-
from .
|
|
15
|
+
from ..utils.progress import track_operation, get_progress_tracker
|
|
16
|
+
from ..config import get_config
|
|
17
|
+
from ..models import AWSAccount, OrganizationalUnit, AWSOrganization, validate_aws_response
|
|
12
18
|
|
|
13
|
-
|
|
14
|
-
def describe_organization(region, org_client):
|
|
15
|
-
"""
|
|
16
|
-
Describe the organization.
|
|
17
|
-
|
|
18
|
-
:param region: AWS Region
|
|
19
|
-
:return:
|
|
20
|
-
"""
|
|
21
|
-
print(f"{Fore.GREEN}❇️ Describe Organization {Fore.RESET}")
|
|
22
|
-
|
|
23
|
-
organization = org_client.describe_organization()
|
|
24
|
-
organization = organization["Organization"]
|
|
25
|
-
return organization
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def list_roots(region):
|
|
29
|
-
"""
|
|
30
|
-
List the roots.
|
|
31
|
-
|
|
32
|
-
:param region: AWS Region
|
|
33
|
-
:return:
|
|
34
|
-
"""
|
|
35
|
-
org_client = client("organizations", region_name=region)
|
|
36
|
-
roots = org_client.list_roots()
|
|
37
|
-
return roots["Roots"]
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
# def list organizational units pagination
|
|
41
|
-
def list_organizational_units_pag(parent_id, region, next_token=None):
|
|
42
|
-
"""
|
|
43
|
-
List organizational Units with pagination.
|
|
44
|
-
|
|
45
|
-
:param parent_id:
|
|
46
|
-
:param region:
|
|
47
|
-
:param next_token:
|
|
48
|
-
:return:
|
|
49
|
-
"""
|
|
50
|
-
org_client = client("organizations", region_name=region)
|
|
51
|
-
paginator = org_client.get_paginator("list_organizational_units_for_parent")
|
|
52
|
-
response_iterator = paginator.paginate(
|
|
53
|
-
ParentId=parent_id,
|
|
54
|
-
PaginationConfig={"MaxItems": 1000, "PageSize": 4, "StartingToken": next_token},
|
|
55
|
-
)
|
|
56
|
-
response_iterator = response_iterator.build_full_result()
|
|
57
|
-
return response_iterator["OrganizationalUnits"]
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def list_organizational_units(parent_id, region, org_client, org_units=None):
|
|
61
|
-
"""
|
|
62
|
-
List Organizational units.
|
|
63
|
-
|
|
64
|
-
:param org_client:
|
|
65
|
-
:param parent_id:
|
|
66
|
-
:param region:
|
|
67
|
-
:param org_units:
|
|
68
|
-
:return:
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
|
-
if org_units is None:
|
|
72
|
-
org_units = []
|
|
73
|
-
ous = org_client.list_organizational_units_for_parent(
|
|
74
|
-
ParentId=parent_id,
|
|
75
|
-
MaxResults=20,
|
|
76
|
-
)
|
|
77
|
-
ous = ous["OrganizationalUnits"]
|
|
78
|
-
|
|
79
|
-
if len(ous) >= 20:
|
|
80
|
-
logging.info("Paginating ...")
|
|
81
|
-
add_ous = list_organizational_units_pag(parent_id=parent_id,
|
|
82
|
-
region=region, next_token=ous["NextToken"]
|
|
83
|
-
)
|
|
84
|
-
for ou in add_ous:
|
|
85
|
-
ous.append(ou)
|
|
86
|
-
logging.debug(add_ous)
|
|
87
|
-
|
|
88
|
-
for o in ous:
|
|
89
|
-
org_units.append(o)
|
|
90
|
-
logging.debug(
|
|
91
|
-
f"The parent Id is: {parent_id}",
|
|
92
|
-
)
|
|
93
|
-
logging.debug(ous)
|
|
94
|
-
if len(ous) > 0:
|
|
95
|
-
for ou in ous:
|
|
96
|
-
logging.debug(ou)
|
|
97
|
-
if "Id" in ou.keys():
|
|
98
|
-
logging.debug(f"Search nested for: {ou['Name']}")
|
|
99
|
-
ous_next = list_organizational_units(parent_id=ou["Id"],
|
|
100
|
-
region=region, org_units=org_units,
|
|
101
|
-
org_client=org_client,
|
|
102
|
-
)
|
|
103
|
-
logging.debug(ous_next)
|
|
104
|
-
if len(ous_next) > 0:
|
|
105
|
-
logging.debug("Find Nested")
|
|
106
|
-
|
|
107
|
-
return org_units
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def list_parents(child_id, region):
|
|
111
|
-
"""
|
|
112
|
-
List the parents of a child.
|
|
113
|
-
|
|
114
|
-
:param child_id:
|
|
115
|
-
:param region:
|
|
116
|
-
:return:
|
|
117
|
-
"""
|
|
118
|
-
org_client = client("organizations", region_name=region)
|
|
119
|
-
response = org_client.list_parents(
|
|
120
|
-
ChildId=child_id,
|
|
121
|
-
)
|
|
122
|
-
return response["Parents"]
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
123
20
|
|
|
124
21
|
|
|
125
|
-
def
|
|
22
|
+
def describe_organization(region: str) -> Dict[str, Any]:
|
|
126
23
|
"""
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
:param list_ous:
|
|
130
|
-
:param region:
|
|
131
|
-
:return list_ous:
|
|
24
|
+
Describe the organization.
|
|
132
25
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
26
|
+
Args:
|
|
27
|
+
region: AWS Region
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Organization details
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
AWSServiceError: If the API call fails
|
|
34
|
+
"""
|
|
35
|
+
client_manager = get_client_manager(region=region)
|
|
36
|
+
progress = get_progress_tracker()
|
|
37
|
+
|
|
38
|
+
progress.show_success("🏢 Getting Organization Info")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
response = client_manager.call_api("organizations", "describe_organization")
|
|
42
|
+
validate_aws_response(response, ["Organization"])
|
|
43
|
+
|
|
44
|
+
organization = response["Organization"]
|
|
45
|
+
logger.info(f"Retrieved organization: {organization.get('Id', 'Unknown')}")
|
|
46
|
+
return organization
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.error(f"Failed to describe organization: {e}")
|
|
50
|
+
raise AWSServiceError(f"Failed to describe organization: {e}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def list_roots(region: str) -> List[Dict[str, Any]]:
|
|
54
|
+
"""
|
|
55
|
+
List the organization roots.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
region: AWS Region
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of root objects
|
|
62
|
+
"""
|
|
63
|
+
client_manager = get_client_manager(region=region)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
response = client_manager.call_api("organizations", "list_roots")
|
|
67
|
+
validate_aws_response(response, ["Roots"])
|
|
68
|
+
|
|
69
|
+
roots = response["Roots"]
|
|
70
|
+
logger.info(f"Found {len(roots)} organization roots")
|
|
71
|
+
return roots
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Failed to list roots: {e}")
|
|
75
|
+
raise AWSServiceError(f"Failed to list roots: {e}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def list_organizational_units(parent_id: str, region: str) -> List[Dict[str, Any]]:
|
|
79
|
+
"""
|
|
80
|
+
List organizational units recursively.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
parent_id: Parent ID to start from
|
|
84
|
+
region: AWS region
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
List of all organizational units
|
|
88
|
+
"""
|
|
89
|
+
client_manager = get_client_manager(region=region)
|
|
90
|
+
config = get_config()
|
|
91
|
+
all_ous = []
|
|
92
|
+
|
|
93
|
+
def _get_ous_recursive(current_parent_id: str, depth: int = 0) -> None:
|
|
94
|
+
"""Recursively get all OUs."""
|
|
95
|
+
if depth > 10: # Prevent infinite recursion
|
|
96
|
+
logger.warning(f"Maximum recursion depth reached for parent {current_parent_id}")
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Get OUs for current parent
|
|
101
|
+
ous = client_manager.paginate_api_call(
|
|
102
|
+
"organizations",
|
|
103
|
+
"list_organizational_units_for_parent",
|
|
104
|
+
"OrganizationalUnits",
|
|
105
|
+
ParentId=current_parent_id,
|
|
106
|
+
PaginationConfig={
|
|
107
|
+
'MaxItems': config.pagination.max_items,
|
|
108
|
+
'PageSize': config.pagination.default_page_size
|
|
109
|
+
}
|
|
139
110
|
)
|
|
140
|
-
|
|
141
|
-
ou
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
root_id,
|
|
250
|
-
org,
|
|
251
|
-
list_ous,
|
|
252
|
-
):
|
|
253
|
-
"""
|
|
254
|
-
Init organization dictionary.
|
|
255
|
-
|
|
256
|
-
:param root_id:
|
|
257
|
-
:param org:
|
|
258
|
-
:param list_ous:
|
|
259
|
-
:return:
|
|
111
|
+
|
|
112
|
+
for ou in ous:
|
|
113
|
+
# Add parent information
|
|
114
|
+
parents_response = client_manager.call_api(
|
|
115
|
+
"organizations",
|
|
116
|
+
"list_parents",
|
|
117
|
+
ChildId=ou["Id"]
|
|
118
|
+
)
|
|
119
|
+
ou["Parents"] = parents_response.get("Parents", [])
|
|
120
|
+
all_ous.append(ou)
|
|
121
|
+
|
|
122
|
+
# Recursively get child OUs
|
|
123
|
+
_get_ous_recursive(ou["Id"], depth + 1)
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.warning(f"Failed to get OUs for parent {current_parent_id}: {e}")
|
|
127
|
+
|
|
128
|
+
with track_operation("Listing organizational units") as task_id:
|
|
129
|
+
_get_ous_recursive(parent_id)
|
|
130
|
+
|
|
131
|
+
logger.info(f"Found {len(all_ous)} organizational units")
|
|
132
|
+
return all_ous
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def list_accounts(region: str) -> List[Dict[str, Any]]:
|
|
136
|
+
"""
|
|
137
|
+
List all accounts in the organization.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
region: AWS region
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
List of account objects with parent information
|
|
144
|
+
"""
|
|
145
|
+
client_manager = get_client_manager(region=region)
|
|
146
|
+
config = get_config()
|
|
147
|
+
|
|
148
|
+
with track_operation("Listing organization accounts") as task_id:
|
|
149
|
+
try:
|
|
150
|
+
# Get all accounts using pagination
|
|
151
|
+
accounts = client_manager.paginate_api_call(
|
|
152
|
+
"organizations",
|
|
153
|
+
"list_accounts",
|
|
154
|
+
"Accounts",
|
|
155
|
+
PaginationConfig={
|
|
156
|
+
'MaxItems': config.pagination.max_items,
|
|
157
|
+
'PageSize': config.pagination.default_page_size
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
logger.info(f"Found {len(accounts)} accounts in organization")
|
|
162
|
+
|
|
163
|
+
# Add parent information for each account
|
|
164
|
+
indexed_accounts = []
|
|
165
|
+
progress = get_progress_tracker()
|
|
166
|
+
|
|
167
|
+
with progress.track_operation(f"Getting parent info for {len(accounts)} accounts", total=len(accounts)) as parent_task:
|
|
168
|
+
for i, account in enumerate(accounts):
|
|
169
|
+
try:
|
|
170
|
+
parents_response = client_manager.call_api(
|
|
171
|
+
"organizations",
|
|
172
|
+
"list_parents",
|
|
173
|
+
ChildId=account["Id"]
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
indexed_accounts.append({
|
|
177
|
+
"account": account["Id"],
|
|
178
|
+
"name": account["Name"],
|
|
179
|
+
"email": account.get("Email", ""),
|
|
180
|
+
"status": account.get("Status", "ACTIVE"),
|
|
181
|
+
"parents": parents_response.get("Parents", [])
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
progress.update_progress(parent_task)
|
|
185
|
+
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.warning(f"Failed to get parents for account {account['Id']}: {e}")
|
|
188
|
+
indexed_accounts.append({
|
|
189
|
+
"account": account["Id"],
|
|
190
|
+
"name": account["Name"],
|
|
191
|
+
"email": account.get("Email", ""),
|
|
192
|
+
"status": account.get("Status", "ACTIVE"),
|
|
193
|
+
"parents": []
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
return indexed_accounts
|
|
197
|
+
|
|
198
|
+
except Exception as e:
|
|
199
|
+
logger.error(f"Failed to list accounts: {e}")
|
|
200
|
+
raise AWSServiceError(f"Failed to list accounts: {e}")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def create_organization_complete_map(
|
|
204
|
+
root_id: str,
|
|
205
|
+
organization: Dict[str, Any],
|
|
206
|
+
organizational_units: List[Dict[str, Any]],
|
|
207
|
+
accounts: List[Dict[str, Any]]
|
|
208
|
+
) -> Dict[str, Any]:
|
|
209
|
+
"""
|
|
210
|
+
Create a complete organization map with nested structure.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
root_id: Organization root ID
|
|
214
|
+
organization: Organization details
|
|
215
|
+
organizational_units: List of OUs
|
|
216
|
+
accounts: List of accounts
|
|
217
|
+
|
|
218
|
+
Returns:
|
|
219
|
+
Complete organization structure
|
|
260
220
|
"""
|
|
261
221
|
organizations_complete = {
|
|
262
222
|
"rootId": root_id,
|
|
263
|
-
"masterAccountId":
|
|
223
|
+
"masterAccountId": organization["MasterAccountId"],
|
|
264
224
|
"noOutAccounts": [],
|
|
265
225
|
"organizationalUnits": {},
|
|
266
226
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
"
|
|
227
|
+
|
|
228
|
+
# Build OU hierarchy
|
|
229
|
+
for ou in organizational_units:
|
|
230
|
+
for parent in ou.get("Parents", []):
|
|
231
|
+
if parent["Type"] == "ROOT":
|
|
232
|
+
organizations_complete["organizationalUnits"][ou["Name"]] = {
|
|
233
|
+
"Id": ou["Id"],
|
|
234
|
+
"Name": ou["Name"],
|
|
274
235
|
"accounts": {},
|
|
275
236
|
"nestedOus": {},
|
|
276
237
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
:param llist_accounts:
|
|
294
|
-
:return:
|
|
295
|
-
"""
|
|
296
|
-
# Iterate in ous for getting ous tree
|
|
297
|
-
for a, i in zip(list_ous, range(len(list_ous))):
|
|
298
|
-
for p in a["Parents"]:
|
|
299
|
-
if p["Type"] == "ORGANIZATIONAL_UNIT":
|
|
300
|
-
o = find_ou_name(reference_outs_list, p["Id"])
|
|
301
|
-
|
|
302
|
-
if o not in organizations_complete["organizationalUnits"].keys():
|
|
303
|
-
p = search_ou_map(
|
|
304
|
-
organizations_complete["organizationalUnits"], ou_id=o
|
|
305
|
-
)
|
|
306
|
-
o = p["Name"]
|
|
307
|
-
|
|
308
|
-
organizations_complete["organizationalUnits"][o]["nestedOus"][
|
|
309
|
-
find_ou_name(reference_outs_list, a["Id"])
|
|
310
|
-
] = {"Id": a["Id"], "Name": a["Name"], "accounts": [], "nestedOus": {}}
|
|
311
|
-
# print(organizations_complete["organizationalUnits"][o]["nestedOus"])
|
|
312
|
-
if (
|
|
313
|
-
len(organizations_complete["organizationalUnits"][o]["nestedOus"])
|
|
314
|
-
> 0
|
|
315
|
-
):
|
|
316
|
-
new_list_ous = organizations_complete["organizationalUnits"][o][
|
|
317
|
-
"nestedOus"
|
|
318
|
-
]
|
|
319
|
-
|
|
320
|
-
new_list_ous = plop_dict_out(ous_list=list_ous, ou=new_list_ous)
|
|
321
|
-
organizations_complete = map_organizations_complete(
|
|
322
|
-
organizations_complete=organizations_complete,
|
|
323
|
-
list_ous=new_list_ous,
|
|
324
|
-
llist_accounts=llist_accounts,
|
|
325
|
-
reference_outs_list=reference_outs_list,
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
return organizations_complete
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def plop_dict_out(
|
|
332
|
-
ous_list: list,
|
|
333
|
-
ou,
|
|
334
|
-
):
|
|
335
|
-
"""
|
|
336
|
-
Clean list.
|
|
337
|
-
|
|
338
|
-
:param ous_list:
|
|
339
|
-
:param ou:
|
|
340
|
-
:return:
|
|
341
|
-
"""
|
|
342
|
-
for o in ou.keys():
|
|
343
|
-
# for c in ou.keys():
|
|
344
|
-
for unit in ous_list:
|
|
345
|
-
if unit["Id"] == ou[o]["Id"]:
|
|
346
|
-
ous_list.remove(unit)
|
|
347
|
-
|
|
348
|
-
return ous_list
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
def set_accounts_tree(llist_accounts, organizations_complete, list_ous):
|
|
352
|
-
"""
|
|
353
|
-
Set accounts tree.
|
|
354
|
-
|
|
355
|
-
:param llist_accounts:
|
|
356
|
-
:param organizations_complete:
|
|
357
|
-
:param list_ous:
|
|
358
|
-
:return:
|
|
359
|
-
"""
|
|
360
|
-
# Iterate in list accounts to get parent ous
|
|
361
|
-
for c, i in zip(llist_accounts, range(len(llist_accounts))):
|
|
362
|
-
# print(f"\n aa_{i}= OrganizationsAccount(\"{c['account']}\")", file=f)
|
|
363
|
-
for p in c["parents"]:
|
|
364
|
-
if p["Type"] == "ROOT":
|
|
365
|
-
organizations_complete["noOutAccounts"].append(
|
|
366
|
-
{"account": c["account"], "name": c["name"]}
|
|
367
|
-
)
|
|
368
|
-
|
|
369
|
-
for o, j in zip(list_ous, range(len(list_ous))):
|
|
370
|
-
if p["Id"] == o["Id"] and p["Type"] == "ORGANIZATIONAL_UNIT":
|
|
371
|
-
organizations_complete["organizationalUnits"][
|
|
372
|
-
find_ou_name(list_ous, o["Id"])
|
|
373
|
-
]["accounts"][c["name"]] = {
|
|
374
|
-
"account": c["account"],
|
|
375
|
-
"name": c["name"],
|
|
238
|
+
|
|
239
|
+
# Add accounts to appropriate OUs or root
|
|
240
|
+
for account in accounts:
|
|
241
|
+
for parent in account.get("parents", []):
|
|
242
|
+
if parent["Type"] == "ROOT":
|
|
243
|
+
organizations_complete["noOutAccounts"].append({
|
|
244
|
+
"account": account["account"],
|
|
245
|
+
"name": account["name"]
|
|
246
|
+
})
|
|
247
|
+
elif parent["Type"] == "ORGANIZATIONAL_UNIT":
|
|
248
|
+
# Find the OU name
|
|
249
|
+
ou_name = find_ou_name(organizational_units, parent["Id"])
|
|
250
|
+
if ou_name and ou_name in organizations_complete["organizationalUnits"]:
|
|
251
|
+
organizations_complete["organizationalUnits"][ou_name]["accounts"][account["name"]] = {
|
|
252
|
+
"account": account["account"],
|
|
253
|
+
"name": account["name"]
|
|
376
254
|
}
|
|
377
|
-
|
|
255
|
+
|
|
378
256
|
return organizations_complete
|
|
379
257
|
|
|
380
258
|
|
|
381
|
-
def graph_organizations(diagrams_path, region, auto):
|
|
259
|
+
def graph_organizations(diagrams_path: str, region: str, auto: bool) -> None:
|
|
382
260
|
"""
|
|
383
|
-
Create organizations
|
|
261
|
+
Create organizations graph with improved error handling and progress tracking.
|
|
384
262
|
|
|
385
|
-
:
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
263
|
+
Args:
|
|
264
|
+
diagrams_path: Output directory path
|
|
265
|
+
region: AWS region
|
|
266
|
+
auto: Whether to automatically create diagrams
|
|
389
267
|
"""
|
|
390
268
|
template_file = "graph_org.py"
|
|
391
|
-
code_path =
|
|
392
|
-
json_path =
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
print(
|
|
407
|
-
Fore.BLUE + emoji.emojize(":sparkle: List Organizational Units " + Fore.RESET)
|
|
408
|
-
)
|
|
409
|
-
logging.debug("The Organizational Units list ")
|
|
410
|
-
|
|
411
|
-
ous = list_organizational_units(parent_id=roots[0]["Id"], region=region, org_client=org_client)
|
|
412
|
-
logging.debug(ous)
|
|
413
|
-
logging.debug("The Organizational Units list with parents info")
|
|
414
|
-
i_ous = index_ous(ous, region=region)
|
|
415
|
-
logging.debug(i_ous)
|
|
416
|
-
|
|
417
|
-
print(
|
|
418
|
-
Fore.BLUE
|
|
419
|
-
+ emoji.emojize(":sparkle: Getting the Account list info" + Fore.RESET)
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
l_accounts = list_accounts(region=region, org_client=org_client)
|
|
423
|
-
logging.debug(l_accounts)
|
|
424
|
-
logging.debug("The Account list with parents info")
|
|
425
|
-
|
|
426
|
-
print(
|
|
427
|
-
Fore.YELLOW
|
|
428
|
-
+ emoji.emojize(
|
|
429
|
-
f":information: There are {len(l_accounts)} Accounts in your organization"
|
|
430
|
-
+ Fore.RESET
|
|
269
|
+
code_path = Path(diagrams_path) / "code"
|
|
270
|
+
json_path = Path(diagrams_path) / "json"
|
|
271
|
+
|
|
272
|
+
# Ensure directories exist
|
|
273
|
+
code_path.mkdir(parents=True, exist_ok=True)
|
|
274
|
+
json_path.mkdir(parents=True, exist_ok=True)
|
|
275
|
+
|
|
276
|
+
progress = get_progress_tracker()
|
|
277
|
+
|
|
278
|
+
try:
|
|
279
|
+
# Create template file
|
|
280
|
+
create_file(
|
|
281
|
+
template_content=graph_template,
|
|
282
|
+
file_name=template_file,
|
|
283
|
+
directory_path=str(code_path),
|
|
431
284
|
)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
else:
|
|
475
|
-
print(
|
|
476
|
-
Fore.YELLOW
|
|
477
|
-
+ emoji.emojize(
|
|
478
|
-
f":sparkles: Run -> python3 {code_path}/graph_org.py " + Fore.RESET
|
|
479
|
-
)
|
|
285
|
+
|
|
286
|
+
# Get organization data
|
|
287
|
+
organization = describe_organization(region=region)
|
|
288
|
+
roots = list_roots(region=region)
|
|
289
|
+
|
|
290
|
+
if not roots:
|
|
291
|
+
raise AWSServiceError("No organization roots found")
|
|
292
|
+
|
|
293
|
+
root_id = roots[0]["Id"]
|
|
294
|
+
|
|
295
|
+
# Get organizational units
|
|
296
|
+
progress.show_success("📋 Listing Organizational Units")
|
|
297
|
+
organizational_units = list_organizational_units(parent_id=root_id, region=region)
|
|
298
|
+
|
|
299
|
+
# Get accounts
|
|
300
|
+
progress.show_success("👥 Getting Account Information")
|
|
301
|
+
accounts = list_accounts(region=region)
|
|
302
|
+
|
|
303
|
+
progress.show_summary(
|
|
304
|
+
"Organization Summary",
|
|
305
|
+
[
|
|
306
|
+
f"Organization ID: {organization.get('Id', 'Unknown')}",
|
|
307
|
+
f"Master Account: {organization.get('MasterAccountId', 'Unknown')}",
|
|
308
|
+
f"Organizational Units: {len(organizational_units)}",
|
|
309
|
+
f"Accounts: {len(accounts)}"
|
|
310
|
+
]
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Save basic accounts data
|
|
314
|
+
save_results(
|
|
315
|
+
results=accounts,
|
|
316
|
+
filename="organizations.json",
|
|
317
|
+
directory_path=str(json_path)
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
# Create diagram mapper
|
|
321
|
+
create_mapper(
|
|
322
|
+
template_file=str(code_path / template_file),
|
|
323
|
+
org=organization,
|
|
324
|
+
root_id=root_id,
|
|
325
|
+
list_ous=organizational_units,
|
|
326
|
+
list_accounts=accounts,
|
|
480
327
|
)
|
|
328
|
+
|
|
329
|
+
# Create complete organization map
|
|
330
|
+
organizations_complete = create_organization_complete_map(
|
|
331
|
+
root_id=root_id,
|
|
332
|
+
organization=organization,
|
|
333
|
+
organizational_units=organizational_units,
|
|
334
|
+
accounts=accounts
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
save_results(
|
|
338
|
+
results=organizations_complete,
|
|
339
|
+
filename="organizations_complete.json",
|
|
340
|
+
directory_path=str(json_path)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if auto:
|
|
344
|
+
progress.show_success(f"🎨 Creating diagrams in {code_path}")
|
|
345
|
+
command = os.system(f"cd {code_path} && python3 {template_file}")
|
|
346
|
+
if command != 0:
|
|
347
|
+
progress.show_error("Failed to create diagrams", f"Command exit code: {command}")
|
|
348
|
+
else:
|
|
349
|
+
progress.show_success("Diagrams created successfully")
|
|
350
|
+
else:
|
|
351
|
+
progress.show_success(
|
|
352
|
+
"Ready to create diagrams",
|
|
353
|
+
f"Run: python3 {code_path / template_file}"
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
except Exception as e:
|
|
357
|
+
logger.error(f"Failed to create organization graph: {e}")
|
|
358
|
+
progress.show_error("Organization diagram generation failed", str(e))
|
|
359
|
+
raise
|