runbooks 1.1.4__py3-none-any.whl → 1.1.6__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.
Files changed (273) hide show
  1. runbooks/__init__.py +31 -2
  2. runbooks/__init___optimized.py +18 -4
  3. runbooks/_platform/__init__.py +1 -5
  4. runbooks/_platform/core/runbooks_wrapper.py +141 -138
  5. runbooks/aws2/accuracy_validator.py +812 -0
  6. runbooks/base.py +7 -0
  7. runbooks/cfat/assessment/compliance.py +1 -1
  8. runbooks/cfat/assessment/runner.py +1 -0
  9. runbooks/cfat/cloud_foundations_assessment.py +227 -239
  10. runbooks/cli/__init__.py +1 -1
  11. runbooks/cli/commands/cfat.py +64 -23
  12. runbooks/cli/commands/finops.py +1005 -54
  13. runbooks/cli/commands/inventory.py +135 -91
  14. runbooks/cli/commands/operate.py +9 -36
  15. runbooks/cli/commands/security.py +42 -18
  16. runbooks/cli/commands/validation.py +432 -18
  17. runbooks/cli/commands/vpc.py +81 -17
  18. runbooks/cli/registry.py +22 -10
  19. runbooks/cloudops/__init__.py +20 -27
  20. runbooks/cloudops/base.py +96 -107
  21. runbooks/cloudops/cost_optimizer.py +544 -542
  22. runbooks/cloudops/infrastructure_optimizer.py +5 -4
  23. runbooks/cloudops/interfaces.py +224 -225
  24. runbooks/cloudops/lifecycle_manager.py +5 -4
  25. runbooks/cloudops/mcp_cost_validation.py +252 -235
  26. runbooks/cloudops/models.py +78 -53
  27. runbooks/cloudops/monitoring_automation.py +5 -4
  28. runbooks/cloudops/notebook_framework.py +177 -213
  29. runbooks/cloudops/security_enforcer.py +125 -159
  30. runbooks/common/accuracy_validator.py +17 -12
  31. runbooks/common/aws_pricing.py +349 -326
  32. runbooks/common/aws_pricing_api.py +211 -212
  33. runbooks/common/aws_profile_manager.py +40 -36
  34. runbooks/common/aws_utils.py +74 -79
  35. runbooks/common/business_logic.py +126 -104
  36. runbooks/common/cli_decorators.py +36 -60
  37. runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
  38. runbooks/common/cross_account_manager.py +197 -204
  39. runbooks/common/date_utils.py +27 -39
  40. runbooks/common/decorators.py +29 -19
  41. runbooks/common/dry_run_examples.py +173 -208
  42. runbooks/common/dry_run_framework.py +157 -155
  43. runbooks/common/enhanced_exception_handler.py +15 -4
  44. runbooks/common/enhanced_logging_example.py +50 -64
  45. runbooks/common/enhanced_logging_integration_example.py +65 -37
  46. runbooks/common/env_utils.py +16 -16
  47. runbooks/common/error_handling.py +40 -38
  48. runbooks/common/lazy_loader.py +41 -23
  49. runbooks/common/logging_integration_helper.py +79 -86
  50. runbooks/common/mcp_cost_explorer_integration.py +476 -493
  51. runbooks/common/mcp_integration.py +99 -79
  52. runbooks/common/memory_optimization.py +140 -118
  53. runbooks/common/module_cli_base.py +37 -58
  54. runbooks/common/organizations_client.py +175 -193
  55. runbooks/common/patterns.py +23 -25
  56. runbooks/common/performance_monitoring.py +67 -71
  57. runbooks/common/performance_optimization_engine.py +283 -274
  58. runbooks/common/profile_utils.py +111 -37
  59. runbooks/common/rich_utils.py +315 -141
  60. runbooks/common/sre_performance_suite.py +177 -186
  61. runbooks/enterprise/__init__.py +1 -1
  62. runbooks/enterprise/logging.py +144 -106
  63. runbooks/enterprise/security.py +187 -204
  64. runbooks/enterprise/validation.py +43 -56
  65. runbooks/finops/__init__.py +26 -30
  66. runbooks/finops/account_resolver.py +1 -1
  67. runbooks/finops/advanced_optimization_engine.py +980 -0
  68. runbooks/finops/automation_core.py +268 -231
  69. runbooks/finops/business_case_config.py +184 -179
  70. runbooks/finops/cli.py +660 -139
  71. runbooks/finops/commvault_ec2_analysis.py +157 -164
  72. runbooks/finops/compute_cost_optimizer.py +336 -320
  73. runbooks/finops/config.py +20 -20
  74. runbooks/finops/cost_optimizer.py +484 -618
  75. runbooks/finops/cost_processor.py +332 -214
  76. runbooks/finops/dashboard_runner.py +1006 -172
  77. runbooks/finops/ebs_cost_optimizer.py +991 -657
  78. runbooks/finops/elastic_ip_optimizer.py +317 -257
  79. runbooks/finops/enhanced_mcp_integration.py +340 -0
  80. runbooks/finops/enhanced_progress.py +32 -29
  81. runbooks/finops/enhanced_trend_visualization.py +3 -2
  82. runbooks/finops/enterprise_wrappers.py +223 -285
  83. runbooks/finops/executive_export.py +203 -160
  84. runbooks/finops/helpers.py +130 -288
  85. runbooks/finops/iam_guidance.py +1 -1
  86. runbooks/finops/infrastructure/__init__.py +80 -0
  87. runbooks/finops/infrastructure/commands.py +506 -0
  88. runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
  89. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
  90. runbooks/finops/markdown_exporter.py +337 -174
  91. runbooks/finops/mcp_validator.py +1952 -0
  92. runbooks/finops/nat_gateway_optimizer.py +1512 -481
  93. runbooks/finops/network_cost_optimizer.py +657 -587
  94. runbooks/finops/notebook_utils.py +226 -188
  95. runbooks/finops/optimization_engine.py +1136 -0
  96. runbooks/finops/optimizer.py +19 -23
  97. runbooks/finops/rds_snapshot_optimizer.py +367 -411
  98. runbooks/finops/reservation_optimizer.py +427 -363
  99. runbooks/finops/scenario_cli_integration.py +64 -65
  100. runbooks/finops/scenarios.py +1277 -438
  101. runbooks/finops/schemas.py +218 -182
  102. runbooks/finops/snapshot_manager.py +2289 -0
  103. runbooks/finops/types.py +3 -3
  104. runbooks/finops/validation_framework.py +259 -265
  105. runbooks/finops/vpc_cleanup_exporter.py +189 -144
  106. runbooks/finops/vpc_cleanup_optimizer.py +591 -573
  107. runbooks/finops/workspaces_analyzer.py +171 -182
  108. runbooks/integration/__init__.py +89 -0
  109. runbooks/integration/mcp_integration.py +1920 -0
  110. runbooks/inventory/CLAUDE.md +816 -0
  111. runbooks/inventory/__init__.py +2 -2
  112. runbooks/inventory/aws_decorators.py +2 -3
  113. runbooks/inventory/check_cloudtrail_compliance.py +2 -4
  114. runbooks/inventory/check_controltower_readiness.py +152 -151
  115. runbooks/inventory/check_landingzone_readiness.py +85 -84
  116. runbooks/inventory/cloud_foundations_integration.py +144 -149
  117. runbooks/inventory/collectors/aws_comprehensive.py +1 -1
  118. runbooks/inventory/collectors/aws_networking.py +109 -99
  119. runbooks/inventory/collectors/base.py +4 -0
  120. runbooks/inventory/core/collector.py +495 -313
  121. runbooks/inventory/core/formatter.py +11 -0
  122. runbooks/inventory/draw_org_structure.py +8 -9
  123. runbooks/inventory/drift_detection_cli.py +69 -96
  124. runbooks/inventory/ec2_vpc_utils.py +2 -2
  125. runbooks/inventory/find_cfn_drift_detection.py +5 -7
  126. runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
  127. runbooks/inventory/find_cfn_stackset_drift.py +5 -6
  128. runbooks/inventory/find_ec2_security_groups.py +48 -42
  129. runbooks/inventory/find_landingzone_versions.py +4 -6
  130. runbooks/inventory/find_vpc_flow_logs.py +7 -9
  131. runbooks/inventory/inventory_mcp_cli.py +48 -46
  132. runbooks/inventory/inventory_modules.py +103 -91
  133. runbooks/inventory/list_cfn_stacks.py +9 -10
  134. runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
  135. runbooks/inventory/list_cfn_stackset_operations.py +79 -57
  136. runbooks/inventory/list_cfn_stacksets.py +8 -10
  137. runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
  138. runbooks/inventory/list_ds_directories.py +65 -53
  139. runbooks/inventory/list_ec2_availability_zones.py +2 -4
  140. runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
  141. runbooks/inventory/list_ec2_instances.py +23 -28
  142. runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
  143. runbooks/inventory/list_elbs_load_balancers.py +22 -20
  144. runbooks/inventory/list_enis_network_interfaces.py +26 -33
  145. runbooks/inventory/list_guardduty_detectors.py +2 -4
  146. runbooks/inventory/list_iam_policies.py +2 -4
  147. runbooks/inventory/list_iam_roles.py +5 -7
  148. runbooks/inventory/list_iam_saml_providers.py +4 -6
  149. runbooks/inventory/list_lambda_functions.py +38 -38
  150. runbooks/inventory/list_org_accounts.py +6 -8
  151. runbooks/inventory/list_org_accounts_users.py +55 -44
  152. runbooks/inventory/list_rds_db_instances.py +31 -33
  153. runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
  154. runbooks/inventory/list_route53_hosted_zones.py +3 -5
  155. runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
  156. runbooks/inventory/list_sns_topics.py +2 -4
  157. runbooks/inventory/list_ssm_parameters.py +4 -7
  158. runbooks/inventory/list_vpc_subnets.py +2 -4
  159. runbooks/inventory/list_vpcs.py +7 -10
  160. runbooks/inventory/mcp_inventory_validator.py +554 -468
  161. runbooks/inventory/mcp_vpc_validator.py +359 -442
  162. runbooks/inventory/organizations_discovery.py +63 -55
  163. runbooks/inventory/recover_cfn_stack_ids.py +7 -8
  164. runbooks/inventory/requirements.txt +0 -1
  165. runbooks/inventory/rich_inventory_display.py +35 -34
  166. runbooks/inventory/run_on_multi_accounts.py +3 -5
  167. runbooks/inventory/unified_validation_engine.py +281 -253
  168. runbooks/inventory/verify_ec2_security_groups.py +1 -1
  169. runbooks/inventory/vpc_analyzer.py +735 -697
  170. runbooks/inventory/vpc_architecture_validator.py +293 -348
  171. runbooks/inventory/vpc_dependency_analyzer.py +384 -380
  172. runbooks/inventory/vpc_flow_analyzer.py +1 -1
  173. runbooks/main.py +49 -34
  174. runbooks/main_final.py +91 -60
  175. runbooks/main_minimal.py +22 -10
  176. runbooks/main_optimized.py +131 -100
  177. runbooks/main_ultra_minimal.py +7 -2
  178. runbooks/mcp/__init__.py +36 -0
  179. runbooks/mcp/integration.py +679 -0
  180. runbooks/monitoring/performance_monitor.py +9 -4
  181. runbooks/operate/dynamodb_operations.py +3 -1
  182. runbooks/operate/ec2_operations.py +145 -137
  183. runbooks/operate/iam_operations.py +146 -152
  184. runbooks/operate/networking_cost_heatmap.py +29 -8
  185. runbooks/operate/rds_operations.py +223 -254
  186. runbooks/operate/s3_operations.py +107 -118
  187. runbooks/operate/vpc_operations.py +646 -616
  188. runbooks/remediation/base.py +1 -1
  189. runbooks/remediation/commons.py +10 -7
  190. runbooks/remediation/commvault_ec2_analysis.py +70 -66
  191. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
  192. runbooks/remediation/multi_account.py +24 -21
  193. runbooks/remediation/rds_snapshot_list.py +86 -60
  194. runbooks/remediation/remediation_cli.py +92 -146
  195. runbooks/remediation/universal_account_discovery.py +83 -79
  196. runbooks/remediation/workspaces_list.py +46 -41
  197. runbooks/security/__init__.py +19 -0
  198. runbooks/security/assessment_runner.py +1150 -0
  199. runbooks/security/baseline_checker.py +812 -0
  200. runbooks/security/cloudops_automation_security_validator.py +509 -535
  201. runbooks/security/compliance_automation_engine.py +17 -17
  202. runbooks/security/config/__init__.py +2 -2
  203. runbooks/security/config/compliance_config.py +50 -50
  204. runbooks/security/config_template_generator.py +63 -76
  205. runbooks/security/enterprise_security_framework.py +1 -1
  206. runbooks/security/executive_security_dashboard.py +519 -508
  207. runbooks/security/multi_account_security_controls.py +959 -1210
  208. runbooks/security/real_time_security_monitor.py +422 -444
  209. runbooks/security/security_baseline_tester.py +1 -1
  210. runbooks/security/security_cli.py +143 -112
  211. runbooks/security/test_2way_validation.py +439 -0
  212. runbooks/security/two_way_validation_framework.py +852 -0
  213. runbooks/sre/production_monitoring_framework.py +167 -177
  214. runbooks/tdd/__init__.py +15 -0
  215. runbooks/tdd/cli.py +1071 -0
  216. runbooks/utils/__init__.py +14 -17
  217. runbooks/utils/logger.py +7 -2
  218. runbooks/utils/version_validator.py +50 -47
  219. runbooks/validation/__init__.py +6 -6
  220. runbooks/validation/cli.py +9 -3
  221. runbooks/validation/comprehensive_2way_validator.py +745 -704
  222. runbooks/validation/mcp_validator.py +906 -228
  223. runbooks/validation/terraform_citations_validator.py +104 -115
  224. runbooks/validation/terraform_drift_detector.py +461 -454
  225. runbooks/vpc/README.md +617 -0
  226. runbooks/vpc/__init__.py +8 -1
  227. runbooks/vpc/analyzer.py +577 -0
  228. runbooks/vpc/cleanup_wrapper.py +476 -413
  229. runbooks/vpc/cli_cloudtrail_commands.py +339 -0
  230. runbooks/vpc/cli_mcp_validation_commands.py +480 -0
  231. runbooks/vpc/cloudtrail_audit_integration.py +717 -0
  232. runbooks/vpc/config.py +92 -97
  233. runbooks/vpc/cost_engine.py +411 -148
  234. runbooks/vpc/cost_explorer_integration.py +553 -0
  235. runbooks/vpc/cross_account_session.py +101 -106
  236. runbooks/vpc/enhanced_mcp_validation.py +917 -0
  237. runbooks/vpc/eni_gate_validator.py +961 -0
  238. runbooks/vpc/heatmap_engine.py +185 -160
  239. runbooks/vpc/mcp_no_eni_validator.py +680 -639
  240. runbooks/vpc/nat_gateway_optimizer.py +358 -0
  241. runbooks/vpc/networking_wrapper.py +15 -8
  242. runbooks/vpc/pdca_remediation_planner.py +528 -0
  243. runbooks/vpc/performance_optimized_analyzer.py +219 -231
  244. runbooks/vpc/runbooks_adapter.py +1167 -241
  245. runbooks/vpc/tdd_red_phase_stubs.py +601 -0
  246. runbooks/vpc/test_data_loader.py +358 -0
  247. runbooks/vpc/tests/conftest.py +314 -4
  248. runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
  249. runbooks/vpc/tests/test_cost_engine.py +0 -2
  250. runbooks/vpc/topology_generator.py +326 -0
  251. runbooks/vpc/unified_scenarios.py +1297 -1124
  252. runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
  253. runbooks-1.1.6.dist-info/METADATA +327 -0
  254. runbooks-1.1.6.dist-info/RECORD +489 -0
  255. runbooks/finops/README.md +0 -414
  256. runbooks/finops/accuracy_cross_validator.py +0 -647
  257. runbooks/finops/business_cases.py +0 -950
  258. runbooks/finops/dashboard_router.py +0 -922
  259. runbooks/finops/ebs_optimizer.py +0 -973
  260. runbooks/finops/embedded_mcp_validator.py +0 -1629
  261. runbooks/finops/enhanced_dashboard_runner.py +0 -527
  262. runbooks/finops/finops_dashboard.py +0 -584
  263. runbooks/finops/finops_scenarios.py +0 -1218
  264. runbooks/finops/legacy_migration.py +0 -730
  265. runbooks/finops/multi_dashboard.py +0 -1519
  266. runbooks/finops/single_dashboard.py +0 -1113
  267. runbooks/finops/unlimited_scenarios.py +0 -393
  268. runbooks-1.1.4.dist-info/METADATA +0 -800
  269. runbooks-1.1.4.dist-info/RECORD +0 -468
  270. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
  271. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
  272. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
  273. {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
@@ -35,7 +35,7 @@ from runbooks.common.rich_utils import console, print_error, print_info, print_w
35
35
  @dataclass
36
36
  class AWSAccount:
37
37
  """Universal AWS account representation."""
38
-
38
+
39
39
  account_id: str
40
40
  account_name: Optional[str] = None
41
41
  status: str = "ACTIVE"
@@ -47,86 +47,84 @@ class AWSAccount:
47
47
  class UniversalAccountDiscovery:
48
48
  """
49
49
  Universal AWS account discovery that works with ANY AWS setup.
50
-
50
+
51
51
  Discovery methods (in priority order):
52
52
  1. Environment variables (REMEDIATION_TARGET_ACCOUNTS)
53
53
  2. Configuration file (REMEDIATION_ACCOUNT_CONFIG)
54
54
  3. AWS Organizations API (if available)
55
55
  4. Current account (single account mode)
56
-
56
+
57
57
  No hardcoded account arrays - fully dynamic discovery.
58
58
  """
59
-
59
+
60
60
  def __init__(self, profile: Optional[str] = None):
61
61
  """Initialize universal account discovery."""
62
62
  self.profile = profile
63
63
  self.resolved_profile = get_profile_for_operation("management", profile)
64
64
  self.session = self._create_session()
65
-
65
+
66
66
  def _create_session(self) -> boto3.Session:
67
67
  """Create AWS session using universal profile management."""
68
68
  return boto3.Session(profile_name=self.resolved_profile)
69
-
69
+
70
70
  def discover_target_accounts(self, include_current: bool = True) -> List[AWSAccount]:
71
71
  """
72
72
  Discover target accounts for remediation using universal approach.
73
-
73
+
74
74
  Args:
75
75
  include_current: Include current account in results
76
-
76
+
77
77
  Returns:
78
78
  List[AWSAccount]: Discovered target accounts
79
79
  """
80
80
  console.log("[cyan]🔍 Starting universal account discovery...[/]")
81
-
81
+
82
82
  discovered_accounts = []
83
-
83
+
84
84
  # Method 1: Environment variables (highest priority)
85
85
  env_accounts = self._get_accounts_from_environment()
86
86
  if env_accounts:
87
87
  console.log(f"[green]✓ Found {len(env_accounts)} accounts from environment variables[/]")
88
88
  discovered_accounts.extend(env_accounts)
89
89
  return discovered_accounts
90
-
90
+
91
91
  # Method 2: Configuration file
92
92
  config_accounts = self._get_accounts_from_config()
93
93
  if config_accounts:
94
94
  console.log(f"[green]✓ Found {len(config_accounts)} accounts from configuration file[/]")
95
95
  discovered_accounts.extend(config_accounts)
96
96
  return discovered_accounts
97
-
97
+
98
98
  # Method 3: AWS Organizations API (if available)
99
99
  org_accounts = self._get_accounts_from_organizations()
100
100
  if org_accounts:
101
101
  console.log(f"[green]✓ Found {len(org_accounts)} accounts from AWS Organizations[/]")
102
102
  discovered_accounts.extend(org_accounts)
103
103
  return discovered_accounts
104
-
104
+
105
105
  # Method 4: Current account fallback (single account mode)
106
106
  if include_current:
107
107
  current_account = self._get_current_account()
108
108
  if current_account:
109
109
  console.log("[yellow]🔍 Single account mode: Using current account[/]")
110
110
  discovered_accounts.append(current_account)
111
-
111
+
112
112
  if not discovered_accounts:
113
113
  print_warning("No target accounts discovered. Check configuration or permissions.")
114
-
114
+
115
115
  return discovered_accounts
116
-
116
+
117
117
  def _get_accounts_from_environment(self) -> List[AWSAccount]:
118
118
  """Get accounts from environment variables."""
119
119
  env_accounts = os.getenv("REMEDIATION_TARGET_ACCOUNTS")
120
120
  if not env_accounts:
121
121
  return []
122
-
122
+
123
123
  try:
124
124
  account_ids = [acc.strip() for acc in env_accounts.split(",")]
125
125
  return [
126
126
  AWSAccount(
127
- account_id=account_id,
128
- account_name=f"Env-Account-{account_id}",
129
- profile_name=self.resolved_profile
127
+ account_id=account_id, account_name=f"Env-Account-{account_id}", profile_name=self.resolved_profile
130
128
  )
131
129
  for account_id in account_ids
132
130
  if account_id
@@ -134,17 +132,17 @@ class UniversalAccountDiscovery:
134
132
  except Exception as e:
135
133
  print_warning(f"Failed to parse REMEDIATION_TARGET_ACCOUNTS: {e}")
136
134
  return []
137
-
135
+
138
136
  def _get_accounts_from_config(self) -> List[AWSAccount]:
139
137
  """Get accounts from configuration file."""
140
138
  config_path = os.getenv("REMEDIATION_ACCOUNT_CONFIG")
141
139
  if not config_path or not os.path.exists(config_path):
142
140
  return []
143
-
141
+
144
142
  try:
145
- with open(config_path, 'r') as f:
143
+ with open(config_path, "r") as f:
146
144
  config = json.load(f)
147
-
145
+
148
146
  accounts = []
149
147
  for account_config in config.get("target_accounts", []):
150
148
  account = AWSAccount(
@@ -152,27 +150,27 @@ class UniversalAccountDiscovery:
152
150
  account_name=account_config.get("account_name"),
153
151
  status=account_config.get("status", "ACTIVE"),
154
152
  email=account_config.get("email"),
155
- profile_name=account_config.get("profile_name", self.resolved_profile)
153
+ profile_name=account_config.get("profile_name", self.resolved_profile),
156
154
  )
157
155
  accounts.append(account)
158
-
156
+
159
157
  console.log(f"[dim cyan]Loaded account configuration from: {config_path}[/]")
160
158
  return accounts
161
-
159
+
162
160
  except Exception as e:
163
161
  print_warning(f"Failed to load account configuration from {config_path}: {e}")
164
162
  return []
165
-
163
+
166
164
  def _get_accounts_from_organizations(self) -> List[AWSAccount]:
167
165
  """Get accounts from AWS Organizations API."""
168
166
  try:
169
167
  # Check if Organizations API is available
170
168
  orgs_client = self.session.client("organizations")
171
-
169
+
172
170
  # Try to list accounts
173
171
  paginator = orgs_client.get_paginator("list_accounts")
174
172
  accounts = []
175
-
173
+
176
174
  for page in paginator.paginate():
177
175
  for account in page["Accounts"]:
178
176
  aws_account = AWSAccount(
@@ -181,16 +179,16 @@ class UniversalAccountDiscovery:
181
179
  status=account.get("Status", "ACTIVE"),
182
180
  email=account.get("Email"),
183
181
  joined_method=account.get("JoinedMethod"),
184
- profile_name=self.resolved_profile
182
+ profile_name=self.resolved_profile,
185
183
  )
186
184
  accounts.append(aws_account)
187
-
185
+
188
186
  # Filter to active accounts only
189
187
  active_accounts = [acc for acc in accounts if acc.status == "ACTIVE"]
190
188
  console.log(f"[dim cyan]Discovered {len(active_accounts)} active accounts via Organizations API[/]")
191
-
189
+
192
190
  return active_accounts
193
-
191
+
194
192
  except ClientError as e:
195
193
  error_code = e.response.get("Error", {}).get("Code", "Unknown")
196
194
  if error_code in ["AccessDenied", "AWSOrganizationsNotInUseException"]:
@@ -201,103 +199,109 @@ class UniversalAccountDiscovery:
201
199
  except Exception as e:
202
200
  print_warning(f"Failed to access Organizations API: {e}")
203
201
  return []
204
-
202
+
205
203
  def _get_current_account(self) -> Optional[AWSAccount]:
206
204
  """Get current account as fallback."""
207
205
  try:
208
206
  sts_client = self.session.client("sts")
209
207
  identity = sts_client.get_caller_identity()
210
-
208
+
211
209
  return AWSAccount(
212
210
  account_id=identity["Account"],
213
211
  account_name=f"Current-Account-{identity['Account']}",
214
212
  status="ACTIVE",
215
- profile_name=self.resolved_profile
213
+ profile_name=self.resolved_profile,
216
214
  )
217
-
215
+
218
216
  except Exception as e:
219
217
  print_error(f"Failed to get current account identity: {e}")
220
218
  return None
221
-
219
+
222
220
  def filter_accounts_by_criteria(
223
- self,
224
- accounts: List[AWSAccount],
221
+ self,
222
+ accounts: List[AWSAccount],
225
223
  include_patterns: Optional[List[str]] = None,
226
224
  exclude_patterns: Optional[List[str]] = None,
227
- max_accounts: Optional[int] = None
225
+ max_accounts: Optional[int] = None,
228
226
  ) -> List[AWSAccount]:
229
227
  """
230
228
  Filter discovered accounts by various criteria.
231
-
229
+
232
230
  Args:
233
231
  accounts: List of discovered accounts
234
232
  include_patterns: Account ID or name patterns to include
235
233
  exclude_patterns: Account ID or name patterns to exclude
236
234
  max_accounts: Maximum number of accounts to return
237
-
235
+
238
236
  Returns:
239
237
  List[AWSAccount]: Filtered accounts
240
238
  """
241
239
  filtered_accounts = accounts.copy()
242
-
240
+
243
241
  # Apply include patterns
244
242
  if include_patterns:
245
243
  filtered_accounts = [
246
- acc for acc in filtered_accounts
247
- if any(pattern in acc.account_id or (acc.account_name and pattern in acc.account_name)
248
- for pattern in include_patterns)
244
+ acc
245
+ for acc in filtered_accounts
246
+ if any(
247
+ pattern in acc.account_id or (acc.account_name and pattern in acc.account_name)
248
+ for pattern in include_patterns
249
+ )
249
250
  ]
250
-
251
+
251
252
  # Apply exclude patterns
252
253
  if exclude_patterns:
253
254
  filtered_accounts = [
254
- acc for acc in filtered_accounts
255
- if not any(pattern in acc.account_id or (acc.account_name and pattern in acc.account_name)
256
- for pattern in exclude_patterns)
255
+ acc
256
+ for acc in filtered_accounts
257
+ if not any(
258
+ pattern in acc.account_id or (acc.account_name and pattern in acc.account_name)
259
+ for pattern in exclude_patterns
260
+ )
257
261
  ]
258
-
262
+
259
263
  # Apply max accounts limit
260
264
  if max_accounts and len(filtered_accounts) > max_accounts:
261
265
  console.log(f"[yellow]Limiting to {max_accounts} accounts (found {len(filtered_accounts)})[/]")
262
266
  filtered_accounts = filtered_accounts[:max_accounts]
263
-
267
+
264
268
  return filtered_accounts
265
-
269
+
266
270
  def validate_account_access(self, accounts: List[AWSAccount]) -> List[AWSAccount]:
267
271
  """
268
272
  Validate access to discovered accounts.
269
-
273
+
270
274
  Args:
271
275
  accounts: List of accounts to validate
272
-
276
+
273
277
  Returns:
274
278
  List[AWSAccount]: Accounts with validated access
275
279
  """
276
280
  validated_accounts = []
277
-
281
+
278
282
  for account in accounts:
279
283
  try:
280
284
  # Try to get caller identity to validate access
281
285
  session = boto3.Session(profile_name=account.profile_name or self.resolved_profile)
282
286
  sts_client = session.client("sts")
283
287
  identity = sts_client.get_caller_identity()
284
-
288
+
285
289
  # Verify account ID matches
286
290
  if identity["Account"] == account.account_id:
287
291
  validated_accounts.append(account)
288
292
  console.log(f"[green]✓ Validated access to account: {account.account_id}[/]")
289
293
  else:
290
294
  print_warning(f"Account ID mismatch for {account.account_id}: got {identity['Account']}")
291
-
295
+
292
296
  except Exception as e:
293
297
  print_warning(f"Failed to validate access to account {account.account_id}: {e}")
294
-
298
+
295
299
  return validated_accounts
296
-
300
+
297
301
  def export_account_config_template(self, output_path: str) -> None:
298
302
  """
299
303
  Export account configuration template for enterprise customization.
300
-
304
+
301
305
  Args:
302
306
  output_path: Path to save the configuration template
303
307
  """
@@ -308,25 +312,25 @@ class UniversalAccountDiscovery:
308
312
  "account_name": "Production Environment",
309
313
  "status": "ACTIVE",
310
314
  "email": "prod@company.com",
311
- "profile_name": "prod-profile"
315
+ "profile_name": "prod-profile",
312
316
  },
313
317
  {
314
- "account_id": "444455556666",
318
+ "account_id": "444455556666",
315
319
  "account_name": "Staging Environment",
316
320
  "status": "ACTIVE",
317
321
  "email": "staging@company.com",
318
- "profile_name": "staging-profile"
319
- }
322
+ "profile_name": "staging-profile",
323
+ },
320
324
  ],
