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,9 +1,12 @@
1
1
  import copy
2
2
  import logging
3
+ import os
4
+ import re
3
5
  import uuid
6
+ from collections.abc import Callable
4
7
  from dataclasses import dataclass
5
8
  from datetime import UTC, datetime
6
- from typing import Final, Protocol
9
+ from typing import Final, Protocol, TypeVar
7
10
 
8
11
  from localstack import config
9
12
  from localstack.aws.api.cloudformation import (
@@ -15,17 +18,14 @@ from localstack.aws.api.cloudformation import (
15
18
  from localstack.constants import INTERNAL_AWS_SECRET_ACCESS_KEY
16
19
  from localstack.services.cloudformation.analytics import track_resource_operation
17
20
  from localstack.services.cloudformation.deployment_utils import log_not_available_message
18
- from localstack.services.cloudformation.engine.template_deployer import REGEX_OUTPUT_APIGATEWAY
19
21
  from localstack.services.cloudformation.engine.v2.change_set_model import (
20
22
  NodeDependsOn,
21
23
  NodeOutput,
22
24
  NodeResource,
23
- TerminalValueCreated,
24
- TerminalValueModified,
25
- TerminalValueUnchanged,
26
25
  is_nothing,
27
26
  )
28
27
  from localstack.services.cloudformation.engine.v2.change_set_model_preproc import (
28
+ _AWS_URL_SUFFIX,
29
29
  MOCKED_REFERENCE,
30
30
  ChangeSetModelPreproc,
31
31
  PreprocEntityDelta,
@@ -41,28 +41,50 @@ from localstack.services.cloudformation.resource_provider import (
41
41
  ResourceProviderPayload,
42
42
  )
43
43
  from localstack.services.cloudformation.v2.entities import ChangeSet, ResolvedResource
44
- from localstack.utils.urls import localstack_host
45
44
 
46
45
  LOG = logging.getLogger(__name__)
47
46
 
48
47
  EventOperationFromAction = {"Add": "CREATE", "Modify": "UPDATE", "Remove": "DELETE"}
49
48
 
49
+ REGEX_OUTPUT_APIGATEWAY = re.compile(
50
+ rf"^(https?://.+\.execute-api\.)(?:[^-]+-){{2,3}}\d\.(amazonaws\.com|{_AWS_URL_SUFFIX})/?(.*)$"
51
+ )
52
+
53
+ _T = TypeVar("_T")
54
+
50
55
 
51
56
  @dataclass
52
57
  class ChangeSetModelExecutorResult:
53
58
  resources: dict[str, ResolvedResource]
54
59
  outputs: list[Output]
60
+ failure_message: str | None = None
55
61
 
56
62
 
57
63
  class DeferredAction(Protocol):
58
64
  def __call__(self) -> None: ...
59
65
 
60
66
 
67
+ @dataclass
68
+ class Deferred:
69
+ name: str
70
+ action: DeferredAction
71
+
72
+
73
+ class TriggerRollback(Exception):
74
+ """
75
+ Sentinel exception to signal that the deployment should be stopped for a reason
76
+ """
77
+
78
+ def __init__(self, logical_resource_id: str, reason: str | None):
79
+ self.logical_resource_id = logical_resource_id
80
+ self.reason = reason
81
+
82
+
61
83
  class ChangeSetModelExecutor(ChangeSetModelPreproc):
62
84
  # TODO: add typing for resolved resources and parameters.
63
85
  resources: Final[dict[str, ResolvedResource]]
64
86
  outputs: Final[list[Output]]
65
- _deferred_actions: list[DeferredAction]
87
+ _deferred_actions: list[Deferred]
66
88
 
67
89
  def __init__(self, change_set: ChangeSet):
68
90
  super().__init__(change_set=change_set)
@@ -76,24 +98,42 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
76
98
 
77
99
  def execute(self) -> ChangeSetModelExecutorResult:
78
100
  # constructive process
79
- self.process()
101
+ failure_message = None
102
+ try:
103
+ self.process()
104
+ except TriggerRollback as e:
105
+ failure_message = e.reason
106
+ except Exception as e:
107
+ failure_message = str(e)
80
108
 
109
+ is_deletion = self._change_set.stack.status == StackStatus.DELETE_IN_PROGRESS
81
110
  if self._deferred_actions:
82
- self._change_set.stack.set_stack_status(StackStatus.UPDATE_COMPLETE_CLEANUP_IN_PROGRESS)
111
+ if not is_deletion:
112
+ # TODO: correct status
113
+ # TODO: differentiate between update and create
114
+ self._change_set.stack.set_stack_status(
115
+ StackStatus.ROLLBACK_IN_PROGRESS
116
+ if failure_message
117
+ else StackStatus.UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
118
+ )
83
119
 
84
120
  # perform all deferred actions such as deletions. These must happen in reverse from their
85
121
  # defined order so that resource dependencies are honoured
86
122
  # TODO: errors will stop all rollbacks; get parity on this behaviour
87
- for action in self._deferred_actions[::-1]:
88
- action()
123
+ for deferred in self._deferred_actions[::-1]:
124
+ LOG.debug("executing deferred action: '%s'", deferred.name)
125
+ deferred.action()
126
+
127
+ if failure_message and not is_deletion:
128
+ # TODO: differentiate between update and create
129
+ self._change_set.stack.set_stack_status(StackStatus.ROLLBACK_COMPLETE)
89
130
 
90
131
  return ChangeSetModelExecutorResult(
91
- resources=self.resources,
92
- outputs=self.outputs,
132
+ resources=self.resources, outputs=self.outputs, failure_message=failure_message
93
133
  )
94
134
 
95
- def _defer_action(self, action: DeferredAction):
96
- self._deferred_actions.append(action)
135
+ def _defer_action(self, name: str, action: DeferredAction):
136
+ self._deferred_actions.append(Deferred(name=name, action=action))
97
137
 
98
138
  def _get_physical_id(self, logical_resource_id, strict: bool = True) -> str | None:
99
139
  physical_resource_id = None
@@ -129,9 +169,10 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
129
169
  else:
130
170
  status = f"{status_from_action}_{event_status.name}"
131
171
 
172
+ physical_resource_id = self._get_physical_id(logical_resource_id, False)
132
173
  self._change_set.stack.set_resource_status(
133
174
  logical_resource_id=logical_resource_id,
134
- physical_resource_id=self._get_physical_id(logical_resource_id, False) or "",
175
+ physical_resource_id=physical_resource_id,
135
176
  resource_type=resource_type,
136
177
  status=ResourceStatus(status),
137
178
  resource_status_reason=reason,
@@ -184,6 +225,12 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
184
225
  try:
185
226
  delta = super().visit_node_resource(node_resource=node_resource)
186
227
  except Exception as e:
228
+ LOG.debug(
229
+ "preprocessing resource '%s' failed: %s",
230
+ node_resource.name,
231
+ e,
232
+ exc_info=LOG.isEnabledFor(logging.DEBUG) and config.CFN_VERBOSE_ERRORS,
233
+ )
187
234
  self._process_event(
188
235
  action=node_resource.change_type.to_change_action(),
189
236
  logical_resource_id=node_resource.name,
@@ -211,10 +258,24 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
211
258
  # Update the latest version of this resource for downstream references.
212
259
  if not is_nothing(after):
213
260
  after_logical_id = after.logical_id
214
- after_physical_id: str = self._after_resource_physical_id(
215
- resource_logical_id=after_logical_id
216
- )
217
- after.physical_resource_id = after_physical_id
261
+ resource = self.resources[after_logical_id]
262
+ resource_failed_to_deploy = resource["ResourceStatus"] in {
263
+ ResourceStatus.CREATE_FAILED,
264
+ ResourceStatus.UPDATE_FAILED,
265
+ }
266
+ if not resource_failed_to_deploy:
267
+ after_physical_id: str = self._after_resource_physical_id(
268
+ resource_logical_id=after_logical_id
269
+ )
270
+ after.physical_resource_id = after_physical_id
271
+ after.status = resource["ResourceStatus"]
272
+
273
+ # terminate the deployment process
274
+ if resource_failed_to_deploy:
275
+ raise TriggerRollback(
276
+ logical_resource_id=after_logical_id,
277
+ reason=resource.get("ResourceStatusReason"),
278
+ )
218
279
  return delta
219
280
 
220
281
  def visit_node_output(
@@ -283,6 +344,7 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
283
344
  resource_type=before.resource_type,
284
345
  before_properties=before_properties,
285
346
  after_properties=None,
347
+ part_of_replacement=True,
286
348
  )
287
349
  self._process_event(
288
350
  action=ChangeAction.Remove,
@@ -292,7 +354,7 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
292
354
  reason=event.message,
293
355
  )
294
356
 
295
- self._defer_action(cleanup)
357
+ self._defer_action(f"cleanup-from-replacement-{name}", cleanup)
296
358
  else:
297
359
  event = self._execute_resource_action(
298
360
  action=ChangeAction.Modify,
@@ -331,7 +393,7 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
331
393
  reason=event.message,
332
394
  )
333
395
 
334
- self._defer_action(perform_deletion)
396
+ self._defer_action(f"type-migration-{name}", perform_deletion)
335
397
 
336
398
  event = self._execute_resource_action(
337
399
  action=ChangeAction.Add,
@@ -375,7 +437,7 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
375
437
  reason=event.message,
376
438
  )
377
439
 
378
- self._defer_action(perform_deletion)
440
+ self._defer_action(f"remove-{name}", perform_deletion)
379
441
  elif not is_nothing(after):
380
442
  # Case: addition
381
443
  self._process_event(
@@ -417,6 +479,7 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
417
479
  resource_type: str,
418
480
  before_properties: PreprocProperties | None,
419
481
  after_properties: PreprocProperties | None,
482
+ part_of_replacement: bool = False,
420
483
  ) -> ProgressEvent:
421
484
  LOG.debug("Executing resource action: %s for resource '%s'", action, logical_resource_id)
422
485
  payload = self.create_resource_provider_payload(
@@ -454,9 +517,11 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
454
517
  resource_type,
455
518
  f'No resource provider found for "{resource_type}"',
456
519
  )
457
- LOG.warning(
458
- "Deployment of resource type %s successful due to config CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES"
459
- )
520
+ if "CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES" not in os.environ:
521
+ LOG.warning(
522
+ "Deployment of resource type %s succeeded, but will fail in upcoming LocalStack releases unless CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES is explicitly enabled.",
523
+ resource_type,
524
+ )
460
525
  event = ProgressEvent(
461
526
  OperationStatus.SUCCESS,
462
527
  resource_model={},
@@ -473,6 +538,19 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
473
538
  message=f"Resource type {resource_type} not supported",
474
539
  )
475
540
 
541
+ if part_of_replacement and action == ChangeAction.Remove:
542
+ # Early return as we don't want to update internal state of the executor if this is a
543
+ # cleanup of an old resource. The new resource has already been created and the state
544
+ # updated
545
+ return event
546
+
547
+ status_from_action = EventOperationFromAction[action.value]
548
+ resolved_resource = ResolvedResource(
549
+ Properties=event.resource_model,
550
+ LogicalResourceId=logical_resource_id,
551
+ Type=resource_type,
552
+ LastUpdatedTimestamp=datetime.now(UTC),
553
+ )
476
554
  match event.status:
477
555
  case OperationStatus.SUCCESS:
478
556
  # merge the resources state with the external state
@@ -487,33 +565,29 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
487
565
 
488
566
  # Don't update the resolved resources if we have deleted that resource
489
567
  if action != ChangeAction.Remove:
490
- status_from_action = EventOperationFromAction[action.value]
491
568
  physical_resource_id = (
492
569
  extra_resource_properties["PhysicalResourceId"]
493
570
  if resource_provider
494
571
  else MOCKED_REFERENCE
495
572
  )
496
- resolved_resource = ResolvedResource(
497
- Properties=event.resource_model,
498
- LogicalResourceId=logical_resource_id,
499
- Type=resource_type,
500
- LastUpdatedTimestamp=datetime.now(UTC),
501
- ResourceStatus=ResourceStatus(f"{status_from_action}_COMPLETE"),
502
- PhysicalResourceId=physical_resource_id,
573
+ resolved_resource["PhysicalResourceId"] = physical_resource_id
574
+ resolved_resource["ResourceStatus"] = ResourceStatus(
575
+ f"{status_from_action}_COMPLETE"
503
576
  )
504
577
  # TODO: do we actually need this line?
505
578
  resolved_resource.update(extra_resource_properties)
506
-
507
- self.resources[logical_resource_id] = resolved_resource
508
-
509
579
  case OperationStatus.FAILED:
510
580
  reason = event.message
511
581
  LOG.warning(
512
582
  "Resource provider operation failed: '%s'",
513
583
  reason,
514
584
  )
585
+ resolved_resource["ResourceStatus"] = ResourceStatus(f"{status_from_action}_FAILED")
586
+ resolved_resource["ResourceStatusReason"] = reason
515
587
  case other:
516
588
  raise NotImplementedError(f"Event status '{other}' not handled")
589
+
590
+ self.resources[logical_resource_id] = resolved_resource
517
591
  return event
518
592
 
519
593
  def create_resource_provider_payload(
@@ -572,43 +646,10 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
572
646
  }
573
647
  return resource_provider_payload
574
648
 
575
- @staticmethod
576
- def _replace_url_outputs_if_required(value: str) -> str:
577
- api_match = REGEX_OUTPUT_APIGATEWAY.match(value)
578
- if api_match and value not in config.CFN_STRING_REPLACEMENT_DENY_LIST:
579
- prefix = api_match[1]
580
- host = api_match[2]
581
- path = api_match[3]
582
- port = localstack_host().port
583
- value = f"{prefix}{host}:{port}/{path}"
584
- return value
585
-
586
- return value
587
-
588
- def visit_terminal_value_created(
589
- self, value: TerminalValueCreated
590
- ) -> PreprocEntityDelta[str, str]:
591
- if isinstance(value.value, str):
592
- after = self._replace_url_outputs_if_required(value.value)
593
- else:
594
- after = value.value
595
- return PreprocEntityDelta(after=after)
596
-
597
- def visit_terminal_value_modified(
598
- self, value: TerminalValueModified
599
- ) -> PreprocEntityDelta[str, str]:
600
- # we only need to transform the after
601
- if isinstance(value.modified_value, str):
602
- after = self._replace_url_outputs_if_required(value.modified_value)
603
- else:
604
- after = value.modified_value
605
- return PreprocEntityDelta(before=value.value, after=after)
606
-
607
- def visit_terminal_value_unchanged(
608
- self, terminal_value_unchanged: TerminalValueUnchanged
649
+ def _maybe_perform_on_delta(
650
+ self, delta: PreprocEntityDelta, f: Callable[[_T], _T]
609
651
  ) -> PreprocEntityDelta:
610
- if isinstance(terminal_value_unchanged.value, str):
611
- value = self._replace_url_outputs_if_required(terminal_value_unchanged.value)
612
- else:
613
- value = terminal_value_unchanged.value
614
- return PreprocEntityDelta(before=value, after=value)
652
+ # we only care about the after state
653
+ if isinstance(delta.after, str):
654
+ delta.after = f(delta.after)
655
+ return delta