runbooks 0.2.3__py3-none-any.whl → 0.6.1__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 (221) hide show
  1. conftest.py +26 -0
  2. jupyter-agent/.env.template +2 -0
  3. jupyter-agent/.gitattributes +35 -0
  4. jupyter-agent/README.md +16 -0
  5. jupyter-agent/app.py +256 -0
  6. jupyter-agent/cloudops-agent.png +0 -0
  7. jupyter-agent/ds-system-prompt.txt +154 -0
  8. jupyter-agent/jupyter-agent.png +0 -0
  9. jupyter-agent/llama3_template.jinja +123 -0
  10. jupyter-agent/requirements.txt +9 -0
  11. jupyter-agent/utils.py +409 -0
  12. runbooks/__init__.py +71 -3
  13. runbooks/__main__.py +13 -0
  14. runbooks/aws/ec2_describe_instances.py +1 -1
  15. runbooks/aws/ec2_run_instances.py +8 -2
  16. runbooks/aws/ec2_start_stop_instances.py +17 -4
  17. runbooks/aws/ec2_unused_volumes.py +5 -1
  18. runbooks/aws/s3_create_bucket.py +4 -2
  19. runbooks/aws/s3_list_objects.py +6 -1
  20. runbooks/aws/tagging_lambda_handler.py +13 -2
  21. runbooks/aws/tags.json +12 -0
  22. runbooks/base.py +353 -0
  23. runbooks/cfat/README.md +49 -0
  24. runbooks/cfat/__init__.py +74 -0
  25. runbooks/cfat/app.ts +644 -0
  26. runbooks/cfat/assessment/__init__.py +40 -0
  27. runbooks/cfat/assessment/asana-import.csv +39 -0
  28. runbooks/cfat/assessment/cfat-checks.csv +31 -0
  29. runbooks/cfat/assessment/cfat.txt +520 -0
  30. runbooks/cfat/assessment/collectors.py +200 -0
  31. runbooks/cfat/assessment/jira-import.csv +39 -0
  32. runbooks/cfat/assessment/runner.py +387 -0
  33. runbooks/cfat/assessment/validators.py +290 -0
  34. runbooks/cfat/cli.py +103 -0
  35. runbooks/cfat/docs/asana-import.csv +24 -0
  36. runbooks/cfat/docs/cfat-checks.csv +31 -0
  37. runbooks/cfat/docs/cfat.txt +335 -0
  38. runbooks/cfat/docs/checks-output.png +0 -0
  39. runbooks/cfat/docs/cloudshell-console-run.png +0 -0
  40. runbooks/cfat/docs/cloudshell-download.png +0 -0
  41. runbooks/cfat/docs/cloudshell-output.png +0 -0
  42. runbooks/cfat/docs/downloadfile.png +0 -0
  43. runbooks/cfat/docs/jira-import.csv +24 -0
  44. runbooks/cfat/docs/open-cloudshell.png +0 -0
  45. runbooks/cfat/docs/report-header.png +0 -0
  46. runbooks/cfat/models.py +1026 -0
  47. runbooks/cfat/package-lock.json +5116 -0
  48. runbooks/cfat/package.json +38 -0
  49. runbooks/cfat/report.py +496 -0
  50. runbooks/cfat/reporting/__init__.py +46 -0
  51. runbooks/cfat/reporting/exporters.py +337 -0
  52. runbooks/cfat/reporting/formatters.py +496 -0
  53. runbooks/cfat/reporting/templates.py +135 -0
  54. runbooks/cfat/run-assessment.sh +23 -0
  55. runbooks/cfat/runner.py +69 -0
  56. runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
  57. runbooks/cfat/src/actions/check-config-existence.ts +37 -0
  58. runbooks/cfat/src/actions/check-control-tower.ts +37 -0
  59. runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
  60. runbooks/cfat/src/actions/check-iam-users.ts +50 -0
  61. runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
  62. runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
  63. runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
  64. runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
  65. runbooks/cfat/src/actions/create-backlog.ts +372 -0
  66. runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
  67. runbooks/cfat/src/actions/create-report.ts +616 -0
  68. runbooks/cfat/src/actions/define-account-type.ts +51 -0
  69. runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
  70. runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
  71. runbooks/cfat/src/actions/get-idc-info.ts +34 -0
  72. runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
  73. runbooks/cfat/src/actions/get-org-details.ts +35 -0
  74. runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
  75. runbooks/cfat/src/actions/get-org-ous.ts +35 -0
  76. runbooks/cfat/src/actions/get-regions.ts +22 -0
  77. runbooks/cfat/src/actions/zip-assessment.ts +27 -0
  78. runbooks/cfat/src/types/index.d.ts +147 -0
  79. runbooks/cfat/tests/__init__.py +141 -0
  80. runbooks/cfat/tests/test_cli.py +340 -0
  81. runbooks/cfat/tests/test_integration.py +290 -0
  82. runbooks/cfat/tests/test_models.py +505 -0
  83. runbooks/cfat/tests/test_reporting.py +354 -0
  84. runbooks/cfat/tsconfig.json +16 -0
  85. runbooks/cfat/webpack.config.cjs +27 -0
  86. runbooks/config.py +260 -0
  87. runbooks/finops/__init__.py +88 -0
  88. runbooks/finops/aws_client.py +245 -0
  89. runbooks/finops/cli.py +151 -0
  90. runbooks/finops/cost_processor.py +410 -0
  91. runbooks/finops/dashboard_runner.py +448 -0
  92. runbooks/finops/helpers.py +355 -0
  93. runbooks/finops/main.py +14 -0
  94. runbooks/finops/profile_processor.py +174 -0
  95. runbooks/finops/types.py +66 -0
  96. runbooks/finops/visualisations.py +80 -0
  97. runbooks/inventory/.gitignore +354 -0
  98. runbooks/inventory/ArgumentsClass.py +261 -0
  99. runbooks/inventory/Inventory_Modules.py +6130 -0
  100. runbooks/inventory/LandingZone/delete_lz.py +1075 -0
  101. runbooks/inventory/README.md +1320 -0
  102. runbooks/inventory/__init__.py +62 -0
  103. runbooks/inventory/account_class.py +532 -0
  104. runbooks/inventory/all_my_instances_wrapper.py +123 -0
  105. runbooks/inventory/aws_decorators.py +201 -0
  106. runbooks/inventory/cfn_move_stack_instances.py +1526 -0
  107. runbooks/inventory/check_cloudtrail_compliance.py +614 -0
  108. runbooks/inventory/check_controltower_readiness.py +1107 -0
  109. runbooks/inventory/check_landingzone_readiness.py +711 -0
  110. runbooks/inventory/cloudtrail.md +727 -0
  111. runbooks/inventory/collectors/__init__.py +20 -0
  112. runbooks/inventory/collectors/aws_compute.py +518 -0
  113. runbooks/inventory/collectors/aws_networking.py +275 -0
  114. runbooks/inventory/collectors/base.py +222 -0
  115. runbooks/inventory/core/__init__.py +19 -0
  116. runbooks/inventory/core/collector.py +303 -0
  117. runbooks/inventory/core/formatter.py +296 -0
  118. runbooks/inventory/delete_s3_buckets_objects.py +169 -0
  119. runbooks/inventory/discovery.md +81 -0
  120. runbooks/inventory/draw_org_structure.py +748 -0
  121. runbooks/inventory/ec2_vpc_utils.py +341 -0
  122. runbooks/inventory/find_cfn_drift_detection.py +272 -0
  123. runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
  124. runbooks/inventory/find_cfn_stackset_drift.py +733 -0
  125. runbooks/inventory/find_ec2_security_groups.py +669 -0
  126. runbooks/inventory/find_landingzone_versions.py +201 -0
  127. runbooks/inventory/find_vpc_flow_logs.py +1221 -0
  128. runbooks/inventory/inventory.sh +659 -0
  129. runbooks/inventory/list_cfn_stacks.py +558 -0
  130. runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
  131. runbooks/inventory/list_cfn_stackset_operations.py +734 -0
  132. runbooks/inventory/list_cfn_stacksets.py +453 -0
  133. runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
  134. runbooks/inventory/list_ds_directories.py +354 -0
  135. runbooks/inventory/list_ec2_availability_zones.py +286 -0
  136. runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
  137. runbooks/inventory/list_ec2_instances.py +425 -0
  138. runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
  139. runbooks/inventory/list_elbs_load_balancers.py +411 -0
  140. runbooks/inventory/list_enis_network_interfaces.py +526 -0
  141. runbooks/inventory/list_guardduty_detectors.py +568 -0
  142. runbooks/inventory/list_iam_policies.py +404 -0
  143. runbooks/inventory/list_iam_roles.py +518 -0
  144. runbooks/inventory/list_iam_saml_providers.py +359 -0
  145. runbooks/inventory/list_lambda_functions.py +882 -0
  146. runbooks/inventory/list_org_accounts.py +446 -0
  147. runbooks/inventory/list_org_accounts_users.py +354 -0
  148. runbooks/inventory/list_rds_db_instances.py +406 -0
  149. runbooks/inventory/list_route53_hosted_zones.py +318 -0
  150. runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
  151. runbooks/inventory/list_sns_topics.py +360 -0
  152. runbooks/inventory/list_ssm_parameters.py +402 -0
  153. runbooks/inventory/list_vpc_subnets.py +433 -0
  154. runbooks/inventory/list_vpcs.py +422 -0
  155. runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
  156. runbooks/inventory/models/__init__.py +24 -0
  157. runbooks/inventory/models/account.py +192 -0
  158. runbooks/inventory/models/inventory.py +309 -0
  159. runbooks/inventory/models/resource.py +247 -0
  160. runbooks/inventory/recover_cfn_stack_ids.py +205 -0
  161. runbooks/inventory/requirements.txt +12 -0
  162. runbooks/inventory/run_on_multi_accounts.py +211 -0
  163. runbooks/inventory/tests/common_test_data.py +3661 -0
  164. runbooks/inventory/tests/common_test_functions.py +204 -0
  165. runbooks/inventory/tests/script_test_data.py +0 -0
  166. runbooks/inventory/tests/setup.py +24 -0
  167. runbooks/inventory/tests/src.py +18 -0
  168. runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
  169. runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
  170. runbooks/inventory/tests/test_inventory_modules.py +55 -0
  171. runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
  172. runbooks/inventory/tests/test_moto_integration_example.py +273 -0
  173. runbooks/inventory/tests/test_org_list_accounts.py +49 -0
  174. runbooks/inventory/update_aws_actions.py +173 -0
  175. runbooks/inventory/update_cfn_stacksets.py +1215 -0
  176. runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
  177. runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
  178. runbooks/inventory/update_s3_public_access_block.py +539 -0
  179. runbooks/inventory/utils/__init__.py +23 -0
  180. runbooks/inventory/utils/aws_helpers.py +510 -0
  181. runbooks/inventory/utils/threading_utils.py +493 -0
  182. runbooks/inventory/utils/validation.py +682 -0
  183. runbooks/inventory/verify_ec2_security_groups.py +1430 -0
  184. runbooks/main.py +785 -0
  185. runbooks/organizations/__init__.py +12 -0
  186. runbooks/organizations/manager.py +374 -0
  187. runbooks/security_baseline/README.md +324 -0
  188. runbooks/security_baseline/checklist/alternate_contacts.py +8 -1
  189. runbooks/security_baseline/checklist/bucket_public_access.py +4 -1
  190. runbooks/security_baseline/checklist/cloudwatch_alarm_configuration.py +9 -2
  191. runbooks/security_baseline/checklist/guardduty_enabled.py +9 -2
  192. runbooks/security_baseline/checklist/multi_region_instance_usage.py +5 -1
  193. runbooks/security_baseline/checklist/root_access_key.py +6 -1
  194. runbooks/security_baseline/config-origin.json +1 -1
  195. runbooks/security_baseline/config.json +1 -1
  196. runbooks/security_baseline/permission.json +1 -1
  197. runbooks/security_baseline/report_generator.py +10 -2
  198. runbooks/security_baseline/report_template_en.html +8 -8
  199. runbooks/security_baseline/report_template_jp.html +8 -8
  200. runbooks/security_baseline/report_template_kr.html +13 -13
  201. runbooks/security_baseline/report_template_vn.html +8 -8
  202. runbooks/security_baseline/requirements.txt +7 -0
  203. runbooks/security_baseline/run_script.py +8 -2
  204. runbooks/security_baseline/security_baseline_tester.py +10 -2
  205. runbooks/security_baseline/utils/common.py +5 -1
  206. runbooks/utils/__init__.py +204 -0
  207. runbooks-0.6.1.dist-info/METADATA +373 -0
  208. runbooks-0.6.1.dist-info/RECORD +237 -0
  209. {runbooks-0.2.3.dist-info → runbooks-0.6.1.dist-info}/WHEEL +1 -1
  210. runbooks-0.6.1.dist-info/entry_points.txt +7 -0
  211. runbooks-0.6.1.dist-info/licenses/LICENSE +201 -0
  212. runbooks-0.6.1.dist-info/top_level.txt +3 -0
  213. runbooks/python101/calculator.py +0 -34
  214. runbooks/python101/config.py +0 -1
  215. runbooks/python101/exceptions.py +0 -16
  216. runbooks/python101/file_manager.py +0 -218
  217. runbooks/python101/toolkit.py +0 -153
  218. runbooks-0.2.3.dist-info/METADATA +0 -435
  219. runbooks-0.2.3.dist-info/RECORD +0 -61
  220. runbooks-0.2.3.dist-info/entry_points.txt +0 -3
  221. runbooks-0.2.3.dist-info/top_level.txt +0 -1
