clear-skies-aws 1.10.2__py3-none-any.whl → 2.0.2__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.
- {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.2.dist-info}/METADATA +36 -35
- clear_skies_aws-2.0.2.dist-info/RECORD +63 -0
- {clear_skies_aws-1.10.2.dist-info → clear_skies_aws-2.0.2.dist-info}/WHEEL +1 -1
- clear_skies_aws-2.0.2.dist-info/licenses/LICENSE +21 -0
- clearskies_aws/__init__.py +15 -2
- clearskies_aws/actions/__init__.py +13 -106
- clearskies_aws/actions/action_aws.py +74 -57
- clearskies_aws/actions/assume_role.py +43 -30
- clearskies_aws/actions/ses.py +82 -73
- clearskies_aws/actions/sns.py +27 -30
- clearskies_aws/actions/sqs.py +32 -33
- clearskies_aws/actions/step_function.py +38 -31
- clearskies_aws/backends/__init__.py +11 -4
- clearskies_aws/backends/backend.py +106 -0
- clearskies_aws/backends/dynamo_db_backend.py +150 -155
- clearskies_aws/backends/dynamo_db_condition_parser.py +40 -80
- clearskies_aws/backends/dynamo_db_parti_ql_backend.py +179 -337
- clearskies_aws/backends/sqs_backend.py +32 -51
- clearskies_aws/configs/__init__.py +0 -0
- clearskies_aws/contexts/__init__.py +23 -10
- clearskies_aws/contexts/cli_web_socket_mock.py +19 -0
- clearskies_aws/contexts/lambda_alb.py +76 -0
- clearskies_aws/contexts/lambda_api_gateway.py +75 -28
- clearskies_aws/contexts/lambda_api_gateway_web_socket.py +56 -29
- clearskies_aws/contexts/lambda_invocation.py +15 -44
- clearskies_aws/contexts/lambda_sns.py +8 -33
- clearskies_aws/contexts/lambda_sqs_standard_partial_batch.py +14 -36
- clearskies_aws/di/__init__.py +6 -1
- clearskies_aws/di/aws_additional_config_auto_import.py +37 -0
- clearskies_aws/di/inject/__init__.py +6 -0
- clearskies_aws/di/inject/boto3.py +15 -0
- clearskies_aws/di/inject/boto3_session.py +13 -0
- clearskies_aws/di/inject/parameter_store.py +15 -0
- clearskies_aws/{handlers → endpoints}/secrets_manager_rotation.py +76 -55
- clearskies_aws/endpoints/simple_body_routing.py +41 -0
- clearskies_aws/input_outputs/__init__.py +21 -8
- clearskies_aws/input_outputs/{cli_websocket_mock.py → cli_web_socket_mock.py} +9 -3
- clearskies_aws/input_outputs/lambda_alb.py +53 -0
- clearskies_aws/input_outputs/lambda_api_gateway.py +106 -88
- clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +69 -6
- clearskies_aws/input_outputs/lambda_input_output.py +87 -0
- clearskies_aws/input_outputs/lambda_invocation.py +77 -26
- clearskies_aws/input_outputs/lambda_sns.py +66 -39
- clearskies_aws/input_outputs/lambda_sqs_standard.py +70 -40
- clearskies_aws/mocks/actions/ses.py +25 -19
- clearskies_aws/mocks/actions/sns.py +18 -12
- clearskies_aws/mocks/actions/sqs.py +18 -12
- clearskies_aws/mocks/actions/step_function.py +19 -13
- clearskies_aws/models/__init__.py +0 -0
- clearskies_aws/models/web_socket_connection_model.py +182 -0
- clearskies_aws/secrets/__init__.py +13 -7
- clearskies_aws/secrets/additional_configs/__init__.py +10 -2
- clearskies_aws/secrets/additional_configs/iam_db_auth.py +26 -16
- clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py +43 -39
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +30 -31
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py +70 -49
- clearskies_aws/secrets/akeyless_with_ssm_cache.py +32 -18
- clearskies_aws/secrets/parameter_store.py +34 -32
- clearskies_aws/secrets/secrets.py +16 -0
- clearskies_aws/secrets/secrets_manager.py +78 -57
- clear_skies_aws-1.10.2.dist-info/LICENSE +0 -7
- clear_skies_aws-1.10.2.dist-info/RECORD +0 -71
- clearskies_aws/actions/assume_role_test.py +0 -72
- clearskies_aws/actions/ses_test.py +0 -89
- clearskies_aws/actions/sns_test.py +0 -77
- clearskies_aws/actions/sqs_test.py +0 -127
- clearskies_aws/actions/step_function_test.py +0 -103
- clearskies_aws/backends/dynamo_db_backend_test.py +0 -300
- clearskies_aws/backends/dynamo_db_condition_parser_test.py +0 -266
- clearskies_aws/backends/dynamo_db_parti_ql_backend_test.py +0 -544
- clearskies_aws/backends/sqs_backend_test.py +0 -31
- clearskies_aws/contexts/cli.py +0 -19
- clearskies_aws/contexts/cli_websocket_mock.py +0 -33
- clearskies_aws/contexts/lambda_elb.py +0 -30
- clearskies_aws/contexts/lambda_http_gateway.py +0 -30
- clearskies_aws/contexts/lambda_sqs_standard_partial_batch_test.py +0 -66
- clearskies_aws/contexts/wsgi.py +0 -19
- clearskies_aws/di/standard_dependencies.py +0 -60
- clearskies_aws/handlers/simple_body_routing.py +0 -39
- clearskies_aws/input_outputs/lambda_api_gateway_test.py +0 -87
- clearskies_aws/input_outputs/lambda_elb.py +0 -21
- clearskies_aws/input_outputs/lambda_http_gateway.py +0 -12
- clearskies_aws/secrets/parameter_store_test.py +0 -18
- clearskies_aws/secrets/secrets_manager_test.py +0 -18
- clearskies_aws/web_socket_connection_model.py +0 -43
- clearskies_aws/{handlers → endpoints}/__init__.py +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from types import ModuleType
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
|
|
4
6
|
class AssumeRole:
|
|
5
7
|
"""
|
|
6
8
|
Used by the various actions if you need to assume a role before making an AWS call.
|
|
@@ -16,54 +18,65 @@ class AssumeRole:
|
|
|
16
18
|
class User(clearskies.Model):
|
|
17
19
|
def __init__(self, memory_backend, columns):
|
|
18
20
|
super().__init__(memory_backend, columns)
|
|
21
|
+
|
|
19
22
|
def columns_configuration(self):
|
|
20
|
-
return OrderedDict(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
return OrderedDict(
|
|
24
|
+
[
|
|
25
|
+
clearskies.column_types.string(
|
|
26
|
+
"name",
|
|
27
|
+
on_change=[
|
|
28
|
+
clearskies_aws.actions.sqs(
|
|
29
|
+
queue_url="https://queue.url.example.aws.com",
|
|
30
|
+
assume_role=clearskies_aws.actions.assume_role(
|
|
31
|
+
role_arn="arn:aws:iam:role/name",
|
|
32
|
+
external_id="12345",
|
|
33
|
+
),
|
|
29
34
|
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
],
|
|
36
|
+
),
|
|
37
|
+
]
|
|
38
|
+
)
|
|
34
39
|
|
|
35
40
|
Example:
|
|
36
41
|
Here's a more complicated example with a double-assumme-role to show how to combine them::
|
|
37
42
|
|
|
38
43
|
first_assume_role = clearskies_aws.actions.assume_role(
|
|
39
|
-
role_arn=
|
|
40
|
-
external_id=
|
|
44
|
+
role_arn="arn:aws:123456789012:iam:role/name",
|
|
45
|
+
external_id="12345",
|
|
41
46
|
)
|
|
42
47
|
final_assume_role = clearskies_aws.actions.assume_role(
|
|
43
|
-
role_arn=
|
|
44
|
-
external_id=
|
|
48
|
+
role_arn="arn:aws:210987654321:iam:role/name-2",
|
|
49
|
+
external_id="54321",
|
|
45
50
|
source=first_assume_role,
|
|
46
51
|
)
|
|
52
|
+
|
|
53
|
+
|
|
47
54
|
class User(clearskies.Model):
|
|
48
55
|
def __init__(self, memory_backend, columns):
|
|
49
56
|
super().__init__(memory_backend, columns)
|
|
57
|
+
|
|
50
58
|
def columns_configuration(self):
|
|
51
|
-
return OrderedDict(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
return OrderedDict(
|
|
60
|
+
[
|
|
61
|
+
clearskies.column_types.string(
|
|
62
|
+
"name",
|
|
63
|
+
on_change=[
|
|
64
|
+
clearskies_aws.actions.sqs(
|
|
65
|
+
queue_url="https://queue.url.example.aws.com",
|
|
66
|
+
assume_role=final_assume_role,
|
|
67
|
+
)
|
|
68
|
+
],
|
|
69
|
+
),
|
|
70
|
+
]
|
|
71
|
+
)
|
|
60
72
|
|
|
61
73
|
"""
|
|
74
|
+
|
|
62
75
|
role_arn = ""
|
|
63
76
|
external_id = ""
|
|
64
77
|
role_session_name = ""
|
|
65
78
|
duration = 3600
|
|
66
|
-
source:
|
|
79
|
+
source: AssumeRole | None = None
|
|
67
80
|
|
|
68
81
|
def __init__(
|
|
69
82
|
self,
|
|
@@ -71,7 +84,7 @@ class AssumeRole:
|
|
|
71
84
|
external_id: str = "",
|
|
72
85
|
role_session_name: str = "",
|
|
73
86
|
duration: int = 3600,
|
|
74
|
-
source:
|
|
87
|
+
source: AssumeRole | None = None,
|
|
75
88
|
):
|
|
76
89
|
"""Assume a role."""
|
|
77
90
|
self.role_arn = role_arn
|
|
@@ -91,7 +104,7 @@ class AssumeRole:
|
|
|
91
104
|
"DurationSeconds": self.duration,
|
|
92
105
|
}
|
|
93
106
|
if self.external_id:
|
|
94
|
-
calling_params[
|
|
107
|
+
calling_params["ExternalId"] = self.external_id
|
|
95
108
|
credentials = boto3.client("sts").assume_role(**calling_params)["Credentials"]
|
|
96
109
|
|
|
97
110
|
# now let's make a new session using those
|
clearskies_aws/actions/ses.py
CHANGED
|
@@ -1,72 +1,84 @@
|
|
|
1
|
-
import
|
|
2
|
-
import clearskies
|
|
3
|
-
import datetime
|
|
4
|
-
|
|
5
|
-
from botocore.exceptions import ClientError
|
|
6
|
-
from clearskies.environment import Environment
|
|
7
|
-
from clearskies.models import Models
|
|
8
|
-
from collections.abc import Sequence
|
|
9
|
-
from typing import Any, Callable, List, Optional, Union
|
|
10
|
-
from types import ModuleType
|
|
1
|
+
from __future__ import annotations
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
from
|
|
14
|
-
from .action_aws import ActionAws
|
|
15
|
-
class SES(ActionAws):
|
|
16
|
-
_name = "ses"
|
|
17
|
-
|
|
18
|
-
def __init__(self, environment: Environment, boto3: boto3, di: StandardDependencies) -> None:
|
|
19
|
-
"""Setup action."""
|
|
20
|
-
super().__init__(environment, boto3, di)
|
|
3
|
+
import datetime
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
21
5
|
|
|
22
|
-
|
|
6
|
+
import clearskies
|
|
7
|
+
import jinja2
|
|
8
|
+
from clearskies import Model
|
|
9
|
+
from clearskies.configs import Any as AnyConfig
|
|
10
|
+
from clearskies.configs import Email, EmailOrEmailListOrCallable, String
|
|
11
|
+
from clearskies.decorators import parameters_to_properties
|
|
12
|
+
from types_boto3_ses import SESClient
|
|
13
|
+
|
|
14
|
+
from clearskies_aws.actions import action_aws
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from clearskies_aws.actions import AssumeRole
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SES(action_aws.ActionAws[SESClient]):
|
|
21
|
+
sender = Email(required=True)
|
|
22
|
+
to = EmailOrEmailListOrCallable(required=False)
|
|
23
|
+
cc = EmailOrEmailListOrCallable(required=False)
|
|
24
|
+
bcc = EmailOrEmailListOrCallable(required=False)
|
|
25
|
+
subject = String(required=False)
|
|
26
|
+
message = String(required=False)
|
|
27
|
+
subject_template = AnyConfig(required=False)
|
|
28
|
+
message_template = AnyConfig(required=False)
|
|
29
|
+
subject_template_file = String(required=False)
|
|
30
|
+
message_template_file = String(required=False)
|
|
31
|
+
dependencies_for_template: list[Any] = []
|
|
32
|
+
|
|
33
|
+
destinations: dict[str, list[str | Callable]] = {
|
|
34
|
+
"to": [],
|
|
35
|
+
"cc": [],
|
|
36
|
+
"bcc": [],
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@parameters_to_properties
|
|
40
|
+
def __init__(
|
|
23
41
|
self,
|
|
24
|
-
sender,
|
|
25
|
-
to:
|
|
26
|
-
cc:
|
|
27
|
-
bcc:
|
|
28
|
-
subject:
|
|
29
|
-
message:
|
|
30
|
-
subject_template:
|
|
31
|
-
message_template:
|
|
32
|
-
subject_template_file:
|
|
33
|
-
message_template_file:
|
|
34
|
-
assume_role:
|
|
35
|
-
dependencies_for_template:
|
|
36
|
-
when:
|
|
42
|
+
sender: str,
|
|
43
|
+
to: list | str | Callable | None = None,
|
|
44
|
+
cc: list | str | Callable | None = None,
|
|
45
|
+
bcc: list | str | Callable | None = None,
|
|
46
|
+
subject: str | None = None,
|
|
47
|
+
message: str | None = None,
|
|
48
|
+
subject_template: jinja2.Template | None = None,
|
|
49
|
+
message_template: jinja2.Template | None = None,
|
|
50
|
+
subject_template_file: str | None = None,
|
|
51
|
+
message_template_file: str | None = None,
|
|
52
|
+
assume_role: AssumeRole | None = None,
|
|
53
|
+
dependencies_for_template: list[Any] = [],
|
|
54
|
+
when: Callable | None = None,
|
|
37
55
|
) -> None:
|
|
38
56
|
"""Configure the rules for this email notification."""
|
|
39
|
-
super().
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
super().__init__(service_name="ses", assume_role=assume_role, when=when)
|
|
58
|
+
|
|
59
|
+
def configure(self):
|
|
60
|
+
self.finalize_and_validate_configuration()
|
|
61
|
+
# First finalize and validate configuration to set up defaults
|
|
62
|
+
|
|
45
63
|
# this just moves the data from the various "to" inputs (to, cc, bcc) into the self.destinations
|
|
46
64
|
# dictionary, after normalizing it so that it is always a list.
|
|
65
|
+
if not self.to and not self.cc and not self.bcc:
|
|
66
|
+
raise ValueError("You must configure at least one 'to' address or one 'cc' address or one 'bcc' address")
|
|
67
|
+
|
|
47
68
|
for key in self.destinations.keys():
|
|
48
|
-
destination_values =
|
|
69
|
+
destination_values = getattr(self, key, None)
|
|
49
70
|
if not destination_values:
|
|
50
71
|
continue
|
|
51
72
|
if type(destination_values) == str or callable(destination_values):
|
|
52
73
|
self.destinations[key] = [destination_values]
|
|
53
74
|
else:
|
|
54
75
|
self.destinations[key] = destination_values
|
|
55
|
-
self.subject = subject
|
|
56
|
-
self.message = message
|
|
57
|
-
self.sender = sender
|
|
58
|
-
self.subject_template = None
|
|
59
|
-
self.message_template = None
|
|
60
|
-
self.dependencies_for_template = dependencies_for_template if dependencies_for_template else []
|
|
61
|
-
|
|
62
|
-
if not to and not cc:
|
|
63
|
-
raise ValueError("You must configure at least one 'to' address or one 'cc' address")
|
|
64
76
|
num_subjects = 0
|
|
65
77
|
num_messages = 0
|
|
66
|
-
for source in [subject, subject_template, subject_template_file]:
|
|
78
|
+
for source in [self.subject, self.subject_template, self.subject_template_file]:
|
|
67
79
|
if source:
|
|
68
80
|
num_subjects += 1
|
|
69
|
-
for source in [message, message_template, message_template_file]:
|
|
81
|
+
for source in [self.message, self.message_template, self.message_template_file]:
|
|
70
82
|
if source:
|
|
71
83
|
num_messages += 1
|
|
72
84
|
if num_subjects > 1:
|
|
@@ -78,25 +90,21 @@ class SES(ActionAws):
|
|
|
78
90
|
"More than one of 'message', 'message_template', or 'message_template_file' was set, but only one of these may be set."
|
|
79
91
|
)
|
|
80
92
|
|
|
81
|
-
if subject_template_file:
|
|
82
|
-
|
|
83
|
-
with open(subject_template_file, "r", encoding="utf-8") as template:
|
|
93
|
+
if self.subject_template_file:
|
|
94
|
+
with open(self.subject_template_file, "r", encoding="utf-8") as template:
|
|
84
95
|
self.subject_template = jinja2.Template(template.read())
|
|
85
|
-
elif subject_template:
|
|
86
|
-
|
|
87
|
-
self.subject_template = jinja2.Template(subject_template)
|
|
96
|
+
elif self.subject_template and not isinstance(self.subject_template, jinja2.Template):
|
|
97
|
+
self.subject_template = jinja2.Template(self.subject_template)
|
|
88
98
|
|
|
89
|
-
if message_template_file:
|
|
90
|
-
|
|
91
|
-
with open(message_template_file, "r", encoding="utf-8") as template:
|
|
99
|
+
if self.message_template_file:
|
|
100
|
+
with open(self.message_template_file, "r", encoding="utf-8") as template:
|
|
92
101
|
self.message_template = jinja2.Template(template.read())
|
|
93
|
-
elif message_template:
|
|
94
|
-
|
|
95
|
-
self.message_template = jinja2.Template(message_template)
|
|
102
|
+
elif self.message_template and not isinstance(self.message_template, jinja2.Template):
|
|
103
|
+
self.message_template = jinja2.Template(self.message_template)
|
|
96
104
|
|
|
97
|
-
def _execute_action(self, client:
|
|
105
|
+
def _execute_action(self, client: SESClient, model: Model) -> None:
|
|
98
106
|
"""Send a notification as configured."""
|
|
99
|
-
utcnow = self.di.build(
|
|
107
|
+
utcnow = self.di.build("utcnow")
|
|
100
108
|
|
|
101
109
|
tos = self._resolve_destination("to", model)
|
|
102
110
|
if not tos:
|
|
@@ -118,15 +126,12 @@ class SES(ActionAws):
|
|
|
118
126
|
"Data": self._resolve_message_as_text(model, utcnow),
|
|
119
127
|
},
|
|
120
128
|
},
|
|
121
|
-
"Subject": {
|
|
122
|
-
"Charset": "utf-8",
|
|
123
|
-
"Data": self._resolve_subject(model, utcnow)
|
|
124
|
-
},
|
|
129
|
+
"Subject": {"Charset": "utf-8", "Data": self._resolve_subject(model, utcnow)},
|
|
125
130
|
},
|
|
126
131
|
Source=self.sender,
|
|
127
132
|
)
|
|
128
133
|
|
|
129
|
-
def _resolve_destination(self, name: str, model: clearskies.Model) ->
|
|
134
|
+
def _resolve_destination(self, name: str, model: clearskies.Model) -> list[str]:
|
|
130
135
|
"""
|
|
131
136
|
Return a list of to/cc/bcc addresses.
|
|
132
137
|
|
|
@@ -144,15 +149,19 @@ class SES(ActionAws):
|
|
|
144
149
|
more = [more]
|
|
145
150
|
for entry in more:
|
|
146
151
|
if not isinstance(entry, str):
|
|
147
|
-
raise ValueError(
|
|
152
|
+
raise ValueError(
|
|
153
|
+
f"I invoked a callable to fetch the '{name}' addresses for model '{model.__class__.__name__}' but it returned something other than a string. Callables must return a valid email address or a list of email addresses."
|
|
154
|
+
)
|
|
148
155
|
if "@" not in entry:
|
|
149
|
-
raise ValueError(
|
|
156
|
+
raise ValueError(
|
|
157
|
+
f"I invoked a callable to fetch the '{name}' addresses for model '{model.__class__.__name__}' but it returned a non-email address. Callables must return a valid email address or a list of email addresses."
|
|
158
|
+
)
|
|
150
159
|
resolved.extend(more)
|
|
151
160
|
continue
|
|
152
161
|
if "@" in destination:
|
|
153
162
|
resolved.append(destination)
|
|
154
163
|
continue
|
|
155
|
-
resolved.append(model
|
|
164
|
+
resolved.append(getattr(model, destination))
|
|
156
165
|
return resolved
|
|
157
166
|
|
|
158
167
|
def _resolve_message_as_html(self, model: clearskies.Model, now: datetime.datetime) -> str:
|
clearskies_aws/actions/sns.py
CHANGED
|
@@ -1,42 +1,39 @@
|
|
|
1
|
-
import
|
|
2
|
-
import clearskies
|
|
3
|
-
import datetime
|
|
4
|
-
import json
|
|
1
|
+
from __future__ import annotations
|
|
5
2
|
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from clearskies
|
|
9
|
-
from clearskies.
|
|
10
|
-
from
|
|
11
|
-
from
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from clearskies import Model
|
|
6
|
+
from clearskies.configs import Callable as CallableConfig
|
|
7
|
+
from clearskies.configs import String
|
|
8
|
+
from clearskies.decorators import parameters_to_properties
|
|
9
|
+
from types_boto3_sns import SNSClient
|
|
12
10
|
|
|
13
|
-
from ..di import StandardDependencies
|
|
14
|
-
from .assume_role import AssumeRole
|
|
15
11
|
from .action_aws import ActionAws
|
|
16
|
-
|
|
17
|
-
_name = "sns"
|
|
12
|
+
from .assume_role import AssumeRole
|
|
18
13
|
|
|
19
|
-
def __init__(self, environment: Environment, boto3: boto3, di: StandardDependencies) -> None:
|
|
20
|
-
super().__init__(environment, boto3, di)
|
|
21
14
|
|
|
22
|
-
|
|
15
|
+
class SNS(ActionAws[SNSClient]):
|
|
16
|
+
topic = String(required=False)
|
|
17
|
+
topic_environment_key = String(required=False)
|
|
18
|
+
topic_callable = CallableConfig(required=False)
|
|
19
|
+
|
|
20
|
+
@parameters_to_properties
|
|
21
|
+
def __init__(
|
|
23
22
|
self,
|
|
24
23
|
topic=None,
|
|
25
24
|
topic_environment_key=None,
|
|
26
|
-
topic_callable:
|
|
27
|
-
message_callable:
|
|
28
|
-
when:
|
|
29
|
-
assume_role:
|
|
25
|
+
topic_callable: Callable | None = None,
|
|
26
|
+
message_callable: Callable | None = None,
|
|
27
|
+
when: Callable | None = None,
|
|
28
|
+
assume_role: AssumeRole | None = None,
|
|
30
29
|
) -> None:
|
|
31
|
-
"""
|
|
32
|
-
super().
|
|
33
|
-
|
|
34
|
-
self.topic = topic
|
|
35
|
-
self.topic_environment_key = topic_environment_key
|
|
36
|
-
self.topic_callable = topic_callable
|
|
30
|
+
"""Configure the SNS action."""
|
|
31
|
+
super().__init__(service_name="sns", message_callable=message_callable, when=when, assume_role=assume_role)
|
|
37
32
|
|
|
33
|
+
def configure(self):
|
|
34
|
+
self.finalize_and_validate_configuration()
|
|
38
35
|
topics = 0
|
|
39
|
-
for value in [topic, topic_environment_key, topic_callable]:
|
|
36
|
+
for value in [self.topic, self.topic_environment_key, self.topic_callable]:
|
|
40
37
|
if value:
|
|
41
38
|
topics += 1
|
|
42
39
|
if topics > 1:
|
|
@@ -46,7 +43,7 @@ class SNS(ActionAws):
|
|
|
46
43
|
if not topics:
|
|
47
44
|
raise ValueError("You must provide at least one of 'topic', 'topic_environment_key', or 'topic_callable'.")
|
|
48
45
|
|
|
49
|
-
def _execute_action(self, client:
|
|
46
|
+
def _execute_action(self, client: SNSClient, model: Model) -> None:
|
|
50
47
|
"""Send a notification as configured."""
|
|
51
48
|
topic_arn = self.get_topic_arn(model)
|
|
52
49
|
if not topic_arn:
|
|
@@ -56,7 +53,7 @@ class SNS(ActionAws):
|
|
|
56
53
|
Message=self.get_message_body(model),
|
|
57
54
|
)
|
|
58
55
|
|
|
59
|
-
def get_topic_arn(self, model:
|
|
56
|
+
def get_topic_arn(self, model: Model) -> str:
|
|
60
57
|
if self.topic:
|
|
61
58
|
return self.topic
|
|
62
59
|
if self.topic_environment_key:
|
clearskies_aws/actions/sqs.py
CHANGED
|
@@ -1,44 +1,41 @@
|
|
|
1
|
-
import
|
|
2
|
-
import json
|
|
3
|
-
import datetime
|
|
1
|
+
from __future__ import annotations
|
|
4
2
|
|
|
5
|
-
from
|
|
6
|
-
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from clearskies.configs import Callable as CallableConfig
|
|
6
|
+
from clearskies.configs import String
|
|
7
|
+
from clearskies.decorators import parameters_to_properties
|
|
7
8
|
from clearskies.model import Model
|
|
8
|
-
from
|
|
9
|
-
from collections import OrderedDict
|
|
10
|
-
from types import ModuleType
|
|
11
|
-
from typing import List, Optional, Callable, Union
|
|
9
|
+
from types_boto3_sqs import SQSClient
|
|
12
10
|
|
|
13
|
-
from ..di import StandardDependencies
|
|
14
11
|
from . import assume_role
|
|
15
12
|
from .action_aws import ActionAws
|
|
16
|
-
class SQS(ActionAws):
|
|
17
|
-
_name = "sqs"
|
|
18
13
|
|
|
19
|
-
def __init__(self, environment: Environment, boto3: boto3, di: StandardDependencies) -> None:
|
|
20
|
-
"""Setup action."""
|
|
21
|
-
super().__init__(environment, boto3, di)
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
class SQS(ActionAws[SQSClient]):
|
|
16
|
+
queue_url = String(required=False)
|
|
17
|
+
queue_url_environment_key = String(required=False)
|
|
18
|
+
queue_url_callable = CallableConfig(required=False)
|
|
19
|
+
message_group_id = CallableConfig(required=False)
|
|
20
|
+
|
|
21
|
+
@parameters_to_properties
|
|
22
|
+
def __init__(
|
|
24
23
|
self,
|
|
25
|
-
queue_url: str =
|
|
26
|
-
queue_url_environment_key: str =
|
|
27
|
-
queue_url_callable:
|
|
28
|
-
message_callable:
|
|
29
|
-
when:
|
|
30
|
-
assume_role:
|
|
31
|
-
message_group_id:
|
|
24
|
+
queue_url: str = "",
|
|
25
|
+
queue_url_environment_key: str = "",
|
|
26
|
+
queue_url_callable: Callable | None = None,
|
|
27
|
+
message_callable: Callable | None = None,
|
|
28
|
+
when: Callable | None = None,
|
|
29
|
+
assume_role: assume_role.AssumeRole | None = None,
|
|
30
|
+
message_group_id: str | Callable | None = None,
|
|
32
31
|
) -> None:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
self.queue_url = queue_url
|
|
36
|
-
self.queue_url_environment_key = queue_url_environment_key
|
|
37
|
-
self.queue_url_callable = queue_url_callable
|
|
38
|
-
self.message_group_id = message_group_id
|
|
32
|
+
"""Set up the SQS action."""
|
|
33
|
+
super().__init__(service_name="sqs", message_callable=message_callable, when=when, assume_role=assume_role)
|
|
39
34
|
|
|
35
|
+
def configure(self):
|
|
36
|
+
self.finalize_and_validate_configuration()
|
|
40
37
|
queue_urls = 0
|
|
41
|
-
for value in [queue_url, queue_url_environment_key, queue_url_callable]:
|
|
38
|
+
for value in [self.queue_url, self.queue_url_environment_key, self.queue_url_callable]:
|
|
42
39
|
if value:
|
|
43
40
|
queue_urls += 1
|
|
44
41
|
if queue_urls > 1:
|
|
@@ -49,12 +46,12 @@ class SQS(ActionAws):
|
|
|
49
46
|
raise ValueError(
|
|
50
47
|
"You must provide at least one of 'queue_url', 'queue_url_environment_key', or 'queue_url_callable'."
|
|
51
48
|
)
|
|
52
|
-
if message_group_id and not callable(message_group_id) and not isinstance(message_group_id, str):
|
|
49
|
+
if self.message_group_id and not callable(self.message_group_id) and not isinstance(self.message_group_id, str):
|
|
53
50
|
raise ValueError(
|
|
54
51
|
"If provided, 'message_group_id' must be a string or callable, but the provided value was neither."
|
|
55
52
|
)
|
|
56
53
|
|
|
57
|
-
def _execute_action(self, client:
|
|
54
|
+
def _execute_action(self, client: SQSClient, model: Model) -> None:
|
|
58
55
|
"""Send a notification as configured."""
|
|
59
56
|
params = {
|
|
60
57
|
"QueueUrl": self.get_queue_url(model),
|
|
@@ -67,7 +64,9 @@ class SQS(ActionAws):
|
|
|
67
64
|
if callable(self.message_group_id):
|
|
68
65
|
message_group_id = self.di.call_function(self.message_group_id, model=model)
|
|
69
66
|
if not isinstance(message_group_id, str):
|
|
70
|
-
raise ValueError(
|
|
67
|
+
raise ValueError(
|
|
68
|
+
f"I called the message_group_id function for SQS for model '{model.__class__.__name__}' but the value it returned was not a string. The message group id must be a string."
|
|
69
|
+
)
|
|
71
70
|
else:
|
|
72
71
|
message_group_id = self.message_group_id
|
|
73
72
|
params["MessageGroupId"] = message_group_id
|
|
@@ -1,39 +1,45 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
from
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
import boto3
|
|
6
|
+
from clearskies import Model
|
|
7
|
+
from clearskies.configs import Callable as CallableConfig
|
|
8
|
+
from clearskies.configs import String
|
|
9
|
+
from clearskies.decorators import parameters_to_properties
|
|
10
|
+
from types_boto3_stepfunctions import SFNClient
|
|
7
11
|
|
|
8
|
-
from ..di import StandardDependencies
|
|
9
|
-
from .assume_role import AssumeRole
|
|
10
12
|
from .action_aws import ActionAws
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
from .assume_role import AssumeRole
|
|
14
|
+
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
class StepFunction(ActionAws[SFNClient]):
|
|
17
|
+
arn = String(required=False)
|
|
18
|
+
arn_environment_key = String(required=False)
|
|
19
|
+
arn_callable = CallableConfig(required=False)
|
|
20
|
+
column_to_store_execution_arn = String(required=False)
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
@parameters_to_properties
|
|
23
|
+
def __init__(
|
|
18
24
|
self,
|
|
19
|
-
arn:
|
|
20
|
-
arn_environment_key:
|
|
21
|
-
arn_callable:
|
|
22
|
-
column_to_store_execution_arn:
|
|
23
|
-
message_callable:
|
|
24
|
-
when:
|
|
25
|
-
assume_role:
|
|
25
|
+
arn: str | None = None,
|
|
26
|
+
arn_environment_key: str | None = None,
|
|
27
|
+
arn_callable: Callable | None = None,
|
|
28
|
+
column_to_store_execution_arn: str | None = None,
|
|
29
|
+
message_callable: Callable | None = None,
|
|
30
|
+
when: Callable | None = None,
|
|
31
|
+
assume_role: AssumeRole | None = None,
|
|
26
32
|
) -> None:
|
|
27
|
-
"""
|
|
28
|
-
super().
|
|
33
|
+
"""Configure the Step Function action."""
|
|
34
|
+
super().__init__(
|
|
35
|
+
service_name="stepfunctions", message_callable=message_callable, when=when, assume_role=assume_role
|
|
36
|
+
)
|
|
29
37
|
|
|
30
|
-
|
|
31
|
-
self.
|
|
32
|
-
self.arn_callable = arn_callable
|
|
33
|
-
self.column_to_store_execution_arn = column_to_store_execution_arn
|
|
38
|
+
def configure(self):
|
|
39
|
+
self.finalize_and_validate_configuration()
|
|
34
40
|
|
|
35
41
|
arns = 0
|
|
36
|
-
for value in [arn, arn_environment_key, arn_callable]:
|
|
42
|
+
for value in [self.arn, self.arn_environment_key, self.arn_callable]:
|
|
37
43
|
if value:
|
|
38
44
|
arns += 1
|
|
39
45
|
if arns > 1:
|
|
@@ -43,22 +49,23 @@ class StepFunction(ActionAws):
|
|
|
43
49
|
if not arns:
|
|
44
50
|
raise ValueError("You must provide at least one of 'arn', 'arn_environment_key', or 'arn_callable'.")
|
|
45
51
|
|
|
46
|
-
def _execute_action(self, client:
|
|
52
|
+
def _execute_action(self, client: SFNClient, model: Model) -> None:
|
|
47
53
|
"""Send a notification as configured."""
|
|
48
54
|
arn = self.get_arn(model)
|
|
49
55
|
default_region = self.default_region()
|
|
50
|
-
arn_region = arn.split(
|
|
56
|
+
arn_region = arn.split(":")[3]
|
|
51
57
|
if default_region and default_region != arn_region:
|
|
52
|
-
|
|
58
|
+
self.region = arn_region
|
|
59
|
+
client = self._get_client()
|
|
53
60
|
response = client.start_execution(
|
|
54
61
|
stateMachineArn=self.get_arn(model),
|
|
55
62
|
input=self.get_message_body(model),
|
|
56
63
|
)
|
|
57
64
|
|
|
58
65
|
if self.column_to_store_execution_arn:
|
|
59
|
-
model.save({self.column_to_store_execution_arn: response[
|
|
66
|
+
model.save({self.column_to_store_execution_arn: response["executionArn"]})
|
|
60
67
|
|
|
61
|
-
def get_arn(self, model:
|
|
68
|
+
def get_arn(self, model: Model) -> str:
|
|
62
69
|
if self.arn:
|
|
63
70
|
return self.arn
|
|
64
71
|
if self.arn_environment_key:
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
3
|
-
from .
|
|
4
|
-
from .
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from clearskies_aws.backends.backend import Backend
|
|
4
|
+
from clearskies_aws.backends.dynamo_db_backend import DynamoDBBackend
|
|
5
|
+
from clearskies_aws.backends.dynamo_db_condition_parser import DynamoDBConditionParser
|
|
6
|
+
from clearskies_aws.backends.dynamo_db_parti_ql_backend import (
|
|
7
|
+
DynamoDBPartiQLBackend,
|
|
8
|
+
DynamoDBPartiQLCursor,
|
|
9
|
+
)
|
|
10
|
+
from clearskies_aws.backends.sqs_backend import SqsBackend
|
|
5
11
|
|
|
6
12
|
__all__ = [
|
|
13
|
+
"Backend",
|
|
7
14
|
"DynamoDBBackend",
|
|
8
15
|
"SqsBackend",
|
|
9
16
|
"DynamoDBPartiQLBackend",
|