runbooks 1.1.4__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/__init__.py +31 -2
- runbooks/__init___optimized.py +18 -4
- runbooks/_platform/__init__.py +1 -5
- runbooks/_platform/core/runbooks_wrapper.py +141 -138
- runbooks/aws2/accuracy_validator.py +812 -0
- runbooks/base.py +7 -0
- runbooks/cfat/assessment/compliance.py +1 -1
- runbooks/cfat/assessment/runner.py +1 -0
- runbooks/cfat/cloud_foundations_assessment.py +227 -239
- runbooks/cli/__init__.py +1 -1
- runbooks/cli/commands/cfat.py +64 -23
- runbooks/cli/commands/finops.py +1005 -54
- runbooks/cli/commands/inventory.py +135 -91
- runbooks/cli/commands/operate.py +9 -36
- runbooks/cli/commands/security.py +42 -18
- runbooks/cli/commands/validation.py +432 -18
- runbooks/cli/commands/vpc.py +81 -17
- runbooks/cli/registry.py +22 -10
- runbooks/cloudops/__init__.py +20 -27
- runbooks/cloudops/base.py +96 -107
- runbooks/cloudops/cost_optimizer.py +544 -542
- runbooks/cloudops/infrastructure_optimizer.py +5 -4
- runbooks/cloudops/interfaces.py +224 -225
- runbooks/cloudops/lifecycle_manager.py +5 -4
- runbooks/cloudops/mcp_cost_validation.py +252 -235
- runbooks/cloudops/models.py +78 -53
- runbooks/cloudops/monitoring_automation.py +5 -4
- runbooks/cloudops/notebook_framework.py +177 -213
- runbooks/cloudops/security_enforcer.py +125 -159
- runbooks/common/accuracy_validator.py +17 -12
- runbooks/common/aws_pricing.py +349 -326
- runbooks/common/aws_pricing_api.py +211 -212
- runbooks/common/aws_profile_manager.py +40 -36
- runbooks/common/aws_utils.py +74 -79
- runbooks/common/business_logic.py +126 -104
- runbooks/common/cli_decorators.py +36 -60
- runbooks/common/comprehensive_cost_explorer_integration.py +455 -463
- runbooks/common/cross_account_manager.py +197 -204
- runbooks/common/date_utils.py +27 -39
- runbooks/common/decorators.py +29 -19
- runbooks/common/dry_run_examples.py +173 -208
- runbooks/common/dry_run_framework.py +157 -155
- runbooks/common/enhanced_exception_handler.py +15 -4
- runbooks/common/enhanced_logging_example.py +50 -64
- runbooks/common/enhanced_logging_integration_example.py +65 -37
- runbooks/common/env_utils.py +16 -16
- runbooks/common/error_handling.py +40 -38
- runbooks/common/lazy_loader.py +41 -23
- runbooks/common/logging_integration_helper.py +79 -86
- runbooks/common/mcp_cost_explorer_integration.py +476 -493
- runbooks/common/mcp_integration.py +99 -79
- runbooks/common/memory_optimization.py +140 -118
- runbooks/common/module_cli_base.py +37 -58
- runbooks/common/organizations_client.py +175 -193
- runbooks/common/patterns.py +23 -25
- runbooks/common/performance_monitoring.py +67 -71
- runbooks/common/performance_optimization_engine.py +283 -274
- runbooks/common/profile_utils.py +111 -37
- runbooks/common/rich_utils.py +315 -141
- runbooks/common/sre_performance_suite.py +177 -186
- runbooks/enterprise/__init__.py +1 -1
- runbooks/enterprise/logging.py +144 -106
- runbooks/enterprise/security.py +187 -204
- runbooks/enterprise/validation.py +43 -56
- runbooks/finops/__init__.py +26 -30
- runbooks/finops/account_resolver.py +1 -1
- runbooks/finops/advanced_optimization_engine.py +980 -0
- runbooks/finops/automation_core.py +268 -231
- runbooks/finops/business_case_config.py +184 -179
- runbooks/finops/cli.py +660 -139
- runbooks/finops/commvault_ec2_analysis.py +157 -164
- runbooks/finops/compute_cost_optimizer.py +336 -320
- runbooks/finops/config.py +20 -20
- runbooks/finops/cost_optimizer.py +484 -618
- runbooks/finops/cost_processor.py +332 -214
- runbooks/finops/dashboard_runner.py +1006 -172
- runbooks/finops/ebs_cost_optimizer.py +991 -657
- runbooks/finops/elastic_ip_optimizer.py +317 -257
- runbooks/finops/enhanced_mcp_integration.py +340 -0
- runbooks/finops/enhanced_progress.py +32 -29
- runbooks/finops/enhanced_trend_visualization.py +3 -2
- runbooks/finops/enterprise_wrappers.py +223 -285
- runbooks/finops/executive_export.py +203 -160
- runbooks/finops/helpers.py +130 -288
- runbooks/finops/iam_guidance.py +1 -1
- runbooks/finops/infrastructure/__init__.py +80 -0
- runbooks/finops/infrastructure/commands.py +506 -0
- runbooks/finops/infrastructure/load_balancer_optimizer.py +866 -0
- runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +832 -0
- runbooks/finops/markdown_exporter.py +337 -174
- runbooks/finops/mcp_validator.py +1952 -0
- runbooks/finops/nat_gateway_optimizer.py +1512 -481
- runbooks/finops/network_cost_optimizer.py +657 -587
- runbooks/finops/notebook_utils.py +226 -188
- runbooks/finops/optimization_engine.py +1136 -0
- runbooks/finops/optimizer.py +19 -23
- runbooks/finops/rds_snapshot_optimizer.py +367 -411
- runbooks/finops/reservation_optimizer.py +427 -363
- runbooks/finops/scenario_cli_integration.py +64 -65
- runbooks/finops/scenarios.py +1277 -438
- runbooks/finops/schemas.py +218 -182
- runbooks/finops/snapshot_manager.py +2289 -0
- runbooks/finops/types.py +3 -3
- runbooks/finops/validation_framework.py +259 -265
- runbooks/finops/vpc_cleanup_exporter.py +189 -144
- runbooks/finops/vpc_cleanup_optimizer.py +591 -573
- runbooks/finops/workspaces_analyzer.py +171 -182
- runbooks/integration/__init__.py +89 -0
- runbooks/integration/mcp_integration.py +1920 -0
- runbooks/inventory/CLAUDE.md +816 -0
- runbooks/inventory/__init__.py +2 -2
- runbooks/inventory/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/cloud_foundations_integration.py +144 -149
- runbooks/inventory/collectors/aws_comprehensive.py +1 -1
- runbooks/inventory/collectors/aws_networking.py +109 -99
- runbooks/inventory/collectors/base.py +4 -0
- runbooks/inventory/core/collector.py +495 -313
- runbooks/inventory/core/formatter.py +11 -0
- runbooks/inventory/draw_org_structure.py +8 -9
- runbooks/inventory/drift_detection_cli.py +69 -96
- 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_mcp_cli.py +48 -46
- 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_rds_snapshots_aggregator.py +192 -208
- 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 +554 -468
- runbooks/inventory/mcp_vpc_validator.py +359 -442
- runbooks/inventory/organizations_discovery.py +63 -55
- runbooks/inventory/recover_cfn_stack_ids.py +7 -8
- runbooks/inventory/requirements.txt +0 -1
- runbooks/inventory/rich_inventory_display.py +35 -34
- runbooks/inventory/run_on_multi_accounts.py +3 -5
- runbooks/inventory/unified_validation_engine.py +281 -253
- runbooks/inventory/verify_ec2_security_groups.py +1 -1
- runbooks/inventory/vpc_analyzer.py +735 -697
- runbooks/inventory/vpc_architecture_validator.py +293 -348
- runbooks/inventory/vpc_dependency_analyzer.py +384 -380
- runbooks/inventory/vpc_flow_analyzer.py +1 -1
- runbooks/main.py +49 -34
- runbooks/main_final.py +91 -60
- runbooks/main_minimal.py +22 -10
- runbooks/main_optimized.py +131 -100
- runbooks/main_ultra_minimal.py +7 -2
- runbooks/mcp/__init__.py +36 -0
- runbooks/mcp/integration.py +679 -0
- runbooks/monitoring/performance_monitor.py +9 -4
- runbooks/operate/dynamodb_operations.py +3 -1
- runbooks/operate/ec2_operations.py +145 -137
- runbooks/operate/iam_operations.py +146 -152
- runbooks/operate/networking_cost_heatmap.py +29 -8
- runbooks/operate/rds_operations.py +223 -254
- runbooks/operate/s3_operations.py +107 -118
- runbooks/operate/vpc_operations.py +646 -616
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/commons.py +10 -7
- runbooks/remediation/commvault_ec2_analysis.py +70 -66
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -0
- runbooks/remediation/multi_account.py +24 -21
- runbooks/remediation/rds_snapshot_list.py +86 -60
- runbooks/remediation/remediation_cli.py +92 -146
- runbooks/remediation/universal_account_discovery.py +83 -79
- runbooks/remediation/workspaces_list.py +46 -41
- runbooks/security/__init__.py +19 -0
- runbooks/security/assessment_runner.py +1150 -0
- runbooks/security/baseline_checker.py +812 -0
- runbooks/security/cloudops_automation_security_validator.py +509 -535
- runbooks/security/compliance_automation_engine.py +17 -17
- runbooks/security/config/__init__.py +2 -2
- runbooks/security/config/compliance_config.py +50 -50
- runbooks/security/config_template_generator.py +63 -76
- runbooks/security/enterprise_security_framework.py +1 -1
- runbooks/security/executive_security_dashboard.py +519 -508
- runbooks/security/multi_account_security_controls.py +959 -1210
- runbooks/security/real_time_security_monitor.py +422 -444
- runbooks/security/security_baseline_tester.py +1 -1
- runbooks/security/security_cli.py +143 -112
- runbooks/security/test_2way_validation.py +439 -0
- runbooks/security/two_way_validation_framework.py +852 -0
- runbooks/sre/production_monitoring_framework.py +167 -177
- runbooks/tdd/__init__.py +15 -0
- runbooks/tdd/cli.py +1071 -0
- runbooks/utils/__init__.py +14 -17
- runbooks/utils/logger.py +7 -2
- runbooks/utils/version_validator.py +50 -47
- runbooks/validation/__init__.py +6 -6
- runbooks/validation/cli.py +9 -3
- runbooks/validation/comprehensive_2way_validator.py +745 -704
- runbooks/validation/mcp_validator.py +906 -228
- runbooks/validation/terraform_citations_validator.py +104 -115
- runbooks/validation/terraform_drift_detector.py +461 -454
- runbooks/vpc/README.md +617 -0
- runbooks/vpc/__init__.py +8 -1
- runbooks/vpc/analyzer.py +577 -0
- runbooks/vpc/cleanup_wrapper.py +476 -413
- runbooks/vpc/cli_cloudtrail_commands.py +339 -0
- runbooks/vpc/cli_mcp_validation_commands.py +480 -0
- runbooks/vpc/cloudtrail_audit_integration.py +717 -0
- runbooks/vpc/config.py +92 -97
- runbooks/vpc/cost_engine.py +411 -148
- runbooks/vpc/cost_explorer_integration.py +553 -0
- runbooks/vpc/cross_account_session.py +101 -106
- runbooks/vpc/enhanced_mcp_validation.py +917 -0
- runbooks/vpc/eni_gate_validator.py +961 -0
- runbooks/vpc/heatmap_engine.py +185 -160
- runbooks/vpc/mcp_no_eni_validator.py +680 -639
- runbooks/vpc/nat_gateway_optimizer.py +358 -0
- runbooks/vpc/networking_wrapper.py +15 -8
- runbooks/vpc/pdca_remediation_planner.py +528 -0
- runbooks/vpc/performance_optimized_analyzer.py +219 -231
- runbooks/vpc/runbooks_adapter.py +1167 -241
- runbooks/vpc/tdd_red_phase_stubs.py +601 -0
- runbooks/vpc/test_data_loader.py +358 -0
- runbooks/vpc/tests/conftest.py +314 -4
- runbooks/vpc/tests/test_cleanup_framework.py +1022 -0
- runbooks/vpc/tests/test_cost_engine.py +0 -2
- runbooks/vpc/topology_generator.py +326 -0
- runbooks/vpc/unified_scenarios.py +1297 -1124
- runbooks/vpc/vpc_cleanup_integration.py +1943 -1115
- runbooks-1.1.6.dist-info/METADATA +327 -0
- runbooks-1.1.6.dist-info/RECORD +489 -0
- runbooks/finops/README.md +0 -414
- runbooks/finops/accuracy_cross_validator.py +0 -647
- runbooks/finops/business_cases.py +0 -950
- runbooks/finops/dashboard_router.py +0 -922
- runbooks/finops/ebs_optimizer.py +0 -973
- runbooks/finops/embedded_mcp_validator.py +0 -1629
- runbooks/finops/enhanced_dashboard_runner.py +0 -527
- runbooks/finops/finops_dashboard.py +0 -584
- runbooks/finops/finops_scenarios.py +0 -1218
- runbooks/finops/legacy_migration.py +0 -730
- runbooks/finops/multi_dashboard.py +0 -1519
- runbooks/finops/single_dashboard.py +0 -1113
- runbooks/finops/unlimited_scenarios.py +0 -393
- runbooks-1.1.4.dist-info/METADATA +0 -800
- runbooks-1.1.4.dist-info/RECORD +0 -468
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/WHEEL +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/entry_points.txt +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/licenses/LICENSE +0 -0
- {runbooks-1.1.4.dist-info → runbooks-1.1.6.dist-info}/top_level.txt +0 -0
@@ -203,6 +203,17 @@ class InventoryFormatter(CloudFoundationsFormatter):
|
|
203
203
|
|
204
204
|
def format_console_table(self) -> str:
|
205
205
|
"""Format inventory data for console display."""
|
206
|
+
import os
|
207
|
+
import sys
|
208
|
+
import re
|
209
|
+
|
210
|
+
# Test Mode Support: Disable Rich Console in test environments to prevent I/O conflicts
|
211
|
+
USE_RICH = os.getenv("RUNBOOKS_TEST_MODE") != "1"
|
212
|
+
|
213
|
+
if not USE_RICH:
|
214
|
+
# Test mode: Use simple text formatting to prevent Click CliRunner I/O conflicts
|
215
|
+
return self._format_simple_text_table()
|
216
|
+
|
206
217
|
try:
|
207
218
|
from rich.console import Console
|
208
219
|
from rich.table import Table
|
@@ -58,7 +58,7 @@ from typing import Any, Dict, List, Optional
|
|
58
58
|
|
59
59
|
import boto3
|
60
60
|
from ArgumentsClass import CommonArguments
|
61
|
-
from
|
61
|
+
from runbooks.common.rich_utils import console
|
62
62
|
from graphviz import Digraph
|
63
63
|
|
64
64
|
# Optional imports for enhanced features
|
@@ -74,7 +74,6 @@ except ImportError:
|
|
74
74
|
|
75
75
|
__version__ = "2025.04.09"
|
76
76
|
|
77
|
-
init()
|
78
77
|
|
79
78
|
# Visual styling constants
|
80
79
|
account_fillcolor = "orange"
|
@@ -379,7 +378,7 @@ def generate_mermaid(org_structure: Any, filename: str) -> None:
|
|
379
378
|
try:
|
380
379
|
with open(filename, "w", encoding="utf-8") as f:
|
381
380
|
f.write("\n".join(lines))
|
382
|
-
print(f"Mermaid diagram successfully saved to '{
|
381
|
+
print(f"Mermaid diagram successfully saved to '[red]{filename}'")
|
383
382
|
logging.info(f"Mermaid diagram successfully saved to {filename}")
|
384
383
|
except Exception as e:
|
385
384
|
logging.error(f"Failed to write Mermaid diagram to {filename}: {e}")
|
@@ -412,7 +411,7 @@ def generate_diagrams(org_structure: Any, filename: str) -> None:
|
|
412
411
|
)
|
413
412
|
except ImportError as imp_err:
|
414
413
|
logging.error("Please install the 'diagrams' package to use generate_diagrams: pip install diagrams")
|
415
|
-
print(f"
|
414
|
+
print(f"[yellow]Warning: diagrams library not available. Install with: pip install diagrams")
|
416
415
|
return
|
417
416
|
|
418
417
|
def build_diagram(node: Dict[str, Any]):
|
@@ -434,7 +433,7 @@ def generate_diagrams(org_structure: Any, filename: str) -> None:
|
|
434
433
|
try:
|
435
434
|
with Diagram("AWS Organization Diagram", filename=filename, show=False, direction="LR"):
|
436
435
|
build_diagram(org_structure)
|
437
|
-
print(f"Diagrams image successfully generated as '{
|
436
|
+
print(f"Diagrams image successfully generated as '[red]{filename}'")
|
438
437
|
logging.info(f"Diagrams image successfully generated as {filename}")
|
439
438
|
except Exception as e:
|
440
439
|
logging.error(f"Failed to generate diagrams image: {e}")
|
@@ -636,7 +635,7 @@ def draw_org(froot: str, filename: str):
|
|
636
635
|
|
637
636
|
# Save the diagram to a PNG file
|
638
637
|
dot_unflat.render(filename, format="png", view=False)
|
639
|
-
print(f"Diagram saved to '{
|
638
|
+
print(f"Diagram saved to '[red]{filename}.png'")
|
640
639
|
|
641
640
|
|
642
641
|
#####################
|
@@ -708,7 +707,7 @@ if __name__ == "__main__":
|
|
708
707
|
if anticipated_time > 30:
|
709
708
|
print()
|
710
709
|
print(
|
711
|
-
f"
|
710
|
+
f"[red]Since this may take a while, you could re-run this script for only a specific OU by using the '--ou <OU ID>' parameter "
|
712
711
|
)
|
713
712
|
print()
|
714
713
|
else:
|
@@ -737,11 +736,11 @@ if __name__ == "__main__":
|
|
737
736
|
# Display timing information
|
738
737
|
if pTiming and pPolicy:
|
739
738
|
print(
|
740
|
-
f"
|
739
|
+
f"[green]Drawing the Org structure when policies are included took {time() - begin_time:.2f} seconds"
|
741
740
|
)
|
742
741
|
elif pTiming:
|
743
742
|
print(
|
744
|
-
f"
|
743
|
+
f"[green]Drawing the Org structure without policies took {time() - begin_time:.2f} seconds"
|
745
744
|
)
|
746
745
|
|
747
746
|
print("Thank you for using this script")
|
@@ -11,7 +11,7 @@ Strategic Alignment:
|
|
11
11
|
|
12
12
|
Features:
|
13
13
|
- Real-time AWS API cross-validation
|
14
|
-
- Terraform state drift detection
|
14
|
+
- Terraform state drift detection
|
15
15
|
- Rich CLI enterprise UX with visual indicators
|
16
16
|
- Evidence-based drift reporting
|
17
17
|
- Profile override priority system
|
@@ -34,7 +34,7 @@ from ..common.rich_utils import console, print_error, print_info, print_success
|
|
34
34
|
from .mcp_inventory_validator import (
|
35
35
|
create_inventory_mcp_validator,
|
36
36
|
generate_drift_report,
|
37
|
-
validate_inventory_results_with_mcp
|
37
|
+
validate_inventory_results_with_mcp,
|
38
38
|
)
|
39
39
|
|
40
40
|
|
@@ -45,46 +45,32 @@ def drift():
|
|
45
45
|
|
46
46
|
|
47
47
|
@drift.command("detect")
|
48
|
-
@click.option(
|
49
|
-
|
50
|
-
help="AWS profile to use (overrides environment variables)"
|
51
|
-
)
|
52
|
-
@click.option(
|
53
|
-
"--profiles",
|
54
|
-
help="Comma-separated list of AWS profiles for multi-account analysis"
|
55
|
-
)
|
48
|
+
@click.option("--profile", help="AWS profile to use (overrides environment variables)")
|
49
|
+
@click.option("--profiles", help="Comma-separated list of AWS profiles for multi-account analysis")
|
56
50
|
@click.option(
|
57
51
|
"--terraform-dir",
|
58
52
|
default="/Volumes/Working/1xOps/CloudOps-Runbooks/terraform-aws",
|
59
|
-
help="Path to terraform configuration directory"
|
53
|
+
help="Path to terraform configuration directory",
|
60
54
|
)
|
61
55
|
@click.option(
|
62
56
|
"--report-format",
|
63
57
|
type=click.Choice(["console", "json", "csv"]),
|
64
58
|
default="console",
|
65
|
-
help="Output format for drift report"
|
66
|
-
)
|
67
|
-
@click.option(
|
68
|
-
"--output-file",
|
69
|
-
help="File path to save drift report (optional)"
|
70
|
-
)
|
71
|
-
@click.option(
|
72
|
-
"--threshold",
|
73
|
-
type=float,
|
74
|
-
default=99.5,
|
75
|
-
help="Accuracy threshold for drift detection (default: 99.5%)"
|
59
|
+
help="Output format for drift report",
|
76
60
|
)
|
61
|
+
@click.option("--output-file", help="File path to save drift report (optional)")
|
62
|
+
@click.option("--threshold", type=float, default=99.5, help="Accuracy threshold for drift detection (default: 99.5%)")
|
77
63
|
def detect_drift(
|
78
64
|
profile: Optional[str],
|
79
|
-
profiles: Optional[str],
|
65
|
+
profiles: Optional[str],
|
80
66
|
terraform_dir: str,
|
81
67
|
report_format: str,
|
82
68
|
output_file: Optional[str],
|
83
|
-
threshold: float
|
69
|
+
threshold: float,
|
84
70
|
):
|
85
71
|
"""
|
86
72
|
Detect infrastructure drift using 3-way validation.
|
87
|
-
|
73
|
+
|
88
74
|
Compares inventory collection results against AWS API and terraform state
|
89
75
|
to identify discrepancies and provide actionable recommendations.
|
90
76
|
"""
|
@@ -98,9 +84,9 @@ def detect_drift(
|
|
98
84
|
# Use operational profile as default
|
99
85
|
default_profile = get_profile_for_operation("operational", None)
|
100
86
|
profile_list = [default_profile]
|
101
|
-
|
87
|
+
|
102
88
|
print_info(f"Starting drift detection for {len(profile_list)} profile(s)")
|
103
|
-
|
89
|
+
|
104
90
|
# Validate all profiles
|
105
91
|
valid_profiles = []
|
106
92
|
for prof in profile_list:
|
@@ -108,99 +94,83 @@ def detect_drift(
|
|
108
94
|
valid_profiles.append(prof)
|
109
95
|
else:
|
110
96
|
print_error(f"Profile '{prof}' validation failed - skipping")
|
111
|
-
|
97
|
+
|
112
98
|
if not valid_profiles:
|
113
99
|
print_error("No valid profiles available for drift detection")
|
114
100
|
return
|
115
|
-
|
101
|
+
|
116
102
|
# Create validator with terraform integration
|
117
|
-
validator = create_inventory_mcp_validator(
|
118
|
-
|
119
|
-
terraform_directory=terraform_dir
|
120
|
-
)
|
121
|
-
|
103
|
+
validator = create_inventory_mcp_validator(profiles=valid_profiles, terraform_directory=terraform_dir)
|
104
|
+
|
122
105
|
# Set custom threshold if provided
|
123
106
|
validator.validation_threshold = threshold
|
124
|
-
|
107
|
+
|
125
108
|
console.print(f"[blue]🔍 Initializing drift detection...[/]")
|
126
109
|
console.print(f"[dim]Terraform directory: {terraform_dir}[/]")
|
127
110
|
console.print(f"[dim]Accuracy threshold: {threshold}%[/]")
|
128
|
-
|
111
|
+
|
129
112
|
# For demonstration, create mock inventory data
|
130
113
|
# In practice, this would come from actual inventory collection
|
131
114
|
mock_inventory = _create_mock_inventory_data(valid_profiles)
|
132
|
-
|
115
|
+
|
133
116
|
# Perform enhanced validation with drift detection
|
134
117
|
validation_results = validator.validate_inventory_data(mock_inventory)
|
135
|
-
|
118
|
+
|
136
119
|
# Generate summary results
|
137
120
|
overall_accuracy = validation_results.get("total_accuracy", 0)
|
138
121
|
terraform_integration = validation_results.get("terraform_integration", {})
|
139
|
-
|
122
|
+
|
140
123
|
if validation_results.get("passed_validation", False):
|
141
124
|
print_success(f"✅ Drift detection completed: {overall_accuracy:.1f}% accuracy")
|
142
125
|
else:
|
143
126
|
console.print(f"[yellow]🔄 Infrastructure drift detected: {overall_accuracy:.1f}% accuracy[/]")
|
144
|
-
|
127
|
+
|
145
128
|
# Display terraform integration status
|
146
129
|
if terraform_integration.get("enabled", False):
|
147
130
|
tf_files = terraform_integration.get("state_files_discovered", 0)
|
148
131
|
print_info(f"Terraform integration: {tf_files} configuration files analyzed")
|
149
|
-
|
132
|
+
|
150
133
|
drift_analysis = terraform_integration.get("drift_analysis", {})
|
151
134
|
if drift_analysis:
|
152
135
|
drift_pct = drift_analysis.get("drift_percentage", 0)
|
153
136
|
tf_coverage = drift_analysis.get("terraform_coverage_percentage", 0)
|
154
137
|
console.print(f"[dim]📊 {drift_pct:.1f}% of accounts have drift detected[/]")
|
155
138
|
console.print(f"[dim]🎯 {tf_coverage:.1f}% of accounts have terraform coverage[/]")
|
156
|
-
|
139
|
+
|
157
140
|
# Generate and export report if requested
|
158
141
|
if output_file or report_format != "console":
|
159
142
|
drift_report = generate_drift_report(valid_profiles, mock_inventory, terraform_dir)
|
160
|
-
|
143
|
+
|
161
144
|
if output_file:
|
162
145
|
_export_drift_report(drift_report, output_file, report_format)
|
163
146
|
print_success(f"Drift report exported to: {output_file}")
|
164
|
-
|
147
|
+
|
165
148
|
print_info("Drift detection analysis complete")
|
166
|
-
|
149
|
+
|
167
150
|
except Exception as e:
|
168
151
|
print_error(f"Drift detection failed: {str(e)}")
|
169
152
|
raise click.Abort()
|
170
153
|
|
171
154
|
|
172
155
|
@drift.command("report")
|
173
|
-
@click.option(
|
174
|
-
"--profile",
|
175
|
-
help="AWS profile to use for single-account analysis"
|
176
|
-
)
|
156
|
+
@click.option("--profile", help="AWS profile to use for single-account analysis")
|
177
157
|
@click.option(
|
178
158
|
"--terraform-dir",
|
179
159
|
default="/Volumes/Working/1xOps/CloudOps-Runbooks/terraform-aws",
|
180
|
-
help="Path to terraform configuration directory"
|
160
|
+
help="Path to terraform configuration directory",
|
181
161
|
)
|
182
162
|
@click.option(
|
183
163
|
"--format",
|
184
164
|
"report_format",
|
185
165
|
type=click.Choice(["json", "csv", "markdown"]),
|
186
166
|
default="json",
|
187
|
-
help="Report output format"
|
167
|
+
help="Report output format",
|
188
168
|
)
|
189
|
-
@click.option(
|
190
|
-
|
191
|
-
"output_file",
|
192
|
-
required=True,
|
193
|
-
help="Output file path for drift report"
|
194
|
-
)
|
195
|
-
def generate_report(
|
196
|
-
profile: Optional[str],
|
197
|
-
terraform_dir: str,
|
198
|
-
report_format: str,
|
199
|
-
output_file: str
|
200
|
-
):
|
169
|
+
@click.option("--output", "output_file", required=True, help="Output file path for drift report")
|
170
|
+
def generate_report(profile: Optional[str], terraform_dir: str, report_format: str, output_file: str):
|
201
171
|
"""
|
202
172
|
Generate comprehensive infrastructure drift report.
|
203
|
-
|
173
|
+
|
204
174
|
Creates detailed drift analysis report with actionable recommendations
|
205
175
|
for infrastructure as code management and compliance.
|
206
176
|
"""
|
@@ -208,35 +178,35 @@ def generate_report(
|
|
208
178
|
# Use default profile if none specified
|
209
179
|
if not profile:
|
210
180
|
profile = get_profile_for_operation("operational", None)
|
211
|
-
|
181
|
+
|
212
182
|
print_info(f"Generating drift report for profile: {profile}")
|
213
|
-
|
183
|
+
|
214
184
|
# Validate profile
|
215
185
|
if not validate_profile_access(profile, "drift-reporting"):
|
216
186
|
print_error(f"Profile '{profile}' validation failed")
|
217
187
|
return
|
218
|
-
|
188
|
+
|
219
189
|
# Create mock inventory for demonstration
|
220
190
|
mock_inventory = _create_mock_inventory_data([profile])
|
221
|
-
|
191
|
+
|
222
192
|
# Generate comprehensive drift report
|
223
193
|
drift_report = generate_drift_report([profile], mock_inventory, terraform_dir)
|
224
|
-
|
194
|
+
|
225
195
|
# Export report
|
226
196
|
_export_drift_report(drift_report, output_file, report_format)
|
227
|
-
|
197
|
+
|
228
198
|
print_success(f"Drift report generated: {output_file}")
|
229
|
-
|
199
|
+
|
230
200
|
# Display summary
|
231
201
|
accounts_analyzed = drift_report.get("accounts_analyzed", 0)
|
232
202
|
overall_accuracy = drift_report.get("overall_accuracy", 0)
|
233
203
|
drift_detected = drift_report.get("drift_detected", False)
|
234
|
-
|
204
|
+
|
235
205
|
console.print(f"[dim]📊 Analysis Summary:[/]")
|
236
206
|
console.print(f"[dim] Accounts analyzed: {accounts_analyzed}[/]")
|
237
207
|
console.print(f"[dim] Overall accuracy: {overall_accuracy:.1f}%[/]")
|
238
208
|
console.print(f"[dim] Drift detected: {'Yes' if drift_detected else 'No'}[/]")
|
239
|
-
|
209
|
+
|
240
210
|
except Exception as e:
|
241
211
|
print_error(f"Report generation failed: {str(e)}")
|
242
212
|
raise click.Abort()
|
@@ -245,7 +215,7 @@ def generate_report(
|
|
245
215
|
def _create_mock_inventory_data(profiles: List[str]) -> dict:
|
246
216
|
"""Create mock inventory data for demonstration purposes."""
|
247
217
|
mock_data = {}
|
248
|
-
|
218
|
+
|
249
219
|
for profile in profiles:
|
250
220
|
mock_data[profile] = {
|
251
221
|
"resource_counts": {
|
@@ -258,12 +228,12 @@ def _create_mock_inventory_data(profiles: List[str]) -> dict:
|
|
258
228
|
"cloudformation": 4,
|
259
229
|
"elbv2": 1,
|
260
230
|
"route53": 2,
|
261
|
-
"sns": 3
|
231
|
+
"sns": 3,
|
262
232
|
},
|
263
233
|
"regions": ["us-east-1", "us-west-2", "ap-southeast-2"],
|
264
|
-
"collection_timestamp": "2024-09-10T12:00:00Z"
|
234
|
+
"collection_timestamp": "2024-09-10T12:00:00Z",
|
265
235
|
}
|
266
|
-
|
236
|
+
|
267
237
|
return mock_data
|
268
238
|
|
269
239
|
|
@@ -271,50 +241,53 @@ def _export_drift_report(report_data: dict, output_file: str, format_type: str)
|
|
271
241
|
"""Export drift report to specified format."""
|
272
242
|
import json
|
273
243
|
from pathlib import Path
|
274
|
-
|
244
|
+
|
275
245
|
output_path = Path(output_file)
|
276
246
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
277
|
-
|
247
|
+
|
278
248
|
if format_type == "json":
|
279
|
-
with open(output_path,
|
249
|
+
with open(output_path, "w") as f:
|
280
250
|
json.dump(report_data, f, indent=2, default=str)
|
281
251
|
elif format_type == "csv":
|
282
252
|
# Create CSV summary
|
283
253
|
import csv
|
284
|
-
|
254
|
+
|
255
|
+
with open(output_path, "w", newline="") as f:
|
285
256
|
writer = csv.writer(f)
|
286
|
-
writer.writerow([
|
287
|
-
|
257
|
+
writer.writerow(["Account ID", "Profile", "Accuracy %", "Drift Detected", "Terraform Coverage"])
|
258
|
+
|
288
259
|
for account in report_data.get("detailed_analysis", []):
|
289
|
-
writer.writerow(
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
260
|
+
writer.writerow(
|
261
|
+
[
|
262
|
+
account.get("account_id", "Unknown"),
|
263
|
+
account.get("profile", "Unknown"),
|
264
|
+
f"{account.get('accuracy_percent', 0):.1f}",
|
265
|
+
"Yes" if account.get("drift_summary", {}).get("drift_detected", 0) > 0 else "No",
|
266
|
+
"Yes" if account.get("terraform_coverage", False) else "No",
|
267
|
+
]
|
268
|
+
)
|
296
269
|
elif format_type == "markdown":
|
297
270
|
# Create markdown report
|
298
|
-
with open(output_path,
|
271
|
+
with open(output_path, "w") as f:
|
299
272
|
f.write("# Infrastructure Drift Analysis Report\n\n")
|
300
273
|
f.write(f"**Generated:** {report_data.get('generated_timestamp', 'Unknown')}\n\n")
|
301
|
-
|
274
|
+
|
302
275
|
terraform_info = report_data.get("terraform_integration", {})
|
303
276
|
f.write(f"**Terraform Integration:** {terraform_info.get('enabled', False)}\n")
|
304
277
|
f.write(f"**State Files Discovered:** {terraform_info.get('state_files_discovered', 0)}\n\n")
|
305
|
-
|
278
|
+
|
306
279
|
f.write("## Summary\n\n")
|
307
280
|
f.write(f"- **Accounts Analyzed:** {report_data.get('accounts_analyzed', 0)}\n")
|
308
281
|
f.write(f"- **Overall Accuracy:** {report_data.get('overall_accuracy', 0):.1f}%\n")
|
309
282
|
f.write(f"- **Drift Detected:** {'Yes' if report_data.get('drift_detected', False) else 'No'}\n\n")
|
310
|
-
|
283
|
+
|
311
284
|
f.write("## Detailed Analysis\n\n")
|
312
285
|
for account in report_data.get("detailed_analysis", []):
|
313
286
|
f.write(f"### Account: {account.get('account_id', 'Unknown')}\n\n")
|
314
287
|
f.write(f"- **Profile:** {account.get('profile', 'Unknown')}\n")
|
315
288
|
f.write(f"- **Accuracy:** {account.get('accuracy_percent', 0):.1f}%\n")
|
316
289
|
f.write(f"- **Terraform Coverage:** {'Yes' if account.get('terraform_coverage', False) else 'No'}\n\n")
|
317
|
-
|
290
|
+
|
318
291
|
recommendations = account.get("recommendations", [])
|
319
292
|
if recommendations:
|
320
293
|
f.write("**Recommendations:**\n")
|
@@ -324,4 +297,4 @@ def _export_drift_report(report_data: dict, output_file: str, format_type: str)
|
|
324
297
|
|
325
298
|
|
326
299
|
if __name__ == "__main__":
|
327
|
-
drift()
|
300
|
+
drift()
|
@@ -14,7 +14,7 @@ def del_vpc(ocredentials, fVPCId, fRegion):
|
|
14
14
|
|
15
15
|
import boto3
|
16
16
|
from botocore.exceptions import ClientError
|
17
|
-
from
|
17
|
+
from runbooks.common.rich_utils import console
|
18
18
|
|
19
19
|
# ERASE_LINE = '\x1b[2K'
|
20
20
|
|
@@ -335,7 +335,7 @@ def del_vpc(ocredentials, fVPCId, fRegion):
|
|
335
335
|
return 1 # out of the try
|
336
336
|
except ClientError as my_Error:
|
337
337
|
print(my_Error)
|
338
|
-
print(f"
|
338
|
+
print(f"[red]What to do now?")
|
339
339
|
return 1
|
340
340
|
|
341
341
|
return 0
|
@@ -70,9 +70,8 @@ import Inventory_Modules
|
|
70
70
|
from account_class import aws_acct_access
|
71
71
|
from ArgumentsClass import CommonArguments
|
72
72
|
from botocore.exceptions import ClientError
|
73
|
-
from
|
73
|
+
from runbooks.common.rich_utils import console
|
74
74
|
|
75
|
-
init()
|
76
75
|
__version__ = "2023.05.04"
|
77
76
|
|
78
77
|
# Configure comprehensive argument parsing for CloudFormation drift detection operations
|
@@ -142,7 +141,6 @@ This would enable:
|
|
142
141
|
"""
|
143
142
|
|
144
143
|
##########################
|
145
|
-
ERASE_LINE = "\x1b[2K" # Terminal control for dynamic output updates
|
146
144
|
|
147
145
|
# Initialize AWS account access and organizational context for drift detection
|
148
146
|
aws_acct = aws_acct_access(pProfile)
|
@@ -235,7 +233,7 @@ for account in ChildAccounts:
|
|
235
233
|
# Log regional stack discovery progress for operational visibility
|
236
234
|
logging.warning(f"Account: {account['AccountId']} | Region: {region} | Found {StackNum} Stacks")
|
237
235
|
logging.info(
|
238
|
-
f"
|
236
|
+
f"[red]Account: {account['AccountId']} Region: {region} Found {StackNum} Stacks"
|
239
237
|
)
|
240
238
|
|
241
239
|
except ClientError as my_Error:
|
@@ -260,10 +258,10 @@ for account in ChildAccounts:
|
|
260
258
|
NumStacksFound += 1 # Increment total drift detection counter
|
261
259
|
|
262
260
|
# Provide comprehensive operational summary with drift detection metrics
|
263
|
-
print(
|
261
|
+
console.print()
|
264
262
|
print(
|
265
|
-
f"
|
266
|
-
f"{len(RegionList)} regions
|
263
|
+
f"[red]Looked through {NumStacksFound} Stacks across {len(ChildAccounts)} accounts across "
|
264
|
+
f"{len(RegionList)} regions"
|
267
265
|
)
|
268
266
|
print()
|
269
267
|
|
@@ -76,7 +76,7 @@ from ArgumentsClass import CommonArguments
|
|
76
76
|
|
77
77
|
# import simplejson as json
|
78
78
|
from botocore.exceptions import ClientError
|
79
|
-
from
|
79
|
+
from runbooks.common.rich_utils import console
|
80
80
|
from Inventory_Modules import (
|
81
81
|
display_results,
|
82
82
|
find_stack_instances3,
|
@@ -87,9 +87,7 @@ from Inventory_Modules import (
|
|
87
87
|
print_timings,
|
88
88
|
)
|
89
89
|
|
90
|
-
init()
|
91
90
|
__version__ = "2024.05.18"
|
92
|
-
ERASE_LINE = "\x1b[2K"
|
93
91
|
begin_time = time()
|
94
92
|
|
95
93
|
|
@@ -250,7 +248,7 @@ def setup_auth_and_regions(fProfile: str, f_AccountList: list, f_Region: str, f_
|
|
250
248
|
pass # Valid profile specification (string or None for default)
|
251
249
|
else: # Invalid profile type (list, integer, etc.) - should be caught at argparse level
|
252
250
|
print(
|
253
|
-
f"
|
251
|
+
f"[red]You specified an invalid profile name. This script only allows for one profile at a time. Please try again."
|
254
252
|
)
|
255
253
|
sys.exit(7)
|
256
254
|
|
@@ -268,8 +266,8 @@ def setup_auth_and_regions(fProfile: str, f_AccountList: list, f_Region: str, f_
|
|
268
266
|
# Validate home region specification ensuring single region for StackSet control plane
|
269
267
|
if f_Region.lower() not in AllRegions:
|
270
268
|
print(
|
271
|
-
f"
|
272
|
-
f"Please run the command again and specify only a single, valid region
|
269
|
+
f"[red]You specified '{f_Region}' as the region, but this script only works with a single region.\n"
|
270
|
+
f"Please run the command again and specify only a single, valid region"
|
273
271
|
)
|
274
272
|
sys.exit(9)
|
275
273
|
|
@@ -309,12 +307,12 @@ def setup_auth_and_regions(fProfile: str, f_AccountList: list, f_Region: str, f_
|
|
309
307
|
|
310
308
|
# Display account exclusion list if specified
|
311
309
|
print(
|
312
|
-
f"While skipping these accounts:\n{
|
310
|
+
f"While skipping these accounts:\n[red]{f_args.SkipAccounts}"
|
313
311
|
) if f_args.SkipAccounts is not None else ""
|
314
312
|
|
315
313
|
# Display fragment filtering configuration with exact vs substring matching
|
316
314
|
if f_args.Exact:
|
317
|
-
print(f"\t\tFor stacksets that
|
315
|
+
print(f"\t\tFor stacksets that [red]exactly match: {f_args.Fragments}")
|
318
316
|
else:
|
319
317
|
print(
|
320
318
|
f"\t\tFor stacksets that contain th{'is fragment' if len(f_args.Fragments) == 1 else 'ese fragments'}: {f_args.Fragments}"
|
@@ -712,7 +710,7 @@ if __name__ == "__main__":
|
|
712
710
|
|
713
711
|
if pTiming:
|
714
712
|
print(ERASE_LINE)
|
715
|
-
print(f"
|
713
|
+
print(f"[green]This script took {time() - begin_time:.2f} seconds")
|
716
714
|
|
717
715
|
print()
|
718
716
|
print("Thanks for using this script...")
|
@@ -70,11 +70,10 @@ import Inventory_Modules
|
|
70
70
|
from account_class import aws_acct_access
|
71
71
|
from ArgumentsClass import CommonArguments
|
72
72
|
from botocore.exceptions import ClientError
|
73
|
-
from
|
73
|
+
from runbooks.common.rich_utils import console
|
74
74
|
from Inventory_Modules import display_results
|
75
75
|
|
76
76
|
# Initialize colorama for cross-platform colored terminal output
|
77
|
-
init()
|
78
77
|
__version__ = "2024.03.06"
|
79
78
|
begin_time = time() # Script execution timing for performance monitoring
|
80
79
|
sleep_interval = 5 # Default wait interval for drift detection operations
|
@@ -248,7 +247,7 @@ def setup_auth(fProfile: str) -> aws_acct_access:
|
|
248
247
|
|
249
248
|
# Validate successful authentication and account access
|
250
249
|
if not aws_acct.Success:
|
251
|
-
print(f"
|
250
|
+
print(f"[red]Profile {pProfile} failed to access an account. Check credentials and try again")
|
252
251
|
sys.exit(99)
|
253
252
|
|
254
253
|
# Display comprehensive operational context for user confirmation and audit logging
|
@@ -259,7 +258,7 @@ def setup_auth(fProfile: str) -> aws_acct_access:
|
|
259
258
|
|
260
259
|
# Display fragment filtering criteria with exact vs substring matching context
|
261
260
|
if pExact:
|
262
|
-
print(f"\t\tFor stacksets that
|
261
|
+
print(f"\t\tFor stacksets that [red]exactly match: {pFragments}")
|
263
262
|
else:
|
264
263
|
print(
|
265
264
|
f"\t\tFor stacksets that contain th{'is fragment' if len(pFragments) == 1 else 'ese fragments'}: {pFragments}"
|
@@ -723,11 +722,11 @@ if __name__ == "__main__":
|
|
723
722
|
display_results(sorted_all_stacksets, display_dict_stacksets, None, pSaveFilename)
|
724
723
|
|
725
724
|
print(ERASE_LINE)
|
726
|
-
print(f"
|
725
|
+
print(f"[red]Looked through {len(sorted_all_stacksets)} StackSets across the {pRegion} region")
|
727
726
|
print()
|
728
727
|
|
729
728
|
if pTiming:
|
730
729
|
print(ERASE_LINE)
|
731
|
-
print(f"
|
730
|
+
print(f"[green]This script took {time() - begin_time:.3f} seconds")
|
732
731
|
print("Thanks for using this script...")
|
733
732
|
print()
|