localstack-core 4.4.1.dev59__py3-none-any.whl → 4.4.1.dev65__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 (22) hide show
  1. localstack/aws/api/ec2/__init__.py +70 -2
  2. localstack/aws/api/s3/__init__.py +2 -0
  3. localstack/services/cloudformation/engine/v2/change_set_model.py +107 -146
  4. localstack/services/cloudformation/engine/v2/change_set_model_describer.py +41 -22
  5. localstack/services/cloudformation/engine/v2/change_set_model_executor.py +51 -23
  6. localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +244 -123
  7. localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +16 -1
  8. localstack/services/s3/provider.py +3 -2
  9. localstack/services/sns/provider.py +27 -9
  10. localstack/testing/pytest/cloudformation/fixtures.py +13 -1
  11. localstack/version.py +2 -2
  12. {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/METADATA +3 -3
  13. {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/RECORD +21 -21
  14. localstack_core-4.4.1.dev65.dist-info/plux.json +1 -0
  15. localstack_core-4.4.1.dev59.dist-info/plux.json +0 -1
  16. {localstack_core-4.4.1.dev59.data → localstack_core-4.4.1.dev65.data}/scripts/localstack +0 -0
  17. {localstack_core-4.4.1.dev59.data → localstack_core-4.4.1.dev65.data}/scripts/localstack-supervisor +0 -0
  18. {localstack_core-4.4.1.dev59.data → localstack_core-4.4.1.dev65.data}/scripts/localstack.bat +0 -0
  19. {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/WHEEL +0 -0
  20. {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/entry_points.txt +0 -0
  21. {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/licenses/LICENSE.txt +0 -0
  22. {localstack_core-4.4.1.dev59.dist-info → localstack_core-4.4.1.dev65.dist-info}/top_level.txt +0 -0
@@ -6,8 +6,11 @@ from typing import Final, Optional
6
6
  import localstack.aws.api.cloudformation as cfn_api
7
7
  from localstack.services.cloudformation.engine.v2.change_set_model import (
8
8
  NodeIntrinsicFunction,
9
+ NodeProperty,
9
10
  NodeResource,
11
+ Nothing,
10
12
  PropertiesKey,
13
+ is_nothing,
11
14
  )
12
15
  from localstack.services.cloudformation.engine.v2.change_set_model_preproc import (
13
16
  ChangeSetModelPreproc,
@@ -45,26 +48,36 @@ class ChangeSetModelDescriber(ChangeSetModelPreproc):
45
48
  # artificially limit the precision of our output to match AWS's?
46
49
 
47
50
  arguments_delta = self.visit(node_intrinsic_function.arguments)
48
- before_argument_list = arguments_delta.before
49
- after_argument_list = arguments_delta.after
50
-
51
- before = None
52
- if before_argument_list:
53
- before_logical_name_of_resource = before_argument_list[0]
54
- before_attribute_name = before_argument_list[1]
51
+ before_argument: Optional[list[str]] = arguments_delta.before
52
+ if isinstance(before_argument, str):
53
+ before_argument = before_argument.split(".")
54
+ after_argument: Optional[list[str]] = arguments_delta.after
55
+ if isinstance(after_argument, str):
56
+ after_argument = after_argument.split(".")
57
+
58
+ before = Nothing
59
+ if not is_nothing(before_argument):
60
+ before_logical_name_of_resource = before_argument[0]
61
+ before_attribute_name = before_argument[1]
55
62
  before_node_resource = self._get_node_resource_for(
56
63
  resource_name=before_logical_name_of_resource, node_template=self._node_template
57
64
  )
58
- before_node_property = self._get_node_property_for(
65
+ before_node_property: Optional[NodeProperty] = self._get_node_property_for(
59
66
  property_name=before_attribute_name, node_resource=before_node_resource
60
67
  )
61
- before_property_delta = self.visit(before_node_property)
62
- before = before_property_delta.before
68
+ if before_node_property is not None:
69
+ before_property_delta = self.visit(before_node_property)
70
+ before = before_property_delta.before
71
+ else:
72
+ before = self._before_deployed_property_value_of(
73
+ resource_logical_id=before_logical_name_of_resource,
74
+ property_name=before_attribute_name,
75
+ )
63
76
 
64
- after = None
65
- if after_argument_list:
66
- after_logical_name_of_resource = after_argument_list[0]
67
- after_attribute_name = after_argument_list[1]
77
+ after = Nothing
78
+ if not is_nothing(after_argument):
79
+ after_logical_name_of_resource = after_argument[0]
80
+ after_attribute_name = after_argument[1]
68
81
  after_node_resource = self._get_node_resource_for(
69
82
  resource_name=after_logical_name_of_resource, node_template=self._node_template
70
83
  )
@@ -74,12 +87,18 @@ class ChangeSetModelDescriber(ChangeSetModelPreproc):
74
87
  )
75
88
  if after_node_property is not None:
76
89
  after_property_delta = self.visit(after_node_property)
90
+ if after_property_delta.before == after_property_delta.after:
91
+ after = after_property_delta.after
92
+ else:
93
+ after = CHANGESET_KNOWN_AFTER_APPLY
77
94
  else:
78
- after_property_delta = PreprocEntityDelta(after=CHANGESET_KNOWN_AFTER_APPLY)
79
- if after_property_delta.before == after_property_delta.after:
80
- after = after_property_delta.after
81
- else:
82
- after = CHANGESET_KNOWN_AFTER_APPLY
95
+ try:
96
+ after = self._after_deployed_property_value_of(
97
+ resource_logical_id=after_logical_name_of_resource,
98
+ property_name=after_attribute_name,
99
+ )
100
+ except RuntimeError:
101
+ after = CHANGESET_KNOWN_AFTER_APPLY
83
102
 
84
103
  return PreprocEntityDelta(before=before, after=after)
85
104
 
@@ -137,7 +156,7 @@ class ChangeSetModelDescriber(ChangeSetModelPreproc):
137
156
  if before == after:
138
157
  # unchanged: nothing to do.
139
158
  return
140
- if before is not None and after is not None:
159
+ if not is_nothing(before) and not is_nothing(after):
141
160
  # Case: change on same type.
142
161
  if before.resource_type == after.resource_type:
143
162
  # Register a Modified if changed.
@@ -167,7 +186,7 @@ class ChangeSetModelDescriber(ChangeSetModelPreproc):
167
186
  before_properties=None,
168
187
  after_properties=after.properties,
169
188
  )
170
- elif before is not None:
189
+ elif not is_nothing(before):
171
190
  # Case: removal
172
191
  self._register_resource_change(
173
192
  logical_id=name,
@@ -176,7 +195,7 @@ class ChangeSetModelDescriber(ChangeSetModelPreproc):
176
195
  before_properties=before.properties,
177
196
  after_properties=None,
178
197
  )
179
- elif after is not None:
198
+ elif not is_nothing(after):
180
199
  # Case: addition
181
200
  self._register_resource_change(
182
201
  logical_id=name,
@@ -11,6 +11,7 @@ from localstack.services.cloudformation.engine.v2.change_set_model import (
11
11
  NodeOutput,
12
12
  NodeParameter,
13
13
  NodeResource,
14
+ is_nothing,
14
15
  )
15
16
  from localstack.services.cloudformation.engine.v2.change_set_model_preproc import (
16
17
  ChangeSetModelPreproc,
@@ -103,42 +104,46 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
103
104
  `after` delta with the physical resource ID, if side effects resulted in an update.
104
105
  """
105
106
  delta = super().visit_node_resource(node_resource=node_resource)
106
- self._execute_on_resource_change(
107
- name=node_resource.name, before=delta.before, after=delta.after
108
- )
109
- after_resource = delta.after
110
- if after_resource is not None and delta.before != delta.after:
111
- after_logical_id = after_resource.logical_id
112
- after_physical_id: Optional[str] = self._after_resource_physical_id(
107
+ before = delta.before
108
+ after = delta.after
109
+
110
+ if before != after:
111
+ # There are changes for this resource.
112
+ self._execute_resource_change(name=node_resource.name, before=before, after=after)
113
+ else:
114
+ # There are no updates for this resource; iff the resource was previously
115
+ # deployed, then the resolved details are copied in the current state for
116
+ # references or other downstream operations.
117
+ if not is_nothing(before):
118
+ before_logical_id = delta.before.logical_id
119
+ before_resource = self._before_resolved_resources.get(before_logical_id, dict())
120
+ self.resources[before_logical_id] = before_resource
121
+
122
+ # Update the latest version of this resource for downstream references.
123
+ if not is_nothing(after):
124
+ after_logical_id = after.logical_id
125
+ after_physical_id: str = self._after_resource_physical_id(
113
126
  resource_logical_id=after_logical_id
114
127
  )
115
- if after_physical_id is None:
116
- raise RuntimeError(
117
- f"No PhysicalResourceId was found for resource '{after_physical_id}' post-update."
118
- )
119
- after_resource.physical_resource_id = after_physical_id
128
+ after.physical_resource_id = after_physical_id
120
129
  return delta
121
130
 
122
131
  def visit_node_output(
123
132
  self, node_output: NodeOutput
124
133
  ) -> PreprocEntityDelta[PreprocOutput, PreprocOutput]:
125
134
  delta = super().visit_node_output(node_output=node_output)
126
- if delta.after is None:
127
- # handling deletion so the output does not really matter
128
- # TODO: are there other situations?
135
+ after = delta.after
136
+ if is_nothing(after) or (isinstance(after, PreprocOutput) and after.condition is False):
129
137
  return delta
130
-
131
138
  self.outputs[delta.after.name] = delta.after.value
132
139
  return delta
133
140
 
134
- def _execute_on_resource_change(
141
+ def _execute_resource_change(
135
142
  self, name: str, before: Optional[PreprocResource], after: Optional[PreprocResource]
136
143
  ) -> None:
137
- if before == after:
138
- # unchanged: nothing to do.
139
- return
144
+ # Changes are to be made about this resource.
140
145
  # TODO: this logic is a POC and should be revised.
141
- if before is not None and after is not None:
146
+ if not is_nothing(before) and not is_nothing(after):
142
147
  # Case: change on same type.
143
148
  if before.resource_type == after.resource_type:
144
149
  # Register a Modified if changed.
@@ -173,7 +178,7 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
173
178
  before_properties=None,
174
179
  after_properties=after.properties,
175
180
  )
176
- elif before is not None:
181
+ elif not is_nothing(before):
177
182
  # Case: removal
178
183
  # XXX hacky, stick the previous resources' properties into the payload
179
184
  # XXX hacky, stick the previous resources' properties into the payload
@@ -186,7 +191,7 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
186
191
  before_properties=before_properties,
187
192
  after_properties=None,
188
193
  )
189
- elif after is not None:
194
+ elif not is_nothing(after):
190
195
  # Case: addition
191
196
  self._execute_resource_action(
192
197
  action=ChangeAction.Add,
@@ -257,11 +262,34 @@ class ChangeSetModelExecutor(ChangeSetModelPreproc):
257
262
  case OperationStatus.SUCCESS:
258
263
  # merge the resources state with the external state
259
264
  # TODO: this is likely a duplicate of updating from extra_resource_properties
265
+
266
+ # TODO: add typing
267
+ # TODO: avoid the use of string literals for sampling from the object, use typed classes instead
268
+ # TODO: avoid sampling from resources and use tmp var reference
269
+ # TODO: add utils functions to abstract this logic away (resource.update(..))
270
+ # TODO: avoid the use of setdefault (debuggability/readability)
271
+ # TODO: review the use of merge
272
+
260
273
  self.resources[logical_resource_id]["Properties"].update(event.resource_model)
261
274
  self.resources[logical_resource_id].update(extra_resource_properties)
262
275
  # XXX for legacy delete_stack compatibility
263
276
  self.resources[logical_resource_id]["LogicalResourceId"] = logical_resource_id
264
277
  self.resources[logical_resource_id]["Type"] = resource_type
278
+
279
+ # TODO: review why the physical id is returned as None during updates
280
+ # TODO: abstract this in member function of resource classes instead
281
+ physical_resource_id = None
282
+ try:
283
+ physical_resource_id = self._after_resource_physical_id(logical_resource_id)
284
+ except RuntimeError:
285
+ # The physical id is missing or is set to None, which is invalid.
286
+ pass
287
+ if physical_resource_id is None:
288
+ # The physical resource id is None after an update that didn't rewrite the resource, the previous
289
+ # resource id is therefore the current physical id of this resource.
290
+ physical_resource_id = self._before_resource_physical_id(logical_resource_id)
291
+ self.resources[logical_resource_id]["PhysicalResourceId"] = physical_resource_id
292
+
265
293
  case OperationStatus.FAILED:
266
294
  reason = event.message
267
295
  LOG.warning(