runbooks 1.1.7__py3-none-any.whl → 1.1.10__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 (113) hide show
  1. runbooks/__init__.py +1 -1
  2. runbooks/__init___optimized.py +2 -1
  3. runbooks/_platform/__init__.py +1 -1
  4. runbooks/cfat/cli.py +4 -3
  5. runbooks/cfat/cloud_foundations_assessment.py +1 -2
  6. runbooks/cfat/tests/test_cli.py +4 -1
  7. runbooks/cli/commands/finops.py +68 -19
  8. runbooks/cli/commands/inventory.py +838 -14
  9. runbooks/cli/commands/operate.py +65 -4
  10. runbooks/cli/commands/vpc.py +1 -1
  11. runbooks/cloudops/cost_optimizer.py +1 -3
  12. runbooks/common/cli_decorators.py +6 -4
  13. runbooks/common/config_loader.py +787 -0
  14. runbooks/common/config_schema.py +280 -0
  15. runbooks/common/dry_run_framework.py +14 -2
  16. runbooks/common/mcp_integration.py +238 -0
  17. runbooks/finops/ebs_cost_optimizer.py +7 -4
  18. runbooks/finops/elastic_ip_optimizer.py +7 -4
  19. runbooks/finops/infrastructure/__init__.py +3 -2
  20. runbooks/finops/infrastructure/commands.py +7 -4
  21. runbooks/finops/infrastructure/load_balancer_optimizer.py +7 -4
  22. runbooks/finops/infrastructure/vpc_endpoint_optimizer.py +7 -4
  23. runbooks/finops/nat_gateway_optimizer.py +7 -4
  24. runbooks/finops/tests/run_tests.py +1 -1
  25. runbooks/inventory/ArgumentsClass.py +2 -1
  26. runbooks/inventory/CLAUDE.md +41 -0
  27. runbooks/inventory/README.md +210 -2
  28. runbooks/inventory/Tests/test_Inventory_Modules.py +27 -10
  29. runbooks/inventory/Tests/test_cfn_describe_stacks.py +18 -7
  30. runbooks/inventory/Tests/test_ec2_describe_instances.py +30 -15
  31. runbooks/inventory/Tests/test_lambda_list_functions.py +17 -3
  32. runbooks/inventory/Tests/test_org_list_accounts.py +17 -4
  33. runbooks/inventory/account_class.py +0 -1
  34. runbooks/inventory/all_my_instances_wrapper.py +4 -8
  35. runbooks/inventory/aws_organization.png +0 -0
  36. runbooks/inventory/check_cloudtrail_compliance.py +4 -4
  37. runbooks/inventory/check_controltower_readiness.py +50 -47
  38. runbooks/inventory/check_landingzone_readiness.py +35 -31
  39. runbooks/inventory/cloud_foundations_integration.py +8 -3
  40. runbooks/inventory/collectors/aws_compute.py +59 -11
  41. runbooks/inventory/collectors/aws_management.py +39 -5
  42. runbooks/inventory/core/collector.py +1655 -159
  43. runbooks/inventory/core/concurrent_paginator.py +511 -0
  44. runbooks/inventory/discovery.md +15 -6
  45. runbooks/inventory/{draw_org_structure.py → draw_org.py} +55 -9
  46. runbooks/inventory/drift_detection_cli.py +8 -68
  47. runbooks/inventory/find_cfn_drift_detection.py +14 -4
  48. runbooks/inventory/find_cfn_orphaned_stacks.py +7 -5
  49. runbooks/inventory/find_cfn_stackset_drift.py +5 -5
  50. runbooks/inventory/find_ec2_security_groups.py +6 -3
  51. runbooks/inventory/find_landingzone_versions.py +5 -5
  52. runbooks/inventory/find_vpc_flow_logs.py +5 -5
  53. runbooks/inventory/inventory.sh +20 -7
  54. runbooks/inventory/inventory_mcp_cli.py +4 -0
  55. runbooks/inventory/inventory_modules.py +9 -7
  56. runbooks/inventory/list_cfn_stacks.py +18 -8
  57. runbooks/inventory/list_cfn_stackset_operation_results.py +2 -2
  58. runbooks/inventory/list_cfn_stackset_operations.py +32 -20
  59. runbooks/inventory/list_cfn_stacksets.py +7 -4
  60. runbooks/inventory/list_config_recorders_delivery_channels.py +4 -4
  61. runbooks/inventory/list_ds_directories.py +3 -3
  62. runbooks/inventory/list_ec2_availability_zones.py +7 -3
  63. runbooks/inventory/list_ec2_ebs_volumes.py +3 -3
  64. runbooks/inventory/list_ec2_instances.py +1 -1
  65. runbooks/inventory/list_ecs_clusters_and_tasks.py +8 -4
  66. runbooks/inventory/list_elbs_load_balancers.py +7 -3
  67. runbooks/inventory/list_enis_network_interfaces.py +3 -3
  68. runbooks/inventory/list_guardduty_detectors.py +9 -5
  69. runbooks/inventory/list_iam_policies.py +7 -3
  70. runbooks/inventory/list_iam_roles.py +3 -3
  71. runbooks/inventory/list_iam_saml_providers.py +8 -4
  72. runbooks/inventory/list_lambda_functions.py +8 -4
  73. runbooks/inventory/list_org_accounts.py +306 -276
  74. runbooks/inventory/list_org_accounts_users.py +45 -9
  75. runbooks/inventory/list_rds_db_instances.py +4 -4
  76. runbooks/inventory/list_route53_hosted_zones.py +3 -3
  77. runbooks/inventory/list_servicecatalog_provisioned_products.py +5 -5
  78. runbooks/inventory/list_sns_topics.py +4 -4
  79. runbooks/inventory/list_ssm_parameters.py +6 -3
  80. runbooks/inventory/list_vpc_subnets.py +8 -4
  81. runbooks/inventory/list_vpcs.py +15 -4
  82. runbooks/inventory/mcp_inventory_validator.py +771 -134
  83. runbooks/inventory/mcp_vpc_validator.py +6 -0
  84. runbooks/inventory/organizations_discovery.py +17 -3
  85. runbooks/inventory/organizations_utils.py +553 -0
  86. runbooks/inventory/output_formatters.py +422 -0
  87. runbooks/inventory/recover_cfn_stack_ids.py +5 -5
  88. runbooks/inventory/run_on_multi_accounts.py +3 -3
  89. runbooks/inventory/tag_coverage.py +481 -0
  90. runbooks/inventory/validation_utils.py +358 -0
  91. runbooks/inventory/verify_ec2_security_groups.py +18 -5
  92. runbooks/inventory/vpc_architecture_validator.py +7 -1
  93. runbooks/inventory/vpc_dependency_analyzer.py +6 -0
  94. runbooks/main_final.py +2 -2
  95. runbooks/main_ultra_minimal.py +2 -2
  96. runbooks/mcp/integration.py +6 -4
  97. runbooks/remediation/acm_remediation.py +2 -2
  98. runbooks/remediation/cloudtrail_remediation.py +2 -2
  99. runbooks/remediation/cognito_remediation.py +2 -2
  100. runbooks/remediation/dynamodb_remediation.py +2 -2
  101. runbooks/remediation/ec2_remediation.py +2 -2
  102. runbooks/remediation/kms_remediation.py +2 -2
  103. runbooks/remediation/lambda_remediation.py +2 -2
  104. runbooks/remediation/rds_remediation.py +2 -2
  105. runbooks/remediation/s3_remediation.py +1 -1
  106. runbooks/vpc/cloudtrail_audit_integration.py +1 -1
  107. {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/METADATA +74 -4
  108. {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/RECORD +112 -105
  109. runbooks/__init__.py.backup +0 -134
  110. {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/WHEEL +0 -0
  111. {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/entry_points.txt +0 -0
  112. {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/licenses/LICENSE +0 -0
  113. {runbooks-1.1.7.dist-info → runbooks-1.1.10.dist-info}/top_level.txt +0 -0
@@ -32,20 +32,20 @@ from typing import List, Optional
32
32
  from ..common.profile_utils import get_profile_for_operation, validate_profile_access
33
33
  from ..common.rich_utils import console, print_error, print_info, print_success
34
34
  from .mcp_inventory_validator import (
35
+
36
+
37
+ # Terminal control constants
35
38
  create_inventory_mcp_validator,
36
39
  generate_drift_report,
37
40
  validate_inventory_results_with_mcp,
38
41
  )
39
42
 
40
43
 
41
- @click.group()
42
- def drift():
43
- """Infrastructure drift detection and validation commands."""
44
- pass
45
44
 
46
-
47
- @drift.command("detect")
48
- @click.option("--profile", help="AWS profile to use (overrides environment variables)")
45
+ # Terminal control constants
46
+ ERASE_LINE = '\x1b[2K'
47
+ @click.command()
48
+ @click.option("--profile", "-p", help="AWS profile to use (overrides environment variables)")
49
49
  @click.option("--profiles", help="Comma-separated list of AWS profiles for multi-account analysis")
50
50
  @click.option(
51
51
  "--terraform-dir",
@@ -60,7 +60,7 @@ def drift():
60
60
  )
61
61
  @click.option("--output-file", help="File path to save drift report (optional)")
62
62
  @click.option("--threshold", type=float, default=99.5, help="Accuracy threshold for drift detection (default: 99.5%)")
63
- def detect_drift(
63
+ def drift(
64
64
  profile: Optional[str],
65
65
  profiles: Optional[str],
66
66
  terraform_dir: str,
@@ -152,66 +152,6 @@ def detect_drift(
152
152
  raise click.Abort()
153
153
 
154
154
 
155
- @drift.command("report")
156
- @click.option("--profile", help="AWS profile to use for single-account analysis")
157
- @click.option(
158
- "--terraform-dir",
159
- default="/Volumes/Working/1xOps/CloudOps-Runbooks/terraform-aws",
160
- help="Path to terraform configuration directory",
161
- )
162
- @click.option(
163
- "--format",
164
- "report_format",
165
- type=click.Choice(["json", "csv", "markdown"]),
166
- default="json",
167
- help="Report output format",
168
- )
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):
171
- """
172
- Generate comprehensive infrastructure drift report.
173
-
174
- Creates detailed drift analysis report with actionable recommendations
175
- for infrastructure as code management and compliance.
176
- """
177
- try:
178
- # Use default profile if none specified
179
- if not profile:
180
- profile = get_profile_for_operation("operational", None)
181
-
182
- print_info(f"Generating drift report for profile: {profile}")
183
-
184
- # Validate profile
185
- if not validate_profile_access(profile, "drift-reporting"):
186
- print_error(f"Profile '{profile}' validation failed")
187
- return
188
-
189
- # Create mock inventory for demonstration
190
- mock_inventory = _create_mock_inventory_data([profile])
191
-
192
- # Generate comprehensive drift report
193
- drift_report = generate_drift_report([profile], mock_inventory, terraform_dir)
194
-
195
- # Export report
196
- _export_drift_report(drift_report, output_file, report_format)
197
-
198
- print_success(f"Drift report generated: {output_file}")
199
-
200
- # Display summary
201
- accounts_analyzed = drift_report.get("accounts_analyzed", 0)
202
- overall_accuracy = drift_report.get("overall_accuracy", 0)
203
- drift_detected = drift_report.get("drift_detected", False)
204
-
205
- console.print(f"[dim]📊 Analysis Summary:[/]")
206
- console.print(f"[dim] Accounts analyzed: {accounts_analyzed}[/]")
207
- console.print(f"[dim] Overall accuracy: {overall_accuracy:.1f}%[/]")
208
- console.print(f"[dim] Drift detected: {'Yes' if drift_detected else 'No'}[/]")
209
-
210
- except Exception as e:
211
- print_error(f"Report generation failed: {str(e)}")
212
- raise click.Abort()
213
-
214
-
215
155
  def _create_mock_inventory_data(profiles: List[str]) -> dict:
216
156
  """Create mock inventory data for demonstration purposes."""
217
157
  mock_data = {}
@@ -66,13 +66,13 @@ Future Enhancements:
66
66
 
67
67
  import logging
68
68
 
69
- import Inventory_Modules
70
- from account_class import aws_acct_access
71
- from ArgumentsClass import CommonArguments
69
+ from runbooks.inventory import inventory_modules as Inventory_Modules
70
+ from runbooks.inventory.account_class import aws_acct_access
71
+ from runbooks.inventory.ArgumentsClass import CommonArguments
72
72
  from botocore.exceptions import ClientError
73
73
  from runbooks.common.rich_utils import console
74
+ from runbooks import __version__
74
75
 
75
- __version__ = "2023.05.04"
76
76
 
77
77
  # Configure comprehensive argument parsing for CloudFormation drift detection operations
78
78
  parser = CommonArguments()
@@ -174,6 +174,16 @@ if aws_acct.AccountType == "Root":
174
174
  "AccountStatus": aws_acct.AccountStatus, # Account operational status
175
175
  }
176
176
  ]
177
+ else:
178
+ # Non-root account: Initialize ChildAccounts with single account entry
179
+ ChildAccounts = [
180
+ {
181
+ "MgmtAccount": aws_acct.acct_number, # Management account identifier
182
+ "AccountId": aws_acct.acct_number, # Account number for single account scope
183
+ "AccountEmail": aws_acct.MgmtEmail if hasattr(aws_acct, 'MgmtEmail') else "N/A", # Management email
184
+ "AccountStatus": aws_acct.AccountStatus if hasattr(aws_acct, 'AccountStatus') else "ACTIVE", # Account status
185
+ }
186
+ ]
177
187
 
178
188
  # Initialize drift detection operation tracking and regional scope configuration
179
189
  NumStacksFound = 0 # Counter for tracking total stacks processed for drift detection
@@ -71,13 +71,13 @@ import sys
71
71
  from os.path import split
72
72
  from time import time
73
73
 
74
- from account_class import aws_acct_access
75
- from ArgumentsClass import CommonArguments
74
+ from runbooks.inventory.account_class import aws_acct_access
75
+ from runbooks.inventory.ArgumentsClass import CommonArguments
76
76
 
77
77
  # import simplejson as json
78
78
  from botocore.exceptions import ClientError
79
79
  from runbooks.common.rich_utils import console
80
- from Inventory_Modules import (
80
+ from runbooks.inventory.inventory_modules import (
81
81
  display_results,
82
82
  find_stack_instances3,
83
83
  find_stacks2,
@@ -86,8 +86,11 @@ from Inventory_Modules import (
86
86
  get_regions3,
87
87
  print_timings,
88
88
  )
89
+ from runbooks import __version__
90
+
91
+ # Terminal control constants
92
+ ERASE_LINE = '\x1b[2K'
89
93
 
90
- __version__ = "2024.05.18"
91
94
  begin_time = time()
92
95
 
93
96
 
@@ -650,7 +653,6 @@ if __name__ == "__main__":
650
653
  logging.getLogger("s3transfer").setLevel(logging.CRITICAL)
651
654
  logging.getLogger("urllib3").setLevel(logging.CRITICAL)
652
655
 
653
- ERASE_LINE = "\x1b[2K"
654
656
  begin_time = time()
655
657
 
656
658
  # Setup credentials and regions (filtered by what they wanted to check)
@@ -66,15 +66,15 @@ from datetime import datetime
66
66
  from os.path import split
67
67
  from time import time
68
68
 
69
- import Inventory_Modules
70
- from account_class import aws_acct_access
71
- from ArgumentsClass import CommonArguments
69
+ from runbooks.inventory import inventory_modules as Inventory_Modules
70
+ from runbooks.inventory.account_class import aws_acct_access
71
+ from runbooks.inventory.ArgumentsClass import CommonArguments
72
72
  from botocore.exceptions import ClientError
73
73
  from runbooks.common.rich_utils import console
74
- from Inventory_Modules import display_results
74
+ from runbooks.inventory.inventory_modules import display_results
75
+ from runbooks import __version__
75
76
 
76
77
  # Initialize colorama for cross-platform colored terminal output
77
- __version__ = "2024.03.06"
78
78
  begin_time = time() # Script execution timing for performance monitoring
79
79
  sleep_interval = 5 # Default wait interval for drift detection operations
80
80
 
@@ -60,19 +60,22 @@ from queue import Queue
60
60
  from threading import Thread
61
61
  from time import time
62
62
 
63
- from ArgumentsClass import CommonArguments
63
+ from runbooks.inventory.ArgumentsClass import CommonArguments
64
64
  from botocore.exceptions import ClientError
65
65
  from runbooks.common.rich_utils import console
66
- from Inventory_Modules import (
66
+ from runbooks.inventory.inventory_modules import (
67
67
  display_results,
68
68
  find_references_to_security_groups2,
69
69
  find_security_groups2,
70
70
  get_all_credentials,
71
71
  )
72
+ from runbooks import __version__
73
+
74
+ # Terminal control constants
75
+ ERASE_LINE = '\x1b[2K'
72
76
  # Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
73
77
  # from tqdm.auto import tqdm
74
78
 
75
- __version__ = "2024.09.24"
76
79
  begin_time = time()
77
80
 
78
81
 
@@ -73,14 +73,14 @@ License: MIT
73
73
  import logging
74
74
 
75
75
  import boto3
76
- import Inventory_Modules
77
- from ArgumentsClass import CommonArguments
76
+ from runbooks.inventory import inventory_modules as Inventory_Modules
77
+ from runbooks.inventory.ArgumentsClass import CommonArguments
78
78
  from botocore.exceptions import ClientError, CredentialRetrievalError, InvalidConfigError
79
- # colorama removed - migrated to Rich
79
+ from runbooks.common.rich_utils import console
80
+ from runbooks import __version__
80
81
 
81
- # Initialize colorama for cross-platform colored terminal output
82
+ # colorama removed - migrated to Rich
82
83
 
83
- __version__ = "2023.05.31"
84
84
 
85
85
  # Configure comprehensive CLI argument parsing for Landing Zone discovery operations
86
86
  parser = CommonArguments()
@@ -88,16 +88,16 @@ from time import sleep, time
88
88
  from typing import Any, List
89
89
 
90
90
  import boto3
91
- import Inventory_Modules
92
- from account_class import aws_acct_access
93
- from ArgumentsClass import CommonArguments
91
+ from runbooks.inventory import inventory_modules as Inventory_Modules
92
+ from runbooks.inventory.account_class import aws_acct_access
93
+ from runbooks.inventory.ArgumentsClass import CommonArguments
94
94
  from botocore.config import Config
95
95
  from botocore.exceptions import ClientError
96
96
  from runbooks.common.rich_utils import console
97
- from Inventory_Modules import RemoveCoreAccounts, display_results, get_all_credentials, get_regions3
97
+ from runbooks.inventory.inventory_modules import RemoveCoreAccounts, display_results, get_all_credentials, get_regions3
98
+ from runbooks import __version__
98
99
 
99
100
  # Initialize colorama for cross-platform colored terminal output
100
- __version__ = "2024.03.10"
101
101
  begin_time = time() # Script execution timing for performance monitoring
102
102
  sleep_interval = 5 # Default wait interval for CloudWatch Logs query processing
103
103
 
@@ -66,7 +66,7 @@ fi
66
66
 
67
67
  # Test execution settings
68
68
  MAX_CONCURRENT_TESTS=5
69
- TEST_TIMEOUT=300 # 5 minutes per test
69
+ TEST_TIMEOUT=540 # 540 seconds per test (68 accounts × regions: list_cfn_stacks/list_vpcs timeout at 489s, need 540s with 10% headroom)
70
70
  RETRY_ATTEMPTS=2
71
71
 
72
72
  # Logging and output configuration
@@ -77,6 +77,9 @@ ERROR_ANALYSIS_FILE="error_analysis_$(date +%Y%m%d_%H%M%S).txt"
77
77
  # Create test output directory
78
78
  mkdir -p "$TEST_LOG_DIR"
79
79
 
80
+ # Initialize results temp file for IPC (counter persistence across subshells)
81
+ : > "$TEST_LOG_DIR/results.tmp"
82
+
80
83
  # Test result tracking (using arrays compatible with older bash versions)
81
84
  TEST_RESULTS_KEYS=()
82
85
  TEST_RESULTS_VALUES=()
@@ -205,11 +208,11 @@ function log_test_result() {
205
208
  # Determine test status
206
209
  if [ "$exit_code" -eq 0 ]; then
207
210
  status="PASSED"
208
- TESTS_PASSED=$((TESTS_PASSED + 1))
211
+ echo "PASS:$script_name" >> "$TEST_LOG_DIR/results.tmp"
209
212
  else
210
213
  status="FAILED"
211
- TESTS_FAILED=$((TESTS_FAILED + 1))
212
-
214
+ echo "FAIL:$script_name" >> "$TEST_LOG_DIR/results.tmp"
215
+
213
216
  # Perform error analysis
214
217
  analyze_test_errors "$script_name" "$log_file"
215
218
  fi
@@ -339,9 +342,9 @@ function execute_test() {
339
342
  echo "========================================" >> "$output_file"
340
343
 
341
344
  # Execute test with timeout (macOS compatible)
342
- # Set Python path to include inventory directory for local module imports
345
+ # Use module execution pattern for package imports (python -m runbooks.inventory.script)
343
346
  # Start the command in background and track its PID
344
- (cd "$INVENTORY_DIR" && PYTHONPATH=".:$(pwd):$PYTHONPATH" uv run python "$script_name" $test_parameters) >> "$output_file" 2>&1 &
347
+ (cd "$INVENTORY_DIR" && uv run python -m runbooks.inventory.$(basename "$script_name" .py) $test_parameters) >> "$output_file" 2>&1 &
345
348
  test_cmd_pid=$!
346
349
 
347
350
  # Wait for process completion or timeout
@@ -393,7 +396,8 @@ scripts_to_not_test="Inventory_Modules.py cfn_recover_stack_ids.py cfn_lockdown_
393
396
  account_class.py org_check_alz_readiness.py controltower_check_account_readiness.py s3_delete_objects.py cfn_enable_drift_detection.py \
394
397
  org_describe_landingzone_versions.py cfn_move_stack_instances.py multi_account_runner.py iam_update_roles_cross_account.py ec2_vpc_utils.py \
395
398
  cfn_recover_stack_ids.py setup.py aws_decorators.py cfn_list_stack_set_operation_results.py __pycache__ tests \
396
- update_aws_actions.py update_iam_roles_cross_accounts.py run_on_multi_accounts.py"
399
+ update_aws_actions.py update_iam_roles_cross_accounts.py run_on_multi_accounts.py vpc_architecture_validator.py vpc_dependency_analyzer.py mcp_vpc_validator.py \
400
+ check_controltower_readiness.py"
397
401
 
398
402
  # Scripts that require interactive responses (cannot be tested autonomously)
399
403
  # These scripts need manual input and are skipped in automated testing
@@ -588,6 +592,8 @@ echo "==========================================================================
588
592
  echo "\nTEST EXECUTION SUMMARY"
589
593
  echo "======================"
590
594
  echo "Total Tests Executed: ${#arrScripts[@]}"
595
+ TESTS_PASSED=$(grep -c "^PASS:" "$TEST_LOG_DIR/results.tmp" 2>/dev/null || echo 0)
596
+ TESTS_FAILED=$(grep -c "^FAIL:" "$TEST_LOG_DIR/results.tmp" 2>/dev/null || echo 0)
591
597
  echo "Tests Passed: $TESTS_PASSED"
592
598
  echo "Tests Failed: $TESTS_FAILED"
593
599
  echo "Tests Skipped: $TESTS_SKIPPED"
@@ -649,10 +655,17 @@ echo "==========================================================================
649
655
 
650
656
  } | tee -a "$TEST_LOG_DIR/$SUMMARY_FILE"
651
657
 
658
+ # Read final counts from temp file for exit code logic
659
+ TESTS_PASSED=$(grep -c "^PASS:" "$TEST_LOG_DIR/results.tmp" 2>/dev/null || echo 0)
660
+ TESTS_FAILED=$(grep -c "^FAIL:" "$TEST_LOG_DIR/results.tmp" 2>/dev/null || echo 0)
661
+
652
662
  # Set exit code based on test results
653
663
  if [[ $TESTS_FAILED -gt 0 ]]; then
654
664
  echo "\nWARNING: $TESTS_FAILED tests failed. Review logs for details."
655
665
  exit 1
666
+ elif [[ $TESTS_PASSED -eq 0 && $TESTS_FAILED -eq 0 ]]; then
667
+ echo "\nERROR: No tests executed or counter error detected."
668
+ exit 1
656
669
  else
657
670
  echo "\nSUCCESS: All tests passed successfully."
658
671
  exit 0
@@ -24,6 +24,10 @@ from ..common.rich_utils import console, print_error, print_info, print_success,
24
24
  from .mcp_inventory_validator import create_inventory_mcp_validator
25
25
 
26
26
 
27
+
28
+
29
+ # Terminal control constants
30
+ ERASE_LINE = '\x1b[2K'
27
31
  @click.command()
28
32
  @click.option("--profile", help="AWS profile name (takes precedence over environment variables)")
29
33
  @click.option(
@@ -1,6 +1,6 @@
1
1
  import logging
2
+ from runbooks import __version__
2
3
 
3
- __version__ = "2025.04.03"
4
4
 
5
5
  """
6
6
  ** Why are some functions "function" vs. "function2" vs. "function3"?**
@@ -457,8 +457,6 @@ def print_timings(fTiming: bool = False, fverbose: int = 50, fbegin_time=None, f
457
457
 
458
458
  from runbooks.common.rich_utils import console
459
459
 
460
- init()
461
-
462
460
  if fTiming and fverbose < 50 and fbegin_time is not None:
463
461
  print(f"[green]{fmessage}\nThis script has taken {time() - fbegin_time:.6f} seconds so far")
464
462
 
@@ -2652,8 +2650,12 @@ def find_account_policies2(
2652
2650
  from botocore.exceptions import ClientError
2653
2651
 
2654
2652
  # Handle both profile-based and credential-based authentication
2653
+ # Priority: Profile > ParentProfile > AccessKeyId > default credential chain
2655
2654
  if "Profile" in ocredentials and ocredentials["Profile"]:
2656
2655
  session_iam = boto3.Session(profile_name=ocredentials["Profile"], region_name=fRegion)
2656
+ elif "ParentProfile" in ocredentials and ocredentials["ParentProfile"]:
2657
+ # Use ParentProfile from get_all_credentials output
2658
+ session_iam = boto3.Session(profile_name=ocredentials["ParentProfile"], region_name=fRegion)
2657
2659
  elif "AccessKeyId" in ocredentials and ocredentials["AccessKeyId"]:
2658
2660
  session_iam = boto3.Session(
2659
2661
  aws_access_key_id=ocredentials["AccessKeyId"],
@@ -5080,8 +5082,12 @@ def find_ssm_parameters2(ocredentials):
5080
5082
  region = ocredentials["Region"]
5081
5083
  logging.info(f"Finding ssm parameters for account {ocredentials['AccountNumber']} in Region {region}")
5082
5084
  # Handle both profile-based and credential-based authentication
5085
+ # Priority: Profile > ParentProfile > AccessKeyId > default credential chain
5083
5086
  if "Profile" in ocredentials and ocredentials["Profile"]:
5084
5087
  session_ssm = boto3.Session(profile_name=ocredentials["Profile"], region_name=region)
5088
+ elif "ParentProfile" in ocredentials and ocredentials["ParentProfile"]:
5089
+ # Use ParentProfile from get_all_credentials output
5090
+ session_ssm = boto3.Session(profile_name=ocredentials["ParentProfile"], region_name=region)
5085
5091
  elif "AccessKeyId" in ocredentials and ocredentials["AccessKeyId"]:
5086
5092
  session_ssm = boto3.Session(
5087
5093
  aws_access_key_id=ocredentials["AccessKeyId"],
@@ -5345,7 +5351,6 @@ def display_results(
5345
5351
 
5346
5352
  from runbooks.common.rich_utils import console
5347
5353
 
5348
- init()
5349
5354
  """
5350
5355
  Note that this function simply formats the output of the data within the list provided
5351
5356
  @param: results_list: This should be a list of dictionaries, matching to the fields in fdisplay_dict
@@ -5760,8 +5765,6 @@ def get_all_credentials(
5760
5765
 
5761
5766
  # from time import time
5762
5767
  from runbooks.common.rich_utils import console
5763
-
5764
- init()
5765
5768
  # ERASE_LINE = '\x1b[2K'
5766
5769
  # begin_time = time()
5767
5770
  print(f"[green]Timing is enabled") if fTiming else None
@@ -5853,7 +5856,6 @@ def get_credentials_for_accounts_in_org(
5853
5856
  from runbooks.common.rich_utils import console
5854
5857
  from runbooks.common.rich_utils import create_progress_bar
5855
5858
 
5856
- init()
5857
5859
  begin_time = time()
5858
5860
 
5859
5861
  class AssembleCredentials(Thread):
@@ -2,12 +2,19 @@
2
2
  """
3
3
  AWS CloudFormation Stack Inventory Collection
4
4
 
5
- A comprehensive CloudFormation stack discovery and management tool for multi-account
6
- AWS Organizations. Provides detailed stack inventory with advanced filtering capabilities
5
+ A comprehensive CloudFormation stack discovery and management tool for multi-account
6
+ AWS Organizations. Provides detailed stack inventory with advanced filtering capabilities
7
7
  and optional stack deletion functionality.
8
8
 
9
9
  **AWS API Mapping**: `cloudformation.describe_stacks()`, `cloudformation.list_stacks()`
10
10
 
11
+ .. TODO v1.1.11: Performance optimization for large-scale CloudFormation stack discovery
12
+ - Current: Timeouts at 540s for large AWS Organizations with 100+ accounts
13
+ - Root Cause: Sequential API calls with pagination bottlenecks
14
+ - Improvement: Implement concurrent pagination (40-80% speedup expected)
15
+ - Target: Complete discovery in <180s for 100+ accounts across 16 regions
16
+ - Reference: FinOps proven pattern (docs/development/finops-performance-optimization.md)
17
+
11
18
  Features:
12
19
  - Multi-account CloudFormation stack discovery
13
20
  - Advanced status filtering (CREATE_COMPLETE, UPDATE_FAILED, etc.)
@@ -63,15 +70,19 @@ from os.path import split
63
70
  from pprint import pprint
64
71
  from time import time
65
72
 
66
- import Inventory_Modules
67
- from account_class import aws_acct_access
68
- from ArgumentsClass import CommonArguments
73
+ from runbooks.inventory import inventory_modules as Inventory_Modules
74
+ from runbooks.inventory.account_class import aws_acct_access
75
+ from runbooks.inventory.ArgumentsClass import CommonArguments
69
76
  from botocore.exceptions import ClientError
70
77
  from runbooks.common.rich_utils import console
71
- from Inventory_Modules import display_results, get_all_credentials
78
+ from runbooks.inventory.inventory_modules import display_results, get_all_credentials
79
+ from runbooks import __version__
80
+
81
+
72
82
 
73
83
 
74
- __version__ = "2024.05.31"
84
+ # Terminal control constants
85
+ ERASE_LINE = '\x1b[2K'
75
86
 
76
87
  ###########################
77
88
 
@@ -529,7 +540,6 @@ if __name__ == "__main__":
529
540
  begin_time = time()
530
541
 
531
542
  ##########################
532
- ERASE_LINE = "\x1b[2K"
533
543
 
534
544
  print()
535
545
  # Setup the aws_acct object
@@ -75,10 +75,10 @@ Output:
75
75
  import logging
76
76
  import re
77
77
 
78
- from ArgumentsClass import CommonArguments
78
+ from runbooks.inventory.ArgumentsClass import CommonArguments
79
79
  from runbooks.common.rich_utils import console
80
+ from runbooks import __version__
80
81
 
81
- __version__ = "2024.06.20"
82
82
 
83
83
  # Configure CLI argument parsing for StackSet results analysis and correlation
84
84
  parser = CommonArguments()
@@ -73,17 +73,21 @@ from queue import Queue
73
73
  from threading import Thread
74
74
  from time import time
75
75
 
76
- import Inventory_Modules
77
- from account_class import aws_acct_access
78
- from ArgumentsClass import CommonArguments
76
+ from runbooks.inventory import inventory_modules as Inventory_Modules
77
+ from runbooks.inventory.account_class import aws_acct_access
78
+ from runbooks.inventory.ArgumentsClass import CommonArguments
79
79
  from botocore.exceptions import ClientError
80
80
  from runbooks.common.rich_utils import console
81
- from Inventory_Modules import display_results, find_stacksets3, get_regions3
81
+ from runbooks.inventory.inventory_modules import display_results, find_stacksets3, get_regions3
82
+ from runbooks import __version__
83
+
82
84
  # Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
83
85
  # from tqdm.auto import tqdm
84
86
 
85
87
 
86
- __version__ = "2024.05.18"
88
+
89
+ # Terminal control constants
90
+ ERASE_LINE = '\x1b[2K'
87
91
  begin_time = time()
88
92
  DefaultMaxWorkerThreads = 5
89
93
 
@@ -679,21 +683,29 @@ def find_last_operations(faws_acct: aws_acct_access, fStackSetNames: list):
679
683
  )
680
684
 
681
685
  for stacksetname in fStackSetNames:
682
- # Retrieve most recent operation for the current StackSet
683
- StackSetOps = StackSetOps_client.list_stack_set_operations(
684
- StackSetName=stacksetname, MaxResults=1, CallAs="SELF"
685
- )["Summaries"]
686
-
687
- # Extract and aggregate operation metadata for analysis
688
- AllStackSetOps.append(
689
- {
690
- "StackSetName": stacksetname, # StackSet identifier for correlation
691
- "Operation": StackSetOps[0]["Action"], # Operation type for lifecycle tracking
692
- "LatestStatus": StackSetOps[0]["Status"], # Current operation status
693
- "LatestDate": StackSetOps[0]["EndTimestamp"], # Completion timestamp
694
- "Details": StackSetOps[0]["StatusDetails"]["FailedStackInstancesCount"], # Failure count for analysis
695
- }
696
- )
686
+ try:
687
+ # Retrieve most recent operation for the current StackSet
688
+ StackSetOps = StackSetOps_client.list_stack_set_operations(
689
+ StackSetName=stacksetname, MaxResults=1, CallAs="SELF"
690
+ )["Summaries"]
691
+
692
+ # Extract and aggregate operation metadata for analysis
693
+ if StackSetOps: # Only process if operations exist
694
+ AllStackSetOps.append(
695
+ {
696
+ "StackSetName": stacksetname, # StackSet identifier for correlation
697
+ "Operation": StackSetOps[0]["Action"], # Operation type for lifecycle tracking
698
+ "LatestStatus": StackSetOps[0]["Status"], # Current operation status
699
+ "LatestDate": StackSetOps[0]["EndTimestamp"], # Completion timestamp
700
+ "Details": StackSetOps[0]["StatusDetails"]["FailedStackInstancesCount"], # Failure count for analysis
701
+ }
702
+ )
703
+ except StackSetOps_client.exceptions.StackSetNotFoundException:
704
+ logging.warning(f"StackSet {stacksetname} not found or inaccessible - skipping")
705
+ continue # Skip this StackSet, continue with others
706
+ except ClientError as e:
707
+ logging.error(f"Error querying StackSet {stacksetname}: {e}")
708
+ continue
697
709
 
698
710
  # Update progress after processing each StackSet
699
711
  progress.update(task, advance=1)
@@ -86,10 +86,10 @@ import sys
86
86
  from os.path import split
87
87
  from time import time
88
88
 
89
- from account_class import aws_acct_access
90
- from ArgumentsClass import CommonArguments
89
+ from runbooks.inventory.account_class import aws_acct_access
90
+ from runbooks.inventory.ArgumentsClass import CommonArguments
91
91
  from runbooks.common.rich_utils import console
92
- from Inventory_Modules import (
92
+ from runbooks.inventory.inventory_modules import (
93
93
  RemoveCoreAccounts,
94
94
  display_results,
95
95
  find_stack_instances2,
@@ -97,9 +97,12 @@ from Inventory_Modules import (
97
97
  get_credentials_for_accounts_in_org,
98
98
  get_regions3,
99
99
  )
100
+ from runbooks import __version__
101
+
102
+ # Terminal control constants
103
+ ERASE_LINE = '\x1b[2K'
100
104
 
101
105
 
102
- __version__ = "2024.06.20"
103
106
  begin_time = time()
104
107
 
105
108
  #####################
@@ -90,15 +90,15 @@ from queue import Queue
90
90
  from threading import Thread
91
91
  from time import time
92
92
 
93
- import Inventory_Modules
94
- from ArgumentsClass import CommonArguments
93
+ from runbooks.inventory import inventory_modules as Inventory_Modules
94
+ from runbooks.inventory.ArgumentsClass import CommonArguments
95
95
  from botocore.exceptions import ClientError
96
96
  from runbooks.common.rich_utils import console
97
- from Inventory_Modules import del_config_recorder_or_delivery_channel2, display_results, get_all_credentials
97
+ from runbooks.inventory.inventory_modules import del_config_recorder_or_delivery_channel2, display_results, get_all_credentials
98
+ from runbooks import __version__
98
99
  # Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
99
100
  # from tqdm.auto import tqdm
100
101
 
101
- __version__ = "2024.05.31"
102
102
 
103
103
 
104
104
  ##################
@@ -58,14 +58,14 @@ import logging
58
58
  import sys
59
59
  from time import time
60
60
 
61
- from ArgumentsClass import CommonArguments
61
+ from runbooks.inventory.ArgumentsClass import CommonArguments
62
62
  from botocore.exceptions import ClientError
63
63
  from runbooks.common.rich_utils import console
64
- from Inventory_Modules import display_results, find_directories2, get_all_credentials
64
+ from runbooks.inventory.inventory_modules import display_results, find_directories2, get_all_credentials
65
+ from runbooks import __version__
65
66
  # Migrated to Rich.Progress - see rich_utils.py for enterprise UX standards
66
67
  # from tqdm.auto import tqdm
67
68
 
68
- __version__ = "2024.05.31"
69
69
 
70
70
 
71
71
  def parse_args(f_arguments):
@@ -66,11 +66,15 @@ import logging
66
66
  import sys
67
67
  from time import time
68
68
 
69
- from ArgumentsClass import CommonArguments
69
+ from runbooks.inventory.ArgumentsClass import CommonArguments
70
70
  from runbooks.common.rich_utils import console
71
- from Inventory_Modules import display_results, get_all_credentials, get_region_azs2
71
+ from runbooks.inventory.inventory_modules import display_results, get_all_credentials, get_region_azs2
72
+ from runbooks import __version__
72
73
 
73
- __version__ = "2024.03.06"
74
+
75
+
76
+ # Terminal control constants
77
+ ERASE_LINE = '\x1b[2K'
74
78
  begin_time = time()
75
79
 
76
80