runbooks 1.1.4__py3-none-any.whl → 1.1.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.
- 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 +138 -35
- 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 +11 -0
- 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 +63 -74
- 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 +201 -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/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/drift_detection_cli.py +69 -96
- runbooks/inventory/inventory_mcp_cli.py +48 -46
- runbooks/inventory/list_rds_snapshots_aggregator.py +192 -208
- runbooks/inventory/mcp_inventory_validator.py +549 -465
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +55 -51
- runbooks/inventory/rich_inventory_display.py +33 -32
- runbooks/inventory/unified_validation_engine.py +278 -251
- runbooks/inventory/vpc_analyzer.py +732 -695
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +382 -378
- 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 +447 -451
- 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.5.dist-info/METADATA +328 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/RECORD +214 -193
- 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 → runbooks-1.1.5.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.5.dist-info}/top_level.txt +0 -0
@@ -23,8 +23,16 @@ import click
|
|
23
23
|
from botocore.exceptions import ClientError
|
24
24
|
|
25
25
|
from ..common.rich_utils import (
|
26
|
-
console,
|
27
|
-
|
26
|
+
console,
|
27
|
+
create_panel,
|
28
|
+
create_progress_bar,
|
29
|
+
create_table,
|
30
|
+
format_cost,
|
31
|
+
print_error,
|
32
|
+
print_header,
|
33
|
+
print_info,
|
34
|
+
print_success,
|
35
|
+
print_warning,
|
28
36
|
)
|
29
37
|
|
30
38
|
logger = logging.getLogger(__name__)
|
@@ -33,11 +41,11 @@ logger = logging.getLogger(__name__)
|
|
33
41
|
class CommvaultEC2Analysis:
|
34
42
|
"""
|
35
43
|
FinOps-25: Commvault EC2 Investigation Framework
|
36
|
-
|
44
|
+
|
37
45
|
Provides systematic analysis of EC2 instances in Commvault backup account
|
38
46
|
to determine utilization patterns and optimization opportunities.
|
39
47
|
"""
|
40
|
-
|
48
|
+
|
41
49
|
def __init__(self, profile_name: Optional[str] = None, account_id: Optional[str] = None):
|
42
50
|
"""
|
43
51
|
Initialize Commvault EC2 analysis.
|
@@ -54,325 +62,308 @@ class CommvaultEC2Analysis:
|
|
54
62
|
self.account_id = account_id
|
55
63
|
else:
|
56
64
|
try:
|
57
|
-
self.account_id = self.session.client(
|
65
|
+
self.account_id = self.session.client("sts").get_caller_identity()["Account"]
|
58
66
|
except Exception as e:
|
59
67
|
logger.warning(f"Could not resolve account ID from profile: {e}")
|
60
68
|
self.account_id = "unknown"
|
61
|
-
|
69
|
+
|
62
70
|
def analyze_commvault_instances(self, region: str = "us-east-1") -> Dict:
|
63
71
|
"""
|
64
72
|
Analyze EC2 instances in Commvault account for utilization patterns.
|
65
|
-
|
73
|
+
|
66
74
|
Args:
|
67
75
|
region: AWS region to analyze (default: us-east-1)
|
68
|
-
|
76
|
+
|
69
77
|
Returns:
|
70
78
|
Dict containing analysis results with cost implications
|
71
79
|
"""
|
72
80
|
print_header("FinOps-25: Commvault EC2 Investigation", f"Account: {self.account_id}")
|
73
|
-
|
81
|
+
|
74
82
|
try:
|
75
|
-
ec2_client = self.session.client(
|
76
|
-
cloudwatch_client = self.session.client(
|
77
|
-
|
83
|
+
ec2_client = self.session.client("ec2", region_name=region)
|
84
|
+
cloudwatch_client = self.session.client("cloudwatch", region_name=region)
|
85
|
+
|
78
86
|
# Get all EC2 instances
|
79
87
|
response = ec2_client.describe_instances()
|
80
88
|
instances = []
|
81
|
-
|
82
|
-
for reservation in response[
|
83
|
-
for instance in reservation[
|
84
|
-
if instance[
|
89
|
+
|
90
|
+
for reservation in response["Reservations"]:
|
91
|
+
for instance in reservation["Instances"]:
|
92
|
+
if instance["State"]["Name"] != "terminated":
|
85
93
|
instances.append(instance)
|
86
|
-
|
94
|
+
|
87
95
|
if not instances:
|
88
96
|
print_warning(f"No active instances found in account {self.account_id}")
|
89
97
|
return {"instances": [], "total_cost": 0, "optimization_potential": 0}
|
90
|
-
|
98
|
+
|
91
99
|
print_info(f"Found {len(instances)} active instances for analysis")
|
92
|
-
|
100
|
+
|
93
101
|
# Analyze each instance
|
94
102
|
analysis_results = []
|
95
103
|
total_monthly_cost = 0
|
96
|
-
|
104
|
+
|
97
105
|
with create_progress_bar() as progress:
|
98
106
|
task = progress.add_task("Analyzing instances...", total=len(instances))
|
99
|
-
|
107
|
+
|
100
108
|
for instance in instances:
|
101
|
-
instance_analysis = self._analyze_single_instance(
|
102
|
-
instance, cloudwatch_client, region
|
103
|
-
)
|
109
|
+
instance_analysis = self._analyze_single_instance(instance, cloudwatch_client, region)
|
104
110
|
analysis_results.append(instance_analysis)
|
105
|
-
total_monthly_cost += instance_analysis[
|
111
|
+
total_monthly_cost += instance_analysis["estimated_monthly_cost"]
|
106
112
|
progress.advance(task)
|
107
|
-
|
113
|
+
|
108
114
|
# Generate summary
|
109
115
|
optimization_potential = self._calculate_optimization_potential(analysis_results)
|
110
|
-
|
116
|
+
|
111
117
|
# Display results
|
112
118
|
self._display_analysis_results(analysis_results, total_monthly_cost, optimization_potential)
|
113
|
-
|
119
|
+
|
114
120
|
return {
|
115
121
|
"instances": analysis_results,
|
116
122
|
"total_monthly_cost": total_monthly_cost,
|
117
123
|
"optimization_potential": optimization_potential,
|
118
124
|
"account_id": self.account_id,
|
119
|
-
"analysis_timestamp": datetime.now().isoformat()
|
125
|
+
"analysis_timestamp": datetime.now().isoformat(),
|
120
126
|
}
|
121
|
-
|
127
|
+
|
122
128
|
except ClientError as e:
|
123
129
|
print_error(f"AWS API Error: {e}")
|
124
130
|
raise
|
125
131
|
except Exception as e:
|
126
132
|
print_error(f"Analysis Error: {e}")
|
127
133
|
raise
|
128
|
-
|
134
|
+
|
129
135
|
def _analyze_single_instance(self, instance: Dict, cloudwatch_client, region: str) -> Dict:
|
130
136
|
"""Analyze a single EC2 instance for utilization patterns."""
|
131
|
-
instance_id = instance[
|
132
|
-
instance_type = instance[
|
133
|
-
|
137
|
+
instance_id = instance["InstanceId"]
|
138
|
+
instance_type = instance["InstanceType"]
|
139
|
+
|
134
140
|
# Get CloudWatch metrics for last 30 days
|
135
141
|
end_time = datetime.now(timezone.utc)
|
136
142
|
start_time = end_time - timedelta(days=30)
|
137
|
-
|
143
|
+
|
138
144
|
try:
|
139
145
|
# CPU Utilization
|
140
146
|
cpu_response = cloudwatch_client.get_metric_statistics(
|
141
|
-
Namespace=
|
142
|
-
MetricName=
|
143
|
-
Dimensions=[{
|
147
|
+
Namespace="AWS/EC2",
|
148
|
+
MetricName="CPUUtilization",
|
149
|
+
Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
|
144
150
|
StartTime=start_time,
|
145
151
|
EndTime=end_time,
|
146
152
|
Period=3600, # 1 hour intervals
|
147
|
-
Statistics=[
|
153
|
+
Statistics=["Average"],
|
148
154
|
)
|
149
|
-
|
155
|
+
|
150
156
|
# Network metrics for backup activity indication
|
151
157
|
network_in_response = cloudwatch_client.get_metric_statistics(
|
152
|
-
Namespace=
|
153
|
-
MetricName=
|
154
|
-
Dimensions=[{
|
158
|
+
Namespace="AWS/EC2",
|
159
|
+
MetricName="NetworkIn",
|
160
|
+
Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
|
155
161
|
StartTime=start_time,
|
156
162
|
EndTime=end_time,
|
157
163
|
Period=3600,
|
158
|
-
Statistics=[
|
164
|
+
Statistics=["Sum"],
|
159
165
|
)
|
160
|
-
|
166
|
+
|
161
167
|
network_out_response = cloudwatch_client.get_metric_statistics(
|
162
|
-
Namespace=
|
163
|
-
MetricName=
|
164
|
-
Dimensions=[{
|
168
|
+
Namespace="AWS/EC2",
|
169
|
+
MetricName="NetworkOut",
|
170
|
+
Dimensions=[{"Name": "InstanceId", "Value": instance_id}],
|
165
171
|
StartTime=start_time,
|
166
172
|
EndTime=end_time,
|
167
173
|
Period=3600,
|
168
|
-
Statistics=[
|
174
|
+
Statistics=["Sum"],
|
169
175
|
)
|
170
|
-
|
176
|
+
|
171
177
|
except ClientError as e:
|
172
178
|
logger.warning(f"CloudWatch metrics unavailable for {instance_id}: {e}")
|
173
|
-
cpu_response = {
|
174
|
-
network_in_response = {
|
175
|
-
network_out_response = {
|
176
|
-
|
179
|
+
cpu_response = {"Datapoints": []}
|
180
|
+
network_in_response = {"Datapoints": []}
|
181
|
+
network_out_response = {"Datapoints": []}
|
182
|
+
|
177
183
|
# Calculate averages
|
178
184
|
avg_cpu = 0
|
179
|
-
if cpu_response[
|
180
|
-
avg_cpu = sum(dp[
|
181
|
-
|
182
|
-
total_network_in =
|
183
|
-
|
184
|
-
|
185
|
+
if cpu_response["Datapoints"]:
|
186
|
+
avg_cpu = sum(dp["Average"] for dp in cpu_response["Datapoints"]) / len(cpu_response["Datapoints"])
|
187
|
+
|
188
|
+
total_network_in = (
|
189
|
+
sum(dp["Sum"] for dp in network_in_response["Datapoints"]) if network_in_response["Datapoints"] else 0
|
190
|
+
)
|
191
|
+
total_network_out = (
|
192
|
+
sum(dp["Sum"] for dp in network_out_response["Datapoints"]) if network_out_response["Datapoints"] else 0
|
193
|
+
)
|
194
|
+
|
185
195
|
# Estimate monthly cost (simplified pricing model)
|
186
196
|
estimated_monthly_cost = self._estimate_instance_cost(instance_type)
|
187
|
-
|
197
|
+
|
188
198
|
# Get instance tags
|
189
|
-
tags = {tag[
|
190
|
-
|
199
|
+
tags = {tag["Key"]: tag["Value"] for tag in instance.get("Tags", [])}
|
200
|
+
|
191
201
|
# Backup activity assessment
|
192
|
-
backup_activity_score = self._assess_backup_activity(
|
193
|
-
|
194
|
-
)
|
195
|
-
|
202
|
+
backup_activity_score = self._assess_backup_activity(avg_cpu, total_network_in, total_network_out, tags)
|
203
|
+
|
196
204
|
return {
|
197
205
|
"instance_id": instance_id,
|
198
206
|
"instance_type": instance_type,
|
199
|
-
"state": instance[
|
200
|
-
"launch_time": instance.get(
|
207
|
+
"state": instance["State"]["Name"],
|
208
|
+
"launch_time": instance.get("LaunchTime", "").isoformat() if instance.get("LaunchTime") else "",
|
201
209
|
"avg_cpu_utilization": round(avg_cpu, 2),
|
202
210
|
"network_in_bytes": int(total_network_in),
|
203
211
|
"network_out_bytes": int(total_network_out),
|
204
212
|
"estimated_monthly_cost": estimated_monthly_cost,
|
205
213
|
"tags": tags,
|
206
214
|
"backup_activity_score": backup_activity_score,
|
207
|
-
"recommendation": self._generate_recommendation(backup_activity_score, avg_cpu)
|
215
|
+
"recommendation": self._generate_recommendation(backup_activity_score, avg_cpu),
|
208
216
|
}
|
209
|
-
|
217
|
+
|
210
218
|
def _estimate_instance_cost(self, instance_type: str) -> float:
|
211
219
|
"""Estimate monthly cost for EC2 instance type."""
|
212
220
|
# Simplified pricing model - actual costs may vary
|
213
221
|
instance_pricing = {
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
222
|
+
"t2.micro": 8.76,
|
223
|
+
"t2.small": 17.52,
|
224
|
+
"t2.medium": 35.04,
|
225
|
+
"t2.large": 70.08,
|
226
|
+
"t3.micro": 7.59,
|
227
|
+
"t3.small": 15.18,
|
228
|
+
"t3.medium": 30.37,
|
229
|
+
"t3.large": 60.74,
|
230
|
+
"m5.large": 70.08,
|
231
|
+
"m5.xlarge": 140.16,
|
232
|
+
"m5.2xlarge": 280.32,
|
233
|
+
"c5.large": 62.93,
|
234
|
+
"c5.xlarge": 125.87,
|
235
|
+
"r5.large": 91.98,
|
236
|
+
"r5.xlarge": 183.96,
|
229
237
|
}
|
230
|
-
|
238
|
+
|
231
239
|
return instance_pricing.get(instance_type, 100.0) # Default estimate
|
232
|
-
|
240
|
+
|
233
241
|
def _assess_backup_activity(self, cpu: float, network_in: int, network_out: int, tags: Dict) -> str:
|
234
242
|
"""Assess likelihood of backup activity based on metrics and tags."""
|
235
243
|
score_factors = []
|
236
|
-
|
244
|
+
|
237
245
|
# CPU utilization assessment
|
238
246
|
if cpu > 20:
|
239
247
|
score_factors.append("High CPU usage suggests active processes")
|
240
248
|
elif cpu < 5:
|
241
249
|
score_factors.append("Low CPU usage may indicate idle instance")
|
242
|
-
|
250
|
+
|
243
251
|
# Network activity assessment
|
244
252
|
total_network = network_in + network_out
|
245
253
|
if total_network > 10 * 1024**3: # 10 GB
|
246
254
|
score_factors.append("High network activity suggests data transfer")
|
247
255
|
elif total_network < 1 * 1024**3: # 1 GB
|
248
256
|
score_factors.append("Low network activity may indicate minimal backup activity")
|
249
|
-
|
257
|
+
|
250
258
|
# Tag analysis for Commvault indicators
|
251
|
-
commvault_indicators = [
|
259
|
+
commvault_indicators = ["commvault", "backup", "cv", "media", "agent"]
|
252
260
|
for indicator in commvault_indicators:
|
253
261
|
for tag_value in tags.values():
|
254
262
|
if indicator.lower() in str(tag_value).lower():
|
255
263
|
score_factors.append(f"Tag indicates Commvault purpose: {tag_value}")
|
256
264
|
break
|
257
|
-
|
265
|
+
|
258
266
|
if len(score_factors) >= 2:
|
259
267
|
return "LIKELY_ACTIVE"
|
260
268
|
elif len(score_factors) == 1:
|
261
269
|
return "UNCERTAIN"
|
262
270
|
else:
|
263
271
|
return "LIKELY_IDLE"
|
264
|
-
|
272
|
+
|
265
273
|
def _generate_recommendation(self, activity_score: str, cpu: float) -> str:
|
266
274
|
"""Generate optimization recommendation based on analysis."""
|
267
275
|
if activity_score == "LIKELY_IDLE" and cpu < 5:
|
268
276
|
return "CANDIDATE_FOR_DECOMMISSION"
|
269
277
|
elif activity_score == "UNCERTAIN":
|
270
|
-
return "REQUIRES_DEEPER_INVESTIGATION"
|
278
|
+
return "REQUIRES_DEEPER_INVESTIGATION"
|
271
279
|
elif activity_score == "LIKELY_ACTIVE":
|
272
280
|
return "RETAIN_MONITOR_USAGE"
|
273
281
|
else:
|
274
282
|
return "MANUAL_REVIEW_REQUIRED"
|
275
|
-
|
283
|
+
|
276
284
|
def _calculate_optimization_potential(self, instances: List[Dict]) -> Dict:
|
277
285
|
"""Calculate potential cost savings from optimization."""
|
278
|
-
decommission_candidates = [
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
investigation_required = [
|
284
|
-
i for i in instances
|
285
|
-
if i['recommendation'] == 'REQUIRES_DEEPER_INVESTIGATION'
|
286
|
-
]
|
287
|
-
|
288
|
-
potential_monthly_savings = sum(i['estimated_monthly_cost'] for i in decommission_candidates)
|
286
|
+
decommission_candidates = [i for i in instances if i["recommendation"] == "CANDIDATE_FOR_DECOMMISSION"]
|
287
|
+
|
288
|
+
investigation_required = [i for i in instances if i["recommendation"] == "REQUIRES_DEEPER_INVESTIGATION"]
|
289
|
+
|
290
|
+
potential_monthly_savings = sum(i["estimated_monthly_cost"] for i in decommission_candidates)
|
289
291
|
potential_annual_savings = potential_monthly_savings * 12
|
290
|
-
|
292
|
+
|
291
293
|
return {
|
292
294
|
"decommission_candidates": len(decommission_candidates),
|
293
295
|
"investigation_required": len(investigation_required),
|
294
296
|
"potential_monthly_savings": potential_monthly_savings,
|
295
297
|
"potential_annual_savings": potential_annual_savings,
|
296
|
-
"confidence_level": "HIGH" if len(decommission_candidates) > 0 else "MEDIUM"
|
298
|
+
"confidence_level": "HIGH" if len(decommission_candidates) > 0 else "MEDIUM",
|
297
299
|
}
|
298
|
-
|
300
|
+
|
299
301
|
def _display_analysis_results(self, instances: List[Dict], total_cost: float, optimization: Dict):
|
300
302
|
"""Display comprehensive analysis results."""
|
301
303
|
# Summary table
|
302
304
|
summary_table = create_table(
|
303
305
|
title="FinOps-25: Commvault EC2 Analysis Summary",
|
304
|
-
caption=f"Account: {self.account_id} | Analysis Date: {datetime.now().strftime('%Y-%m-%d')}"
|
306
|
+
caption=f"Account: {self.account_id} | Analysis Date: {datetime.now().strftime('%Y-%m-%d')}",
|
305
307
|
)
|
306
|
-
|
308
|
+
|
307
309
|
summary_table.add_column("Metric", style="cyan", width=25)
|
308
310
|
summary_table.add_column("Value", style="green", justify="right", width=20)
|
309
311
|
summary_table.add_column("Impact", style="yellow", width=30)
|
310
|
-
|
311
|
-
summary_table.add_row(
|
312
|
-
|
313
|
-
str(len(instances)),
|
314
|
-
"Infrastructure scope"
|
315
|
-
)
|
316
|
-
summary_table.add_row(
|
317
|
-
"Monthly Cost",
|
318
|
-
format_cost(total_cost, period="monthly"),
|
319
|
-
"Current infrastructure cost"
|
320
|
-
)
|
312
|
+
|
313
|
+
summary_table.add_row("Total Instances", str(len(instances)), "Infrastructure scope")
|
314
|
+
summary_table.add_row("Monthly Cost", format_cost(total_cost, period="monthly"), "Current infrastructure cost")
|
321
315
|
summary_table.add_row(
|
322
316
|
"Decommission Candidates",
|
323
|
-
str(optimization[
|
324
|
-
"Immediate optimization opportunities"
|
317
|
+
str(optimization["decommission_candidates"]),
|
318
|
+
"Immediate optimization opportunities",
|
325
319
|
)
|
326
320
|
summary_table.add_row(
|
327
|
-
"Investigation Required",
|
328
|
-
str(optimization['investigation_required']),
|
329
|
-
"Further analysis needed"
|
321
|
+
"Investigation Required", str(optimization["investigation_required"]), "Further analysis needed"
|
330
322
|
)
|
331
323
|
summary_table.add_row(
|
332
324
|
"Potential Annual Savings",
|
333
|
-
format_cost(optimization[
|
334
|
-
f"Confidence: {optimization['confidence_level']}"
|
325
|
+
format_cost(optimization["potential_annual_savings"], period="annual"),
|
326
|
+
f"Confidence: {optimization['confidence_level']}",
|
335
327
|
)
|
336
|
-
|
328
|
+
|
337
329
|
console.print(summary_table)
|
338
|
-
|
330
|
+
|
339
331
|
# Detailed instance analysis
|
340
332
|
if instances:
|
341
333
|
detail_table = create_table(
|
342
|
-
title="Detailed Instance Analysis",
|
343
|
-
caption="CPU and network utilization patterns with recommendations"
|
334
|
+
title="Detailed Instance Analysis", caption="CPU and network utilization patterns with recommendations"
|
344
335
|
)
|
345
|
-
|
336
|
+
|
346
337
|
detail_table.add_column("Instance ID", style="cyan", width=18)
|
347
338
|
detail_table.add_column("Type", style="blue", width=12)
|
348
339
|
detail_table.add_column("Avg CPU %", style="yellow", justify="right", width=10)
|
349
340
|
detail_table.add_column("Network (GB)", style="magenta", justify="right", width=12)
|
350
341
|
detail_table.add_column("Monthly Cost", style="green", justify="right", width=12)
|
351
342
|
detail_table.add_column("Recommendation", style="red", width=20)
|
352
|
-
|
343
|
+
|
353
344
|
for instance in instances:
|
354
|
-
network_gb = (instance[
|
355
|
-
|
345
|
+
network_gb = (instance["network_in_bytes"] + instance["network_out_bytes"]) / (1024**3)
|
346
|
+
|
356
347
|
recommendation_style = {
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
}.get(instance[
|
362
|
-
|
348
|
+
"CANDIDATE_FOR_DECOMMISSION": "[red]DECOMMISSION[/red]",
|
349
|
+
"REQUIRES_DEEPER_INVESTIGATION": "[yellow]INVESTIGATE[/yellow]",
|
350
|
+
"RETAIN_MONITOR_USAGE": "[green]RETAIN[/green]",
|
351
|
+
"MANUAL_REVIEW_REQUIRED": "[blue]MANUAL REVIEW[/blue]",
|
352
|
+
}.get(instance["recommendation"], instance["recommendation"])
|
353
|
+
|
363
354
|
detail_table.add_row(
|
364
|
-
instance[
|
365
|
-
instance[
|
355
|
+
instance["instance_id"],
|
356
|
+
instance["instance_type"],
|
366
357
|
f"{instance['avg_cpu_utilization']:.1f}%",
|
367
358
|
f"{network_gb:.1f}",
|
368
|
-
format_cost(instance[
|
369
|
-
recommendation_style
|
359
|
+
format_cost(instance["estimated_monthly_cost"], period="monthly"),
|
360
|
+
recommendation_style,
|
370
361
|
)
|
371
|
-
|
362
|
+
|
372
363
|
console.print(detail_table)
|
373
|
-
|
364
|
+
|
374
365
|
# Business impact panel
|
375
|
-
if optimization[
|
366
|
+
if optimization["potential_annual_savings"] > 0:
|
376
367
|
impact_panel = create_panel(
|
377
368
|
f"[bold green]Business Impact Analysis[/bold green]\n\n"
|
378
369
|
f"💰 [yellow]Optimization Potential:[/yellow] {format_cost(optimization['potential_annual_savings'], period='annual')}\n"
|
@@ -381,15 +372,16 @@ class CommvaultEC2Analysis:
|
|
381
372
|
f"⏱️ [yellow]Timeline:[/yellow] 3-4 weeks investigation + approval process\n\n"
|
382
373
|
f"[blue]Strategic Value:[/blue] Establish investigation methodology for infrastructure optimization\n"
|
383
374
|
f"[blue]Risk Assessment:[/blue] Medium risk - requires careful backup workflow validation",
|
384
|
-
title="FinOps-25: Commvault Investigation Framework Results"
|
375
|
+
title="FinOps-25: Commvault Investigation Framework Results",
|
385
376
|
)
|
386
377
|
console.print(impact_panel)
|
387
|
-
|
378
|
+
|
388
379
|
print_success(f"FinOps-25 analysis complete - {len(instances)} instances analyzed")
|
389
380
|
|
390
381
|
|
391
|
-
def analyze_commvault_ec2(
|
392
|
-
|
382
|
+
def analyze_commvault_ec2(
|
383
|
+
profile: Optional[str] = None, account_id: Optional[str] = None, region: str = "us-east-1"
|
384
|
+
) -> Dict:
|
393
385
|
"""
|
394
386
|
Business wrapper function for FinOps-25 Commvault EC2 investigation.
|
395
387
|
|
@@ -406,26 +398,27 @@ def analyze_commvault_ec2(profile: Optional[str] = None, account_id: Optional[st
|
|
406
398
|
|
407
399
|
|
408
400
|
@click.command()
|
409
|
-
@click.option(
|
410
|
-
@click.option(
|
411
|
-
@click.option(
|
412
|
-
@click.option(
|
401
|
+
@click.option("--profile", help="AWS profile name")
|
402
|
+
@click.option("--account-id", help="Target account ID (defaults to profile account)")
|
403
|
+
@click.option("--region", default="us-east-1", help="AWS region")
|
404
|
+
@click.option("--output-file", help="Save results to file")
|
413
405
|
def main(profile, account_id, region, output_file):
|
414
406
|
"""FinOps-25: Commvault EC2 Investigation Framework - CLI interface."""
|
415
407
|
try:
|
416
408
|
# If account_id not provided, it will be auto-resolved from profile
|
417
409
|
results = analyze_commvault_ec2(profile, account_id, region)
|
418
|
-
|
410
|
+
|
419
411
|
if output_file:
|
420
412
|
import json
|
421
|
-
|
413
|
+
|
414
|
+
with open(output_file, "w") as f:
|
422
415
|
json.dump(results, f, indent=2, default=str)
|
423
416
|
print_success(f"Results saved to {output_file}")
|
424
|
-
|
417
|
+
|
425
418
|
except Exception as e:
|
426
419
|
print_error(f"Analysis failed: {e}")
|
427
420
|
raise click.Abort()
|
428
421
|
|
429
422
|
|
430
|
-
if __name__ ==
|
431
|
-
main()
|
423
|
+
if __name__ == "__main__":
|
424
|
+
main()
|