runbooks 1.1.5__py3-none-any.whl → 1.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runbooks/cli/commands/inventory.py +21 -80
- runbooks/common/accuracy_validator.py +6 -12
- runbooks/common/mcp_integration.py +38 -7
- runbooks/common/rich_utils.py +116 -2
- runbooks/inventory/CLAUDE.md +1 -1
- runbooks/inventory/aws_decorators.py +2 -3
- runbooks/inventory/check_cloudtrail_compliance.py +2 -4
- runbooks/inventory/check_controltower_readiness.py +152 -151
- runbooks/inventory/check_landingzone_readiness.py +85 -84
- runbooks/inventory/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/ec2_vpc_utils.py +2 -2
- runbooks/inventory/find_cfn_drift_detection.py +5 -7
- runbooks/inventory/find_cfn_orphaned_stacks.py +7 -9
- runbooks/inventory/find_cfn_stackset_drift.py +5 -6
- runbooks/inventory/find_ec2_security_groups.py +48 -42
- runbooks/inventory/find_landingzone_versions.py +4 -6
- runbooks/inventory/find_vpc_flow_logs.py +7 -9
- runbooks/inventory/inventory_modules.py +103 -91
- runbooks/inventory/list_cfn_stacks.py +9 -10
- runbooks/inventory/list_cfn_stackset_operation_results.py +1 -3
- runbooks/inventory/list_cfn_stackset_operations.py +79 -57
- runbooks/inventory/list_cfn_stacksets.py +8 -10
- runbooks/inventory/list_config_recorders_delivery_channels.py +49 -39
- runbooks/inventory/list_ds_directories.py +65 -53
- runbooks/inventory/list_ec2_availability_zones.py +2 -4
- runbooks/inventory/list_ec2_ebs_volumes.py +32 -35
- runbooks/inventory/list_ec2_instances.py +23 -28
- runbooks/inventory/list_ecs_clusters_and_tasks.py +26 -34
- runbooks/inventory/list_elbs_load_balancers.py +22 -20
- runbooks/inventory/list_enis_network_interfaces.py +26 -33
- runbooks/inventory/list_guardduty_detectors.py +2 -4
- runbooks/inventory/list_iam_policies.py +2 -4
- runbooks/inventory/list_iam_roles.py +5 -7
- runbooks/inventory/list_iam_saml_providers.py +4 -6
- runbooks/inventory/list_lambda_functions.py +38 -38
- runbooks/inventory/list_org_accounts.py +6 -8
- runbooks/inventory/list_org_accounts_users.py +55 -44
- runbooks/inventory/list_rds_db_instances.py +31 -33
- runbooks/inventory/list_route53_hosted_zones.py +3 -5
- runbooks/inventory/list_servicecatalog_provisioned_products.py +37 -41
- runbooks/inventory/list_sns_topics.py +2 -4
- runbooks/inventory/list_ssm_parameters.py +4 -7
- runbooks/inventory/list_vpc_subnets.py +2 -4
- runbooks/inventory/list_vpcs.py +7 -10
- runbooks/inventory/mcp_inventory_validator.py +5 -3
- runbooks/inventory/organizations_discovery.py +8 -4
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +2 -2
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +3 -2
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +3 -2
- runbooks/inventory/vpc_dependency_analyzer.py +2 -2
- runbooks/validation/terraform_drift_detector.py +16 -5
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/METADATA +3 -4
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/RECORD +62 -62
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.5.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
@@ -106,7 +106,6 @@ def create_inventory_group():
|
|
106
106
|
click.echo(ctx.get_help())
|
107
107
|
|
108
108
|
@inventory.command()
|
109
|
-
@common_aws_options
|
110
109
|
@click.option("--resources", "-r", multiple=True, help="Resource types (ec2, rds, lambda, s3, etc.)")
|
111
110
|
@click.option("--all-resources", is_flag=True, help="Collect all resource types")
|
112
111
|
@click.option("--all-profiles", is_flag=True, help="Collect from all organization accounts")
|
@@ -152,9 +151,6 @@ def create_inventory_group():
|
|
152
151
|
@click.pass_context
|
153
152
|
def collect(
|
154
153
|
ctx,
|
155
|
-
profile,
|
156
|
-
region,
|
157
|
-
dry_run,
|
158
154
|
resources,
|
159
155
|
all_resources,
|
160
156
|
all_profiles,
|
@@ -209,6 +205,11 @@ def create_inventory_group():
|
|
209
205
|
try:
|
210
206
|
from runbooks.inventory.core.collector import run_inventory_collection
|
211
207
|
|
208
|
+
# Access group-level AWS options from context (Bug #1 fix: profile override priority)
|
209
|
+
profile = ctx.obj.get('profile')
|
210
|
+
region = ctx.obj.get('region')
|
211
|
+
dry_run = ctx.obj.get('dry_run', False)
|
212
|
+
|
212
213
|
# Enhanced context for inventory collection
|
213
214
|
context_args = {
|
214
215
|
"profile": profile,
|
@@ -266,18 +267,27 @@ def create_inventory_group():
|
|
266
267
|
help="Resource types to validate",
|
267
268
|
)
|
268
269
|
@click.option("--test-mode", is_flag=True, default=True, help="Run in test mode with sample data")
|
270
|
+
@click.option(
|
271
|
+
"--real-validation",
|
272
|
+
is_flag=True,
|
273
|
+
default=False,
|
274
|
+
help="Run validation against real AWS APIs (requires valid profiles)",
|
275
|
+
)
|
269
276
|
@click.pass_context
|
270
|
-
def validate_mcp(ctx, resource_types, test_mode):
|
277
|
+
def validate_mcp(ctx, resource_types, test_mode, real_validation):
|
271
278
|
"""Test inventory MCP validation functionality."""
|
272
279
|
try:
|
273
280
|
from runbooks.inventory.mcp_inventory_validator import create_inventory_mcp_validator
|
274
281
|
from runbooks.common.profile_utils import get_profile_for_operation
|
275
282
|
|
283
|
+
# Access profile from group-level context (Bug #3 fix: profile override support)
|
284
|
+
profile = ctx.obj.get('profile')
|
285
|
+
|
276
286
|
console.print(f"[blue]🔍 Testing Inventory MCP Validation[/blue]")
|
277
|
-
console.print(f"[dim]Profile: {
|
287
|
+
console.print(f"[dim]Profile: {profile or 'environment fallback'} | Resources: {', '.join(resource_types)} | Test mode: {test_mode}[/dim]")
|
278
288
|
|
279
289
|
# Initialize validator
|
280
|
-
operational_profile = get_profile_for_operation("operational",
|
290
|
+
operational_profile = get_profile_for_operation("operational", profile)
|
281
291
|
validator = create_inventory_mcp_validator([operational_profile])
|
282
292
|
|
283
293
|
# Test with sample data
|
@@ -302,78 +312,9 @@ def create_inventory_group():
|
|
302
312
|
console.print(f"[red]❌ MCP validation test failed: {e}[/red]")
|
303
313
|
raise click.ClickException(str(e))
|
304
314
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
@click.option(
|
310
|
-
"--export-format",
|
311
|
-
type=click.Choice(["json", "csv", "markdown", "table"]),
|
312
|
-
default="table",
|
313
|
-
help="Export format for results",
|
314
|
-
)
|
315
|
-
@click.option("--output-dir", default="./awso_evidence", help="Output directory for exports")
|
316
|
-
@click.option("--filter-account", help="Filter snapshots by specific account ID")
|
317
|
-
@click.option("--filter-status", help="Filter snapshots by status (available, creating, deleting)")
|
318
|
-
@click.option("--max-age-days", type=int, help="Filter snapshots older than specified days")
|
319
|
-
@click.pass_context
|
320
|
-
def discover_rds_snapshots(
|
321
|
-
ctx,
|
322
|
-
profile,
|
323
|
-
region,
|
324
|
-
dry_run,
|
325
|
-
all,
|
326
|
-
combine,
|
327
|
-
export_format,
|
328
|
-
output_dir,
|
329
|
-
filter_account,
|
330
|
-
filter_status,
|
331
|
-
max_age_days,
|
332
|
-
):
|
333
|
-
"""
|
334
|
-
🔍 Discover RDS snapshots using AWS Config organization-aggregator.
|
335
|
-
|
336
|
-
✅ Enhanced Cross-Account Discovery:
|
337
|
-
- Leverages AWS Config organization-aggregator for cross-account access
|
338
|
-
- Multi-region discovery across 7 key AWS regions
|
339
|
-
- Intelligent Organizations detection with graceful standalone fallback
|
340
|
-
- Multi-format exports: JSON, CSV, Markdown, Table
|
341
|
-
|
342
|
-
Profile Priority: User > Environment > Default
|
343
|
-
Universal AWS compatibility with any profile configuration
|
344
|
-
|
345
|
-
Examples:
|
346
|
-
runbooks inventory rds-snapshots # Default profile
|
347
|
-
runbooks inventory rds-snapshots --profile org-profile # Organizations profile
|
348
|
-
runbooks inventory rds-snapshots --all --combine # Multi-account discovery
|
349
|
-
runbooks inventory rds-snapshots --filter-status available # Filter by status
|
350
|
-
runbooks inventory rds-snapshots --max-age-days 30 --csv # Recent snapshots
|
351
|
-
"""
|
352
|
-
try:
|
353
|
-
from runbooks.inventory.rds_snapshots_discovery import run_rds_snapshots_discovery
|
354
|
-
|
355
|
-
# Enhanced context for RDS snapshots discovery
|
356
|
-
context_args = {
|
357
|
-
"profile": profile,
|
358
|
-
"region": region,
|
359
|
-
"dry_run": dry_run,
|
360
|
-
"all": all,
|
361
|
-
"combine": combine,
|
362
|
-
"export_format": export_format,
|
363
|
-
"output_dir": output_dir,
|
364
|
-
"filter_account": filter_account,
|
365
|
-
"filter_status": filter_status,
|
366
|
-
"max_age_days": max_age_days,
|
367
|
-
}
|
368
|
-
|
369
|
-
# Run RDS snapshots discovery
|
370
|
-
return run_rds_snapshots_discovery(**context_args)
|
371
|
-
|
372
|
-
except ImportError as e:
|
373
|
-
console.print(f"[red]❌ RDS snapshots discovery module not available: {e}[/red]")
|
374
|
-
raise click.ClickException("RDS snapshots discovery functionality not available")
|
375
|
-
except Exception as e:
|
376
|
-
console.print(f"[red]❌ RDS snapshots discovery failed: {e}[/red]")
|
377
|
-
raise click.ClickException(str(e))
|
315
|
+
# NOTE: rds-snapshots command removed in v1.1.6 (Bug #2 fix: phantom command elimination)
|
316
|
+
# Reason: Module rds_snapshots_discovery.py doesn't exist (was never implemented)
|
317
|
+
# Future work: Implement proper RDS snapshots discovery in v1.2.0
|
318
|
+
# See: artifacts/future-work/rds-snapshots-discovery-v1.2.0.md
|
378
319
|
|
379
320
|
return inventory
|
@@ -85,18 +85,12 @@ except ImportError:
|
|
85
85
|
# Fallback implementation if FinOps not available
|
86
86
|
FINOPS_INTEGRATION_AVAILABLE = False
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
class AccuracyLevel(Enum):
|
96
|
-
ENTERPRISE = 99.99
|
97
|
-
BUSINESS = 99.50
|
98
|
-
OPERATIONAL = 95.00
|
99
|
-
DEVELOPMENT = 90.00
|
88
|
+
# Import ValidationResult from mcp_validator which is the source of truth
|
89
|
+
from ..finops.mcp_validator import ValidationResult, ValidationStatus, AccuracyLevel
|
90
|
+
|
91
|
+
# Define AccuracyCrossValidator as None for compatibility
|
92
|
+
AccuracyCrossValidator = None
|
93
|
+
CrossValidationReport = None
|
100
94
|
|
101
95
|
|
102
96
|
T = TypeVar("T")
|
@@ -457,6 +457,9 @@ class EnterpriseMCPIntegrator:
|
|
457
457
|
|
458
458
|
ec2_client = ops_session.client("ec2")
|
459
459
|
|
460
|
+
# Track validation results for accuracy calculation
|
461
|
+
validation_results = []
|
462
|
+
|
460
463
|
with Progress(
|
461
464
|
SpinnerColumn(),
|
462
465
|
TextColumn("[progress.description]{task.description}"),
|
@@ -468,10 +471,12 @@ class EnterpriseMCPIntegrator:
|
|
468
471
|
task = progress.add_task("Cross-validating VPC data with AWS APIs...", total=100)
|
469
472
|
|
470
473
|
# Cross-validate VPC discovery
|
471
|
-
await self._validate_vpc_discovery(ec2_client, vpc_data, progress, task)
|
474
|
+
vpc_discovery_result = await self._validate_vpc_discovery(ec2_client, vpc_data, progress, task)
|
475
|
+
validation_results.append(vpc_discovery_result)
|
472
476
|
|
473
477
|
# Validate VPC dependencies (ENIs, subnets, etc.)
|
474
|
-
await self._validate_vpc_dependencies(ec2_client, vpc_data, progress, task)
|
478
|
+
vpc_dependency_result = await self._validate_vpc_dependencies(ec2_client, vpc_data, progress, task)
|
479
|
+
validation_results.append(vpc_dependency_result)
|
475
480
|
|
476
481
|
# Validate cost data if available
|
477
482
|
if "cost_data" in vpc_data:
|
@@ -482,19 +487,27 @@ class EnterpriseMCPIntegrator:
|
|
482
487
|
|
483
488
|
progress.update(task, completed=100)
|
484
489
|
|
485
|
-
|
486
|
-
|
490
|
+
# Calculate real accuracy from validation results
|
491
|
+
total_validations = sum(r["total"] for r in validation_results)
|
492
|
+
successful_validations = sum(r["validated"] for r in validation_results)
|
493
|
+
calculated_accuracy = (successful_validations / total_validations * 100) if total_validations > 0 else 0.0
|
494
|
+
|
495
|
+
result.success = calculated_accuracy >= 99.5 # Success only if meets threshold
|
496
|
+
result.accuracy_score = calculated_accuracy # Real calculated value, not hardcoded
|
487
497
|
result.total_resources_validated = len(vpc_data.get("vpc_candidates", []))
|
488
498
|
result.performance_metrics = {
|
489
499
|
"validation_time_seconds": time.time() - start_time,
|
490
500
|
"vpc_discovery_validated": True,
|
491
501
|
"dependency_analysis_validated": True,
|
502
|
+
"total_validations": total_validations,
|
503
|
+
"successful_validations": successful_validations,
|
492
504
|
}
|
493
505
|
|
494
|
-
print_success(f"VPC MCP validation complete: {result.accuracy_score}% accuracy")
|
506
|
+
print_success(f"VPC MCP validation complete: {result.accuracy_score:.1f}% accuracy")
|
495
507
|
|
496
508
|
except Exception as e:
|
497
509
|
result.success = False
|
510
|
+
result.accuracy_score = 0.0 # Honest failure - no optimistic defaults
|
498
511
|
result.error_details = [str(e)]
|
499
512
|
print_error(f"VPC MCP validation failed: {str(e)}")
|
500
513
|
|
@@ -635,7 +648,7 @@ class EnterpriseMCPIntegrator:
|
|
635
648
|
except Exception as e:
|
636
649
|
print_warning(f"Cost validation error: {str(e)[:50]}...")
|
637
650
|
|
638
|
-
async def _validate_vpc_discovery(self, ec2_client, vpc_data: Dict, progress, task) ->
|
651
|
+
async def _validate_vpc_discovery(self, ec2_client, vpc_data: Dict, progress, task) -> Dict[str, Any]:
|
639
652
|
"""Validate VPC discovery against AWS EC2 API."""
|
640
653
|
try:
|
641
654
|
# Get actual VPCs from AWS
|
@@ -663,14 +676,23 @@ class EnterpriseMCPIntegrator:
|
|
663
676
|
|
664
677
|
print_info(f"VPC Discovery Validation: {validated_vpcs} validated out of {vpc_count_match} actual VPCs")
|
665
678
|
|
679
|
+
# Return validation results for accuracy calculation
|
680
|
+
return {
|
681
|
+
"total": vpc_count_match,
|
682
|
+
"validated": validated_vpcs,
|
683
|
+
"accuracy": (validated_vpcs / vpc_count_match * 100) if vpc_count_match > 0 else 0.0
|
684
|
+
}
|
685
|
+
|
666
686
|
except Exception as e:
|
667
687
|
print_warning(f"VPC discovery validation error: {str(e)[:50]}...")
|
688
|
+
return {"total": 0, "validated": 0, "accuracy": 0.0}
|
668
689
|
|
669
|
-
async def _validate_vpc_dependencies(self, ec2_client, vpc_data: Dict, progress, task) ->
|
690
|
+
async def _validate_vpc_dependencies(self, ec2_client, vpc_data: Dict, progress, task) -> Dict[str, Any]:
|
670
691
|
"""Validate VPC dependency counts (ENIs, subnets, etc.)."""
|
671
692
|
try:
|
672
693
|
vpc_candidates = vpc_data.get("vpc_candidates", [])
|
673
694
|
validated_count = 0
|
695
|
+
total_checked = 0
|
674
696
|
|
675
697
|
for candidate in vpc_candidates[:5]: # Sample validation for performance
|
676
698
|
vpc_id = (
|
@@ -680,6 +702,7 @@ class EnterpriseMCPIntegrator:
|
|
680
702
|
)
|
681
703
|
|
682
704
|
if vpc_id:
|
705
|
+
total_checked += 1
|
683
706
|
# Cross-validate ENI count (critical for safety)
|
684
707
|
eni_response = ec2_client.describe_network_interfaces(
|
685
708
|
Filters=[{"Name": "vpc-id", "Values": [vpc_id]}]
|
@@ -697,8 +720,16 @@ class EnterpriseMCPIntegrator:
|
|
697
720
|
|
698
721
|
progress.update(task, advance=30, description=f"Validated dependencies for {validated_count} VPCs...")
|
699
722
|
|
723
|
+
# Return validation results for accuracy calculation
|
724
|
+
return {
|
725
|
+
"total": total_checked,
|
726
|
+
"validated": validated_count,
|
727
|
+
"accuracy": (validated_count / total_checked * 100) if total_checked > 0 else 0.0
|
728
|
+
}
|
729
|
+
|
700
730
|
except Exception as e:
|
701
731
|
print_warning(f"VPC dependency validation error: {str(e)[:50]}...")
|
732
|
+
return {"total": 0, "validated": 0, "accuracy": 0.0}
|
702
733
|
|
703
734
|
async def _validate_vpc_cost_data(self, cost_client, vpc_data: Dict, progress, task) -> None:
|
704
735
|
"""Validate VPC cost data using Cost Explorer API."""
|
runbooks/common/rich_utils.py
CHANGED
@@ -50,27 +50,76 @@ USE_RICH = os.getenv("RUNBOOKS_TEST_MODE") != "1"
|
|
50
50
|
|
51
51
|
if USE_RICH:
|
52
52
|
from rich.console import Console as RichConsole
|
53
|
+
from rich.progress import Progress as RichProgress
|
53
54
|
|
54
55
|
Console = RichConsole
|
55
56
|
Table = RichTable
|
57
|
+
Progress = RichProgress
|
56
58
|
else:
|
57
59
|
# Mock Rich Console for testing - plain text output compatible with Click CliRunner
|
58
60
|
class MockConsole:
|
59
61
|
"""Mock console that prints to stdout without Rich formatting."""
|
60
62
|
|
63
|
+
def __init__(self, **kwargs):
|
64
|
+
"""Initialize mock console - ignore all kwargs for compatibility."""
|
65
|
+
self._capture_buffer = None
|
66
|
+
|
61
67
|
def print(self, *args, **kwargs):
|
62
|
-
"""
|
68
|
+
"""
|
69
|
+
Mock print that outputs plain text to stdout.
|
70
|
+
|
71
|
+
Accepts all Rich Console.print() parameters but ignores styling.
|
72
|
+
Compatible with Click CliRunner's StringIO buffer management.
|
73
|
+
"""
|
74
|
+
# Ignore all kwargs (style, highlight, etc.) - test mode doesn't need them
|
63
75
|
if args:
|
64
76
|
# Extract text content from Rich markup if present
|
65
77
|
text = str(args[0]) if args else ""
|
66
78
|
# Remove Rich markup tags for plain output
|
67
79
|
text = re.sub(r"\[.*?\]", "", text)
|
68
|
-
|
80
|
+
|
81
|
+
# If capturing, append to buffer instead of printing
|
82
|
+
if self._capture_buffer is not None:
|
83
|
+
self._capture_buffer.append(text)
|
84
|
+
else:
|
85
|
+
# Use print() to stdout - avoid sys.stdout.write() which causes I/O errors
|
86
|
+
# DO NOT use file= parameter or flush= parameter with Click CliRunner
|
87
|
+
print(text)
|
88
|
+
|
89
|
+
def log(self, *args, **kwargs):
|
90
|
+
"""Mock log method - same as print for testing compatibility."""
|
91
|
+
self.print(*args, **kwargs)
|
92
|
+
|
93
|
+
def capture(self):
|
94
|
+
"""
|
95
|
+
Mock capture context manager for testing.
|
96
|
+
|
97
|
+
Returns a context manager that captures console output to a buffer
|
98
|
+
instead of printing to stdout. Compatible with Rich Console.capture() API.
|
99
|
+
"""
|
100
|
+
class MockCapture:
|
101
|
+
def __init__(self, console):
|
102
|
+
self.console = console
|
103
|
+
self.buffer = []
|
104
|
+
|
105
|
+
def __enter__(self):
|
106
|
+
self.console._capture_buffer = self.buffer
|
107
|
+
return self
|
108
|
+
|
109
|
+
def __exit__(self, *args):
|
110
|
+
self.console._capture_buffer = None
|
111
|
+
|
112
|
+
def get(self):
|
113
|
+
"""Return captured output as string."""
|
114
|
+
return "\n".join(self.buffer)
|
115
|
+
|
116
|
+
return MockCapture(self)
|
69
117
|
|
70
118
|
def __enter__(self):
|
71
119
|
return self
|
72
120
|
|
73
121
|
def __exit__(self, *args):
|
122
|
+
# CRITICAL: Don't close anything - let Click CliRunner manage streams
|
74
123
|
pass
|
75
124
|
|
76
125
|
class MockTable:
|
@@ -87,8 +136,70 @@ else:
|
|
87
136
|
def add_row(self, *args):
|
88
137
|
self.rows.append(args)
|
89
138
|
|
139
|
+
class MockProgress:
|
140
|
+
"""
|
141
|
+
Mock Progress for testing - prevents I/O conflicts with Click CliRunner.
|
142
|
+
|
143
|
+
Provides complete Rich.Progress API compatibility without any stream operations
|
144
|
+
that could interfere with Click's StringIO buffer management.
|
145
|
+
"""
|
146
|
+
|
147
|
+
def __init__(self, *columns, **kwargs):
|
148
|
+
"""Initialize mock progress - ignore all kwargs for test compatibility."""
|
149
|
+
self.columns = columns
|
150
|
+
self.kwargs = kwargs
|
151
|
+
self.tasks = {}
|
152
|
+
self.task_counter = 0
|
153
|
+
self._started = False
|
154
|
+
|
155
|
+
def add_task(self, description, total=None, **kwargs):
|
156
|
+
"""Add a mock task and return task ID."""
|
157
|
+
task_id = self.task_counter
|
158
|
+
self.tasks[task_id] = {
|
159
|
+
"description": description,
|
160
|
+
"total": total,
|
161
|
+
"completed": 0,
|
162
|
+
"kwargs": kwargs
|
163
|
+
}
|
164
|
+
self.task_counter += 1
|
165
|
+
return task_id
|
166
|
+
|
167
|
+
def update(self, task_id, **kwargs):
|
168
|
+
"""Update mock task progress."""
|
169
|
+
if task_id in self.tasks:
|
170
|
+
self.tasks[task_id].update(kwargs)
|
171
|
+
|
172
|
+
def start(self):
|
173
|
+
"""Mock start method - no-op for test safety."""
|
174
|
+
self._started = True
|
175
|
+
return self
|
176
|
+
|
177
|
+
def stop(self):
|
178
|
+
"""Mock stop method - CRITICAL: no stream operations."""
|
179
|
+
self._started = False
|
180
|
+
# IMPORTANT: Do NOT close any streams or file handles
|
181
|
+
# Click CliRunner manages its own StringIO lifecycle
|
182
|
+
|
183
|
+
def __enter__(self):
|
184
|
+
"""Context manager entry - start progress."""
|
185
|
+
self.start()
|
186
|
+
return self
|
187
|
+
|
188
|
+
def __exit__(self, *args):
|
189
|
+
"""
|
190
|
+
Context manager exit - stop progress WITHOUT stream closure.
|
191
|
+
|
192
|
+
CRITICAL: This method must NOT perform any file operations that could
|
193
|
+
close Click CliRunner's StringIO buffer. The stop() method is intentionally
|
194
|
+
a no-op to prevent "ValueError: I/O operation on closed file" errors.
|
195
|
+
"""
|
196
|
+
self.stop()
|
197
|
+
# Explicitly return None to allow exception propagation
|
198
|
+
return None
|
199
|
+
|
90
200
|
Console = MockConsole
|
91
201
|
Table = MockTable
|
202
|
+
Progress = MockProgress
|
92
203
|
|
93
204
|
# CloudOps Custom Theme
|
94
205
|
CLOUDOPS_THEME = Theme(
|
@@ -917,6 +1028,9 @@ __all__ = [
|
|
917
1028
|
"CLOUDOPS_THEME",
|
918
1029
|
"STATUS_INDICATORS",
|
919
1030
|
"console",
|
1031
|
+
"Console",
|
1032
|
+
"Progress",
|
1033
|
+
"Table",
|
920
1034
|
"get_console",
|
921
1035
|
"get_context_aware_console",
|
922
1036
|
"print_header",
|
runbooks/inventory/CLAUDE.md
CHANGED
@@ -309,7 +309,7 @@ three_mode_validation:
|
|
309
309
|
```
|
310
310
|
|
311
311
|
### MCP Validation Protocols ✨ **100% ACCURACY ACHIEVED**
|
312
|
-
**AWS MCP Server Integration (Proven
|
312
|
+
**AWS MCP Server Integration (Proven Enterprise Direct Function Testing Pattern)**:
|
313
313
|
|
314
314
|
```yaml
|
315
315
|
mcp_validation_framework:
|
@@ -84,9 +84,8 @@ import functools
|
|
84
84
|
import time
|
85
85
|
from typing import Any, Callable
|
86
86
|
|
87
|
-
from
|
87
|
+
from runbooks.common.rich_utils import console
|
88
88
|
|
89
|
-
init()
|
90
89
|
|
91
90
|
|
92
91
|
def timer(to_time_or_not: bool = False) -> Callable:
|
@@ -191,7 +190,7 @@ def timer(to_time_or_not: bool = False) -> Callable:
|
|
191
190
|
# Display timing information if enabled with colorized output
|
192
191
|
if to_time_or_not:
|
193
192
|
print()
|
194
|
-
print(f"
|
193
|
+
print(f"[green]Finished function {func.__name__!r} in {run_time:.4f} seconds")
|
195
194
|
print()
|
196
195
|
|
197
196
|
return value
|
@@ -133,10 +133,9 @@ from time import time
|
|
133
133
|
import Inventory_Modules
|
134
134
|
from ArgumentsClass import CommonArguments
|
135
135
|
from botocore.exceptions import ClientError
|
136
|
-
from
|
136
|
+
from runbooks.common.rich_utils import console
|
137
137
|
from Inventory_Modules import display_results, get_all_credentials
|
138
138
|
|
139
|
-
init()
|
140
139
|
__version__ = "2023.10.03"
|
141
140
|
|
142
141
|
|
@@ -443,7 +442,6 @@ def check_account_for_cloudtrail(f_AllCredentials):
|
|
443
442
|
|
444
443
|
##################
|
445
444
|
# ANSI escape sequence for terminal line clearing in progress display
|
446
|
-
ERASE_LINE = "\x1b[2K"
|
447
445
|
|
448
446
|
if __name__ == "__main__":
|
449
447
|
"""
|
@@ -607,7 +605,7 @@ if __name__ == "__main__":
|
|
607
605
|
# Display performance timing for operational optimization and SLA compliance
|
608
606
|
if pTiming:
|
609
607
|
print(ERASE_LINE) # Clear progress indicators for clean timing display
|
610
|
-
print(f"
|
608
|
+
print(f"[green]This script took {time() - begin_time:.2f} seconds")
|
611
609
|
|
612
610
|
# Display completion message for user confirmation and operational closure
|
613
611
|
print("Thank you for using this script")
|