321
325
  "discovery_settings": {
322
326
  "max_concurrent_accounts": 10,
323
327
  "validation_timeout_seconds": 30,
324
- "include_suspended_accounts": False
325
- }
328
+ "include_suspended_accounts": False,
329
+ },
326
330
  }
327
-
331
+
328
332
  try:
329
- with open(output_path, 'w') as f:
333
+ with open(output_path, "w") as f:
330
334
  json.dump(template, f, indent=2)
331
335
  console.log(f"[green]Account configuration template exported to: {output_path}[/]")
332
336
  except Exception as e:
@@ -336,10 +340,10 @@ class UniversalAccountDiscovery:
336
340
  def discover_remediation_accounts(profile: Optional[str] = None) -> List[AWSAccount]:
337
341
  """
338
342
  Convenience function for universal account discovery.
339
-
343
+
340
344
  Args:
341
345
  profile: AWS profile to use for discovery
342
-
346
+
343
347
  Returns:
344
348
  List[AWSAccount]: Discovered accounts for remediation
345
349
  """
@@ -350,21 +354,21 @@ def discover_remediation_accounts(profile: Optional[str] = None) -> List[AWSAcco
350
354
  def get_account_by_id(account_id: str, profile: Optional[str] = None) -> Optional[AWSAccount]:
351
355
  """
352
356
  Get specific account by ID using universal discovery.
353
-
357
+
354
358
  Args:
355
359
  account_id: Target account ID
356
360
  profile: AWS profile to use
357
-
361
+
358
362
  Returns:
359
363
  Optional[AWSAccount]: Account if found, None otherwise
360
364
  """
361
365
  discovery = UniversalAccountDiscovery(profile=profile)
362
366
  accounts = discovery.discover_target_accounts()
363
-
367
+
364
368
  for account in accounts:
365
369
  if account.account_id == account_id:
366
370
  return account
367
-
371
+
368
372
  return None
369
373
 
370
374
 
@@ -372,6 +376,6 @@ def get_account_by_id(account_id: str, profile: Optional[str] = None) -> Optiona
372
376
  __all__ = [
373
377
  "AWSAccount",
374
378
  "UniversalAccountDiscovery",
375
- "discover_remediation_accounts",
379
+ "discover_remediation_accounts",
376
380
  "get_account_by_id",
377
- ]
381
+ ]
@@ -15,8 +15,14 @@ from botocore.exceptions import ClientError
15
15
 
