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.
- localstack/aws/api/cloudformation/__init__.py +18 -4
- localstack/aws/api/cloudwatch/__init__.py +41 -1
- localstack/aws/api/config/__init__.py +4 -0
- localstack/aws/api/core.py +6 -2
- localstack/aws/api/dynamodb/__init__.py +30 -0
- localstack/aws/api/ec2/__init__.py +1522 -65
- localstack/aws/api/iam/__init__.py +7 -0
- localstack/aws/api/kinesis/__init__.py +19 -0
- localstack/aws/api/kms/__init__.py +6 -0
- localstack/aws/api/lambda_/__init__.py +13 -0
- localstack/aws/api/logs/__init__.py +15 -0
- localstack/aws/api/redshift/__init__.py +9 -3
- localstack/aws/api/route53/__init__.py +5 -0
- localstack/aws/api/s3/__init__.py +12 -0
- localstack/aws/api/s3control/__init__.py +54 -0
- localstack/aws/api/ssm/__init__.py +2 -0
- localstack/aws/api/transcribe/__init__.py +17 -0
- localstack/aws/client.py +7 -2
- localstack/aws/forwarder.py +52 -5
- localstack/aws/handlers/analytics.py +1 -1
- localstack/aws/handlers/internal_requests.py +6 -1
- localstack/aws/handlers/logging.py +12 -2
- localstack/aws/handlers/metric_handler.py +41 -1
- localstack/aws/handlers/service.py +40 -20
- localstack/aws/mocking.py +2 -2
- localstack/aws/patches.py +2 -2
- localstack/aws/protocol/parser.py +459 -32
- localstack/aws/protocol/serializer.py +689 -69
- localstack/aws/protocol/service_router.py +120 -20
- localstack/aws/protocol/validate.py +1 -1
- localstack/aws/scaffold.py +1 -1
- localstack/aws/skeleton.py +4 -2
- localstack/aws/spec-patches.json +58 -0
- localstack/aws/spec.py +37 -16
- localstack/cli/exceptions.py +1 -1
- localstack/cli/localstack.py +6 -6
- localstack/cli/lpm.py +3 -4
- localstack/cli/plugins.py +1 -1
- localstack/cli/profiles.py +1 -2
- localstack/config.py +25 -18
- localstack/constants.py +4 -29
- localstack/dev/kubernetes/__main__.py +130 -7
- localstack/dev/run/configurators.py +1 -4
- localstack/dev/run/paths.py +1 -1
- localstack/dns/plugins.py +5 -1
- localstack/dns/server.py +13 -4
- localstack/logging/format.py +3 -3
- localstack/packages/api.py +9 -8
- localstack/packages/core.py +2 -2
- localstack/packages/plugins.py +0 -8
- localstack/runtime/analytics.py +3 -0
- localstack/runtime/hooks.py +1 -1
- localstack/runtime/init.py +2 -2
- localstack/runtime/main.py +5 -5
- localstack/runtime/patches.py +2 -2
- localstack/services/apigateway/helpers.py +1 -4
- localstack/services/apigateway/legacy/helpers.py +7 -8
- localstack/services/apigateway/legacy/integration.py +4 -3
- localstack/services/apigateway/legacy/invocations.py +6 -5
- localstack/services/apigateway/legacy/provider.py +148 -68
- localstack/services/apigateway/legacy/templates.py +1 -1
- localstack/services/apigateway/next_gen/execute_api/handlers/method_request.py +7 -2
- localstack/services/apigateway/next_gen/execute_api/handlers/resource_router.py +1 -2
- localstack/services/apigateway/next_gen/execute_api/integrations/aws.py +3 -0
- localstack/services/apigateway/next_gen/execute_api/integrations/http.py +3 -3
- localstack/services/apigateway/next_gen/execute_api/template_mapping.py +2 -2
- localstack/services/apigateway/next_gen/execute_api/test_invoke.py +114 -9
- localstack/services/apigateway/next_gen/provider.py +5 -0
- localstack/services/apigateway/resource_providers/aws_apigateway_resource.py +1 -1
- localstack/services/cloudformation/api_utils.py +4 -8
- localstack/services/cloudformation/cfn_utils.py +1 -1
- localstack/services/cloudformation/engine/entities.py +14 -4
- localstack/services/cloudformation/engine/template_deployer.py +6 -4
- localstack/services/cloudformation/engine/transformers.py +6 -4
- localstack/services/cloudformation/engine/v2/change_set_model.py +201 -13
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +52 -3
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +117 -76
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +205 -52
- localstack/services/cloudformation/engine/v2/change_set_model_transform.py +350 -116
- localstack/services/cloudformation/engine/v2/change_set_model_validator.py +56 -14
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
- localstack/services/cloudformation/engine/v2/resolving.py +7 -5
- localstack/services/cloudformation/engine/yaml_parser.py +9 -2
- localstack/services/cloudformation/provider.py +7 -5
- localstack/services/cloudformation/resource_provider.py +7 -1
- localstack/services/cloudformation/resources.py +24149 -0
- localstack/services/cloudformation/service_models.py +2 -2
- localstack/services/cloudformation/v2/entities.py +19 -9
- localstack/services/cloudformation/v2/provider.py +336 -106
- localstack/services/cloudformation/v2/types.py +13 -7
- localstack/services/cloudformation/v2/utils.py +4 -1
- localstack/services/cloudwatch/alarm_scheduler.py +4 -1
- localstack/services/cloudwatch/provider.py +18 -13
- localstack/services/cloudwatch/provider_v2.py +25 -28
- localstack/services/dynamodb/packages.py +2 -1
- localstack/services/dynamodb/provider.py +42 -0
- localstack/services/dynamodb/server.py +2 -2
- localstack/services/dynamodb/v2/provider.py +42 -0
- localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
- localstack/services/edge.py +1 -1
- localstack/services/es/provider.py +2 -2
- localstack/services/events/event_rule_engine.py +31 -13
- localstack/services/events/models.py +4 -5
- localstack/services/events/provider.py +17 -14
- localstack/services/events/target.py +17 -9
- localstack/services/events/v1/provider.py +5 -5
- localstack/services/firehose/provider.py +14 -4
- localstack/services/iam/provider.py +11 -116
- localstack/services/iam/resources/policy_simulator.py +133 -0
- localstack/services/kinesis/models.py +15 -2
- localstack/services/kinesis/provider.py +86 -3
- localstack/services/kms/provider.py +14 -5
- localstack/services/lambda_/api_utils.py +6 -3
- localstack/services/lambda_/invocation/docker_runtime_executor.py +1 -1
- localstack/services/lambda_/invocation/event_manager.py +1 -1
- localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
- localstack/services/lambda_/invocation/lambda_models.py +10 -7
- localstack/services/lambda_/invocation/lambda_service.py +5 -1
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/lambda_/provider.py +4 -3
- localstack/services/lambda_/provider_utils.py +1 -1
- localstack/services/logs/provider.py +36 -19
- localstack/services/moto.py +2 -1
- localstack/services/opensearch/cluster.py +15 -7
- localstack/services/opensearch/packages.py +26 -7
- localstack/services/opensearch/provider.py +8 -2
- localstack/services/opensearch/versions.py +56 -7
- localstack/services/plugins.py +11 -7
- localstack/services/providers.py +10 -2
- localstack/services/redshift/provider.py +0 -21
- localstack/services/s3/constants.py +5 -2
- localstack/services/s3/cors.py +4 -4
- localstack/services/s3/models.py +1 -1
- localstack/services/s3/notifications.py +55 -39
- localstack/services/s3/presigned_url.py +35 -54
- localstack/services/s3/provider.py +73 -15
- localstack/services/s3/utils.py +42 -22
- localstack/services/s3/validation.py +46 -32
- localstack/services/s3/website_hosting.py +4 -2
- localstack/services/ses/provider.py +18 -8
- localstack/services/sns/constants.py +7 -1
- localstack/services/sns/executor.py +9 -2
- localstack/services/sns/provider.py +8 -5
- localstack/services/sns/publisher.py +31 -16
- localstack/services/sns/v2/models.py +167 -0
- localstack/services/sns/v2/provider.py +867 -0
- localstack/services/sns/v2/utils.py +130 -0
- localstack/services/sqs/constants.py +1 -1
- localstack/services/sqs/developer_api.py +205 -0
- localstack/services/sqs/models.py +48 -5
- localstack/services/sqs/provider.py +38 -311
- localstack/services/sqs/query_api.py +6 -2
- localstack/services/sqs/utils.py +121 -2
- localstack/services/ssm/provider.py +1 -1
- localstack/services/stepfunctions/asl/component/intrinsic/member.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_choice/comparison/comparison.py +5 -11
- localstack/services/stepfunctions/asl/component/state/state_choice/state_choice.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_map/state_map.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_execution/state_parallel/state_parallel.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_execution/state_task/state_task.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_fail/state_fail.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_pass/state_pass.py +2 -2
- localstack/services/stepfunctions/asl/component/state/state_succeed/state_succeed.py +1 -1
- localstack/services/stepfunctions/asl/component/state/state_wait/state_wait.py +1 -1
- localstack/services/stepfunctions/asl/eval/environment.py +1 -1
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
- localstack/services/stepfunctions/backend/execution.py +2 -1
- localstack/services/stores.py +1 -1
- localstack/services/transcribe/provider.py +6 -1
- localstack/state/codecs.py +61 -0
- localstack/state/core.py +11 -5
- localstack/state/pickle.py +10 -49
- localstack/testing/aws/cloudformation_utils.py +1 -1
- localstack/testing/pytest/cloudformation/fixtures.py +3 -3
- localstack/testing/pytest/cloudformation/transformers.py +0 -0
- localstack/testing/pytest/container.py +4 -5
- localstack/testing/pytest/fixtures.py +33 -31
- localstack/testing/pytest/in_memory_localstack.py +0 -4
- localstack/testing/pytest/marking.py +38 -11
- localstack/testing/pytest/stepfunctions/utils.py +4 -3
- localstack/testing/pytest/util.py +1 -1
- localstack/testing/pytest/validation_tracking.py +1 -2
- localstack/testing/snapshots/transformer_utility.py +6 -1
- localstack/utils/analytics/events.py +2 -2
- localstack/utils/analytics/metadata.py +6 -4
- localstack/utils/analytics/metrics/counter.py +8 -15
- localstack/utils/analytics/publisher.py +1 -2
- localstack/utils/analytics/service_providers.py +19 -0
- localstack/utils/analytics/service_request_aggregator.py +2 -2
- localstack/utils/archives.py +11 -11
- localstack/utils/asyncio.py +2 -2
- localstack/utils/aws/arns.py +24 -29
- localstack/utils/aws/aws_responses.py +8 -8
- localstack/utils/aws/aws_stack.py +2 -3
- localstack/utils/aws/dead_letter_queue.py +1 -5
- localstack/utils/aws/message_forwarding.py +1 -2
- localstack/utils/aws/request_context.py +4 -5
- localstack/utils/aws/resources.py +1 -1
- localstack/utils/aws/templating.py +1 -1
- localstack/utils/batch_policy.py +3 -3
- localstack/utils/bootstrap.py +21 -13
- localstack/utils/catalog/catalog.py +139 -0
- localstack/utils/catalog/catalog_loader.py +119 -0
- localstack/utils/catalog/common.py +58 -0
- localstack/utils/catalog/plugins.py +28 -0
- localstack/utils/cloudwatch/cloudwatch_util.py +5 -5
- localstack/utils/collections.py +7 -8
- localstack/utils/config_listener.py +1 -1
- localstack/utils/container_networking.py +2 -3
- localstack/utils/container_utils/container_client.py +135 -136
- localstack/utils/container_utils/docker_cmd_client.py +85 -69
- localstack/utils/container_utils/docker_sdk_client.py +69 -66
- localstack/utils/crypto.py +10 -10
- localstack/utils/diagnose.py +3 -4
- localstack/utils/docker_utils.py +9 -5
- localstack/utils/files.py +33 -13
- localstack/utils/functions.py +4 -3
- localstack/utils/http.py +11 -11
- localstack/utils/json.py +20 -6
- localstack/utils/kinesis/kinesis_connector.py +2 -1
- localstack/utils/net.py +15 -9
- localstack/utils/no_exit_argument_parser.py +2 -2
- localstack/utils/numbers.py +9 -2
- localstack/utils/objects.py +7 -6
- localstack/utils/patch.py +10 -3
- localstack/utils/run.py +12 -11
- localstack/utils/scheduler.py +11 -11
- localstack/utils/server/tcp_proxy.py +2 -2
- localstack/utils/serving.py +3 -4
- localstack/utils/strings.py +15 -16
- localstack/utils/sync.py +126 -1
- localstack/utils/tagging.py +8 -6
- localstack/utils/testutil.py +8 -8
- localstack/utils/threads.py +2 -2
- localstack/utils/time.py +12 -4
- localstack/utils/urls.py +1 -3
- localstack/utils/xray/traceid.py +1 -1
- localstack/version.py +16 -3
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/METADATA +18 -14
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/RECORD +248 -239
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/entry_points.txt +8 -4
- localstack_core-4.10.1.dev12.dist-info/plux.json +1 -0
- localstack/packages/terraform.py +0 -46
- localstack/services/cloudformation/deploy.html +0 -144
- localstack/services/cloudformation/deploy_ui.py +0 -47
- localstack/services/cloudformation/plugins.py +0 -12
- localstack_core-4.7.1.dev49.dist-info/plux.json +0 -1
- {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack +0 -0
- {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.7.1.dev49.data → localstack_core-4.10.1.dev12.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/WHEEL +0 -0
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.7.1.dev49.dist-info → localstack_core-4.10.1.dev12.dist-info}/top_level.txt +0 -0
|
@@ -14,6 +14,7 @@ from localstack.services.cloudformation.v2.types import (
|
|
|
14
14
|
EngineParameter,
|
|
15
15
|
engine_parameter_value,
|
|
16
16
|
)
|
|
17
|
+
from localstack.utils.json import extract_jsonpath
|
|
17
18
|
from localstack.utils.strings import camel_to_snake_case
|
|
18
19
|
|
|
19
20
|
T = TypeVar("T")
|
|
@@ -69,6 +70,9 @@ def parent_change_type_of(children: list[Maybe[ChangeSetEntity]]):
|
|
|
69
70
|
change_types = [c.change_type for c in children if not is_nothing(c)]
|
|
70
71
|
if not change_types:
|
|
71
72
|
return ChangeType.UNCHANGED
|
|
73
|
+
# TODO: rework this logic. Currently if any values are different then we consider it
|
|
74
|
+
# modified, but e.g. if everything is unchanged or created, the result should probably be
|
|
75
|
+
# "created"
|
|
72
76
|
first_type = change_types[0]
|
|
73
77
|
if all(ct == first_type for ct in change_types):
|
|
74
78
|
return first_type
|
|
@@ -106,6 +110,30 @@ class Scope(str):
|
|
|
106
110
|
def unwrap(self) -> list[str]:
|
|
107
111
|
return self.split(self._SEPARATOR)
|
|
108
112
|
|
|
113
|
+
@property
|
|
114
|
+
def parent(self) -> Scope:
|
|
115
|
+
return Scope(self._SEPARATOR.join(self.split(self._SEPARATOR)[:-1]))
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def jsonpath(self) -> str:
|
|
119
|
+
parts = self.split("/")
|
|
120
|
+
json_parts = []
|
|
121
|
+
|
|
122
|
+
for part in parts:
|
|
123
|
+
if not part: # Skip empty strings from leading/trailing slashes
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
if part == "divergence":
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
# Wrap keys with special characters (e.g., colon) in quotes
|
|
130
|
+
if ":" in part:
|
|
131
|
+
json_parts.append(f'"{part}"')
|
|
132
|
+
else:
|
|
133
|
+
json_parts.append(part)
|
|
134
|
+
|
|
135
|
+
return f"$.{'.'.join(json_parts)}"
|
|
136
|
+
|
|
109
137
|
|
|
110
138
|
class ChangeType(enum.Enum):
|
|
111
139
|
UNCHANGED = "Unchanged"
|
|
@@ -127,7 +155,7 @@ class ChangeType(enum.Enum):
|
|
|
127
155
|
|
|
128
156
|
class ChangeSetEntity(abc.ABC):
|
|
129
157
|
scope: Final[Scope]
|
|
130
|
-
change_type:
|
|
158
|
+
change_type: ChangeType
|
|
131
159
|
|
|
132
160
|
def __init__(self, scope: Scope, change_type: ChangeType):
|
|
133
161
|
self.scope = scope
|
|
@@ -343,11 +371,21 @@ class NodeTransform(ChangeSetNode):
|
|
|
343
371
|
|
|
344
372
|
class NodeResources(ChangeSetNode):
|
|
345
373
|
resources: Final[list[NodeResource]]
|
|
374
|
+
fn_transform: Final[Maybe[NodeIntrinsicFunctionFnTransform]]
|
|
375
|
+
fn_foreaches: Final[list[NodeForEach]]
|
|
346
376
|
|
|
347
|
-
def __init__(
|
|
348
|
-
|
|
377
|
+
def __init__(
|
|
378
|
+
self,
|
|
379
|
+
scope: Scope,
|
|
380
|
+
resources: list[NodeResource],
|
|
381
|
+
fn_transform: Maybe[NodeIntrinsicFunctionFnTransform],
|
|
382
|
+
fn_foreaches: list[NodeForEach],
|
|
383
|
+
):
|
|
384
|
+
change_type = parent_change_type_of(resources + [fn_transform] + fn_foreaches)
|
|
349
385
|
super().__init__(scope=scope, change_type=change_type)
|
|
350
386
|
self.resources = resources
|
|
387
|
+
self.fn_transform = fn_transform
|
|
388
|
+
self.fn_foreaches = fn_foreaches
|
|
351
389
|
|
|
352
390
|
|
|
353
391
|
class NodeResource(ChangeSetNode):
|
|
@@ -359,6 +397,7 @@ class NodeResource(ChangeSetNode):
|
|
|
359
397
|
requires_replacement: Final[bool]
|
|
360
398
|
deletion_policy: Final[Maybe[ChangeSetTerminal]]
|
|
361
399
|
update_replace_policy: Final[Maybe[ChangeSetTerminal]]
|
|
400
|
+
fn_transform: Final[Maybe[NodeIntrinsicFunctionFnTransform]]
|
|
362
401
|
|
|
363
402
|
def __init__(
|
|
364
403
|
self,
|
|
@@ -372,6 +411,7 @@ class NodeResource(ChangeSetNode):
|
|
|
372
411
|
requires_replacement: bool,
|
|
373
412
|
deletion_policy: Maybe[ChangeSetTerminal],
|
|
374
413
|
update_replace_policy: Maybe[ChangeSetTerminal],
|
|
414
|
+
fn_transform: Maybe[NodeIntrinsicFunctionFnTransform],
|
|
375
415
|
):
|
|
376
416
|
super().__init__(scope=scope, change_type=change_type)
|
|
377
417
|
self.name = name
|
|
@@ -382,15 +422,23 @@ class NodeResource(ChangeSetNode):
|
|
|
382
422
|
self.requires_replacement = requires_replacement
|
|
383
423
|
self.deletion_policy = deletion_policy
|
|
384
424
|
self.update_replace_policy = update_replace_policy
|
|
425
|
+
self.fn_transform = fn_transform
|
|
385
426
|
|
|
386
427
|
|
|
387
428
|
class NodeProperties(ChangeSetNode):
|
|
388
429
|
properties: Final[list[NodeProperty]]
|
|
430
|
+
fn_transform: Final[Maybe[NodeIntrinsicFunctionFnTransform]]
|
|
389
431
|
|
|
390
|
-
def __init__(
|
|
432
|
+
def __init__(
|
|
433
|
+
self,
|
|
434
|
+
scope: Scope,
|
|
435
|
+
properties: list[NodeProperty],
|
|
436
|
+
fn_transform: Maybe[NodeIntrinsicFunctionFnTransform],
|
|
437
|
+
):
|
|
391
438
|
change_type = parent_change_type_of(properties)
|
|
392
439
|
super().__init__(scope=scope, change_type=change_type)
|
|
393
440
|
self.properties = properties
|
|
441
|
+
self.fn_transform = fn_transform
|
|
394
442
|
|
|
395
443
|
|
|
396
444
|
class NodeDependsOn(ChangeSetNode):
|
|
@@ -427,6 +475,40 @@ class NodeIntrinsicFunction(ChangeSetNode):
|
|
|
427
475
|
self.arguments = arguments
|
|
428
476
|
|
|
429
477
|
|
|
478
|
+
class NodeIntrinsicFunctionFnTransform(NodeIntrinsicFunction):
|
|
479
|
+
def __init__(
|
|
480
|
+
self,
|
|
481
|
+
scope: Scope,
|
|
482
|
+
change_type: ChangeType,
|
|
483
|
+
intrinsic_function: str,
|
|
484
|
+
arguments: ChangeSetEntity,
|
|
485
|
+
before_siblings: list[Any],
|
|
486
|
+
after_siblings: list[Any],
|
|
487
|
+
):
|
|
488
|
+
super().__init__(
|
|
489
|
+
scope=scope,
|
|
490
|
+
change_type=change_type,
|
|
491
|
+
intrinsic_function=intrinsic_function,
|
|
492
|
+
arguments=arguments,
|
|
493
|
+
)
|
|
494
|
+
self.before_siblings = before_siblings
|
|
495
|
+
self.after_siblings = after_siblings
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class NodeForEach(ChangeSetNode):
|
|
499
|
+
def __init__(
|
|
500
|
+
self,
|
|
501
|
+
scope: Scope,
|
|
502
|
+
change_type: Final[ChangeType],
|
|
503
|
+
arguments: Final[ChangeSetEntity],
|
|
504
|
+
):
|
|
505
|
+
super().__init__(
|
|
506
|
+
scope=scope,
|
|
507
|
+
change_type=change_type,
|
|
508
|
+
)
|
|
509
|
+
self.arguments = arguments
|
|
510
|
+
|
|
511
|
+
|
|
430
512
|
class NodeObject(ChangeSetNode):
|
|
431
513
|
bindings: Final[dict[str, ChangeSetEntity]]
|
|
432
514
|
|
|
@@ -598,6 +680,7 @@ class ChangeSetModel:
|
|
|
598
680
|
arguments = self._visit_value(
|
|
599
681
|
scope=arguments_scope, before_value=before_arguments, after_value=after_arguments
|
|
600
682
|
)
|
|
683
|
+
|
|
601
684
|
if is_created(before=before_arguments, after=after_arguments):
|
|
602
685
|
change_type = ChangeType.CREATED
|
|
603
686
|
elif is_removed(before=before_arguments, after=after_arguments):
|
|
@@ -611,15 +694,47 @@ class ChangeSetModel:
|
|
|
611
694
|
change_type = resolve_function(arguments)
|
|
612
695
|
else:
|
|
613
696
|
change_type = arguments.change_type
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
697
|
+
|
|
698
|
+
if intrinsic_function == FnTransform:
|
|
699
|
+
if scope.count(FnTransform) > 1:
|
|
700
|
+
raise RuntimeError(
|
|
701
|
+
"Invalid: Fn::Transforms cannot be nested inside another Fn::Transform"
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
path = scope.parent.jsonpath
|
|
705
|
+
before_siblings = extract_jsonpath(self._before_template, path)
|
|
706
|
+
after_siblings = extract_jsonpath(self._after_template, path)
|
|
707
|
+
|
|
708
|
+
node_intrinsic_function = NodeIntrinsicFunctionFnTransform(
|
|
709
|
+
scope=scope,
|
|
710
|
+
change_type=change_type,
|
|
711
|
+
arguments=arguments,
|
|
712
|
+
intrinsic_function=intrinsic_function,
|
|
713
|
+
before_siblings=before_siblings,
|
|
714
|
+
after_siblings=after_siblings,
|
|
715
|
+
)
|
|
716
|
+
else:
|
|
717
|
+
node_intrinsic_function = NodeIntrinsicFunction(
|
|
718
|
+
scope=scope,
|
|
719
|
+
change_type=change_type,
|
|
720
|
+
intrinsic_function=intrinsic_function,
|
|
721
|
+
arguments=arguments,
|
|
722
|
+
)
|
|
620
723
|
self._visited_scopes[scope] = node_intrinsic_function
|
|
621
724
|
return node_intrinsic_function
|
|
622
725
|
|
|
726
|
+
def _visit_foreach(
|
|
727
|
+
self, scope: Scope, before_arguments: Maybe[list], after_arguments: Maybe[list]
|
|
728
|
+
) -> NodeForEach:
|
|
729
|
+
node_foreach = self._visited_scopes.get(scope)
|
|
730
|
+
if isinstance(node_foreach, NodeForEach):
|
|
731
|
+
return node_foreach
|
|
732
|
+
arguments_scope = scope.open_scope("args")
|
|
733
|
+
arguments = self._visit_array(
|
|
734
|
+
arguments_scope, before_array=before_arguments, after_array=after_arguments
|
|
735
|
+
)
|
|
736
|
+
return NodeForEach(scope=scope, change_type=arguments.change_type, arguments=arguments)
|
|
737
|
+
|
|
623
738
|
def _resolve_intrinsic_function_fn_sub(self, arguments: ChangeSetEntity) -> ChangeType:
|
|
624
739
|
# TODO: This routine should instead export the implicit Ref and GetAtt calls within the first
|
|
625
740
|
# string template parameter and compute the respective change set types. Currently,
|
|
@@ -667,6 +782,9 @@ class ChangeSetModel:
|
|
|
667
782
|
|
|
668
783
|
logical_id = arguments.value
|
|
669
784
|
|
|
785
|
+
if isinstance(logical_id, str) and logical_id.startswith("AWS::"):
|
|
786
|
+
return arguments.change_type
|
|
787
|
+
|
|
670
788
|
node_condition = self._retrieve_condition_if_exists(condition_name=logical_id)
|
|
671
789
|
if isinstance(node_condition, NodeCondition):
|
|
672
790
|
return node_condition.change_type
|
|
@@ -745,6 +863,7 @@ class ChangeSetModel:
|
|
|
745
863
|
) -> bool:
|
|
746
864
|
# a bit hacky but we have to load the resource provider executor _and_ resource provider to get the schema
|
|
747
865
|
# Note: we don't log the attempt to load the resource provider, we need to make sure this is only done once and we already do this in the executor
|
|
866
|
+
|
|
748
867
|
resource_provider = ResourceProviderExecutor.try_load_resource_provider(resource_type.value)
|
|
749
868
|
if not resource_provider:
|
|
750
869
|
# if we don't support a resource, assume an in-place update for simplicity
|
|
@@ -889,10 +1008,18 @@ class ChangeSetModel:
|
|
|
889
1008
|
return node_properties
|
|
890
1009
|
property_names: list[str] = self._safe_keys_of(before_properties, after_properties)
|
|
891
1010
|
properties: list[NodeProperty] = []
|
|
1011
|
+
fn_transform = Nothing
|
|
1012
|
+
|
|
892
1013
|
for property_name in property_names:
|
|
893
1014
|
property_scope, (before_property, after_property) = self._safe_access_in(
|
|
894
1015
|
scope, property_name, before_properties, after_properties
|
|
895
1016
|
)
|
|
1017
|
+
if property_name == FnTransform:
|
|
1018
|
+
fn_transform = self._visit_intrinsic_function(
|
|
1019
|
+
property_scope, FnTransform, before_property, after_property
|
|
1020
|
+
)
|
|
1021
|
+
continue
|
|
1022
|
+
|
|
896
1023
|
property_ = self._visit_property(
|
|
897
1024
|
scope=property_scope,
|
|
898
1025
|
property_name=property_name,
|
|
@@ -900,7 +1027,10 @@ class ChangeSetModel:
|
|
|
900
1027
|
after_property=after_property,
|
|
901
1028
|
)
|
|
902
1029
|
properties.append(property_)
|
|
903
|
-
|
|
1030
|
+
|
|
1031
|
+
node_properties = NodeProperties(
|
|
1032
|
+
scope=scope, properties=properties, fn_transform=fn_transform
|
|
1033
|
+
)
|
|
904
1034
|
self._visited_scopes[scope] = node_properties
|
|
905
1035
|
return node_properties
|
|
906
1036
|
|
|
@@ -1000,10 +1130,44 @@ class ChangeSetModel:
|
|
|
1000
1130
|
after_update_replace_policy,
|
|
1001
1131
|
)
|
|
1002
1132
|
|
|
1133
|
+
fn_transform = Nothing
|
|
1134
|
+
scope_fn_transform, (before_fn_transform_args, after_fn_transform_args) = (
|
|
1135
|
+
self._safe_access_in(scope, FnTransform, before_resource, after_resource)
|
|
1136
|
+
)
|
|
1137
|
+
if not is_nothing(before_fn_transform_args) or not is_nothing(after_fn_transform_args):
|
|
1138
|
+
if scope_fn_transform.count(FnTransform) > 1:
|
|
1139
|
+
raise RuntimeError(
|
|
1140
|
+
"Invalid: Fn::Transforms cannot be nested inside another Fn::Transform"
|
|
1141
|
+
)
|
|
1142
|
+
path = "$" + ".".join(scope_fn_transform.split("/")[:-1])
|
|
1143
|
+
before_siblings = extract_jsonpath(self._before_template, path)
|
|
1144
|
+
after_siblings = extract_jsonpath(self._after_template, path)
|
|
1145
|
+
arguments_scope = scope.open_scope("args")
|
|
1146
|
+
arguments = self._visit_value(
|
|
1147
|
+
scope=arguments_scope,
|
|
1148
|
+
before_value=before_fn_transform_args,
|
|
1149
|
+
after_value=after_fn_transform_args,
|
|
1150
|
+
)
|
|
1151
|
+
fn_transform = NodeIntrinsicFunctionFnTransform(
|
|
1152
|
+
scope=scope_fn_transform,
|
|
1153
|
+
change_type=ChangeType.MODIFIED, # TODO
|
|
1154
|
+
arguments=arguments, # TODO
|
|
1155
|
+
intrinsic_function=FnTransform,
|
|
1156
|
+
before_siblings=before_siblings,
|
|
1157
|
+
after_siblings=after_siblings,
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1003
1160
|
change_type = change_type_of(
|
|
1004
1161
|
before_resource,
|
|
1005
1162
|
after_resource,
|
|
1006
|
-
[
|
|
1163
|
+
[
|
|
1164
|
+
properties,
|
|
1165
|
+
condition_reference,
|
|
1166
|
+
depends_on,
|
|
1167
|
+
deletion_policy,
|
|
1168
|
+
update_replace_policy,
|
|
1169
|
+
fn_transform,
|
|
1170
|
+
],
|
|
1007
1171
|
)
|
|
1008
1172
|
requires_replacement = self._resolve_requires_replacement(
|
|
1009
1173
|
node_properties=properties, resource_type=terminal_value_type
|
|
@@ -1019,6 +1183,7 @@ class ChangeSetModel:
|
|
|
1019
1183
|
requires_replacement=requires_replacement,
|
|
1020
1184
|
deletion_policy=deletion_policy,
|
|
1021
1185
|
update_replace_policy=update_replace_policy,
|
|
1186
|
+
fn_transform=fn_transform,
|
|
1022
1187
|
)
|
|
1023
1188
|
self._visited_scopes[scope] = node_resource
|
|
1024
1189
|
return node_resource
|
|
@@ -1029,10 +1194,28 @@ class ChangeSetModel:
|
|
|
1029
1194
|
# TODO: investigate type changes behavior.
|
|
1030
1195
|
resources: list[NodeResource] = []
|
|
1031
1196
|
resource_names = self._safe_keys_of(before_resources, after_resources)
|
|
1197
|
+
fn_transform = Nothing
|
|
1198
|
+
fn_foreaches = []
|
|
1032
1199
|
for resource_name in resource_names:
|
|
1033
1200
|
resource_scope, (before_resource, after_resource) = self._safe_access_in(
|
|
1034
1201
|
scope, resource_name, before_resources, after_resources
|
|
1035
1202
|
)
|
|
1203
|
+
if resource_name == FnTransform:
|
|
1204
|
+
fn_transform = self._visit_intrinsic_function(
|
|
1205
|
+
scope=resource_scope,
|
|
1206
|
+
intrinsic_function=resource_name,
|
|
1207
|
+
before_arguments=before_resource,
|
|
1208
|
+
after_arguments=after_resource,
|
|
1209
|
+
)
|
|
1210
|
+
continue
|
|
1211
|
+
elif resource_name.startswith("Fn::ForEach"):
|
|
1212
|
+
fn_for_each = self._visit_foreach(
|
|
1213
|
+
scope=resource_scope,
|
|
1214
|
+
before_arguments=before_resource,
|
|
1215
|
+
after_arguments=after_resource,
|
|
1216
|
+
)
|
|
1217
|
+
fn_foreaches.append(fn_for_each)
|
|
1218
|
+
continue
|
|
1036
1219
|
resource = self._visit_resource(
|
|
1037
1220
|
scope=resource_scope,
|
|
1038
1221
|
resource_name=resource_name,
|
|
@@ -1040,7 +1223,12 @@ class ChangeSetModel:
|
|
|
1040
1223
|
after_resource=after_resource,
|
|
1041
1224
|
)
|
|
1042
1225
|
resources.append(resource)
|
|
1043
|
-
return NodeResources(
|
|
1226
|
+
return NodeResources(
|
|
1227
|
+
scope=scope,
|
|
1228
|
+
resources=resources,
|
|
1229
|
+
fn_transform=fn_transform,
|
|
1230
|
+
fn_foreaches=fn_foreaches,
|
|
1231
|
+
)
|
|
1044
1232
|
|
|
1045
1233
|
def _visit_mapping(
|
|
1046
1234
|
self, scope: Scope, name: str, before_mapping: Maybe[dict], after_mapping: Maybe[dict]
|
|
@@ -20,6 +20,7 @@ from localstack.services.cloudformation.engine.v2.change_set_model_preproc impor
|
|
|
20
20
|
PreprocResource,
|
|
21
21
|
)
|
|
22
22
|
from localstack.services.cloudformation.v2.entities import ChangeSet
|
|
23
|
+
from localstack.utils.numbers import is_number
|
|
23
24
|
|
|
24
25
|
CHANGESET_KNOWN_AFTER_APPLY: Final[str] = "{{changeSet:KNOWN_AFTER_APPLY}}"
|
|
25
26
|
|
|
@@ -96,11 +97,27 @@ class ChangeSetModelDescriber(ChangeSetModelPreproc):
|
|
|
96
97
|
|
|
97
98
|
return value
|
|
98
99
|
|
|
100
|
+
def visit_node_intrinsic_function(self, node_intrinsic_function: NodeIntrinsicFunction):
|
|
101
|
+
"""
|
|
102
|
+
Intrinsic function results are always strings when referring to the describe output
|
|
103
|
+
"""
|
|
104
|
+
# TODO: what about other places?
|
|
105
|
+
# TODO: should this be put in the preproc?
|
|
106
|
+
delta = super().visit_node_intrinsic_function(node_intrinsic_function)
|
|
107
|
+
if is_number(delta.before):
|
|
108
|
+
delta.before = str(delta.before)
|
|
109
|
+
if is_number(delta.after):
|
|
110
|
+
delta.after = str(delta.after)
|
|
111
|
+
return delta
|
|
112
|
+
|
|
99
113
|
def visit_node_intrinsic_function_fn_join(
|
|
100
114
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
101
115
|
) -> PreprocEntityDelta:
|
|
102
|
-
|
|
103
|
-
|
|
116
|
+
delta_args = super().visit(node_intrinsic_function.arguments)
|
|
117
|
+
if isinstance(delta_args.after, list) and CHANGESET_KNOWN_AFTER_APPLY in delta_args.after:
|
|
118
|
+
delta_args.after = CHANGESET_KNOWN_AFTER_APPLY
|
|
119
|
+
return delta_args
|
|
120
|
+
|
|
104
121
|
delta = super().visit_node_intrinsic_function_fn_join(
|
|
105
122
|
node_intrinsic_function=node_intrinsic_function
|
|
106
123
|
)
|
|
@@ -112,6 +129,30 @@ class ChangeSetModelDescriber(ChangeSetModelPreproc):
|
|
|
112
129
|
delta.after = CHANGESET_KNOWN_AFTER_APPLY
|
|
113
130
|
return delta
|
|
114
131
|
|
|
132
|
+
def visit_node_intrinsic_function_fn_select(
|
|
133
|
+
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
134
|
+
):
|
|
135
|
+
# TODO: should this not _ALWAYS_ return CHANGESET_KNOWN_AFTER_APPLY?
|
|
136
|
+
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
|
137
|
+
delta = PreprocEntityDelta()
|
|
138
|
+
if not is_nothing(arguments_delta.before):
|
|
139
|
+
idx = arguments_delta.before[0]
|
|
140
|
+
arr = arguments_delta.before[1]
|
|
141
|
+
try:
|
|
142
|
+
delta.before = arr[int(idx)]
|
|
143
|
+
except Exception:
|
|
144
|
+
delta.before = CHANGESET_KNOWN_AFTER_APPLY
|
|
145
|
+
|
|
146
|
+
if not is_nothing(arguments_delta.after):
|
|
147
|
+
idx = arguments_delta.after[0]
|
|
148
|
+
arr = arguments_delta.after[1]
|
|
149
|
+
try:
|
|
150
|
+
delta.after = arr[int(idx)]
|
|
151
|
+
except Exception:
|
|
152
|
+
delta.after = CHANGESET_KNOWN_AFTER_APPLY
|
|
153
|
+
|
|
154
|
+
return delta
|
|
155
|
+
|
|
115
156
|
def _register_resource_change(
|
|
116
157
|
self,
|
|
117
158
|
logical_id: str,
|
|
@@ -240,6 +281,14 @@ class ChangeSetModelDescriber(ChangeSetModelPreproc):
|
|
|
240
281
|
export_name = node_intrinsic_function.arguments.value
|
|
241
282
|
|
|
242
283
|
self._change_set.status_reason = f"[WARN] --include-property-values option can return incomplete ChangeSet data because: ChangeSet creation failed for resource [{resource_name}] because: No export named {export_name}"
|
|
243
|
-
delta.after =
|
|
284
|
+
delta.after = CHANGESET_KNOWN_AFTER_APPLY
|
|
244
285
|
|
|
245
286
|
return delta
|
|
287
|
+
|
|
288
|
+
def visit_node_intrinsic_function_fn_split(
|
|
289
|
+
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
290
|
+
) -> PreprocEntityDelta:
|
|
291
|
+
delta = super().visit_node_intrinsic_function_fn_split(node_intrinsic_function)
|
|
292
|
+
if isinstance(delta.after, list) and ":".join(delta.after) == CHANGESET_KNOWN_AFTER_APPLY:
|
|
293
|
+
delta.after = [CHANGESET_KNOWN_AFTER_APPLY]
|
|
294
|
+
return delta
|