boto3-assist 0.1.14__tar.gz → 0.2.0__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.2.0/.env.unittest +17 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/.gitignore +2 -1
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/PKG-INFO +1 -3
- boto3_assist-0.2.0/examples/cloudwatch/log_report.py +62 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/models/order_item_model.py +5 -1
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/order_example/main.py +6 -5
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/services/order_item_service.py +1 -1
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/user_post_example/main.py +1 -1
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/ec2/regions_report.py +1 -1
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/pyproject.toml +1 -1
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/requirements-dev.txt +1 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/boto3session.py +3 -2
- boto3_assist-0.2.0/src/boto3_assist/cloudwatch/cloudwatch_connection.py +86 -0
- boto3_assist-0.2.0/src/boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +17 -0
- boto3_assist-0.2.0/src/boto3_assist/cloudwatch/cloudwatch_log_connection.py +62 -0
- boto3_assist-0.2.0/src/boto3_assist/cloudwatch/cloudwatch_logs.py +39 -0
- boto3_assist-0.2.0/src/boto3_assist/cloudwatch/cloudwatch_query.py +191 -0
- boto3_assist-0.2.0/src/boto3_assist/connection.py +101 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/connection_tracker.py +8 -8
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb.py +28 -17
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_connection.py +3 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_index.py +23 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_iservice.py +4 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_model_base.py +5 -5
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.py +6 -3
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_reserved_words.txt +0 -1
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/ec2/ec2_connection.py +3 -0
- boto3_assist-0.2.0/src/boto3_assist/environment_services/environment_loader.py +111 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/environment_services/environment_variables.py +4 -0
- boto3_assist-0.2.0/src/boto3_assist/version.py +1 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/dynamodb/dynamodb_model_serializtion_test.py +0 -1
- boto3_assist-0.2.0/tests/dynamodb/dynamodb_moto_sorting_test.py +159 -0
- boto3_assist-0.2.0/tests/dynamodb/models/cms/base.py +54 -0
- boto3_assist-0.2.0/tests/dynamodb/models/cms/content_block.py +53 -0
- boto3_assist-0.2.0/tests/dynamodb/models/cms/page.py +103 -0
- boto3_assist-0.2.0/tests/dynamodb/models/cms/template.py +59 -0
- boto3_assist-0.1.14/.env.development +0 -1
- boto3_assist-0.1.14/src/boto3_assist/environment_services/environment_loader.py +0 -47
- boto3_assist-0.1.14/src/boto3_assist/version.py +0 -1
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/.env.docker +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/.env.docker.001 +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/.env.docker.nosql.workbench +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/.vscode/launch.json +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/.vscode/settings.json +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/.vscode/tasks.json +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/LICENSE-EXPLAINED.txt +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/LICENSE.txt +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/README.md +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/aws_regions_with_status.csv +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/aws_regions_with_status.json +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/devops/build.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/devops/readme.md +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/__init__.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/models/order_model.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/models/product_model.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/models/user_model.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/models/user_post_model.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/order_example/products.json +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/services/order_service.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/services/product_service.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/services/table_service.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/services/user_post_service.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/services/user_service.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/services/user_service_client_example.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/examples/dynamodb/services/user_service_resource_example.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/module-headers.txt +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/mypy.ini +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/requirements.txt +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/run-checks.sh +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/__init__.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_connection_tracker.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_helpers.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_importer.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_key.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/dynamodb_reindexer.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/readme.md +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/dynamodb/troubleshooting.md +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/environment_services/__init__.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/utilities/datetime_utility.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/utilities/logging_utility.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/utilities/serialization_utility.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/src/boto3_assist/utilities/string_utility.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/__init__.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/__top/__init__.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/dynamodb/__init__.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/dynamodb/dynamodb_model_base_test.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/dynamodb/dynamodb_model_projections_test.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/dynamodb/dynamodb_reindex_test.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/dynamodb/models/simple_model.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/dynamodb/models/user_model.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/examples_test/__init__.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/examples_test/user_service_test.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/utilities/__init__.py +0 -0
- {boto3_assist-0.1.14 → boto3_assist-0.2.0}/tests/utilities/serialization_utility_test.py +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# let's make sure we don't hit any actual aws resources
|
|
2
|
+
# when running moto mock tests
|
|
3
|
+
AWS_ACCESS_KEY_ID="testing"
|
|
4
|
+
AWS_SECRET_ACCESS_KEY="testing"
|
|
5
|
+
AWS_SECURITY_TOKEN="testing"
|
|
6
|
+
AWS_SESSION_TOKEN="testing"
|
|
7
|
+
AWS_DEFAULT_REGION="us-east-1"
|
|
8
|
+
AWS_REGION="us-east-1"
|
|
9
|
+
# So far it looks like I need a profile or I get an error like the one below
|
|
10
|
+
# "The config profile (<profile>) could not be found"
|
|
11
|
+
# "The config profile () could not be found" <- if using the default profile
|
|
12
|
+
# AWS_PROFILE="default"
|
|
13
|
+
# AWS_DEFAULT_PROFILE="default"
|
|
14
|
+
# I don't want to use default profile, so I'm using a mock profile
|
|
15
|
+
# However it must exist in ~/.aws/config or you'll get the same error above
|
|
16
|
+
AWS_PROFILE="moto-mock-tests"
|
|
17
|
+
AWS_DEFAULT_PROFILE="moto-mock-tests"
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: boto3_assist
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Additional boto3 wrappers to make your life a little easier
|
|
5
5
|
Author-email: Eric Wilson <boto3-assist@geekcafe.com>
|
|
6
|
-
License-File: LICENSE-EXPLAINED.txt
|
|
7
|
-
License-File: LICENSE.txt
|
|
8
6
|
Classifier: License :: Other/Proprietary License
|
|
9
7
|
Classifier: Operating System :: OS Independent
|
|
10
8
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import csv
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from boto3_assist.environment_services.environment_loader import EnvironmentLoader
|
|
7
|
+
from boto3_assist.ec2.ec2_connection import EC2Connection
|
|
8
|
+
from boto3_assist.cloudwatch.cloudwatch_query import CloudWatchQuery
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LogReport(CloudWatchQuery):
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
super().__init__()
|
|
14
|
+
|
|
15
|
+
def export(self, regions: List[Dict[str, Any]], file_format: str = "csv"):
|
|
16
|
+
"""
|
|
17
|
+
Export the regions to a file
|
|
18
|
+
Args:
|
|
19
|
+
regions (List[Dict[str, Any]]): _description_
|
|
20
|
+
file_format (str, optional): _description_. Defaults to "csv".
|
|
21
|
+
"""
|
|
22
|
+
if file_format == "csv":
|
|
23
|
+
self.__export_regions_to_csv(regions)
|
|
24
|
+
elif file_format == "json":
|
|
25
|
+
self.__export_regions_to_json(regions)
|
|
26
|
+
|
|
27
|
+
def __export_regions_to_csv(self, regions, filename="aws_regions_with_status.csv"):
|
|
28
|
+
with open(filename, "w", newline="", encoding="utf-8") as csvfile:
|
|
29
|
+
writer = csv.writer(csvfile)
|
|
30
|
+
writer.writerow(["RegionName", "OptInStatus"])
|
|
31
|
+
for region in regions:
|
|
32
|
+
writer.writerow([region["RegionName"], region["OptInStatus"]])
|
|
33
|
+
|
|
34
|
+
def __export_regions_to_json(
|
|
35
|
+
self, regions, filename="aws_regions_with_status.json"
|
|
36
|
+
):
|
|
37
|
+
with open(filename, "w", encoding="utf-8") as jsonfile:
|
|
38
|
+
json.dump(regions, jsonfile, indent=4)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main():
|
|
42
|
+
"""Main"""
|
|
43
|
+
|
|
44
|
+
env_file_name: str = os.getenv("ENVRIONMENT_FILE", ".env")
|
|
45
|
+
path = os.path.join(str(Path(__file__).parents[2].absolute()), env_file_name)
|
|
46
|
+
el: EnvironmentLoader = EnvironmentLoader()
|
|
47
|
+
if not os.path.exists(path=path):
|
|
48
|
+
raise FileNotFoundError("Failed to find the environmetn file")
|
|
49
|
+
loaded: bool = el.load_environment_file(path=path)
|
|
50
|
+
if not loaded:
|
|
51
|
+
raise RuntimeError("Failed to load my local environment")
|
|
52
|
+
|
|
53
|
+
report: CloudWatchQuery = CloudWatchQuery()
|
|
54
|
+
|
|
55
|
+
# Retrieve AWS regions with their status
|
|
56
|
+
logs = report.get_log_sizes()
|
|
57
|
+
|
|
58
|
+
print(logs)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
main()
|
|
@@ -33,14 +33,18 @@ class OrderItem(DynamoDBModelBase):
|
|
|
33
33
|
self.__setup_indexes()
|
|
34
34
|
|
|
35
35
|
def __setup_indexes(self):
|
|
36
|
-
#
|
|
36
|
+
# the primary key will be made off of the
|
|
37
|
+
# order.id and this item.id
|
|
38
|
+
# this will allow for a 1 to many search on the items related to an order
|
|
37
39
|
primay: DynamoDBIndex = DynamoDBIndex()
|
|
38
40
|
primay.name = "primary"
|
|
41
|
+
# create a partition key off of the order key
|
|
39
42
|
primay.partition_key.attribute_name = "pk"
|
|
40
43
|
primay.partition_key.value = lambda: DynamoDBKey.build_key(
|
|
41
44
|
("order", self.order_id)
|
|
42
45
|
)
|
|
43
46
|
|
|
47
|
+
# create the sort key off of this items id
|
|
44
48
|
primay.sort_key.attribute_name = "sk"
|
|
45
49
|
primay.sort_key.value = lambda: DynamoDBKey.build_key(("item", self.id))
|
|
46
50
|
self.indexes.add_primary(primay)
|
|
@@ -5,17 +5,18 @@ DynamoDB Example
|
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
7
|
import random
|
|
8
|
+
from datetime import UTC, datetime, timedelta
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import List
|
|
10
|
-
|
|
11
|
+
|
|
11
12
|
from boto3_assist.dynamodb.dynamodb import DynamoDB
|
|
12
13
|
from boto3_assist.environment_services.environment_loader import EnvironmentLoader
|
|
13
14
|
from boto3_assist.utilities.serialization_utility import JsonEncoder
|
|
14
15
|
from boto3_assist.utilities.string_utility import StringUtility
|
|
15
|
-
from examples.dynamodb.services.table_service import DynamoDBTableService
|
|
16
|
-
from examples.dynamodb.services.order_service import OrderService, Order
|
|
17
|
-
from examples.dynamodb.services.order_item_service import OrderItemService, OrderItem
|
|
18
16
|
from examples.dynamodb.models.product_model import Product
|
|
17
|
+
from examples.dynamodb.services.order_item_service import OrderItem, OrderItemService
|
|
18
|
+
from examples.dynamodb.services.order_service import Order, OrderService
|
|
19
|
+
from examples.dynamodb.services.table_service import DynamoDBTableService
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class DynamoDBExample:
|
|
@@ -154,7 +155,7 @@ def main():
|
|
|
154
155
|
el: EnvironmentLoader = EnvironmentLoader()
|
|
155
156
|
if not os.path.exists(path=path):
|
|
156
157
|
raise FileNotFoundError("Failed to find the environmetn file")
|
|
157
|
-
loaded: bool = el.load_environment_file(path)
|
|
158
|
+
loaded: bool = el.load_environment_file(path=path)
|
|
158
159
|
if not loaded:
|
|
159
160
|
raise RuntimeError("Failed to load my local environment")
|
|
160
161
|
|
|
@@ -181,7 +181,7 @@ def main():
|
|
|
181
181
|
el: EnvironmentLoader = EnvironmentLoader()
|
|
182
182
|
if not os.path.exists(path=path):
|
|
183
183
|
raise FileNotFoundError("Failed to find the environmetn file")
|
|
184
|
-
loaded: bool = el.load_environment_file(path)
|
|
184
|
+
loaded: bool = el.load_environment_file(path=path)
|
|
185
185
|
if not loaded:
|
|
186
186
|
raise RuntimeError("Failed to load my local environment")
|
|
187
187
|
|
|
@@ -60,7 +60,7 @@ def main():
|
|
|
60
60
|
el: EnvironmentLoader = EnvironmentLoader()
|
|
61
61
|
if not os.path.exists(path=path):
|
|
62
62
|
raise FileNotFoundError("Failed to find the environmetn file")
|
|
63
|
-
loaded: bool = el.load_environment_file(path)
|
|
63
|
+
loaded: bool = el.load_environment_file(path=path)
|
|
64
64
|
if not loaded:
|
|
65
65
|
raise RuntimeError("Failed to load my local environment")
|
|
66
66
|
|
|
@@ -45,6 +45,7 @@ class Boto3SessionManager:
|
|
|
45
45
|
self.aws_access_key_id = aws_access_key_id
|
|
46
46
|
self.aws_secret_access_key = aws_secret_access_key
|
|
47
47
|
self.aws_session_token = aws_session_token
|
|
48
|
+
|
|
48
49
|
self.__session: Any = None
|
|
49
50
|
self.__client: Any = None
|
|
50
51
|
self.__resource: Any = None
|
|
@@ -90,7 +91,7 @@ class Boto3SessionManager:
|
|
|
90
91
|
|
|
91
92
|
def __get_aws_session(
|
|
92
93
|
self, aws_profile: Optional[str] = None, aws_region: Optional[str] = None
|
|
93
|
-
) -> boto3.Session:
|
|
94
|
+
) -> boto3.Session | None:
|
|
94
95
|
"""Get a boto3 session for AWS."""
|
|
95
96
|
logger.debug({"profile": aws_profile, "region": aws_region})
|
|
96
97
|
try:
|
|
@@ -153,7 +154,7 @@ class Boto3SessionManager:
|
|
|
153
154
|
)
|
|
154
155
|
return self.__resource
|
|
155
156
|
|
|
156
|
-
def __create_boto3_session(self) -> boto3.Session:
|
|
157
|
+
def __create_boto3_session(self) -> boto3.Session | None:
|
|
157
158
|
try:
|
|
158
159
|
session = boto3.Session(
|
|
159
160
|
profile_name=self.aws_profile,
|
|
@@ -0,0 +1,86 @@
|
|
|
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
|
+
from boto3_assist.boto3session import Boto3SessionManager
|
|
12
|
+
from boto3_assist.environment_services.environment_variables import (
|
|
13
|
+
EnvironmentVariables,
|
|
14
|
+
)
|
|
15
|
+
from boto3_assist.cloudwatch.cloudwatch_connection_tracker import (
|
|
16
|
+
CloudWatchConnectionTracker,
|
|
17
|
+
)
|
|
18
|
+
from boto3_assist.connection import Connection
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from mypy_boto3_cloudwatch import CloudWatchClient, CloudWatchServiceResource
|
|
22
|
+
else:
|
|
23
|
+
CloudWatchClient = object
|
|
24
|
+
CloudWatchServiceResource = object
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
logger = Logger()
|
|
28
|
+
tracker: CloudWatchConnectionTracker = CloudWatchConnectionTracker()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CloudWatchConnection(Connection):
|
|
32
|
+
"""CW Environment"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
aws_profile: Optional[str] = None,
|
|
38
|
+
aws_region: Optional[str] = None,
|
|
39
|
+
aws_access_key_id: Optional[str] = None,
|
|
40
|
+
aws_secret_access_key: Optional[str] = None,
|
|
41
|
+
) -> None:
|
|
42
|
+
super().__init__(
|
|
43
|
+
service_name="cloudwatch",
|
|
44
|
+
aws_profile=aws_profile,
|
|
45
|
+
aws_region=aws_region,
|
|
46
|
+
aws_access_key_id=aws_access_key_id,
|
|
47
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
self.__client: CloudWatchClient | None = None
|
|
51
|
+
self.__resource: CloudWatchServiceResource | None = None
|
|
52
|
+
|
|
53
|
+
self.raise_on_error: bool = True
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def client(self) -> CloudWatchClient:
|
|
57
|
+
"""CloudWatch Client Connection"""
|
|
58
|
+
if self.__client is None:
|
|
59
|
+
logger.info("Creating CloudWatch Client")
|
|
60
|
+
self.__client = self.session.client
|
|
61
|
+
|
|
62
|
+
if self.raise_on_error and self.__client is None:
|
|
63
|
+
raise RuntimeError("CloudWatch Client is not available")
|
|
64
|
+
return self.__client
|
|
65
|
+
|
|
66
|
+
@client.setter
|
|
67
|
+
def client(self, value: CloudWatchClient):
|
|
68
|
+
logger.info("Setting CloudWatch Client")
|
|
69
|
+
self.__client = value
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def resource(self) -> CloudWatchServiceResource:
|
|
73
|
+
"""CloudWatch Resource Connection"""
|
|
74
|
+
if self.__resource is None:
|
|
75
|
+
logger.info("Creating CloudWatch Resource")
|
|
76
|
+
self.__resource = self.session.resource
|
|
77
|
+
|
|
78
|
+
if self.raise_on_error and self.__resource is None:
|
|
79
|
+
raise RuntimeError("CloudWatch Resource is not available")
|
|
80
|
+
|
|
81
|
+
return self.__resource
|
|
82
|
+
|
|
83
|
+
@resource.setter
|
|
84
|
+
def resource(self, value: CloudWatchServiceResource):
|
|
85
|
+
logger.info("Setting CloudWatch Resource")
|
|
86
|
+
self.__resource = value
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from boto3_assist.connection_tracker import ConnectionTracker
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CloudWatchConnectionTracker(ConnectionTracker):
|
|
11
|
+
"""
|
|
12
|
+
Tracks CloudWatch Connection Requests.
|
|
13
|
+
Useful in for performance tuning and debugging.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
super().__init__("CloudWatch")
|
|
@@ -0,0 +1,62 @@
|
|
|
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.cloudwatch.cloudwatch_connection_tracker import (
|
|
13
|
+
CloudWatchConnectionTracker,
|
|
14
|
+
)
|
|
15
|
+
from boto3_assist.connection import Connection
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from mypy_boto3_logs import CloudWatchLogsClient
|
|
19
|
+
else:
|
|
20
|
+
CloudWatchLogsClient = object
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = Logger()
|
|
24
|
+
tracker: CloudWatchConnectionTracker = CloudWatchConnectionTracker()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CloudWatchConnection(Connection):
|
|
28
|
+
"""CW Logs Environment"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
aws_profile: Optional[str] = None,
|
|
34
|
+
aws_region: Optional[str] = None,
|
|
35
|
+
aws_access_key_id: Optional[str] = None,
|
|
36
|
+
aws_secret_access_key: Optional[str] = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
super().__init__(
|
|
39
|
+
service_name="logs",
|
|
40
|
+
aws_profile=aws_profile,
|
|
41
|
+
aws_region=aws_region,
|
|
42
|
+
aws_access_key_id=aws_access_key_id,
|
|
43
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
self.__client: CloudWatchLogsClient | None = None
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def client(self) -> CloudWatchLogsClient:
|
|
50
|
+
"""CloudWatch Client Connection"""
|
|
51
|
+
if self.__client is None:
|
|
52
|
+
logger.debug("Creating CloudWatch Client")
|
|
53
|
+
self.__client = self.session.client
|
|
54
|
+
|
|
55
|
+
if self.raise_on_error and self.__client is None:
|
|
56
|
+
raise RuntimeError("CloudWatch Client is not available")
|
|
57
|
+
return self.__client
|
|
58
|
+
|
|
59
|
+
@client.setter
|
|
60
|
+
def client(self, value: CloudWatchLogsClient):
|
|
61
|
+
logger.debug("Setting CloudWatch Client")
|
|
62
|
+
self.__client = value
|
|
@@ -0,0 +1,39 @@
|
|
|
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, List, Dict, Any
|
|
8
|
+
from boto3_assist.cloudwatch.cloudwatch_log_connection import CloudWatchConnection
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CloudWatchLogs(CloudWatchConnection):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
*,
|
|
15
|
+
aws_profile: Optional[str] = None,
|
|
16
|
+
aws_region: Optional[str] = None,
|
|
17
|
+
aws_access_key_id: Optional[str] = None,
|
|
18
|
+
aws_secret_access_key: Optional[str] = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__(
|
|
21
|
+
aws_profile=aws_profile,
|
|
22
|
+
aws_region=aws_region,
|
|
23
|
+
aws_access_key_id=aws_access_key_id,
|
|
24
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def list_log_groups(self):
|
|
28
|
+
"""Retrieve all log groups in the AWS account."""
|
|
29
|
+
log_groups: List[Dict[str, Any]] = []
|
|
30
|
+
paginator = self.client.get_paginator("describe_log_groups")
|
|
31
|
+
for page in paginator.paginate():
|
|
32
|
+
log_groups.extend(page["logGroups"]) # type: ignore[arg-type]
|
|
33
|
+
return log_groups
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main():
|
|
37
|
+
query: CloudWatchLogs = CloudWatchLogs()
|
|
38
|
+
result = query.list_log_groups()
|
|
39
|
+
print(result)
|
|
@@ -0,0 +1,191 @@
|
|
|
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 datetime import datetime, timedelta, UTC
|
|
9
|
+
from typing import Optional, Dict, Any, List
|
|
10
|
+
from boto3_assist.cloudwatch.cloudwatch_connection import CloudWatchConnection
|
|
11
|
+
from boto3_assist.cloudwatch.cloudwatch_logs import CloudWatchLogs
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CloudWatchQuery(CloudWatchConnection):
|
|
15
|
+
"""Query Cloud Watch"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
*,
|
|
20
|
+
aws_profile: Optional[str] = None,
|
|
21
|
+
aws_region: Optional[str] = None,
|
|
22
|
+
aws_access_key_id: Optional[str] = None,
|
|
23
|
+
aws_secret_access_key: Optional[str] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
super().__init__(
|
|
26
|
+
aws_profile=aws_profile,
|
|
27
|
+
aws_region=aws_region,
|
|
28
|
+
aws_access_key_id=aws_access_key_id,
|
|
29
|
+
aws_secret_access_key=aws_secret_access_key,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
self.__cw_logs: CloudWatchLogs | None = None
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def cw_logs(self) -> CloudWatchLogs:
|
|
36
|
+
"""CloudWatch Logs Connection"""
|
|
37
|
+
if self.__cw_logs is None:
|
|
38
|
+
self.__cw_logs = CloudWatchLogs(
|
|
39
|
+
aws_profile=self.aws_profile,
|
|
40
|
+
aws_region=self.aws_region,
|
|
41
|
+
aws_access_key_id=self.aws_access_key_id,
|
|
42
|
+
aws_secret_access_key=self.aws_secret_access_key,
|
|
43
|
+
)
|
|
44
|
+
return self.__cw_logs
|
|
45
|
+
|
|
46
|
+
def get_log_group_size(
|
|
47
|
+
self, log_group_name: str, start_time: datetime, end_time: datetime
|
|
48
|
+
) -> Dict[str, Any]:
|
|
49
|
+
"""
|
|
50
|
+
Get the log group size for a given period of time
|
|
51
|
+
Args:
|
|
52
|
+
log_group_name (str): _description_
|
|
53
|
+
start_time (datetime): _description_
|
|
54
|
+
end_time (datetime): _description_
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
_type_: _description_
|
|
58
|
+
"""
|
|
59
|
+
response = self.client.get_metric_data(
|
|
60
|
+
MetricDataQueries=[
|
|
61
|
+
{
|
|
62
|
+
"Id": "storedBytes",
|
|
63
|
+
"MetricStat": {
|
|
64
|
+
"Metric": {
|
|
65
|
+
"Namespace": "AWS/Logs",
|
|
66
|
+
# "MetricName": "StoredBytes",
|
|
67
|
+
"MetricName": "IncomingBytes",
|
|
68
|
+
"Dimensions": [
|
|
69
|
+
{"Name": "LogGroupName", "Value": log_group_name}
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
"Period": 86400, # Daily data
|
|
73
|
+
"Stat": "Sum",
|
|
74
|
+
},
|
|
75
|
+
"ReturnData": True,
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
StartTime=start_time,
|
|
79
|
+
EndTime=end_time,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# Extract the total size in bytes for the period
|
|
83
|
+
size: float = 0.0
|
|
84
|
+
if response["MetricDataResults"]:
|
|
85
|
+
# Access the first MetricDataResult
|
|
86
|
+
metric_data_result = response["MetricDataResults"][0]
|
|
87
|
+
# Sum the values if they exist
|
|
88
|
+
size = (
|
|
89
|
+
sum(metric_data_result["Values"]) if metric_data_result["Values"] else 0
|
|
90
|
+
)
|
|
91
|
+
else:
|
|
92
|
+
size = 0
|
|
93
|
+
|
|
94
|
+
size_mb = size / (1024 * 1024)
|
|
95
|
+
size_gb = size_mb / 1024
|
|
96
|
+
resp: Dict[str, Any] = {
|
|
97
|
+
"LogGroupName": log_group_name,
|
|
98
|
+
"Size": {
|
|
99
|
+
"Bytes": size,
|
|
100
|
+
"MB": size_mb,
|
|
101
|
+
"GB": size_gb,
|
|
102
|
+
},
|
|
103
|
+
"StartDate": start_time.isoformat(),
|
|
104
|
+
"EndDate": end_time.isoformat(),
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return resp
|
|
108
|
+
|
|
109
|
+
def get_log_sizes(
|
|
110
|
+
self,
|
|
111
|
+
start_date_time: datetime | None = None,
|
|
112
|
+
end_date_time: datetime | None = None,
|
|
113
|
+
days: int | None = 7,
|
|
114
|
+
top: int = 0,
|
|
115
|
+
) -> List[Dict[str, Any]]:
|
|
116
|
+
"""
|
|
117
|
+
Gets the log sizes for all log groups
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
start_date_time (datetime | None, optional): The Start Date. Defaults to None.
|
|
121
|
+
If None it's set to now in UTC time - the days field
|
|
122
|
+
end_date_time (datetime | None, optional): he Start Date. Defaults to None.
|
|
123
|
+
If None it's set to not in UTC time
|
|
124
|
+
days (int | None, optional): The days offset. Defaults to 7.
|
|
125
|
+
top (int, optional): If greater than zero it will return the top x after sorting
|
|
126
|
+
Defaults to 0.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
list: _description_
|
|
130
|
+
"""
|
|
131
|
+
if not days:
|
|
132
|
+
days = 7
|
|
133
|
+
start_time = start_date_time or (datetime.now(UTC) - timedelta(days=days))
|
|
134
|
+
end_time = end_date_time or datetime.now(UTC)
|
|
135
|
+
|
|
136
|
+
# Step 1: List all log groups
|
|
137
|
+
log_groups = self.cw_logs.list_log_groups()
|
|
138
|
+
log_group_sizes = []
|
|
139
|
+
|
|
140
|
+
# Step 2: Get sizes for each log group
|
|
141
|
+
for log_group in log_groups:
|
|
142
|
+
log_group_name = log_group["logGroupName"]
|
|
143
|
+
|
|
144
|
+
size_info = self.get_log_group_size(log_group_name, start_time, end_time)
|
|
145
|
+
log_group_sizes.append(size_info)
|
|
146
|
+
|
|
147
|
+
# Step 3: Sort by size
|
|
148
|
+
# top_log_groups = sorted(log_group_sizes, key=lambda x: x[1], reverse=True)
|
|
149
|
+
top_log_groups = sorted(
|
|
150
|
+
log_group_sizes,
|
|
151
|
+
key=lambda x: x.get("Size", {}).get("Bytes", 0),
|
|
152
|
+
reverse=True,
|
|
153
|
+
)
|
|
154
|
+
if top and top > 0:
|
|
155
|
+
# find the top x if provided
|
|
156
|
+
top_log_groups = top_log_groups[:top]
|
|
157
|
+
|
|
158
|
+
return top_log_groups
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def main():
|
|
162
|
+
log_group = os.environ.get("LOG_GROUP_QUERY_SAMPLE", "<enter-log-group-here>")
|
|
163
|
+
start = datetime.now() - timedelta(days=7) # Last 30 days
|
|
164
|
+
end = datetime.now()
|
|
165
|
+
cw_query: CloudWatchQuery = CloudWatchQuery()
|
|
166
|
+
result = cw_query.get_log_group_size(log_group, start, end)
|
|
167
|
+
print(result)
|
|
168
|
+
|
|
169
|
+
top = 25
|
|
170
|
+
days = 7
|
|
171
|
+
top_log_groups = cw_query.get_log_sizes(top=top, days=days)
|
|
172
|
+
print(f"Top {top} log groups by size for the last week:")
|
|
173
|
+
|
|
174
|
+
for top_log_group in top_log_groups:
|
|
175
|
+
log_group_name = top_log_group["LogGroupName"]
|
|
176
|
+
size_in_bytes = top_log_group.get("Size", {}).get("Bytes", 0)
|
|
177
|
+
size_in_megs = top_log_group.get("Size", {}).get("MB", 0)
|
|
178
|
+
size_in_gigs = top_log_group.get("Size", {}).get("GB", 0)
|
|
179
|
+
size: str = ""
|
|
180
|
+
if size_in_gigs > 1:
|
|
181
|
+
size = f"{size_in_gigs:.2f} GB"
|
|
182
|
+
elif size_in_megs > 1:
|
|
183
|
+
size = f"{size_in_megs:.2f} MB"
|
|
184
|
+
else:
|
|
185
|
+
size = f"{size_in_bytes} bytes"
|
|
186
|
+
|
|
187
|
+
print(f"{size}: {log_group_name}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
if __name__ == "__main__":
|
|
191
|
+
main()
|