clear-skies-aws 2.0.1__py3-none-any.whl → 2.0.3__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-2.0.1.dist-info → clear_skies_aws-2.0.3.dist-info}/METADATA +2 -2
- clear_skies_aws-2.0.3.dist-info/RECORD +63 -0
- {clear_skies_aws-2.0.1.dist-info → clear_skies_aws-2.0.3.dist-info}/WHEEL +1 -1
- clearskies_aws/__init__.py +27 -0
- clearskies_aws/actions/__init__.py +15 -0
- clearskies_aws/actions/action_aws.py +135 -0
- clearskies_aws/actions/assume_role.py +115 -0
- clearskies_aws/actions/ses.py +203 -0
- clearskies_aws/actions/sns.py +61 -0
- clearskies_aws/actions/sqs.py +81 -0
- clearskies_aws/actions/step_function.py +73 -0
- clearskies_aws/backends/__init__.py +19 -0
- clearskies_aws/backends/backend.py +106 -0
- clearskies_aws/backends/dynamo_db_backend.py +609 -0
- clearskies_aws/backends/dynamo_db_condition_parser.py +325 -0
- clearskies_aws/backends/dynamo_db_parti_ql_backend.py +965 -0
- clearskies_aws/backends/sqs_backend.py +61 -0
- clearskies_aws/configs/__init__.py +0 -0
- clearskies_aws/contexts/__init__.py +23 -0
- clearskies_aws/contexts/cli_web_socket_mock.py +20 -0
- clearskies_aws/contexts/lambda_alb.py +81 -0
- clearskies_aws/contexts/lambda_api_gateway.py +81 -0
- clearskies_aws/contexts/lambda_api_gateway_web_socket.py +79 -0
- clearskies_aws/contexts/lambda_invoke.py +138 -0
- clearskies_aws/contexts/lambda_sns.py +124 -0
- clearskies_aws/contexts/lambda_sqs_standard.py +139 -0
- clearskies_aws/di/__init__.py +6 -0
- 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/endpoints/__init__.py +1 -0
- clearskies_aws/endpoints/secrets_manager_rotation.py +194 -0
- clearskies_aws/endpoints/simple_body_routing.py +41 -0
- clearskies_aws/input_outputs/__init__.py +21 -0
- clearskies_aws/input_outputs/cli_web_socket_mock.py +20 -0
- clearskies_aws/input_outputs/lambda_alb.py +53 -0
- clearskies_aws/input_outputs/lambda_api_gateway.py +123 -0
- clearskies_aws/input_outputs/lambda_api_gateway_web_socket.py +73 -0
- clearskies_aws/input_outputs/lambda_input_output.py +89 -0
- clearskies_aws/input_outputs/lambda_invoke.py +88 -0
- clearskies_aws/input_outputs/lambda_sns.py +88 -0
- clearskies_aws/input_outputs/lambda_sqs_standard.py +86 -0
- clearskies_aws/mocks/__init__.py +1 -0
- clearskies_aws/mocks/actions/__init__.py +6 -0
- clearskies_aws/mocks/actions/ses.py +34 -0
- clearskies_aws/mocks/actions/sns.py +29 -0
- clearskies_aws/mocks/actions/sqs.py +29 -0
- clearskies_aws/mocks/actions/step_function.py +32 -0
- clearskies_aws/models/__init__.py +1 -0
- clearskies_aws/models/web_socket_connection_model.py +182 -0
- clearskies_aws/secrets/__init__.py +13 -0
- clearskies_aws/secrets/additional_configs/__init__.py +62 -0
- clearskies_aws/secrets/additional_configs/iam_db_auth.py +39 -0
- clearskies_aws/secrets/additional_configs/iam_db_auth_with_ssm.py +96 -0
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +80 -0
- clearskies_aws/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssm_bastion.py +162 -0
- clearskies_aws/secrets/akeyless_with_ssm_cache.py +60 -0
- clearskies_aws/secrets/parameter_store.py +52 -0
- clearskies_aws/secrets/secrets.py +16 -0
- clearskies_aws/secrets/secrets_manager.py +96 -0
- clear_skies_aws-2.0.1.dist-info/RECORD +0 -4
- {clear_skies_aws-2.0.1.dist-info → clear_skies_aws-2.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from clearskies import Model
|
|
7
|
+
from clearskies.query import Query
|
|
8
|
+
from types_boto3_sqs import SQSClient
|
|
9
|
+
|
|
10
|
+
from clearskies_aws.backends import backend
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SqsBackend(backend.Backend):
|
|
14
|
+
"""
|
|
15
|
+
SQS backend for clearskies.
|
|
16
|
+
|
|
17
|
+
There's not too much to this. Just set it on your model and set the table name equal to the SQS url.
|
|
18
|
+
|
|
19
|
+
This doesn't support setting message attributes. The SQS call is simple enough that if you need
|
|
20
|
+
those you may as well just invoke the boto3 SDK yourself.
|
|
21
|
+
|
|
22
|
+
Note that this is a *write-only* backend. Reading from an SQS queue is different enough from
|
|
23
|
+
the way that clearskies models works that it doesn't make sense to try to make those happen here.
|
|
24
|
+
|
|
25
|
+
See the SQS context in this library for processing your queue data.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
_sqs: SQSClient
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def sqs(self) -> SQSClient:
|
|
32
|
+
if not hasattr(self, "_sqs"):
|
|
33
|
+
if not self.environment.get("AWS_REGION", True):
|
|
34
|
+
raise ValueError("To use SQS you must use set AWS_REGION in the .env file or an environment variable")
|
|
35
|
+
|
|
36
|
+
self._sqs = self.boto3.client("sqs", region_name=self.environment.get("AWS_REGION", True))
|
|
37
|
+
|
|
38
|
+
return self._sqs
|
|
39
|
+
|
|
40
|
+
def create(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
41
|
+
self.sqs.send_message(
|
|
42
|
+
QueueUrl=model.destination_name(),
|
|
43
|
+
MessageBody=json.dumps(data),
|
|
44
|
+
)
|
|
45
|
+
return {**data}
|
|
46
|
+
|
|
47
|
+
def update(self, id: int | str, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
48
|
+
raise ValueError("The SQS backend only supports the create operation")
|
|
49
|
+
|
|
50
|
+
def delete(self, id: int | str, model: Model) -> bool:
|
|
51
|
+
raise ValueError("The SQS backend only supports the create operation")
|
|
52
|
+
|
|
53
|
+
def count(self, query: Query) -> int:
|
|
54
|
+
raise ValueError("The SQS backend only supports the create operation")
|
|
55
|
+
|
|
56
|
+
def records(
|
|
57
|
+
self,
|
|
58
|
+
query: Query,
|
|
59
|
+
next_page_data: dict[str, str | int] | None = None,
|
|
60
|
+
) -> list[dict[str, Any]]:
|
|
61
|
+
raise ValueError("The SQS backend only supports the create operation")
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from clearskies_aws.contexts.cli_web_socket_mock import CliWebSocketMock
|
|
4
|
+
from clearskies_aws.contexts.lambda_alb import LambdaAlb
|
|
5
|
+
from clearskies_aws.contexts.lambda_api_gateway import LambdaApiGateway
|
|
6
|
+
from clearskies_aws.contexts.lambda_api_gateway_web_socket import (
|
|
7
|
+
LambdaApiGatewayWebSocket,
|
|
8
|
+
)
|
|
9
|
+
from clearskies_aws.contexts.lambda_invoke import LambdaInvoke
|
|
10
|
+
from clearskies_aws.contexts.lambda_sns import LambdaSns
|
|
11
|
+
from clearskies_aws.contexts.lambda_sqs_standard import (
|
|
12
|
+
LambdaSqsStandard,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"CliWebSocketMock",
|
|
17
|
+
"LambdaAlb",
|
|
18
|
+
"LambdaApiGateway",
|
|
19
|
+
"LambdaApiGatewayWebSocket",
|
|
20
|
+
"LambdaInvoke",
|
|
21
|
+
"LambdaSns",
|
|
22
|
+
"LambdaSqsStandard",
|
|
23
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from clearskies.contexts import cli
|
|
4
|
+
|
|
5
|
+
from clearskies_aws.input_outputs import CliWebSocketMock as CliWebSocketMockInputOutput
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CliWebSocketMock(cli.Cli):
|
|
9
|
+
"""
|
|
10
|
+
Help assist with testing websockets locally.
|
|
11
|
+
|
|
12
|
+
The LambdaApiGatewayWebSocket context makes it easy to run websocket applications, but testing
|
|
13
|
+
these locally is literally impossible. This context provides a close analogue to the way
|
|
14
|
+
the LambdaApiGatewayWebSocket context works to give some testing capabilities when running
|
|
15
|
+
locally. It works identically to `clearskies.contexts.Cli` but you have to provide a
|
|
16
|
+
`connection_id` property in the JSON body.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __call__(self):
|
|
20
|
+
return self.execute_application(CLIWebSocketMockInputOutput())
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from clearskies.contexts.context import Context
|
|
6
|
+
|
|
7
|
+
from clearskies_aws.input_outputs import LambdaAlb as LambdaAlbInputOutput
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LambdaAlb(Context):
|
|
11
|
+
"""
|
|
12
|
+
Run a clearskies application in a lambda behind an application load balancer.
|
|
13
|
+
|
|
14
|
+
There's nothing special here: just build your application, use the LambdaAlb context in a standard AWS lambda
|
|
15
|
+
handler, and attach your lambda to an ALB. This generally expects that the ALB will forward all requests to
|
|
16
|
+
the clearskies application, which will therefore handle all routing. However, you can also use path-based
|
|
17
|
+
routing in your target group to forward some subset of requests to separate lambdas, each using this same
|
|
18
|
+
context. When you do this, keep in mind that AWS still passes along the full path (including the part handled
|
|
19
|
+
by the ALB), so you want to make sure that your clearskies application is configured with the full URL as well.
|
|
20
|
+
|
|
21
|
+
Per AWS norms, you should create the context in the "root" of your python application, and then invoke it
|
|
22
|
+
inside a standard lambda handler function. This will allow AWS to cache the full application, improving
|
|
23
|
+
performance. If you create and invoke the context inside of your lambda handler, it will effectively turn
|
|
24
|
+
off any caching. In addition, clearskies does a fair amount of configuration validation when you create the
|
|
25
|
+
context, so this work will be repeated on every call.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
import clearskies
|
|
29
|
+
import clearskies_aws
|
|
30
|
+
from clearskies.validators import Required, Unique
|
|
31
|
+
from clearskies import columns
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class User(clearskies.Model):
|
|
35
|
+
id_column_name = "id"
|
|
36
|
+
backend = clearskies.backends.MemoryBackend()
|
|
37
|
+
|
|
38
|
+
id = columns.Uuid()
|
|
39
|
+
name = columns.String(validators=[Required()])
|
|
40
|
+
username = columns.String(
|
|
41
|
+
validators=[
|
|
42
|
+
Required(),
|
|
43
|
+
Unique(),
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
age = columns.Integer(validators=[Required()])
|
|
47
|
+
created_at = columns.Created()
|
|
48
|
+
updated_at = columns.Updated()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
application = clearskies_aws.contexts.LambdaAlb(
|
|
52
|
+
clearskies.endpoints.RestfulApi(
|
|
53
|
+
url="users",
|
|
54
|
+
model_class=User,
|
|
55
|
+
readable_column_names=["id", "name", "username", "age", "created_at", "updated_at"],
|
|
56
|
+
writeable_column_names=["name", "username", "age"],
|
|
57
|
+
sortable_column_names=["id", "name", "username", "age", "created_at", "updated_at"],
|
|
58
|
+
searchable_column_names=["id", "name", "username", "age", "created_at", "updated_at"],
|
|
59
|
+
default_sort_column_name="name",
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def lambda_handler(event, context):
|
|
65
|
+
return application(event, context)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Context Specifics
|
|
69
|
+
|
|
70
|
+
When using this context, two additional named arguments become available to any callables invoked by clearskies:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
| Name | Type | Description |
|
|
74
|
+
|:-------------:|:----------------:|----------------------------------|
|
|
75
|
+
| `event` | `dict[str, Any]` | The lambda `event` object |
|
|
76
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
77
|
+
```
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __call__(self, event: dict[str, Any], context: dict[str, Any]) -> Any: # type: ignore[override]
|
|
81
|
+
return self.execute_application(LambdaAlbInputOutput(event, context))
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from clearskies.contexts.context import Context
|
|
6
|
+
|
|
7
|
+
from clearskies_aws.input_outputs import LambdaApiGateway as LambdaApiGatewayInputOutput
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LambdaApiGateway(Context):
|
|
11
|
+
"""
|
|
12
|
+
Run a clearskies application in a lambda behind an API Gateway (v1 or v2).
|
|
13
|
+
|
|
14
|
+
There's nothing special here: just build your application, use the LambdaApiGateway context in a standard AWS
|
|
15
|
+
lambda handler, and attach your lambda to an Api Gateway. Per AWS norms, you should create the context in
|
|
16
|
+
the "root" of your python application, and then invoke it inside a standard lambda handler function. This
|
|
17
|
+
will allow AWS to cache the full application, improving performance. If you create and invoke the context
|
|
18
|
+
inside of your lambda handler, it will effectively turn off any caching. In addition, clearskies does a fair
|
|
19
|
+
amount of configuration validation when you create the context, so this work will be repeated on every call.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
import clearskies
|
|
23
|
+
import clearskies_aws
|
|
24
|
+
from clearskies.validators import Required, Unique
|
|
25
|
+
from clearskies import columns
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class User(clearskies.Model):
|
|
29
|
+
id_column_name = "id"
|
|
30
|
+
backend = clearskies.backends.MemoryBackend()
|
|
31
|
+
|
|
32
|
+
id = columns.Uuid()
|
|
33
|
+
name = columns.String(validators=[Required()])
|
|
34
|
+
username = columns.String(
|
|
35
|
+
validators=[
|
|
36
|
+
Required(),
|
|
37
|
+
Unique(),
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
age = columns.Integer(validators=[Required()])
|
|
41
|
+
created_at = columns.Created()
|
|
42
|
+
updated_at = columns.Updated()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
application = clearskies_aws.contexts.LambdaApiGateway(
|
|
46
|
+
clearskies.endpoints.RestfulApi(
|
|
47
|
+
url="users",
|
|
48
|
+
model_class=User,
|
|
49
|
+
readable_column_names=["id", "name", "username", "age", "created_at", "updated_at"],
|
|
50
|
+
writeable_column_names=["name", "username", "age"],
|
|
51
|
+
sortable_column_names=["id", "name", "username", "age", "created_at", "updated_at"],
|
|
52
|
+
searchable_column_names=["id", "name", "username", "age", "created_at", "updated_at"],
|
|
53
|
+
default_sort_column_name="name",
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def lambda_handler(event, context):
|
|
59
|
+
return application(event, context)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Context Specifics
|
|
63
|
+
|
|
64
|
+
When using this context, a number of additional named arguments become available to any callables invoked by
|
|
65
|
+
clearskies:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
| Name | Type | Description |
|
|
69
|
+
|:-------------:|:----------------:|----------------------------------|
|
|
70
|
+
| ` event` | `dict[str, Any]` | The lambda `event` object |
|
|
71
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
72
|
+
| `resource` | `str` | The route resource |
|
|
73
|
+
| `stage` | `str` | The stage of the lambda function |
|
|
74
|
+
| `request_id` | `str` | The AWS request id for the call |
|
|
75
|
+
| `api_id` | `str` | The id of the API |
|
|
76
|
+
| `api_version` | `str` | "v1" or "v2" |
|
|
77
|
+
```
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
def __call__(self, event: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]: # type: ignore[override]
|
|
81
|
+
return self.execute_application(LambdaApiGatewayInputOutput(event, context))
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from clearskies.contexts.context import Context
|
|
6
|
+
|
|
7
|
+
from clearskies_aws.input_outputs import (
|
|
8
|
+
LambdaApiGatewayWebSocket as LambdaApiGatewayWebSocketInputOutput,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LambdaApiGatewayWebSocket(Context):
|
|
13
|
+
"""
|
|
14
|
+
Run a clearskies application behind an API Gateway that is configured for use as a websocket.
|
|
15
|
+
|
|
16
|
+
Websockets work much differently than standard API endpoints. Most importantly, none of the standard HTTP
|
|
17
|
+
concepts exist. Websockets requests don't have any of:
|
|
18
|
+
|
|
19
|
+
1. URL Path
|
|
20
|
+
2. Query Parameters
|
|
21
|
+
3. HTTP Headers
|
|
22
|
+
4. Response Headers
|
|
23
|
+
5. An HTTP Response
|
|
24
|
+
|
|
25
|
+
So in short, everything works completely differently. The reason is because a websocket is a
|
|
26
|
+
two-way communication channel that's created over a TCP/IP connection. It does start with an HTTP request,
|
|
27
|
+
but this is a one time request when the communication channel is first created. Later messages (which
|
|
28
|
+
are where the bulk of the communication happens) travel over the already-open connection, so
|
|
29
|
+
the communication looks nothing like HTTP. Usually, the data traveling over this connection is
|
|
30
|
+
a JSON payload, and since the connection is already opened it doesn't have any of the metadata associated
|
|
31
|
+
with an HTTP request (hence the lack of url/query/headers). In addition, the communication is no longer
|
|
32
|
+
transactional - messages from the client to the server do not come with a direct response, and the server
|
|
33
|
+
can send messages to the client without needing the former to initiate the conversation.
|
|
34
|
+
|
|
35
|
+
Routing and authorization are usually handled in-band, which means that the routing parameters or authentication
|
|
36
|
+
data are added directly to the JSON body sent over the open connection. This often results in applications
|
|
37
|
+
having to handle such things themselves, since the typical standards of web frameworks won't match up. In
|
|
38
|
+
the case of routing with an API Gateway, it has its own suggested standard of setting a routekey where the
|
|
39
|
+
API gateway will check for an application-defined route parameter in the request body and use this to route
|
|
40
|
+
to an appropriate lambda. With clearskies, you can also use the `clearskies.endpoints.BodyParameterRouting`
|
|
41
|
+
to accomplish the same.
|
|
42
|
+
|
|
43
|
+
With a websocket through API Gateway, headers are available during the `on_connect` phase, so you can always
|
|
44
|
+
perform authentication then and record the result with the connection id (which can be used much like a
|
|
45
|
+
session id). Otherwise, authentication is typically handled by including the authentication token in every
|
|
46
|
+
message payload.
|
|
47
|
+
|
|
48
|
+
### Sending Messages
|
|
49
|
+
|
|
50
|
+
An important part of using websockets is being able to manage and send messages to clients. To help with this,
|
|
51
|
+
there is a base model class in `clearskies_aws.models.WebSocketConnectionModel`. Check the documentation for
|
|
52
|
+
this class to understand how this is managed and see a "starter" websocket application.
|
|
53
|
+
|
|
54
|
+
### Context Specifics
|
|
55
|
+
|
|
56
|
+
The following parameters are made available by name to any function invoked by clearskies when using
|
|
57
|
+
this context:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
| Name | Type | Description |
|
|
61
|
+
|:---------------:|:----------------:|--------------------------------------------------|
|
|
62
|
+
| `event` | `dict[str, Any]` | The lambda `event` object |
|
|
63
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
64
|
+
| `connection_id` | `str` | The Connection ID |
|
|
65
|
+
| `route_key` | `str` | The value of the route key, as determined by AWS |
|
|
66
|
+
| `stage` | `str` | The stage of the lambda function |
|
|
67
|
+
| `request_id` | `str` | The AWS request id for the call |
|
|
68
|
+
| `api_id` | `str` | The id of the API |
|
|
69
|
+
| `domain_name` | `str` | The domain name |
|
|
70
|
+
| `event_type` | `str` | One of "MESSAGE", "CONNECT", or "DISCONNECT" |
|
|
71
|
+
| `connected_at` | `str` | The connection time |
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __call__(
|
|
77
|
+
self, event: dict[str, Any], context: dict[str, Any], url: str = "", request_method: str = ""
|
|
78
|
+
) -> dict[str, Any]: # type: ignore[override]
|
|
79
|
+
return self.execute_application(LambdaApiGatewayWebSocketInputOutput(event, context, url))
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from clearskies.authentication import Public
|
|
6
|
+
from clearskies.contexts.context import Context
|
|
7
|
+
|
|
8
|
+
from clearskies_aws.input_outputs import LambdaInvoke as LambdaInvokeInputOutput
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LambdaInvoke(Context):
|
|
12
|
+
"""
|
|
13
|
+
Execute a lambda directly.
|
|
14
|
+
|
|
15
|
+
This context is used when your clearskies application is running in a lambda that is executed
|
|
16
|
+
directly by some variation of `aws lambda invoke`. For this context, the `event` object passed
|
|
17
|
+
to the lambda handler becomes the request body in the clearskies application. Note that, unlike
|
|
18
|
+
other lambda execution strategies (ALB, Api Gateway, etc...) the event object is exactly equal
|
|
19
|
+
to the body sent in with the lambda function.
|
|
20
|
+
|
|
21
|
+
### Usage
|
|
22
|
+
|
|
23
|
+
Here's a simple example:
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
import clearskies
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def my_function(request_data):
|
|
30
|
+
return request_data
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
lambda_invoke = clearskies_aws.contexts.LambdaInvoke(
|
|
34
|
+
clearskies.endpoints.Callable(
|
|
35
|
+
my_function,
|
|
36
|
+
return_standard_response=False,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def lambda_handler(event, context):
|
|
42
|
+
return lambda_invoke(event, context)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
You can attach this to a lambda and might invoke it like so, with the following response:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
$ aws lambda invoke --function-name [function_name] --cli-binary-format raw-in-base64-out --payload '{"some":"data"}' | jq
|
|
49
|
+
{ "Payload": {"some": "data"} }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Invoking a lambda doesn't happen from an http context, so there is no URL/request method/headers/etc.
|
|
53
|
+
This typically means that clearskies applications in this context don't do routing and don't have
|
|
54
|
+
authentication configured. If you wanted to though, you could use `clearskies.endpoints.BodyParameterRouting`
|
|
55
|
+
to setup some basic routing and let the invoking client choose a route by providing a parameter in the
|
|
56
|
+
payload passed to lambda invoke.
|
|
57
|
+
|
|
58
|
+
You can pass a URL/request method into the context when you invoke it, which is often used to simplify
|
|
59
|
+
configuration: you can setup a standard clearskies applications with multiple endpoints, and then
|
|
60
|
+
use that one application in multiple lambdas, specifying which endpoint to call for each lambda.
|
|
61
|
+
This can also be helpful if you already have a clearskies application prepared for a standard http context
|
|
62
|
+
and want to execute some subset of those endpoints in a lambda invoke context. It looks something like this:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
import clearskies
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def some_function(request_data):
|
|
69
|
+
return request_data
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def some_other_function(request_data):
|
|
73
|
+
return request_data
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def something_else(request_data):
|
|
77
|
+
return request_data
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
lambda_invoke = clearskies_aws.contexts.LambdaInvoke(
|
|
81
|
+
clearskies.endpoints.EndpointGroup(
|
|
82
|
+
[
|
|
83
|
+
clearskies.endpoints.Callable(
|
|
84
|
+
some_function,
|
|
85
|
+
url="some_function",
|
|
86
|
+
),
|
|
87
|
+
clearskies.endpoints.Callable(
|
|
88
|
+
some_other_function,
|
|
89
|
+
url="some_other_function",
|
|
90
|
+
),
|
|
91
|
+
clearskies.endpoints.Callable(
|
|
92
|
+
something_else,
|
|
93
|
+
url="something_else",
|
|
94
|
+
),
|
|
95
|
+
]
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def some_function_handler(event, context):
|
|
101
|
+
return lambda_invoke(event, context, url="some_function")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def some_other_function_handler(event, context):
|
|
105
|
+
return lambda_invoke(event, context, url="some_other_function")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def something_else_handler(event, context):
|
|
109
|
+
return lambda_invoke(event, context, url="something_else")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Context Specifics
|
|
113
|
+
|
|
114
|
+
When using the lambda_invoke context, it exposes a few context specific parameters which can be injected into
|
|
115
|
+
any function called by clearskies:
|
|
116
|
+
|
|
117
|
+
| Name | Type | Description |
|
|
118
|
+
|:------------------:|:----------------:|:-------------------------------:|
|
|
119
|
+
| `event` | `dict[str, Any]` | The lambda `event` object |
|
|
120
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
121
|
+
| `invocation_type` | `str` | Always `"direct"` |
|
|
122
|
+
| `function_name` | `str` | The name of the lambda function |
|
|
123
|
+
| `function_version` | `str` | The function version |
|
|
124
|
+
| `request_id` | `str` | The AWS request id for the call |
|
|
125
|
+
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __call__(
|
|
129
|
+
self, event: dict[str, Any], context: dict[str, Any], request_method: str = "", url: str = ""
|
|
130
|
+
) -> dict[str, Any]: # type: ignore[override]
|
|
131
|
+
return self.execute_application(
|
|
132
|
+
LambdaInvokeInputOutput(
|
|
133
|
+
event,
|
|
134
|
+
context,
|
|
135
|
+
request_method=request_method,
|
|
136
|
+
url=url,
|
|
137
|
+
)
|
|
138
|
+
)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from clearskies.authentication import Public
|
|
6
|
+
from clearskies.contexts.context import Context
|
|
7
|
+
|
|
8
|
+
from clearskies_aws.input_outputs import LambdaSns as LambdaSnsInputOutput
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LambdaSns(Context):
|
|
12
|
+
"""
|
|
13
|
+
Execute a clearskies application when attached to a lambda triggered by SNS.
|
|
14
|
+
|
|
15
|
+
This one is very straight-forward: just attach your clearskies application to work with an
|
|
16
|
+
SNS-triggered lambda. `request_data` provided to the clearskies application will be the
|
|
17
|
+
message sent to the SNS. Since this is no longer an http context, the various http parameters
|
|
18
|
+
(url, request method, headers, and even responses) do not exist. Routing won't exist unless
|
|
19
|
+
you use `clearskies.endpoints.BodyParameterRouting` and include the route parameter in your
|
|
20
|
+
SNS message body.
|
|
21
|
+
|
|
22
|
+
### Usage
|
|
23
|
+
|
|
24
|
+
Here's a simple example:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
import clearskies
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def my_function(request_data):
|
|
31
|
+
print(request_data)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
lambda_invoke = clearskies_aws.contexts.LambdaInvoke(
|
|
35
|
+
clearskies.endpoints.Callable(
|
|
36
|
+
my_function,
|
|
37
|
+
return_standard_response=False,
|
|
38
|
+
)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def lambda_handler(event, context):
|
|
43
|
+
return lambda_invoke(event, context)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Note the lack of a return value. You can return a value if you want, but it will be ignored
|
|
47
|
+
because SNS has no concept of a return response.
|
|
48
|
+
|
|
49
|
+
If you have a number of Lambda/SNS handlers, you can bundle them together for ease-of-management
|
|
50
|
+
and specify the URL when you invoke them:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
import clearskies
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def some_function(request_data):
|
|
57
|
+
return request_data
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def some_other_function(request_data):
|
|
61
|
+
return request_data
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def something_else(request_data):
|
|
65
|
+
return request_data
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
lambda_invoke = clearskies_aws.contexts.LambdaSns(
|
|
69
|
+
clearskies.endpoints.EndpointGroup(
|
|
70
|
+
[
|
|
71
|
+
clearskies.endpoints.Callable(
|
|
72
|
+
some_function,
|
|
73
|
+
url="some_function",
|
|
74
|
+
),
|
|
75
|
+
clearskies.endpoints.Callable(
|
|
76
|
+
some_other_function,
|
|
77
|
+
url="some_other_function",
|
|
78
|
+
),
|
|
79
|
+
clearskies.endpoints.Callable(
|
|
80
|
+
something_else,
|
|
81
|
+
url="something_else",
|
|
82
|
+
),
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def some_function_handler(event, context):
|
|
89
|
+
return lambda_invoke(event, context, url="some_function")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def some_other_function_handler(event, context):
|
|
93
|
+
return lambda_invoke(event, context, url="some_other_function")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def something_else_handler(event, context):
|
|
97
|
+
return lambda_invoke(event, context, url="something_else")
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Context Specifics
|
|
101
|
+
|
|
102
|
+
When you use the LambdaSns context, it makes the following named parameters available
|
|
103
|
+
to any callable that is invoked by clearskies:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
| Name | Type | Description |
|
|
107
|
+
|:------------:|:----------------:|------------------------------------------------|
|
|
108
|
+
| `event` | `dict[str, Any]` | The lambda `event` object |
|
|
109
|
+
| `context` | `dict[str, Any]` | The lambda `context` object |
|
|
110
|
+
| `message_id` | `str` | The AWS message id |
|
|
111
|
+
| `topic_arn` | `str` | The ARN of the SNS topic that sent the message |
|
|
112
|
+
| `subject` | `str` | Any subject attached to the SNS message |
|
|
113
|
+
| `timestamp` | `str` | The timestamp when the message was sent |
|
|
114
|
+
```
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def __call__(self, event: dict[str, Any], context: dict[str, Any], request_method: str = "", url: str = ""): # type: ignore[override]
|
|
118
|
+
try:
|
|
119
|
+
return self.execute_application(
|
|
120
|
+
LambdaSnsInputOutput(event, context, request_method=request_method, url=url)
|
|
121
|
+
)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print("Failed message " + event["Records"][0]["Sns"]["MessageId"] + ". Error error: " + str(e))
|
|
124
|
+
raise e
|