boto3-assist 0.32.0__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.
- boto3_assist/__init__.py +0 -0
- boto3_assist/aws_config.py +199 -0
- boto3_assist/aws_lambda/event_info.py +414 -0
- boto3_assist/aws_lambda/mock_context.py +5 -0
- boto3_assist/boto3session.py +87 -0
- boto3_assist/cloudwatch/cloudwatch_connection.py +84 -0
- boto3_assist/cloudwatch/cloudwatch_connection_tracker.py +17 -0
- boto3_assist/cloudwatch/cloudwatch_log_connection.py +62 -0
- boto3_assist/cloudwatch/cloudwatch_logs.py +39 -0
- boto3_assist/cloudwatch/cloudwatch_query.py +191 -0
- boto3_assist/cognito/cognito_authorizer.py +169 -0
- boto3_assist/cognito/cognito_connection.py +59 -0
- boto3_assist/cognito/cognito_utility.py +514 -0
- boto3_assist/cognito/jwks_cache.py +21 -0
- boto3_assist/cognito/user.py +27 -0
- boto3_assist/connection.py +146 -0
- boto3_assist/connection_tracker.py +120 -0
- boto3_assist/dynamodb/dynamodb.py +1206 -0
- boto3_assist/dynamodb/dynamodb_connection.py +113 -0
- boto3_assist/dynamodb/dynamodb_helpers.py +333 -0
- boto3_assist/dynamodb/dynamodb_importer.py +102 -0
- boto3_assist/dynamodb/dynamodb_index.py +507 -0
- boto3_assist/dynamodb/dynamodb_iservice.py +29 -0
- boto3_assist/dynamodb/dynamodb_key.py +130 -0
- boto3_assist/dynamodb/dynamodb_model_base.py +382 -0
- boto3_assist/dynamodb/dynamodb_model_base_interfaces.py +34 -0
- boto3_assist/dynamodb/dynamodb_re_indexer.py +165 -0
- boto3_assist/dynamodb/dynamodb_reindexer.py +165 -0
- boto3_assist/dynamodb/dynamodb_reserved_words.py +52 -0
- boto3_assist/dynamodb/dynamodb_reserved_words.txt +573 -0
- boto3_assist/dynamodb/readme.md +68 -0
- boto3_assist/dynamodb/troubleshooting.md +7 -0
- boto3_assist/ec2/ec2_connection.py +57 -0
- boto3_assist/environment_services/__init__.py +0 -0
- boto3_assist/environment_services/environment_loader.py +128 -0
- boto3_assist/environment_services/environment_variables.py +219 -0
- boto3_assist/erc/__init__.py +64 -0
- boto3_assist/erc/ecr_connection.py +57 -0
- boto3_assist/errors/custom_exceptions.py +46 -0
- boto3_assist/http_status_codes.py +80 -0
- boto3_assist/models/serializable_model.py +9 -0
- boto3_assist/role_assumption_mixin.py +38 -0
- boto3_assist/s3/s3.py +64 -0
- boto3_assist/s3/s3_bucket.py +67 -0
- boto3_assist/s3/s3_connection.py +76 -0
- boto3_assist/s3/s3_event_data.py +168 -0
- boto3_assist/s3/s3_object.py +695 -0
- boto3_assist/securityhub/securityhub.py +150 -0
- boto3_assist/securityhub/securityhub_connection.py +57 -0
- boto3_assist/session_setup_mixin.py +70 -0
- boto3_assist/ssm/connection.py +57 -0
- boto3_assist/ssm/parameter_store/parameter_store.py +116 -0
- boto3_assist/utilities/datetime_utility.py +349 -0
- boto3_assist/utilities/decimal_conversion_utility.py +140 -0
- boto3_assist/utilities/dictionary_utility.py +32 -0
- boto3_assist/utilities/file_operations.py +135 -0
- boto3_assist/utilities/http_utility.py +48 -0
- boto3_assist/utilities/logging_utility.py +0 -0
- boto3_assist/utilities/numbers_utility.py +329 -0
- boto3_assist/utilities/serialization_utility.py +664 -0
- boto3_assist/utilities/string_utility.py +337 -0
- boto3_assist/version.py +1 -0
- boto3_assist-0.32.0.dist-info/METADATA +76 -0
- boto3_assist-0.32.0.dist-info/RECORD +67 -0
- boto3_assist-0.32.0.dist-info/WHEEL +4 -0
- boto3_assist-0.32.0.dist-info/licenses/LICENSE-EXPLAINED.txt +11 -0
- boto3_assist-0.32.0.dist-info/licenses/LICENSE.txt +21 -0
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
import json
|
|
9
|
+
from typing import List, Union, Optional, IO
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from dotenv import load_dotenv
|
|
12
|
+
from aws_lambda_powertools import Logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = Logger(__name__)
|
|
16
|
+
|
|
17
|
+
DEBUGGING = os.getenv("DEBUGGING", "false").lower() == "true"
|
|
18
|
+
|
|
19
|
+
StrPath = Union[str, "os.PathLike[str]"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EnvironmentLoader:
|
|
23
|
+
"""Environment Loader"""
|
|
24
|
+
|
|
25
|
+
def __init__(self) -> None:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def load_environment_file(
|
|
29
|
+
self,
|
|
30
|
+
*,
|
|
31
|
+
starting_path: Optional[str] = None,
|
|
32
|
+
file_name: Optional[str] = None,
|
|
33
|
+
path: Optional[StrPath] = None,
|
|
34
|
+
stream: Optional[IO[str]] = None,
|
|
35
|
+
verbose: bool = False,
|
|
36
|
+
override: bool = True,
|
|
37
|
+
interpolate: bool = True,
|
|
38
|
+
encoding: Optional[str] = "utf-8",
|
|
39
|
+
raise_error_if_not_found: bool = False,
|
|
40
|
+
) -> bool:
|
|
41
|
+
"""
|
|
42
|
+
Loads an environment file into memory. This simply passes off to load_dotenv in dotenv.
|
|
43
|
+
However one small change is that I'm defaulting override to True instead of False.
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
path: Absolute or relative path to .env file.
|
|
48
|
+
stream: Text stream (such as `io.StringIO`) with .env content, used if
|
|
49
|
+
`dotenv_path` is `None`.
|
|
50
|
+
verbose: Whether to output a warning the .env file is missing.
|
|
51
|
+
override: Whether to override the system environment variables with the variables
|
|
52
|
+
from the `.env` file.
|
|
53
|
+
encoding: Encoding to be used to read the file.
|
|
54
|
+
Returns:
|
|
55
|
+
Bool: True if at least one environment variable is set else False
|
|
56
|
+
|
|
57
|
+
If both `dotenv_path` and `stream` are `None`, `find_dotenv()` is used to find the
|
|
58
|
+
.env file.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
if not starting_path:
|
|
62
|
+
starting_path = __file__
|
|
63
|
+
|
|
64
|
+
if file_name is None:
|
|
65
|
+
file_name = ".env"
|
|
66
|
+
|
|
67
|
+
new_path: str | StrPath | None = path or self.find_file(
|
|
68
|
+
starting_path=starting_path,
|
|
69
|
+
file_name=file_name,
|
|
70
|
+
raise_error_if_not_found=raise_error_if_not_found,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
loaded = load_dotenv(
|
|
74
|
+
dotenv_path=new_path,
|
|
75
|
+
stream=stream,
|
|
76
|
+
verbose=verbose,
|
|
77
|
+
override=override,
|
|
78
|
+
interpolate=interpolate,
|
|
79
|
+
encoding=encoding,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if DEBUGGING:
|
|
83
|
+
env_vars = os.environ
|
|
84
|
+
logger.debug(f"Loaded environment file: {path}")
|
|
85
|
+
print(env_vars)
|
|
86
|
+
|
|
87
|
+
return loaded
|
|
88
|
+
|
|
89
|
+
def find_file(
|
|
90
|
+
self, starting_path: str, file_name: str, raise_error_if_not_found: bool = True
|
|
91
|
+
) -> str | None:
|
|
92
|
+
"""Searches the project directory structor for a file"""
|
|
93
|
+
parents = 10
|
|
94
|
+
starting_path = starting_path or __file__
|
|
95
|
+
|
|
96
|
+
paths: List[str] = []
|
|
97
|
+
for parent in range(parents):
|
|
98
|
+
path = Path(starting_path).parents[parent].absolute()
|
|
99
|
+
logger.debug(f"searching for {file_name} in: {path}")
|
|
100
|
+
tmp = os.path.join(path, file_name)
|
|
101
|
+
paths.append(tmp)
|
|
102
|
+
if os.path.exists(tmp):
|
|
103
|
+
return tmp
|
|
104
|
+
|
|
105
|
+
if raise_error_if_not_found:
|
|
106
|
+
searched_paths = "\n".join(paths)
|
|
107
|
+
raise RuntimeError(
|
|
108
|
+
f"Failed to locate environment file: {file_name} in: \n {searched_paths}"
|
|
109
|
+
)
|
|
110
|
+
|
|
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,219 @@
|
|
|
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
|
+
|
|
9
|
+
from aws_lambda_powertools import Logger
|
|
10
|
+
|
|
11
|
+
logger = Logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EnvironmentVariables:
|
|
15
|
+
"""
|
|
16
|
+
Easy access to allo the environment variables we use in the appliction.
|
|
17
|
+
It's a best practice to use this vs doing and os.getevn in each application.
|
|
18
|
+
This helps us track all the enviroment variables in use
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
class AWS:
|
|
22
|
+
"""AWS Specific Environment Vars"""
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def region() -> str | None:
|
|
26
|
+
"""
|
|
27
|
+
gets the aws region from an environment var
|
|
28
|
+
"""
|
|
29
|
+
value = os.getenv("AWS_REGION")
|
|
30
|
+
if not value:
|
|
31
|
+
value = None
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def profile() -> str | None:
|
|
36
|
+
"""
|
|
37
|
+
Get the aws profile used for cli/boto3 commands
|
|
38
|
+
This should only be set with temporty creds and only for development purposes
|
|
39
|
+
"""
|
|
40
|
+
value = os.getenv("AWS_PROFILE")
|
|
41
|
+
if not value:
|
|
42
|
+
value = None
|
|
43
|
+
return value
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def account_id() -> str | None:
|
|
47
|
+
"""
|
|
48
|
+
gets the aws account id from an environment var
|
|
49
|
+
"""
|
|
50
|
+
value = os.getenv("AWS_ACCOUNT_ID")
|
|
51
|
+
return value
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def amazon_trace_id() -> str:
|
|
55
|
+
"""
|
|
56
|
+
gets the amazon trace id from an environment var
|
|
57
|
+
"""
|
|
58
|
+
value = os.getenv("_X_AMZN_TRACE_ID", "NA")
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def endpoint_url() -> str | None:
|
|
63
|
+
"""
|
|
64
|
+
Gets the AWS_ENDPOINT_URL environment var
|
|
65
|
+
"""
|
|
66
|
+
value = os.getenv("AWS_ENDPOINT_URL")
|
|
67
|
+
return value
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def display_aws_access_key_id() -> bool:
|
|
71
|
+
"""
|
|
72
|
+
Determines if you want to display the aws access key
|
|
73
|
+
"""
|
|
74
|
+
value = (
|
|
75
|
+
str(os.getenv("DISPLAY_AWS_ACCESS_KEY_ID", "false")).lower() == "true"
|
|
76
|
+
)
|
|
77
|
+
return value
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def display_aws_secret_access_key() -> bool:
|
|
81
|
+
"""
|
|
82
|
+
Determines if you want to display the aws access key
|
|
83
|
+
"""
|
|
84
|
+
value = (
|
|
85
|
+
str(os.getenv("DISPLAY_AWS_SECRET_ACCESS_KEY", "false")).lower()
|
|
86
|
+
== "true"
|
|
87
|
+
)
|
|
88
|
+
return value
|
|
89
|
+
|
|
90
|
+
@staticmethod
|
|
91
|
+
def aws_access_key_id() -> str | None:
|
|
92
|
+
"""
|
|
93
|
+
The aws_access_key_id. Often used for local development.
|
|
94
|
+
"""
|
|
95
|
+
value = os.getenv("ACCESS_KEY_ID")
|
|
96
|
+
return value
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def aws_secret_access_key() -> str | None:
|
|
100
|
+
"""
|
|
101
|
+
The aws_secret_access_key. Often used for local development.
|
|
102
|
+
"""
|
|
103
|
+
value = os.getenv("SECRET_ACCESS_KEY")
|
|
104
|
+
return value
|
|
105
|
+
|
|
106
|
+
class SES:
|
|
107
|
+
"""SES Settings"""
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def user_name() -> str | None:
|
|
111
|
+
"""
|
|
112
|
+
gets the ses user-name from an environment var
|
|
113
|
+
"""
|
|
114
|
+
value = os.getenv("SES_USER_NAME")
|
|
115
|
+
return value
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def password() -> str | None:
|
|
119
|
+
"""
|
|
120
|
+
gets the ses password from an environment var
|
|
121
|
+
"""
|
|
122
|
+
value = os.getenv("SES_PASSWORD")
|
|
123
|
+
return value
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def endpoint() -> str | None:
|
|
127
|
+
"""
|
|
128
|
+
gets the ses endpoint from an environment var
|
|
129
|
+
"""
|
|
130
|
+
value = os.getenv("SES_END_POINT")
|
|
131
|
+
return value
|
|
132
|
+
|
|
133
|
+
class Cognito:
|
|
134
|
+
"""Cognito Settings"""
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def user_pool() -> str | None:
|
|
138
|
+
"""
|
|
139
|
+
gets the cognito user pool from an environment var
|
|
140
|
+
"""
|
|
141
|
+
value = os.getenv("COGNITO_USER_POOL")
|
|
142
|
+
return value
|
|
143
|
+
|
|
144
|
+
class DynamoDB:
|
|
145
|
+
"""DynamoDB Settings"""
|
|
146
|
+
|
|
147
|
+
@staticmethod
|
|
148
|
+
def raise_on_error_setting() -> bool:
|
|
149
|
+
"""
|
|
150
|
+
determines if we raise errors on saves, gets, etc
|
|
151
|
+
this is useful to turn off for some local testing. but otherwise I
|
|
152
|
+
would recommend to leave it on
|
|
153
|
+
"""
|
|
154
|
+
value = str(os.getenv("RAISE_ON_DB_ERROR", "true")).lower() == "true"
|
|
155
|
+
|
|
156
|
+
return value
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def single_table() -> str | None:
|
|
160
|
+
"""
|
|
161
|
+
If a single table design is used this can be a usefull way to send it around
|
|
162
|
+
"""
|
|
163
|
+
value = os.getenv("DYNAMODB_SINGLE_TABLE")
|
|
164
|
+
return value
|
|
165
|
+
|
|
166
|
+
@staticmethod
|
|
167
|
+
def endpoint_url() -> str | None:
|
|
168
|
+
"""
|
|
169
|
+
The DynamoDB Endpoint url. Often used for local development.
|
|
170
|
+
For example a docker containers defaults to http://localhost:8000
|
|
171
|
+
"""
|
|
172
|
+
value = os.getenv("AWS_DYNAMODB_ENDPOINT_URL")
|
|
173
|
+
return value
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def aws_access_key_id() -> str | None:
|
|
177
|
+
"""
|
|
178
|
+
The DynamoDB aws_access_key_id. Often used for local development.
|
|
179
|
+
For example a docker containers defaults to dummy_access_key
|
|
180
|
+
"""
|
|
181
|
+
value = os.getenv("AWS_DYNAMODB_ACCESS_KEY_ID")
|
|
182
|
+
return value
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def aws_secret_access_key() -> str | None:
|
|
186
|
+
"""
|
|
187
|
+
The DynamoDB aws_secret_access_key. Often used for local development.
|
|
188
|
+
For example a docker containers defaults to dummy_secret_key
|
|
189
|
+
"""
|
|
190
|
+
value = os.getenv("AWS_DYNAMODB_SECRET_ACCESS_KEY")
|
|
191
|
+
return value
|
|
192
|
+
|
|
193
|
+
@staticmethod
|
|
194
|
+
def get_integration_tests_setting() -> bool:
|
|
195
|
+
"""
|
|
196
|
+
deteremine if integration tests are run from an environment var
|
|
197
|
+
"""
|
|
198
|
+
value = str(os.getenv("RUN_INTEGRATION_TESTS", "False")).lower() == "true"
|
|
199
|
+
env = EnvironmentVariables.get_environment_setting()
|
|
200
|
+
|
|
201
|
+
if env.lower().startswith("prod"):
|
|
202
|
+
value = False
|
|
203
|
+
|
|
204
|
+
return value
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def get_environment_setting() -> str:
|
|
208
|
+
"""
|
|
209
|
+
gets the environment name from an environment var
|
|
210
|
+
"""
|
|
211
|
+
value = os.getenv("ENVIRONMENT")
|
|
212
|
+
|
|
213
|
+
if not value:
|
|
214
|
+
logger.warning(
|
|
215
|
+
"ENVIRONMENT var is not set. A future version will throw an error."
|
|
216
|
+
)
|
|
217
|
+
return ""
|
|
218
|
+
|
|
219
|
+
return value
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
from botocore.exceptions import ClientError
|
|
3
|
+
from .ecr_connection import ECRConnection
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ECR(ECRConnection):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
def retag_image(self, repository_name: str, source_tag: str, target_tag: str):
|
|
10
|
+
"""
|
|
11
|
+
ReTag an ECR Image without the overhead of pulling it down, tagging and pushing.
|
|
12
|
+
Simply retags by getting the manifest, and pushing up changes.
|
|
13
|
+
Args:
|
|
14
|
+
repository_name (str):
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
ecr = self.client
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
# 1) Get the manifest for the source tag
|
|
21
|
+
resp = ecr.batch_get_image(
|
|
22
|
+
repositoryName=repository_name,
|
|
23
|
+
imageIds=[{"imageTag": source_tag}],
|
|
24
|
+
# optional, but helps ensure you get the right manifest type:
|
|
25
|
+
acceptedMediaTypes=[
|
|
26
|
+
"application/vnd.docker.distribution.manifest.v2+json",
|
|
27
|
+
"application/vnd.oci.image.manifest.v1+json",
|
|
28
|
+
],
|
|
29
|
+
)
|
|
30
|
+
images = resp.get("images", [])
|
|
31
|
+
if not images:
|
|
32
|
+
raise RuntimeError(f"No image found with tag {source_tag!r}")
|
|
33
|
+
|
|
34
|
+
manifest = images[0]["imageManifest"]
|
|
35
|
+
|
|
36
|
+
# 2) Push the same manifest under the new tag
|
|
37
|
+
put_resp = ecr.put_image(
|
|
38
|
+
repositoryName=repository_name,
|
|
39
|
+
imageManifest=manifest,
|
|
40
|
+
imageTag=target_tag,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
failures = put_resp.get("failures")
|
|
44
|
+
|
|
45
|
+
if failures and len(failures) > 0:
|
|
46
|
+
raise RuntimeError(
|
|
47
|
+
f"Failed to tag {repository_name}:{target_tag} - {failures}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
print(f"Successfully tagged {repository_name}:{target_tag}")
|
|
51
|
+
return put_resp
|
|
52
|
+
|
|
53
|
+
except ClientError as e:
|
|
54
|
+
print(
|
|
55
|
+
f"ECR error: {e.response.get('Error', {}).get('Code', 'NA')} "
|
|
56
|
+
f"- {e.response.get('Error', {}).get('Message', 'NA')}"
|
|
57
|
+
)
|
|
58
|
+
raise
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
# Example: retag version 1.16.54 → dev
|
|
63
|
+
ecr: ECR = ECR(aws_profile="geek-cafe", aws_region="us-east-1")
|
|
64
|
+
ecr.retag_image("my-repo", "1.16.54", "dev")
|
|
@@ -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_ecr import Client
|
|
16
|
+
else:
|
|
17
|
+
Client = object
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
logger = Logger()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ECRConnection(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="ecr",
|
|
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: Client | None = None
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def client(self) -> Client:
|
|
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: Client):
|
|
56
|
+
logger.info("Setting Client")
|
|
57
|
+
self.__client = value
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
class Error(Exception):
|
|
2
|
+
"""Base class for exceptions in this module."""
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DbFailures(Error):
|
|
6
|
+
"""DB Failure Error"""
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InvalidHttpMethod(Exception):
|
|
10
|
+
"""Invalid Http Method"""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
code=422,
|
|
15
|
+
message="Invalid Http Method",
|
|
16
|
+
):
|
|
17
|
+
"""The user account is not valid"""
|
|
18
|
+
self.message = {
|
|
19
|
+
"status_code": code,
|
|
20
|
+
"message": message,
|
|
21
|
+
}
|
|
22
|
+
super().__init__(self.message)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class InvalidRoutePath(Exception):
|
|
26
|
+
"""Invalid Http Route"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, message="Invalid Route"):
|
|
29
|
+
"""Invalid Route"""
|
|
30
|
+
self.message = {
|
|
31
|
+
"status_code": 404,
|
|
32
|
+
"message": message,
|
|
33
|
+
}
|
|
34
|
+
super().__init__(self.message)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FileNotFound(Exception):
|
|
38
|
+
"""File Not Found Error"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, message="File Not Found"):
|
|
41
|
+
"""Invalid Route"""
|
|
42
|
+
self.message = {
|
|
43
|
+
"status_code": 404,
|
|
44
|
+
"message": message,
|
|
45
|
+
}
|
|
46
|
+
super().__init__(self.message)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class HttpStatusCodes(Enum):
|
|
11
|
+
"""Http Status Codes"""
|
|
12
|
+
|
|
13
|
+
HTTP_400_BAD_REQUEST = 400
|
|
14
|
+
"""
|
|
15
|
+
The server cannot or will not process the request due to something that is perceived to be a client error
|
|
16
|
+
(e.g., malformed request syntax, invalid request message framing, or deceptive request routing)."""
|
|
17
|
+
HTTP_401_UNAUTHENTICATED = 401
|
|
18
|
+
"""
|
|
19
|
+
Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated".
|
|
20
|
+
That is, the client must authenticate itself to get the requested response.
|
|
21
|
+
"""
|
|
22
|
+
HTTP_403_FORBIDDEN = 403
|
|
23
|
+
"""
|
|
24
|
+
The client does not have access rights to the content; that is, it is "unauthorized", so the server is refusing
|
|
25
|
+
to give the requested resource. Unlike 401 Unauthorized (which is technically UnAuthenticated);
|
|
26
|
+
here, the client's identity is known to the server.
|
|
27
|
+
"""
|
|
28
|
+
HTTP_404_NOT_FOUND = 404
|
|
29
|
+
"""
|
|
30
|
+
The server cannot find the requested resource. In the browser, this means the URL is not recognized.
|
|
31
|
+
In an API, this can also mean that the endpoint is valid but the resource itself does not exist.
|
|
32
|
+
Servers may also send this response instead of 403 Forbidden to hide the existence of a resource from
|
|
33
|
+
an unauthorized client. This response code is probably the most well known due to its frequent occurrence on the web.
|
|
34
|
+
"""
|
|
35
|
+
HTTP_405_METHOD_NOT_ALLOWED = 405
|
|
36
|
+
"""
|
|
37
|
+
The request method is known by the server but is not supported by the target resource.
|
|
38
|
+
For example, an API may not allow calling DELETE to remove a resource.
|
|
39
|
+
"""
|
|
40
|
+
HTTP_406_NOT_ACCEPTABLE = 406
|
|
41
|
+
"""
|
|
42
|
+
This response is sent when the web server, after performing server-driven content negotiation, doesn't find any
|
|
43
|
+
content that conforms to the criteria given by the user agent.
|
|
44
|
+
"""
|
|
45
|
+
HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407
|
|
46
|
+
"""
|
|
47
|
+
This is similar to 401 Unauthorized but authentication is needed to be done by a proxy.
|
|
48
|
+
"""
|
|
49
|
+
HTTP_408_REQUEST_TIMEOUT = 408
|
|
50
|
+
"""
|
|
51
|
+
This response is sent on an idle connection by some servers, even without any previous request by the client.
|
|
52
|
+
It means that the server would like to shut down this unused connection. This response is used much more since
|
|
53
|
+
some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection mechanisms to speed up surfing.
|
|
54
|
+
Also note that some servers merely shut down the connection without sending this message.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415
|
|
58
|
+
"""
|
|
59
|
+
The media format of the requested data is not supported by the server, so the server is rejecting the request.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
HTTP_418_IM_A_TEAPOT = 418
|
|
63
|
+
"""
|
|
64
|
+
The server refuses the attempt to brew coffee with a teapot.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
HTTP_422_UNEXPECTED_OUTCOME = 422
|
|
68
|
+
"""
|
|
69
|
+
The request was well-formed but was unable to be followed due to semantic errors.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
HTTP_429_TOO_MANY_REQUESTS = 429
|
|
73
|
+
"""
|
|
74
|
+
The user has sent too many requests in a given amount of time ("rate limiting").
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
HTTP_500_INTERNAL_SERVER_ERROR = 500
|
|
78
|
+
"""
|
|
79
|
+
The server has encountered a situation it does not know how to handle.
|
|
80
|
+
"""
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from boto3_assist.utilities.serialization_utility import SerializableModel # noqa: F401 # pylint: disable=unused-import
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geek Cafe, LLC
|
|
3
|
+
Maintainers: Eric Wilson
|
|
4
|
+
MIT License. See Project Root for the license information.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import boto3
|
|
8
|
+
from typing import List
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RoleAssumptionMixin:
|
|
12
|
+
def _assume_roles_in_chain(
|
|
13
|
+
self,
|
|
14
|
+
base_session: boto3.Session,
|
|
15
|
+
role_chain: List[str],
|
|
16
|
+
session_name: str,
|
|
17
|
+
duration_seconds: int,
|
|
18
|
+
region: str,
|
|
19
|
+
) -> boto3.Session:
|
|
20
|
+
session = base_session
|
|
21
|
+
|
|
22
|
+
for role_arn in role_chain:
|
|
23
|
+
sts_client = session.client("sts")
|
|
24
|
+
response = sts_client.assume_role(
|
|
25
|
+
RoleArn=role_arn,
|
|
26
|
+
RoleSessionName=session_name,
|
|
27
|
+
DurationSeconds=duration_seconds,
|
|
28
|
+
)
|
|
29
|
+
creds = response["Credentials"]
|
|
30
|
+
|
|
31
|
+
session = boto3.Session(
|
|
32
|
+
aws_access_key_id=creds["AccessKeyId"],
|
|
33
|
+
aws_secret_access_key=creds["SecretAccessKey"],
|
|
34
|
+
aws_session_token=creds["SessionToken"],
|
|
35
|
+
region_name=region,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return session
|