16
16
  from .commons import display_aws_account_info, get_client, write_to_csv
17
17
  from ..common.rich_utils import (
18
- console, print_header, print_success, print_error, print_warning,
19
- create_table, create_progress_bar, format_cost
18
+ console,
19
+ print_header,
20
+ print_success,
21
+ print_error,
22
+ print_warning,
23
+ create_table,
24
+ create_progress_bar,
25
+ format_cost,
20
26
  )
21
27
 
22
28
  logger = logging.getLogger(__name__)
@@ -25,7 +31,7 @@ logger = logging.getLogger(__name__)
25
31
  def calculate_workspace_monthly_cost(workspace_bundle_id: str, running_mode: str) -> float:
26
32
  """
27
33
  Calculate monthly cost for WorkSpace based on bundle and running mode.
28
-
34
+
29
35
  JIRA FinOps-24: Cost calculations for significant annual savings savings target
30
36
  Based on AWS WorkSpaces pricing: https://aws.amazon.com/workspaces/pricing/
31
37
  """
@@ -34,27 +40,23 @@ def calculate_workspace_monthly_cost(workspace_bundle_id: str, running_mode: str
34
40
  # Value bundles
35
41
  "wsb-bh8rsxt14": {"name": "Value", "monthly": 25.0, "hourly": 0.22}, # Windows 10 Value
36
42
  "wsb-3t36q8qkj": {"name": "Value", "monthly": 25.0, "hourly": 0.22}, # Amazon Linux 2 Value
37
-
38
- # Standard bundles
43
+ # Standard bundles
39
44
  "wsb-92tn3b7gx": {"name": "Standard", "monthly": 35.0, "hourly": 0.50}, # Windows 10 Standard
40
45
  "wsb-2bs6k5lgj": {"name": "Standard", "monthly": 35.0, "hourly": 0.50}, # Amazon Linux 2 Standard
41
-
42
46
  # Performance bundles
43
47
  "wsb-gk1wpk43z": {"name": "Performance", "monthly": 68.0, "hourly": 0.85}, # Windows 10 Performance
44
48
  "wsb-1b5w6vnzg": {"name": "Performance", "monthly": 68.0, "hourly": 0.85}, # Amazon Linux 2 Performance
45
-
46
49
  # PowerPro bundles
47
50
  "wsb-8vbljg4r6": {"name": "PowerPro", "monthly": 134.0, "hourly": 1.50}, # Windows 10 PowerPro
48
51
  "wsb-vbljg4r61": {"name": "PowerPro", "monthly": 134.0, "hourly": 1.50}, # Amazon Linux 2 PowerPro
49
-
50
52
  # Graphics bundles
51
53
  "wsb-1pzkp0bx8": {"name": "Graphics", "monthly": 144.0, "hourly": 1.75}, # Windows 10 Graphics
52
54
  "wsb-pszkp0bx9": {"name": "Graphics", "monthly": 144.0, "hourly": 1.75}, # Amazon Linux 2 Graphics
53
55
  }
