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
@@ -4,7 +4,7 @@ from datetime import UTC, datetime
4
4
  from enum import Enum
5
5
  from typing import Literal, TypeAlias, TypedDict
6
6
 
7
- from localstack.aws.api.core import ServiceException
7
+ from localstack.aws.api import CommonServiceException
8
8
  from localstack.aws.api.events import (
9
9
  ApiDestinationDescription,
10
10
  ApiDestinationHttpMethod,
@@ -66,10 +66,9 @@ from localstack.utils.tagging import TaggingService
66
66
  TargetDict = dict[TargetId, Target]
67
67
 
68
68
 
69
- class ValidationException(ServiceException):
70
- code: str = "ValidationException"
71
- sender_fault: bool = True
72
- status_code: int = 400
69
+ class ValidationException(CommonServiceException):
70
+ def __init__(self, message: str):
71
+ super().__init__("ValidationException", message, 400, True)
73
72
 
74
73
 
75
74
  class InvalidEventPatternException(Exception):
@@ -193,7 +193,7 @@ def encode_next_token(token: int) -> NextToken:
193
193
 
194
194
  def get_filtered_dict(name_prefix: str, input_dict: dict) -> dict:
195
195
  """Filter dictionary by prefix."""
196
- return {name: value for name, value in input_dict.items() if name.startswith(name_prefix)}
196
+ return {name: value for name, value in dict(input_dict).items() if name.startswith(name_prefix)}
197
197
 
198
198
 
199
199
  def validate_event(event: PutEventsRequestEntry) -> None | PutEventsResultEntry:
@@ -768,8 +768,8 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
768
768
 
769
769
  # Find all rules that have a target with the specified ARN
770
770
  matching_rule_names = []
771
- for rule_name, rule in event_bus.rules.items():
772
- for target_id, target in rule.targets.items():
771
+ for rule_name, rule in dict(event_bus.rules).items():
772
+ for target_id, target in dict(rule.targets).items():
773
773
  if target["Arn"] == target_arn:
774
774
  matching_rule_names.append(rule_name)
775
775
  break # Found a match in this rule, no need to check other targets
@@ -1028,7 +1028,7 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1028
1028
  self._check_event_bus_exists(event_source_arn, store)
1029
1029
  archives = {
1030
1030
  key: archive
1031
- for key, archive in store.archives.items()
1031
+ for key, archive in dict(store.archives).items()
1032
1032
  if archive.event_source_arn == event_source_arn
1033
1033
  }
1034
1034
  elif name_prefix:
@@ -1161,7 +1161,7 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1161
1161
  if event_source_arn:
1162
1162
  replays = {
1163
1163
  key: replay
1164
- for key, replay in store.replays.items()
1164
+ for key, replay in dict(store.replays).items()
1165
1165
  if replay.event_source_arn == event_source_arn
1166
1166
  }
1167
1167
  elif name_prefix:
@@ -1508,7 +1508,7 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1508
1508
  """
1509
1509
  if isinstance(rules, Rule):
1510
1510
  rules = {rules.name: rules}
1511
- for rule in rules.values():
1511
+ for rule in list(rules.values()):
1512
1512
  del self._rule_services_store[rule.arn]
1513
1513
 
1514
1514
  def _delete_target_sender(self, ids: TargetIdList, rule) -> None:
@@ -1571,7 +1571,7 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1571
1571
  def _get_scheduled_rule_job_function(self, account_id, region, rule: Rule) -> Callable:
1572
1572
  def func(*args, **kwargs):
1573
1573
  """Create custom scheduled event and send it to all targets specified by associated rule using respective TargetSender"""
1574
- for target in rule.targets.values():
1574
+ for target in list(rule.targets.values()):
1575
1575
  if custom_input := target.get("Input"):
1576
1576
  event = json.loads(custom_input)
1577
1577
  else:
@@ -1636,7 +1636,8 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1636
1636
  ) -> EventBusList:
1637
1637
  """Return a converted dict of EventBus model objects as a list of event buses in API type EventBus format."""
1638
1638
  event_bus_list = [
1639
- self._event_bus_to_api_type_event_bus(event_bus) for event_bus in event_buses.values()
1639
+ self._event_bus_to_api_type_event_bus(event_bus)
1640
+ for event_bus in list(event_buses.values())
1640
1641
  ]
1641
1642
  return event_bus_list
1642
1643
 
@@ -1680,7 +1681,7 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1680
1681
 
1681
1682
  def _rule_dict_to_rule_response_list(self, rules: RuleDict) -> RuleResponseList:
1682
1683
  """Return a converted dict of Rule model objects as a list of rules in API type Rule format."""
1683
- rule_list = [self._rule_to_api_type_rule(rule) for rule in rules.values()]
1684
+ rule_list = [self._rule_to_api_type_rule(rule) for rule in list(rules.values())]
1684
1685
  return rule_list
1685
1686
 
1686
1687
  def _rule_to_api_type_rule(self, rule: Rule) -> ApiTypeRule:
@@ -1700,7 +1701,9 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1700
1701
 
1701
1702
  def _archive_dict_to_archive_response_list(self, archives: ArchiveDict) -> ArchiveResponseList:
1702
1703
  """Return a converted dict of Archive model objects as a list of archives in API type Archive format."""
1703
- archive_list = [self._archive_to_api_type_archive(archive) for archive in archives.values()]
1704
+ archive_list = [
1705
+ self._archive_to_api_type_archive(archive) for archive in list(archives.values())
1706
+ ]
1704
1707
  return archive_list
1705
1708
 
1706
1709
  def _archive_to_api_type_archive(self, archive: Archive) -> ApiTypeArchive:
@@ -1734,7 +1737,7 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1734
1737
 
1735
1738
  def _replay_dict_to_replay_response_list(self, replays: ReplayDict) -> ReplayList:
1736
1739
  """Return a converted dict of Replay model objects as a list of replays in API type Replay format."""
1737
- replay_list = [self._replay_to_api_type_replay(replay) for replay in replays.values()]
1740
+ replay_list = [self._replay_to_api_type_replay(replay) for replay in list(replays.values())]
1738
1741
  return replay_list
1739
1742
 
1740
1743
  def _replay_to_api_type_replay(self, replay: Replay) -> ApiTypeReplay:
@@ -1789,7 +1792,7 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1789
1792
  """Return a converted dict of Connection model objects as a list of connections in API type Connection format."""
1790
1793
  connection_list = [
1791
1794
  self._connection_to_api_type_connection(connection)
1792
- for connection in connections.values()
1795
+ for connection in list(connections.values())
1793
1796
  ]
1794
1797
  return connection_list
1795
1798
 
@@ -1816,7 +1819,7 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1816
1819
  """Return a converted dict of ApiDestination model objects as a list of connections in API type ApiDestination format."""
1817
1820
  api_destination_list = [
1818
1821
  self._api_destination_to_api_type_api_destination(api_destination)
1819
- for api_destination in api_destinations.values()
1822
+ for api_destination in list(api_destinations.values())
1820
1823
  ]
1821
1824
  return api_destination_list
1822
1825
 
@@ -1942,7 +1945,7 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
1942
1945
  )
1943
1946
  return
1944
1947
 
1945
- for target in rule.targets.values():
1948
+ for target in list(rule.targets.values()):
1946
1949
  target_id = target["Id"]
1947
1950
  if is_archive_arn(target["Arn"]):
1948
1951
  self._put_to_archive(
@@ -90,11 +90,20 @@ def get_template_replacements(
90
90
  return template_replacements
91
91
 
92
92
 
93
- def replace_template_placeholders(
94
- template: str, replacements: dict[str, Any], is_json_template: bool
95
- ) -> TransformedEvent:
96
- """Replace placeholders defined by <key> in the template with the values from the replacements dict.
97
- Can handle single template string or template dict."""
93
+ def replace_template_placeholders(template: str, replacements: dict[str, Any]) -> TransformedEvent:
94
+ """
95
+ Replaces placeholders in an EventBridge-style InputTemplate string.
96
+
97
+ :param template: The template string containing placeholders like ``<$.foo.bar>``.
98
+ :type template: str
99
+ :param replacements: A dictionary providing values to fill in.
100
+ :type replacements: dict
101
+ :returns: The transformed string with placeholders replaced by values from ``replacements``.
102
+ :rtype: str
103
+ """
104
+
105
+ ...
106
+ is_json_template = template.strip().startswith("{")
98
107
 
99
108
  def replace_placeholder(match):
100
109
  key = match.group(1)
@@ -110,6 +119,8 @@ def replace_template_placeholders(
110
119
  if is_json_template:
111
120
  return json.dumps(value)
112
121
  return f"[{','.join(value)}]"
122
+ if isinstance(value, bool):
123
+ return json.dumps(value)
113
124
  if is_nested_in_string(template, match):
114
125
  return value
115
126
  if is_json_template:
@@ -222,10 +233,7 @@ class TargetSender(ABC):
222
233
  predefined_template_replacements = self._get_predefined_template_replacements(event)
223
234
  template_replacements.update(predefined_template_replacements)
224
235
 
225
- is_json_template = input_template.strip().startswith("{")
226
- populated_template = replace_template_placeholders(
227
- input_template, template_replacements, is_json_template
228
- )
236
+ populated_template = replace_template_placeholders(input_template, template_replacements)
229
237
 
230
238
  return populated_template
231
239
 
@@ -206,12 +206,12 @@ class EventsProvider(EventsApi, ServiceLifecycleHook):
206
206
  raise ValueError("If the value is greater than 1, the unit must be plural")
207
207
 
208
208
  if "minute" in unit:
209
- return "*/%s * * * *" % value
209
+ return f"*/{value} * * * *"
210
210
  if "hour" in unit:
211
- return "0 */%s * * *" % value
211
+ return f"0 */{value} * * *"
212
212
  if "day" in unit:
213
- return "0 0 */%s * *" % value
214
- raise ValueError("Unable to parse events schedule expression: %s" % schedule)
213
+ return f"0 0 */{value} * *"
214
+ raise ValueError(f"Unable to parse events schedule expression: {schedule}")
215
215
  return schedule
216
216
 
217
217
  @staticmethod
@@ -374,7 +374,7 @@ def _dump_events_to_files(events_with_added_uuid):
374
374
  for event in events_with_added_uuid:
375
375
  target = os.path.join(
376
376
  _get_events_tmp_dir(),
377
- "%s_%s" % (current_time_millis, event["uuid"]),
377
+ "{}_{}".format(current_time_millis, event["uuid"]),
378
378
  )
379
379
  save_file(target, json.dumps(event["event"]))
380
380
  except Exception as e:
@@ -706,7 +706,11 @@ class FirehoseProvider(FirehoseApi):
706
706
  try:
707
707
  requests.post(url, json=record_to_send, headers=headers)
708
708
  except Exception as e:
709
- LOG.exception("Unable to put Firehose records to HTTP endpoint %s.", url)
709
+ LOG.error(
710
+ "Unable to put Firehose records to HTTP endpoint %s.",
711
+ url,
712
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
713
+ )
710
714
  raise e
711
715
  if "RedshiftDestinationDescription" in destination:
712
716
  s3_dest_desc = destination["RedshiftDestinationDescription"][
@@ -782,7 +786,11 @@ class FirehoseProvider(FirehoseApi):
782
786
  try:
783
787
  db_connection.create(index=search_db_index, id=obj_id, body=body)
784
788
  except Exception as e:
785
- LOG.exception("Unable to put record to stream %s.", delivery_stream_name)
789
+ LOG.error(
790
+ "Unable to put record to stream %s.",
791
+ delivery_stream_name,
792
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
793
+ )
786
794
  raise e
787
795
 
788
796
  def _add_missing_record_attributes(self, records: list[dict]) -> None:
@@ -860,9 +868,10 @@ class FirehoseProvider(FirehoseApi):
860
868
  LOG.debug("Publishing to S3 destination: %s. Data: %s", bucket, batched_data)
861
869
  s3.put_object(Bucket=bucket, Key=obj_path, Body=batched_data)
862
870
  except Exception as e:
863
- LOG.exception(
871
+ LOG.error(
864
872
  "Unable to put records %s to s3 bucket.",
865
873
  records,
874
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
866
875
  )
867
876
  raise e
868
877
 
@@ -917,9 +926,10 @@ class FirehoseProvider(FirehoseApi):
917
926
  )
918
927
  redshift_data.execute_statement(Parameters=row_to_insert, **execute_statement)
919
928
  except Exception as e:
920
- LOG.exception(
929
+ LOG.error(
921
930
  "Unable to put records %s to redshift cluster.",
922
931
  row_to_insert,
932
+ exc_info=LOG.isEnabledFor(logging.DEBUG),
923
933
  )
924
934
  raise e
925
935
 
@@ -20,10 +20,7 @@ from moto.iam.utils import generate_access_key_id_from_account_id
20
20
 
21
21
  from localstack.aws.api import CommonServiceException, RequestContext, handler
22
22
  from localstack.aws.api.iam import (
23
- ActionNameListType,
24
- ActionNameType,
25
23
  AttachedPermissionsBoundary,
26
- ContextEntryListType,
27
24
  CreateRoleRequest,
28
25
  CreateRoleResponse,
29
26
  CreateServiceLinkedRoleResponse,
@@ -33,7 +30,6 @@ from localstack.aws.api.iam import (
33
30
  DeleteServiceLinkedRoleResponse,
34
31
  DeletionTaskIdType,
35
32
  DeletionTaskStatusType,
36
- EvaluationResult,
37
33
  GetServiceLinkedRoleDeletionStatusResponse,
38
34
  GetUserResponse,
39
35
  IamApi,
@@ -43,16 +39,12 @@ from localstack.aws.api.iam import (
43
39
  ListServiceSpecificCredentialsResponse,
44
40
  MalformedPolicyDocumentException,
45
41
  NoSuchEntityException,
46
- PolicyEvaluationDecisionType,
47
42
  ResetServiceSpecificCredentialResponse,
48
- ResourceHandlingOptionType,
49
- ResourceNameListType,
50
- ResourceNameType,
51
43
  Role,
52
44
  ServiceSpecificCredential,
53
45
  ServiceSpecificCredentialMetadata,
54
46
  SimulatePolicyResponse,
55
- SimulationPolicyListType,
47
+ SimulatePrincipalPolicyRequest,
56
48
  User,
57
49
  allUsers,
58
50
  arnType,
@@ -65,7 +57,6 @@ from localstack.aws.api.iam import (
65
57
  maxItemsType,
66
58
  pathPrefixType,
67
59
  pathType,
68
- policyDocumentType,
69
60
  roleDescriptionType,
70
61
  roleNameType,
71
62
  serviceName,
@@ -78,6 +69,10 @@ from localstack.aws.api.iam import (
78
69
  from localstack.aws.connect import connect_to
79
70
  from localstack.constants import INTERNAL_AWS_SECRET_ACCESS_KEY
80
71
  from localstack.services.iam.iam_patches import apply_iam_patches
72
+ from localstack.services.iam.resources.policy_simulator import (
73
+ BasicIAMPolicySimulator,
74
+ IAMPolicySimulator,
75
+ )
81
76
  from localstack.services.iam.resources.service_linked_roles import SERVICE_LINKED_ROLES
82
77
  from localstack.services.moto import call_moto
83
78
  from localstack.utils.aws.request_context import extract_access_key_id_from_auth_header
@@ -108,58 +103,12 @@ def get_iam_backend(context: RequestContext) -> IAMBackend:
108
103
  return iam_backends[context.account_id][context.partition]
109
104
 
110
105
 
111
- def get_policies_from_principal(backend: IAMBackend, principal_arn: str) -> list[dict]:
112
- policies = []
113
- if ":role" in principal_arn:
114
- role_name = principal_arn.split("/")[-1]
115
-
116
- policies.append(backend.get_role(role_name=role_name).assume_role_policy_document)
117
-
118
- policy_names = backend.list_role_policies(role_name=role_name)
119
- policies.extend(
120
- [
121
- backend.get_role_policy(role_name=role_name, policy_name=policy_name)[1]
122
- for policy_name in policy_names
123
- ]
124
- )
125
-
126
- attached_policies, _ = backend.list_attached_role_policies(role_name=role_name)
127
- policies.extend([policy.document for policy in attached_policies])
128
-
129
- if ":group" in principal_arn:
130
- print(principal_arn)
131
- group_name = principal_arn.split("/")[-1]
132
- policy_names = backend.list_group_policies(group_name=group_name)
133
- policies.extend(
134
- [
135
- backend.get_group_policy(group_name=group_name, policy_name=policy_name)[1]
136
- for policy_name in policy_names
137
- ]
138
- )
139
-
140
- attached_policies, _ = backend.list_attached_group_policies(group_name=group_name)
141
- policies.extend([policy.document for policy in attached_policies])
142
-
143
- if ":user" in principal_arn:
144
- print(principal_arn)
145
- user_name = principal_arn.split("/")[-1]
146
- policy_names = backend.list_user_policies(user_name=user_name)
147
- policies.extend(
148
- [
149
- backend.get_user_policy(user_name=user_name, policy_name=policy_name)[1]
150
- for policy_name in policy_names
151
- ]
152
- )
153
-
154
- attached_policies, _ = backend.list_attached_user_policies(user_name=user_name)
155
- policies.extend([policy.document for policy in attached_policies])
156
-
157
- return policies
158
-
159
-
160
106
  class IamProvider(IamApi):
107
+ policy_simulator: IAMPolicySimulator
108
+
161
109
  def __init__(self):
162
110
  apply_iam_patches()
111
+ self.policy_simulator = BasicIAMPolicySimulator()
163
112
 
164
113
  @handler("CreateRole", expand=False)
165
114
  def create_role(
@@ -181,68 +130,14 @@ class IamProvider(IamApi):
181
130
 
182
131
  return result
183
132
 
184
- @staticmethod
185
- def build_evaluation_result(
186
- action_name: ActionNameType, resource_name: ResourceNameType, policy_statements: list[dict]
187
- ) -> EvaluationResult:
188
- eval_res = EvaluationResult()
189
- eval_res["EvalActionName"] = action_name
190
- eval_res["EvalResourceName"] = resource_name
191
- eval_res["EvalDecision"] = PolicyEvaluationDecisionType.explicitDeny
192
- for statement in policy_statements:
193
- # TODO Implement evaluation logic here
194
- if (
195
- action_name in statement["Action"]
196
- and resource_name in statement["Resource"]
197
- and statement["Effect"] == "Allow"
198
- ):
199
- eval_res["EvalDecision"] = PolicyEvaluationDecisionType.allowed
200
- eval_res["MatchedStatements"] = [] # TODO: add support for statement compilation.
201
- return eval_res
202
-
133
+ @handler("SimulatePrincipalPolicy", expand=False)
203
134
  def simulate_principal_policy(
204
135
  self,
205
136
  context: RequestContext,
206
- policy_source_arn: arnType,
207
- action_names: ActionNameListType,
208
- policy_input_list: SimulationPolicyListType = None,
209
- permissions_boundary_policy_input_list: SimulationPolicyListType = None,
210
- resource_arns: ResourceNameListType = None,
211
- resource_policy: policyDocumentType = None,
212
- resource_owner: ResourceNameType = None,
213
- caller_arn: ResourceNameType = None,
214
- context_entries: ContextEntryListType = None,
215
- resource_handling_option: ResourceHandlingOptionType = None,
216
- max_items: maxItemsType = None,
217
- marker: markerType = None,
137
+ request: SimulatePrincipalPolicyRequest,
218
138
  **kwargs,
219
139
  ) -> SimulatePolicyResponse:
220
- backend = get_iam_backend(context)
221
-
222
- policies = get_policies_from_principal(backend, policy_source_arn)
223
-
224
- def _get_statements_from_policy_list(policies: list[str]):
225
- statements = []
226
- for policy_str in policies:
227
- policy_dict = json.loads(policy_str)
228
- if isinstance(policy_dict["Statement"], list):
229
- statements.extend(policy_dict["Statement"])
230
- else:
231
- statements.append(policy_dict["Statement"])
232
- return statements
233
-
234
- policy_statements = _get_statements_from_policy_list(policies)
235
-
236
- evaluations = [
237
- self.build_evaluation_result(action_name, resource_arn, policy_statements)
238
- for action_name in action_names
239
- for resource_arn in resource_arns
240
- ]
241
-
242
- response = SimulatePolicyResponse()
243
- response["IsTruncated"] = False
244
- response["EvaluationResults"] = evaluations
245
- return response
140
+ return self.policy_simulator.simulate_principal_policy(context, request)
246
141
 
247
142
  def delete_policy(self, context: RequestContext, policy_arn: arnType, **kwargs) -> None:
248
143
  backend = get_iam_backend(context)
@@ -0,0 +1,133 @@
1
+ import abc
2
+ import json
3
+
4
+ from moto.iam import iam_backends
5
+ from moto.iam.models import IAMBackend
6
+
7
+ from localstack.aws.api import RequestContext
8
+ from localstack.aws.api.iam import (
9
+ ActionNameType,
10
+ EvaluationResult,
11
+ PolicyEvaluationDecisionType,
12
+ ResourceNameType,
13
+ SimulatePolicyResponse,
14
+ SimulatePrincipalPolicyRequest,
15
+ )
16
+
17
+
18
+ class IAMPolicySimulator(abc.ABC):
19
+ @abc.abstractmethod
20
+ def simulate_principal_policy(
21
+ self, context: RequestContext, request: SimulatePrincipalPolicyRequest
22
+ ) -> SimulatePolicyResponse:
23
+ """
24
+ Simulate principal policy
25
+ :param request: SimulatePrincipalPolicyRequest
26
+ :param context: RequestContext
27
+ :return: SimulatePrincipalResponse
28
+ """
29
+ pass
30
+
31
+
32
+ class BasicIAMPolicySimulator(IAMPolicySimulator):
33
+ def simulate_principal_policy(
34
+ self,
35
+ context: RequestContext,
36
+ request: SimulatePrincipalPolicyRequest,
37
+ ) -> SimulatePolicyResponse:
38
+ backend = self.get_iam_backend(context)
39
+ policies = self.get_policies_from_principal(backend, request.get("PolicySourceArn"))
40
+
41
+ def _get_statements_from_policy_list(_policies: list[str]):
42
+ statements = []
43
+ for policy_str in _policies:
44
+ policy_dict = json.loads(policy_str)
45
+ if isinstance(policy_dict["Statement"], list):
46
+ statements.extend(policy_dict["Statement"])
47
+ else:
48
+ statements.append(policy_dict["Statement"])
49
+ return statements
50
+
51
+ policy_statements = _get_statements_from_policy_list(policies)
52
+
53
+ evaluations = [
54
+ self.build_evaluation_result(action_name, resource_arn, policy_statements)
55
+ for action_name in request.get("ActionNames")
56
+ for resource_arn in request.get("ResourceArns")
57
+ ]
58
+
59
+ response = SimulatePolicyResponse()
60
+ response["IsTruncated"] = False
61
+ response["EvaluationResults"] = evaluations
62
+
63
+ return response
64
+
65
+ @staticmethod
66
+ def build_evaluation_result(
67
+ action_name: ActionNameType, resource_name: ResourceNameType, policy_statements: list[dict]
68
+ ) -> EvaluationResult:
69
+ eval_res = EvaluationResult()
70
+ eval_res["EvalActionName"] = action_name
71
+ eval_res["EvalResourceName"] = resource_name
72
+ eval_res["EvalDecision"] = PolicyEvaluationDecisionType.explicitDeny
73
+ for statement in policy_statements:
74
+ # TODO Implement evaluation logic here
75
+ if (
76
+ action_name in statement["Action"]
77
+ and resource_name in statement["Resource"]
78
+ and statement["Effect"] == "Allow"
79
+ ):
80
+ eval_res["EvalDecision"] = PolicyEvaluationDecisionType.allowed
81
+ eval_res["MatchedStatements"] = [] # TODO: add support for statement compilation.
82
+ return eval_res
83
+
84
+ @staticmethod
85
+ def get_iam_backend(context: RequestContext) -> IAMBackend:
86
+ return iam_backends[context.account_id][context.partition]
87
+
88
+ @staticmethod
89
+ def get_policies_from_principal(backend: IAMBackend, principal_arn: str) -> list[dict]:
90
+ policies = []
91
+ if ":role" in principal_arn:
92
+ role_name = principal_arn.split("/")[-1]
93
+
94
+ policies.append(backend.get_role(role_name=role_name).assume_role_policy_document)
95
+
96
+ policy_names = backend.list_role_policies(role_name=role_name)
97
+ policies.extend(
98
+ [
99
+ backend.get_role_policy(role_name=role_name, policy_name=policy_name)[1]
100
+ for policy_name in policy_names
101
+ ]
102
+ )
103
+
104
+ attached_policies, _ = backend.list_attached_role_policies(role_name=role_name)
105
+ policies.extend([policy.document for policy in attached_policies])
106
+
107
+ if ":group" in principal_arn:
108
+ group_name = principal_arn.split("/")[-1]
109
+ policy_names = backend.list_group_policies(group_name=group_name)
110
+ policies.extend(
111
+ [
112
+ backend.get_group_policy(group_name=group_name, policy_name=policy_name)[1]
113
+ for policy_name in policy_names
114
+ ]
115
+ )
116
+
117
+ attached_policies, _ = backend.list_attached_group_policies(group_name=group_name)
118
+ policies.extend([policy.document for policy in attached_policies])
119
+
120
+ if ":user" in principal_arn:
121
+ user_name = principal_arn.split("/")[-1]
122
+ policy_names = backend.list_user_policies(user_name=user_name)
123
+ policies.extend(
124
+ [
125
+ backend.get_user_policy(user_name=user_name, policy_name=policy_name)[1]
126
+ for policy_name in policy_names
127
+ ]
128
+ )
129
+
130
+ attached_policies, _ = backend.list_attached_user_policies(user_name=user_name)
131
+ policies.extend([policy.document for policy in attached_policies])
132
+
133
+ return policies
@@ -1,7 +1,18 @@
1
1
  from collections import defaultdict
2
2
 
3
- from localstack.aws.api.kinesis import ConsumerDescription, MetricsName, StreamName
4
- from localstack.services.stores import AccountRegionBundle, BaseStore, LocalAttribute
3
+ from localstack.aws.api.kinesis import (
4
+ ConsumerDescription,
5
+ MetricsName,
6
+ Policy,
7
+ ResourceARN,
8
+ StreamName,
9
+ )
10
+ from localstack.services.stores import (
11
+ AccountRegionBundle,
12
+ BaseStore,
13
+ CrossAccountAttribute,
14
+ LocalAttribute,
15
+ )
5
16
 
6
17
 
7
18
  class KinesisStore(BaseStore):
@@ -13,5 +24,7 @@ class KinesisStore(BaseStore):
13
24
  default=lambda: defaultdict(set)
14
25
  )
15
26
 
27
+ resource_policies: dict[ResourceARN, Policy] = CrossAccountAttribute(default=dict)
28
+
16
29
 
17
30
  kinesis_stores = AccountRegionBundle("kinesis", KinesisStore)