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.
Files changed (253) hide show
  1. localstack/aws/api/cloudformation/__init__.py +18 -4
  2. localstack/aws/api/cloudwatch/__init__.py +41 -1
  3. localstack/aws/api/config/__init__.py +4 -0
  4. localstack/aws/api/core.py +6 -2
  5. localstack/aws/api/dynamodb/__init__.py +30 -0
  6. localstack/aws/api/ec2/__init__.py +1522 -65
  7. localstack/aws/api/iam/__init__.py +7 -0
  8. localstack/aws/api/kinesis/__init__.py +19 -0
  9. localstack/aws/api/kms/__init__.py +6 -0
  10. localstack/aws/api/lambda_/__init__.py +13 -0
  11. localstack/aws/api/logs/__init__.py +15 -0
  12. localstack/aws/api/redshift/__init__.py +9 -3
  13. localstack/aws/api/route53/__init__.py +5 -0
  14. localstack/aws/api/s3/__init__.py +12 -0
  15. localstack/aws/api/s3control/__init__.py +54 -0
  16. localstack/aws/api/ssm/__init__.py +2 -0
  17. localstack/aws/api/transcribe/__init__.py +17 -0
  18. localstack/aws/client.py +7 -2
  19. localstack/aws/forwarder.py +52 -5
  20. localstack/aws/handlers/analytics.py +1 -1
  21. localstack/aws/handlers/internal_requests.py +6 -1
  22. localstack/aws/handlers/logging.py +12 -2
  23. localstack/aws/handlers/metric_handler.py +41 -1
  24. localstack/aws/handlers/service.py +40 -20
  25. localstack/aws/mocking.py +2 -2
  26. localstack/aws/patches.py +2 -2
  27. localstack/aws/protocol/parser.py +459 -32
  28. localstack/aws/protocol/serializer.py +689 -69
  29. localstack/aws/protocol/service_router.py +120 -20
  30. localstack/aws/protocol/validate.py +1 -1
  31. localstack/aws/scaffold.py +1 -1
  32. localstack/aws/skeleton.py +4 -2
  33. localstack/aws/spec-patches.json +58 -0
  34. localstack/aws/spec.py +37 -16
  35. localstack/cli/exceptions.py +1 -1
  36. localstack/cli/localstack.py +6 -6
  37. localstack/cli/lpm.py +3 -4
  38. localstack/cli/plugins.py +1 -1
  39. localstack/cli/profiles.py +1 -2
  40. localstack/config.py +25 -18
  41. localstack/constants.py +4 -29
  42. localstack/dev/kubernetes/__main__.py +130 -7
  43. localstack/dev/run/configurators.py +1 -4
  44. localstack/dev/run/paths.py +1 -1
  45. localstack/dns/plugins.py +5 -1
  46. localstack/dns/server.py +13 -4
  47. localstack/logging/format.py +3 -3
  48. localstack/packages/api.py +9 -8
  49. localstack/packages/core.py +2 -2
  50. localstack/packages/plugins.py +0 -8
  51. localstack/runtime/analytics.py +3 -0
  52. localstack/runtime/hooks.py +1 -1
  53. localstack/runtime/init.py +2 -2
  54. localstack/runtime/main.py +5 -5
  55. localstack/runtime/patches.py +2 -2
  56. localstack/services/apigateway/helpers.py +1 -4
  57. localstack/services/apigateway/legacy/helpers.py +7 -8
  58. localstack/services/apigateway/legacy/integration.py +4 -3
  59. localstack/services/apigateway/legacy/invocations.py +6 -5
  60. localstack/services/apigateway/legacy/provider.py +148 -68
  61. localstack/services/apigateway/legacy/templates.py +1 -1
  62. localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py +7 -2
  63. localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py +1 -2
  64. localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +3 -0
  65. localstack/services/apigateway/next_gen/execute_api/integrations/http.py +3 -3
  66. localstack/services/apigateway/next_gen/execute_api/template_mapping.py +2 -2
  67. localstack/services/apigateway/next_gen/execute_api/test_invoke.py +114 -9
  68. localstack/services/apigateway/next_gen/provider.py +5 -0
  69. localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +1 -1
  70. localstack/services/cloudformation/api_utils.py +4 -8
  71. localstack/services/cloudformation/cfn_utils.py +1 -1
  72. localstack/services/cloudformation/engine/entities.py +14 -4
  73. localstack/services/cloudformation/engine/template_deployer.py +6 -4
  74. localstack/services/cloudformation/engine/transformers.py +6 -4
  75. localstack/services/cloudformation/engine/v2/change_set_model.py +201 -13
  76. localstack/services/cloudformation/engine/v2/change_set_model_describer.py +52 -3
  77. localstack/services/cloudformation/engine/v2/change_set_model_executor.py +117 -76
  78. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +205 -52
  79. localstack/services/cloudformation/engine/v2/change_set_model_transform.py +350 -116
  80. localstack/services/cloudformation/engine/v2/change_set_model_validator.py +56 -14
  81. localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
  82. localstack/services/cloudformation/engine/v2/resolving.py +7 -5
  83. localstack/services/cloudformation/engine/yaml_parser.py +9 -2
  84. localstack/services/cloudformation/provider.py +7 -5
  85. localstack/services/cloudformation/resource_provider.py +7 -1
  86. localstack/services/cloudformation/resources.py +24149 -0
  87. localstack/services/cloudformation/service_models.py +2 -2
  88. localstack/services/cloudformation/v2/entities.py +19 -9
  89. localstack/services/cloudformation/v2/provider.py +336 -106
  90. localstack/services/cloudformation/v2/types.py +13 -7
  91. localstack/services/cloudformation/v2/utils.py +4 -1
  92. localstack/services/cloudwatch/alarm_scheduler.py +4 -1
  93. localstack/services/cloudwatch/provider.py +18 -13
  94. localstack/services/cloudwatch/provider_v2.py +25 -28
  95. localstack/services/dynamodb/packages.py +2 -1
  96. localstack/services/dynamodb/provider.py +42 -0
  97. localstack/services/dynamodb/server.py +2 -2
  98. localstack/services/dynamodb/v2/provider.py +42 -0
  99. localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
  100. localstack/services/edge.py +1 -1
  101. localstack/services/es/provider.py +2 -2
  102. localstack/services/events/event_rule_engine.py +31 -13
  103. localstack/services/events/models.py +4 -5
  104. localstack/services/events/provider.py +17 -14
  105. localstack/services/events/target.py +17 -9
  106. localstack/services/events/v1/provider.py +5 -5
  107. localstack/services/firehose/provider.py +14 -4
  108. localstack/services/iam/provider.py +11 -116
  109. localstack/services/iam/resources/policy_simulator.py +133 -0
  110. localstack/services/kinesis/models.py +15 -2
  111. localstack/services/kinesis/provider.py +86 -3
  112. localstack/services/kms/provider.py +14 -5
  113. localstack/services/lambda_/api_utils.py +6 -3
  114. localstack/services/lambda_/invocation/docker_runtime_executor.py +1 -1
  115. localstack/services/lambda_/invocation/event_manager.py +1 -1
  116. localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
  117. localstack/services/lambda_/invocation/lambda_models.py +10 -7
  118. localstack/services/lambda_/invocation/lambda_service.py +5 -1
  119. localstack/services/lambda_/packages.py +1 -1
  120. localstack/services/lambda_/provider.py +4 -3
  121. localstack/services/lambda_/provider_utils.py +1 -1
  122. localstack/services/logs/provider.py +36 -19
  123. localstack/services/moto.py +2 -1
  124. localstack/services/opensearch/cluster.py +15 -7
  125. localstack/services/opensearch/packages.py +26 -7
  126. localstack/services/opensearch/provider.py +8 -2
  127. localstack/services/opensearch/versions.py +56 -7
  128. localstack/services/plugins.py +11 -7
  129. localstack/services/providers.py +10 -2
  130. localstack/services/redshift/provider.py +0 -21
  131. localstack/services/s3/constants.py +5 -2
  132. localstack/services/s3/cors.py +4 -4
  133. localstack/services/s3/models.py +1 -1
  134. localstack/services/s3/notifications.py +55 -39
  135. localstack/services/s3/presigned_url.py +35 -54
  136. localstack/services/s3/provider.py +73 -15
  137. localstack/services/s3/utils.py +42 -22
  138. localstack/services/s3/validation.py +46 -32
  139. localstack/services/s3/website_hosting.py +4 -2
  140. localstack/services/ses/provider.py +18 -8
  141. localstack/services/sns/constants.py +7 -1
  142. localstack/services/sns/executor.py +9 -2
  143. localstack/services/sns/provider.py +8 -5
  144. localstack/services/sns/publisher.py +31 -16
  145. localstack/services/sns/v2/models.py +167 -0
  146. localstack/services/sns/v2/provider.py +867 -0
  147. localstack/services/sns/v2/utils.py +130 -0
  148. localstack/services/sqs/constants.py +1 -1
  149. localstack/services/sqs/developer_api.py +205 -0
  150. localstack/services/sqs/models.py +48 -5
  151. localstack/services/sqs/provider.py +38 -311
  152. localstack/services/sqs/query_api.py +6 -2
  153. localstack/services/sqs/utils.py +121 -2
  154. localstack/services/ssm/provider.py +1 -1
  155. localstack/services/stepfunctions/asl/component/intrinsic/member.py +1 -1
  156. localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py +5 -11
  157. localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py +2 -2
  158. localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +2 -2
  159. localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py +1 -1
  160. localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py +2 -2
  161. localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +1 -1
  162. localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py +2 -2
  163. localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py +1 -1
  164. localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py +1 -1
  165. localstack/services/stepfunctions/asl/eval/environment.py +1 -1
  166. localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
  167. localstack/services/stepfunctions/backend/execution.py +2 -1
  168. localstack/services/stores.py +1 -1
  169. localstack/services/transcribe/provider.py +6 -1
  170. localstack/state/codecs.py +61 -0
  171. localstack/state/core.py +11 -5
  172. localstack/state/pickle.py +10 -49
  173. localstack/testing/aws/cloudformation_utils.py +1 -1
  174. localstack/testing/pytest/cloudformation/fixtures.py +3 -3
  175. localstack/testing/pytest/cloudformation/transformers.py +0 -0
  176. localstack/testing/pytest/container.py +4 -5
  177. localstack/testing/pytest/fixtures.py +33 -31
  178. localstack/testing/pytest/in_memory_localstack.py +0 -4
  179. localstack/testing/pytest/marking.py +38 -11
  180. localstack/testing/pytest/stepfunctions/utils.py +4 -3
  181. localstack/testing/pytest/util.py +1 -1
  182. localstack/testing/pytest/validation_tracking.py +1 -2
  183. localstack/testing/snapshots/transformer_utility.py +6 -1
  184. localstack/utils/analytics/events.py +2 -2
  185. localstack/utils/analytics/metadata.py +6 -4
  186. localstack/utils/analytics/metrics/counter.py +8 -15
  187. localstack/utils/analytics/publisher.py +1 -2
  188. localstack/utils/analytics/service_providers.py +19 -0
  189. localstack/utils/analytics/service_request_aggregator.py +2 -2
  190. localstack/utils/archives.py +11 -11
  191. localstack/utils/asyncio.py +2 -2
  192. localstack/utils/aws/arns.py +24 -29
  193. localstack/utils/aws/aws_responses.py +8 -8
  194. localstack/utils/aws/aws_stack.py +2 -3
  195. localstack/utils/aws/dead_letter_queue.py +1 -5
  196. localstack/utils/aws/message_forwarding.py +1 -2
  197. localstack/utils/aws/request_context.py +4 -5
  198. localstack/utils/aws/resources.py +1 -1
  199. localstack/utils/aws/templating.py +1 -1
  200. localstack/utils/batch_policy.py +3 -3
  201. localstack/utils/bootstrap.py +21 -13
  202. localstack/utils/catalog/catalog.py +139 -0
  203. localstack/utils/catalog/catalog_loader.py +119 -0
  204. localstack/utils/catalog/common.py +58 -0
  205. localstack/utils/catalog/plugins.py +28 -0
  206. localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
  207. localstack/utils/collections.py +7 -8
  208. localstack/utils/config_listener.py +1 -1
  209. localstack/utils/container_networking.py +2 -3
  210. localstack/utils/container_utils/container_client.py +135 -136
  211. localstack/utils/container_utils/docker_cmd_client.py +85 -69
  212. localstack/utils/container_utils/docker_sdk_client.py +69 -66
  213. localstack/utils/crypto.py +10 -10
  214. localstack/utils/diagnose.py +3 -4
  215. localstack/utils/docker_utils.py +9 -5
  216. localstack/utils/files.py +33 -13
  217. localstack/utils/functions.py +4 -3
  218. localstack/utils/http.py +11 -11
  219. localstack/utils/json.py +20 -6
  220. localstack/utils/kinesis/kinesis_connector.py +2 -1
  221. localstack/utils/net.py +15 -9
  222. localstack/utils/no_exit_argument_parser.py +2 -2
  223. localstack/utils/numbers.py +9 -2
  224. localstack/utils/objects.py +7 -6
  225. localstack/utils/patch.py +10 -3
  226. localstack/utils/run.py +12 -11
  227. localstack/utils/scheduler.py +11 -11
  228. localstack/utils/server/tcp_proxy.py +2 -2
  229. localstack/utils/serving.py +3 -4
  230. localstack/utils/strings.py +15 -16
  231. localstack/utils/sync.py +126 -1
  232. localstack/utils/tagging.py +8 -6
  233. localstack/utils/testutil.py +8 -8
  234. localstack/utils/threads.py +2 -2
  235. localstack/utils/time.py +12 -4
  236. localstack/utils/urls.py +1 -3
  237. localstack/utils/xray/traceid.py +1 -1
  238. localstack/version.py +16 -3
  239. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/METADATA +18 -14
  240. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/RECORD +248 -239
  241. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/entry_points.txt +8 -4
  242. localstack_core-4.10.1.dev12.dist-info/plux.json +1 -0
  243. localstack/packages/terraform.py +0 -46
  244. localstack/services/cloudformation/deploy.html +0 -144
  245. localstack/services/cloudformation/deploy_ui.py +0 -47
  246. localstack/services/cloudformation/plugins.py +0 -12
  247. localstack_core-4.7.1.dev49.dist-info/plux.json +0 -1
  248. {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack +0 -0
  249. {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack-supervisor +0 -0
  250. {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack.bat +0 -0
  251. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/WHEEL +0 -0
  252. {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/licenses/LICENSE.txt +0 -0
  253. {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
- path_query = test_request.get("pathWithQueryString", "/").split("?")
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
- resource = deployment.rest_api.resources[test_request["resourceId"]]
164
- resource_method = resource["resourceMethods"][http_method]
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 invocation_context.integration["type"] == "MOCK":
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
- log = log_template(invocation_context, response_headers)
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 %s" % params["restApiId"]
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: %s" % req_data)
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(r"^[^:]+://[^:/]*%s([:/]|$)" % host, url) for host in candidates):
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 = "%s%s" % (path or ".", k)
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 ExistingIds, ResourceIdentifier, Tags, generate_short_uid
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 "%s" in stack "%s"'
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(StackChangeSet, self).__init__(account_id, region_name, params, template)
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 "%s" (yet)' % value["Ref"]
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("${%s}" % key, str(resolved_val))
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.exception(
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.exception(
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
- if value.get("ParameterType") == "CommaDelimitedList":
261
- formatted_stack_parameters[key] = value.get("ParameterValue").split(",")
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] = value.get("ParameterValue")
265
+ formatted_stack_parameters[key] = parameter_value
264
266
 
265
267
  transformation_id = f"{account_id}::{macro['Name']}"
266
268
  event = {