54
-
56
+
55
57
  # Get bundle info or use default
56
58
  bundle_info = bundle_costs.get(workspace_bundle_id, {"name": "Standard", "monthly": 35.0, "hourly": 0.50})
57
-
59
+
58
60
  # Calculate cost based on running mode
59
61
  if running_mode.upper() == "AUTO_STOP":
60
62
  # Auto-stop: Pay monthly fee + hourly usage (simplified to monthly for unused)
@@ -113,12 +115,12 @@ def get_workspaces(
113
115
  ):
114
116
  """
115
117
  🚨 HIGH-RISK: Analyze WorkSpaces usage and optionally delete unused ones.
116
-
118
+
117
119
  WorkSpaces Resource Optimization: Enhanced cleanup with dynamic cost calculation using business case configuration
118
120
  """
119
121
 
120
122
  print_header("WorkSpaces Cost Optimization Analysis", "latest version")
121
-
123
+
122
124
  # HIGH-RISK OPERATION WARNING
123
125
  if delete_unused and not confirm:
124
126
  print_warning("🚨 HIGH-RISK OPERATION: WorkSpace deletion")
@@ -143,7 +145,9 @@ def get_workspaces(
143
145
  start_time = end_time - timedelta(days=days)
144
146
  unused_threshold = end_time - timedelta(days=unused_days)
145
147
 
146
- console.print(f"[dim]Analyzing usage from {start_time.strftime('%Y-%m-%d')} to {end_time.strftime('%Y-%m-%d')}[/dim]")
148
+ console.print(
149
+ f"[dim]Analyzing usage from {start_time.strftime('%Y-%m-%d')} to {end_time.strftime('%Y-%m-%d')}[/dim]"
150
+ )
147
151
 
148
152
  # Collect all workspaces first for progress tracking
149
153
  all_workspaces = []
@@ -153,12 +157,9 @@ def get_workspaces(
153
157
 
154
158
  total_cost = 0.0
155
159
  unused_cost = 0.0
156
-
160
+
157
161
  with create_progress_bar() as progress:
158
- task_id = progress.add_task(
159
- f"Analyzing {len(all_workspaces)} WorkSpaces...",
160
- total=len(all_workspaces)
161
- )
162
+ task_id = progress.add_task(f"Analyzing {len(all_workspaces)} WorkSpaces...", total=len(all_workspaces))
162
163
 
163
164
  for workspace in all_workspaces:
164
165
  workspace_id = workspace["WorkspaceId"]
@@ -235,15 +236,15 @@ def get_workspaces(
235
236
 
236
237
  # Create summary table with Rich CLI
237
238
  print_header("WorkSpaces Analysis Summary")
238
-
239
+
239
240
  summary_table = create_table(
240
241
  title="WorkSpaces Cost Analysis - JIRA FinOps-24",
241
242
  columns=[
242
243
  {"header": "Metric", "style": "cyan"},
243
244
  {"header": "Value", "style": "green bold"},
244
245
  {"header": "Monthly Cost", "style": "red"},
245
- {"header": "Annual Cost", "style": "red bold"}
246
- ]
246
+ {"header": "Annual Cost", "style": "red bold"},
247
+ ],
247
248
  )
248
249
 
249
250
  # Basic metrics
@@ -251,32 +252,32 @@ def get_workspaces(
251
252
  "Total WorkSpaces",
252
253
  str(len(data)),
253
254
  format_cost(total_cost) if calculate_savings or analyze else "N/A",
254
- format_cost(total_cost * 12) if calculate_savings or analyze else "N/A"
255
+ format_cost(total_cost * 12) if calculate_savings or analyze else "N/A",
255
256
  )
256
-
257
+
257
258
  summary_table.add_row(
258
259
  f"Unused WorkSpaces (>{unused_days} days)",
259
260
  str(len(unused_workspaces)),
260
- format_cost(unused_cost) if calculate_savings or analyze else "N/A",
261
- format_cost(unused_cost * 12) if calculate_savings or analyze else "N/A"
261
+ format_cost(unused_cost) if calculate_savings or analyze else "N/A",
262
+ format_cost(unused_cost * 12) if calculate_savings or analyze else "N/A",
262
263
  )
263
264
 
264
265
  if calculate_savings or analyze:
265
266
  potential_savings_monthly = unused_cost
266
267
  potential_savings_annual = unused_cost * 12
267
-
268
+
268
269
  summary_table.add_row(
269
270
  "🎯 Potential Savings",
270
271
  f"{len(unused_workspaces)} WorkSpaces",
271
272
  format_cost(potential_savings_monthly),
272
- format_cost(potential_savings_annual)
273
+ format_cost(potential_savings_annual),
273
274
  )
274
275
 
275
276
  console.print(summary_table)
276
277
 
277
278
  if unused_workspaces:
278
279
  print_warning(f"⚠ Found {len(unused_workspaces)} unused WorkSpaces:")
279
-
280
+
280
281
  # Create detailed unused workspaces table
281
282
  unused_table = create_table(
282
283
  title="Unused WorkSpaces Details",
@@ -286,22 +287,22 @@ def get_workspaces(
286
287
  {"header": "Days Since Connection", "style": "yellow"},
287
288
  {"header": "Running Mode", "style": "green"},
288
289
  {"header": "Monthly Cost", "style": "red"},
289
- {"header": "State", "style": "magenta"}
290
- ]
290
+ {"header": "State", "style": "magenta"},
291
+ ],
291
292
  )
292
-
293
+
293
294
  for ws in unused_workspaces[:10]: # Show first 10 for readability
294
295
  unused_table.add_row(
295
- ws['WorkspaceId'],
296
- ws['UserName'],
297
- str(ws['DaysSinceConnection']),
298
- ws['RunningMode'],
299
- format_cost(ws['MonthlyCost']) if ws['MonthlyCost'] > 0 else "N/A",
300
- ws['State']
296
+ ws["WorkspaceId"],
297
+ ws["UserName"],
298
+ str(ws["DaysSinceConnection"]),
299
+ ws["RunningMode"],
300
+ format_cost(ws["MonthlyCost"]) if ws["MonthlyCost"] > 0 else "N/A",
301
+ ws["State"],
301
302
  )
302
-
303
+
303
304
  console.print(unused_table)
304
-
305
+
305
306
  if len(unused_workspaces) > 10:
306
307
  console.print(f"[dim]... and {len(unused_workspaces) - 10} more unused WorkSpaces[/dim]")
307
308
 
@@ -309,9 +310,13 @@ def get_workspaces(
309
310
  if calculate_savings or analyze:
310
311
  target_annual_savings = 12518.0 # JIRA FinOps-24 target
311
312
  if potential_savings_annual >= target_annual_savings * 0.8: # 80% of target
312
- print_success(f"🎯 Target Achievement: {potential_savings_annual/target_annual_savings*100:.1f}% of significant annual savings savings target")
313
+ print_success(
314
+ f"🎯 Target Achievement: {potential_savings_annual / target_annual_savings * 100:.1f}% of significant annual savings savings target"
315
+ )
313
316
  else:
314
- print_warning(f"📊 Analysis: {potential_savings_annual/target_annual_savings*100:.1f}% of significant annual savings savings target")
317
+ print_warning(
318
+ f"📊 Analysis: {potential_savings_annual / target_annual_savings * 100:.1f}% of significant annual savings savings target"
319
+ )
315
320
 
316
321
  # Handle deletion of unused WorkSpaces
317
322
  if delete_unused and unused_workspaces:
@@ -210,6 +210,16 @@ from .run_script import parse_arguments
210
210
  from .security_baseline_tester import SecurityBaselineTester
211
211
  from .security_export import SecurityExporter
212
212
 
213
+ # Import new assessment and baseline modules
214
+ from .assessment_runner import (
215
+ SecurityAssessmentRunner,
216
+ SecurityAssessmentResults,
217
+ SecurityCheckResult,
218
+ SecurityFrameworkType,
219
+ SecurityCheckSeverity,
220
+ )
221
+ from .baseline_checker import SecurityBaselineChecker, BaselineAssessmentResults, BaselineCheckType
222
+
213
223
  # Import centralized version from main runbooks package
214
224
  from runbooks import __version__
215
225
 
@@ -300,6 +310,15 @@ __all__ = [
300
310
  # CLI functions
301
311
  "run_security_script",
302
312
  "parse_arguments",
313
+ # New assessment and baseline modules
314
+ "SecurityAssessmentRunner",
315
+ "SecurityAssessmentResults",
316
+ "SecurityCheckResult",
317
+ "SecurityFrameworkType",
318
+ "SecurityCheckSeverity",
319
+ "SecurityBaselineChecker",
320
+ "BaselineAssessmentResults",
321
+ "BaselineCheckType",
303
322
  # Metadata
304
323
  "__version__",
305
324
  "__author__",