localstack-core 4.7.1.dev139__py3-none-any.whl → 4.10.1.dev42__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.
Potentially problematic release.
This version of localstack-core might be problematic. Click here for more details.
- localstack/aws/api/acm/__init__.py +122 -122
- localstack/aws/api/apigateway/__init__.py +560 -559
- localstack/aws/api/cloudcontrol/__init__.py +63 -63
- localstack/aws/api/cloudformation/__init__.py +1041 -969
- localstack/aws/api/cloudwatch/__init__.py +408 -368
- localstack/aws/api/config/__init__.py +788 -786
- localstack/aws/api/core.py +4 -0
- localstack/aws/api/dynamodb/__init__.py +753 -759
- localstack/aws/api/dynamodbstreams/__init__.py +74 -74
- localstack/aws/api/ec2/__init__.py +9713 -8573
- localstack/aws/api/es/__init__.py +453 -453
- localstack/aws/api/events/__init__.py +552 -552
- localstack/aws/api/firehose/__init__.py +541 -543
- localstack/aws/api/iam/__init__.py +646 -572
- localstack/aws/api/kinesis/__init__.py +251 -144
- localstack/aws/api/kms/__init__.py +343 -333
- localstack/aws/api/lambda_/__init__.py +585 -571
- localstack/aws/api/logs/__init__.py +682 -666
- localstack/aws/api/opensearch/__init__.py +814 -785
- localstack/aws/api/pipes/__init__.py +336 -336
- localstack/aws/api/redshift/__init__.py +1192 -1164
- localstack/aws/api/resource_groups/__init__.py +175 -175
- localstack/aws/api/resourcegroupstaggingapi/__init__.py +67 -67
- localstack/aws/api/route53/__init__.py +256 -254
- localstack/aws/api/route53resolver/__init__.py +396 -396
- localstack/aws/api/s3/__init__.py +1358 -1345
- localstack/aws/api/s3control/__init__.py +616 -584
- localstack/aws/api/scheduler/__init__.py +118 -118
- localstack/aws/api/secretsmanager/__init__.py +193 -193
- localstack/aws/api/ses/__init__.py +227 -227
- localstack/aws/api/sns/__init__.py +115 -115
- localstack/aws/api/sqs/__init__.py +100 -100
- localstack/aws/api/ssm/__init__.py +1978 -1970
- localstack/aws/api/stepfunctions/__init__.py +323 -323
- localstack/aws/api/sts/__init__.py +90 -66
- localstack/aws/api/support/__init__.py +112 -112
- localstack/aws/api/swf/__init__.py +378 -386
- localstack/aws/api/transcribe/__init__.py +425 -425
- localstack/aws/client.py +7 -2
- localstack/aws/forwarder.py +52 -5
- localstack/aws/handlers/analytics.py +1 -1
- localstack/aws/handlers/logging.py +12 -2
- localstack/aws/handlers/metric_handler.py +41 -1
- localstack/aws/handlers/service.py +43 -10
- localstack/aws/protocol/parser.py +440 -21
- localstack/aws/protocol/serializer.py +684 -64
- localstack/aws/protocol/service_router.py +120 -20
- localstack/aws/scaffold.py +15 -17
- localstack/aws/skeleton.py +4 -2
- localstack/aws/spec-patches.json +58 -0
- localstack/aws/spec.py +33 -13
- localstack/cli/exceptions.py +1 -1
- localstack/cli/localstack.py +10 -5
- localstack/cli/lpm.py +3 -4
- localstack/cli/profiles.py +1 -2
- localstack/config.py +18 -12
- localstack/constants.py +4 -29
- localstack/dev/kubernetes/__main__.py +39 -4
- localstack/dev/run/paths.py +1 -1
- localstack/dns/plugins.py +5 -1
- localstack/dns/server.py +12 -3
- localstack/packages/api.py +9 -8
- localstack/packages/core.py +2 -2
- localstack/packages/plugins.py +0 -8
- localstack/runtime/init.py +1 -1
- localstack/services/apigateway/helpers.py +5 -9
- localstack/services/apigateway/legacy/provider.py +85 -12
- localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +3 -0
- localstack/services/apigateway/next_gen/execute_api/integrations/http.py +3 -3
- localstack/services/apigateway/next_gen/execute_api/test_invoke.py +50 -6
- localstack/services/apigateway/next_gen/provider.py +5 -0
- localstack/services/apigateway/patches.py +0 -9
- localstack/services/cloudformation/engine/entities.py +12 -1
- localstack/services/cloudformation/engine/v2/change_set_model.py +0 -3
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +14 -0
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +13 -15
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +118 -24
- localstack/services/cloudformation/engine/v2/change_set_model_transform.py +4 -1
- localstack/services/cloudformation/engine/v2/change_set_model_validator.py +5 -14
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
- localstack/services/cloudformation/engine/v2/resolving.py +6 -4
- localstack/services/cloudformation/engine/yaml_parser.py +9 -2
- localstack/services/cloudformation/provider.py +2 -2
- localstack/services/cloudformation/resource_provider.py +5 -1
- localstack/services/cloudformation/resources.py +24149 -0
- localstack/services/cloudformation/v2/entities.py +6 -3
- localstack/services/cloudformation/v2/provider.py +178 -33
- localstack/services/cloudformation/v2/types.py +8 -4
- localstack/services/cloudwatch/provider_v2.py +25 -28
- localstack/services/dynamodb/packages.py +2 -1
- localstack/services/dynamodb/provider.py +42 -0
- localstack/services/dynamodb/v2/provider.py +42 -0
- localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
- localstack/services/es/provider.py +2 -2
- localstack/services/events/event_rule_engine.py +31 -13
- localstack/services/events/models.py +4 -5
- localstack/services/events/target.py +17 -9
- localstack/services/iam/provider.py +11 -116
- localstack/services/iam/resources/policy_simulator.py +133 -0
- localstack/services/kinesis/models.py +15 -2
- localstack/services/kinesis/packages.py +1 -1
- localstack/services/kinesis/provider.py +77 -0
- localstack/services/kms/models.py +34 -4
- localstack/services/kms/provider.py +107 -21
- localstack/services/lambda_/api_utils.py +3 -1
- localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +1 -1
- localstack/services/lambda_/runtimes.py +8 -3
- localstack/services/logs/provider.py +36 -19
- localstack/services/moto.py +2 -1
- localstack/services/opensearch/cluster.py +15 -7
- localstack/services/opensearch/packages.py +26 -7
- localstack/services/opensearch/provider.py +6 -1
- localstack/services/opensearch/versions.py +56 -7
- localstack/services/s3/constants.py +5 -2
- localstack/services/s3/cors.py +4 -4
- localstack/services/s3/notifications.py +1 -1
- localstack/services/s3/presigned_url.py +27 -43
- localstack/services/s3/provider.py +68 -12
- localstack/services/s3/utils.py +42 -11
- localstack/services/ses/provider.py +16 -7
- localstack/services/sns/constants.py +7 -1
- localstack/services/sns/v2/models.py +190 -0
- localstack/services/sns/v2/provider.py +992 -2
- localstack/services/sns/v2/utils.py +138 -0
- localstack/services/sqs/developer_api.py +205 -0
- localstack/services/sqs/models.py +79 -13
- localstack/services/sqs/provider.py +8 -309
- localstack/services/sqs/query_api.py +1 -1
- localstack/services/sqs/utils.py +121 -2
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
- localstack/testing/aws/cloudformation_utils.py +1 -1
- localstack/testing/pytest/cloudformation/fixtures.py +3 -3
- localstack/testing/pytest/container.py +4 -5
- localstack/testing/pytest/fixtures.py +20 -19
- localstack/testing/pytest/in_memory_localstack.py +0 -4
- localstack/testing/pytest/marking.py +13 -4
- localstack/testing/pytest/stepfunctions/utils.py +4 -3
- localstack/testing/pytest/util.py +1 -1
- localstack/testing/pytest/validation_tracking.py +1 -2
- localstack/testing/snapshots/transformer_utility.py +7 -0
- localstack/testing/testselection/matching.py +0 -1
- localstack/utils/analytics/events.py +2 -2
- localstack/utils/analytics/metadata.py +1 -2
- localstack/utils/analytics/metrics/counter.py +6 -8
- localstack/utils/analytics/publisher.py +1 -2
- localstack/utils/analytics/service_request_aggregator.py +2 -2
- localstack/utils/archives.py +11 -11
- localstack/utils/aws/arns.py +17 -9
- localstack/utils/aws/aws_responses.py +7 -7
- localstack/utils/aws/aws_stack.py +2 -3
- localstack/utils/aws/client_types.py +0 -8
- localstack/utils/aws/message_forwarding.py +1 -2
- localstack/utils/aws/request_context.py +4 -5
- localstack/utils/batch_policy.py +3 -3
- localstack/utils/bootstrap.py +7 -7
- localstack/utils/catalog/catalog.py +139 -0
- localstack/utils/catalog/catalog_loader.py +119 -0
- localstack/utils/catalog/common.py +58 -0
- localstack/utils/catalog/plugins.py +28 -0
- localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
- localstack/utils/collections.py +7 -8
- localstack/utils/config_listener.py +1 -1
- localstack/utils/container_networking.py +2 -3
- localstack/utils/container_utils/container_client.py +115 -131
- localstack/utils/container_utils/docker_cmd_client.py +42 -42
- localstack/utils/container_utils/docker_sdk_client.py +63 -62
- localstack/utils/crypto.py +109 -0
- localstack/utils/diagnose.py +2 -3
- localstack/utils/docker_utils.py +3 -4
- localstack/utils/files.py +31 -7
- localstack/utils/functions.py +3 -2
- localstack/utils/http.py +4 -5
- localstack/utils/json.py +19 -5
- localstack/utils/kinesis/kinesis_connector.py +2 -1
- localstack/utils/net.py +6 -6
- localstack/utils/no_exit_argument_parser.py +2 -2
- localstack/utils/numbers.py +9 -2
- localstack/utils/objects.py +6 -5
- localstack/utils/patch.py +2 -1
- localstack/utils/run.py +10 -9
- localstack/utils/scheduler.py +11 -11
- localstack/utils/server/tcp_proxy.py +2 -2
- localstack/utils/serving.py +2 -3
- localstack/utils/strings.py +10 -11
- localstack/utils/sync.py +126 -1
- localstack/utils/tagging.py +1 -4
- localstack/utils/testutil.py +5 -4
- localstack/utils/threads.py +2 -2
- localstack/utils/time.py +11 -3
- localstack/utils/urls.py +1 -3
- localstack/version.py +2 -2
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/METADATA +19 -13
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/RECORD +203 -199
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/entry_points.txt +4 -2
- localstack_core-4.10.1.dev42.dist-info/plux.json +1 -0
- localstack/packages/terraform.py +0 -46
- localstack/services/cloudformation/deploy.html +0 -144
- localstack/services/cloudformation/deploy_ui.py +0 -47
- localstack/services/cloudformation/plugins.py +0 -12
- localstack_core-4.7.1.dev139.dist-info/plux.json +0 -1
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev42.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/WHEEL +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev42.dist-info}/top_level.txt +0 -0
localstack/aws/client.py
CHANGED
|
@@ -21,6 +21,7 @@ from localstack.utils.strings import to_str
|
|
|
21
21
|
from .api import CommonServiceException, RequestContext, ServiceException, ServiceResponse
|
|
22
22
|
from .connect import get_service_endpoint
|
|
23
23
|
from .gateway import Gateway
|
|
24
|
+
from .spec import ProtocolName
|
|
24
25
|
|
|
25
26
|
LOG = logging.getLogger(__name__)
|
|
26
27
|
|
|
@@ -284,13 +285,17 @@ def _patch_botocore_endpoint_in_memory():
|
|
|
284
285
|
|
|
285
286
|
|
|
286
287
|
def parse_response(
|
|
287
|
-
operation: OperationModel,
|
|
288
|
+
operation: OperationModel,
|
|
289
|
+
protocol: ProtocolName,
|
|
290
|
+
response: Response,
|
|
291
|
+
include_response_metadata: bool = True,
|
|
288
292
|
) -> ServiceResponse:
|
|
289
293
|
"""
|
|
290
294
|
Parses an HTTP Response object into an AWS response object using botocore. It does this by adapting the
|
|
291
295
|
procedure of ``botocore.endpoint.convert_to_response_dict`` to work with Werkzeug's server-side response object.
|
|
292
296
|
|
|
293
297
|
:param operation: the operation of the original request
|
|
298
|
+
:param protocol: the protocol of the original request
|
|
294
299
|
:param response: the HTTP response object containing the response of the operation
|
|
295
300
|
:param include_response_metadata: True if the ResponseMetadata (typical for boto response dicts) should be included
|
|
296
301
|
:return: a parsed dictionary as it is returned by botocore
|
|
@@ -322,7 +327,7 @@ def parse_response(
|
|
|
322
327
|
timestamp_parser=_cbor_timestamp_parser, blob_parser=_cbor_blob_parser
|
|
323
328
|
)
|
|
324
329
|
|
|
325
|
-
parser = factory.create_parser(
|
|
330
|
+
parser = factory.create_parser(protocol)
|
|
326
331
|
parsed_response = parser.parse(response_dict, operation.output_shape)
|
|
327
332
|
|
|
328
333
|
if response.status_code >= 301:
|
localstack/aws/forwarder.py
CHANGED
|
@@ -8,6 +8,8 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
from botocore.awsrequest import AWSPreparedRequest, prepare_request_dict
|
|
10
10
|
from botocore.config import Config as BotoConfig
|
|
11
|
+
from botocore.model import OperationModel
|
|
12
|
+
from botocore.serialize import create_serializer
|
|
11
13
|
from werkzeug.datastructures import Headers
|
|
12
14
|
|
|
13
15
|
from localstack.aws.api.core import (
|
|
@@ -19,7 +21,7 @@ from localstack.aws.api.core import (
|
|
|
19
21
|
from localstack.aws.client import create_http_request, parse_response, raise_service_exception
|
|
20
22
|
from localstack.aws.connect import connect_to
|
|
21
23
|
from localstack.aws.skeleton import DispatchTable, create_dispatch_table
|
|
22
|
-
from localstack.aws.spec import load_service
|
|
24
|
+
from localstack.aws.spec import ProtocolName, load_service
|
|
23
25
|
from localstack.constants import AWS_REGION_US_EAST_1
|
|
24
26
|
from localstack.http import Response
|
|
25
27
|
from localstack.http.proxy import Proxy
|
|
@@ -79,7 +81,7 @@ class AwsRequestProxy:
|
|
|
79
81
|
if not self.parse_response:
|
|
80
82
|
return http_response
|
|
81
83
|
parsed_response = parse_response(
|
|
82
|
-
context.operation, http_response, self.include_response_metadata
|
|
84
|
+
context.operation, context.protocol, http_response, self.include_response_metadata
|
|
83
85
|
)
|
|
84
86
|
raise_service_exception(http_response, parsed_response)
|
|
85
87
|
return parsed_response
|
|
@@ -90,6 +92,7 @@ class AwsRequestProxy:
|
|
|
90
92
|
action=original.operation.name,
|
|
91
93
|
parameters=service_request,
|
|
92
94
|
region=original.region,
|
|
95
|
+
protocol=original.protocol,
|
|
93
96
|
)
|
|
94
97
|
# update the newly created context with non-payload specific request headers (the payload can differ from
|
|
95
98
|
# the original request, f.e. it could be JSON encoded now while the initial request was CBOR encoded)
|
|
@@ -184,7 +187,9 @@ def dispatch_to_backend(
|
|
|
184
187
|
:raises ServiceException: if the dispatcher returned an error response
|
|
185
188
|
"""
|
|
186
189
|
http_response = http_request_dispatcher(context)
|
|
187
|
-
parsed_response = parse_response(
|
|
190
|
+
parsed_response = parse_response(
|
|
191
|
+
context.operation, context.protocol, http_response, include_response_metadata
|
|
192
|
+
)
|
|
188
193
|
raise_service_exception(http_response, parsed_response)
|
|
189
194
|
return parsed_response
|
|
190
195
|
|
|
@@ -196,6 +201,7 @@ _non_validating_boto_config = BotoConfig(parameter_validation=False)
|
|
|
196
201
|
def create_aws_request_context(
|
|
197
202
|
service_name: str,
|
|
198
203
|
action: str,
|
|
204
|
+
protocol: ProtocolName = None,
|
|
199
205
|
parameters: Mapping[str, Any] = None,
|
|
200
206
|
region: str = None,
|
|
201
207
|
endpoint_url: str | None = None,
|
|
@@ -210,6 +216,7 @@ def create_aws_request_context(
|
|
|
210
216
|
|
|
211
217
|
:param service_name: the AWS service
|
|
212
218
|
:param action: the action to invoke
|
|
219
|
+
:param protocol: the protocol to use
|
|
213
220
|
:param parameters: the invocation parameters
|
|
214
221
|
:param region: the region name (default is us-east-1)
|
|
215
222
|
:param endpoint_url: the endpoint to call (defaults to localstack)
|
|
@@ -222,6 +229,8 @@ def create_aws_request_context(
|
|
|
222
229
|
|
|
223
230
|
service = load_service(service_name)
|
|
224
231
|
operation = service.operation_model(action)
|
|
232
|
+
# TODO: remove this once every usage upstream has been removed
|
|
233
|
+
protocol = protocol or service.resolved_protocol
|
|
225
234
|
|
|
226
235
|
# we re-use botocore internals here to serialize the HTTP request,
|
|
227
236
|
# but deactivate validation (validation errors should be handled by the backend)
|
|
@@ -243,8 +252,14 @@ def create_aws_request_context(
|
|
|
243
252
|
endpoint_url = "http://localhost.localstack.cloud"
|
|
244
253
|
# pre-process the request args (some params are modified using botocore event handlers)
|
|
245
254
|
parameters = client._emit_api_params(parameters, operation, request_context)
|
|
246
|
-
|
|
247
|
-
|
|
255
|
+
|
|
256
|
+
request_dict = _convert_to_request_dict_with_protocol(
|
|
257
|
+
client=client,
|
|
258
|
+
protocol=protocol,
|
|
259
|
+
api_params=parameters,
|
|
260
|
+
operation_model=operation,
|
|
261
|
+
endpoint_url=endpoint_url,
|
|
262
|
+
context=request_context,
|
|
248
263
|
)
|
|
249
264
|
|
|
250
265
|
if auth_path := request_dict.get("auth_path"):
|
|
@@ -266,7 +281,39 @@ def create_aws_request_context(
|
|
|
266
281
|
context = RequestContext(request=create_http_request(aws_request))
|
|
267
282
|
context.service = service
|
|
268
283
|
context.operation = operation
|
|
284
|
+
context.protocol = protocol
|
|
269
285
|
context.region = region
|
|
270
286
|
context.service_request = parameters
|
|
271
287
|
|
|
272
288
|
return context
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _convert_to_request_dict_with_protocol(
|
|
292
|
+
client,
|
|
293
|
+
protocol: ProtocolName,
|
|
294
|
+
api_params: dict,
|
|
295
|
+
operation_model: OperationModel,
|
|
296
|
+
endpoint_url: str,
|
|
297
|
+
context: dict,
|
|
298
|
+
set_user_agent_header: bool = True,
|
|
299
|
+
) -> dict:
|
|
300
|
+
"""
|
|
301
|
+
This function is taken from botocore Client._convert_to_request_dict, but we are overriding the serializer
|
|
302
|
+
Botocore does not expose a way to create a client with a specific protocol, but we need this functionality
|
|
303
|
+
to support multi-protocols.
|
|
304
|
+
"""
|
|
305
|
+
serializer = create_serializer(protocol, include_validation=False)
|
|
306
|
+
request_dict = serializer.serialize_to_request(api_params, operation_model)
|
|
307
|
+
if not client._client_config.inject_host_prefix:
|
|
308
|
+
request_dict.pop("host_prefix", None)
|
|
309
|
+
if set_user_agent_header:
|
|
310
|
+
user_agent = client._user_agent_creator.to_string()
|
|
311
|
+
else:
|
|
312
|
+
user_agent = None
|
|
313
|
+
prepare_request_dict(
|
|
314
|
+
request_dict,
|
|
315
|
+
endpoint_url=endpoint_url,
|
|
316
|
+
user_agent=user_agent,
|
|
317
|
+
context=context,
|
|
318
|
+
)
|
|
319
|
+
return request_dict
|
|
@@ -60,7 +60,7 @@ class ServiceRequestCounter:
|
|
|
60
60
|
if context.service_exception:
|
|
61
61
|
return context.service_exception.code
|
|
62
62
|
|
|
63
|
-
response = parse_response(context.operation, response)
|
|
63
|
+
response = parse_response(context.operation, context.protocol, response)
|
|
64
64
|
return response["Error"]["Code"]
|
|
65
65
|
except Exception:
|
|
66
66
|
if config.DEBUG_ANALYTICS:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Handlers for logging."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import types
|
|
4
5
|
from functools import cached_property
|
|
5
6
|
|
|
6
7
|
from localstack.aws.api import RequestContext, ServiceException
|
|
@@ -21,6 +22,14 @@ class ExceptionLogger(ExceptionHandler):
|
|
|
21
22
|
def __init__(self, logger=None):
|
|
22
23
|
self.logger = logger or LOG
|
|
23
24
|
|
|
25
|
+
try:
|
|
26
|
+
import moto.core.exceptions
|
|
27
|
+
|
|
28
|
+
self._moto_service_exception = moto.core.exceptions.ServiceException
|
|
29
|
+
except (ModuleNotFoundError, AttributeError):
|
|
30
|
+
# Moto may not be available in stripped-down versions of LocalStack, like LocalStack S3 image.
|
|
31
|
+
self._moto_service_exception = types.EllipsisType
|
|
32
|
+
|
|
24
33
|
def __call__(
|
|
25
34
|
self,
|
|
26
35
|
chain: HandlerChain,
|
|
@@ -28,9 +37,10 @@ class ExceptionLogger(ExceptionHandler):
|
|
|
28
37
|
context: RequestContext,
|
|
29
38
|
response: Response,
|
|
30
39
|
):
|
|
31
|
-
if isinstance(exception, ServiceException):
|
|
40
|
+
if isinstance(exception, (ServiceException, self._moto_service_exception)):
|
|
32
41
|
# We do not want to log an error/stacktrace if the handler is working as expected, but chooses to throw
|
|
33
|
-
# a service exception
|
|
42
|
+
# a service exception. It may also throw a Moto ServiceException, which should not be logged either
|
|
43
|
+
# because ServiceExceptionHandler understands it.
|
|
34
44
|
return
|
|
35
45
|
if self.logger.isEnabledFor(level=logging.DEBUG):
|
|
36
46
|
self.logger.exception("exception during call chain", exc_info=exception)
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
+
import csv
|
|
1
2
|
import logging
|
|
3
|
+
import os
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
2
6
|
|
|
3
7
|
from localstack import config
|
|
4
8
|
from localstack.aws.api import RequestContext
|
|
5
9
|
from localstack.aws.chain import HandlerChain
|
|
10
|
+
from localstack.constants import ENV_INTERNAL_TEST_STORE_METRICS_PATH
|
|
6
11
|
from localstack.http import Response
|
|
12
|
+
from localstack.utils.strings import short_uid
|
|
7
13
|
|
|
8
14
|
LOG = logging.getLogger(__name__)
|
|
9
15
|
|
|
@@ -137,6 +143,32 @@ class MetricHandler:
|
|
|
137
143
|
|
|
138
144
|
def __init__(self) -> None:
|
|
139
145
|
self.metrics_handler_items = {}
|
|
146
|
+
self.local_filename = None
|
|
147
|
+
|
|
148
|
+
if self.should_store_metric_locally():
|
|
149
|
+
self.local_filename = self.create_local_file()
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def should_store_metric_locally() -> bool:
|
|
153
|
+
return config.is_collect_metrics_mode() and config.store_test_metrics_in_local_filesystem()
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def create_local_file():
|
|
157
|
+
folder = Path(
|
|
158
|
+
os.environ.get(ENV_INTERNAL_TEST_STORE_METRICS_PATH, "/tmp/localstack-metrics")
|
|
159
|
+
)
|
|
160
|
+
if not folder.exists():
|
|
161
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
LOG.debug("Metric reports will be stored in %s", folder)
|
|
163
|
+
filename = (
|
|
164
|
+
folder
|
|
165
|
+
/ f"metric-report-raw-data-{datetime.utcnow().strftime('%Y-%m-%d__%H_%M_%S')}-{short_uid()}.csv"
|
|
166
|
+
)
|
|
167
|
+
with open(filename, "w") as fd:
|
|
168
|
+
LOG.debug("Creating new metric data file %s", filename)
|
|
169
|
+
writer = csv.writer(fd)
|
|
170
|
+
writer.writerow(Metric.RAW_DATA_HEADER)
|
|
171
|
+
return filename
|
|
140
172
|
|
|
141
173
|
def create_metric_handler_item(
|
|
142
174
|
self, chain: HandlerChain, context: RequestContext, response: Response
|
|
@@ -194,7 +226,15 @@ class MetricHandler:
|
|
|
194
226
|
)
|
|
195
227
|
# refrain from adding duplicates
|
|
196
228
|
if metric not in MetricHandler.metric_data:
|
|
197
|
-
|
|
229
|
+
self.append_metric(metric)
|
|
198
230
|
|
|
199
231
|
# cleanup
|
|
200
232
|
del self.metrics_handler_items[context]
|
|
233
|
+
|
|
234
|
+
def append_metric(self, metric: Metric):
|
|
235
|
+
if self.should_store_metric_locally():
|
|
236
|
+
with open(self.local_filename, "a") as fd:
|
|
237
|
+
writer = csv.writer(fd)
|
|
238
|
+
writer.writerow(metric)
|
|
239
|
+
else:
|
|
240
|
+
MetricHandler.metric_data.append(metric)
|
|
@@ -2,22 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import traceback
|
|
5
|
+
import types
|
|
5
6
|
from collections import defaultdict
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
8
9
|
from botocore.model import OperationModel, ServiceModel
|
|
10
|
+
from plux.core.plugin import PluginDisabled
|
|
9
11
|
|
|
10
12
|
from localstack import config
|
|
11
13
|
from localstack.http import Response
|
|
12
|
-
from localstack.utils.coverage_docs import get_coverage_link_for_service
|
|
13
14
|
|
|
15
|
+
from ...utils.coverage_docs import get_coverage_link_for_service
|
|
14
16
|
from ..api import CommonServiceException, RequestContext, ServiceException
|
|
15
17
|
from ..api.core import ServiceOperation
|
|
16
18
|
from ..chain import CompositeResponseHandler, ExceptionHandler, Handler, HandlerChain
|
|
17
19
|
from ..client import parse_response, parse_service_exception
|
|
18
20
|
from ..protocol.parser import RequestParser, create_parser
|
|
19
21
|
from ..protocol.serializer import create_serializer
|
|
20
|
-
from ..protocol.service_router import determine_aws_service_model
|
|
22
|
+
from ..protocol.service_router import determine_aws_protocol, determine_aws_service_model
|
|
21
23
|
from ..skeleton import Skeleton, create_skeleton
|
|
22
24
|
|
|
23
25
|
LOG = logging.getLogger(__name__)
|
|
@@ -33,6 +35,8 @@ class ServiceNameParser(Handler):
|
|
|
33
35
|
# example). If it is already set, we can skip the parsing of the request. It is very important for S3, because
|
|
34
36
|
# parsing the request will consume the data stream and prevent streaming.
|
|
35
37
|
if context.service:
|
|
38
|
+
if not context.protocol:
|
|
39
|
+
context.protocol = determine_aws_protocol(context.request, context.service)
|
|
36
40
|
return
|
|
37
41
|
|
|
38
42
|
service_model = determine_aws_service_model(context.request)
|
|
@@ -41,6 +45,7 @@ class ServiceNameParser(Handler):
|
|
|
41
45
|
return
|
|
42
46
|
|
|
43
47
|
context.service = service_model
|
|
48
|
+
context.protocol = determine_aws_protocol(context.request, service_model)
|
|
44
49
|
|
|
45
50
|
|
|
46
51
|
class ServiceRequestParser(Handler):
|
|
@@ -63,7 +68,7 @@ class ServiceRequestParser(Handler):
|
|
|
63
68
|
return self.parse_and_enrich(context)
|
|
64
69
|
|
|
65
70
|
def parse_and_enrich(self, context: RequestContext):
|
|
66
|
-
parser = create_parser(context.service)
|
|
71
|
+
parser = create_parser(context.service, context.protocol)
|
|
67
72
|
operation, instance = parser.parse(context.request)
|
|
68
73
|
|
|
69
74
|
# enrich context
|
|
@@ -137,7 +142,7 @@ class ServiceRequestRouter(Handler):
|
|
|
137
142
|
operation_name = operation.name
|
|
138
143
|
message = f"no handler for operation '{operation_name}' on service '{service_name}'"
|
|
139
144
|
error = CommonServiceException("InternalFailure", message, status_code=501)
|
|
140
|
-
serializer = create_serializer(context.service)
|
|
145
|
+
serializer = create_serializer(context.service, context.protocol)
|
|
141
146
|
return serializer.serialize_error_to_response(
|
|
142
147
|
error, operation, context.request.headers, context.request_id
|
|
143
148
|
)
|
|
@@ -153,6 +158,15 @@ class ServiceExceptionSerializer(ExceptionHandler):
|
|
|
153
158
|
def __init__(self):
|
|
154
159
|
self.handle_internal_failures = True
|
|
155
160
|
|
|
161
|
+
try:
|
|
162
|
+
import moto.core.exceptions
|
|
163
|
+
|
|
164
|
+
self._moto_service_exception = moto.core.exceptions.ServiceException
|
|
165
|
+
except (ModuleNotFoundError, AttributeError) as exc:
|
|
166
|
+
# Moto may not be available in stripped-down versions of LocalStack, like LocalStack S3 image.
|
|
167
|
+
LOG.debug("Unable to set up Moto ServiceException translation: %s", exc)
|
|
168
|
+
self._moto_service_exception = types.EllipsisType
|
|
169
|
+
|
|
156
170
|
def __call__(
|
|
157
171
|
self,
|
|
158
172
|
chain: HandlerChain,
|
|
@@ -176,9 +190,16 @@ class ServiceExceptionSerializer(ExceptionHandler):
|
|
|
176
190
|
action_name = operation.name
|
|
177
191
|
exception_message: str | None = exception.args[0] if exception.args else None
|
|
178
192
|
message = exception_message or get_coverage_link_for_service(service_name, action_name)
|
|
179
|
-
LOG.info(message)
|
|
180
193
|
error = CommonServiceException("InternalFailure", message, status_code=501)
|
|
181
|
-
|
|
194
|
+
LOG.info(message)
|
|
195
|
+
|
|
196
|
+
elif isinstance(exception, self._moto_service_exception):
|
|
197
|
+
# Translate Moto ServiceException to native ServiceException if Moto is available.
|
|
198
|
+
# This allows handler chain to gracefully handles Moto errors when provider handlers invoke Moto methods directly.
|
|
199
|
+
error = CommonServiceException(
|
|
200
|
+
code=exception.code,
|
|
201
|
+
message=exception.message,
|
|
202
|
+
)
|
|
182
203
|
|
|
183
204
|
elif not isinstance(exception, ServiceException):
|
|
184
205
|
if not self.handle_internal_failures:
|
|
@@ -202,14 +223,24 @@ class ServiceExceptionSerializer(ExceptionHandler):
|
|
|
202
223
|
operation = context.service.operation_model(context.service.operation_names[0])
|
|
203
224
|
msg = f"exception while calling {service_name} with unknown operation: {message}"
|
|
204
225
|
|
|
205
|
-
|
|
226
|
+
# Check for license restricted plugin message and set status code to 501
|
|
227
|
+
if (
|
|
228
|
+
isinstance(exception, PluginDisabled)
|
|
229
|
+
and "not part of the active license agreement"
|
|
230
|
+
in str(getattr(exception, "reason", "")).lower()
|
|
231
|
+
):
|
|
232
|
+
status_code = 501
|
|
233
|
+
msg = f"exception while calling {service_name}.{operation.name}: {str(getattr(exception, 'reason', ''))}"
|
|
234
|
+
else:
|
|
235
|
+
status_code = 501 if config.FAIL_FAST else 500
|
|
206
236
|
|
|
207
237
|
error = CommonServiceException(
|
|
208
238
|
"InternalError", msg, status_code=status_code
|
|
209
239
|
).with_traceback(exception.__traceback__)
|
|
210
|
-
context.service_exception = error
|
|
211
240
|
|
|
212
|
-
|
|
241
|
+
context.service_exception = error
|
|
242
|
+
|
|
243
|
+
serializer = create_serializer(context.service, context.protocol)
|
|
213
244
|
return serializer.serialize_error_to_response(
|
|
214
245
|
error, operation, context.request.headers, context.request_id
|
|
215
246
|
)
|
|
@@ -252,7 +283,9 @@ class ServiceResponseParser(Handler):
|
|
|
252
283
|
return
|
|
253
284
|
|
|
254
285
|
# in this case we need to parse the raw response
|
|
255
|
-
parsed = parse_response(
|
|
286
|
+
parsed = parse_response(
|
|
287
|
+
context.operation, context.protocol, response, include_response_metadata=False
|
|
288
|
+
)
|
|
256
289
|
if service_exception := parse_service_exception(response, parsed):
|
|
257
290
|
context.service_exception = service_exception
|
|
258
291
|
else:
|