runbooks 0.2.5__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.
- conftest.py +26 -0
- jupyter-agent/.env.template +2 -0
- jupyter-agent/.gitattributes +35 -0
- jupyter-agent/README.md +16 -0
- jupyter-agent/app.py +256 -0
- jupyter-agent/cloudops-agent.png +0 -0
- jupyter-agent/ds-system-prompt.txt +154 -0
- jupyter-agent/jupyter-agent.png +0 -0
- jupyter-agent/llama3_template.jinja +123 -0
- jupyter-agent/requirements.txt +9 -0
- jupyter-agent/utils.py +409 -0
- runbooks/__init__.py +71 -3
- runbooks/__main__.py +13 -0
- runbooks/aws/ec2_describe_instances.py +1 -1
- runbooks/aws/ec2_run_instances.py +8 -2
- runbooks/aws/ec2_start_stop_instances.py +17 -4
- runbooks/aws/ec2_unused_volumes.py +5 -1
- runbooks/aws/s3_create_bucket.py +4 -2
- runbooks/aws/s3_list_objects.py +6 -1
- runbooks/aws/tagging_lambda_handler.py +13 -2
- runbooks/aws/tags.json +12 -0
- runbooks/base.py +353 -0
- runbooks/cfat/README.md +49 -0
- runbooks/cfat/__init__.py +74 -0
- runbooks/cfat/app.ts +644 -0
- runbooks/cfat/assessment/__init__.py +40 -0
- runbooks/cfat/assessment/asana-import.csv +39 -0
- runbooks/cfat/assessment/cfat-checks.csv +31 -0
- runbooks/cfat/assessment/cfat.txt +520 -0
- runbooks/cfat/assessment/collectors.py +200 -0
- runbooks/cfat/assessment/jira-import.csv +39 -0
- runbooks/cfat/assessment/runner.py +387 -0
- runbooks/cfat/assessment/validators.py +290 -0
- runbooks/cfat/cli.py +103 -0
- runbooks/cfat/docs/asana-import.csv +24 -0
- runbooks/cfat/docs/cfat-checks.csv +31 -0
- runbooks/cfat/docs/cfat.txt +335 -0
- runbooks/cfat/docs/checks-output.png +0 -0
- runbooks/cfat/docs/cloudshell-console-run.png +0 -0
- runbooks/cfat/docs/cloudshell-download.png +0 -0
- runbooks/cfat/docs/cloudshell-output.png +0 -0
- runbooks/cfat/docs/downloadfile.png +0 -0
- runbooks/cfat/docs/jira-import.csv +24 -0
- runbooks/cfat/docs/open-cloudshell.png +0 -0
- runbooks/cfat/docs/report-header.png +0 -0
- runbooks/cfat/models.py +1026 -0
- runbooks/cfat/package-lock.json +5116 -0
- runbooks/cfat/package.json +38 -0
- runbooks/cfat/report.py +496 -0
- runbooks/cfat/reporting/__init__.py +46 -0
- runbooks/cfat/reporting/exporters.py +337 -0
- runbooks/cfat/reporting/formatters.py +496 -0
- runbooks/cfat/reporting/templates.py +135 -0
- runbooks/cfat/run-assessment.sh +23 -0
- runbooks/cfat/runner.py +69 -0
- runbooks/cfat/src/actions/check-cloudtrail-existence.ts +43 -0
- runbooks/cfat/src/actions/check-config-existence.ts +37 -0
- runbooks/cfat/src/actions/check-control-tower.ts +37 -0
- runbooks/cfat/src/actions/check-ec2-existence.ts +46 -0
- runbooks/cfat/src/actions/check-iam-users.ts +50 -0
- runbooks/cfat/src/actions/check-legacy-cur.ts +30 -0
- runbooks/cfat/src/actions/check-org-cloudformation.ts +30 -0
- runbooks/cfat/src/actions/check-vpc-existence.ts +43 -0
- runbooks/cfat/src/actions/create-asanaimport.ts +14 -0
- runbooks/cfat/src/actions/create-backlog.ts +372 -0
- runbooks/cfat/src/actions/create-jiraimport.ts +15 -0
- runbooks/cfat/src/actions/create-report.ts +616 -0
- runbooks/cfat/src/actions/define-account-type.ts +51 -0
- runbooks/cfat/src/actions/get-enabled-org-policy-types.ts +40 -0
- runbooks/cfat/src/actions/get-enabled-org-services.ts +26 -0
- runbooks/cfat/src/actions/get-idc-info.ts +34 -0
- runbooks/cfat/src/actions/get-org-da-accounts.ts +34 -0
- runbooks/cfat/src/actions/get-org-details.ts +35 -0
- runbooks/cfat/src/actions/get-org-member-accounts.ts +44 -0
- runbooks/cfat/src/actions/get-org-ous.ts +35 -0
- runbooks/cfat/src/actions/get-regions.ts +22 -0
- runbooks/cfat/src/actions/zip-assessment.ts +27 -0
- runbooks/cfat/src/types/index.d.ts +147 -0
- runbooks/cfat/tests/__init__.py +141 -0
- runbooks/cfat/tests/test_cli.py +340 -0
- runbooks/cfat/tests/test_integration.py +290 -0
- runbooks/cfat/tests/test_models.py +505 -0
- runbooks/cfat/tests/test_reporting.py +354 -0
- runbooks/cfat/tsconfig.json +16 -0
- runbooks/cfat/webpack.config.cjs +27 -0
- runbooks/config.py +260 -0
- runbooks/finops/__init__.py +88 -0
- runbooks/finops/aws_client.py +245 -0
- runbooks/finops/cli.py +151 -0
- runbooks/finops/cost_processor.py +410 -0
- runbooks/finops/dashboard_runner.py +448 -0
- runbooks/finops/helpers.py +355 -0
- runbooks/finops/main.py +14 -0
- runbooks/finops/profile_processor.py +174 -0
- runbooks/finops/types.py +66 -0
- runbooks/finops/visualisations.py +80 -0
- runbooks/inventory/.gitignore +354 -0
- runbooks/inventory/ArgumentsClass.py +261 -0
- runbooks/inventory/Inventory_Modules.py +6130 -0
- runbooks/inventory/LandingZone/delete_lz.py +1075 -0
- runbooks/inventory/README.md +1320 -0
- runbooks/inventory/__init__.py +62 -0
- runbooks/inventory/account_class.py +532 -0
- runbooks/inventory/all_my_instances_wrapper.py +123 -0
- runbooks/inventory/aws_decorators.py +201 -0
- runbooks/inventory/cfn_move_stack_instances.py +1526 -0
- runbooks/inventory/check_cloudtrail_compliance.py +614 -0
- runbooks/inventory/check_controltower_readiness.py +1107 -0
- runbooks/inventory/check_landingzone_readiness.py +711 -0
- runbooks/inventory/cloudtrail.md +727 -0
- runbooks/inventory/collectors/__init__.py +20 -0
- runbooks/inventory/collectors/aws_compute.py +518 -0
- runbooks/inventory/collectors/aws_networking.py +275 -0
- runbooks/inventory/collectors/base.py +222 -0
- runbooks/inventory/core/__init__.py +19 -0
- runbooks/inventory/core/collector.py +303 -0
- runbooks/inventory/core/formatter.py +296 -0
- runbooks/inventory/delete_s3_buckets_objects.py +169 -0
- runbooks/inventory/discovery.md +81 -0
- runbooks/inventory/draw_org_structure.py +748 -0
- runbooks/inventory/ec2_vpc_utils.py +341 -0
- runbooks/inventory/find_cfn_drift_detection.py +272 -0
- runbooks/inventory/find_cfn_orphaned_stacks.py +719 -0
- runbooks/inventory/find_cfn_stackset_drift.py +733 -0
- runbooks/inventory/find_ec2_security_groups.py +669 -0
- runbooks/inventory/find_landingzone_versions.py +201 -0
- runbooks/inventory/find_vpc_flow_logs.py +1221 -0
- runbooks/inventory/inventory.sh +659 -0
- runbooks/inventory/list_cfn_stacks.py +558 -0
- runbooks/inventory/list_cfn_stackset_operation_results.py +252 -0
- runbooks/inventory/list_cfn_stackset_operations.py +734 -0
- runbooks/inventory/list_cfn_stacksets.py +453 -0
- runbooks/inventory/list_config_recorders_delivery_channels.py +681 -0
- runbooks/inventory/list_ds_directories.py +354 -0
- runbooks/inventory/list_ec2_availability_zones.py +286 -0
- runbooks/inventory/list_ec2_ebs_volumes.py +244 -0
- runbooks/inventory/list_ec2_instances.py +425 -0
- runbooks/inventory/list_ecs_clusters_and_tasks.py +562 -0
- runbooks/inventory/list_elbs_load_balancers.py +411 -0
- runbooks/inventory/list_enis_network_interfaces.py +526 -0
- runbooks/inventory/list_guardduty_detectors.py +568 -0
- runbooks/inventory/list_iam_policies.py +404 -0
- runbooks/inventory/list_iam_roles.py +518 -0
- runbooks/inventory/list_iam_saml_providers.py +359 -0
- runbooks/inventory/list_lambda_functions.py +882 -0
- runbooks/inventory/list_org_accounts.py +446 -0
- runbooks/inventory/list_org_accounts_users.py +354 -0
- runbooks/inventory/list_rds_db_instances.py +406 -0
- runbooks/inventory/list_route53_hosted_zones.py +318 -0
- runbooks/inventory/list_servicecatalog_provisioned_products.py +575 -0
- runbooks/inventory/list_sns_topics.py +360 -0
- runbooks/inventory/list_ssm_parameters.py +402 -0
- runbooks/inventory/list_vpc_subnets.py +433 -0
- runbooks/inventory/list_vpcs.py +422 -0
- runbooks/inventory/lockdown_cfn_stackset_role.py +224 -0
- runbooks/inventory/models/__init__.py +24 -0
- runbooks/inventory/models/account.py +192 -0
- runbooks/inventory/models/inventory.py +309 -0
- runbooks/inventory/models/resource.py +247 -0
- runbooks/inventory/recover_cfn_stack_ids.py +205 -0
- runbooks/inventory/requirements.txt +12 -0
- runbooks/inventory/run_on_multi_accounts.py +211 -0
- runbooks/inventory/tests/common_test_data.py +3661 -0
- runbooks/inventory/tests/common_test_functions.py +204 -0
- runbooks/inventory/tests/script_test_data.py +0 -0
- runbooks/inventory/tests/setup.py +24 -0
- runbooks/inventory/tests/src.py +18 -0
- runbooks/inventory/tests/test_cfn_describe_stacks.py +208 -0
- runbooks/inventory/tests/test_ec2_describe_instances.py +162 -0
- runbooks/inventory/tests/test_inventory_modules.py +55 -0
- runbooks/inventory/tests/test_lambda_list_functions.py +86 -0
- runbooks/inventory/tests/test_moto_integration_example.py +273 -0
- runbooks/inventory/tests/test_org_list_accounts.py +49 -0
- runbooks/inventory/update_aws_actions.py +173 -0
- runbooks/inventory/update_cfn_stacksets.py +1215 -0
- runbooks/inventory/update_cloudwatch_logs_retention_policy.py +294 -0
- runbooks/inventory/update_iam_roles_cross_accounts.py +478 -0
- runbooks/inventory/update_s3_public_access_block.py +539 -0
- runbooks/inventory/utils/__init__.py +23 -0
- runbooks/inventory/utils/aws_helpers.py +510 -0
- runbooks/inventory/utils/threading_utils.py +493 -0
- runbooks/inventory/utils/validation.py +682 -0
- runbooks/inventory/verify_ec2_security_groups.py +1430 -0
- runbooks/main.py +785 -0
- runbooks/organizations/__init__.py +12 -0
- runbooks/organizations/manager.py +374 -0
- runbooks/security_baseline/README.md +324 -0
- runbooks/security_baseline/checklist/alternate_contacts.py +8 -1
- runbooks/security_baseline/checklist/bucket_public_access.py +4 -1
- runbooks/security_baseline/checklist/cloudwatch_alarm_configuration.py +9 -2
- runbooks/security_baseline/checklist/guardduty_enabled.py +9 -2
- runbooks/security_baseline/checklist/multi_region_instance_usage.py +5 -1
- runbooks/security_baseline/checklist/root_access_key.py +6 -1
- runbooks/security_baseline/config-origin.json +1 -1
- runbooks/security_baseline/config.json +1 -1
- runbooks/security_baseline/permission.json +1 -1
- runbooks/security_baseline/report_generator.py +10 -2
- runbooks/security_baseline/report_template_en.html +7 -7
- runbooks/security_baseline/report_template_jp.html +7 -7
- runbooks/security_baseline/report_template_kr.html +12 -12
- runbooks/security_baseline/report_template_vn.html +7 -7
- runbooks/security_baseline/requirements.txt +7 -0
- runbooks/security_baseline/run_script.py +8 -2
- runbooks/security_baseline/security_baseline_tester.py +10 -2
- runbooks/security_baseline/utils/common.py +5 -1
- runbooks/utils/__init__.py +204 -0
- runbooks-0.6.1.dist-info/METADATA +373 -0
- runbooks-0.6.1.dist-info/RECORD +237 -0
- {runbooks-0.2.5.dist-info → runbooks-0.6.1.dist-info}/WHEEL +1 -1
- runbooks-0.6.1.dist-info/entry_points.txt +7 -0
- runbooks-0.6.1.dist-info/licenses/LICENSE +201 -0
- runbooks-0.6.1.dist-info/top_level.txt +3 -0
- runbooks/python101/calculator.py +0 -34
- runbooks/python101/config.py +0 -1
- runbooks/python101/exceptions.py +0 -16
- runbooks/python101/file_manager.py +0 -218
- runbooks/python101/toolkit.py +0 -153
- runbooks-0.2.5.dist-info/METADATA +0 -439
- runbooks-0.2.5.dist-info/RECORD +0 -61
- runbooks-0.2.5.dist-info/entry_points.txt +0 -3
- runbooks-0.2.5.dist-info/top_level.txt +0 -1
@@ -24,19 +24,19 @@
|
|
24
24
|
</path>
|
25
25
|
</symbol>
|
26
26
|
<symbol id="bi-x-circle-fill" fill="currentColor" viewBox="0 0 16 16">
|
27
|
-
<path
|
27
|
+
<path
|
28
28
|
d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z">
|
29
29
|
</path>
|
30
30
|
</symbol>
|
31
31
|
<symbol id="bi-dash-circle" fill="currentColor" viewBox="0 0 16 16">
|
32
|
-
<path
|
32
|
+
<path
|
33
33
|
d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z">
|
34
34
|
</path>
|
35
|
-
<path
|
35
|
+
<path
|
36
36
|
d="M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z">
|
37
37
|
</path>
|
38
38
|
</symbol>
|
39
|
-
</svg>
|
39
|
+
</svg>
|
40
40
|
|
41
41
|
<div class="container">
|
42
42
|
<div class="row justify-content-center">
|
@@ -88,15 +88,15 @@
|
|
88
88
|
{% for section in result_sections %}
|
89
89
|
<div class="card-body" id="{{ section.level|lower }}-list">
|
90
90
|
<div class="accordion" id="accordionSection{{ section.level }}">
|
91
|
-
|
91
|
+
|
92
92
|
{% for item in section.result_items %}
|
93
93
|
<div class="accordion-item">
|
94
94
|
<h2 class="accordion-header" id="panelsStayOpen-heading-{{ section.level|lower }}-{{ '%02d'|format(loop.index) }}">
|
95
95
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#panelsStayOpen-collapse-{{ section.level|lower }}-{{ '%02d'|format(loop.index)}}" aria-expanded="true" aria-controls="panelsStayOpen-collapse-{{ section.level|lower }}-{{ '%02d'|format(loop.index)}}">
|
96
96
|
<span class="badge text-bg-{% if section.level == 'Error' %}dark{% elif section.level == 'Info' %}primary{% else %}{{ section.level|lower }}{% endif %} rounded-pill">{{ section.level }}</span>
|
97
|
-
<div class="ms-2 me-auto">{{ item.title }}</div>
|
97
|
+
<div class="ms-2 me-auto">{{ item.title }}</div>
|
98
98
|
</button>
|
99
|
-
</h2>
|
99
|
+
</h2>
|
100
100
|
<div id="panelsStayOpen-collapse-{{ section.level|lower }}-{{ '%02d'|format(loop.index)}}" class="accordion-collapse collapse" aria-labelledby="panelsStayOpen-heading-{{ section.level|lower }}-{{ '%02d'|format(loop.index) }}">
|
101
101
|
<div class="accordion-body">
|
102
102
|
<div class="alert alert-{% if section.level == 'Error' %}dark{% elif section.level == 'Info' %}primary{% else %}{{ section.level|lower }}{% endif %} d-flex align-items-center" role="alert">
|
@@ -37,7 +37,9 @@ def parse_arguments():
|
|
37
37
|
description="AWS Security Baseline Tester - Evaluate your AWS account's security configuration."
|
38
38
|
)
|
39
39
|
parser.add_argument(
|
40
|
-
"--profile",
|
40
|
+
"--profile",
|
41
|
+
default="default",
|
42
|
+
help="AWS IAM profile to use for authentication (default: 'default').",
|
41
43
|
)
|
42
44
|
parser.add_argument(
|
43
45
|
"--language",
|
@@ -45,7 +47,11 @@ def parse_arguments():
|
|
45
47
|
default="EN",
|
46
48
|
help="Language for the Security Baseline Report (default: 'EN').",
|
47
49
|
)
|
48
|
-
parser.add_argument(
|
50
|
+
parser.add_argument(
|
51
|
+
"--output",
|
52
|
+
default=None,
|
53
|
+
help="Custom output directory for HTML results (default: ./results).",
|
54
|
+
)
|
49
55
|
return parser.parse_args()
|
50
56
|
|
51
57
|
|
@@ -60,7 +60,10 @@ class SecurityBaselineTester:
|
|
60
60
|
|
61
61
|
logging.info(self.translator.translate("test_completed"))
|
62
62
|
except Exception as e:
|
63
|
-
logging.error(
|
63
|
+
logging.error(
|
64
|
+
f"An error occurred during the security baseline test: {str(e)}",
|
65
|
+
exc_info=True,
|
66
|
+
)
|
64
67
|
|
65
68
|
def _validate_session(self):
|
66
69
|
if self.session.region_name is None:
|
@@ -122,7 +125,12 @@ class SecurityBaselineTester:
|
|
122
125
|
"iam_password_policy",
|
123
126
|
]:
|
124
127
|
return check_method(self.session, translator)
|
125
|
-
elif check_name in [
|
128
|
+
elif check_name in [
|
129
|
+
"root_mfa",
|
130
|
+
"root_usage",
|
131
|
+
"root_access_key",
|
132
|
+
"iam_user_mfa",
|
133
|
+
]:
|
126
134
|
return check_method(self.session, translator, credential_report)
|
127
135
|
elif check_name == "trusted_advisor":
|
128
136
|
return check_method(translator)
|
@@ -15,7 +15,11 @@ class Ret:
|
|
15
15
|
self.headers = {"Content-Type": "text/html;charset=UTF-8"}
|
16
16
|
|
17
17
|
def to_dict(self) -> dict:
|
18
|
-
return {
|
18
|
+
return {
|
19
|
+
"statusCode": self.status_code,
|
20
|
+
"body": self.body,
|
21
|
+
"headers": self.headers,
|
22
|
+
}
|
19
23
|
|
20
24
|
|
21
25
|
class CheckResult:
|
runbooks/utils/__init__.py
CHANGED
@@ -0,0 +1,204 @@
|
|
1
|
+
"""
|
2
|
+
Utilities package for CloudOps Runbooks.
|
3
|
+
|
4
|
+
This package provides utility modules including logging, configuration,
|
5
|
+
and helper functions used throughout the runbooks package.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import sys
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import Optional
|
11
|
+
|
12
|
+
try:
|
13
|
+
from loguru import logger
|
14
|
+
|
15
|
+
_HAS_LOGURU = True
|
16
|
+
except ImportError:
|
17
|
+
import logging
|
18
|
+
|
19
|
+
logger = logging.getLogger(__name__)
|
20
|
+
_HAS_LOGURU = False
|
21
|
+
|
22
|
+
# Legacy utilities
|
23
|
+
from runbooks.utils.logger import configure_logger
|
24
|
+
|
25
|
+
|
26
|
+
def setup_logging(debug: bool = False, log_file: Optional[str] = None) -> None:
|
27
|
+
"""
|
28
|
+
Configure logging for the application.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
debug: Enable debug logging if True
|
32
|
+
log_file: Optional path to log file
|
33
|
+
"""
|
34
|
+
if _HAS_LOGURU:
|
35
|
+
from loguru import logger as loguru_logger
|
36
|
+
|
37
|
+
# Remove default handler
|
38
|
+
loguru_logger.remove()
|
39
|
+
|
40
|
+
# Console handler with appropriate level
|
41
|
+
log_level = "DEBUG" if debug else "INFO"
|
42
|
+
loguru_logger.add(
|
43
|
+
sys.stderr,
|
44
|
+
level=log_level,
|
45
|
+
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
46
|
+
colorize=True,
|
47
|
+
)
|
48
|
+
|
49
|
+
# File handler if specified
|
50
|
+
if log_file:
|
51
|
+
log_path = Path(log_file)
|
52
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
53
|
+
|
54
|
+
loguru_logger.add(
|
55
|
+
log_path,
|
56
|
+
level="DEBUG",
|
57
|
+
format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
|
58
|
+
rotation="10 MB",
|
59
|
+
retention="7 days",
|
60
|
+
compression="zip",
|
61
|
+
)
|
62
|
+
|
63
|
+
loguru_logger.info(f"Logging initialized with level: {log_level}")
|
64
|
+
else:
|
65
|
+
# Fallback to standard logging
|
66
|
+
import logging
|
67
|
+
|
68
|
+
log_level = logging.DEBUG if debug else logging.INFO
|
69
|
+
logging.basicConfig(
|
70
|
+
level=log_level,
|
71
|
+
format="%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d - %(message)s",
|
72
|
+
handlers=[logging.StreamHandler(sys.stderr)],
|
73
|
+
)
|
74
|
+
|
75
|
+
if log_file:
|
76
|
+
log_path = Path(log_file)
|
77
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
78
|
+
file_handler = logging.FileHandler(log_path)
|
79
|
+
file_handler.setFormatter(
|
80
|
+
logging.Formatter("%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d - %(message)s")
|
81
|
+
)
|
82
|
+
logging.getLogger().addHandler(file_handler)
|
83
|
+
|
84
|
+
logging.info(f"Logging initialized with level: {log_level}")
|
85
|
+
|
86
|
+
|
87
|
+
def validate_aws_profile(profile: str) -> bool:
|
88
|
+
"""
|
89
|
+
Validate that an AWS profile exists in credentials.
|
90
|
+
|
91
|
+
Args:
|
92
|
+
profile: AWS profile name to validate
|
93
|
+
|
94
|
+
Returns:
|
95
|
+
True if profile exists, False otherwise
|
96
|
+
"""
|
97
|
+
import configparser
|
98
|
+
|
99
|
+
try:
|
100
|
+
# Check ~/.aws/credentials
|
101
|
+
credentials_path = Path.home() / ".aws" / "credentials"
|
102
|
+
if credentials_path.exists():
|
103
|
+
config = configparser.ConfigParser()
|
104
|
+
config.read(credentials_path)
|
105
|
+
if profile in config.sections():
|
106
|
+
return True
|
107
|
+
|
108
|
+
# Check ~/.aws/config
|
109
|
+
config_path = Path.home() / ".aws" / "config"
|
110
|
+
if config_path.exists():
|
111
|
+
config = configparser.ConfigParser()
|
112
|
+
config.read(config_path)
|
113
|
+
profile_section = f"profile {profile}" if profile != "default" else "default"
|
114
|
+
if profile_section in config.sections():
|
115
|
+
return True
|
116
|
+
|
117
|
+
return False
|
118
|
+
except Exception as e:
|
119
|
+
logger.warning(f"Error validating AWS profile '{profile}': {e}")
|
120
|
+
return False
|
121
|
+
|
122
|
+
|
123
|
+
def ensure_directory(path: Path) -> Path:
|
124
|
+
"""
|
125
|
+
Ensure directory exists, creating it if necessary.
|
126
|
+
|
127
|
+
Args:
|
128
|
+
path: Directory path to ensure
|
129
|
+
|
130
|
+
Returns:
|
131
|
+
The directory path
|
132
|
+
"""
|
133
|
+
path.mkdir(parents=True, exist_ok=True)
|
134
|
+
return path
|
135
|
+
|
136
|
+
|
137
|
+
def format_size(size_bytes: int) -> str:
|
138
|
+
"""
|
139
|
+
Format byte size in human readable format.
|
140
|
+
|
141
|
+
Args:
|
142
|
+
size_bytes: Size in bytes
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
Formatted size string (e.g., "1.5 GB")
|
146
|
+
"""
|
147
|
+
if size_bytes == 0:
|
148
|
+
return "0 B"
|
149
|
+
|
150
|
+
size_names = ["B", "KB", "MB", "GB", "TB", "PB"]
|
151
|
+
size_index = 0
|
152
|
+
size = float(size_bytes)
|
153
|
+
|
154
|
+
while size >= 1024 and size_index < len(size_names) - 1:
|
155
|
+
size /= 1024
|
156
|
+
size_index += 1
|
157
|
+
|
158
|
+
return f"{size:.1f} {size_names[size_index]}"
|
159
|
+
|
160
|
+
|
161
|
+
def retry_with_backoff(max_retries: int = 3, backoff_factor: float = 1.0):
|
162
|
+
"""
|
163
|
+
Decorator for retrying operations with exponential backoff.
|
164
|
+
|
165
|
+
Args:
|
166
|
+
max_retries: Maximum number of retry attempts
|
167
|
+
backoff_factor: Factor for exponential backoff
|
168
|
+
"""
|
169
|
+
import time
|
170
|
+
from functools import wraps
|
171
|
+
|
172
|
+
def decorator(func):
|
173
|
+
@wraps(func)
|
174
|
+
def wrapper(*args, **kwargs):
|
175
|
+
last_exception = None
|
176
|
+
|
177
|
+
for attempt in range(max_retries + 1):
|
178
|
+
try:
|
179
|
+
return func(*args, **kwargs)
|
180
|
+
except Exception as e:
|
181
|
+
last_exception = e
|
182
|
+
if attempt == max_retries:
|
183
|
+
logger.error(f"Failed after {max_retries} retries: {e}")
|
184
|
+
raise
|
185
|
+
|
186
|
+
wait_time = backoff_factor * (2**attempt)
|
187
|
+
logger.warning(f"Attempt {attempt + 1} failed: {e}. Retrying in {wait_time}s...")
|
188
|
+
time.sleep(wait_time)
|
189
|
+
|
190
|
+
raise last_exception
|
191
|
+
|
192
|
+
return wrapper
|
193
|
+
|
194
|
+
return decorator
|
195
|
+
|
196
|
+
|
197
|
+
__all__ = [
|
198
|
+
"setup_logging",
|
199
|
+
"validate_aws_profile",
|
200
|
+
"ensure_directory",
|
201
|
+
"format_size",
|
202
|
+
"retry_with_backoff",
|
203
|
+
"configure_logger",
|
204
|
+
]
|