runbooks 0.7.6__py3-none-any.whl → 0.7.9__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 (111) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/base.py +5 -1
  3. runbooks/cfat/__init__.py +8 -4
  4. runbooks/cfat/assessment/collectors.py +171 -14
  5. runbooks/cfat/assessment/compliance.py +871 -0
  6. runbooks/cfat/assessment/runner.py +122 -11
  7. runbooks/cfat/models.py +6 -2
  8. runbooks/common/logger.py +14 -0
  9. runbooks/common/rich_utils.py +451 -0
  10. runbooks/enterprise/__init__.py +68 -0
  11. runbooks/enterprise/error_handling.py +411 -0
  12. runbooks/enterprise/logging.py +439 -0
  13. runbooks/enterprise/multi_tenant.py +583 -0
  14. runbooks/finops/README.md +468 -241
  15. runbooks/finops/__init__.py +39 -3
  16. runbooks/finops/cli.py +83 -18
  17. runbooks/finops/cross_validation.py +375 -0
  18. runbooks/finops/dashboard_runner.py +812 -164
  19. runbooks/finops/enhanced_dashboard_runner.py +525 -0
  20. runbooks/finops/finops_dashboard.py +1892 -0
  21. runbooks/finops/helpers.py +485 -51
  22. runbooks/finops/optimizer.py +823 -0
  23. runbooks/finops/tests/__init__.py +19 -0
  24. runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
  25. runbooks/finops/tests/run_comprehensive_tests.py +421 -0
  26. runbooks/finops/tests/run_tests.py +305 -0
  27. runbooks/finops/tests/test_finops_dashboard.py +705 -0
  28. runbooks/finops/tests/test_integration.py +477 -0
  29. runbooks/finops/tests/test_performance.py +380 -0
  30. runbooks/finops/tests/test_performance_benchmarks.py +500 -0
  31. runbooks/finops/tests/test_reference_images_validation.py +867 -0
  32. runbooks/finops/tests/test_single_account_features.py +715 -0
  33. runbooks/finops/tests/validate_test_suite.py +220 -0
  34. runbooks/finops/types.py +1 -1
  35. runbooks/hitl/enhanced_workflow_engine.py +725 -0
  36. runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
  37. runbooks/inventory/collectors/aws_comprehensive.py +442 -0
  38. runbooks/inventory/collectors/enterprise_scale.py +281 -0
  39. runbooks/inventory/core/collector.py +172 -13
  40. runbooks/inventory/discovery.md +1 -1
  41. runbooks/inventory/list_ec2_instances.py +18 -20
  42. runbooks/inventory/list_ssm_parameters.py +31 -3
  43. runbooks/inventory/organizations_discovery.py +1269 -0
  44. runbooks/inventory/rich_inventory_display.py +393 -0
  45. runbooks/inventory/run_on_multi_accounts.py +35 -19
  46. runbooks/inventory/runbooks.security.report_generator.log +0 -0
  47. runbooks/inventory/runbooks.security.run_script.log +0 -0
  48. runbooks/inventory/vpc_flow_analyzer.py +1030 -0
  49. runbooks/main.py +2215 -119
  50. runbooks/metrics/dora_metrics_engine.py +599 -0
  51. runbooks/operate/__init__.py +2 -2
  52. runbooks/operate/base.py +122 -10
  53. runbooks/operate/deployment_framework.py +1032 -0
  54. runbooks/operate/deployment_validator.py +853 -0
  55. runbooks/operate/dynamodb_operations.py +10 -6
  56. runbooks/operate/ec2_operations.py +319 -11
  57. runbooks/operate/executive_dashboard.py +779 -0
  58. runbooks/operate/mcp_integration.py +750 -0
  59. runbooks/operate/nat_gateway_operations.py +1120 -0
  60. runbooks/operate/networking_cost_heatmap.py +685 -0
  61. runbooks/operate/privatelink_operations.py +940 -0
  62. runbooks/operate/s3_operations.py +10 -6
  63. runbooks/operate/vpc_endpoints.py +644 -0
  64. runbooks/operate/vpc_operations.py +1038 -0
  65. runbooks/remediation/__init__.py +2 -2
  66. runbooks/remediation/acm_remediation.py +1 -1
  67. runbooks/remediation/base.py +1 -1
  68. runbooks/remediation/cloudtrail_remediation.py +1 -1
  69. runbooks/remediation/cognito_remediation.py +1 -1
  70. runbooks/remediation/dynamodb_remediation.py +1 -1
  71. runbooks/remediation/ec2_remediation.py +1 -1
  72. runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
  73. runbooks/remediation/kms_enable_key_rotation.py +1 -1
  74. runbooks/remediation/kms_remediation.py +1 -1
  75. runbooks/remediation/lambda_remediation.py +1 -1
  76. runbooks/remediation/multi_account.py +1 -1
  77. runbooks/remediation/rds_remediation.py +1 -1
  78. runbooks/remediation/s3_block_public_access.py +1 -1
  79. runbooks/remediation/s3_enable_access_logging.py +1 -1
  80. runbooks/remediation/s3_encryption.py +1 -1
  81. runbooks/remediation/s3_remediation.py +1 -1
  82. runbooks/remediation/vpc_remediation.py +475 -0
  83. runbooks/security/__init__.py +3 -1
  84. runbooks/security/compliance_automation.py +632 -0
  85. runbooks/security/report_generator.py +10 -0
  86. runbooks/security/run_script.py +31 -5
  87. runbooks/security/security_baseline_tester.py +169 -30
  88. runbooks/security/security_export.py +477 -0
  89. runbooks/validation/__init__.py +10 -0
  90. runbooks/validation/benchmark.py +484 -0
  91. runbooks/validation/cli.py +356 -0
  92. runbooks/validation/mcp_validator.py +768 -0
  93. runbooks/vpc/__init__.py +38 -0
  94. runbooks/vpc/config.py +212 -0
  95. runbooks/vpc/cost_engine.py +347 -0
  96. runbooks/vpc/heatmap_engine.py +605 -0
  97. runbooks/vpc/manager_interface.py +634 -0
  98. runbooks/vpc/networking_wrapper.py +1260 -0
  99. runbooks/vpc/rich_formatters.py +679 -0
  100. runbooks/vpc/tests/__init__.py +5 -0
  101. runbooks/vpc/tests/conftest.py +356 -0
  102. runbooks/vpc/tests/test_cli_integration.py +530 -0
  103. runbooks/vpc/tests/test_config.py +458 -0
  104. runbooks/vpc/tests/test_cost_engine.py +479 -0
  105. runbooks/vpc/tests/test_networking_wrapper.py +512 -0
  106. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
  107. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
  108. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
  109. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
  110. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
  111. {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -24,9 +24,13 @@ from typing import Any, Dict, List, Optional, Union
24
24
  import boto3
25
25
  from botocore.exceptions import BotoCoreError, ClientError
26
26
  from loguru import logger
27
+ from rich.console import Console
27
28
 
28
29
  from runbooks.operate.base import BaseOperation, OperationContext, OperationResult, OperationStatus
29
30
 
31
+ # Initialize Rich console for enhanced CLI output
32
+ console = Console()
33
+
30
34
 
31
35
  class S3Operations(BaseOperation):
32
36
  """
@@ -1148,8 +1152,8 @@ def main():
1148
1152
  import sys
1149
1153
 
1150
1154
  if len(sys.argv) < 2:
1151
- print("Usage: python s3_operations.py <operation> [args...]")
1152
- print("Operations: create-bucket, list-objects, list-buckets, put-object, delete-object")
1155
+ console.print("[yellow]Usage: python s3_operations.py <operation> [args...][/yellow]")
1156
+ console.print("[blue]Operations: create-bucket, list-objects, list-buckets, put-object, delete-object[/blue]")
1153
1157
  sys.exit(1)
1154
1158
 
1155
1159
  operation = sys.argv[1]
@@ -1193,14 +1197,14 @@ def main():
1193
1197
  else:
1194
1198
  raise ValueError(f"Unknown operation: {operation}")
1195
1199
 
1196
- # Print results
1200
+ # Print results with Rich formatting
1197
1201
  for result in results:
1198
1202
  if result.success:
1199
- print(f"✅ {result.operation_type} completed successfully")
1203
+ console.print(f"[green]✅ {result.operation_type} completed successfully[/green]")
1200
1204
  if result.response_data:
1201
- print(f" Data: {json.dumps(result.response_data, default=str, indent=2)}")
1205
+ console.print(f"[blue] Data: {json.dumps(result.response_data, default=str, indent=2)}[/blue]")
1202
1206
  else:
1203
- print(f"❌ {result.operation_type} failed: {result.error_message}")
1207
+ console.print(f"[red]❌ {result.operation_type} failed: {result.error_message}[/red]")
1204
1208
 
1205
1209
  except Exception as e:
1206
1210
  logger.error(f"Error during operation: {e}")
@@ -0,0 +1,644 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ VPC Endpoints Operations for CloudOps Runbooks Platform
4
+
5
+ This module provides enterprise-grade VPC endpoint management with cost optimization,
6
+ ROI analysis, and strategic decision support following McKinsey operating principles.
7
+
8
+ Addresses GitHub Issue #96 expanded scope: VPC Endpoints management with
9
+ comprehensive cost optimization and business value analysis.
10
+
11
+ Features:
12
+ - Complete VPC endpoint lifecycle management (create, modify, delete)
13
+ - Cost optimization analysis (endpoint costs vs NAT Gateway savings)
14
+ - ROI calculator for endpoint deployment decisions
15
+ - Security compliance validation
16
+ - Integration with existing VPC operations module
17
+ - McKinsey-style decision frameworks for endpoint deployment
18
+
19
+ Author: CloudOps Runbooks Team
20
+ Version: 0.7.8
21
+ Enhanced for Sprint 2 VPC Scope Expansion
22
+ """
23
+
24
+ import json
25
+ import logging
26
+ from datetime import datetime, timedelta
27
+ from typing import Any, Dict, List, Optional, Tuple
28
+
29
+ import boto3
30
+ from botocore.exceptions import BotoCoreError, ClientError
31
+
32
+ from runbooks.operate.base import BaseOperation, OperationResult
33
+ from runbooks.common.rich_utils import (
34
+ console,
35
+ create_panel,
36
+ create_table,
37
+ format_cost,
38
+ print_error,
39
+ print_status,
40
+ print_success,
41
+ )
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+
46
+ class VPCEndpointOperations(BaseOperation):
47
+ """
48
+ Enterprise VPC Endpoint operations with cost optimization and ROI analysis.
49
+
50
+ Extends BaseOperation following CloudOps Runbooks patterns for:
51
+ - VPC endpoint lifecycle management
52
+ - Cost-benefit analysis vs NAT Gateway usage
53
+ - Security compliance validation
54
+ - McKinsey-style ROI decision frameworks
55
+
56
+ GitHub Issue #96 - VPC & Infrastructure Enhancement
57
+ """
58
+
59
+ service_name = "ec2"
60
+ supported_operations = {
61
+ "create_endpoint",
62
+ "delete_endpoint",
63
+ "modify_endpoint",
64
+ "describe_endpoints",
65
+ "analyze_endpoint_costs",
66
+ "calculate_endpoint_roi",
67
+ "optimize_endpoint_placement",
68
+ "security_compliance_check",
69
+ }
70
+ requires_confirmation = True # Endpoints have cost implications
71
+
72
+ # VPC Endpoint pricing (monthly estimates in USD)
73
+ ENDPOINT_PRICING = {
74
+ "Interface": 7.30, # ~$7.30/month per interface endpoint
75
+ "Gateway": 0.00, # S3/DynamoDB gateways are free
76
+ "GatewayLoadBalancer": 7.30, # Same as interface endpoints
77
+ }
78
+
79
+ # Data processing costs (per GB)
80
+ DATA_PROCESSING_COSTS = {
81
+ "Interface": 0.01, # $0.01 per GB processed
82
+ "GatewayLoadBalancer": 0.0025, # $0.0025 per GB processed
83
+ }
84
+
85
+ def __init__(self, profile: str, region: str, dry_run: bool = True):
86
+ """Initialize VPC Endpoint operations."""
87
+ super().__init__(profile, region, dry_run)
88
+ self.ec2_client = self.session.client("ec2")
89
+ self.cloudwatch = self.session.client("cloudwatch")
90
+
91
+ def create_endpoint(
92
+ self,
93
+ vpc_id: str,
94
+ service_name: str,
95
+ endpoint_type: str = "Interface",
96
+ subnet_ids: Optional[List[str]] = None,
97
+ security_group_ids: Optional[List[str]] = None,
98
+ policy_document: Optional[str] = None,
99
+ private_dns_enabled: bool = True,
100
+ tags: Optional[Dict[str, str]] = None,
101
+ ) -> OperationResult:
102
+ """
103
+ Create a VPC endpoint with cost analysis and ROI validation.
104
+
105
+ Args:
106
+ vpc_id: Target VPC ID
107
+ service_name: AWS service name (e.g., 'com.amazonaws.us-east-1.s3')
108
+ endpoint_type: Interface, Gateway, or GatewayLoadBalancer
109
+ subnet_ids: Subnet IDs for Interface endpoints
110
+ security_group_ids: Security group IDs for Interface endpoints
111
+ policy_document: IAM policy document (JSON string)
112
+ private_dns_enabled: Enable private DNS resolution
113
+ tags: Resource tags
114
+
115
+ Returns:
116
+ OperationResult with endpoint creation details and cost analysis
117
+ """
118
+ try:
119
+ print_status(f"Creating VPC endpoint for {service_name} in {vpc_id}", "info")
120
+
121
+ # Validate inputs and perform cost analysis
122
+ validation_result = self._validate_endpoint_creation(vpc_id, service_name, endpoint_type, subnet_ids)
123
+ if not validation_result["valid"]:
124
+ return self.create_result(
125
+ success=False,
126
+ message=f"Endpoint validation failed: {validation_result['message']}",
127
+ data=validation_result,
128
+ )
129
+
130
+ # Calculate ROI before creation
131
+ roi_analysis = self.calculate_endpoint_roi(service_name, vpc_id, endpoint_type, estimated_monthly_gb=100)
132
+
133
+ # Display cost analysis to user
134
+ self._display_cost_analysis(roi_analysis)
135
+
136
+ if self.dry_run:
137
+ print_status("DRY RUN: VPC endpoint creation simulated", "warning")
138
+ return self.create_result(
139
+ success=True,
140
+ message=f"DRY RUN: Would create {endpoint_type} endpoint for {service_name}",
141
+ data={
142
+ "vpc_id": vpc_id,
143
+ "service_name": service_name,
144
+ "endpoint_type": endpoint_type,
145
+ "estimated_monthly_cost": roi_analysis.get("monthly_cost", 0),
146
+ "roi_analysis": roi_analysis,
147
+ "dry_run": True,
148
+ },
149
+ )
150
+
151
+ # Build endpoint creation parameters
152
+ create_params = {
153
+ "VpcId": vpc_id,
154
+ "ServiceName": service_name,
155
+ "VpcEndpointType": endpoint_type,
156
+ "PolicyDocument": policy_document,
157
+ }
158
+
159
+ if endpoint_type == "Interface":
160
+ if subnet_ids:
161
+ create_params["SubnetIds"] = subnet_ids
162
+ if security_group_ids:
163
+ create_params["SecurityGroupIds"] = security_group_ids
164
+ create_params["PrivateDnsEnabled"] = private_dns_enabled
165
+
166
+ if tags:
167
+ tag_specs = [
168
+ {"ResourceType": "vpc-endpoint", "Tags": [{"Key": k, "Value": v} for k, v in tags.items()]}
169
+ ]
170
+ create_params["TagSpecifications"] = tag_specs
171
+
172
+ # Create the endpoint
173
+ response = self.ec2_client.create_vpc_endpoint(**create_params)
174
+ endpoint = response["VpcEndpoint"]
175
+ endpoint_id = endpoint["VpcEndpointId"]
176
+
177
+ print_success(f"VPC endpoint created: {endpoint_id}")
178
+
179
+ # Add cost tracking tags
180
+ cost_tags = {
181
+ "CloudOps-CostCenter": "NetworkOptimization",
182
+ "CloudOps-CreatedBy": "VPCEndpointOperations",
183
+ "CloudOps-EstimatedMonthlyCost": str(roi_analysis.get("monthly_cost", 0)),
184
+ "CloudOps-CreatedAt": datetime.now().isoformat(),
185
+ }
186
+
187
+ self._tag_endpoint(endpoint_id, cost_tags)
188
+
189
+ return self.create_result(
190
+ success=True,
191
+ message=f"VPC endpoint created successfully: {endpoint_id}",
192
+ data={
193
+ "endpoint_id": endpoint_id,
194
+ "vpc_id": vpc_id,
195
+ "service_name": service_name,
196
+ "endpoint_type": endpoint_type,
197
+ "state": endpoint["State"],
198
+ "creation_timestamp": endpoint["CreationTimestamp"].isoformat(),
199
+ "estimated_monthly_cost": roi_analysis.get("monthly_cost", 0),
200
+ "roi_analysis": roi_analysis,
201
+ },
202
+ )
203
+
204
+ except ClientError as e:
205
+ error_msg = f"Failed to create VPC endpoint: {e.response['Error']['Message']}"
206
+ print_error(error_msg, e)
207
+ return self.create_result(
208
+ success=False, message=error_msg, data={"error_code": e.response["Error"]["Code"]}
209
+ )
210
+ except Exception as e:
211
+ error_msg = f"Unexpected error creating VPC endpoint: {str(e)}"
212
+ print_error(error_msg, e)
213
+ return self.create_result(success=False, message=error_msg, data={"error": str(e)})
214
+
215
+ def delete_endpoint(self, endpoint_id: str) -> OperationResult:
216
+ """
217
+ Delete a VPC endpoint with cost impact analysis.
218
+
219
+ Args:
220
+ endpoint_id: VPC endpoint ID to delete
221
+
222
+ Returns:
223
+ OperationResult with deletion status and cost impact
224
+ """
225
+ try:
226
+ print_status(f"Deleting VPC endpoint {endpoint_id}", "warning")
227
+
228
+ # Get endpoint details before deletion for cost analysis
229
+ endpoint_details = self._get_endpoint_details(endpoint_id)
230
+ if not endpoint_details:
231
+ return self.create_result(
232
+ success=False, message=f"VPC endpoint {endpoint_id} not found", data={"endpoint_id": endpoint_id}
233
+ )
234
+
235
+ # Calculate cost impact of deletion
236
+ cost_impact = self._calculate_deletion_cost_impact(endpoint_details)
237
+
238
+ if self.dry_run:
239
+ print_status("DRY RUN: VPC endpoint deletion simulated", "warning")
240
+ return self.create_result(
241
+ success=True,
242
+ message=f"DRY RUN: Would delete endpoint {endpoint_id}",
243
+ data={
244
+ "endpoint_id": endpoint_id,
245
+ "service_name": endpoint_details.get("ServiceName", "unknown"),
246
+ "cost_impact": cost_impact,
247
+ "dry_run": True,
248
+ },
249
+ )
250
+
251
+ # Perform deletion
252
+ response = self.ec2_client.delete_vpc_endpoints(VpcEndpointIds=[endpoint_id])
253
+
254
+ if response["Unsuccessful"]:
255
+ error_detail = response["Unsuccessful"][0]
256
+ error_msg = f"Failed to delete endpoint {endpoint_id}: {error_detail['Error']['Message']}"
257
+ print_error(error_msg)
258
+ return self.create_result(success=False, message=error_msg, data={"error": error_detail["Error"]})
259
+
260
+ print_success(f"VPC endpoint {endpoint_id} deleted successfully")
261
+
262
+ return self.create_result(
263
+ success=True,
264
+ message=f"VPC endpoint deleted successfully: {endpoint_id}",
265
+ data={
266
+ "endpoint_id": endpoint_id,
267
+ "service_name": endpoint_details.get("ServiceName", "unknown"),
268
+ "cost_impact": cost_impact,
269
+ "deleted_at": datetime.now().isoformat(),
270
+ },
271
+ )
272
+
273
+ except ClientError as e:
274
+ error_msg = f"Failed to delete VPC endpoint: {e.response['Error']['Message']}"
275
+ print_error(error_msg, e)
276
+ return self.create_result(
277
+ success=False, message=error_msg, data={"error_code": e.response["Error"]["Code"]}
278
+ )
279
+ except Exception as e:
280
+ error_msg = f"Unexpected error deleting VPC endpoint: {str(e)}"
281
+ print_error(error_msg, e)
282
+ return self.create_result(success=False, message=error_msg, data={"error": str(e)})
283
+
284
+ def describe_endpoints(
285
+ self, vpc_id: Optional[str] = None, endpoint_ids: Optional[List[str]] = None
286
+ ) -> OperationResult:
287
+ """
288
+ Describe VPC endpoints with cost analysis and optimization recommendations.
289
+
290
+ Args:
291
+ vpc_id: Filter by VPC ID
292
+ endpoint_ids: Specific endpoint IDs to describe
293
+
294
+ Returns:
295
+ OperationResult with endpoint details and cost analysis
296
+ """
297
+ try:
298
+ print_status("Retrieving VPC endpoints with cost analysis...", "info")
299
+
300
+ # Build filters
301
+ filters = []
302
+ if vpc_id:
303
+ filters.append({"Name": "vpc-id", "Values": [vpc_id]})
304
+
305
+ describe_params = {}
306
+ if filters:
307
+ describe_params["Filters"] = filters
308
+ if endpoint_ids:
309
+ describe_params["VpcEndpointIds"] = endpoint_ids
310
+
311
+ # Get endpoints
312
+ response = self.ec2_client.describe_vpc_endpoints(**describe_params)
313
+ endpoints = response["VpcEndpoints"]
314
+
315
+ if not endpoints:
316
+ print_status("No VPC endpoints found", "info")
317
+ return self.create_result(
318
+ success=True, message="No VPC endpoints found", data={"endpoints": [], "total_count": 0}
319
+ )
320
+
321
+ # Enhance endpoints with cost analysis
322
+ enhanced_endpoints = []
323
+ total_monthly_cost = 0.0
324
+
325
+ for endpoint in endpoints:
326
+ enhanced_endpoint = self._enhance_endpoint_with_cost_analysis(endpoint)
327
+ enhanced_endpoints.append(enhanced_endpoint)
328
+ total_monthly_cost += enhanced_endpoint.get("estimated_monthly_cost", 0)
329
+
330
+ # Display endpoints table
331
+ self._display_endpoints_table(enhanced_endpoints, total_monthly_cost)
332
+
333
+ # Generate optimization recommendations
334
+ optimization_recommendations = self._generate_optimization_recommendations(enhanced_endpoints)
335
+
336
+ print_success(
337
+ f"Found {len(endpoints)} VPC endpoints with total estimated cost: ${total_monthly_cost:.2f}/month"
338
+ )
339
+
340
+ return self.create_result(
341
+ success=True,
342
+ message=f"Retrieved {len(endpoints)} VPC endpoints",
343
+ data={
344
+ "endpoints": enhanced_endpoints,
345
+ "total_count": len(endpoints),
346
+ "total_monthly_cost": total_monthly_cost,
347
+ "optimization_recommendations": optimization_recommendations,
348
+ },
349
+ )
350
+
351
+ except ClientError as e:
352
+ error_msg = f"Failed to describe VPC endpoints: {e.response['Error']['Message']}"
353
+ print_error(error_msg, e)
354
+ return self.create_result(
355
+ success=False, message=error_msg, data={"error_code": e.response["Error"]["Code"]}
356
+ )
357
+ except Exception as e:
358
+ error_msg = f"Unexpected error describing VPC endpoints: {str(e)}"
359
+ print_error(error_msg, e)
360
+ return self.create_result(success=False, message=error_msg, data={"error": str(e)})
361
+
362
+ def calculate_endpoint_roi(
363
+ self,
364
+ service_name: str,
365
+ vpc_id: str,
366
+ endpoint_type: str = "Interface",
367
+ estimated_monthly_gb: float = 100,
368
+ nat_gateway_count: int = 1,
369
+ ) -> Dict[str, Any]:
370
+ """
371
+ Calculate ROI for VPC endpoint deployment using McKinsey-style analysis.
372
+
373
+ Args:
374
+ service_name: AWS service name
375
+ vpc_id: Target VPC ID
376
+ endpoint_type: Interface, Gateway, or GatewayLoadBalancer
377
+ estimated_monthly_gb: Estimated monthly data transfer in GB
378
+ nat_gateway_count: Number of NAT Gateways that could be replaced/optimized
379
+
380
+ Returns:
381
+ Comprehensive ROI analysis with McKinsey decision framework
382
+ """
383
+ try:
384
+ # Calculate endpoint costs
385
+ endpoint_monthly_cost = self.ENDPOINT_PRICING.get(endpoint_type, 7.30)
386
+ data_processing_cost = self.DATA_PROCESSING_COSTS.get(endpoint_type, 0.01)
387
+ total_data_cost = estimated_monthly_gb * data_processing_cost
388
+ total_endpoint_cost = endpoint_monthly_cost + total_data_cost
389
+
390
+ # Calculate NAT Gateway costs (baseline for comparison)
391
+ nat_gateway_monthly_cost = 45.0 * nat_gateway_count # $45/month per NAT Gateway
392
+ nat_data_processing = estimated_monthly_gb * 0.045 # $0.045 per GB through NAT
393
+ total_nat_cost = nat_gateway_monthly_cost + nat_data_processing
394
+
395
+ # Calculate savings and ROI
396
+ monthly_savings = total_nat_cost - total_endpoint_cost
397
+ annual_savings = monthly_savings * 12
398
+ roi_percentage = (monthly_savings / total_endpoint_cost * 100) if total_endpoint_cost > 0 else 0
399
+
400
+ # McKinsey-style decision framework
401
+ recommendation = "DEPLOY"
402
+ if monthly_savings < 0:
403
+ recommendation = "DO_NOT_DEPLOY"
404
+ elif monthly_savings < 10:
405
+ recommendation = "EVALUATE_FURTHER"
406
+ elif monthly_savings > 50:
407
+ recommendation = "PRIORITY_DEPLOY"
408
+
409
+ # Business case summary
410
+ business_case = {
411
+ "financial_impact": {
412
+ "monthly_savings": monthly_savings,
413
+ "annual_savings": annual_savings,
414
+ "roi_percentage": roi_percentage,
415
+ "payback_period_months": 1 if monthly_savings > 0 else None,
416
+ },
417
+ "strategic_value": {
418
+ "reduced_nat_gateway_dependency": nat_gateway_count,
419
+ "improved_security": endpoint_type == "Interface",
420
+ "reduced_internet_traffic": True,
421
+ "compliance_benefits": "Private connectivity to AWS services",
422
+ },
423
+ "recommendation": recommendation,
424
+ }
425
+
426
+ return {
427
+ "service_name": service_name,
428
+ "vpc_id": vpc_id,
429
+ "endpoint_type": endpoint_type,
430
+ "cost_analysis": {
431
+ "endpoint_monthly_cost": endpoint_monthly_cost,
432
+ "data_processing_cost": total_data_cost,
433
+ "total_endpoint_cost": total_endpoint_cost,
434
+ "nat_gateway_baseline_cost": total_nat_cost,
435
+ "monthly_savings": monthly_savings,
436
+ "annual_savings": annual_savings,
437
+ "roi_percentage": roi_percentage,
438
+ },
439
+ "business_case": business_case,
440
+ "mckinsey_decision_framework": {
441
+ "recommendation": recommendation,
442
+ "confidence_level": "HIGH" if abs(roi_percentage) > 20 else "MEDIUM",
443
+ "decision_criteria": [
444
+ f"Cost savings: ${monthly_savings:.2f}/month",
445
+ f"ROI: {roi_percentage:.1f}%",
446
+ f"Strategic value: {'HIGH' if endpoint_type == 'Interface' else 'MEDIUM'}",
447
+ ],
448
+ },
449
+ }
450
+
451
+ except Exception as e:
452
+ logger.error(f"Failed to calculate endpoint ROI: {e}")
453
+ return {
454
+ "error": str(e),
455
+ "service_name": service_name,
456
+ "vpc_id": vpc_id,
457
+ "recommendation": "ERROR_IN_CALCULATION",
458
+ }
459
+
460
+ def _validate_endpoint_creation(
461
+ self, vpc_id: str, service_name: str, endpoint_type: str, subnet_ids: Optional[List[str]] = None
462
+ ) -> Dict[str, Any]:
463
+ """Validate endpoint creation parameters."""
464
+ try:
465
+ # Validate VPC exists
466
+ vpc_response = self.ec2_client.describe_vpcs(VpcIds=[vpc_id])
467
+ if not vpc_response["Vpcs"]:
468
+ return {"valid": False, "message": f"VPC {vpc_id} not found"}
469
+
470
+ # Validate service name format
471
+ if not service_name.startswith("com.amazonaws."):
472
+ return {"valid": False, "message": f"Invalid service name format: {service_name}"}
473
+
474
+ # Validate endpoint type
475
+ if endpoint_type not in ["Interface", "Gateway", "GatewayLoadBalancer"]:
476
+ return {"valid": False, "message": f"Invalid endpoint type: {endpoint_type}"}
477
+
478
+ # Validate subnets for Interface endpoints
479
+ if endpoint_type == "Interface" and subnet_ids:
480
+ try:
481
+ subnet_response = self.ec2_client.describe_subnets(SubnetIds=subnet_ids)
482
+ # Ensure all subnets belong to the VPC
483
+ for subnet in subnet_response["Subnets"]:
484
+ if subnet["VpcId"] != vpc_id:
485
+ return {"valid": False, "message": f"Subnet {subnet['SubnetId']} not in VPC {vpc_id}"}
486
+ except ClientError:
487
+ return {"valid": False, "message": "One or more subnet IDs are invalid"}
488
+
489
+ return {"valid": True, "message": "Validation successful"}
490
+
491
+ except ClientError as e:
492
+ return {"valid": False, "message": f"Validation error: {e.response['Error']['Message']}"}
493
+
494
+ def _get_endpoint_details(self, endpoint_id: str) -> Optional[Dict[str, Any]]:
495
+ """Get detailed information about a VPC endpoint."""
496
+ try:
497
+ response = self.ec2_client.describe_vpc_endpoints(VpcEndpointIds=[endpoint_id])
498
+ endpoints = response["VpcEndpoints"]
499
+ return endpoints[0] if endpoints else None
500
+ except ClientError:
501
+ return None
502
+
503
+ def _calculate_deletion_cost_impact(self, endpoint_details: Dict[str, Any]) -> Dict[str, Any]:
504
+ """Calculate the cost impact of deleting an endpoint."""
505
+ endpoint_type = endpoint_details.get("VpcEndpointType", "Interface")
506
+ service_name = endpoint_details.get("ServiceName", "unknown")
507
+
508
+ monthly_cost_saving = self.ENDPOINT_PRICING.get(endpoint_type, 7.30)
509
+
510
+ return {
511
+ "monthly_cost_saving": monthly_cost_saving,
512
+ "annual_cost_saving": monthly_cost_saving * 12,
513
+ "service_name": service_name,
514
+ "endpoint_type": endpoint_type,
515
+ "warning": "Deleting endpoint may increase NAT Gateway costs for service access",
516
+ }
517
+
518
+ def _enhance_endpoint_with_cost_analysis(self, endpoint: Dict[str, Any]) -> Dict[str, Any]:
519
+ """Enhance endpoint data with cost analysis."""
520
+ endpoint_type = endpoint.get("VpcEndpointType", "Interface")
521
+ service_name = endpoint.get("ServiceName", "unknown")
522
+
523
+ # Calculate estimated monthly cost
524
+ base_cost = self.ENDPOINT_PRICING.get(endpoint_type, 7.30)
525
+ estimated_data_gb = 50 # Conservative estimate
526
+ data_cost = estimated_data_gb * self.DATA_PROCESSING_COSTS.get(endpoint_type, 0.01)
527
+ total_monthly_cost = base_cost + data_cost
528
+
529
+ # Add cost analysis to endpoint data
530
+ enhanced = endpoint.copy()
531
+ enhanced.update(
532
+ {
533
+ "estimated_monthly_cost": total_monthly_cost,
534
+ "cost_breakdown": {
535
+ "base_cost": base_cost,
536
+ "estimated_data_cost": data_cost,
537
+ "estimated_monthly_gb": estimated_data_gb,
538
+ },
539
+ "cost_center": "NetworkOptimization",
540
+ }
541
+ )
542
+
543
+ return enhanced
544
+
545
+ def _display_endpoints_table(self, endpoints: List[Dict[str, Any]], total_cost: float) -> None:
546
+ """Display endpoints in a formatted table."""
547
+ table = create_table(
548
+ title=f"VPC Endpoints Analysis - Total Monthly Cost: ${total_cost:.2f}",
549
+ columns=[
550
+ {"name": "Endpoint ID", "style": "cyan", "justify": "left"},
551
+ {"name": "Service", "style": "blue", "justify": "left"},
552
+ {"name": "Type", "style": "green", "justify": "center"},
553
+ {"name": "State", "style": "yellow", "justify": "center"},
554
+ {"name": "Monthly Cost", "style": "red", "justify": "right"},
555
+ {"name": "VPC ID", "style": "dim", "justify": "left"},
556
+ ],
557
+ )
558
+
559
+ for endpoint in endpoints:
560
+ table.add_row(
561
+ endpoint["VpcEndpointId"][:20] + "...",
562
+ endpoint.get("ServiceName", "unknown").split(".")[-1],
563
+ endpoint.get("VpcEndpointType", "Interface"),
564
+ endpoint.get("State", "unknown"),
565
+ f"${endpoint.get('estimated_monthly_cost', 0):.2f}",
566
+ endpoint.get("VpcId", "unknown"),
567
+ )
568
+
569
+ console.print(table)
570
+
571
+ def _display_cost_analysis(self, roi_analysis: Dict[str, Any]) -> None:
572
+ """Display cost analysis in a formatted panel."""
573
+ cost_data = roi_analysis.get("cost_analysis", {})
574
+ recommendation = roi_analysis.get("mckinsey_decision_framework", {}).get("recommendation", "UNKNOWN")
575
+
576
+ analysis_text = f"""
577
+ [bold]Cost Analysis Summary[/bold]
578
+
579
+ Endpoint Monthly Cost: ${cost_data.get("total_endpoint_cost", 0):.2f}
580
+ NAT Gateway Baseline: ${cost_data.get("nat_gateway_baseline_cost", 0):.2f}
581
+ Monthly Savings: ${cost_data.get("monthly_savings", 0):.2f}
582
+ ROI: {cost_data.get("roi_percentage", 0):.1f}%
583
+
584
+ [bold]McKinsey Recommendation: {recommendation}[/bold]
585
+ """
586
+
587
+ panel = create_panel(
588
+ analysis_text,
589
+ title="VPC Endpoint ROI Analysis",
590
+ border_style="cyan" if cost_data.get("monthly_savings", 0) > 0 else "red",
591
+ )
592
+
593
+ console.print(panel)
594
+
595
+ def _generate_optimization_recommendations(self, endpoints: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
596
+ """Generate optimization recommendations based on endpoint analysis."""
597
+ recommendations = []
598
+
599
+ # Group endpoints by service
600
+ service_groups = {}
601
+ for endpoint in endpoints:
602
+ service = endpoint.get("ServiceName", "unknown")
603
+ if service not in service_groups:
604
+ service_groups[service] = []
605
+ service_groups[service].append(endpoint)
606
+
607
+ # Analyze for consolidation opportunities
608
+ for service, service_endpoints in service_groups.items():
609
+ if len(service_endpoints) > 2:
610
+ total_cost = sum(ep.get("estimated_monthly_cost", 0) for ep in service_endpoints)
611
+ recommendations.append(
612
+ {
613
+ "type": "CONSOLIDATION_OPPORTUNITY",
614
+ "service": service,
615
+ "current_endpoints": len(service_endpoints),
616
+ "estimated_savings": total_cost * 0.3, # Conservative 30% savings
617
+ "recommendation": f"Consider consolidating {len(service_endpoints)} {service} endpoints",
618
+ }
619
+ )
620
+
621
+ # Check for unused endpoints (placeholder - would need CloudWatch metrics)
622
+ for endpoint in endpoints:
623
+ if endpoint.get("State") != "available":
624
+ recommendations.append(
625
+ {
626
+ "type": "UNUSED_ENDPOINT",
627
+ "endpoint_id": endpoint["VpcEndpointId"],
628
+ "monthly_savings": endpoint.get("estimated_monthly_cost", 0),
629
+ "recommendation": f"Consider deleting unused endpoint {endpoint['VpcEndpointId']}",
630
+ }
631
+ )
632
+
633
+ return recommendations
634
+
635
+ def _tag_endpoint(self, endpoint_id: str, tags: Dict[str, str]) -> None:
636
+ """Add tags to a VPC endpoint."""
637
+ try:
638
+ self.ec2_client.create_tags(Resources=[endpoint_id], Tags=[{"Key": k, "Value": v} for k, v in tags.items()])
639
+ except ClientError as e:
640
+ logger.warning(f"Failed to tag endpoint {endpoint_id}: {e}")
641
+
642
+
643
+ # Export the operations class
644
+ __all__ = ["VPCEndpointOperations"]