localstack-core 4.7.1.dev139__py3-none-any.whl → 4.10.1.dev7__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 +1 -0
- localstack/aws/api/cloudwatch/__init__.py +41 -1
- localstack/aws/api/config/__init__.py +4 -0
- localstack/aws/api/core.py +4 -0
- localstack/aws/api/ec2/__init__.py +1113 -56
- 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 +2 -0
- localstack/aws/api/s3/__init__.py +12 -0
- localstack/aws/api/s3control/__init__.py +32 -0
- localstack/aws/api/ssm/__init__.py +2 -0
- localstack/aws/client.py +7 -2
- localstack/aws/forwarder.py +52 -5
- localstack/aws/handlers/analytics.py +1 -1
- localstack/aws/handlers/logging.py +12 -2
- localstack/aws/handlers/metric_handler.py +41 -1
- localstack/aws/handlers/service.py +32 -9
- localstack/aws/protocol/parser.py +440 -21
- localstack/aws/protocol/serializer.py +684 -64
- localstack/aws/protocol/service_router.py +120 -20
- localstack/aws/skeleton.py +4 -2
- localstack/aws/spec-patches.json +58 -0
- localstack/aws/spec.py +33 -13
- localstack/cli/exceptions.py +1 -1
- localstack/cli/localstack.py +4 -4
- localstack/cli/lpm.py +3 -4
- localstack/cli/profiles.py +1 -2
- localstack/config.py +18 -12
- localstack/constants.py +4 -29
- localstack/dev/kubernetes/__main__.py +1 -1
- localstack/dev/run/paths.py +1 -1
- localstack/dns/plugins.py +5 -1
- localstack/dns/server.py +12 -3
- localstack/packages/api.py +9 -8
- localstack/packages/core.py +2 -2
- localstack/packages/plugins.py +0 -8
- localstack/runtime/init.py +1 -1
- localstack/services/apigateway/legacy/provider.py +53 -3
- 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/test_invoke.py +50 -6
- localstack/services/apigateway/next_gen/provider.py +5 -0
- localstack/services/cloudformation/engine/entities.py +12 -1
- localstack/services/cloudformation/engine/v2/change_set_model.py +0 -3
- localstack/services/cloudformation/engine/v2/change_set_model_describer.py +14 -0
- localstack/services/cloudformation/engine/v2/change_set_model_executor.py +13 -15
- localstack/services/cloudformation/engine/v2/change_set_model_preproc.py +118 -24
- localstack/services/cloudformation/engine/v2/change_set_model_transform.py +4 -1
- localstack/services/cloudformation/engine/v2/change_set_model_validator.py +5 -14
- localstack/services/cloudformation/engine/v2/change_set_model_visitor.py +1 -0
- localstack/services/cloudformation/engine/v2/resolving.py +6 -4
- localstack/services/cloudformation/engine/yaml_parser.py +9 -2
- localstack/services/cloudformation/resource_provider.py +5 -1
- localstack/services/cloudformation/resources.py +24149 -0
- localstack/services/cloudformation/v2/entities.py +6 -3
- localstack/services/cloudformation/v2/provider.py +172 -27
- localstack/services/cloudformation/v2/types.py +8 -4
- 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/v2/provider.py +42 -0
- localstack/services/ecr/resource_providers/aws_ecr_repository.py +5 -2
- 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/target.py +17 -9
- 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 +77 -0
- localstack/services/kms/provider.py +14 -5
- localstack/services/lambda_/invocation/internal_sqs_queue.py +5 -9
- localstack/services/lambda_/packages.py +1 -1
- localstack/services/logs/provider.py +1 -1
- 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 +6 -1
- localstack/services/opensearch/versions.py +56 -7
- localstack/services/s3/constants.py +5 -2
- localstack/services/s3/cors.py +4 -4
- localstack/services/s3/notifications.py +1 -1
- localstack/services/s3/presigned_url.py +27 -43
- localstack/services/s3/provider.py +67 -11
- localstack/services/s3/utils.py +42 -11
- localstack/services/ses/provider.py +16 -7
- localstack/services/sns/constants.py +7 -1
- localstack/services/sns/v2/models.py +167 -0
- localstack/services/sns/v2/provider.py +860 -2
- localstack/services/sns/v2/utils.py +130 -0
- localstack/services/sqs/developer_api.py +205 -0
- localstack/services/sqs/models.py +42 -3
- localstack/services/sqs/provider.py +8 -309
- localstack/services/sqs/query_api.py +1 -1
- localstack/services/sqs/utils.py +121 -2
- localstack/services/stepfunctions/asl/jsonata/jsonata.py +1 -1
- localstack/testing/aws/cloudformation_utils.py +1 -1
- localstack/testing/pytest/cloudformation/fixtures.py +3 -3
- localstack/testing/pytest/container.py +4 -5
- localstack/testing/pytest/fixtures.py +20 -19
- localstack/testing/pytest/in_memory_localstack.py +0 -4
- localstack/testing/pytest/marking.py +13 -4
- 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 +5 -0
- localstack/utils/analytics/events.py +2 -2
- localstack/utils/analytics/metadata.py +1 -2
- localstack/utils/analytics/metrics/counter.py +6 -8
- localstack/utils/analytics/publisher.py +1 -2
- localstack/utils/analytics/service_request_aggregator.py +2 -2
- localstack/utils/archives.py +11 -11
- localstack/utils/aws/arns.py +17 -9
- localstack/utils/aws/aws_responses.py +7 -7
- localstack/utils/aws/aws_stack.py +2 -3
- localstack/utils/aws/message_forwarding.py +1 -2
- localstack/utils/aws/request_context.py +4 -5
- localstack/utils/batch_policy.py +3 -3
- localstack/utils/bootstrap.py +7 -7
- localstack/utils/catalog/catalog.py +139 -0
- localstack/utils/catalog/catalog_loader.py +11 -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 +115 -131
- localstack/utils/container_utils/docker_cmd_client.py +42 -42
- localstack/utils/container_utils/docker_sdk_client.py +63 -62
- localstack/utils/diagnose.py +2 -3
- localstack/utils/docker_utils.py +3 -4
- localstack/utils/files.py +31 -7
- localstack/utils/functions.py +3 -2
- localstack/utils/http.py +4 -5
- localstack/utils/json.py +19 -5
- localstack/utils/kinesis/kinesis_connector.py +2 -1
- localstack/utils/net.py +6 -6
- localstack/utils/no_exit_argument_parser.py +2 -2
- localstack/utils/numbers.py +9 -2
- localstack/utils/objects.py +6 -5
- localstack/utils/patch.py +2 -1
- localstack/utils/run.py +10 -9
- localstack/utils/scheduler.py +11 -11
- localstack/utils/server/tcp_proxy.py +2 -2
- localstack/utils/serving.py +2 -3
- localstack/utils/strings.py +10 -11
- localstack/utils/sync.py +126 -1
- localstack/utils/tagging.py +1 -4
- localstack/utils/testutil.py +5 -4
- localstack/utils/threads.py +2 -2
- localstack/utils/time.py +11 -3
- localstack/utils/urls.py +1 -3
- localstack/version.py +2 -2
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev7.dist-info}/METADATA +17 -12
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev7.dist-info}/RECORD +168 -164
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev7.dist-info}/entry_points.txt +4 -2
- localstack_core-4.10.1.dev7.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.dev139.dist-info/plux.json +0 -1
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev7.data}/scripts/localstack +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev7.data}/scripts/localstack-supervisor +0 -0
- {localstack_core-4.7.1.dev139.data → localstack_core-4.10.1.dev7.data}/scripts/localstack.bat +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev7.dist-info}/WHEEL +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev7.dist-info}/licenses/LICENSE.txt +0 -0
- {localstack_core-4.7.1.dev139.dist-info → localstack_core-4.10.1.dev7.dist-info}/top_level.txt +0 -0
|
@@ -30,6 +30,7 @@ from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
|
30
30
|
NodeProperties,
|
|
31
31
|
NodeProperty,
|
|
32
32
|
NodeResource,
|
|
33
|
+
NodeResources,
|
|
33
34
|
NodeTemplate,
|
|
34
35
|
Nothing,
|
|
35
36
|
NothingType,
|
|
@@ -45,6 +46,7 @@ from localstack.services.cloudformation.engine.v2.change_set_model_visitor impor
|
|
|
45
46
|
ChangeSetModelVisitor,
|
|
46
47
|
)
|
|
47
48
|
from localstack.services.cloudformation.engine.v2.resolving import (
|
|
49
|
+
REGEX_DYNAMIC_REF,
|
|
48
50
|
extract_dynamic_reference,
|
|
49
51
|
perform_dynamic_reference_lookup,
|
|
50
52
|
)
|
|
@@ -55,6 +57,7 @@ from localstack.services.cloudformation.stores import (
|
|
|
55
57
|
from localstack.services.cloudformation.v2.entities import ChangeSet
|
|
56
58
|
from localstack.services.cloudformation.v2.types import ResolvedResource
|
|
57
59
|
from localstack.utils.aws.arns import get_partition
|
|
60
|
+
from localstack.utils.numbers import to_number
|
|
58
61
|
from localstack.utils.objects import get_value_from_path
|
|
59
62
|
from localstack.utils.run import to_str
|
|
60
63
|
from localstack.utils.strings import to_bytes
|
|
@@ -224,6 +227,8 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
224
227
|
def process(self) -> None:
|
|
225
228
|
self._setup_runtime_cache()
|
|
226
229
|
node_template = self._change_set.update_model.node_template
|
|
230
|
+
node_conditions = self._change_set.update_model.node_template.conditions
|
|
231
|
+
self.visit(node_conditions)
|
|
227
232
|
self.visit(node_template)
|
|
228
233
|
self._save_runtime_cache()
|
|
229
234
|
|
|
@@ -273,10 +278,10 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
273
278
|
property_value: Any | None = get_value_from_path(properties, property_name)
|
|
274
279
|
|
|
275
280
|
if property_value:
|
|
276
|
-
if not isinstance(property_value, (str, list)):
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
#
|
|
281
|
+
if not isinstance(property_value, (str, list, dict)):
|
|
282
|
+
# Str: Standard expected type. TODO validate bools and numbers
|
|
283
|
+
# List: Multiple resource types can return a list of values e.g. AWS::EC2::VPC.
|
|
284
|
+
# Dict: Custom resources in CloudFormation can return arbitrary data structures.
|
|
280
285
|
raise RuntimeError(
|
|
281
286
|
f"Accessing property '{property_name}' from '{resource_logical_id}' resulted in a non-string value nor list"
|
|
282
287
|
)
|
|
@@ -432,6 +437,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
432
437
|
def _perform_dynamic_replacements(self, value: _T) -> _T:
|
|
433
438
|
if not isinstance(value, str):
|
|
434
439
|
return value
|
|
440
|
+
|
|
435
441
|
if dynamic_ref := extract_dynamic_reference(value):
|
|
436
442
|
new_value = perform_dynamic_reference_lookup(
|
|
437
443
|
reference=dynamic_ref,
|
|
@@ -439,7 +445,11 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
439
445
|
region_name=self._change_set.region_name,
|
|
440
446
|
)
|
|
441
447
|
if new_value:
|
|
442
|
-
|
|
448
|
+
# We need to use a function here, to avoid backslash processing by regex.
|
|
449
|
+
# From the regex sub documentation:
|
|
450
|
+
# repl can be a string or a function; if it is a string, any backslash escapes in it are processed.
|
|
451
|
+
# Using a function, we can avoid this processing.
|
|
452
|
+
return REGEX_DYNAMIC_REF.sub(lambda _: new_value, value)
|
|
443
453
|
|
|
444
454
|
return value
|
|
445
455
|
|
|
@@ -559,13 +569,49 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
559
569
|
arguments_list = arguments.split(".")
|
|
560
570
|
else:
|
|
561
571
|
arguments_list = arguments
|
|
572
|
+
|
|
573
|
+
if len(arguments_list) < 2:
|
|
574
|
+
raise ValidationError(
|
|
575
|
+
"Template error: every Fn::GetAtt object requires two non-empty parameters, the resource name and the resource attribute"
|
|
576
|
+
)
|
|
577
|
+
|
|
562
578
|
logical_name_of_resource = arguments_list[0]
|
|
563
|
-
attribute_name = arguments_list[1]
|
|
579
|
+
attribute_name = ".".join(arguments_list[1:])
|
|
564
580
|
|
|
565
581
|
node_resource = self._get_node_resource_for(
|
|
566
582
|
resource_name=logical_name_of_resource,
|
|
567
583
|
node_template=self._change_set.update_model.node_template,
|
|
568
584
|
)
|
|
585
|
+
|
|
586
|
+
if not is_nothing(node_resource.condition_reference):
|
|
587
|
+
condition = self._get_node_condition_if_exists(node_resource.condition_reference.value)
|
|
588
|
+
evaluation_result = self._resolve_condition(condition.name)
|
|
589
|
+
|
|
590
|
+
if select_before and not evaluation_result.before:
|
|
591
|
+
raise ValidationError(
|
|
592
|
+
f"Template format error: Unresolved resource dependencies [{logical_name_of_resource}] in the Resources block of the template"
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
if not select_before and not evaluation_result.after:
|
|
596
|
+
raise ValidationError(
|
|
597
|
+
f"Template format error: Unresolved resource dependencies [{logical_name_of_resource}] in the Resources block of the template"
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
# Custom Resources can mutate their definition
|
|
601
|
+
# So the preproc should search first in the resource values and then check the template
|
|
602
|
+
if select_before:
|
|
603
|
+
value = self._before_deployed_property_value_of(
|
|
604
|
+
resource_logical_id=logical_name_of_resource,
|
|
605
|
+
property_name=attribute_name,
|
|
606
|
+
)
|
|
607
|
+
else:
|
|
608
|
+
value = self._after_deployed_property_value_of(
|
|
609
|
+
resource_logical_id=logical_name_of_resource,
|
|
610
|
+
property_name=attribute_name,
|
|
611
|
+
)
|
|
612
|
+
if value is not None:
|
|
613
|
+
return value
|
|
614
|
+
|
|
569
615
|
node_property: NodeProperty | None = self._get_node_property_for(
|
|
570
616
|
property_name=attribute_name, node_resource=node_resource
|
|
571
617
|
)
|
|
@@ -573,19 +619,7 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
573
619
|
# The property is statically defined in the template and its value can be computed.
|
|
574
620
|
property_delta = self.visit(node_property)
|
|
575
621
|
value = property_delta.before if select_before else property_delta.after
|
|
576
|
-
|
|
577
|
-
# The property is not statically defined and must therefore be available in
|
|
578
|
-
# the properties deployed set.
|
|
579
|
-
if select_before:
|
|
580
|
-
value = self._before_deployed_property_value_of(
|
|
581
|
-
resource_logical_id=logical_name_of_resource,
|
|
582
|
-
property_name=attribute_name,
|
|
583
|
-
)
|
|
584
|
-
else:
|
|
585
|
-
value = self._after_deployed_property_value_of(
|
|
586
|
-
resource_logical_id=logical_name_of_resource,
|
|
587
|
-
property_name=attribute_name,
|
|
588
|
-
)
|
|
622
|
+
|
|
589
623
|
return value
|
|
590
624
|
|
|
591
625
|
def visit_node_intrinsic_function_fn_get_att(
|
|
@@ -614,6 +648,12 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
614
648
|
return args[0] == args[1]
|
|
615
649
|
|
|
616
650
|
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
|
651
|
+
|
|
652
|
+
if isinstance(arguments_delta.after, list) and len(arguments_delta.after) != 2:
|
|
653
|
+
raise ValidationError(
|
|
654
|
+
"Template error: every Fn::Equals object requires a list of 2 string parameters."
|
|
655
|
+
)
|
|
656
|
+
|
|
617
657
|
delta = self._cached_apply(
|
|
618
658
|
scope=node_intrinsic_function.scope,
|
|
619
659
|
arguments_delta=arguments_delta,
|
|
@@ -843,13 +883,27 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
843
883
|
):
|
|
844
884
|
# TODO: add further support for schema validation
|
|
845
885
|
def _compute_fn_select(args: list[Any]) -> Any:
|
|
846
|
-
values
|
|
886
|
+
values = args[1]
|
|
887
|
+
# defer evaluation if the selection list contains unresolved elements (e.g., unresolved intrinsics)
|
|
888
|
+
if isinstance(values, list) and not all(isinstance(value, str) for value in values):
|
|
889
|
+
raise RuntimeError("Fn::Select list contains unresolved elements")
|
|
890
|
+
|
|
847
891
|
if not isinstance(values, list) or not values:
|
|
848
|
-
raise
|
|
892
|
+
raise ValidationError(
|
|
893
|
+
"Template error: Fn::Select requires a list argument with two elements: an integer index and a list"
|
|
894
|
+
)
|
|
895
|
+
try:
|
|
896
|
+
index: int = int(args[0])
|
|
897
|
+
except ValueError as e:
|
|
898
|
+
raise ValidationError(
|
|
899
|
+
"Template error: Fn::Select requires a list argument with two elements: an integer index and a list"
|
|
900
|
+
) from e
|
|
901
|
+
|
|
849
902
|
values_len = len(values)
|
|
850
|
-
index
|
|
851
|
-
|
|
852
|
-
|
|
903
|
+
if index < 0 or index >= values_len:
|
|
904
|
+
raise ValidationError(
|
|
905
|
+
"Template error: Fn::Select requires a list argument with two elements: an integer index and a list"
|
|
906
|
+
)
|
|
853
907
|
selection = values[index]
|
|
854
908
|
return selection
|
|
855
909
|
|
|
@@ -876,6 +930,17 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
876
930
|
return split_string
|
|
877
931
|
|
|
878
932
|
arguments_delta = self.visit(node_intrinsic_function.arguments)
|
|
933
|
+
|
|
934
|
+
if not (
|
|
935
|
+
is_nothing(arguments_delta.after)
|
|
936
|
+
or isinstance(arguments_delta.after, list)
|
|
937
|
+
and len(arguments_delta.after) == 2
|
|
938
|
+
):
|
|
939
|
+
raise ValidationError(
|
|
940
|
+
"Template error: every Fn::Split object requires two parameters, "
|
|
941
|
+
"(1) a string delimiter and (2) a string to be split or a function that returns a string to be split."
|
|
942
|
+
)
|
|
943
|
+
|
|
879
944
|
delta = self._cached_apply(
|
|
880
945
|
scope=node_intrinsic_function.scope,
|
|
881
946
|
arguments_delta=arguments_delta,
|
|
@@ -975,6 +1040,10 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
975
1040
|
return PreprocEntityDelta(before=before_parameters, after=after_parameters)
|
|
976
1041
|
|
|
977
1042
|
def visit_node_parameter(self, node_parameter: NodeParameter) -> PreprocEntityDelta:
|
|
1043
|
+
if not VALID_LOGICAL_RESOURCE_ID_RE.match(node_parameter.name):
|
|
1044
|
+
raise ValidationError(
|
|
1045
|
+
f"Template format error: Parameter name {node_parameter.name} is non alphanumeric."
|
|
1046
|
+
)
|
|
978
1047
|
dynamic_value = node_parameter.dynamic_value
|
|
979
1048
|
dynamic_delta = self.visit(dynamic_value)
|
|
980
1049
|
|
|
@@ -990,6 +1059,9 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
990
1059
|
match type_:
|
|
991
1060
|
case "List<String>" | "CommaDelimitedList":
|
|
992
1061
|
return [item.strip() for item in value.split(",")]
|
|
1062
|
+
case "Number":
|
|
1063
|
+
# TODO: validate the parameter type at template parse time (or whatever is in parity with AWS) so we know this cannot fail
|
|
1064
|
+
return to_number(value)
|
|
993
1065
|
return value
|
|
994
1066
|
|
|
995
1067
|
if not is_nothing(after):
|
|
@@ -1131,6 +1203,20 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
1131
1203
|
after = after_delta.after
|
|
1132
1204
|
return PreprocEntityDelta(before=before, after=after)
|
|
1133
1205
|
|
|
1206
|
+
def visit_node_resources(self, node_resources: NodeResources):
|
|
1207
|
+
"""
|
|
1208
|
+
Skip resources where they conditionally evaluate to False
|
|
1209
|
+
"""
|
|
1210
|
+
for node_resource in node_resources.resources:
|
|
1211
|
+
if not is_nothing(node_resource.condition_reference):
|
|
1212
|
+
condition_delta = self._resolve_resource_condition_reference(
|
|
1213
|
+
node_resource.condition_reference
|
|
1214
|
+
)
|
|
1215
|
+
condition_after = condition_delta.after
|
|
1216
|
+
if condition_after is False:
|
|
1217
|
+
continue
|
|
1218
|
+
self.visit(node_resource)
|
|
1219
|
+
|
|
1134
1220
|
def visit_node_resource(
|
|
1135
1221
|
self, node_resource: NodeResource
|
|
1136
1222
|
) -> PreprocEntityDelta[PreprocResource, PreprocResource]:
|
|
@@ -1241,6 +1327,14 @@ class ChangeSetModelPreproc(ChangeSetModelVisitor):
|
|
|
1241
1327
|
before: list[PreprocOutput] = []
|
|
1242
1328
|
after: list[PreprocOutput] = []
|
|
1243
1329
|
for node_output in node_outputs.outputs:
|
|
1330
|
+
if not is_nothing(node_output.condition_reference):
|
|
1331
|
+
condition_delta = self._resolve_resource_condition_reference(
|
|
1332
|
+
node_output.condition_reference
|
|
1333
|
+
)
|
|
1334
|
+
condition_after = condition_delta.after
|
|
1335
|
+
if condition_after is False:
|
|
1336
|
+
continue
|
|
1337
|
+
|
|
1244
1338
|
output_delta: PreprocEntityDelta[PreprocOutput, PreprocOutput] = self.visit(node_output)
|
|
1245
1339
|
output_before = output_delta.before
|
|
1246
1340
|
output_after = output_delta.after
|
|
@@ -501,7 +501,10 @@ class ChangeSetModelTransform(ChangeSetModelPreproc):
|
|
|
501
501
|
def visit_node_intrinsic_function_fn_get_att(
|
|
502
502
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
503
503
|
) -> PreprocEntityDelta:
|
|
504
|
-
|
|
504
|
+
try:
|
|
505
|
+
return super().visit_node_intrinsic_function_fn_get_att(node_intrinsic_function)
|
|
506
|
+
except RuntimeError:
|
|
507
|
+
return self.visit(node_intrinsic_function.arguments)
|
|
505
508
|
|
|
506
509
|
def visit_node_intrinsic_function_fn_sub(
|
|
507
510
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
@@ -4,7 +4,6 @@ from typing import Any
|
|
|
4
4
|
from botocore.exceptions import ParamValidationError
|
|
5
5
|
|
|
6
6
|
from localstack.services.cloudformation.engine.v2.change_set_model import (
|
|
7
|
-
Maybe,
|
|
8
7
|
NodeIntrinsicFunction,
|
|
9
8
|
NodeProperty,
|
|
10
9
|
NodeResource,
|
|
@@ -27,23 +26,15 @@ class ChangeSetModelValidator(ChangeSetModelPreproc):
|
|
|
27
26
|
def visit_node_template(self, node_template: NodeTemplate):
|
|
28
27
|
self.visit(node_template.mappings)
|
|
29
28
|
self.visit(node_template.resources)
|
|
29
|
+
self.visit(node_template.parameters)
|
|
30
30
|
|
|
31
31
|
def visit_node_intrinsic_function_fn_get_att(
|
|
32
32
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
33
33
|
) -> PreprocEntityDelta:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
before = self._before_cache.get(node_intrinsic_function.scope, Nothing)
|
|
39
|
-
if is_nothing(before) and not is_nothing(before_arguments):
|
|
40
|
-
before = ".".join(before_arguments)
|
|
41
|
-
|
|
42
|
-
after = self._after_cache.get(node_intrinsic_function.scope, Nothing)
|
|
43
|
-
if is_nothing(after) and not is_nothing(after_arguments):
|
|
44
|
-
after = ".".join(after_arguments)
|
|
45
|
-
|
|
46
|
-
return PreprocEntityDelta(before=before, after=after)
|
|
34
|
+
try:
|
|
35
|
+
return super().visit_node_intrinsic_function_fn_get_att(node_intrinsic_function)
|
|
36
|
+
except RuntimeError:
|
|
37
|
+
return self.visit(node_intrinsic_function.arguments)
|
|
47
38
|
|
|
48
39
|
def visit_node_intrinsic_function_fn_sub(
|
|
49
40
|
self, node_intrinsic_function: NodeIntrinsicFunction
|
|
@@ -54,6 +54,7 @@ class ChangeSetModelVisitor(abc.ABC):
|
|
|
54
54
|
# entities (parameters, mappings, conditions, etc.). Then compute the output fields; computing
|
|
55
55
|
# only the output fields would only result in the deployment logic of the referenced outputs
|
|
56
56
|
# being evaluated, hence enforce the visiting of all the resources first.
|
|
57
|
+
self.visit(node_template.conditions)
|
|
57
58
|
self.visit(node_template.resources)
|
|
58
59
|
self.visit(node_template.outputs)
|
|
59
60
|
|
|
@@ -10,7 +10,9 @@ from localstack.aws.connect import connect_to
|
|
|
10
10
|
|
|
11
11
|
LOG = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# CloudFormation allows using dynamic references in `Fn::Sub` expressions, so we must make sure
|
|
14
|
+
# we don't capture the parameter usage by excluding ${} characters
|
|
15
|
+
REGEX_DYNAMIC_REF = re.compile(r"{{resolve:([^:]+):([^${}]+)}}")
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
@dataclass
|
|
@@ -21,7 +23,7 @@ class DynamicReference:
|
|
|
21
23
|
|
|
22
24
|
def extract_dynamic_reference(value: Any) -> DynamicReference | None:
|
|
23
25
|
if isinstance(value, str):
|
|
24
|
-
if dynamic_ref_match := REGEX_DYNAMIC_REF.
|
|
26
|
+
if dynamic_ref_match := REGEX_DYNAMIC_REF.search(value):
|
|
25
27
|
return DynamicReference(dynamic_ref_match[1], dynamic_ref_match[2])
|
|
26
28
|
return None
|
|
27
29
|
|
|
@@ -90,9 +92,9 @@ def perform_dynamic_reference_lookup(
|
|
|
90
92
|
raise RuntimeError(
|
|
91
93
|
f"JSON value for {reference.service_name}.{reference.reference_key} not present"
|
|
92
94
|
)
|
|
93
|
-
return json_secret[json_key]
|
|
95
|
+
return str(json_secret[json_key])
|
|
94
96
|
else:
|
|
95
|
-
return secret_value
|
|
97
|
+
return str(secret_value)
|
|
96
98
|
|
|
97
99
|
LOG.warning(
|
|
98
100
|
"Unsupported service for dynamic parameter: service_name=%s", reference.service_name
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import yaml
|
|
2
2
|
|
|
3
|
+
from localstack.services.cloudformation.engine.validations import ValidationError
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
def construct_raw(_, node):
|
|
5
7
|
return node.value
|
|
@@ -60,5 +62,10 @@ customloader = NoDatesSafeLoader
|
|
|
60
62
|
yaml.add_multi_constructor("!", shorthand_constructor, customloader)
|
|
61
63
|
|
|
62
64
|
|
|
63
|
-
def parse_yaml(input_data: str):
|
|
64
|
-
|
|
65
|
+
def parse_yaml(input_data: str) -> dict:
|
|
66
|
+
parsed = yaml.load(input_data, Loader=customloader)
|
|
67
|
+
|
|
68
|
+
if not isinstance(parsed, dict):
|
|
69
|
+
raise ValidationError("Template format error: unsupported structure.")
|
|
70
|
+
|
|
71
|
+
return parsed
|
|
@@ -518,10 +518,14 @@ class ResourceProviderExecutor:
|
|
|
518
518
|
try:
|
|
519
519
|
return resource_provider.update(request)
|
|
520
520
|
except NotImplementedError:
|
|
521
|
+
feature_request_url = "https://github.com/localstack/localstack/issues/new?template=feature-request.yml"
|
|
521
522
|
LOG.warning(
|
|
522
|
-
'Unable to update resource type "%s", id "%s"'
|
|
523
|
+
'Unable to update resource type "%s", id "%s", '
|
|
524
|
+
"the update operation is not implemented for this resource. "
|
|
525
|
+
"Please consider submitting a feature request at this URL: %s",
|
|
523
526
|
request.resource_type,
|
|
524
527
|
request.logical_resource_id,
|
|
528
|
+
feature_request_url,
|
|
525
529
|
)
|
|
526
530
|
if request.previous_state is None:
|
|
527
531
|
# this is an issue with our update detection. We should never be in this state.
|