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.
- runbooks/__init__.py +1 -1
- runbooks/base.py +5 -1
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +871 -0
- runbooks/cfat/assessment/runner.py +122 -11
- runbooks/cfat/models.py +6 -2
- runbooks/common/logger.py +14 -0
- runbooks/common/rich_utils.py +451 -0
- runbooks/enterprise/__init__.py +68 -0
- runbooks/enterprise/error_handling.py +411 -0
- runbooks/enterprise/logging.py +439 -0
- runbooks/enterprise/multi_tenant.py +583 -0
- runbooks/finops/README.md +468 -241
- runbooks/finops/__init__.py +39 -3
- runbooks/finops/cli.py +83 -18
- runbooks/finops/cross_validation.py +375 -0
- runbooks/finops/dashboard_runner.py +812 -164
- runbooks/finops/enhanced_dashboard_runner.py +525 -0
- runbooks/finops/finops_dashboard.py +1892 -0
- runbooks/finops/helpers.py +485 -51
- runbooks/finops/optimizer.py +823 -0
- runbooks/finops/tests/__init__.py +19 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
- runbooks/finops/tests/run_comprehensive_tests.py +421 -0
- runbooks/finops/tests/run_tests.py +305 -0
- runbooks/finops/tests/test_finops_dashboard.py +705 -0
- runbooks/finops/tests/test_integration.py +477 -0
- runbooks/finops/tests/test_performance.py +380 -0
- runbooks/finops/tests/test_performance_benchmarks.py +500 -0
- runbooks/finops/tests/test_reference_images_validation.py +867 -0
- runbooks/finops/tests/test_single_account_features.py +715 -0
- runbooks/finops/tests/validate_test_suite.py +220 -0
- runbooks/finops/types.py +1 -1
- runbooks/hitl/enhanced_workflow_engine.py +725 -0
- runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +442 -0
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +172 -13
- runbooks/inventory/discovery.md +1 -1
- runbooks/inventory/list_ec2_instances.py +18 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1269 -0
- runbooks/inventory/rich_inventory_display.py +393 -0
- runbooks/inventory/run_on_multi_accounts.py +35 -19
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/vpc_flow_analyzer.py +1030 -0
- runbooks/main.py +2215 -119
- runbooks/metrics/dora_metrics_engine.py +599 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +122 -10
- runbooks/operate/deployment_framework.py +1032 -0
- runbooks/operate/deployment_validator.py +853 -0
- runbooks/operate/dynamodb_operations.py +10 -6
- runbooks/operate/ec2_operations.py +319 -11
- runbooks/operate/executive_dashboard.py +779 -0
- runbooks/operate/mcp_integration.py +750 -0
- runbooks/operate/nat_gateway_operations.py +1120 -0
- runbooks/operate/networking_cost_heatmap.py +685 -0
- runbooks/operate/privatelink_operations.py +940 -0
- runbooks/operate/s3_operations.py +10 -6
- runbooks/operate/vpc_endpoints.py +644 -0
- runbooks/operate/vpc_operations.py +1038 -0
- runbooks/remediation/__init__.py +2 -2
- runbooks/remediation/acm_remediation.py +1 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/cloudtrail_remediation.py +1 -1
- runbooks/remediation/cognito_remediation.py +1 -1
- runbooks/remediation/dynamodb_remediation.py +1 -1
- runbooks/remediation/ec2_remediation.py +1 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
- runbooks/remediation/kms_enable_key_rotation.py +1 -1
- runbooks/remediation/kms_remediation.py +1 -1
- runbooks/remediation/lambda_remediation.py +1 -1
- runbooks/remediation/multi_account.py +1 -1
- runbooks/remediation/rds_remediation.py +1 -1
- runbooks/remediation/s3_block_public_access.py +1 -1
- runbooks/remediation/s3_enable_access_logging.py +1 -1
- runbooks/remediation/s3_encryption.py +1 -1
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/remediation/vpc_remediation.py +475 -0
- runbooks/security/__init__.py +3 -1
- runbooks/security/compliance_automation.py +632 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +31 -5
- runbooks/security/security_baseline_tester.py +169 -30
- runbooks/security/security_export.py +477 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +484 -0
- runbooks/validation/cli.py +356 -0
- runbooks/validation/mcp_validator.py +768 -0
- runbooks/vpc/__init__.py +38 -0
- runbooks/vpc/config.py +212 -0
- runbooks/vpc/cost_engine.py +347 -0
- runbooks/vpc/heatmap_engine.py +605 -0
- runbooks/vpc/manager_interface.py +634 -0
- runbooks/vpc/networking_wrapper.py +1260 -0
- runbooks/vpc/rich_formatters.py +679 -0
- runbooks/vpc/tests/__init__.py +5 -0
- runbooks/vpc/tests/conftest.py +356 -0
- runbooks/vpc/tests/test_cli_integration.py +530 -0
- runbooks/vpc/tests/test_config.py +458 -0
- runbooks/vpc/tests/test_cost_engine.py +479 -0
- runbooks/vpc/tests/test_networking_wrapper.py +512 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
- {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"]
|