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.
- runbooks/__init__.py +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/assessment/compliance.py +1 -1
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cli/__init__.py +1 -1
- runbooks/cli/commands/cfat.py +64 -23
- runbooks/cli/commands/finops.py +1005 -54
- runbooks/cli/commands/inventory.py +135 -91
- runbooks/cli/commands/operate.py +9 -36
- runbooks/cli/commands/security.py +42 -18
- runbooks/cli/commands/validation.py +432 -18
- runbooks/cli/commands/vpc.py +81 -17
- runbooks/cli/registry.py +22 -10
- runbooks/cloudops/__init__.py +20 -27
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +544 -542
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +224 -225
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +177 -213
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +17 -12
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +40 -36
- runbooks/common/aws_utils.py +74 -79
- runbooks/common/business_logic.py +126 -104
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
- runbooks/common/cross_account_manager.py +197 -204
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +29 -19
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +476 -493
- runbooks/common/mcp_integration.py +99 -79
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +175 -193
- runbooks/common/patterns.py +23 -25
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +111 -37
- runbooks/common/rich_utils.py +315 -141
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +26 -30
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +484 -618
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +32 -29
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +223 -285
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +337 -174
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1512 -481
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +19 -23
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +64 -65
- runbooks/finops/scenarios.py +1277 -438
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +259 -265
- runbooks/finops/vpc_cleanup_exporter.py +189 -144
- runbooks/finops/vpc_cleanup_optimizer.py +591 -573
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/aws_decorators.py +2 -3
- runbooks/inventory/check_cloudtrail_compliance.py +2 -4
- runbooks/inventory/check_controltower_readiness.py +152 -151
- runbooks/inventory/check_landingzone_readiness.py +85 -84
- runbooks/inventory/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +1 -1
- runbooks/inventory/collectors/aws_networking.py +109 -99
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/drift_detection_cli.py +69 -96
- runbooks/inventory/ec2_vpc_utils.py +2 -2
- runbooks/inventory/find_cfn_drift_detection.py +5 -7
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
- runbooks/inventory/find_cfn_stackset_drift.py +5 -6
- runbooks/inventory/find_ec2_security_groups.py +48 -42
- runbooks/inventory/find_landingzone_versions.py +4 -6
- runbooks/inventory/find_vpc_flow_logs.py +7 -9
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/inventory_modules.py +103 -91
- runbooks/inventory/list_cfn_stacks.py +9 -10
- runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
- runbooks/inventory/list_cfn_stackset_operations.py +79 -57
- runbooks/inventory/list_cfn_stacksets.py +8 -10
- runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
- runbooks/inventory/list_ds_directories.py +65 -53
- runbooks/inventory/list_ec2_availability_zones.py +2 -4
- runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
- runbooks/inventory/list_ec2_instances.py +23 -28
- runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
- runbooks/inventory/list_elbs_load_balancers.py +22 -20
- runbooks/inventory/list_enis_network_interfaces.py +26 -33
- runbooks/inventory/list_guardduty_detectors.py +2 -4
- runbooks/inventory/list_iam_policies.py +2 -4
- runbooks/inventory/list_iam_roles.py +5 -7
- runbooks/inventory/list_iam_saml_providers.py +4 -6
- runbooks/inventory/list_lambda_functions.py +38 -38
- runbooks/inventory/list_org_accounts.py +6 -8
- runbooks/inventory/list_org_accounts_users.py +55 -44
- runbooks/inventory/list_rds_db_instances.py +31 -33
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/list_route53_hosted_zones.py +3 -5
- runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
- runbooks/inventory/list_sns_topics.py +2 -4
- runbooks/inventory/list_ssm_parameters.py +4 -7
- runbooks/inventory/list_vpc_subnets.py +2 -4
- runbooks/inventory/list_vpcs.py +7 -10
- runbooks/inventory/mcp_inventory_validator.py +554 -468
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +63 -55
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +35 -34
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +281 -253
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +735 -697
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +384 -380
- runbooks/inventory/vpc_flow_analyzer.py +1 -1
- runbooks/main.py +49 -34
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/networking_cost_heatmap.py +29 -8
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_operations.py +646 -616
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +70 -66
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +86 -60
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +46 -41
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +50 -47
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +745 -704
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +461 -454
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +185 -160
- runbooks/vpc/mcp_no_eni_validator.py +680 -639
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1297 -1124
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.6.dist-info/METADATA +327 -0
- runbooks-1.1.6.dist-info/RECORD +489 -0
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -973
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.4.dist-info/METADATA +0 -800
- runbooks-1.1.4.dist-info/RECORD +0 -468
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {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,
|
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
|
247
|
-
|
248
|
-
|
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
|
255
|
-
|
256
|
-
|
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,
|
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,
|
19
|
-
|
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(
|
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[
|
296
|
-
ws[
|
297
|
-
str(ws[
|
298
|
-
ws[
|
299
|
-
format_cost(ws[
|
300
|
-
ws[
|
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(
|
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(
|
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:
|
runbooks/security/__init__.py
CHANGED
@@ -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__",
|