localstack-core 4.7.1.dev49__py3-none-any.whl → 4.10.1.dev12__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.
- localstack/aws/api/cloudformation/__init__.py +18 -4
- localstack/aws/api/cloudwatch/__init__.py +41 -1
- localstack/aws/api/config/__init__.py +4 -0
- localstack/aws/api/core.py +6 -2
- localstack/aws/api/dynamodb/__init__.py +30 -0
- localstack/aws/api/ec2/__init__.py +1522 -65
- localstack/aws/api/iam/__init__.py +7 -0
- localstack/aws/api/kinesis/__init__.py +19 -0
- localstack/aws/api/kms/__init__.py +6 -0
- localstack/aws/api/lambda_/__init__.py +13 -0
- localstack/aws/api/logs/__init__.py +15 -0
- localstack/aws/api/redshift/__init__.py +9 -3
- localstack/aws/api/route53/__init__.py +5 -0
- localstack/aws/api/s3/__init__.py +12 -0
- localstack/aws/api/s3control/__init__.py +54 -0
- localstack/aws/api/ssm/__init__.py +2 -0
- localstack/aws/api/transcribe/__init__.py +17 -0
- localstack/aws/client.py +7 -2
- localstack/aws/forwarder.py +52 -5
- localstack/aws/handlers/analytics.py +1 -1
- localstack/aws/handlers/internal_requests.py +6 -1
- localstack/aws/handlers/logging.py +12 -2
- localstack/aws/handlers/metric_handler.py +41 -1
- localstack/aws/handlers/service.py +40 -20
- localstack/aws/mocking.py +2 -2
- localstack/aws/patches.py +2 -2
- localstack/aws/protocol/parser.py +459 -32
- localstack/aws/protocol/serializer.py +689 -69
- localstack/aws/protocol/service_router.py +120 -20
- localstack/aws/protocol/validate.py +1 -1
- localstack/aws/scaffold.py +1 -1
- localstack/aws/skeleton.py +4 -2
- localstack/aws/spec-patches.json +58 -0
- localstack/aws/spec.py +37 -16
- localstack/cli/exceptions.py +1 -1
- localstack/cli/localstack.py +6 -6
- localstack/cli/lpm.py +3 -4
- localstack/cli/plugins.py +1 -1
- localstack/cli/profiles.py +1 -2
- localstack/config.py +25 -18
- localstack/constants.py +4 -29
- localstack/dev/kubernetes/__main__.py +130 -7
- localstack/dev/run/configurators.py +1 -4
- localstack/dev/run/paths.py +1 -1
- localstack/dns/plugins.py +5 -1
- localstack/dns/server.py +13 -4
- localstack/logging/format.py +3 -3
- localstack/packages/api.py +9 -8
- localstack/packages/core.py +2 -2
- localstack/packages/plugins.py +0 -8
- localstack/runtime/analytics.py +3 -0
- localstack/runtime/hooks.py +1 -1
- localstack/runtime/init.py +2 -2
- localstack/runtime/main.py +5 -5
- localstack/runtime/patches.py +2 -2
- localstack/services/apigateway/helpers.py +1 -4
- localstack/services/apigateway/legacy/helpers.py +7 -8
- localstack/services/apigateway/legacy/integration.py +4 -3
- localstack/services/apigateway/legacy/invocations.py +6 -5
- localstack/services/apigateway/legacy/provider.py +148 -68
- localstack/services/apigateway/legacy/templates.py +1 -1
- localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py +7 -2
- localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py +1 -2
- 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/template_mapping.py +2 -2
- localstack/services/apigateway/next_gen/execute_api/test_invoke.py +114 -9
- localstack/services/apigateway/next_gen/provider.py +5 -0
- localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +1 -1
- localstack/services/cloudformation/api_utils.py +4 -8
- localstack/services/cloudformation/cfn_utils.py +1 -1
- localstack/services/cloudformation/engine/entities.py +14 -4
- localstack/services/cloudformation/engine/template_deployer.py +6 -4
- localstack/services/cloudformation/engine/transformers.py +6 -4
- localstack/services/cloudformation/engine/v2/change_set_model.py +201 -13
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +52 -3
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +117 -76
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +205 -52
- localstack/services/cloudformation/engine/v2/change_set_model_transform.py +350 -116
- localstack/services/cloudformation/engine/v2/change_set_model_validator.py +56 -14
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
- localstack/services/cloudformation/engine/v2/resolving.py +7 -5
- localstack/services/cloudformation/engine/yaml_parser.py +9 -2
- localstack/services/cloudformation/provider.py +7 -5
- localstack/services/cloudformation/resource_provider.py +7 -1
- localstack/services/cloudformation/resources.py +24149 -0
- localstack/services/cloudformation/service_models.py +2 -2
- localstack/services/cloudformation/v2/entities.py +19 -9
- localstack/services/cloudformation/v2/provider.py +336 -106
- localstack/services/cloudformation/v2/types.py +13 -7
- localstack/services/cloudformation/v2/utils.py +4 -1
- localstack/services/cloudwatch/alarm_scheduler.py +4 -1
- localstack/services/cloudwatch/provider.py +18 -13
- 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/server.py +2 -2
- localstack/services/dynamodb/v2/provider.py +42 -0
- localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
- localstack/services/edge.py +1 -1
- 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/provider.py +17 -14
- localstack/services/events/target.py +17 -9
- localstack/services/events/v1/provider.py +5 -5
- localstack/services/firehose/provider.py +14 -4
- 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/provider.py +86 -3
- localstack/services/kms/provider.py +14 -5
- localstack/services/lambda_/api_utils.py +6 -3
- localstack/services/lambda_/invocation/docker_runtime_executor.py +1 -1
- localstack/services/lambda_/invocation/event_manager.py +1 -1
- localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
- localstack/services/lambda_/invocation/lambda_models.py +10 -7
- localstack/services/lambda_/invocation/lambda_service.py +5 -1
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +4 -3
- localstack/services/lambda_/provider_utils.py +1 -1
- 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 +8 -2
- localstack/services/opensearch/versions.py +56 -7
- localstack/services/plugins.py +11 -7
- localstack/services/providers.py +10 -2
- localstack/services/redshift/provider.py +0 -21
- localstack/services/s3/constants.py +5 -2
- localstack/services/s3/cors.py +4 -4
- localstack/services/s3/models.py +1 -1
- localstack/services/s3/notifications.py +55 -39
- localstack/services/s3/presigned_url.py +35 -54
- localstack/services/s3/provider.py +73 -15
- localstack/services/s3/utils.py +42 -22
- localstack/services/s3/validation.py +46 -32
- localstack/services/s3/website_hosting.py +4 -2
- localstack/services/ses/provider.py +18 -8
- localstack/services/sns/constants.py +7 -1
- localstack/services/sns/executor.py +9 -2
- localstack/services/sns/provider.py +8 -5
- localstack/services/sns/publisher.py +31 -16
- localstack/services/sns/v2/models.py +167 -0
- localstack/services/sns/v2/provider.py +867 -0
- localstack/services/sns/v2/utils.py +130 -0
- localstack/services/sqs/constants.py +1 -1
- localstack/services/sqs/developer_api.py +205 -0
- localstack/services/sqs/models.py +48 -5
- localstack/services/sqs/provider.py +38 -311
- localstack/services/sqs/query_api.py +6 -2
- localstack/services/sqs/utils.py +121 -2
- localstack/services/ssm/provider.py +1 -1
- localstack/services/stepfunctions/asl/component/intrinsic/member.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py +5 -11
- localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py +1 -1
- localstack/services/stepfunctions/asl/eval/environment.py +1 -1
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
- localstack/services/stepfunctions/backend/execution.py +2 -1
- localstack/services/stores.py +1 -1
- localstack/services/transcribe/provider.py +6 -1
- localstack/state/codecs.py +61 -0
- localstack/state/core.py +11 -5
- localstack/state/pickle.py +10 -49
- localstack/testing/aws/cloudformation_utils.py +1 -1
- localstack/testing/pytest/cloudformation/fixtures.py +3 -3
- localstack/testing/pytest/cloudformation/transformers.py +0 -0
- localstack/testing/pytest/container.py +4 -5
- localstack/testing/pytest/fixtures.py +33 -31
- localstack/testing/pytest/in_memory_localstack.py +0 -4
- localstack/testing/pytest/marking.py +38 -11
- 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 +6 -1
- localstack/utils/analytics/events.py +2 -2
- localstack/utils/analytics/metadata.py +6 -4
- localstack/utils/analytics/metrics/counter.py +8 -15
- localstack/utils/analytics/publisher.py +1 -2
- localstack/utils/analytics/service_providers.py +19 -0
- localstack/utils/analytics/service_request_aggregator.py +2 -2
- localstack/utils/archives.py +11 -11
- localstack/utils/asyncio.py +2 -2
- localstack/utils/aws/arns.py +24 -29
- localstack/utils/aws/aws_responses.py +8 -8
- localstack/utils/aws/aws_stack.py +2 -3
- localstack/utils/aws/dead_letter_queue.py +1 -5
- localstack/utils/aws/message_forwarding.py +1 -2
- localstack/utils/aws/request_context.py +4 -5
- localstack/utils/aws/resources.py +1 -1
- localstack/utils/aws/templating.py +1 -1
- localstack/utils/batch_policy.py +3 -3
- localstack/utils/bootstrap.py +21 -13
- 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 +135 -136
- localstack/utils/container_utils/docker_cmd_client.py +85 -69
- localstack/utils/container_utils/docker_sdk_client.py +69 -66
- localstack/utils/crypto.py +10 -10
- localstack/utils/diagnose.py +3 -4
- localstack/utils/docker_utils.py +9 -5
- localstack/utils/files.py +33 -13
- localstack/utils/functions.py +4 -3
- localstack/utils/http.py +11 -11
- localstack/utils/json.py +20 -6
- localstack/utils/kinesis/kinesis_connector.py +2 -1
- localstack/utils/net.py +15 -9
- localstack/utils/no_exit_argument_parser.py +2 -2
- localstack/utils/numbers.py +9 -2
- localstack/utils/objects.py +7 -6
- localstack/utils/patch.py +10 -3
- localstack/utils/run.py +12 -11
- localstack/utils/scheduler.py +11 -11
- localstack/utils/server/tcp_proxy.py +2 -2
- localstack/utils/serving.py +3 -4
- localstack/utils/strings.py +15 -16
- localstack/utils/sync.py +126 -1
- localstack/utils/tagging.py +8 -6
- localstack/utils/testutil.py +8 -8
- localstack/utils/threads.py +2 -2
- localstack/utils/time.py +12 -4
- localstack/utils/urls.py +1 -3
- localstack/utils/xray/traceid.py +1 -1
- localstack/version.py +16 -3
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/METADATA +18 -14
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/RECORD +248 -239
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/entry_points.txt +8 -4
- localstack_core-4.10.1.dev12.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.dev49.dist-info/plux.json +0 -1
- {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack +0 -0
- {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/WHEEL +0 -0
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import datetime
|
|
2
|
+
import logging
|
|
2
3
|
from urllib.parse import parse_qs
|
|
3
4
|
|
|
4
5
|
from rolo import Request
|
|
@@ -22,6 +23,9 @@ from .variables import (
|
|
|
22
23
|
ContextVarsResponseOverride,
|
|
23
24
|
)
|
|
24
25
|
|
|
26
|
+
LOG = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
|
|
25
29
|
# TODO: we probably need to write and populate those logs as part of the handler chain itself
|
|
26
30
|
# and store it in the InvocationContext. That way, we could also retrieve in when calling TestInvoke
|
|
27
31
|
|
|
@@ -45,6 +49,30 @@ TEST_INVOKE_TEMPLATE = """Execution log for request {request_id}
|
|
|
45
49
|
{formatted_date} : Method completed with status: {method_response_status}
|
|
46
50
|
"""
|
|
47
51
|
|
|
52
|
+
TEST_INVOKE_TEMPLATE_MOCK = """Execution log for request {request_id}
|
|
53
|
+
{formatted_date} : Starting execution for request: {request_id}
|
|
54
|
+
{formatted_date} : HTTP Method: {request_method}, Resource Path: {resource_path}
|
|
55
|
+
{formatted_date} : Method request path: {method_request_path_parameters}
|
|
56
|
+
{formatted_date} : Method request query string: {method_request_query_string}
|
|
57
|
+
{formatted_date} : Method request headers: {method_request_headers}
|
|
58
|
+
{formatted_date} : Method request body before transformations: {method_request_body}
|
|
59
|
+
{formatted_date} : Method response body after transformations: {method_response_body}
|
|
60
|
+
{formatted_date} : Method response headers: {method_response_headers}
|
|
61
|
+
{formatted_date} : Successfully completed execution
|
|
62
|
+
{formatted_date} : Method completed with status: {method_response_status}
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
TEST_INVOKE_TEMPLATE_FAILED = """Execution log for request {request_id}
|
|
66
|
+
{formatted_date} : Starting execution for request: {request_id}
|
|
67
|
+
{formatted_date} : HTTP Method: {request_method}, Resource Path: {resource_path}
|
|
68
|
+
{formatted_date} : Method request path: {method_request_path_parameters}
|
|
69
|
+
{formatted_date} : Method request query string: {method_request_query_string}
|
|
70
|
+
{formatted_date} : Method request headers: {method_request_headers}
|
|
71
|
+
{formatted_date} : Method request body before transformations: {method_request_body}
|
|
72
|
+
{formatted_date} : Execution failed due to {error_type}: {error_message}
|
|
73
|
+
{formatted_date} : Method completed with status: {method_response_status}
|
|
74
|
+
"""
|
|
75
|
+
|
|
48
76
|
|
|
49
77
|
def _dump_headers(headers: Headers) -> str:
|
|
50
78
|
if not headers:
|
|
@@ -63,9 +91,9 @@ def log_template(invocation_context: RestApiInvocationContext, response_headers:
|
|
|
63
91
|
formatted_date = datetime.datetime.now(tz=datetime.UTC).strftime("%a %b %d %H:%M:%S %Z %Y")
|
|
64
92
|
request = invocation_context.invocation_request
|
|
65
93
|
context_var = invocation_context.context_variables
|
|
66
|
-
integration_req = invocation_context.integration_request
|
|
67
|
-
endpoint_resp = invocation_context.endpoint_response
|
|
68
|
-
method_resp = invocation_context.invocation_response
|
|
94
|
+
integration_req = invocation_context.integration_request or {}
|
|
95
|
+
endpoint_resp = invocation_context.endpoint_response or {}
|
|
96
|
+
method_resp = invocation_context.invocation_response or {}
|
|
69
97
|
# TODO: if endpoint_uri is an ARN, it means it's an AWS_PROXY integration
|
|
70
98
|
# this should be transformed to the true URL of a lambda invoke call
|
|
71
99
|
endpoint_uri = integration_req.get("uri", "")
|
|
@@ -93,6 +121,52 @@ def log_template(invocation_context: RestApiInvocationContext, response_headers:
|
|
|
93
121
|
)
|
|
94
122
|
|
|
95
123
|
|
|
124
|
+
def log_mock_template(
|
|
125
|
+
invocation_context: RestApiInvocationContext, response_headers: Headers
|
|
126
|
+
) -> str:
|
|
127
|
+
formatted_date = datetime.datetime.now(tz=datetime.UTC).strftime("%a %b %d %H:%M:%S %Z %Y")
|
|
128
|
+
request = invocation_context.invocation_request
|
|
129
|
+
context_var = invocation_context.context_variables
|
|
130
|
+
method_resp = invocation_context.invocation_response or {}
|
|
131
|
+
|
|
132
|
+
return TEST_INVOKE_TEMPLATE_MOCK.format(
|
|
133
|
+
formatted_date=formatted_date,
|
|
134
|
+
request_id=context_var["requestId"],
|
|
135
|
+
resource_path=request["path"],
|
|
136
|
+
request_method=request["http_method"],
|
|
137
|
+
method_request_path_parameters=dict_to_string(request["path_parameters"]),
|
|
138
|
+
method_request_query_string=dict_to_string(request["query_string_parameters"]),
|
|
139
|
+
method_request_headers=_dump_headers(request.get("headers")),
|
|
140
|
+
method_request_body=to_str(request.get("body", "")),
|
|
141
|
+
method_response_status=method_resp.get("status_code"),
|
|
142
|
+
method_response_body=to_str(method_resp.get("body", "")),
|
|
143
|
+
method_response_headers=_dump_headers(response_headers),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def log_failed_template(
|
|
148
|
+
invocation_context: RestApiInvocationContext, response_status_code: int
|
|
149
|
+
) -> str:
|
|
150
|
+
formatted_date = datetime.datetime.now(tz=datetime.UTC).strftime("%a %b %d %H:%M:%S %Z %Y")
|
|
151
|
+
request = invocation_context.invocation_request
|
|
152
|
+
context_var = invocation_context.context_variables
|
|
153
|
+
|
|
154
|
+
return TEST_INVOKE_TEMPLATE_FAILED.format(
|
|
155
|
+
formatted_date=formatted_date,
|
|
156
|
+
request_id=context_var["requestId"],
|
|
157
|
+
resource_path=request["path"],
|
|
158
|
+
request_method=request["http_method"],
|
|
159
|
+
method_request_path_parameters=dict_to_string(request["path_parameters"]),
|
|
160
|
+
method_request_query_string=dict_to_string(request["query_string_parameters"]),
|
|
161
|
+
method_request_headers=_dump_headers(request.get("headers")),
|
|
162
|
+
method_request_body=to_str(request.get("body", "")),
|
|
163
|
+
method_response_status=response_status_code,
|
|
164
|
+
# TODO: fix the error message
|
|
165
|
+
error_type="",
|
|
166
|
+
error_message="",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
96
170
|
def create_test_chain() -> HandlerChain[RestApiInvocationContext]:
|
|
97
171
|
return HandlerChain(
|
|
98
172
|
request_handlers=[
|
|
@@ -114,13 +188,16 @@ def create_test_invocation_context(
|
|
|
114
188
|
) -> RestApiInvocationContext:
|
|
115
189
|
parse_handler = handlers.parse_request
|
|
116
190
|
http_method = test_request["httpMethod"]
|
|
191
|
+
resource = deployment.rest_api.resources[test_request["resourceId"]]
|
|
192
|
+
resource_path = resource["path"]
|
|
117
193
|
|
|
118
194
|
# we do not need a true HTTP request for the context, as we are skipping all the parsing steps and using the
|
|
119
195
|
# provider data
|
|
120
196
|
invocation_context = RestApiInvocationContext(
|
|
121
197
|
request=Request(method=http_method),
|
|
122
198
|
)
|
|
123
|
-
|
|
199
|
+
test_request_path = test_request.get("pathWithQueryString") or resource_path
|
|
200
|
+
path_query = test_request_path.split("?")
|
|
124
201
|
path = path_query[0]
|
|
125
202
|
multi_query_args: dict[str, list[str]] = {}
|
|
126
203
|
|
|
@@ -140,9 +217,22 @@ def create_test_invocation_context(
|
|
|
140
217
|
# TODO: handle multiValueHeaders
|
|
141
218
|
body=to_bytes(test_request.get("body") or ""),
|
|
142
219
|
)
|
|
220
|
+
|
|
143
221
|
invocation_context.invocation_request = invocation_request
|
|
222
|
+
try:
|
|
223
|
+
# this is AWS behavior, it will accept any value for the `pathWithQueryString`, even if it doesn't match
|
|
224
|
+
# the expected format. It will just fall back to no value if it cannot parse the path parameters out of it
|
|
225
|
+
_, path_parameters = RestAPIResourceRouter(deployment).match(invocation_context)
|
|
226
|
+
except Exception as e:
|
|
227
|
+
LOG.warning(
|
|
228
|
+
"Error while trying to extract path parameters from user-provided 'pathWithQueryString=%s' "
|
|
229
|
+
"for the following resource path: '%s'. Error: '%s'",
|
|
230
|
+
path,
|
|
231
|
+
resource_path,
|
|
232
|
+
e,
|
|
233
|
+
)
|
|
234
|
+
path_parameters = {}
|
|
144
235
|
|
|
145
|
-
_, path_parameters = RestAPIResourceRouter(deployment).match(invocation_context)
|
|
146
236
|
invocation_request["path_parameters"] = path_parameters
|
|
147
237
|
|
|
148
238
|
invocation_context.deployment = deployment
|
|
@@ -160,8 +250,9 @@ def create_test_invocation_context(
|
|
|
160
250
|
responseOverride=ContextVarsResponseOverride(header={}, status=0),
|
|
161
251
|
)
|
|
162
252
|
invocation_context.trace_id = parse_handler.populate_trace_id({})
|
|
163
|
-
|
|
164
|
-
|
|
253
|
+
resource_method = (
|
|
254
|
+
resource["resourceMethods"].get(http_method) or resource["resourceMethods"]["ANY"]
|
|
255
|
+
)
|
|
165
256
|
invocation_context.resource = resource
|
|
166
257
|
invocation_context.resource_method = resource_method
|
|
167
258
|
invocation_context.integration = resource_method["methodIntegration"]
|
|
@@ -179,8 +270,10 @@ def run_test_invocation(
|
|
|
179
270
|
invocation_context = create_test_invocation_context(test_request, deployment)
|
|
180
271
|
|
|
181
272
|
test_chain = create_test_chain()
|
|
273
|
+
is_mock_integration = invocation_context.integration["type"] == "MOCK"
|
|
274
|
+
|
|
182
275
|
# header order is important
|
|
183
|
-
if
|
|
276
|
+
if is_mock_integration:
|
|
184
277
|
base_headers = {"Content-Type": APPLICATION_JSON}
|
|
185
278
|
else:
|
|
186
279
|
# we manually add the trace-id, as it is normally added by handlers.response_enricher which adds to much data
|
|
@@ -199,7 +292,19 @@ def run_test_invocation(
|
|
|
199
292
|
# AWS does not return the Content-Length for TestInvokeMethod
|
|
200
293
|
response_headers.remove("Content-Length")
|
|
201
294
|
|
|
202
|
-
|
|
295
|
+
if not invocation_context.invocation_response:
|
|
296
|
+
# TODO: this is an heuristic to guess if we encounter an exception in the call
|
|
297
|
+
# in the future, we should attach the exception to the context so we could act on it and properly
|
|
298
|
+
# log as we go through the invocation, so that if we have an error we stop logging at the right moment
|
|
299
|
+
for header in ("Content-Type", "X-Amzn-Trace-Id"):
|
|
300
|
+
response_headers.remove(header)
|
|
301
|
+
log = log_failed_template(invocation_context, test_response.status_code)
|
|
302
|
+
|
|
303
|
+
elif is_mock_integration:
|
|
304
|
+
# TODO: revisit how we're building the logs
|
|
305
|
+
log = log_mock_template(invocation_context, response_headers)
|
|
306
|
+
else:
|
|
307
|
+
log = log_template(invocation_context, response_headers)
|
|
203
308
|
|
|
204
309
|
headers = dict(response_headers)
|
|
205
310
|
multi_value_headers = build_multi_value_headers(response_headers)
|
|
@@ -429,6 +429,11 @@ class ApigatewayNextGenProvider(ApigatewayProvider):
|
|
|
429
429
|
if not resource:
|
|
430
430
|
raise NotFoundException("Invalid Resource identifier specified")
|
|
431
431
|
|
|
432
|
+
resource_methods = resource.resource_methods
|
|
433
|
+
|
|
434
|
+
if request["httpMethod"] not in resource_methods and "ANY" not in resource_methods:
|
|
435
|
+
raise NotFoundException("Invalid Method identifier specified")
|
|
436
|
+
|
|
432
437
|
# test httpMethod
|
|
433
438
|
|
|
434
439
|
rest_api_container = get_rest_api_container(context, rest_api_id=rest_api_id)
|
|
@@ -72,7 +72,7 @@ class ApiGatewayResourceProvider(ResourceProvider[ApiGatewayResourceProperties])
|
|
|
72
72
|
root_resource = ([r for r in resources if r["path"] == "/"] or [None])[0]
|
|
73
73
|
if not root_resource:
|
|
74
74
|
raise Exception(
|
|
75
|
-
"Unable to find root resource for REST API
|
|
75
|
+
"Unable to find root resource for REST API {}".format(params["restApiId"])
|
|
76
76
|
)
|
|
77
77
|
params["parentId"] = root_resource["id"]
|
|
78
78
|
response = apigw.create_resource(**params)
|
|
@@ -77,9 +77,7 @@ def get_remote_template_body(url: str) -> str:
|
|
|
77
77
|
result = client.get_object(Bucket=parts[0], Key=parts[2])
|
|
78
78
|
body = to_str(result["Body"].read())
|
|
79
79
|
return body
|
|
80
|
-
raise RuntimeError(
|
|
81
|
-
"Unable to fetch template body (code %s) from URL %s" % (status_code, url)
|
|
82
|
-
)
|
|
80
|
+
raise RuntimeError(f"Unable to fetch template body (code {status_code}) from URL {url}")
|
|
83
81
|
else:
|
|
84
82
|
raise RuntimeError(
|
|
85
83
|
f"Bad status code from fetching template from url '{url}' ({status_code})",
|
|
@@ -112,11 +110,9 @@ def get_template_body(req_data: dict) -> str:
|
|
|
112
110
|
result = client.get_object(Bucket=parts[0], Key=parts[2])
|
|
113
111
|
body = to_str(result["Body"].read())
|
|
114
112
|
return body
|
|
115
|
-
raise Exception(
|
|
116
|
-
"Unable to fetch template body (code %s) from URL %s" % (status_code, url)
|
|
117
|
-
)
|
|
113
|
+
raise Exception(f"Unable to fetch template body (code {status_code}) from URL {url}")
|
|
118
114
|
return to_str(response.content)
|
|
119
|
-
raise Exception("Unable to get template body from input:
|
|
115
|
+
raise Exception(f"Unable to get template body from input: {req_data}")
|
|
120
116
|
|
|
121
117
|
|
|
122
118
|
def is_local_service_url(url: str) -> bool:
|
|
@@ -127,7 +123,7 @@ def is_local_service_url(url: str) -> bool:
|
|
|
127
123
|
constants.LOCALHOST_HOSTNAME,
|
|
128
124
|
localstack_host().host,
|
|
129
125
|
)
|
|
130
|
-
if any(re.match(
|
|
126
|
+
if any(re.match(rf"^[^:]+://[^:/]*{host}([:/]|$)", url) for host in candidates):
|
|
131
127
|
return True
|
|
132
128
|
host = url.split("://")[-1].split("/")[0]
|
|
133
129
|
return "localhost" in host
|
|
@@ -59,7 +59,7 @@ def convert_types(obj, types):
|
|
|
59
59
|
def recurse(o, path):
|
|
60
60
|
if isinstance(o, dict):
|
|
61
61
|
for k, v in dict(o).items():
|
|
62
|
-
key_path = "
|
|
62
|
+
key_path = "{}{}".format(path or ".", k)
|
|
63
63
|
if key in [k, key_path]:
|
|
64
64
|
o[k] = type_class(v)
|
|
65
65
|
return o
|
|
@@ -14,7 +14,13 @@ from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
|
14
14
|
)
|
|
15
15
|
from localstack.utils.aws import arns
|
|
16
16
|
from localstack.utils.collections import select_attributes
|
|
17
|
-
from localstack.utils.id_generator import
|
|
17
|
+
from localstack.utils.id_generator import (
|
|
18
|
+
ExistingIds,
|
|
19
|
+
ResourceIdentifier,
|
|
20
|
+
Tags,
|
|
21
|
+
generate_short_uid,
|
|
22
|
+
generate_uid,
|
|
23
|
+
)
|
|
18
24
|
from localstack.utils.json import clone_safe
|
|
19
25
|
from localstack.utils.objects import recurse_object
|
|
20
26
|
from localstack.utils.strings import long_uid, short_uid
|
|
@@ -75,6 +81,11 @@ class StackIdentifier(ResourceIdentifier):
|
|
|
75
81
|
return generate_short_uid(resource_identifier=self, existing_ids=existing_ids, tags=tags)
|
|
76
82
|
|
|
77
83
|
|
|
84
|
+
class StackIdentifierV2(StackIdentifier):
|
|
85
|
+
def generate(self, existing_ids: ExistingIds = None, tags: Tags = None) -> str:
|
|
86
|
+
return generate_uid(resource_identifier=self, existing_ids=existing_ids, tags=tags)
|
|
87
|
+
|
|
88
|
+
|
|
78
89
|
# TODO: remove metadata (flatten into individual fields)
|
|
79
90
|
class Stack:
|
|
80
91
|
change_sets: list["StackChangeSet"]
|
|
@@ -360,8 +371,7 @@ class Stack:
|
|
|
360
371
|
resource = resource_map.get(resource_id)
|
|
361
372
|
if not resource:
|
|
362
373
|
raise Exception(
|
|
363
|
-
'Unable to find details for resource "
|
|
364
|
-
% (resource_id, self.stack_name)
|
|
374
|
+
f'Unable to find details for resource "{resource_id}" in stack "{self.stack_name}"'
|
|
365
375
|
)
|
|
366
376
|
return resource
|
|
367
377
|
|
|
@@ -393,7 +403,7 @@ class StackChangeSet(Stack):
|
|
|
393
403
|
template = {}
|
|
394
404
|
if params is None:
|
|
395
405
|
params = {}
|
|
396
|
-
super(
|
|
406
|
+
super().__init__(account_id, region_name, params, template)
|
|
397
407
|
|
|
398
408
|
name = self.metadata["ChangeSetName"]
|
|
399
409
|
if not self.metadata.get("ChangeSetId"):
|
|
@@ -329,7 +329,7 @@ def _resolve_refs_recursively(
|
|
|
329
329
|
account_id, region_name, stack_name, resources, parameters, value["Ref"]
|
|
330
330
|
)
|
|
331
331
|
if ref is None:
|
|
332
|
-
msg = 'Unable to resolve Ref for resource "
|
|
332
|
+
msg = 'Unable to resolve Ref for resource "{}" (yet)'.format(value["Ref"])
|
|
333
333
|
LOG.debug("%s - %s", msg, resources.get(value["Ref"]) or set(resources.keys()))
|
|
334
334
|
|
|
335
335
|
raise DependencyNotYetSatisfied(resource_ids=value["Ref"], message=msg)
|
|
@@ -450,7 +450,7 @@ def _resolve_refs_recursively(
|
|
|
450
450
|
raise DependencyNotYetSatisfied(
|
|
451
451
|
resource_ids=key, message=f"Could not resolve {val} to terminal value type"
|
|
452
452
|
)
|
|
453
|
-
result = result.replace("${
|
|
453
|
+
result = result.replace(f"${{{key}}}", str(resolved_val))
|
|
454
454
|
|
|
455
455
|
# resolve placeholders
|
|
456
456
|
result = resolve_placeholders_in_string(
|
|
@@ -1479,10 +1479,11 @@ class TemplateDeployer:
|
|
|
1479
1479
|
# correct order yet.
|
|
1480
1480
|
continue
|
|
1481
1481
|
case OperationStatus.FAILED:
|
|
1482
|
-
LOG.
|
|
1482
|
+
LOG.error(
|
|
1483
1483
|
"Failed to delete resource with id %s. Reason: %s",
|
|
1484
1484
|
resource_id,
|
|
1485
1485
|
event.message or "unknown",
|
|
1486
|
+
exc_info=LOG.isEnabledFor(logging.DEBUG),
|
|
1486
1487
|
)
|
|
1487
1488
|
case OperationStatus.IN_PROGRESS:
|
|
1488
1489
|
# the resource provider executor should not return this state, so
|
|
@@ -1494,10 +1495,11 @@ class TemplateDeployer:
|
|
|
1494
1495
|
raise Exception(f"Use of unsupported status found: {other_status}")
|
|
1495
1496
|
|
|
1496
1497
|
except Exception as e:
|
|
1497
|
-
LOG.
|
|
1498
|
+
LOG.error(
|
|
1498
1499
|
"Failed to delete resource with id %s. Final exception: %s",
|
|
1499
1500
|
resource_id,
|
|
1500
1501
|
e,
|
|
1502
|
+
exc_info=LOG.isEnabledFor(logging.DEBUG),
|
|
1501
1503
|
)
|
|
1502
1504
|
|
|
1503
1505
|
# update status
|
|
@@ -14,6 +14,7 @@ from samtranslator.translator.transform import transform as transform_sam
|
|
|
14
14
|
|
|
15
15
|
from localstack.aws.api import CommonServiceException
|
|
16
16
|
from localstack.aws.connect import connect_to
|
|
17
|
+
from localstack.services.cloudformation.engine.parameters import StackParameter
|
|
17
18
|
from localstack.services.cloudformation.engine.policy_loader import create_policy_loader
|
|
18
19
|
from localstack.services.cloudformation.engine.template_deployer import resolve_refs_recursively
|
|
19
20
|
from localstack.services.cloudformation.engine.validations import ValidationError
|
|
@@ -39,7 +40,7 @@ class ResolveRefsRecursivelyContext:
|
|
|
39
40
|
resources: dict
|
|
40
41
|
mappings: dict
|
|
41
42
|
conditions: dict
|
|
42
|
-
parameters: dict
|
|
43
|
+
parameters: dict[str, StackParameter]
|
|
43
44
|
|
|
44
45
|
def resolve(self, value: Any) -> Any:
|
|
45
46
|
return resolve_refs_recursively(
|
|
@@ -257,10 +258,11 @@ def execute_macro(
|
|
|
257
258
|
formatted_stack_parameters = {}
|
|
258
259
|
for key, value in stack_parameters.items():
|
|
259
260
|
# TODO: we want to support other types of parameters
|
|
260
|
-
|
|
261
|
-
|
|
261
|
+
parameter_value = value.get("ParameterValue")
|
|
262
|
+
if value.get("ParameterType") == "CommaDelimitedList" and isinstance(parameter_value, str):
|
|
263
|
+
formatted_stack_parameters[key] = parameter_value.split(",")
|
|
262
264
|
else:
|
|
263
|
-
formatted_stack_parameters[key] =
|
|
265
|
+
formatted_stack_parameters[key] = parameter_value
|
|
264
266
|
|
|
265
267
|
transformation_id = f"{account_id}::{macro['Name']}"
|
|
266
268
|
event = {
|