runbooks 0.9.6__py3-none-any.whl → 0.9.8__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 (57) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/_platform/__init__.py +19 -0
  3. runbooks/_platform/core/runbooks_wrapper.py +478 -0
  4. runbooks/cloudops/cost_optimizer.py +330 -0
  5. runbooks/cloudops/interfaces.py +3 -3
  6. runbooks/common/mcp_integration.py +174 -0
  7. runbooks/common/performance_monitor.py +4 -4
  8. runbooks/enterprise/__init__.py +18 -10
  9. runbooks/enterprise/security.py +708 -0
  10. runbooks/finops/README.md +1 -1
  11. runbooks/finops/automation_core.py +643 -0
  12. runbooks/finops/business_cases.py +414 -16
  13. runbooks/finops/cli.py +23 -0
  14. runbooks/finops/compute_cost_optimizer.py +865 -0
  15. runbooks/finops/ebs_cost_optimizer.py +718 -0
  16. runbooks/finops/ebs_optimizer.py +909 -0
  17. runbooks/finops/elastic_ip_optimizer.py +675 -0
  18. runbooks/finops/embedded_mcp_validator.py +330 -14
  19. runbooks/finops/enhanced_dashboard_runner.py +2 -1
  20. runbooks/finops/enterprise_wrappers.py +827 -0
  21. runbooks/finops/finops_dashboard.py +322 -11
  22. runbooks/finops/legacy_migration.py +730 -0
  23. runbooks/finops/nat_gateway_optimizer.py +1160 -0
  24. runbooks/finops/network_cost_optimizer.py +1387 -0
  25. runbooks/finops/notebook_utils.py +596 -0
  26. runbooks/finops/reservation_optimizer.py +956 -0
  27. runbooks/finops/single_dashboard.py +16 -16
  28. runbooks/finops/validation_framework.py +753 -0
  29. runbooks/finops/vpc_cleanup_optimizer.py +817 -0
  30. runbooks/finops/workspaces_analyzer.py +1 -1
  31. runbooks/inventory/__init__.py +7 -0
  32. runbooks/inventory/collectors/aws_networking.py +357 -6
  33. runbooks/inventory/mcp_vpc_validator.py +1091 -0
  34. runbooks/inventory/vpc_analyzer.py +1107 -0
  35. runbooks/inventory/vpc_architecture_validator.py +939 -0
  36. runbooks/inventory/vpc_dependency_analyzer.py +845 -0
  37. runbooks/main.py +487 -40
  38. runbooks/operate/vpc_operations.py +1485 -16
  39. runbooks/remediation/commvault_ec2_analysis.py +1 -1
  40. runbooks/remediation/dynamodb_optimize.py +2 -2
  41. runbooks/remediation/rds_instance_list.py +1 -1
  42. runbooks/remediation/rds_snapshot_list.py +1 -1
  43. runbooks/remediation/workspaces_list.py +2 -2
  44. runbooks/security/compliance_automation.py +2 -2
  45. runbooks/vpc/__init__.py +12 -0
  46. runbooks/vpc/cleanup_wrapper.py +757 -0
  47. runbooks/vpc/cost_engine.py +527 -3
  48. runbooks/vpc/networking_wrapper.py +29 -29
  49. runbooks/vpc/runbooks_adapter.py +479 -0
  50. runbooks/vpc/tests/test_config.py +2 -2
  51. runbooks/vpc/vpc_cleanup_integration.py +2629 -0
  52. {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/METADATA +1 -1
  53. {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/RECORD +57 -34
  54. {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/WHEEL +0 -0
  55. {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/entry_points.txt +0 -0
  56. {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/licenses/LICENSE +0 -0
  57. {runbooks-0.9.6.dist-info → runbooks-0.9.8.dist-info}/top_level.txt +0 -0
runbooks/__init__.py CHANGED
@@ -61,7 +61,7 @@ s3_ops = S3Operations()
61
61
 
62
62
  # Centralized Version Management - Single Source of Truth
63
63
  # All modules MUST import __version__ from this location
64
- __version__ = "0.9.6"
64
+ __version__ = "0.9.7"
65
65
 
66
66
  # Fallback for legacy importlib.metadata usage during transition
67
67
  try:
@@ -0,0 +1,19 @@
1
+ """
2
+ Enterprise FinOps Platform Integration Layer
3
+
4
+ This module provides the integration layer between the runbooks package
5
+ and the FinOps notebook interfaces, enabling business-friendly access
6
+ to technical cost optimization capabilities.
7
+ """
8
+
9
+ __version__ = "1.0.0"
10
+
11
+ from .core.runbooks_wrapper import RunbooksWrapper
12
+ from .finops.unit_economics import UnitEconomicsCalculator
13
+ from .components.validators import ProfileValidator
14
+
15
+ __all__ = [
16
+ "RunbooksWrapper",
17
+ "UnitEconomicsCalculator",
18
+ "ProfileValidator"
19
+ ]
@@ -0,0 +1,478 @@
1
+ """
2
+ Enterprise FinOps Platform - RunbooksWrapper
3
+ Provides unified access to all runbooks CLI commands with MCP validation and Rich output
4
+ """
5
+ import subprocess
6
+ import json
7
+ import yaml
8
+ from typing import Dict, List, Optional, Any, Union
9
+ from pathlib import Path
10
+ from datetime import datetime
11
+ import pandas as pd
12
+
13
+ from rich.console import Console
14
+ from rich.table import Table
15
+ from rich.panel import Panel
16
+ from rich.progress import Progress, SpinnerColumn, TextColumn
17
+ from rich import box
18
+
19
+ console = Console()
20
+
21
+ class RunbooksWrapper:
22
+ """
23
+ Enterprise wrapper for runbooks CLI commands with MCP validation and Rich output.
24
+
25
+ Provides Jupyter-friendly interface to all runbooks functionality:
26
+ - Inventory collection and analysis
27
+ - FinOps cost analysis and optimization
28
+ - Security assessments and remediation
29
+ - CFAT well-architected evaluations
30
+ - Operations automation
31
+ - Organization management
32
+ """
33
+
34
+ def __init__(self, default_profile: Optional[str] = None):
35
+ """Initialize wrapper with optional default AWS profile."""
36
+ self.default_profile = default_profile
37
+ self.console = Console()
38
+
39
+ def _execute_command(self, command: str, capture_output: bool = True) -> Dict[str, Any]:
40
+ """
41
+ Execute runbooks command with error handling and rich output.
42
+
43
+ Args:
44
+ command: Full runbooks command to execute
45
+ capture_output: Whether to capture command output
46
+
47
+ Returns:
48
+ Dictionary with command result, output, and metadata
49
+ """
50
+ try:
51
+ with Progress(
52
+ SpinnerColumn(),
53
+ TextColumn("[progress.description]{task.description}"),
54
+ console=console,
55
+ transient=True,
56
+ ) as progress:
57
+ progress.add_task(description="Executing command...", total=None)
58
+
59
+ result = subprocess.run(
60
+ command.split(),
61
+ capture_output=capture_output,
62
+ text=True,
63
+ timeout=300 # 5 minute timeout
64
+ )
65
+
66
+ return {
67
+ "success": result.returncode == 0,
68
+ "returncode": result.returncode,
69
+ "stdout": result.stdout,
70
+ "stderr": result.stderr,
71
+ "command": command,
72
+ "timestamp": datetime.now().isoformat()
73
+ }
74
+
75
+ except subprocess.TimeoutExpired:
76
+ return {
77
+ "success": False,
78
+ "error": "Command timed out after 5 minutes",
79
+ "command": command,
80
+ "timestamp": datetime.now().isoformat()
81
+ }
82
+ except Exception as e:
83
+ return {
84
+ "success": False,
85
+ "error": str(e),
86
+ "command": command,
87
+ "timestamp": datetime.now().isoformat()
88
+ }
89
+
90
+ # Inventory Operations
91
+ def inventory_collect(self,
92
+ resources: List[str] = None,
93
+ profile: str = None,
94
+ all_accounts: bool = False,
95
+ include_costs: bool = False,
96
+ regions: List[str] = None) -> pd.DataFrame:
97
+ """
98
+ Collect inventory across AWS accounts and services.
99
+
100
+ Args:
101
+ resources: List of AWS resources (ec2, s3, rds, lambda, etc.)
102
+ profile: AWS profile to use
103
+ all_accounts: Scan all accounts in organization
104
+ include_costs: Include cost analysis
105
+ regions: Specific regions to scan
106
+
107
+ Returns:
108
+ DataFrame with inventory results
109
+ """
110
+ cmd_parts = ["runbooks", "inventory", "collect"]
111
+
112
+ if resources:
113
+ cmd_parts.extend(["-r", ",".join(resources)])
114
+ if profile or self.default_profile:
115
+ cmd_parts.extend(["--profile", profile or self.default_profile])
116
+ if all_accounts:
117
+ cmd_parts.append("--all-accounts")
118
+ if include_costs:
119
+ cmd_parts.append("--include-costs")
120
+ if regions:
121
+ cmd_parts.extend(["--regions", ",".join(regions)])
122
+
123
+ command = " ".join(cmd_parts)
124
+ result = self._execute_command(command)
125
+
126
+ if result["success"]:
127
+ try:
128
+ # Parse JSON output to DataFrame
129
+ data = json.loads(result["stdout"])
130
+ df = pd.DataFrame(data.get("resources", []))
131
+ self._display_inventory_summary(df)
132
+ return df
133
+ except json.JSONDecodeError:
134
+ console.print("[yellow]Warning: Could not parse JSON output, returning raw text[/yellow]")
135
+ return pd.DataFrame({"output": [result["stdout"]]})
136
+ else:
137
+ console.print(f"[red]Error executing inventory command: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
138
+ return pd.DataFrame()
139
+
140
+ # FinOps Operations
141
+ def finops_analyze(self,
142
+ profile: str = None,
143
+ all_accounts: bool = False,
144
+ target_reduction: str = "20-40%",
145
+ breakdown_by: List[str] = None,
146
+ export_format: str = "json") -> Dict[str, Any]:
147
+ """
148
+ Perform FinOps cost analysis and optimization recommendations.
149
+
150
+ Args:
151
+ profile: AWS billing profile
152
+ all_accounts: Analyze all accounts
153
+ target_reduction: Target cost reduction percentage
154
+ breakdown_by: Breakdown by service, account, region
155
+ export_format: Export format (json, csv, html)
156
+
157
+ Returns:
158
+ Dictionary with cost analysis results
159
+ """
160
+ cmd_parts = ["runbooks", "finops", "--analyze"]
161
+
162
+ if profile or self.default_profile:
163
+ cmd_parts.extend(["--profile", profile or self.default_profile])
164
+ if all_accounts:
165
+ cmd_parts.append("--all-accounts")
166
+ if target_reduction:
167
+ cmd_parts.extend(["--target-reduction", target_reduction])
168
+ if breakdown_by:
169
+ cmd_parts.extend(["--breakdown-by", ",".join(breakdown_by)])
170
+ if export_format:
171
+ cmd_parts.extend(["--export", export_format])
172
+
173
+ command = " ".join(cmd_parts)
174
+ result = self._execute_command(command)
175
+
176
+ if result["success"]:
177
+ try:
178
+ data = json.loads(result["stdout"])
179
+ self._display_finops_summary(data)
180
+ return data
181
+ except json.JSONDecodeError:
182
+ console.print("[yellow]Warning: Could not parse JSON output[/yellow]")
183
+ return {"raw_output": result["stdout"]}
184
+ else:
185
+ console.print(f"[red]Error executing FinOps analysis: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
186
+ return {}
187
+
188
+ # Security Operations
189
+ def security_assess(self,
190
+ profile: str = None,
191
+ all_accounts: bool = False,
192
+ checks: str = "all",
193
+ language: str = "EN",
194
+ format: str = "json") -> Dict[str, Any]:
195
+ """
196
+ Perform security assessment across accounts.
197
+
198
+ Args:
199
+ profile: AWS profile to use
200
+ all_accounts: Assess all accounts
201
+ checks: Specific security checks or "all"
202
+ language: Report language (EN, JP, KR, VN)
203
+ format: Output format (json, html, csv)
204
+
205
+ Returns:
206
+ Dictionary with security assessment results
207
+ """
208
+ cmd_parts = ["runbooks", "security", "assess"]
209
+
210
+ if profile or self.default_profile:
211
+ cmd_parts.extend(["--profile", profile or self.default_profile])
212
+ if all_accounts:
213
+ cmd_parts.append("--all-accounts")
214
+ if checks:
215
+ cmd_parts.extend(["--checks", checks])
216
+ if language:
217
+ cmd_parts.extend(["--language", language])
218
+ if format:
219
+ cmd_parts.extend(["--format", format])
220
+
221
+ command = " ".join(cmd_parts)
222
+ result = self._execute_command(command)
223
+
224
+ if result["success"]:
225
+ try:
226
+ data = json.loads(result["stdout"])
227
+ self._display_security_summary(data)
228
+ return data
229
+ except json.JSONDecodeError:
230
+ return {"raw_output": result["stdout"]}
231
+ else:
232
+ console.print(f"[red]Error executing security assessment: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
233
+ return {}
234
+
235
+ # CFAT Operations
236
+ def cfat_assess(self,
237
+ profile: str = None,
238
+ compliance_framework: str = "AWS Well-Architected",
239
+ output_format: str = "json",
240
+ serve_web: bool = False,
241
+ port: int = 8080) -> Dict[str, Any]:
242
+ """
243
+ Perform Cloud Foundation Assessment Tool evaluation.
244
+
245
+ Args:
246
+ profile: AWS profile to use
247
+ compliance_framework: Framework to assess against
248
+ output_format: Output format
249
+ serve_web: Start web server for results
250
+ port: Web server port
251
+
252
+ Returns:
253
+ Dictionary with CFAT assessment results
254
+ """
255
+ cmd_parts = ["runbooks", "cfat", "assess"]
256
+
257
+ if profile or self.default_profile:
258
+ cmd_parts.extend(["--profile", profile or self.default_profile])
259
+ if compliance_framework:
260
+ cmd_parts.extend(["--compliance-framework", f'"{compliance_framework}"'])
261
+ if output_format:
262
+ cmd_parts.extend(["--output", output_format])
263
+ if serve_web:
264
+ cmd_parts.extend(["--serve-web", "--port", str(port)])
265
+
266
+ command = " ".join(cmd_parts)
267
+ result = self._execute_command(command)
268
+
269
+ if result["success"]:
270
+ try:
271
+ data = json.loads(result["stdout"])
272
+ self._display_cfat_summary(data)
273
+ return data
274
+ except json.JSONDecodeError:
275
+ return {"raw_output": result["stdout"]}
276
+ else:
277
+ console.print(f"[red]Error executing CFAT assessment: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
278
+ return {}
279
+
280
+ # Operations
281
+ def operate_ec2(self, action: str, instance_ids: List[str], profile: str = None, dry_run: bool = True) -> Dict[str, Any]:
282
+ """
283
+ Perform EC2 operations (start, stop, terminate).
284
+
285
+ Args:
286
+ action: Action to perform (start, stop, terminate)
287
+ instance_ids: List of instance IDs
288
+ profile: AWS profile to use
289
+ dry_run: Perform dry run only
290
+
291
+ Returns:
292
+ Dictionary with operation results
293
+ """
294
+ cmd_parts = ["runbooks", "operate", "ec2", action]
295
+ cmd_parts.extend(["--instance-ids"] + instance_ids)
296
+
297
+ if profile or self.default_profile:
298
+ cmd_parts.extend(["--profile", profile or self.default_profile])
299
+ if dry_run:
300
+ cmd_parts.append("--dry-run")
301
+
302
+ command = " ".join(cmd_parts)
303
+ result = self._execute_command(command)
304
+
305
+ if result["success"]:
306
+ console.print(f"[green]✅ EC2 {action} operation completed successfully[/green]")
307
+ return {"success": True, "output": result["stdout"]}
308
+ else:
309
+ console.print(f"[red]❌ EC2 {action} operation failed: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
310
+ return {"success": False, "error": result.get("error", result.get("stderr"))}
311
+
312
+ # Organization Management
313
+ def org_list_ous(self, profile: str = None, output_format: str = "table") -> pd.DataFrame:
314
+ """
315
+ List organizational units in AWS Organizations.
316
+
317
+ Args:
318
+ profile: AWS management profile
319
+ output_format: Output format (table, json)
320
+
321
+ Returns:
322
+ DataFrame with OU information
323
+ """
324
+ cmd_parts = ["runbooks", "org", "list-ous"]
325
+
326
+ if profile or self.default_profile:
327
+ cmd_parts.extend(["--profile", profile or self.default_profile])
328
+ if output_format:
329
+ cmd_parts.extend(["--output", output_format])
330
+
331
+ command = " ".join(cmd_parts)
332
+ result = self._execute_command(command)
333
+
334
+ if result["success"]:
335
+ try:
336
+ if output_format == "json":
337
+ data = json.loads(result["stdout"])
338
+ df = pd.DataFrame(data.get("organizational_units", []))
339
+ else:
340
+ # Parse table output
341
+ df = pd.DataFrame({"output": [result["stdout"]]})
342
+ return df
343
+ except json.JSONDecodeError:
344
+ return pd.DataFrame({"output": [result["stdout"]]})
345
+ else:
346
+ console.print(f"[red]Error listing OUs: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
347
+ return pd.DataFrame()
348
+
349
+ # MCP Validation
350
+ def validate_mcp_servers(self, billing_profile: str = None) -> Dict[str, Any]:
351
+ """
352
+ Validate MCP servers connectivity and accuracy.
353
+
354
+ Args:
355
+ billing_profile: Billing profile for validation
356
+
357
+ Returns:
358
+ Dictionary with validation results
359
+ """
360
+ cmd_parts = ["runbooks", "validate", "mcp-servers"]
361
+
362
+ if billing_profile or self.default_profile:
363
+ cmd_parts.extend(["--billing-profile", billing_profile or self.default_profile])
364
+
365
+ command = " ".join(cmd_parts)
366
+ result = self._execute_command(command)
367
+
368
+ if result["success"]:
369
+ try:
370
+ data = json.loads(result["stdout"])
371
+ self._display_mcp_validation_summary(data)
372
+ return data
373
+ except json.JSONDecodeError:
374
+ return {"raw_output": result["stdout"]}
375
+ else:
376
+ console.print(f"[red]Error validating MCP servers: {result.get('error', result.get('stderr', 'Unknown error'))}[/red]")
377
+ return {}
378
+
379
+ # Rich Display Methods
380
+ def _display_inventory_summary(self, df: pd.DataFrame):
381
+ """Display inventory results summary with Rich formatting."""
382
+ if df.empty:
383
+ console.print("[yellow]No inventory data to display[/yellow]")
384
+ return
385
+
386
+ table = Table(title="📊 Inventory Summary", box=box.ROUNDED)
387
+ table.add_column("Metric", style="cyan")
388
+ table.add_column("Count", justify="right", style="green")
389
+
390
+ total_resources = len(df)
391
+ resource_types = df.get("ResourceType", pd.Series()).nunique() if "ResourceType" in df.columns else 0
392
+ accounts = df.get("AccountId", pd.Series()).nunique() if "AccountId" in df.columns else 0
393
+
394
+ table.add_row("Total Resources", str(total_resources))
395
+ table.add_row("Resource Types", str(resource_types))
396
+ table.add_row("AWS Accounts", str(accounts))
397
+
398
+ console.print(table)
399
+
400
+ def _display_finops_summary(self, data: Dict[str, Any]):
401
+ """Display FinOps analysis summary with Rich formatting."""
402
+ panel_content = []
403
+
404
+ if "total_cost" in data:
405
+ panel_content.append(f"💰 Total Monthly Cost: ${data['total_cost']:,.2f}")
406
+ if "potential_savings" in data:
407
+ panel_content.append(f"💸 Potential Savings: ${data['potential_savings']:,.2f}")
408
+ if "optimization_recommendations" in data:
409
+ panel_content.append(f"📋 Recommendations: {len(data['optimization_recommendations'])}")
410
+
411
+ content = "\n".join(panel_content) if panel_content else "FinOps analysis completed"
412
+
413
+ console.print(Panel(
414
+ content,
415
+ title="💰 FinOps Analysis Summary",
416
+ border_style="green"
417
+ ))
418
+
419
+ def _display_security_summary(self, data: Dict[str, Any]):
420
+ """Display security assessment summary with Rich formatting."""
421
+ panel_content = []
422
+
423
+ if "total_checks" in data:
424
+ panel_content.append(f"🔍 Total Checks: {data['total_checks']}")
425
+ if "passed_checks" in data:
426
+ panel_content.append(f"✅ Passed: {data['passed_checks']}")
427
+ if "failed_checks" in data:
428
+ panel_content.append(f"❌ Failed: {data['failed_checks']}")
429
+ if "compliance_score" in data:
430
+ panel_content.append(f"📊 Compliance Score: {data['compliance_score']}%")
431
+
432
+ content = "\n".join(panel_content) if panel_content else "Security assessment completed"
433
+
434
+ console.print(Panel(
435
+ content,
436
+ title="🔒 Security Assessment Summary",
437
+ border_style="red"
438
+ ))
439
+
440
+ def _display_cfat_summary(self, data: Dict[str, Any]):
441
+ """Display CFAT assessment summary with Rich formatting."""
442
+ panel_content = []
443
+
444
+ if "well_architected_score" in data:
445
+ panel_content.append(f"🏗️ Well-Architected Score: {data['well_architected_score']}%")
446
+ if "pillars_assessed" in data:
447
+ panel_content.append(f"📋 Pillars Assessed: {len(data['pillars_assessed'])}")
448
+ if "high_risk_findings" in data:
449
+ panel_content.append(f"⚠️ High Risk Findings: {data['high_risk_findings']}")
450
+
451
+ content = "\n".join(panel_content) if panel_content else "CFAT assessment completed"
452
+
453
+ console.print(Panel(
454
+ content,
455
+ title="🏗️ CFAT Assessment Summary",
456
+ border_style="blue"
457
+ ))
458
+
459
+ def _display_mcp_validation_summary(self, data: Dict[str, Any]):
460
+ """Display MCP validation summary with Rich formatting."""
461
+ panel_content = []
462
+
463
+ if "accuracy_rate" in data:
464
+ panel_content.append(f"🎯 Accuracy Rate: {data['accuracy_rate']}%")
465
+ if "servers_validated" in data:
466
+ panel_content.append(f"🖥️ Servers Validated: {data['servers_validated']}")
467
+ if "validation_time" in data:
468
+ panel_content.append(f"⏱️ Validation Time: {data['validation_time']}s")
469
+
470
+ content = "\n".join(panel_content) if panel_content else "MCP validation completed"
471
+
472
+ color = "green" if data.get("accuracy_rate", 0) >= 99.5 else "yellow"
473
+
474
+ console.print(Panel(
475
+ content,
476
+ title="🔍 MCP Validation Summary",
477
+ border_style=color
478
+ ))