boto3-assist 0.8.0__tar.gz → 0.9.1__tar.gz
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.
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/PKG-INFO +1 -1
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/pyproject.toml +1 -1
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/requirements-dev.txt +1 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/connection.py +43 -11
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/environment_services/environment_loader.py +18 -1
- boto3_assist-0.9.1/src/boto3_assist/s3/s3_event_data.py +168 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/s3/s3_object.py +63 -1
- boto3_assist-0.9.1/src/boto3_assist/securityhub/securityhub.py +150 -0
- boto3_assist-0.9.1/src/boto3_assist/securityhub/securityhub_connection.py +57 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/utilities/numbers_utility.py +47 -10
- boto3_assist-0.9.1/src/boto3_assist/version.py +1 -0
- boto3_assist-0.9.1/tests/s3/s3_event_data_test.py +68 -0
- boto3_assist-0.8.0/src/boto3_assist/version.py +0 -1
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/.env.docker +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/.env.docker.001 +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/.env.docker.nosql.workbench +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/.env.unittest +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/.gitignore +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/.vscode/launch.json +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/.vscode/settings.json +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/.vscode/tasks.json +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/LICENSE-EXPLAINED.txt +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/LICENSE.txt +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/README.md +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/aws_regions_with_status.csv +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/aws_regions_with_status.json +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/devops/build.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/devops/readme.md +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/cloudwatch/log_report.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/models/order_item_model.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/models/order_model.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/models/product_model.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/models/user_model.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/models/user_post_model.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/order_example/main.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/order_example/products.json +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/services/order_item_service.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/services/order_service.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/services/product_service.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/services/table_service.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/services/user_post_service.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/services/user_service.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/services/user_service_client_example.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/services/user_service_resource_example.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/user_post_example/main.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/ec2/regions_report.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/module-headers.txt +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/mypy.ini +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/requirements.txt +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/run-checks.sh +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/run_unit_tests.sh +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/aws_lambda/event_info.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/boto3session.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cloudwatch/cloudwatch_connection.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cloudwatch/cloudwatch_logs.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cloudwatch/cloudwatch_query.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cognito/cognito_authorizer.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cognito/cognito_connection.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cognito/cognito_utility.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cognito/jwks_cache.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cognito/user.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/connection_tracker.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_connection.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_index.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_iservice.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_key.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_model_base.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/readme.md +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/ec2/ec2_connection.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/environment_services/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/environment_services/environment_variables.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/errors/custom_exceptions.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/http_status_codes.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/models/serializable_model.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/s3/s3.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/s3/s3_bucket.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/s3/s3_connection.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/utilities/datetime_utility.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/utilities/dictionaroy_utility.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/utilities/file_operations.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/utilities/http_utility.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/utilities/logging_utility.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/utilities/serialization_utility.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/utilities/string_utility.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/__top/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dbmodels/cms/base.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dbmodels/cms/content_block.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dbmodels/cms/page.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dbmodels/cms/template.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dbmodels/simple_model.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dbmodels/user_model.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dbmodels/user_required_fields_model.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dynamodb_model_base_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dynamodb_model_projections_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dynamodb_model_serializtion_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dynamodb_moto_sorting_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dynamodb_reindex_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/examples_test/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/examples_test/user_service_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/lambda/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/lambda/event_info_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/models/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/models/serializable_model_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/models/serializable_model_wide_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/s3/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/s3/files/test.txt +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/s3/s3_file_delete_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/s3/s3_file_upload_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/utilities/__init__.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/utilities/serialization_utility_test.py +0 -0
- {boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/utilities/string_utility_test.py +0 -0
|
@@ -31,17 +31,10 @@ class Connection:
|
|
|
31
31
|
aws_secret_access_key: Optional[str] = None,
|
|
32
32
|
aws_end_point_url: Optional[str] = None,
|
|
33
33
|
) -> None:
|
|
34
|
-
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
37
|
-
|
|
38
|
-
self.aws_access_key_id = (
|
|
39
|
-
aws_access_key_id or EnvironmentVariables.AWS.DynamoDB.aws_access_key_id()
|
|
40
|
-
)
|
|
41
|
-
self.aws_secret_access_key = (
|
|
42
|
-
aws_secret_access_key
|
|
43
|
-
or EnvironmentVariables.AWS.DynamoDB.aws_secret_access_key()
|
|
44
|
-
)
|
|
34
|
+
self.__aws_profile = aws_profile
|
|
35
|
+
self.__aws_region = aws_region
|
|
36
|
+
self.__aws_access_key_id = aws_access_key_id
|
|
37
|
+
self.__aws_secret_access_key = aws_secret_access_key
|
|
45
38
|
self.end_point_url = aws_end_point_url
|
|
46
39
|
self.__session: Boto3SessionManager | None = None
|
|
47
40
|
|
|
@@ -84,6 +77,45 @@ class Connection:
|
|
|
84
77
|
|
|
85
78
|
tracker.add(service_name=self.service_name)
|
|
86
79
|
|
|
80
|
+
@property
|
|
81
|
+
def asw_profile(self) -> str | None:
|
|
82
|
+
"""The AWS Profile"""
|
|
83
|
+
return self.__aws_profile or EnvironmentVariables.AWS.profile()
|
|
84
|
+
|
|
85
|
+
@asw_profile.setter
|
|
86
|
+
def aws_profile(self, value: str | None):
|
|
87
|
+
self.__aws_profile = value
|
|
88
|
+
|
|
89
|
+
@property
|
|
90
|
+
def aws_region(self) -> str | None:
|
|
91
|
+
"""The AWS Region"""
|
|
92
|
+
return self.__aws_region or EnvironmentVariables.AWS.region()
|
|
93
|
+
|
|
94
|
+
@aws_region.setter
|
|
95
|
+
def aws_region(self, value: str | None):
|
|
96
|
+
self.__aws_region = value
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def aws_access_key_id(self) -> str | None:
|
|
100
|
+
"""The AWS Access Key"""
|
|
101
|
+
return self.__aws_access_key_id or EnvironmentVariables.AWS.aws_access_key_id()
|
|
102
|
+
|
|
103
|
+
@aws_access_key_id.setter
|
|
104
|
+
def aws_access_key_id(self, value: str | None):
|
|
105
|
+
self.__aws_access_key_id = value
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def aws_secret_access_key(self) -> str | None:
|
|
109
|
+
"""The AWS Access Key"""
|
|
110
|
+
return (
|
|
111
|
+
self.__aws_secret_access_key
|
|
112
|
+
or EnvironmentVariables.AWS.aws_secret_access_key()
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
@aws_secret_access_key.setter
|
|
116
|
+
def aws_secret_access_key(self, value: str | None):
|
|
117
|
+
self.__aws_secret_access_key = value
|
|
118
|
+
|
|
87
119
|
@property
|
|
88
120
|
def service_name(self) -> str:
|
|
89
121
|
"""Service Name"""
|
|
@@ -5,7 +5,7 @@ MIT License. See Project Root for the license information.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
-
|
|
8
|
+
import json
|
|
9
9
|
from typing import List, Union, Optional, IO
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from dotenv import load_dotenv
|
|
@@ -109,3 +109,20 @@ class EnvironmentLoader:
|
|
|
109
109
|
)
|
|
110
110
|
|
|
111
111
|
return None
|
|
112
|
+
|
|
113
|
+
def load_event_file(self, full_path: str) -> dict:
|
|
114
|
+
"""Loads an AWS event file"""
|
|
115
|
+
if not os.path.exists(full_path):
|
|
116
|
+
raise RuntimeError(f"Failed to locate event file: {full_path}")
|
|
117
|
+
|
|
118
|
+
event = {}
|
|
119
|
+
with open(full_path, mode="r", encoding="utf-8") as json_file:
|
|
120
|
+
event = json.load(json_file)
|
|
121
|
+
|
|
122
|
+
if isinstance(event, dict) and "message" in event:
|
|
123
|
+
event = event.get("message", {})
|
|
124
|
+
|
|
125
|
+
if isinstance(event, dict) and "event" in event:
|
|
126
|
+
event = event.get("event", {})
|
|
127
|
+
|
|
128
|
+
return event
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any, List
|
|
8
|
+
from boto3_assist.utilities.numbers_utility import NumberUtility
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class EventData:
|
|
12
|
+
"""An Event Data Object"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, event: Dict[str, Any]):
|
|
15
|
+
self.__event = event
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def version(self) -> str:
|
|
19
|
+
"""Event Version"""
|
|
20
|
+
return self.__event.get("version")
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def id(self) -> str:
|
|
24
|
+
"""Event Id"""
|
|
25
|
+
return self.__event.get("id")
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def detail_type(self) -> str:
|
|
29
|
+
"""Event Detail Type"""
|
|
30
|
+
return self.__event.get("detail-type")
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def source(self) -> str:
|
|
34
|
+
"""Event Source"""
|
|
35
|
+
return self.__event.get("source")
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def account(self) -> str:
|
|
39
|
+
"""Event Account"""
|
|
40
|
+
return self.__event.get("account")
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def time(self) -> str:
|
|
44
|
+
"""Event Time"""
|
|
45
|
+
return self.__event.get("time")
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def region(self) -> str:
|
|
49
|
+
"""Event Region"""
|
|
50
|
+
return self.__event.get("region")
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def resources(self) -> List[str]:
|
|
54
|
+
"""Event Resources"""
|
|
55
|
+
return self.__event.get("resources", [])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class S3BucketData:
|
|
59
|
+
"""S3 Bucket Data"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, bucket_event_data: Dict[str, Any]):
|
|
62
|
+
self.__bucket = bucket_event_data
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def name(self) -> str | None:
|
|
66
|
+
"""Bucket Name Key"""
|
|
67
|
+
|
|
68
|
+
return self.__bucket.get("name")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class S3ObjectData:
|
|
72
|
+
"""S3 Object"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, object_data: Dict[str, Any]):
|
|
75
|
+
self.__s3_object_data = object_data
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def key(self) -> str | None:
|
|
79
|
+
"""Object Key"""
|
|
80
|
+
return self.__s3_object_data.get("key")
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def size(self) -> int:
|
|
84
|
+
"""Object size in bytes"""
|
|
85
|
+
size = NumberUtility.to_number(self.__s3_object_data.get("size"))
|
|
86
|
+
return size
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def etag(self) -> str | None:
|
|
90
|
+
"""Object eTag"""
|
|
91
|
+
return self.__s3_object_data.get("etag")
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def version_id(self) -> str | None:
|
|
95
|
+
"""Object Version Id"""
|
|
96
|
+
return self.__s3_object_data.get("version-id")
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def sequencer(self) -> str | None:
|
|
100
|
+
"""Object eTag"""
|
|
101
|
+
return self.__s3_object_data.get("sequencer")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class S3EventDetail:
|
|
105
|
+
"""The Event Detail"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, event: Dict[str, Any]):
|
|
108
|
+
self.__event = event
|
|
109
|
+
self.__s3_object_data: S3ObjectData | None = None
|
|
110
|
+
self.__s3_bucket_data: S3BucketData | None = None
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def version(self) -> str | None:
|
|
114
|
+
"""Object Key"""
|
|
115
|
+
|
|
116
|
+
return self.__event.get("version")
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def bucket(self) -> S3BucketData:
|
|
120
|
+
"""S# Bucket Information"""
|
|
121
|
+
if not self.__s3_bucket_data:
|
|
122
|
+
self.__s3_bucket_data = S3BucketData(self.__event.get("bucket", {}))
|
|
123
|
+
return self.__s3_bucket_data
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def object(self) -> S3ObjectData:
|
|
127
|
+
"""S3 Object Information"""
|
|
128
|
+
if not self.__s3_object_data:
|
|
129
|
+
self.__s3_object_data = S3ObjectData(self.__event.get("object", {}))
|
|
130
|
+
|
|
131
|
+
return self.__s3_object_data
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def request_id(self) -> str | None:
|
|
135
|
+
"""Detail Request Id"""
|
|
136
|
+
|
|
137
|
+
return self.__event.get("request-id")
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def requester(self) -> str | None:
|
|
141
|
+
"""Detail Requestor"""
|
|
142
|
+
|
|
143
|
+
return self.__event.get("requester")
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def source_ip_address(self) -> str | None:
|
|
147
|
+
"""Source IP Address"""
|
|
148
|
+
|
|
149
|
+
return self.__event.get("source-ip-address")
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def reason(self) -> str | None:
|
|
153
|
+
"""Reason"""
|
|
154
|
+
|
|
155
|
+
return self.__event.get("reason")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class S3Event(EventData):
|
|
159
|
+
"""S3 Data Event"""
|
|
160
|
+
|
|
161
|
+
def __init__(self, event):
|
|
162
|
+
super().__init__(event)
|
|
163
|
+
self.__detail: S3EventDetail = S3EventDetail(event=event.get("detail", {}))
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def detail(self) -> S3EventDetail:
|
|
167
|
+
"""S3 Specific Detail"""
|
|
168
|
+
return self.__detail
|
|
@@ -183,7 +183,7 @@ class S3Object:
|
|
|
183
183
|
)
|
|
184
184
|
|
|
185
185
|
end = DatetimeUtility.get_utc_now()
|
|
186
|
-
logger.debug(f"Signed URL created in {end-start}")
|
|
186
|
+
logger.debug(f"Signed URL created in {end - start}")
|
|
187
187
|
|
|
188
188
|
response = {
|
|
189
189
|
"signed_url": signed_url,
|
|
@@ -604,3 +604,65 @@ class S3Object:
|
|
|
604
604
|
versions.extend(page["Versions"])
|
|
605
605
|
|
|
606
606
|
return versions
|
|
607
|
+
|
|
608
|
+
def copy(
|
|
609
|
+
self,
|
|
610
|
+
source_bucket: str,
|
|
611
|
+
source_key: str,
|
|
612
|
+
destination_bucket: str,
|
|
613
|
+
destination_key: str,
|
|
614
|
+
) -> Dict[str, Any]:
|
|
615
|
+
"""
|
|
616
|
+
Copies an object from one location to another.
|
|
617
|
+
The original is kept.
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
if source_key.startswith("/"):
|
|
621
|
+
# remove the first slash
|
|
622
|
+
source_key = source_key[1:]
|
|
623
|
+
|
|
624
|
+
if destination_key.startswith("/"):
|
|
625
|
+
# remove the first slash
|
|
626
|
+
destination_key = destination_key[1:]
|
|
627
|
+
|
|
628
|
+
response = self.connection.client.copy_object(
|
|
629
|
+
CopySource={"Bucket": source_bucket, "Key": source_key},
|
|
630
|
+
Bucket=destination_bucket,
|
|
631
|
+
Key=destination_key,
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
return response
|
|
635
|
+
|
|
636
|
+
def move(
|
|
637
|
+
self,
|
|
638
|
+
source_bucket: str,
|
|
639
|
+
source_key: str,
|
|
640
|
+
destination_bucket: str,
|
|
641
|
+
destination_key: str,
|
|
642
|
+
) -> Dict[str, Any]:
|
|
643
|
+
"""
|
|
644
|
+
Copies an object from one location to another then deletes the source.
|
|
645
|
+
The source is only deleted if the copy is successful
|
|
646
|
+
"""
|
|
647
|
+
|
|
648
|
+
copy_response = self.connection.client.copy_object(
|
|
649
|
+
CopySource={"Bucket": source_bucket, "Key": source_key},
|
|
650
|
+
Bucket=destination_bucket,
|
|
651
|
+
Key=destination_key,
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
status_code = copy_response.get("statusCode")
|
|
655
|
+
delete_response = {}
|
|
656
|
+
if status_code == 200:
|
|
657
|
+
if source_key.startswith("/"):
|
|
658
|
+
source_key = source_key[1:]
|
|
659
|
+
delete_response = self.delete(bucket_name=source_bucket, key=source_key)
|
|
660
|
+
status_code = copy_response.get("statusCode", status_code)
|
|
661
|
+
|
|
662
|
+
response = {
|
|
663
|
+
"status_code": status_code,
|
|
664
|
+
"copy": copy_response,
|
|
665
|
+
"delete": delete_response,
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return response
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from aws_lambda_powertools import Logger
|
|
11
|
+
|
|
12
|
+
from boto3_assist.securityhub.securityhub_connection import SecurityHubConnection
|
|
13
|
+
|
|
14
|
+
logger = Logger("security-hub-service")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SecurityHub(SecurityHubConnection):
|
|
18
|
+
"""Security Hub Service"""
|
|
19
|
+
|
|
20
|
+
def update_findings_status(
|
|
21
|
+
self,
|
|
22
|
+
region_name: str,
|
|
23
|
+
workflow_status: str,
|
|
24
|
+
note_text: Optional[str] = None,
|
|
25
|
+
updated_by: Optional[str] = None,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Updates the workflow status for all findings in the specified region.
|
|
29
|
+
|
|
30
|
+
:param region_name: AWS region where Security Hub findings are located.
|
|
31
|
+
:param workflow_status: The new workflow status to apply (e.g., NEW, NOTIFIED, SUPPRESSED, RESOLVED).
|
|
32
|
+
"""
|
|
33
|
+
# Initialize Security Hub client
|
|
34
|
+
client = self.client
|
|
35
|
+
|
|
36
|
+
findings_to_update = []
|
|
37
|
+
next_token = None
|
|
38
|
+
|
|
39
|
+
logger.info(f"Fetching findings in region: {region_name}...")
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
# Paginate through findings
|
|
43
|
+
while True:
|
|
44
|
+
response = client.get_findings(
|
|
45
|
+
MaxResults=100,
|
|
46
|
+
Filters={
|
|
47
|
+
"Region": [
|
|
48
|
+
{"Value": region_name, "Comparison": "EQUALS"},
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
NextToken=next_token if next_token else "",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
for finding in response.get("Findings", []):
|
|
55
|
+
current_status = finding.get("Workflow", {}).get("Status")
|
|
56
|
+
if (
|
|
57
|
+
current_status
|
|
58
|
+
and str(current_status).lower() != str(workflow_status).lower()
|
|
59
|
+
):
|
|
60
|
+
findings_to_update.append(
|
|
61
|
+
{
|
|
62
|
+
"Id": finding["Id"],
|
|
63
|
+
"ProductArn": finding["ProductArn"],
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
logger.debug(
|
|
68
|
+
f"Skipping: {finding['Id']} with a status of {current_status}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
next_token = response.get("NextToken")
|
|
72
|
+
if not next_token:
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
print(f"Found {len(findings_to_update)} findings to update.")
|
|
76
|
+
|
|
77
|
+
note_text = note_text or "Automated Update"
|
|
78
|
+
updated_by = updated_by or "System"
|
|
79
|
+
# Update workflow status in batches of 100
|
|
80
|
+
for i in range(0, len(findings_to_update), 100):
|
|
81
|
+
batch = findings_to_update[i : i + 100]
|
|
82
|
+
response = client.batch_update_findings(
|
|
83
|
+
FindingIdentifiers=batch,
|
|
84
|
+
Workflow={"Status": str(workflow_status).upper()},
|
|
85
|
+
Note={"Text": note_text, "UpdatedBy": updated_by},
|
|
86
|
+
)
|
|
87
|
+
logger.debug(
|
|
88
|
+
f"Updated findings {i + 1} to {i + len(batch)} to status: {workflow_status}"
|
|
89
|
+
)
|
|
90
|
+
print(response)
|
|
91
|
+
|
|
92
|
+
logger.info("All findings updated successfully!")
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.exception(f"An error occurred: {str(e)}")
|
|
96
|
+
raise
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main():
|
|
100
|
+
status = "RESOLVED" # Change to NEW, NOTIFIED, SUPPRESSED, or RESOLVED
|
|
101
|
+
note_text = "This region is now disabled."
|
|
102
|
+
# these are my linked regions and the only ones I care about
|
|
103
|
+
# if have SCP's in place for the other regions
|
|
104
|
+
regions_to_skip = ["us-east-1", "us-west-2", "eu-west-2"]
|
|
105
|
+
|
|
106
|
+
aws_profile = os.getenv("SECURITY_HUB_PROFILE")
|
|
107
|
+
|
|
108
|
+
aws_regions = {
|
|
109
|
+
"af-south-1": "Africa (Cape Town)",
|
|
110
|
+
"ap-east-1": "Asia Pacific (Hong Kong)",
|
|
111
|
+
"ap-northeast-1": "Asia Pacific (Tokyo)",
|
|
112
|
+
"ap-northeast-2": "Asia Pacific (Seoul)",
|
|
113
|
+
"ap-northeast-3": "Asia Pacific (Osaka)",
|
|
114
|
+
"ap-south-1": "Asia Pacific (Mumbai)",
|
|
115
|
+
"ap-south-2": "Asia Pacific (Hyderabad)",
|
|
116
|
+
"ap-southeast-1": "Asia Pacific (Singapore)",
|
|
117
|
+
"ap-southeast-2": "Asia Pacific (Sydney)",
|
|
118
|
+
"ap-southeast-3": "Asia Pacific (Jakarta)",
|
|
119
|
+
"ap-southeast-4": "Asia Pacific (Melbourne)",
|
|
120
|
+
"ap-southeast-5": "Asia Pacific (Auckland)",
|
|
121
|
+
"ca-central-1": "Canada (Central)",
|
|
122
|
+
"ca-west-1": "Canada (West)",
|
|
123
|
+
"eu-central-1": "Europe (Frankfurt)",
|
|
124
|
+
"eu-central-2": "Europe (Zurich)",
|
|
125
|
+
"eu-north-1": "Europe (Stockholm)",
|
|
126
|
+
"eu-south-1": "Europe (Milan)",
|
|
127
|
+
"eu-south-2": "Europe (Spain)",
|
|
128
|
+
"eu-west-1": "Europe (Ireland)",
|
|
129
|
+
"eu-west-2": "Europe (London)",
|
|
130
|
+
"eu-west-3": "Europe (Paris)",
|
|
131
|
+
"il-central-1": "Israel (Tel Aviv)",
|
|
132
|
+
"me-central-1": "Middle East (UAE)",
|
|
133
|
+
"sa-east-1": "South America (São Paulo)",
|
|
134
|
+
"us-east-1": "US East (N. Virginia)",
|
|
135
|
+
"us-east-2": "US East (Ohio)",
|
|
136
|
+
"us-west-1": "US West (N. California)",
|
|
137
|
+
"us-west-2": "US West (Oregon)",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
sh: SecurityHub = SecurityHub(aws_profile=aws_profile)
|
|
141
|
+
for region in aws_regions:
|
|
142
|
+
print(region)
|
|
143
|
+
if region not in regions_to_skip:
|
|
144
|
+
sh.update_findings_status(region, status, note_text=note_text)
|
|
145
|
+
else:
|
|
146
|
+
print(f"Skipping region: {region}")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from aws_lambda_powertools import Logger
|
|
11
|
+
|
|
12
|
+
from boto3_assist.connection import Connection
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from mypy_boto3_securityhub import SecurityHubClient
|
|
16
|
+
else:
|
|
17
|
+
SecurityHubClient = object
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = Logger()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SecurityHubConnection(Connection):
|
|
24
|
+
"""Connection"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
aws_profile: Optional[str] = None,
|
|
30
|
+
aws_region: Optional[str] = None,
|
|
31
|
+
aws_end_point_url: Optional[str] = None,
|
|
32
|
+
aws_access_key_id: Optional[str] = None,
|
|
33
|
+
aws_secret_access_key: Optional[str] = None,
|
|
34
|
+
) -> None:
|
|
35
|
+
super().__init__(
|
|
36
|
+
service_name="securityhub",
|
|
37
|
+
aws_profile=aws_profile,
|
|
38
|
+
aws_region=aws_region,
|
|
39
|
+
aws_access_key_id=aws_access_key_id,
|
|
40
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
41
|
+
aws_end_point_url=aws_end_point_url,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self.__client: SecurityHubClient | None = None
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def client(self) -> SecurityHubClient:
|
|
48
|
+
"""Client Connection"""
|
|
49
|
+
if self.__client is None:
|
|
50
|
+
self.__client = self.session.client
|
|
51
|
+
|
|
52
|
+
return self.__client
|
|
53
|
+
|
|
54
|
+
@client.setter
|
|
55
|
+
def client(self, value: SecurityHubClient):
|
|
56
|
+
logger.info("Setting Client")
|
|
57
|
+
self.__client = value
|
|
@@ -47,12 +47,20 @@ class NumberUtility:
|
|
|
47
47
|
@staticmethod
|
|
48
48
|
def to_number_or_none(value: str) -> float | int | None:
|
|
49
49
|
"""Converts a string to a number."""
|
|
50
|
-
if value is
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
if value is None:
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
if str(value).lower() == "nan":
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
numeric_value = float(value)
|
|
58
|
+
# Check if the number is an integer (e.g., 7.0) and return as int
|
|
59
|
+
if numeric_value.is_integer():
|
|
60
|
+
return int(numeric_value)
|
|
61
|
+
return numeric_value
|
|
62
|
+
except (ValueError, TypeError):
|
|
63
|
+
return None
|
|
56
64
|
|
|
57
65
|
@staticmethod
|
|
58
66
|
def to_float(
|
|
@@ -111,6 +119,7 @@ class NumberUtility:
|
|
|
111
119
|
Returns:
|
|
112
120
|
int: number of decimal places
|
|
113
121
|
"""
|
|
122
|
+
|
|
114
123
|
number_str = f"{number}"
|
|
115
124
|
if "." in number_str:
|
|
116
125
|
to_the_right = number_str.split(".")[1]
|
|
@@ -178,6 +187,30 @@ class NumberUtility:
|
|
|
178
187
|
|
|
179
188
|
return 0.0
|
|
180
189
|
|
|
190
|
+
@staticmethod
|
|
191
|
+
def to_number(
|
|
192
|
+
value: str | float | int,
|
|
193
|
+
raise_errors: Optional[bool] = False,
|
|
194
|
+
error_message: Optional[str] = None,
|
|
195
|
+
) -> int | float:
|
|
196
|
+
"""Converts a string to a number."""
|
|
197
|
+
try:
|
|
198
|
+
numeric_value = float(value)
|
|
199
|
+
# Check if the number is an integer (e.g., 7.0) and return as int
|
|
200
|
+
if numeric_value.is_integer():
|
|
201
|
+
return int(numeric_value)
|
|
202
|
+
return numeric_value
|
|
203
|
+
except Exception as e: # noqa: E722, pylint: disable=w0718
|
|
204
|
+
logger.error(f"Unable to convert {value} to number")
|
|
205
|
+
if raise_errors:
|
|
206
|
+
if error_message:
|
|
207
|
+
raise ValueError(
|
|
208
|
+
f"Unable to convert {value} to number, {error_message}"
|
|
209
|
+
) from e
|
|
210
|
+
else:
|
|
211
|
+
raise ValueError(f"Unable to convert {value} to number") from e
|
|
212
|
+
return 0
|
|
213
|
+
|
|
181
214
|
@staticmethod
|
|
182
215
|
def to_significant_figure(
|
|
183
216
|
number: int | float | str, sig_figs: int
|
|
@@ -201,10 +234,14 @@ class NumberUtility:
|
|
|
201
234
|
return number
|
|
202
235
|
|
|
203
236
|
if NumberUtility.is_numeric(number) and isinstance(number, str):
|
|
204
|
-
|
|
205
|
-
number
|
|
206
|
-
|
|
207
|
-
|
|
237
|
+
number = NumberUtility.to_number(
|
|
238
|
+
number,
|
|
239
|
+
raise_errors=True,
|
|
240
|
+
error_message=(
|
|
241
|
+
f"Error attempting to set significant figure for value {number}"
|
|
242
|
+
f", sigfig {sig_figs}"
|
|
243
|
+
),
|
|
244
|
+
)
|
|
208
245
|
|
|
209
246
|
if number == 0:
|
|
210
247
|
if sig_figs > 1:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.9.1'
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from boto3_assist.s3.s3_event_data import S3Event
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class S3EventDataTests(unittest.TestCase):
|
|
7
|
+
"""Test S3 Event Data parsing"""
|
|
8
|
+
|
|
9
|
+
def test_event_info(self):
|
|
10
|
+
"""Test the event information is parsed correctly"""
|
|
11
|
+
event = self.helper_get_event_sample()
|
|
12
|
+
s3_event: S3Event = S3Event(event=event)
|
|
13
|
+
|
|
14
|
+
self.assertEqual(s3_event.version, "0")
|
|
15
|
+
self.assertEqual(s3_event.id, "c576a9d9-edef-6aeb-cf6c-4fc0234bacdc")
|
|
16
|
+
self.assertEqual(s3_event.detail_type, "Object Created")
|
|
17
|
+
self.assertEqual(s3_event.source, "aws.s3")
|
|
18
|
+
self.assertEqual(s3_event.account, "1111111111")
|
|
19
|
+
self.assertEqual(s3_event.time, "2025-02-05T23:55:22Z")
|
|
20
|
+
self.assertEqual(s3_event.region, "us-east-1")
|
|
21
|
+
self.assertEqual(s3_event.resources, ["arn:aws:s3:::my_object_key"])
|
|
22
|
+
self.assertEqual(s3_event.detail.version, "0")
|
|
23
|
+
self.assertEqual(s3_event.detail.bucket.name, "my_object_key")
|
|
24
|
+
self.assertEqual(
|
|
25
|
+
s3_event.detail.object.key,
|
|
26
|
+
"tenants/aaaaaaaaaaaaaa/users/bbbbbbbbbbbbbbbb/sample.json",
|
|
27
|
+
)
|
|
28
|
+
self.assertEqual(s3_event.detail.object.size, 6904)
|
|
29
|
+
self.assertEqual(
|
|
30
|
+
s3_event.detail.object.etag, "f50341a4fa42e09016e76d7878c4fdc5"
|
|
31
|
+
)
|
|
32
|
+
self.assertEqual(
|
|
33
|
+
s3_event.detail.object.version_id, "58QKYNXF62Wo1y7CZUDrDM0ILh5ncSwf"
|
|
34
|
+
)
|
|
35
|
+
self.assertEqual(s3_event.detail.object.sequencer, "0067A3FA6A303C91A8")
|
|
36
|
+
self.assertEqual(s3_event.detail.request_id, "F82R8XDNW8385Q6Y")
|
|
37
|
+
self.assertEqual(s3_event.detail.requester, "959096737760")
|
|
38
|
+
self.assertEqual(s3_event.detail.source_ip_address, "192.168.0.52")
|
|
39
|
+
self.assertEqual(s3_event.detail.reason, "PutObject")
|
|
40
|
+
|
|
41
|
+
def helper_get_event_sample(self):
|
|
42
|
+
event = {
|
|
43
|
+
"version": "0",
|
|
44
|
+
"id": "c576a9d9-edef-6aeb-cf6c-4fc0234bacdc",
|
|
45
|
+
"detail-type": "Object Created",
|
|
46
|
+
"source": "aws.s3",
|
|
47
|
+
"account": "1111111111",
|
|
48
|
+
"time": "2025-02-05T23:55:22Z",
|
|
49
|
+
"region": "us-east-1",
|
|
50
|
+
"resources": ["arn:aws:s3:::my_object_key"],
|
|
51
|
+
"detail": {
|
|
52
|
+
"version": "0",
|
|
53
|
+
"bucket": {"name": "my_object_key"},
|
|
54
|
+
"object": {
|
|
55
|
+
"key": "tenants/aaaaaaaaaaaaaa/users/bbbbbbbbbbbbbbbb/sample.json",
|
|
56
|
+
"size": 6904,
|
|
57
|
+
"etag": "f50341a4fa42e09016e76d7878c4fdc5",
|
|
58
|
+
"version-id": "58QKYNXF62Wo1y7CZUDrDM0ILh5ncSwf",
|
|
59
|
+
"sequencer": "0067A3FA6A303C91A8",
|
|
60
|
+
},
|
|
61
|
+
"request-id": "F82R8XDNW8385Q6Y",
|
|
62
|
+
"requester": "959096737760",
|
|
63
|
+
"source-ip-address": "192.168.0.52",
|
|
64
|
+
"reason": "PutObject",
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return event
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '0.8.0'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.8.0 → boto3_assist-0.9.1}/examples/dynamodb/services/user_service_client_example.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cloudwatch/cloudwatch_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
{boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py
RENAMED
|
File without changes
|
{boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.8.0 → boto3_assist-0.9.1}/src/boto3_assist/utilities/serialization_utility.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dbmodels/user_required_fields_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{boto3_assist-0.8.0 → boto3_assist-0.9.1}/tests/dynamodb/dynamodb_model_serializtion_test.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|