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
@@ -26,6 +26,7 @@ from localstack.aws.api.opensearch import (
26
26
  CognitoOptions,
27
27
  CognitoOptionsStatus,
28
28
  ColdStorageOptions,
29
+ CompatibleVersionsMap,
29
30
  CreateDomainRequest,
30
31
  CreateDomainResponse,
31
32
  DeleteDomainResponse,
@@ -75,7 +76,6 @@ from localstack.aws.api.opensearch import (
75
76
  VolumeType,
76
77
  VPCDerivedInfoStatus,
77
78
  )
78
- from localstack.constants import OPENSEARCH_DEFAULT_VERSION
79
79
  from localstack.services.opensearch import versions
80
80
  from localstack.services.opensearch.cluster import SecurityOptions
81
81
  from localstack.services.opensearch.cluster_manager import (
@@ -84,6 +84,7 @@ from localstack.services.opensearch.cluster_manager import (
84
84
  create_cluster_manager,
85
85
  )
86
86
  from localstack.services.opensearch.models import OpenSearchStore, opensearch_stores
87
+ from localstack.services.opensearch.packages import OPENSEARCH_DEFAULT_VERSION
87
88
  from localstack.services.plugins import ServiceLifecycleHook
88
89
  from localstack.state import AssetDirectory, StateVisitor
89
90
  from localstack.utils.aws.arns import parse_arn
@@ -466,10 +467,11 @@ class OpensearchProvider(OpensearchApi, ServiceLifecycleHook):
466
467
  preferred_port=preferred_port,
467
468
  )
468
469
  except Exception:
469
- LOG.exception(
470
+ LOG.error(
470
471
  "Could not restore domain %s in region %s.",
471
472
  domain_name,
472
473
  region,
474
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
473
475
  )
474
476
 
475
477
  def on_before_state_reset(self):
@@ -649,6 +651,10 @@ class OpensearchProvider(OpensearchApi, ServiceLifecycleHook):
649
651
  for comp in versions.compatible_versions
650
652
  if comp["SourceVersion"] == version_filter
651
653
  ]
654
+ if not compatible_versions:
655
+ compatible_versions = [
656
+ CompatibleVersionsMap(SourceVersion=version_filter, TargetVersions=[])
657
+ ]
652
658
  return GetCompatibleVersionsResponse(CompatibleVersions=compatible_versions)
653
659
 
654
660
  def describe_domain_config(
@@ -13,20 +13,24 @@ from localstack.utils.common import get_arch
13
13
 
14
14
  # Internal representation of the OpenSearch versions (without the "OpenSearch_" prefix)
15
15
  _opensearch_install_versions = {
16
+ "3.1": "3.1.0",
17
+ "2.19": "2.19.3",
18
+ "2.17": "2.17.1",
19
+ "2.15": "2.15.0",
16
20
  "2.13": "2.13.0",
17
21
  "2.11": "2.11.1",
18
22
  "2.9": "2.9.0",
19
23
  "2.7": "2.7.0",
20
24
  "2.5": "2.5.0",
21
25
  "2.3": "2.3.0",
22
- "1.3": "1.3.12",
26
+ "1.3": "1.3.20",
23
27
  "1.2": "1.2.4",
24
28
  "1.1": "1.1.0",
25
29
  "1.0": "1.0.0",
26
30
  }
27
31
  # Internal representation of the Elasticsearch versions (without the "Elasticsearch_" prefix)
28
32
  _elasticsearch_install_versions = {
29
- "7.10": "7.10.0",
33
+ "7.10": "7.10.2",
30
34
  "7.9": "7.9.3",
31
35
  "7.8": "7.8.1",
32
36
  "7.7": "7.7.1",
@@ -221,6 +225,9 @@ compatible_versions = [
221
225
  "OpenSearch_2.9",
222
226
  "OpenSearch_2.11",
223
227
  "OpenSearch_2.13",
228
+ "OpenSearch_2.15",
229
+ "OpenSearch_2.17",
230
+ "OpenSearch_2.19",
224
231
  ],
225
232
  ),
226
233
  CompatibleVersionsMap(
@@ -231,28 +238,68 @@ compatible_versions = [
231
238
  "OpenSearch_2.9",
232
239
  "OpenSearch_2.11",
233
240
  "OpenSearch_2.13",
241
+ "OpenSearch_2.15",
242
+ "OpenSearch_2.17",
243
+ "OpenSearch_2.19",
234
244
  ],
235
245
  ),
236
246
  CompatibleVersionsMap(
237
247
  SourceVersion="OpenSearch_2.5",
238
- TargetVersions=["OpenSearch_2.7", "OpenSearch_2.9", "OpenSearch_2.11", "OpenSearch_2.13"],
248
+ TargetVersions=[
249
+ "OpenSearch_2.7",
250
+ "OpenSearch_2.9",
251
+ "OpenSearch_2.11",
252
+ "OpenSearch_2.13",
253
+ "OpenSearch_2.15",
254
+ "OpenSearch_2.17",
255
+ "OpenSearch_2.19",
256
+ ],
239
257
  ),
240
258
  CompatibleVersionsMap(
241
259
  SourceVersion="OpenSearch_2.7",
242
- TargetVersions=["OpenSearch_2.9", "OpenSearch_2.11", "OpenSearch_2.13"],
260
+ TargetVersions=[
261
+ "OpenSearch_2.9",
262
+ "OpenSearch_2.11",
263
+ "OpenSearch_2.13",
264
+ "OpenSearch_2.15",
265
+ "OpenSearch_2.17",
266
+ "OpenSearch_2.19",
267
+ ],
243
268
  ),
244
269
  CompatibleVersionsMap(
245
270
  SourceVersion="OpenSearch_2.9",
246
- TargetVersions=["OpenSearch_2.11", "OpenSearch_2.13"],
271
+ TargetVersions=[
272
+ "OpenSearch_2.11",
273
+ "OpenSearch_2.13",
274
+ "OpenSearch_2.15",
275
+ "OpenSearch_2.17",
276
+ "OpenSearch_2.19",
277
+ ],
247
278
  ),
248
279
  CompatibleVersionsMap(
249
280
  SourceVersion="OpenSearch_2.11",
250
- TargetVersions=["OpenSearch_2.13"],
281
+ TargetVersions=["OpenSearch_2.13", "OpenSearch_2.15", "OpenSearch_2.17", "OpenSearch_2.19"],
282
+ ),
283
+ CompatibleVersionsMap(
284
+ SourceVersion="OpenSearch_2.13",
285
+ TargetVersions=["OpenSearch_2.15", "OpenSearch_2.17", "OpenSearch_2.19"],
286
+ ),
287
+ CompatibleVersionsMap(
288
+ SourceVersion="OpenSearch_2.15",
289
+ TargetVersions=["OpenSearch_2.17", "OpenSearch_2.19"],
290
+ ),
291
+ CompatibleVersionsMap(
292
+ SourceVersion="OpenSearch_2.17",
293
+ TargetVersions=["OpenSearch_2.19"],
294
+ ),
295
+ CompatibleVersionsMap(
296
+ SourceVersion="OpenSearch_2.19",
297
+ TargetVersions=["OpenSearch_3.1"],
251
298
  ),
252
299
  ]
253
300
 
254
301
 
255
- def get_install_type_and_version(version: str) -> (EngineType, str):
302
+ def get_install_type_and_version(version: str) -> tuple[EngineType, str]:
256
303
  engine_type = EngineType(version.split("_")[0])
257
304
 
258
305
  if version not in install_versions:
@@ -297,6 +344,8 @@ def get_download_url(install_version: str, engine_type: EngineType) -> str:
297
344
  return _opensearch_url(install_version)
298
345
  elif engine_type == EngineType.Elasticsearch:
299
346
  return _es_url(install_version)
347
+ else:
348
+ raise ValueError(f"Unknown OpenSearch engine type: {engine_type}")
300
349
 
301
350
 
302
351
  def fetch_latest_versions() -> dict[str, str]: # pragma: no cover
@@ -286,19 +286,19 @@ class ServiceManager:
286
286
  container = self.get_service_container(name)
287
287
 
288
288
  if not container:
289
- raise ValueError("no such service %s" % name)
289
+ raise ValueError(f"no such service {name}")
290
290
 
291
291
  if container.state == ServiceState.STARTING:
292
292
  if not poll_condition(lambda: container.state != ServiceState.STARTING, timeout=30):
293
- raise TimeoutError("gave up waiting for service %s to start" % name)
293
+ raise TimeoutError(f"gave up waiting for service {name} to start")
294
294
 
295
295
  if container.state == ServiceState.STOPPING:
296
296
  if not poll_condition(lambda: container.state == ServiceState.STOPPED, timeout=30):
297
- raise TimeoutError("gave up waiting for service %s to stop" % name)
297
+ raise TimeoutError(f"gave up waiting for service {name} to stop")
298
298
 
299
299
  with container.lock:
300
300
  if container.state == ServiceState.DISABLED:
301
- raise ServiceDisabled("service %s is disabled" % name)
301
+ raise ServiceDisabled(f"service {name} is disabled")
302
302
 
303
303
  if container.state == ServiceState.RUNNING:
304
304
  return container.service
@@ -314,7 +314,7 @@ class ServiceManager:
314
314
  raise container.errors[-1]
315
315
 
316
316
  raise ServiceStateException(
317
- "service %s is not ready (%s) and could not be started" % (name, container.state)
317
+ f"service {name} is not ready ({container.state}) and could not be started"
318
318
  )
319
319
 
320
320
  # legacy map compatibility
@@ -692,7 +692,7 @@ def check_service_health(api, expect_shutdown=False):
692
692
  LOG.warning('Service "%s" not yet available, retrying...', api)
693
693
  else:
694
694
  LOG.warning('Service "%s" still shutting down, retrying...', api)
695
- raise Exception("Service check failed for api: %s" % api)
695
+ raise Exception(f"Service check failed for api: {api}")
696
696
 
697
697
 
698
698
  @hooks.on_infra_start(should_load=lambda: config.EAGER_SERVICE_LOADING)
@@ -708,4 +708,8 @@ def eager_load_services():
708
708
  except ServiceDisabled as e:
709
709
  LOG.debug("%s", e)
710
710
  except Exception:
711
- LOG.exception("could not load service plugin %s", api)
711
+ LOG.error(
712
+ "could not load service plugin %s",
713
+ api,
714
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
715
+ )
@@ -41,7 +41,7 @@ def apigateway_legacy():
41
41
  return Service.for_provider(provider, dispatch_table_factory=MotoFallbackDispatcher)
42
42
 
43
43
 
44
- @aws_provider()
44
+ @aws_provider(api="cloudformation", name="engine-legacy")
45
45
  def cloudformation():
46
46
  from localstack.services.cloudformation.provider import CloudformationProvider
47
47
 
@@ -49,7 +49,7 @@ def cloudformation():
49
49
  return Service.for_provider(provider)
50
50
 
51
51
 
52
- @aws_provider(api="cloudformation", name="engine-v2")
52
+ @aws_provider(api="cloudformation")
53
53
  def cloudformation_v2():
54
54
  from localstack.services.cloudformation.v2.provider import CloudformationProviderV2
55
55
 
@@ -318,6 +318,14 @@ def sns():
318
318
  return Service.for_provider(provider, dispatch_table_factory=MotoFallbackDispatcher)
319
319
 
320
320
 
321
+ @aws_provider(api="sns", name="v2")
322
+ def sns_v2():
323
+ from localstack.services.sns.v2.provider import SnsProvider
324
+
325
+ provider = SnsProvider()
326
+ return Service.for_provider(provider)
327
+
328
+
321
329
  @aws_provider()
322
330
  def sqs():
323
331
  from localstack.services.sqs.provider import SqsProvider
@@ -1,6 +1,5 @@
1
1
  import os
2
2
 
3
- from moto.redshift import responses as redshift_responses
4
3
  from moto.redshift.models import redshift_backends
5
4
 
6
5
  from localstack import config
@@ -12,26 +11,6 @@ from localstack.aws.api.redshift import (
12
11
  )
13
12
  from localstack.services.moto import call_moto
14
13
  from localstack.state import AssetDirectory, StateVisitor
15
- from localstack.utils.common import recurse_object
16
- from localstack.utils.patch import patch
17
-
18
-
19
- @patch(redshift_responses.itemize)
20
- def itemize(fn, data, parent_key=None, *args, **kwargs):
21
- # TODO: potentially add additional required tags here!
22
- list_parent_tags = ["ClusterSubnetGroups"]
23
-
24
- def fix_keys(o, **kwargs):
25
- if isinstance(o, dict):
26
- for k, v in o.items():
27
- if k in list_parent_tags:
28
- if isinstance(v, dict) and "item" in v:
29
- v[k[:-1]] = v.pop("item")
30
- return o
31
-
32
- result = fn(data, *args, **kwargs)
33
- recurse_object(result, fix_keys)
34
- return result
35
14
 
36
15
 
37
16
  class RedshiftProvider(RedshiftApi):
@@ -10,8 +10,6 @@ from localstack.aws.api.s3 import (
10
10
  )
11
11
  from localstack.aws.api.s3 import Type as GranteeType
12
12
 
13
- S3_VIRTUAL_HOST_FORWARDED_HEADER = "x-s3-vhost-forwarded-for"
14
-
15
13
  S3_UPLOAD_PART_MIN_SIZE = 5242880
16
14
  """
17
15
  This is minimum size allowed by S3 when uploading more than one part for a Multipart Upload, except for the last part
@@ -21,6 +19,11 @@ This is minimum size allowed by S3 when uploading more than one part for a Multi
21
19
  DEFAULT_PRE_SIGNED_ACCESS_KEY_ID = "test"
22
20
  DEFAULT_PRE_SIGNED_SECRET_ACCESS_KEY = "test"
23
21
 
22
+ S3_HOST_ID = "9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg="
23
+ """
24
+ S3 is returning a Host Id as part of its exceptions
25
+ """
26
+
24
27
  AUTHENTICATED_USERS_ACL_GROUP = "http://acs.amazonaws.com/groups/global/AuthenticatedUsers"
25
28
  ALL_USERS_ACL_GROUP = "http://acs.amazonaws.com/groups/global/AllUsers"
26
29
  LOG_DELIVERY_ACL_GROUP = "http://acs.amazonaws.com/groups/s3/LogDelivery"
@@ -21,13 +21,13 @@ from localstack.aws.protocol.op_router import RestServiceOperationRouter
21
21
  from localstack.aws.spec import get_service_catalog
22
22
  from localstack.config import S3_VIRTUAL_HOSTNAME
23
23
  from localstack.http import Request, Response
24
+ from localstack.services.s3.constants import S3_HOST_ID
24
25
  from localstack.services.s3.utils import S3_VIRTUAL_HOSTNAME_REGEX
25
26
 
26
27
  # TODO: add more logging statements
27
28
  LOG = logging.getLogger(__name__)
28
29
 
29
30
  _s3_virtual_host_regex = re.compile(S3_VIRTUAL_HOSTNAME_REGEX)
30
- FAKE_HOST_ID = "9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg="
31
31
 
32
32
  # TODO: refactor those to expose the needed methods maybe in another way that both can import
33
33
  add_default_headers = CorsResponseEnricher.add_cors_headers
@@ -135,7 +135,7 @@ class S3CorsHandler(Handler):
135
135
  if is_options_request:
136
136
  context.operation = self._get_op_from_request(request)
137
137
  raise BadRequest(
138
- "Insufficient information. Origin request header needed.", HostId=FAKE_HOST_ID
138
+ "Insufficient information. Origin request header needed.", HostId=S3_HOST_ID
139
139
  )
140
140
  else:
141
141
  # If the header is missing, Amazon S3 doesn't treat the request as a cross-origin request,
@@ -167,7 +167,7 @@ class S3CorsHandler(Handler):
167
167
  context.operation = self._get_op_from_request(request)
168
168
  raise AccessForbidden(
169
169
  message,
170
- HostId=FAKE_HOST_ID,
170
+ HostId=S3_HOST_ID,
171
171
  Method=request.headers.get("Access-Control-Request-Method", "OPTIONS"),
172
172
  ResourceType="BUCKET",
173
173
  )
@@ -182,7 +182,7 @@ class S3CorsHandler(Handler):
182
182
  context.operation = self._get_op_from_request(request)
183
183
  raise AccessForbidden(
184
184
  "CORSResponse: This CORS request is not allowed. This is usually because the evalution of Origin, request method / Access-Control-Request-Method or Access-Control-Request-Headers are not whitelisted by the resource's CORS spec.",
185
- HostId=FAKE_HOST_ID,
185
+ HostId=S3_HOST_ID,
186
186
  Method=request.headers.get("Access-Control-Request-Method"),
187
187
  ResourceType="OBJECT",
188
188
  )
@@ -638,7 +638,7 @@ class KeyStore:
638
638
 
639
639
  def values(self, *_, **__) -> list[S3Object | S3DeleteMarker]:
640
640
  # we create a shallow copy with dict to avoid size changed during iteration
641
- return [value for value in dict(self._store).values()]
641
+ return list(dict(self._store).values())
642
642
 
643
643
  def is_empty(self) -> bool:
644
644
  return not self._store
@@ -21,6 +21,7 @@ from localstack.aws.api.s3 import (
21
21
  Event,
22
22
  EventBridgeConfiguration,
23
23
  EventList,
24
+ InvalidArgument,
24
25
  LambdaFunctionArn,
25
26
  LambdaFunctionConfiguration,
26
27
  NotificationConfiguration,
@@ -34,8 +35,8 @@ from localstack.aws.api.s3 import (
34
35
  TopicConfiguration,
35
36
  )
36
37
  from localstack.aws.connect import connect_to
38
+ from localstack.services.s3.exceptions import MalformedXML
37
39
  from localstack.services.s3.models import S3Bucket, S3DeleteMarker, S3Object
38
- from localstack.services.s3.utils import _create_invalid_argument_exc
39
40
  from localstack.utils.aws import arns
40
41
  from localstack.utils.aws.arns import ARN_PARTITION_REGEX, get_partition, parse_arn, s3_bucket_arn
41
42
  from localstack.utils.aws.client_types import ServicePrincipal
@@ -104,7 +105,7 @@ class S3EventNotificationContext:
104
105
  key_storage_class: StorageClass | None
105
106
 
106
107
  @classmethod
107
- def from_request_context_native(
108
+ def from_request_context(
108
109
  cls,
109
110
  request_context: RequestContext,
110
111
  s3_bucket: S3Bucket,
@@ -272,26 +273,29 @@ class BaseNotifier:
272
273
  arn, argument_name = self._get_arn_value_and_name(configuration)
273
274
 
274
275
  if not re.match(f"{ARN_PARTITION_REGEX}:{self.service_name}:", arn):
275
- raise _create_invalid_argument_exc(
276
- "The ARN could not be parsed", name=argument_name, value=arn
276
+ raise InvalidArgument(
277
+ "The ARN could not be parsed",
278
+ ArgumentName=argument_name,
279
+ ArgumentValue=arn,
277
280
  )
281
+
278
282
  if not verification_ctx.skip_destination_validation:
279
283
  self._verify_target(arn, verification_ctx)
280
284
 
281
285
  if filter_rules := configuration.get("Filter", {}).get("Key", {}).get("FilterRules"):
282
286
  for rule in filter_rules:
283
- rule["Name"] = rule["Name"].capitalize()
284
- if rule["Name"] not in ["Suffix", "Prefix"]:
285
- raise _create_invalid_argument_exc(
287
+ if "Name" not in rule or "Value" not in rule:
288
+ raise MalformedXML()
289
+
290
+ if rule["Name"].lower() not in ["suffix", "prefix"]:
291
+ raise InvalidArgument(
286
292
  "filter rule name must be either prefix or suffix",
287
- rule["Name"],
288
- rule["Value"],
289
- )
290
- if not rule["Value"]:
291
- raise _create_invalid_argument_exc(
292
- "filter value cannot be empty", rule["Name"], rule["Value"]
293
+ ArgumentName="FilterRule.Name",
294
+ ArgumentValue=rule["Name"],
293
295
  )
294
296
 
297
+ rule["Name"] = rule["Name"].capitalize()
298
+
295
299
  @staticmethod
296
300
  def _get_test_payload(verification_ctx: BucketVerificationContext):
297
301
  return {
@@ -382,7 +386,7 @@ class SqsNotifier(BaseNotifier):
382
386
 
383
387
  @staticmethod
384
388
  def _get_arn_value_and_name(queue_configuration: QueueConfiguration) -> tuple[QueueArn, str]:
385
- return queue_configuration.get("QueueArn", ""), "QueueArn"
389
+ return queue_configuration.get("QueueArn", ""), "Queue"
386
390
 
387
391
  def _verify_target(self, target_arn: str, verification_ctx: BucketVerificationContext) -> None:
388
392
  if not is_api_enabled("sqs"):
@@ -402,13 +406,20 @@ class SqsNotifier(BaseNotifier):
402
406
  queue_url = sqs_client.get_queue_url(
403
407
  QueueName=arn_data["resource"], QueueOwnerAWSAccountId=arn_data["account"]
404
408
  )["QueueUrl"]
405
- except ClientError:
406
- LOG.exception("Could not validate the notification destination %s", target_arn)
407
- raise _create_invalid_argument_exc(
409
+ except ClientError as e:
410
+ code = e.response["Error"]["Code"]
411
+ LOG.error(
412
+ "Could not validate the notification destination %s: %s",
413
+ target_arn,
414
+ code,
415
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
416
+ )
417
+ raise InvalidArgument(
408
418
  "Unable to validate the following destination configurations",
409
- name=target_arn,
410
- value="The destination queue does not exist",
419
+ ArgumentName=target_arn,
420
+ ArgumentValue="The destination queue does not exist",
411
421
  )
422
+
412
423
  # send test event with the request metadata for permissions
413
424
  # https://docs.aws.amazon.com/AmazonS3/latest/userguide/notification-how-to-event-types-and-destinations.html#supported-notification-event-types
414
425
  sqs_client = connect_to(region_name=arn_data["region"]).sqs.request_metadata(
@@ -424,10 +435,10 @@ class SqsNotifier(BaseNotifier):
424
435
  verification_ctx.bucket_name,
425
436
  target_arn,
426
437
  )
427
- raise _create_invalid_argument_exc(
438
+ raise InvalidArgument(
428
439
  "Unable to validate the following destination configurations",
429
- name=target_arn,
430
- value="Permissions on the destination queue do not allow S3 to publish notifications from this bucket",
440
+ ArgumentName=target_arn,
441
+ ArgumentValue="Permissions on the destination queue do not allow S3 to publish notifications from this bucket",
431
442
  ) from e
432
443
 
433
444
  def notify(self, ctx: S3EventNotificationContext, config: QueueConfiguration):
@@ -454,10 +465,11 @@ class SqsNotifier(BaseNotifier):
454
465
  MessageSystemAttributes=system_attributes,
455
466
  )
456
467
  except Exception:
457
- LOG.exception(
468
+ LOG.error(
458
469
  'Unable to send notification for S3 bucket "%s" to SQS queue "%s"',
459
470
  ctx.bucket_name,
460
471
  parsed_arn["resource"],
472
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
461
473
  )
462
474
 
463
475
 
@@ -483,10 +495,10 @@ class SnsNotifier(BaseNotifier):
483
495
  try:
484
496
  sns_client.get_topic_attributes(TopicArn=target_arn)
485
497
  except ClientError:
486
- raise _create_invalid_argument_exc(
498
+ raise InvalidArgument(
487
499
  "Unable to validate the following destination configurations",
488
- name=target_arn,
489
- value="The destination topic does not exist",
500
+ ArgumentName=target_arn,
501
+ ArgumentValue="The destination topic does not exist",
490
502
  )
491
503
 
492
504
  sns_client = connect_to(region_name=arn_data["region"]).sns.request_metadata(
@@ -506,10 +518,10 @@ class SnsNotifier(BaseNotifier):
506
518
  verification_ctx.bucket_name,
507
519
  target_arn,
508
520
  )
509
- raise _create_invalid_argument_exc(
521
+ raise InvalidArgument(
510
522
  "Unable to validate the following destination configurations",
511
- name=target_arn,
512
- value="Permissions on the destination topic do not allow S3 to publish notifications from this bucket",
523
+ ArgumentName=target_arn,
524
+ ArgumentValue="Permissions on the destination topic do not allow S3 to publish notifications from this bucket",
513
525
  ) from e
514
526
 
515
527
  def notify(self, ctx: S3EventNotificationContext, config: TopicConfiguration):
@@ -536,10 +548,11 @@ class SnsNotifier(BaseNotifier):
536
548
  Subject="Amazon S3 Notification",
537
549
  )
538
550
  except Exception:
539
- LOG.exception(
551
+ LOG.error(
540
552
  'Unable to send notification for S3 bucket "%s" to SNS topic "%s"',
541
553
  ctx.bucket_name,
542
554
  topic_arn,
555
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
543
556
  )
544
557
 
545
558
 
@@ -567,10 +580,10 @@ class LambdaNotifier(BaseNotifier):
567
580
  try:
568
581
  lambda_client.get_function(FunctionName=target_arn)
569
582
  except ClientError:
570
- raise _create_invalid_argument_exc(
583
+ raise InvalidArgument(
571
584
  "Unable to validate the following destination configurations",
572
- name=target_arn,
573
- value="The destination Lambda does not exist",
585
+ ArgumentName=target_arn,
586
+ ArgumentValue="The destination Lambda does not exist",
574
587
  )
575
588
  lambda_client = connect_to(region_name=arn_data["region"]).lambda_.request_metadata(
576
589
  source_arn=s3_bucket_arn(verification_ctx.bucket_name, region=verification_ctx.region),
@@ -579,10 +592,10 @@ class LambdaNotifier(BaseNotifier):
579
592
  try:
580
593
  lambda_client.invoke(FunctionName=target_arn, InvocationType=InvocationType.DryRun)
581
594
  except ClientError as e:
582
- raise _create_invalid_argument_exc(
595
+ raise InvalidArgument(
583
596
  "Unable to validate the following destination configurations",
584
- name=f"{target_arn}, null",
585
- value=f"Not authorized to invoke function [{target_arn}]",
597
+ ArgumentName=f"{target_arn}, null",
598
+ ArgumentValue=f"Not authorized to invoke function [{target_arn}]",
586
599
  ) from e
587
600
 
588
601
  def notify(self, ctx: S3EventNotificationContext, config: LambdaFunctionConfiguration):
@@ -604,10 +617,11 @@ class LambdaNotifier(BaseNotifier):
604
617
  Payload=payload,
605
618
  )
606
619
  except Exception:
607
- LOG.exception(
620
+ LOG.error(
608
621
  'Unable to send notification for S3 bucket "%s" to Lambda function "%s".',
609
622
  ctx.bucket_name,
610
623
  lambda_arn,
624
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
611
625
  )
612
626
 
613
627
 
@@ -729,8 +743,10 @@ class EventBridgeNotifier(BaseNotifier):
729
743
  try:
730
744
  events_client.put_events(Entries=[entry])
731
745
  except Exception:
732
- LOG.exception(
733
- 'Unable to send notification for S3 bucket "%s" to EventBridge', ctx.bucket_name
746
+ LOG.error(
747
+ 'Unable to send notification for S3 bucket "%s" to EventBridge',
748
+ ctx.bucket_name,
749
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
734
750
  )
735
751
 
736
752