@@ -0,0 +1,88 @@
1
+ """
2
+ AWS FinOps Dashboard - Cost and Resource Monitoring Tool
3
+
4
+ This module provides terminal-based AWS cost monitoring with features including:
5
+ - Multi-account cost summaries
6
+ - Service-level cost breakdown
7
+ - Budget monitoring
8
+ - EC2 resource status
9
+ - Cost trend analysis
10
+ - Audit reporting for optimization opportunities
11
+
12
+ Integrated as a submodule of CloudOps Runbooks for enterprise FinOps automation.
13
+ """
14
+
15
+ __version__ = "0.7.0"
16
+
17
+ # Core components
18
+ # AWS client utilities
19
+ from runbooks.finops.aws_client import (
20
+ ec2_summary,
21
+ get_accessible_regions,
22
+ get_account_id,
23
+ get_aws_profiles,
24
+ get_budgets,
25
+ get_stopped_instances,
26
+ get_untagged_resources,
27
+ get_unused_eips,
28
+ get_unused_volumes,
29
+ )
30
+
31
+ # Data processors
32
+ from runbooks.finops.cost_processor import get_cost_data, get_trend
33
+ from runbooks.finops.dashboard_runner import run_dashboard
34
+ from runbooks.finops.helpers import (
35
+ export_audit_report_to_csv,
36
+ export_audit_report_to_json,
37
+ export_audit_report_to_pdf,
38
+ export_cost_dashboard_to_pdf,
39
+ export_to_csv,
40
+ export_to_json,
41
+ export_trend_data_to_json,
42
+ load_config_file,
43
+ )
44
+ from runbooks.finops.profile_processor import process_combined_profiles, process_single_profile
45
+
46
+ # Type definitions
47
+ from runbooks.finops.types import BudgetInfo, CostData, EC2Summary, ProfileData, RegionName
48
+
49
+ # Visualization and export
50
+ from runbooks.finops.visualisations import create_trend_bars
51
+
52
+ __all__ = [
53
+ # Core functionality
54
+ "run_dashboard",
55
+ # Processors
56
+ "get_cost_data",
57
+ "get_trend",
58
+ "process_single_profile",
59
+ "process_combined_profiles",
60
+ # AWS utilities
61
+ "get_aws_profiles",
62
+ "get_account_id",
63
+ "get_accessible_regions",
64
+ "ec2_summary",
65
+ "get_stopped_instances",
66
+ "get_unused_volumes",
67
+ "get_unused_eips",
68
+ "get_untagged_resources",
69
+ "get_budgets",
70
+ # Visualization and export
71
+ "create_trend_bars",
72
+ "export_to_csv",
73
+ "export_to_json",
74
+ "export_audit_report_to_pdf",
75
+ "export_cost_dashboard_to_pdf",
76
+ "export_audit_report_to_csv",
77
+ "export_audit_report_to_json",
78
+ "export_trend_data_to_json",
79
+ "load_config_file",
80
+ # Types
81
+ "ProfileData",
82
+ "CostData",
83
+ "BudgetInfo",
84
+ "EC2Summary",
85
+ "RegionName",
86
+ # Metadata
87
+ "__version__",
88
+ ]
@@ -0,0 +1,245 @@
1
+ from collections import defaultdict
2
+ from typing import Dict, List, Optional
3
+
4
+ import boto3
5
+ from boto3.session import Session
6
+ from botocore.exceptions import ClientError
7
+ from rich.console import Console
8
+
9
+ from runbooks.finops.types import BudgetInfo, EC2Summary, RegionName
10
+
11
+ console = Console()
12
+
13
+
14
+ def get_aws_profiles() -> List[str]:
15
+ """Get all configured AWS profiles from the AWS CLI configuration."""
16
+ try:
17
+ session = boto3.Session()
18
+ return session.available_profiles
19
+ except Exception as e:
20
+ console.print(f"[bold red]Error getting AWS profiles: {str(e)}[/]")
21
+ return []
22
+
23
+
24
+ def get_account_id(session: Session) -> Optional[str]:
25
+ """Get the AWS account ID for a session."""
26
+ try:
27
+ account_id = session.client("sts").get_caller_identity().get("Account")
28
+ return str(account_id) if account_id is not None else None
29
+ except Exception as e:
30
+ console.log(f"[yellow]Warning: Could not get account ID: {str(e)}[/]")
31
+ return None
32
+
33
+
34
+ def get_all_regions(session: Session) -> List[RegionName]:
35
+ """
36
+ Get all available AWS regions.
37
+ Using us-east-1 as a default region to get the list of all regions.
38
+
39
+ If the call fails, it will return a hardcoded list of common regions.
40
+ """
41
+ try:
42
+ ec2_client = session.client("ec2", region_name="us-east-1")
43
+ regions = [region["RegionName"] for region in ec2_client.describe_regions()["Regions"]]
44
+ return regions
45
+ except Exception as e:
46
+ console.log(f"[yellow]Warning: Could not get all regions: {str(e)}[/]")
47
+ return [
48
+ "us-east-1",
49
+ "us-east-2",
50
+ "us-west-1",
51
+ "us-west-2",
52
+ "ap-southeast-1",
53
+ "ap-south-1",
54
+ "eu-west-1",
55
+ "eu-west-2",
56
+ "eu-central-1",
57
+ ]
58
+
59
+
60
+ def get_accessible_regions(session: Session) -> List[RegionName]:
61
+ """Get regions that are accessible with the current credentials."""
62
+ all_regions = get_all_regions(session)
63
+ accessible_regions = []
64
+
65
+ for region in all_regions:
66
+ try:
67
+ ec2_client = session.client("ec2", region_name=region)
68
+ ec2_client.describe_instances(MaxResults=5)
69
+ accessible_regions.append(region)
70
+ except Exception:
71
+ console.log(f"[yellow]Region {region} is not accessible with the current credentials[/]")
72
+
73
+ if not accessible_regions:
74
+ console.log("[yellow]No accessible regions found. Using default regions.[/]")
75
+ return ["us-east-1", "us-east-2", "us-west-1", "us-west-2"]
76
+
77
+ return accessible_regions
78
+
79
+
80
+ def ec2_summary(session: Session, regions: Optional[List[RegionName]] = None) -> EC2Summary:
81
+ """Get EC2 instance summary across specified regions or all regions."""
82
+ if regions is None:
83
+ regions = [
84
+ "us-east-1",
85
+ "us-east-2",
86
+ "us-west-1",
87
+ "us-west-2",
88
+ "ap-southeast-1",
89
+ "ap-south-1",
90
+ "eu-central-1",
91
+ "eu-west-1",
92
+ "eu-west-2",
93
+ ]
94
+
95
+ instance_summary: EC2Summary = defaultdict(int)
96
+
97
+ for region in regions:
98
+ try:
99
+ ec2_regional = session.client("ec2", region_name=region)
100
+ instances = ec2_regional.describe_instances()
101
+ for reservation in instances["Reservations"]:
102
+ for instance in reservation["Instances"]:
103
+ state = instance["State"]["Name"]
104
+ instance_summary[state] += 1
105
+ except Exception as e:
106
+ console.log(f"[yellow]Warning: Could not access EC2 in region {region}: {str(e)}[/]")
107
+
108
+ if "running" not in instance_summary:
109
+ instance_summary["running"] = 0
110
+ if "stopped" not in instance_summary:
111
+ instance_summary["stopped"] = 0
112
+
113
+ return instance_summary
114
+
115
+
116
+ def get_stopped_instances(session: Session, regions: List[RegionName]) -> Dict[RegionName, List[str]]:
117
+ """Get stopped EC2 instances per region."""
118
+ stopped = {}
119
+ for region in regions:
120
+ try:
121
+ ec2 = session.client("ec2", region_name=region)
122
+ response = ec2.describe_instances(Filters=[{"Name": "instance-state-name", "Values": ["stopped"]}])
123
+ ids = [inst["InstanceId"] for res in response["Reservations"] for inst in res["Instances"]]
124
+ if ids:
125
+ stopped[region] = ids
126
+ except Exception as e:
127
+ console.log(f"[yellow]Warning: Could not fetch stopped instances in {region}: {str(e)}[/]")
128
+ return stopped
129
+
130
+
131
+ def get_unused_volumes(session: Session, regions: List[RegionName]) -> Dict[RegionName, List[str]]:
132
+ """Get unattached EBS volumes per region."""
133
+ unused = {}
134
+ for region in regions:
135
+ try:
136
+ ec2 = session.client("ec2", region_name=region)
137
+ response = ec2.describe_volumes(Filters=[{"Name": "status", "Values": ["available"]}])
138
+ vols = [vol["VolumeId"] for vol in response["Volumes"]]
139
+ if vols:
140
+ unused[region] = vols
141
+ except Exception as e:
142
+ console.log(f"[yellow]Warning: Could not fetch unused volumes in {region}: {str(e)}[/]")
143
+ return unused
144
+
145
+
146
+ def get_unused_eips(session: Session, regions: List[RegionName]) -> Dict[RegionName, List[str]]:
147
+ """Get unused Elastic IPs per region."""
148
+ eips = {}
149
+ for region in regions:
150
+ try:
151
+ ec2 = session.client("ec2", region_name=region)
152
+ response = ec2.describe_addresses()
153
+ free = [addr["PublicIp"] for addr in response["Addresses"] if not addr.get("AssociationId")]
154
+ if free:
155
+ eips[region] = free
156
+ except Exception as e:
157
+ console.log(f"[yellow]Warning: Could not fetch EIPs in {region}: {str(e)}[/]")
158
+ return eips
159
+
160
+
161
+ def get_untagged_resources(session: Session, regions: List[str]) -> Dict[str, Dict[str, List[str]]]:
162
+ result: Dict[str, Dict[str, List[str]]] = {
163
+ "EC2": {},
164
+ "RDS": {},
165
+ "Lambda": {},
166
+ "ELBv2": {},
167
+ }
168
+
169
+ for region in regions:
170
+ # EC2
171
+ try:
172
+ ec2 = session.client("ec2", region_name=region)
173
+ response = ec2.describe_instances()
174
+ for reservation in response["Reservations"]:
175
+ for instance in reservation["Instances"]:
176
+ if not instance.get("Tags"):
177
+ result["EC2"].setdefault(region, []).append(instance["InstanceId"])
178
+ except Exception as e:
179
+ console.log(f"[yellow]Warning: Could not fetch EC2 instances in {region}: {str(e)}[/]")
180
+
181
+ # RDS
182
+ try:
183
+ rds = session.client("rds", region_name=region)
184
+ response = rds.describe_db_instances()
185
+ for db_instance in response["DBInstances"]:
186
+ arn = db_instance["DBInstanceArn"]
187
+ tags = rds.list_tags_for_resource(ResourceName=arn).get("TagList", [])
188
+ if not tags:
189
+ result["RDS"].setdefault(region, []).append(db_instance["DBInstanceIdentifier"])
190
+ except Exception as e:
191
+ console.log(f"[yellow]Warning: Could not fetch RDS instances in {region}: {str(e)}[/]")
192
+
193
+ # Lambda
194
+ try:
195
+ lambda_client = session.client("lambda", region_name=region)
196
+ response = lambda_client.list_functions()
197
+ for function in response["Functions"]:
198
+ arn = function["FunctionArn"]
199
+ tags = lambda_client.list_tags(Resource=arn).get("Tags", {})
200
+ if not tags:
201
+ result["Lambda"].setdefault(region, []).append(function["FunctionName"])
202
+ except Exception as e:
203
+ console.log(f"[yellow]Warning: Could not fetch Lambda functions in {region}: {str(e)}[/]")
204
+
205
+ # ELBv2
206
+ try:
207
+ elbv2 = session.client("elbv2", region_name=region)
208
+ lbs = elbv2.describe_load_balancers().get("LoadBalancers", [])
209
+
210
+ if lbs:
211
+ arn_to_name = {lb["LoadBalancerArn"]: lb["LoadBalancerName"] for lb in lbs}
212
+ arns = list(arn_to_name.keys())
213
+
214
+ tags_response = elbv2.describe_tags(ResourceArns=arns)
215
+ for desc in tags_response["TagDescriptions"]:
216
+ arn = desc["ResourceArn"]
217
+ if not desc.get("Tags"):
218
+ lb_name = arn_to_name.get(arn, arn)
219
+ result["ELBv2"].setdefault(region, []).append(lb_name)
220
+ except Exception as e:
221
+ console.log(f"[yellow]Warning: Could not fetch ELBv2 load balancers in {region}: {str(e)}[/]")
222
+
223
+ return result
224
+
225
+
226
+ def get_budgets(session: Session) -> List[BudgetInfo]:
227
+ account_id = get_account_id(session)
228
+ budgets = session.client("budgets", region_name="us-east-1")
229
+
230
+ budgets_data: List[BudgetInfo] = []
231
+ try:
232
+ response = budgets.describe_budgets(AccountId=account_id)
233
+ for budget in response["Budgets"]:
234
+ budgets_data.append(
235
+ {
236
+ "name": budget["BudgetName"],
237
+ "limit": float(budget["BudgetLimit"]["Amount"]),
238
+ "actual": float(budget["CalculatedSpend"]["ActualSpend"]["Amount"]),
239
+ "forecast": float(budget["CalculatedSpend"].get("ForecastedSpend", {}).get("Amount", 0.0)) or None,
240
+ }
241
+ )
242
+ except Exception as e:
243
+ pass
244
+
245
+ return budgets_data
runbooks/finops/cli.py ADDED
@@ -0,0 +1,151 @@
1
+ import argparse
2
+ import sys
3
+ from typing import Any, Dict, Optional
4
+
5
+ import requests
6
+ from packaging import version
7
+ from rich.console import Console
8
+
9
+ from runbooks.finops.helpers import load_config_file
10
+
11
+ console = Console()
12
+
13
+ __version__ = "0.7.0"
14
+
15
+
16
+ def welcome_banner() -> None:
17
+ banner = rf"""
18
+ [bold red]
19
+ /$$$$$$ /$$ /$$ /$$$$$$ /$$$$$$$$ /$$ /$$$$$$
20
+ /$$__ $$| $$ /$ | $$ /$$__ $$ | $$_____/|__/ /$$__ $$
21
+ | $$ \ $$| $$ /$$$| $$| $$ \__/ | $$ /$$ /$$$$$$$ | $$ \ $$ /$$$$$$ /$$$$$$$
22
+ | $$$$$$$$| $$/$$ $$ $$| $$$$$$ | $$$$$ | $$| $$__ $$| $$ | $$ /$$__ $$ /$$_____/
23
+ | $$__ $$| $$$$_ $$$$ \____ $$ | $$__/ | $$| $$ \ $$| $$ | $$| $$ \ $$| $$$$$$
24
+ | $$ | $$| $$$/ \ $$$ /$$ \ $$ | $$ | $$| $$ | $$| $$ | $$| $$ | $$ \____ $$
25
+ | $$ | $$| $$/ \ $$| $$$$$$/ | $$ | $$| $$ | $$| $$$$$$/| $$$$$$$/ /$$$$$$$/
26
+ |__/ |__/|__/ \__/ \______/ |__/ |__/|__/ |__/ \______/ | $$____/ |_______/
27
+ | $$
28
+ | $$
29
+ |__/
30
+ [/]
31
+ [bold bright_blue]AWS FinOps Dashboard CLI (v{__version__})[/]
32
+ """
33
+ console.print(banner)
34
+
35
+
36
+ def check_latest_version() -> None:
37
+ """Check for the latest version of the AWS FinOps Dashboard (CLI)."""
38
+ try:
39
+ response = requests.get("https://pypi.org/pypi/aws-finops-dashboard/json", timeout=3)
40
+ latest = response.json()["info"]["version"]
41
+ if version.parse(latest) > version.parse(__version__):
42
+ console.print(f"[bold red]A new version of AWS FinOps Dashboard is available: {latest}[/]")
43
+ console.print(
44
+ "[bold bright_yellow]Please update using:\npipx upgrade aws-finops-dashboard\nor\npip install --upgrade aws-finops-dashboard\n[/]"
45
+ )
46
+ except Exception:
47
+ pass
48
+
49
+
50
+ def main() -> int:
51
+ """Command-line interface entry point."""
52
+ welcome_banner()
53
+ check_latest_version()
54
+ from runbooks.finops.dashboard_runner import run_dashboard
55
+
56
+ # Create the parser instance to be accessible for get_default
57
+ parser = argparse.ArgumentParser(description="AWS FinOps Dashboard CLI")
58
+
59
+ parser.add_argument(
60
+ "--config-file",
61
+ "-C",
62
+ help="Path to a TOML, YAML, or JSON configuration file.",
63
+ type=str,
64
+ )
65
+ parser.add_argument(
66
+ "--profiles",
67
+ "-p",
68
+ nargs="+",
69
+ help="Specific AWS profiles to use (space-separated)",
70
+ type=str,
71
+ )
72
+ parser.add_argument(
73
+ "--regions",
74
+ "-r",
75
+ nargs="+",
76
+ help="AWS regions to check for EC2 instances (space-separated)",
77
+ type=str,
78
+ )
79
+ parser.add_argument("--all", "-a", action="store_true", help="Use all available AWS profiles")
80
+ parser.add_argument(
81
+ "--combine",
82
+ "-c",
83
+ action="store_true",
84
+ help="Combine profiles from the same AWS account",
85
+ )
86
+ parser.add_argument(
87
+ "--report-name",
88
+ "-n",
89
+ help="Specify the base name for the report file (without extension)",
90
+ default=None,
91
+ type=str,
92
+ )
93
+ parser.add_argument(
94
+ "--report-type",
95
+ "-y",
96
+ nargs="+",
97
+ choices=["csv", "json", "pdf"],
98
+ help="Specify one or more report types: csv and/or json and/or pdf (space-separated)",
99
+ type=str,
100
+ default=["csv"],
101
+ )
102
+ parser.add_argument(
103
+ "--dir",
104
+ "-d",
105
+ help="Directory to save the report files (default: current directory)",
106
+ type=str,
107
+ )
108
+ parser.add_argument(
109
+ "--time-range",
110
+ "-t",
111
+ help="Time range for cost data in days (default: current month). Examples: 7, 30, 90",
112
+ type=int,
113
+ )
114
+ parser.add_argument(
115
+ "--tag",
116
+ "-g",
117
+ nargs="+",
118
+ help="Cost allocation tag to filter resources, e.g., --tag Team=DevOps",
119
+ type=str,
120
+ )
121
+ parser.add_argument(
122
+ "--trend",
123
+ action="store_true",
124
+ help="Display a trend report as bars for the past 6 months time range",
125
+ )
126
+ parser.add_argument(
127
+ "--audit",
128
+ action="store_true",
129
+ help="Display an audit report with cost anomalies, stopped EC2 instances, unused EBS columes, budget alerts, and more",
130
+ )
131
+
132
+ args = parser.parse_args()
133
+
134
+ config_data: Optional[Dict[str, Any]] = None
135
+ if args.config_file:
136
+ config_data = load_config_file(args.config_file)
137
+ if config_data is None:
138
+ return 1 # Exit if config file loading failed
139
+
140
+ # Override args with config_data if present and arg is not set via CLI
141
+ if config_data:
142
+ for key, value in config_data.items():
143
+ if hasattr(args, key) and getattr(args, key) == parser.get_default(key):
144
+ setattr(args, key, value)
145
+
146
+ result = run_dashboard(args)
147
+ return 0 if result == 0 else 1
148
+
149
+
150
+ if __name__ == "__main__":
151
+ sys.exit(main())